@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.
Files changed (56) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/README.md +1 -1
  3. package/README.zh-CN.md +1 -1
  4. package/changelog/v1.json +18 -0
  5. package/contributing/Basic/Architecture.md +1 -1
  6. package/contributing/Basic/Architecture.zh-CN.md +1 -1
  7. package/contributing/Basic/Chat-API.md +326 -108
  8. package/contributing/Basic/Chat-API.zh-CN.md +313 -133
  9. package/contributing/Basic/Contributing-Guidelines.md +7 -4
  10. package/contributing/Basic/Contributing-Guidelines.zh-CN.md +7 -6
  11. package/contributing/Home.md +5 -5
  12. package/contributing/State-Management/State-Management-Intro.md +1 -1
  13. package/contributing/State-Management/State-Management-Intro.zh-CN.md +1 -1
  14. package/docs/self-hosting/advanced/auth/next-auth/keycloak.mdx +119 -0
  15. package/docs/self-hosting/advanced/auth/next-auth/keycloak.zh-CN.mdx +116 -0
  16. package/docs/self-hosting/advanced/auth.mdx +3 -0
  17. package/docs/self-hosting/advanced/auth.zh-CN.mdx +3 -0
  18. package/locales/ar/tool.json +21 -1
  19. package/locales/bg-BG/tool.json +21 -1
  20. package/locales/de-DE/tool.json +21 -1
  21. package/locales/en-US/tool.json +21 -1
  22. package/locales/es-ES/tool.json +21 -1
  23. package/locales/fa-IR/tool.json +21 -1
  24. package/locales/fr-FR/tool.json +21 -1
  25. package/locales/it-IT/tool.json +21 -1
  26. package/locales/ja-JP/tool.json +21 -1
  27. package/locales/ko-KR/tool.json +21 -1
  28. package/locales/nl-NL/tool.json +21 -1
  29. package/locales/pl-PL/tool.json +21 -1
  30. package/locales/pt-BR/tool.json +21 -1
  31. package/locales/ru-RU/tool.json +21 -1
  32. package/locales/tr-TR/tool.json +21 -1
  33. package/locales/vi-VN/tool.json +21 -1
  34. package/locales/zh-CN/tool.json +30 -1
  35. package/locales/zh-TW/tool.json +21 -1
  36. package/package.json +1 -1
  37. package/src/libs/next-auth/sso-providers/index.ts +2 -0
  38. package/src/libs/next-auth/sso-providers/keycloak.ts +25 -0
  39. package/src/locales/default/tool.ts +30 -1
  40. package/src/server/modules/SearXNG.ts +10 -2
  41. package/src/server/routers/tools/__test__/search.test.ts +3 -1
  42. package/src/server/routers/tools/search.ts +10 -2
  43. package/src/services/search.ts +2 -2
  44. package/src/store/chat/slices/builtinTool/actions/searXNG.test.ts +28 -8
  45. package/src/store/chat/slices/builtinTool/actions/searXNG.ts +22 -5
  46. package/src/tools/web-browsing/Portal/Search/index.tsx +1 -1
  47. package/src/tools/web-browsing/Render/Search/SearchQuery/SearchView.tsx +1 -1
  48. package/src/tools/web-browsing/Render/Search/SearchQuery/index.tsx +1 -1
  49. package/src/tools/web-browsing/Render/Search/SearchResult/index.tsx +1 -1
  50. package/src/tools/web-browsing/components/CategoryAvatar.tsx +27 -0
  51. package/src/tools/web-browsing/components/SearchBar.tsx +84 -4
  52. package/src/tools/web-browsing/const.ts +26 -0
  53. package/src/tools/web-browsing/index.ts +58 -28
  54. package/src/tools/web-browsing/systemRole.ts +62 -1
  55. package/src/types/tool/search.ts +10 -1
  56. 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 = { 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
  }
@@ -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
- };