@lobehub/chat 1.77.16 → 1.77.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/changelog/v1.json +18 -0
  3. package/contributing/Basic/Architecture.md +1 -1
  4. package/contributing/Basic/Architecture.zh-CN.md +1 -1
  5. package/contributing/Basic/Chat-API.md +326 -108
  6. package/contributing/Basic/Chat-API.zh-CN.md +313 -133
  7. package/contributing/Basic/Contributing-Guidelines.md +7 -4
  8. package/contributing/Basic/Contributing-Guidelines.zh-CN.md +7 -6
  9. package/contributing/Home.md +5 -5
  10. package/contributing/State-Management/State-Management-Intro.md +1 -1
  11. package/contributing/State-Management/State-Management-Intro.zh-CN.md +1 -1
  12. package/docker-compose/local/docker-compose.yml +2 -1
  13. package/locales/ar/components.json +4 -0
  14. package/locales/ar/modelProvider.json +1 -0
  15. package/locales/ar/models.json +8 -5
  16. package/locales/ar/tool.json +21 -1
  17. package/locales/bg-BG/components.json +4 -0
  18. package/locales/bg-BG/modelProvider.json +1 -0
  19. package/locales/bg-BG/models.json +8 -5
  20. package/locales/bg-BG/tool.json +21 -1
  21. package/locales/de-DE/components.json +4 -0
  22. package/locales/de-DE/modelProvider.json +1 -0
  23. package/locales/de-DE/models.json +8 -5
  24. package/locales/de-DE/tool.json +21 -1
  25. package/locales/en-US/components.json +4 -0
  26. package/locales/en-US/modelProvider.json +1 -0
  27. package/locales/en-US/models.json +8 -5
  28. package/locales/en-US/tool.json +21 -1
  29. package/locales/es-ES/components.json +4 -0
  30. package/locales/es-ES/modelProvider.json +1 -0
  31. package/locales/es-ES/models.json +7 -4
  32. package/locales/es-ES/tool.json +21 -1
  33. package/locales/fa-IR/components.json +4 -0
  34. package/locales/fa-IR/modelProvider.json +1 -0
  35. package/locales/fa-IR/models.json +7 -4
  36. package/locales/fa-IR/tool.json +21 -1
  37. package/locales/fr-FR/components.json +4 -0
  38. package/locales/fr-FR/modelProvider.json +1 -0
  39. package/locales/fr-FR/models.json +8 -5
  40. package/locales/fr-FR/tool.json +21 -1
  41. package/locales/it-IT/components.json +4 -0
  42. package/locales/it-IT/modelProvider.json +1 -0
  43. package/locales/it-IT/models.json +7 -4
  44. package/locales/it-IT/tool.json +21 -1
  45. package/locales/ja-JP/components.json +4 -0
  46. package/locales/ja-JP/modelProvider.json +1 -0
  47. package/locales/ja-JP/models.json +8 -5
  48. package/locales/ja-JP/tool.json +21 -1
  49. package/locales/ko-KR/components.json +4 -0
  50. package/locales/ko-KR/modelProvider.json +1 -0
  51. package/locales/ko-KR/models.json +8 -5
  52. package/locales/ko-KR/tool.json +21 -1
  53. package/locales/nl-NL/components.json +4 -0
  54. package/locales/nl-NL/modelProvider.json +1 -0
  55. package/locales/nl-NL/models.json +8 -5
  56. package/locales/nl-NL/tool.json +21 -1
  57. package/locales/pl-PL/components.json +4 -0
  58. package/locales/pl-PL/modelProvider.json +1 -0
  59. package/locales/pl-PL/models.json +8 -5
  60. package/locales/pl-PL/tool.json +21 -1
  61. package/locales/pt-BR/components.json +4 -0
  62. package/locales/pt-BR/modelProvider.json +1 -0
  63. package/locales/pt-BR/models.json +7 -4
  64. package/locales/pt-BR/tool.json +21 -1
  65. package/locales/ru-RU/components.json +4 -0
  66. package/locales/ru-RU/modelProvider.json +1 -0
  67. package/locales/ru-RU/models.json +7 -4
  68. package/locales/ru-RU/tool.json +21 -1
  69. package/locales/tr-TR/components.json +4 -0
  70. package/locales/tr-TR/modelProvider.json +1 -0
  71. package/locales/tr-TR/models.json +8 -5
  72. package/locales/tr-TR/tool.json +21 -1
  73. package/locales/vi-VN/components.json +4 -0
  74. package/locales/vi-VN/modelProvider.json +1 -0
  75. package/locales/vi-VN/models.json +8 -5
  76. package/locales/vi-VN/tool.json +21 -1
  77. package/locales/zh-CN/components.json +4 -0
  78. package/locales/zh-CN/modelProvider.json +1 -0
  79. package/locales/zh-CN/models.json +9 -6
  80. package/locales/zh-CN/tool.json +30 -1
  81. package/locales/zh-TW/components.json +4 -0
  82. package/locales/zh-TW/modelProvider.json +1 -0
  83. package/locales/zh-TW/models.json +7 -4
  84. package/locales/zh-TW/tool.json +21 -1
  85. package/package.json +1 -1
  86. package/src/app/(backend)/webapi/models/[provider]/pull/route.ts +34 -0
  87. package/src/app/(backend)/webapi/{chat/models → models}/[provider]/route.ts +1 -2
  88. package/src/app/[variants]/(main)/settings/llm/ProviderList/Ollama/index.tsx +0 -7
  89. package/src/app/[variants]/(main)/settings/provider/(detail)/ollama/CheckError.tsx +1 -1
  90. package/src/components/FormAction/index.tsx +1 -1
  91. package/src/database/models/__tests__/aiProvider.test.ts +100 -0
  92. package/src/database/models/aiProvider.ts +11 -1
  93. package/src/features/Conversation/Error/OllamaBizError/InvalidOllamaModel.tsx +43 -0
  94. package/src/features/Conversation/Error/OllamaDesktopSetupGuide/index.tsx +61 -0
  95. package/src/features/Conversation/Error/index.tsx +7 -0
  96. package/src/features/DevPanel/SystemInspector/ServerConfig.tsx +18 -2
  97. package/src/features/DevPanel/SystemInspector/index.tsx +25 -6
  98. package/src/features/OllamaModelDownloader/index.tsx +149 -0
  99. package/src/libs/agent-runtime/AgentRuntime.ts +6 -0
  100. package/src/libs/agent-runtime/BaseAI.ts +7 -0
  101. package/src/libs/agent-runtime/ollama/index.ts +84 -2
  102. package/src/libs/agent-runtime/openrouter/__snapshots__/index.test.ts.snap +24 -3263
  103. package/src/libs/agent-runtime/openrouter/fixtures/frontendModels.json +25 -0
  104. package/src/libs/agent-runtime/openrouter/fixtures/models.json +0 -3353
  105. package/src/libs/agent-runtime/openrouter/index.test.ts +56 -1
  106. package/src/libs/agent-runtime/openrouter/index.ts +9 -4
  107. package/src/libs/agent-runtime/types/index.ts +1 -0
  108. package/src/libs/agent-runtime/types/model.ts +44 -0
  109. package/src/libs/agent-runtime/utils/streams/index.ts +1 -0
  110. package/src/libs/agent-runtime/utils/streams/model.ts +110 -0
  111. package/src/locales/default/components.ts +4 -0
  112. package/src/locales/default/modelProvider.ts +1 -0
  113. package/src/locales/default/tool.ts +30 -1
  114. package/src/server/modules/SearXNG.ts +10 -2
  115. package/src/server/routers/tools/__test__/search.test.ts +3 -1
  116. package/src/server/routers/tools/search.ts +10 -2
  117. package/src/services/__tests__/models.test.ts +21 -0
  118. package/src/services/_url.ts +4 -1
  119. package/src/services/chat.ts +1 -1
  120. package/src/services/models.ts +153 -7
  121. package/src/services/search.ts +2 -2
  122. package/src/store/aiInfra/slices/aiModel/action.ts +1 -1
  123. package/src/store/aiInfra/slices/aiProvider/action.ts +2 -1
  124. package/src/store/chat/slices/builtinTool/actions/searXNG.test.ts +28 -8
  125. package/src/store/chat/slices/builtinTool/actions/searXNG.ts +22 -5
  126. package/src/store/user/slices/modelList/action.test.ts +2 -2
  127. package/src/store/user/slices/modelList/action.ts +1 -1
  128. package/src/tools/web-browsing/Portal/Search/index.tsx +1 -1
  129. package/src/tools/web-browsing/Render/Search/SearchQuery/SearchView.tsx +1 -1
  130. package/src/tools/web-browsing/Render/Search/SearchQuery/index.tsx +1 -1
  131. package/src/tools/web-browsing/Render/Search/SearchResult/index.tsx +1 -1
  132. package/src/tools/web-browsing/components/CategoryAvatar.tsx +27 -0
  133. package/src/tools/web-browsing/components/SearchBar.tsx +84 -4
  134. package/src/tools/web-browsing/const.ts +26 -0
  135. package/src/tools/web-browsing/index.ts +58 -28
  136. package/src/tools/web-browsing/systemRole.ts +62 -1
  137. package/src/types/tool/search.ts +10 -1
  138. package/src/app/[variants]/(main)/settings/llm/ProviderList/Ollama/Checker.tsx +0 -73
  139. package/src/app/[variants]/(main)/settings/provider/(detail)/ollama/OllamaModelDownloader/index.tsx +0 -127
  140. package/src/features/Conversation/Error/OllamaBizError/InvalidOllamaModel/index.tsx +0 -154
  141. package/src/features/Conversation/Error/OllamaBizError/InvalidOllamaModel/useDownloadMonitor.ts +0 -29
  142. package/src/helpers/url.ts +0 -17
  143. package/src/services/__tests__/ollama.test.ts +0 -28
  144. package/src/services/ollama.ts +0 -83
  145. /package/src/{app/[variants]/(main)/settings/provider/(detail)/ollama → features}/OllamaModelDownloader/useDownloadMonitor.ts +0 -0
