@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.
- package/CHANGELOG.md +50 -0
- package/changelog/v1.json +18 -0
- package/contributing/Basic/Architecture.md +1 -1
- package/contributing/Basic/Architecture.zh-CN.md +1 -1
- package/contributing/Basic/Chat-API.md +326 -108
- package/contributing/Basic/Chat-API.zh-CN.md +313 -133
- package/contributing/Basic/Contributing-Guidelines.md +7 -4
- package/contributing/Basic/Contributing-Guidelines.zh-CN.md +7 -6
- package/contributing/Home.md +5 -5
- package/contributing/State-Management/State-Management-Intro.md +1 -1
- package/contributing/State-Management/State-Management-Intro.zh-CN.md +1 -1
- package/docker-compose/local/docker-compose.yml +2 -1
- package/locales/ar/components.json +4 -0
- package/locales/ar/modelProvider.json +1 -0
- package/locales/ar/models.json +8 -5
- package/locales/ar/tool.json +21 -1
- package/locales/bg-BG/components.json +4 -0
- package/locales/bg-BG/modelProvider.json +1 -0
- package/locales/bg-BG/models.json +8 -5
- package/locales/bg-BG/tool.json +21 -1
- package/locales/de-DE/components.json +4 -0
- package/locales/de-DE/modelProvider.json +1 -0
- package/locales/de-DE/models.json +8 -5
- package/locales/de-DE/tool.json +21 -1
- package/locales/en-US/components.json +4 -0
- package/locales/en-US/modelProvider.json +1 -0
- package/locales/en-US/models.json +8 -5
- package/locales/en-US/tool.json +21 -1
- package/locales/es-ES/components.json +4 -0
- package/locales/es-ES/modelProvider.json +1 -0
- package/locales/es-ES/models.json +7 -4
- package/locales/es-ES/tool.json +21 -1
- package/locales/fa-IR/components.json +4 -0
- package/locales/fa-IR/modelProvider.json +1 -0
- package/locales/fa-IR/models.json +7 -4
- package/locales/fa-IR/tool.json +21 -1
- package/locales/fr-FR/components.json +4 -0
- package/locales/fr-FR/modelProvider.json +1 -0
- package/locales/fr-FR/models.json +8 -5
- package/locales/fr-FR/tool.json +21 -1
- package/locales/it-IT/components.json +4 -0
- package/locales/it-IT/modelProvider.json +1 -0
- package/locales/it-IT/models.json +7 -4
- package/locales/it-IT/tool.json +21 -1
- package/locales/ja-JP/components.json +4 -0
- package/locales/ja-JP/modelProvider.json +1 -0
- package/locales/ja-JP/models.json +8 -5
- package/locales/ja-JP/tool.json +21 -1
- package/locales/ko-KR/components.json +4 -0
- package/locales/ko-KR/modelProvider.json +1 -0
- package/locales/ko-KR/models.json +8 -5
- package/locales/ko-KR/tool.json +21 -1
- package/locales/nl-NL/components.json +4 -0
- package/locales/nl-NL/modelProvider.json +1 -0
- package/locales/nl-NL/models.json +8 -5
- package/locales/nl-NL/tool.json +21 -1
- package/locales/pl-PL/components.json +4 -0
- package/locales/pl-PL/modelProvider.json +1 -0
- package/locales/pl-PL/models.json +8 -5
- package/locales/pl-PL/tool.json +21 -1
- package/locales/pt-BR/components.json +4 -0
- package/locales/pt-BR/modelProvider.json +1 -0
- package/locales/pt-BR/models.json +7 -4
- package/locales/pt-BR/tool.json +21 -1
- package/locales/ru-RU/components.json +4 -0
- package/locales/ru-RU/modelProvider.json +1 -0
- package/locales/ru-RU/models.json +7 -4
- package/locales/ru-RU/tool.json +21 -1
- package/locales/tr-TR/components.json +4 -0
- package/locales/tr-TR/modelProvider.json +1 -0
- package/locales/tr-TR/models.json +8 -5
- package/locales/tr-TR/tool.json +21 -1
- package/locales/vi-VN/components.json +4 -0
- package/locales/vi-VN/modelProvider.json +1 -0
- package/locales/vi-VN/models.json +8 -5
- package/locales/vi-VN/tool.json +21 -1
- package/locales/zh-CN/components.json +4 -0
- package/locales/zh-CN/modelProvider.json +1 -0
- package/locales/zh-CN/models.json +9 -6
- package/locales/zh-CN/tool.json +30 -1
- package/locales/zh-TW/components.json +4 -0
- package/locales/zh-TW/modelProvider.json +1 -0
- package/locales/zh-TW/models.json +7 -4
- package/locales/zh-TW/tool.json +21 -1
- package/package.json +1 -1
- package/src/app/(backend)/webapi/models/[provider]/pull/route.ts +34 -0
- package/src/app/(backend)/webapi/{chat/models → models}/[provider]/route.ts +1 -2
- package/src/app/[variants]/(main)/settings/llm/ProviderList/Ollama/index.tsx +0 -7
- package/src/app/[variants]/(main)/settings/provider/(detail)/ollama/CheckError.tsx +1 -1
- package/src/components/FormAction/index.tsx +1 -1
- package/src/database/models/__tests__/aiProvider.test.ts +100 -0
- package/src/database/models/aiProvider.ts +11 -1
- package/src/features/Conversation/Error/OllamaBizError/InvalidOllamaModel.tsx +43 -0
- package/src/features/Conversation/Error/OllamaDesktopSetupGuide/index.tsx +61 -0
- package/src/features/Conversation/Error/index.tsx +7 -0
- package/src/features/DevPanel/SystemInspector/ServerConfig.tsx +18 -2
- package/src/features/DevPanel/SystemInspector/index.tsx +25 -6
- package/src/features/OllamaModelDownloader/index.tsx +149 -0
- package/src/libs/agent-runtime/AgentRuntime.ts +6 -0
- package/src/libs/agent-runtime/BaseAI.ts +7 -0
- package/src/libs/agent-runtime/ollama/index.ts +84 -2
- package/src/libs/agent-runtime/openrouter/__snapshots__/index.test.ts.snap +24 -3263
- package/src/libs/agent-runtime/openrouter/fixtures/frontendModels.json +25 -0
- package/src/libs/agent-runtime/openrouter/fixtures/models.json +0 -3353
- package/src/libs/agent-runtime/openrouter/index.test.ts +56 -1
- package/src/libs/agent-runtime/openrouter/index.ts +9 -4
- package/src/libs/agent-runtime/types/index.ts +1 -0
- package/src/libs/agent-runtime/types/model.ts +44 -0
- package/src/libs/agent-runtime/utils/streams/index.ts +1 -0
- package/src/libs/agent-runtime/utils/streams/model.ts +110 -0
- package/src/locales/default/components.ts +4 -0
- package/src/locales/default/modelProvider.ts +1 -0
- package/src/locales/default/tool.ts +30 -1
- package/src/server/modules/SearXNG.ts +10 -2
- package/src/server/routers/tools/__test__/search.test.ts +3 -1
- package/src/server/routers/tools/search.ts +10 -2
- package/src/services/__tests__/models.test.ts +21 -0
- package/src/services/_url.ts +4 -1
- package/src/services/chat.ts +1 -1
- package/src/services/models.ts +153 -7
- package/src/services/search.ts +2 -2
- package/src/store/aiInfra/slices/aiModel/action.ts +1 -1
- package/src/store/aiInfra/slices/aiProvider/action.ts +2 -1
- package/src/store/chat/slices/builtinTool/actions/searXNG.test.ts +28 -8
- package/src/store/chat/slices/builtinTool/actions/searXNG.ts +22 -5
- package/src/store/user/slices/modelList/action.test.ts +2 -2
- package/src/store/user/slices/modelList/action.ts +1 -1
- package/src/tools/web-browsing/Portal/Search/index.tsx +1 -1
- package/src/tools/web-browsing/Render/Search/SearchQuery/SearchView.tsx +1 -1
- package/src/tools/web-browsing/Render/Search/SearchQuery/index.tsx +1 -1
- package/src/tools/web-browsing/Render/Search/SearchResult/index.tsx +1 -1
- package/src/tools/web-browsing/components/CategoryAvatar.tsx +27 -0
- package/src/tools/web-browsing/components/SearchBar.tsx +84 -4
- package/src/tools/web-browsing/const.ts +26 -0
- package/src/tools/web-browsing/index.ts +58 -28
- package/src/tools/web-browsing/systemRole.ts +62 -1
- package/src/types/tool/search.ts +10 -1
- package/src/app/[variants]/(main)/settings/llm/ProviderList/Ollama/Checker.tsx +0 -73
- package/src/app/[variants]/(main)/settings/provider/(detail)/ollama/OllamaModelDownloader/index.tsx +0 -127
- package/src/features/Conversation/Error/OllamaBizError/InvalidOllamaModel/index.tsx +0 -154
- package/src/features/Conversation/Error/OllamaBizError/InvalidOllamaModel/useDownloadMonitor.ts +0 -29
- package/src/helpers/url.ts +0 -17
- package/src/services/__tests__/ollama.test.ts +0 -28
- package/src/services/ollama.ts +0 -83
- /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',
|
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(
|
153
|
-
expect(searchService.search).toHaveBeenNthCalledWith(1,
|
154
|
-
|
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
|
-
|
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
|
161
|
+
// 如果仍然没有搜索到结果,则执行第二次重试(移除所有限制)
|
162
|
+
if (data?.results.length === 0) {
|
149
163
|
data = await searchService.search(params.query);
|
150
|
-
get().updatePluginArguments(id, { ...params,
|
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, '
|
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, '
|
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.
|
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 = {
|
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
|
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
|
|
package/src/types/tool/search.ts
CHANGED
@@ -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
|
}
|