@lobehub/chat 1.77.17 → 1.78.0
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/README.md +1 -1
- package/README.zh-CN.md +1 -1
- 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/docs/self-hosting/advanced/auth/next-auth/keycloak.mdx +119 -0
- package/docs/self-hosting/advanced/auth/next-auth/keycloak.zh-CN.mdx +116 -0
- package/docs/self-hosting/advanced/auth.mdx +3 -0
- package/docs/self-hosting/advanced/auth.zh-CN.mdx +3 -0
- package/locales/ar/tool.json +21 -1
- package/locales/bg-BG/tool.json +21 -1
- package/locales/de-DE/tool.json +21 -1
- package/locales/en-US/tool.json +21 -1
- package/locales/es-ES/tool.json +21 -1
- package/locales/fa-IR/tool.json +21 -1
- package/locales/fr-FR/tool.json +21 -1
- package/locales/it-IT/tool.json +21 -1
- package/locales/ja-JP/tool.json +21 -1
- package/locales/ko-KR/tool.json +21 -1
- package/locales/nl-NL/tool.json +21 -1
- package/locales/pl-PL/tool.json +21 -1
- package/locales/pt-BR/tool.json +21 -1
- package/locales/ru-RU/tool.json +21 -1
- package/locales/tr-TR/tool.json +21 -1
- package/locales/vi-VN/tool.json +21 -1
- package/locales/zh-CN/tool.json +30 -1
- package/locales/zh-TW/tool.json +21 -1
- package/package.json +1 -1
- package/src/libs/next-auth/sso-providers/index.ts +2 -0
- package/src/libs/next-auth/sso-providers/keycloak.ts +25 -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/search.ts +2 -2
- 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/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/helpers/url.ts +0 -17
@@ -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
|
}
|
package/src/helpers/url.ts
DELETED
@@ -1,17 +0,0 @@
|
|
1
|
-
import { ChatMessage } from '@lobehub/ui';
|
2
|
-
|
3
|
-
import { Compressor } from '@/utils/compass';
|
4
|
-
|
5
|
-
export const genShareMessagesUrl = (messages: ChatMessage[], systemRole?: string) => {
|
6
|
-
const compassedMsg = systemRole
|
7
|
-
? [{ content: systemRole, role: 'system' }, ...messages]
|
8
|
-
: messages;
|
9
|
-
|
10
|
-
return `/share?messages=${Compressor.compress(JSON.stringify(compassedMsg))}`;
|
11
|
-
};
|
12
|
-
|
13
|
-
export const genSystemRoleQuery = async (content: string) => {
|
14
|
-
const x = { state: { systemRole: content } };
|
15
|
-
const systemRole = await Compressor.compressAsync(JSON.stringify(x));
|
16
|
-
return `#systemRole=${systemRole}`;
|
17
|
-
};
|