@@ -72,8 +72,10 @@ describe('searXNG actions', () => {
72
72
 
73
73
  const messageId = 'test-message-id';
74
74
  const query: SearchQuery = {
75
+ optionalParams: {
76
+ searchEngines: ['google'],
77
+ },
75
78
  query: 'test query',
76
- searchEngines: ['google'],
77
79
  };
78
80
 
79
81
  await act(async () => {
@@ -82,13 +84,15 @@ describe('searXNG actions', () => {
82
84
 
83
85
  const expectedContent: SearchContent[] = [
84
86
  {
85
- content: 'Test Content',
86
87
  title: 'Test Result',
87
88
  url: 'https://test.com',
89
+ content: 'Test Content',
88
90
  },
89
91
  ];
90
92
 
91
- expect(searchService.search).toHaveBeenCalledWith('test query', ['google']);
93
+ expect( searchService.search ).toHaveBeenCalledWith('test query', {
94
+ searchEngines: [ 'google' ]
95
+ });
92
96
  expect(result.current.searchLoading[messageId]).toBe(false);
93
97
  expect(result.current.internal_updateMessageContent).toHaveBeenCalledWith(
94
98
  messageId,
@@ -133,6 +137,7 @@ describe('searXNG actions', () => {
133
137
  };
134
138
 
135
139
  (searchService.search as Mock)
140
+ .mockResolvedValueOnce(emptyResponse)
136
141
  .mockResolvedValueOnce(emptyResponse)
137
142
  .mockResolvedValueOnce(retryResponse);
138
143
 
@@ -141,20 +146,35 @@ describe('searXNG actions', () => {
141
146
 
142
147
  const messageId = 'test-message-id';
143
148
  const query: SearchQuery = {
149
+ optionalParams: {
150
+ searchEngines: ['custom-engine'],
151
+ searchTimeRange: 'year',
152
+ },
144
153
  query: 'test query',
145
- searchEngines: ['custom-engine'],
146
154
  };
147
155
 
148
156
  await act(async () => {
149
157
  await searchWithSearXNG(messageId, query);
150
158
  });
151
159
 
152
- expect(searchService.search).toHaveBeenCalledTimes(2);
153
- expect(searchService.search).toHaveBeenNthCalledWith(1, 'test query', ['custom-engine']);
154
- expect(searchService.search).toHaveBeenNthCalledWith(2, 'test query');
160
+ expect(searchService.search).toHaveBeenCalledTimes(3);
161
+ expect(searchService.search).toHaveBeenNthCalledWith(1, "test query", {
162
+ "searchEngines": [ "custom-engine" ],
163
+ "searchTimeRange": "year",
164
+ });
165
+ expect(searchService.search).toHaveBeenNthCalledWith(2, "test query", {
166
+ "searchTimeRange": "year",
167
+ });
168
+ expect(result.current.updatePluginArguments).toHaveBeenCalledWith(messageId, {
169
+ optionalParams: {
170
+ "searchTimeRange": "year",
171
+ },
172
+ query: 'test query',
173
+ });
174
+ expect(searchService.search).toHaveBeenNthCalledWith(3, "test query");
155
175
  expect(result.current.updatePluginArguments).toHaveBeenCalledWith(messageId, {
176
+ optionalParams: undefined,
156
177
  query: 'test query',
157
- searchEngines: undefined,
158
178
  });
159
179
  });
160
180
 
@@ -142,12 +142,26 @@ export const searchSlice: StateCreator<
142
142
  get().toggleSearchLoading(id, true);
143
143
  let data: SearchResponse | undefined;
144
144
  try {
145
- data = await searchService.search(params.query, params.searchEngines);
145
+ // 首次查询
146
+ data = await searchService.search(params.query, params.optionalParams);
147
+
148
+ // 如果没有搜索到结果,则执行第一次重试(移除搜索引擎限制)
149
+ if (data?.results.length === 0 && params.optionalParams?.searchEngines && params.optionalParams?.searchEngines?.length > 0) {
150
+ const paramsExcludeSearchEngines = {
151
+ ...params,
152
+ optionalParams: {
153
+ ...params.optionalParams,
154
+ searchEngines: undefined
155
+ }
156
+ };
157
+ data = await searchService.search(params.query, paramsExcludeSearchEngines.optionalParams);
158
+ get().updatePluginArguments(id, paramsExcludeSearchEngines);
159
+ }
146
160
 
147
- // 如果没有搜索到结果,那么尝试使用默认的搜索引擎再搜一次
148
- if (data?.results.length === 0 && params.searchEngines && params.searchEngines?.length > 0) {
161
+ // 如果仍然没有搜索到结果,则执行第二次重试(移除所有限制)
162
+ if (data?.results.length === 0) {
149
163
  data = await searchService.search(params.query);
150
- get().updatePluginArguments(id, { ...params, searchEngines: undefined });
164
+ get().updatePluginArguments(id, { ...params, optionalParams: undefined });
151
165
  }
152
166
 
153
167
  await get().updatePluginState(id, data);
@@ -175,9 +189,12 @@ export const searchSlice: StateCreator<
175
189
 
176
190
  // add 15 search results to message content
177
191
  const searchContent: SearchContent[] = data.results.slice(0, 15).map((item) => ({
178
- content: item.content,
179
192
  title: item.title,
180
193
  url: item.url,
194
+ ...(item.content && { content: item.content }),
195
+ ...(item.publishedDate && { publishedDate: item.publishedDate }),
196
+ ...(item.img_src && { img_src: item.img_src }),
197
+ ...(item.thumbnail && { thumbnail: item.thumbnail }),
181
198
  }));
182
199
 
183
200
  await get().internal_updateMessageContent(id, JSON.stringify(searchContent));
@@ -328,7 +328,7 @@ describe('LLMSettingsSliceAction', () => {
328
328
 
329
329
  const spyOn = vi.spyOn(result.current, 'refreshDefaultModelProviderList');
330
330
 
331
- vi.spyOn(modelsService, 'getChatModels').mockResolvedValueOnce([]);
331
+ vi.spyOn(modelsService, 'getModels').mockResolvedValueOnce([]);
332
332
 
333
333
  renderHook(() => result.current.useFetchProviderModelList(provider, enabledAutoFetch));
334
334
 
@@ -347,7 +347,7 @@ describe('LLMSettingsSliceAction', () => {
347
347
 
348
348
  const spyOn = vi.spyOn(result.current, 'refreshDefaultModelProviderList');
349
349
 
350
- vi.spyOn(modelsService, 'getChatModels').mockResolvedValueOnce([]);
350
+ vi.spyOn(modelsService, 'getModels').mockResolvedValueOnce([]);
351
351
 
352
352
  renderHook(() => result.current.useFetchProviderModelList(provider, enabledAutoFetch));
353
353
 
@@ -202,7 +202,7 @@ export const createModelListSlice: StateCreator<
202
202
  async ([p]) => {
203
203
  const { modelsService } = await import('@/services/models');
204
204
 
205
- return modelsService.getChatModels(p);
205
+ return modelsService.getModels(p);
206
206
  },
207
207
  {
208
208
  onSuccess: async (data) => {
@@ -19,7 +19,7 @@ interface InspectorUIProps {
19
19
 
20
20
  const Inspector = memo<InspectorUIProps>(({ query: args, messageId, response }) => {
21
21
  const engines = uniq((response.results || []).map((result) => result.engine));
22
- const defaultEngines = engines.length > 0 ? engines : args.searchEngines || [];
22
+ const defaultEngines = engines.length > 0 ? engines : args.optionalParams?.searchEngines || [];
23
23
  const loading = useChatStore(chatToolSelectors.isSearXNGSearching(messageId));
24
24
 
25
25
  if (loading) {
@@ -66,7 +66,7 @@ const SearchBar = memo<SearchBarProps>(
66
66
  </Flexbox>
67
67
 
68
68
  <Flexbox align={'center'} horizontal>
69
- <div className={styles.font}>{t('search.searchEngine')}</div>
69
+ <div className={styles.font}>{t('search.searchEngine.title')}</div>
70
70
  {searching ? (
71
71
  <Skeleton.Button active size={'small'} />
72
72
  ) : (
@@ -29,7 +29,7 @@ const SearchQueryView = memo<SearchQueryViewProps>(
29
29
  const { t } = useTranslation('common');
30
30
 
31
31
  const engines = uniq(searchResults.map((result) => result.engine));
32
- const defaultEngines = engines.length > 0 ? engines : args.searchEngines || [];
32
+ const defaultEngines = engines.length > 0 ? engines : args.optionalParams?.searchEngines || [];
33
33
 
34
34
  return !pluginState ? (
35
35
  <Flexbox align={'center'} distribution={'space-between'} height={32} horizontal>
@@ -32,7 +32,7 @@ const SearchResult = memo<SearchResultProps>(
32
32
  const { t } = useTranslation(['tool', 'common']);
33
33
 
34
34
  const engines = uniq(searchResults.map((result) => result.engine));
35
- const defaultEngines = engines.length > 0 ? engines : args.searchEngines || [];
35
+ const defaultEngines = engines.length > 0 ? engines : args.optionalParams?.searchEngines || [];
36
36
  const isMobile = useIsMobile();
37
37
 
38
38
  if (loading || !pluginState)
@@ -0,0 +1,27 @@
1
+ import { Avatar } from 'antd';
2
+ import { useTheme } from 'antd-style';
3
+ import { memo } from 'react';
4
+
5
+ import { CATEGORY_ICON_MAP } from '../const';
6
+
7
+ interface CategoryAvatarProps {
8
+ category: string;
9
+ }
10
+
11
+ export const CategoryAvatar = memo<CategoryAvatarProps>(({ category }) => {
12
+ const theme = useTheme();
13
+ const IconComponent = CATEGORY_ICON_MAP[category];
14
+
15
+ return (
16
+ <Avatar
17
+ alt={category}
18
+ icon={<IconComponent />}
19
+ style={{
20
+ backgroundColor: 'transparent',
21
+ color: theme.colorTextSecondary,
22
+ height: 16,
23
+ width: 16
24
+ }}
25
+ />
26
+ );
27
+ });
@@ -1,5 +1,5 @@
1
1
  import { Icon, Tooltip } from '@lobehub/ui';
2
- import { Button, Checkbox, Input, Select, Space, Typography } from 'antd';
2
+ import { Button, Checkbox, Input, Radio, Select, Space, Typography } from 'antd';
3
3
  import { SearchIcon } from 'lucide-react';
4
4
  import { ReactNode, memo, useState } from 'react';
5
5
  import { useTranslation } from 'react-i18next';
@@ -10,13 +10,16 @@ import { useChatStore } from '@/store/chat';
10
10
  import { chatToolSelectors } from '@/store/chat/selectors';
11
11
  import { SearchQuery } from '@/types/tool/search';
12
12
 
13
- import { ENGINE_ICON_MAP } from '../const';
13
+ import { CATEGORY_ICON_MAP, ENGINE_ICON_MAP } from '../const';
14
+ import { CategoryAvatar } from './CategoryAvatar';
14
15
  import { EngineAvatar } from './EngineAvatar';
15
16
 
16
17
  interface SearchBarProps {
17
18
  aiSummary?: boolean;
19
+ defaultCategories?: string[];
18
20
  defaultEngines?: string[];
19
21
  defaultQuery: string;
22
+ defaultTimeRange?: string;
20
23
  messageId: string;
21
24
  onSearch?: (searchQuery: SearchQuery) => void;
22
25
  searchAddon?: ReactNode;
@@ -25,7 +28,9 @@ interface SearchBarProps {
25
28
 
26
29
  const SearchBar = memo<SearchBarProps>(
27
30
  ({
31
+ defaultCategories = [],
28
32
  defaultEngines = [],
33
+ defaultTimeRange,
29
34
  aiSummary = true,
30
35
  defaultQuery,
31
36
  tooltip = true,
@@ -36,12 +41,21 @@ const SearchBar = memo<SearchBarProps>(
36
41
  const { t } = useTranslation('tool');
37
42
  const loading = useChatStore(chatToolSelectors.isSearXNGSearching(messageId));
38
43
  const [query, setQuery] = useState(defaultQuery);
44
+ const [categories, setCategories] = useState(defaultCategories);
39
45
  const [engines, setEngines] = useState(defaultEngines);
46
+ const [time_range, setTimeRange] = useState(defaultTimeRange);
40
47
  const isMobile = useIsMobile();
41
48
  const [reSearchWithSearXNG] = useChatStore((s) => [s.reSearchWithSearXNG]);
42
49
 
43
50
  const updateAndSearch = async () => {
44
- const data: SearchQuery = { query, searchEngines: engines };
51
+ const data: SearchQuery = {
52
+ optionalParams: {
53
+ searchCategories: categories,
54
+ searchEngines: engines,
55
+ searchTimeRange: time_range,
56
+ },
57
+ query,
58
+ };
45
59
  onSearch?.(data);
46
60
  await reSearchWithSearXNG(messageId, data, { aiSummary });
47
61
  };
@@ -101,6 +115,7 @@ const SearchBar = memo<SearchBarProps>(
101
115
  ),
102
116
  value: item,
103
117
  }))}
118
+ placeholder={t('search.searchEngine.placeholder')}
104
119
  size={'small'}
105
120
  value={engines}
106
121
  variant={'filled'}
@@ -108,7 +123,7 @@ const SearchBar = memo<SearchBarProps>(
108
123
  ) : (
109
124
  <Flexbox align={'flex-start'} gap={8} horizontal>
110
125
  <Typography.Text style={{ marginTop: 2, wordBreak: 'keep-all' }} type={'secondary'}>
111
- {t('search.searchEngine')}
126
+ {t('search.searchEngine.title')}
112
127
  </Typography.Text>
113
128
  <Checkbox.Group
114
129
  onChange={(checkedValue) => {
@@ -127,6 +142,71 @@ const SearchBar = memo<SearchBarProps>(
127
142
  />
128
143
  </Flexbox>
129
144
  )}
145
+
146
+ {isMobile ? (
147
+ <Select
148
+ mode="multiple"
149
+ onChange={(checkedValue) => {
150
+ setCategories(checkedValue);
151
+ }}
152
+ optionRender={(item) => (
153
+ <Flexbox align={'center'} gap={8} horizontal>
154
+ <CategoryAvatar category={item.value as string} />
155
+ {t(`search.searchCategory.value.${item.value}` as any)}
156
+ </Flexbox>
157
+ )}
158
+ options={Object.keys(CATEGORY_ICON_MAP).map((item) => ({
159
+ label: (
160
+ <Flexbox align={'center'} gap={8} horizontal>
161
+ <CategoryAvatar category={item as any} />
162
+ {t(`search.searchCategory.value.${item}` as any)}
163
+ </Flexbox>
164
+ ),
165
+ value: item,
166
+ }))}
167
+ placeholder={t('search.searchCategory.placeholder')}
168
+ size="small"
169
+ value={categories}
170
+ variant="filled"
171
+ />
172
+ ) : (
173
+ <Flexbox align="flex-start" gap={8} horizontal>
174
+ <Typography.Text style={{ marginTop: 2, wordBreak: 'keep-all' }} type={'secondary'}>
175
+ {t('search.searchCategory.title')}
176
+ </Typography.Text>
177
+ <Checkbox.Group
178
+ onChange={(checkedValue) => setCategories(checkedValue)}
179
+ options={Object.keys(CATEGORY_ICON_MAP).map((item) => ({
180
+ label: (
181
+ <Flexbox align={'center'} gap={8} horizontal>
182
+ <CategoryAvatar category={item as any} />
183
+ {t(`search.searchCategory.value.${item}` as any)}
184
+ </Flexbox>
185
+ ),
186
+ value: item,
187
+ }))}
188
+ value={categories}
189
+ />
190
+ </Flexbox>
191
+ )}
192
+
193
+ <Flexbox align={'center'} gap={16} horizontal wrap={'wrap'}>
194
+ <Typography.Text type={'secondary'}>
195
+ {t('search.searchTimeRange.title')}
196
+ </Typography.Text>
197
+ <Radio.Group
198
+ onChange={(e) => setTimeRange(e.target.value)}
199
+ optionType="button"
200
+ options={[
201
+ { label: t('search.searchTimeRange.value.anytime'), value: 'anytime' },
202
+ { label: t('search.searchTimeRange.value.day'), value: 'day' },
203
+ { label: t('search.searchTimeRange.value.week'), value: 'week' },
204
+ { label: t('search.searchTimeRange.value.month'), value: 'month' },
205
+ { label: t('search.searchTimeRange.value.year'), value: 'year' },
206
+ ]}
207
+ value={time_range}
208
+ />
209
+ </Flexbox>
130
210
  </Flexbox>
131
211
  );
132
212
  },
@@ -1,3 +1,29 @@
1
+ import {
2
+ CodeIcon,
3
+ FileIcon,
4
+ FlaskConicalIcon,
5
+ ImageIcon,
6
+ MapIcon,
7
+ MusicIcon,
8
+ NewspaperIcon,
9
+ SearchIcon,
10
+ Share2Icon,
11
+ VideoIcon,
12
+ } from 'lucide-react';
13
+
14
+ export const CATEGORY_ICON_MAP: Record<string, any> = {
15
+ 'files': FileIcon,
16
+ 'general': SearchIcon,
17
+ 'images': ImageIcon,
18
+ 'it': CodeIcon,
19
+ 'map': MapIcon,
20
+ 'music': MusicIcon,
21
+ 'news': NewspaperIcon,
22
+ 'science': FlaskConicalIcon,
23
+ 'social_media': Share2Icon,
24
+ 'videos': VideoIcon,
25
+ };
26
+
1
27
  export const ENGINE_ICON_MAP: Record<string, string> = {
2
28
  'arxiv': 'https://icons.duckduckgo.com/ip3/arxiv.org.ico',
3
29
  'bilibili': 'https://icons.duckduckgo.com/ip3/bilibili.com.ico',
@@ -18,37 +18,67 @@ export const WebBrowsingManifest: BuiltinToolManifest = {
18
18
  name: WebBrowsingApiName.searchWithSearXNG,
19
19
  parameters: {
20
20
  properties: {
21
+ optionalParams: {
22
+ description: "The optional parameters for search query",
23
+ properties: {
24
+ searchCategories: {
25
+ description: 'The search categories you can set:',
26
+ items: {
27
+ enum: [
28
+ 'files',
29
+ 'general',
30
+ 'images',
31
+ 'it',
32
+ 'map',
33
+ 'music',
34
+ 'news',
35
+ 'science',
36
+ 'social_media',
37
+ 'videos',
38
+ ],
39
+ type: 'string',
40
+ },
41
+ type: 'array',
42
+ },
43
+ searchEngines: {
44
+ description: 'The search engines you can use:',
45
+ items: {
46
+ enum: [
47
+ 'google',
48
+ 'bilibili',
49
+ 'bing',
50
+ 'duckduckgo',
51
+ 'npm',
52
+ 'pypi',
53
+ 'github',
54
+ 'arxiv',
55
+ 'google scholar',
56
+ 'z-library',
57
+ 'reddit',
58
+ 'imdb',
59
+ 'brave',
60
+ 'wikipedia',
61
+ 'pinterest',
62
+ 'unsplash',
63
+ 'vimeo',
64
+ 'youtube',
65
+ ],
66
+ type: 'string',
67
+ },
68
+ type: 'array',
69
+ },
70
+ searchTimeRange: {
71
+ description: "The time range you can set:",
72
+ enum: ['anytime', 'day', 'week', 'month', 'year'],
73
+ type: 'string',
74
+ },
75
+ },
76
+ type: 'object',
77
+ },
21
78
  query: {
22
79
  description: 'The search query',
23
80
  type: 'string',
24
81
  },
25
- searchEngines: {
26
- description: 'The search engine you can use:',
27
- items: {
28
- enum: [
29
- 'google',
30
- 'bilibili',
31
- 'bing',
32
- 'duckduckgo',
33
- 'npm',
34
- 'pypi',
35
- 'github',
36
- 'arxiv',
37
- 'google scholar',
38
- 'z-library',
39
- 'reddit',
40
- 'imdb',
41
- 'brave',
42
- 'wikipedia',
43
- 'pinterest',
44
- 'unsplash',
45
- 'vimeo',
46
- 'youtube',
47
- ],
48
- type: 'string',
49
- },
50
- type: 'array',
51
- },
52
82
  },
53
83
  required: ['query'],
54
84
  type: 'object',
@@ -77,7 +107,7 @@ export const WebBrowsingManifest: BuiltinToolManifest = {
77
107
  properties: {
78
108
  urls: {
79
109
  items: {
80
- description: 'The url need to be crawled',
110
+ description: 'The urls need to be crawled',
81
111
  type: 'string',
82
112
  },
83
113
  type: 'array',
@@ -22,6 +22,19 @@ export const systemPrompt = (
22
22
  - For multi-perspective information or comparative analysis: Use 'crawlMultiPages' on several different relevant sources
23
23
  </tool_selection_guidelines>
24
24
 
25
+ <search_categories_selection>
26
+ Choose search categories based on query type:
27
+ - General: general
28
+ - News: news
29
+ - Academic & Science: science
30
+ - Technical: it
31
+ - Images: images
32
+ - Videos: videos
33
+ - Geographic & Maps: map
34
+ - Files: files
35
+ - Social Media: social_media
36
+ </search_categories_selection>
37
+
25
38
  <search_engine_selection>
26
39
  Choose search engines based on the query type:
27
40
  - General knowledge: google, bing, duckduckgo, brave, wikipedia
@@ -30,9 +43,54 @@ Choose search engines based on the query type:
30
43
  - Videos: youtube, vimeo, bilibili
31
44
  - Images: unsplash, pinterest
32
45
  - Entertainment: imdb, reddit
33
- - For region-specific information, prefer search engines popular in that region
34
46
  </search_engine_selection>
35
47
 
48
+ <search_time_range_selection>
49
+ Choose time range based on the query type:
50
+ - For no time restriction: anytime
51
+ - For the latest updates: day
52
+ - For recent developments: week
53
+ - For ongoing trends or updates: month
54
+ - For long-term insights: year
55
+ </search_time_range_selection>
56
+
57
+ <search_strategy_guidelines>
58
+ - Use engine-based searches when a specific search engine is explicitly required
59
+ - Use category-based searches when unsure about engine selection
60
+ - Use time-range filters to prioritize time-sensitive information
61
+ - Leverage cross-platform meta-search capabilities for comprehensive results
62
+ - Prioritize authoritative sources in search results when available
63
+ - For region-specific information, prefer search engines popular in that region
64
+ - Avoid using both 'engines' and 'categories' in a query, unless the chosen engines do not fall under the selected categories.
65
+
66
+ <search_strategy_best_practices>
67
+ - Combine categories for multi-faceted queries:
68
+ * "AI ethics whitepaper PDF" → files + science + general
69
+ * "Python machine learning tutorial video" → videos + it + science
70
+ * "Sustainable energy policy analysis" → news + science + general
71
+
72
+ - Apply keyword-driven category mapping:
73
+ * "GitHub repository statistics" → it + files
74
+ * "Climate change documentary" → videos + science
75
+ * "Restaurant recommendations Paris" → map + social_media
76
+
77
+ - Use file-type targeting for document searches:
78
+ * "Financial statement xls" → files + news
79
+ * "Research paper citation RIS" → files + science
80
+ * "Government policy brief docx" → files + general
81
+
82
+ - Region-specific query handling:
83
+ * "Beijing traffic update" → map + news (engine: baidu)
84
+ * "Moscow event listings" → social_media + news (engine: yandex)
85
+ * "Tokyo restaurant reviews" → social_media + map (engine: google)
86
+
87
+ - Leverage cross-platform capabilities:
88
+ * "Open-source project documentation" → files + it (engines: github + pypi)
89
+ * "Historical weather patterns" → science + general (engines: google scholar + wikipedia)
90
+ * "Movie release dates 2025" → news + videos (engines: imdb + reddit)
91
+ </search_strategy_best_practices>
92
+ </search_strategy_guidelines>
93
+
36
94
  <citation_requirements>
37
95
  - Always cite sources using markdown footnote format (e.g., [^1])
38
96
  - List all referenced URLs at the end of your response
@@ -86,6 +144,9 @@ SearXNG is a metasearch engine that can leverage multiple search engines includi
86
144
 
87
145
  2. Use \`:\` to select language:
88
146
  - Search Wikipedia in a specific language: \`:fr !wp Wau Holland\` (uses French)
147
+
148
+ 3. Use \`site:\` to restrict results to a specific website:
149
+ - Search SearXNG from a specific website: \`site:github.com SearXNG\`
89
150
  </search_syntax>
90
151
  </searxng_description>
91
152
 
@@ -1,6 +1,10 @@
1
1
  export interface SearchQuery {
2
+ optionalParams?: {
3
+ searchCategories?: string[];
4
+ searchEngines?: string[];
5
+ searchTimeRange?: string;
6
+ }
2
7
  query: string;
3
- searchEngines?: string[];
4
8
  }
5
9
 
6
10
  export const SEARCH_SEARXNG_NOT_CONFIG = 'SearXNG is not configured';
@@ -22,18 +26,23 @@ export interface SearchResult {
22
26
  engine: string;
23
27
  engines: string[];
24
28
  iframe_src?: string;
29
+ img_src?: string;
25
30
  parsed_url: string[];
26
31
  positions: number[];
27
32
  publishedDate?: string | null;
28
33
  score: number;
29
34
  template: string;
30
35
  thumbnail?: string | null;
36
+ thumbnail_src?: string | null;
31
37
  title: string;
32
38
  url: string;
33
39
  }
34
40
 
35
41
  export interface SearchContent {
36
42
  content?: string;
43
+ img_src?: string;
44
+ publishedDate?: string | null;
45
+ thumbnail?: string | null;
37
46
  title: string;
38
47
  url: string;
39
48
  }