@lobehub/chat 1.64.1 → 1.64.3

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 (44) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/README.md +1 -1
  3. package/changelog/v1.json +18 -0
  4. package/locales/ar/models.json +9 -0
  5. package/locales/bg-BG/models.json +9 -0
  6. package/locales/de-DE/models.json +9 -0
  7. package/locales/en-US/models.json +9 -0
  8. package/locales/es-ES/models.json +9 -0
  9. package/locales/fa-IR/models.json +3 -0
  10. package/locales/fr-FR/models.json +9 -0
  11. package/locales/it-IT/models.json +9 -0
  12. package/locales/ja-JP/models.json +9 -0
  13. package/locales/ko-KR/models.json +9 -0
  14. package/locales/nl-NL/models.json +9 -0
  15. package/locales/pl-PL/models.json +9 -0
  16. package/locales/pt-BR/models.json +9 -0
  17. package/locales/ru-RU/models.json +9 -0
  18. package/locales/tr-TR/models.json +9 -0
  19. package/locales/vi-VN/models.json +9 -0
  20. package/locales/zh-CN/models.json +9 -0
  21. package/locales/zh-TW/models.json +9 -0
  22. package/package.json +1 -1
  23. package/src/components/WebFavicon/index.tsx +26 -0
  24. package/src/config/aiModels/anthropic.ts +23 -0
  25. package/src/config/aiModels/bedrock.ts +23 -2
  26. package/src/config/modelProviders/anthropic.ts +34 -0
  27. package/src/config/modelProviders/bedrock.ts +51 -0
  28. package/src/features/Conversation/Messages/Assistant/Tool/Render/CustomRender.tsx +1 -0
  29. package/src/features/Conversation/Messages/Assistant/Tool/Render/index.tsx +6 -0
  30. package/src/store/chat/slices/aiChat/actions/generateAIChat.ts +1 -1
  31. package/src/store/chat/slices/builtinTool/{action.test.ts → actions/dalle.test.ts} +2 -2
  32. package/src/store/chat/slices/builtinTool/actions/dalle.ts +126 -0
  33. package/src/store/chat/slices/builtinTool/actions/index.ts +18 -0
  34. package/src/store/chat/slices/builtinTool/actions/searXNG.test.ts +288 -0
  35. package/src/store/chat/slices/builtinTool/{action.ts → actions/searXNG.ts} +15 -116
  36. package/src/store/chat/slices/message/action.ts +1 -1
  37. package/src/store/chat/store.ts +1 -1
  38. package/src/tools/web-browsing/Portal/ResultList/SearchItem/TitleExtra.tsx +5 -1
  39. package/src/tools/web-browsing/Portal/ResultList/SearchItem/Video.tsx +6 -1
  40. package/src/tools/web-browsing/Portal/ResultList/SearchItem/index.tsx +8 -3
  41. package/src/tools/web-browsing/Render/SearchQuery/index.tsx +2 -2
  42. package/src/tools/web-browsing/Render/SearchResult/SearchResultItem.tsx +2 -9
  43. package/src/tools/web-browsing/Render/index.tsx +1 -1
  44. package/src/tools/web-browsing/const.ts +10 -9
@@ -1,35 +1,18 @@
1
- import { produce } from 'immer';
2
- import pMap from 'p-map';
3
- import { SWRResponse } from 'swr';
4
1
  import { StateCreator } from 'zustand/vanilla';
5
2
 
6
- import { useClientDataSWR } from '@/libs/swr';
7
- import { fileService } from '@/services/file';
8
3
  import { searchService } from '@/services/search';
9
- import { imageGenerationService } from '@/services/textToImage';
10
- import { uploadService } from '@/services/upload';
11
4
  import { chatSelectors } from '@/store/chat/selectors';
12
5
  import { ChatStore } from '@/store/chat/store';
13
- import { useFileStore } from '@/store/file';
14
6
  import { CreateMessageParams } from '@/types/message';
15
- import { DallEImageItem } from '@/types/tool/dalle';
16
7
  import {
17
8
  SEARCH_SEARXNG_NOT_CONFIG,
18
9
  SearchContent,
19
10
  SearchQuery,
20
11
  SearchResponse,
21
12
  } from '@/types/tool/search';
22
- import { setNamespace } from '@/utils/storeDebug';
23
13
  import { nanoid } from '@/utils/uuid';
24
14
 
25
- const n = setNamespace('tool');
26
-
27
- const SWR_FETCH_KEY = 'FetchImageItem';
28
- /**
29
- * builtin tool action
30
- */
31
- export interface ChatBuiltinToolAction {
32
- generateImageFromPrompts: (items: DallEImageItem[], id: string) => Promise<void>;
15
+ export interface SearchAction {
33
16
  /**
34
17
  * 重新发起搜索
35
18
  * @description 会更新插件的 arguments 参数,然后再次搜索
@@ -45,69 +28,15 @@ export interface ChatBuiltinToolAction {
45
28
  data: SearchQuery,
46
29
  aiSummary?: boolean,
47
30
  ) => Promise<void | boolean>;
48
- text2image: (id: string, data: DallEImageItem[]) => Promise<void>;
49
-
50
- toggleDallEImageLoading: (key: string, value: boolean) => void;
51
31
  toggleSearchLoading: (id: string, loading: boolean) => void;
52
- updateImageItem: (id: string, updater: (data: DallEImageItem[]) => void) => Promise<void>;
53
- useFetchDalleImageItem: (id: string) => SWRResponse;
54
32
  }
55
33
 
56
- export const chatToolSlice: StateCreator<
34
+ export const searchSlice: StateCreator<
57
35
  ChatStore,
58
36
  [['zustand/devtools', never]],
59
37
  [],
60
- ChatBuiltinToolAction
38
+ SearchAction
61
39
  > = (set, get) => ({
62
- generateImageFromPrompts: async (items, messageId) => {
63
- const { toggleDallEImageLoading, updateImageItem } = get();
64
- // eslint-disable-next-line unicorn/consistent-function-scoping
65
- const getMessageById = (id: string) => chatSelectors.getMessageById(id)(get());
66
-
67
- const message = getMessageById(messageId);
68
- if (!message) return;
69
-
70
- const parent = getMessageById(message!.parentId!);
71
- const originPrompt = parent?.content;
72
- let errorArray: any[] = [];
73
-
74
- await pMap(items, async (params, index) => {
75
- toggleDallEImageLoading(messageId + params.prompt, true);
76
-
77
- let url = '';
78
- try {
79
- url = await imageGenerationService.generateImage(params);
80
- } catch (e) {
81
- toggleDallEImageLoading(messageId + params.prompt, false);
82
- errorArray[index] = e;
83
-
84
- await get().updatePluginState(messageId, { error: errorArray });
85
- }
86
-
87
- if (!url) return;
88
-
89
- await updateImageItem(messageId, (draft) => {
90
- draft[index].previewUrl = url;
91
- });
92
-
93
- toggleDallEImageLoading(messageId + params.prompt, false);
94
- const imageFile = await uploadService.getImageFileByUrlWithCORS(
95
- url,
96
- `${originPrompt || params.prompt}_${index}.png`,
97
- );
98
-
99
- const data = await useFileStore.getState().uploadWithProgress({
100
- file: imageFile,
101
- });
102
-
103
- if (!data) return;
104
-
105
- await updateImageItem(messageId, (draft) => {
106
- draft[index].imageId = data.id;
107
- draft[index].previewUrl = undefined;
108
- });
109
- });
110
- },
111
40
  reSearchWithSearXNG: async (id, data, options) => {
112
41
  get().toggleSearchLoading(id, true);
113
42
  await get().updatePluginArguments(id, data);
@@ -158,6 +87,13 @@ export const chatToolSlice: StateCreator<
158
87
  let data: SearchResponse | undefined;
159
88
  try {
160
89
  data = await searchService.search(params.query, params.searchEngines);
90
+
91
+ // 如果没有搜索到结果,那么尝试使用默认的搜索引擎再搜一次
92
+ if (data?.results.length === 0 && params.searchEngines && params.searchEngines?.length > 0) {
93
+ data = await searchService.search(params.query);
94
+ get().updatePluginArguments(id, { ...params, searchEngines: undefined });
95
+ }
96
+
161
97
  await get().updatePluginState(id, data);
162
98
  } catch (e) {
163
99
  if ((e as Error).message === SEARCH_SEARXNG_NOT_CONFIG) {
@@ -181,8 +117,8 @@ export const chatToolSlice: StateCreator<
181
117
 
182
118
  if (!data) return;
183
119
 
184
- // 只取前 5 个结果作为上下文
185
- const searchContent: SearchContent[] = data.results.slice(0, 5).map((item) => ({
120
+ // add 15 search results to message content
121
+ const searchContent: SearchContent[] = data.results.slice(0, 15).map((item) => ({
186
122
  content: item.content,
187
123
  title: item.title,
188
124
  url: item.url,
@@ -196,49 +132,12 @@ export const chatToolSlice: StateCreator<
196
132
  // 如果 aiSummary 为 true,则会自动触发总结
197
133
  return aiSummary;
198
134
  },
199
- text2image: async (id, data) => {
200
- // const isAutoGen = settingsSelectors.isDalleAutoGenerating(useGlobalStore.getState());
201
- // if (!isAutoGen) return;
202
-
203
- await get().generateImageFromPrompts(data, id);
204
- },
205
135
 
206
- toggleDallEImageLoading: (key, value) => {
136
+ toggleSearchLoading: (id, loading) => {
207
137
  set(
208
- { dalleImageLoading: { ...get().dalleImageLoading, [key]: value } },
138
+ { searchLoading: { ...get().searchLoading, [id]: loading } },
209
139
  false,
210
- n('toggleDallEImageLoading'),
140
+ `toggleSearchLoading/${loading ? 'start' : 'end'}`,
211
141
  );
212
142
  },
213
-
214
- toggleSearchLoading: (id, loading) => {
215
- set({ searchLoading: { ...get().searchLoading, [id]: loading } }, false, 'toggleSearchLoading');
216
- },
217
-
218
- updateImageItem: async (id, updater) => {
219
- const message = chatSelectors.getMessageById(id)(get());
220
- if (!message) return;
221
-
222
- const data: DallEImageItem[] = JSON.parse(message.content);
223
-
224
- const nextContent = produce(data, updater);
225
- await get().internal_updateMessageContent(id, JSON.stringify(nextContent));
226
- },
227
-
228
- useFetchDalleImageItem: (id) =>
229
- useClientDataSWR([SWR_FETCH_KEY, id], async () => {
230
- const item = await fileService.getFile(id);
231
-
232
- set(
233
- produce((draft) => {
234
- if (draft.dalleImageMap[id]) return;
235
-
236
- draft.dalleImageMap[id] = item;
237
- }),
238
- false,
239
- n('useFetchFile'),
240
- );
241
-
242
- return item;
243
- }),
244
143
  });
@@ -380,7 +380,7 @@ export const chatMessage: StateCreator<
380
380
  messageLoadingIds: toggleBooleanList(get().messageLoadingIds, id, loading),
381
381
  },
382
382
  false,
383
- 'internal_toggleMessageLoading',
383
+ `internal_toggleMessageLoading/${loading ? 'start' : 'end'}`,
384
384
  );
385
385
  },
386
386
  internal_toggleLoadingArrays: (key, loading, id, action) => {
@@ -6,7 +6,7 @@ import { StateCreator } from 'zustand/vanilla';
6
6
 
7
7
  import { createDevtools } from '../middleware/createDevtools';
8
8
  import { ChatStoreState, initialState } from './initialState';
9
- import { ChatBuiltinToolAction, chatToolSlice } from './slices/builtinTool/action';
9
+ import { ChatBuiltinToolAction, chatToolSlice } from './slices/builtinTool/actions';
10
10
  import { ChatPortalAction, chatPortalSlice } from './slices/portal/action';
11
11
  import { ChatTranslateAction, chatTranslate } from './slices/translate/action';
12
12
  import { ChatMessageAction, chatMessage } from './slices/message/action';
@@ -4,19 +4,23 @@ import { memo } from 'react';
4
4
  import { useTranslation } from 'react-i18next';
5
5
  import { Flexbox } from 'react-layout-kit';
6
6
 
7
+ import { EngineAvatarGroup } from '@/tools/web-browsing/components/EngineAvatar';
8
+
7
9
  import CategoryAvatar from './CategoryAvatar';
8
10
 
9
11
  interface TitleExtraProps {
10
12
  category: string;
13
+ engines: string[];
11
14
  highlight?: boolean;
12
15
  score: number;
13
16
  }
14
17
 
15
- const TitleExtra = memo<TitleExtraProps>(({ category, score, highlight }) => {
18
+ const TitleExtra = memo<TitleExtraProps>(({ category, score, highlight, engines }) => {
16
19
  const { t } = useTranslation('tool');
17
20
 
18
21
  return (
19
22
  <Flexbox align={'center'} gap={4} horizontal>
23
+ <EngineAvatarGroup engines={engines} />
20
24
  <Tooltip title={t(highlight ? 'search.includedTooltip' : 'search.scoreTooltip')}>
21
25
  {highlight ? (
22
26
  <Tag bordered={false} color={'blue'} style={{ marginInlineEnd: 0 }}>
@@ -113,7 +113,12 @@ const VideoItem = memo<SearchResultProps>(
113
113
  </AntAvatar.Group>
114
114
  <Flexbox className={styles.title}>{title}</Flexbox>
115
115
  </Flexbox>
116
- <TitleExtra category={category} highlight={highlight} score={score} />
116
+ <TitleExtra
117
+ category={category}
118
+ engines={engines}
119
+ highlight={highlight}
120
+ score={score}
121
+ />
117
122
  </Flexbox>
118
123
  <Typography.Text className={styles.url} type={'secondary'}>
119
124
  {url}
@@ -3,9 +3,9 @@ import { createStyles } from 'antd-style';
3
3
  import { memo } from 'react';
4
4
  import { Flexbox } from 'react-layout-kit';
5
5
 
6
+ import WebFavicon from '@/components/WebFavicon';
6
7
  import { SearchResult } from '@/types/tool/search';
7
8
 
8
- import { EngineAvatarGroup } from '../../../components/EngineAvatar';
9
9
  import TitleExtra from './TitleExtra';
10
10
  import Video from './Video';
11
11
 
@@ -73,10 +73,15 @@ const SearchItem = memo<SearchResultProps>((props) => {
73
73
  <Flexbox gap={8}>
74
74
  <Flexbox align={'center'} distribution={'space-between'} horizontal>
75
75
  <Flexbox align={'center'} gap={8} horizontal>
76
- <EngineAvatarGroup engines={engines} />
76
+ <WebFavicon title={title} url={url} />
77
77
  <Flexbox className={styles.title}>{title}</Flexbox>
78
78
  </Flexbox>
79
- <TitleExtra category={category} highlight={props.highlight} score={score} />
79
+ <TitleExtra
80
+ category={category}
81
+ engines={engines}
82
+ highlight={props.highlight}
83
+ score={score}
84
+ />
80
85
  </Flexbox>
81
86
  <Typography.Text className={styles.url} type={'secondary'}>
82
87
  {url}
@@ -33,8 +33,8 @@ const SearchQueryView = memo<SearchQueryViewProps>(
33
33
 
34
34
  return !pluginState ? (
35
35
  <Flexbox align={'center'} distribution={'space-between'} height={32} horizontal>
36
- <Skeleton.Button active style={{ borderRadius: 8, height: 32, width: 180 }} />
37
- <Skeleton.Button active style={{ borderRadius: 8, height: 32, width: 220 }} />
36
+ <Skeleton.Button active style={{ borderRadius: 4, height: 32, width: 180 }} />
37
+ <Skeleton.Button active style={{ borderRadius: 4, height: 32, width: 220 }} />
38
38
  </Flexbox>
39
39
  ) : editing ? (
40
40
  <SearchBar
@@ -1,10 +1,10 @@
1
1
  import { Typography } from 'antd';
2
2
  import { createStyles } from 'antd-style';
3
- import Image from 'next/image';
4
3
  import Link from 'next/link';
5
4
  import { memo } from 'react';
6
5
  import { Flexbox } from 'react-layout-kit';
7
6
 
7
+ import WebFavicon from '@/components/WebFavicon';
8
8
  import { SearchResult } from '@/types/tool/search';
9
9
 
10
10
  const useStyles = createStyles(({ css, token }) => ({
@@ -52,14 +52,7 @@ const SearchResultItem = memo<SearchResult>(({ url, title }) => {
52
52
  <Flexbox className={styles.container} gap={2} justify={'space-between'} key={url}>
53
53
  <div className={styles.title}>{title}</div>
54
54
  <Flexbox align={'center'} gap={4} horizontal>
55
- <Image
56
- alt={title || url}
57
- height={14}
58
- src={`https://icons.duckduckgo.com/ip3/${host}.ico`}
59
- style={{ borderRadius: 4 }}
60
- unoptimized
61
- width={14}
62
- />
55
+ <WebFavicon size={14} title={title} url={url} />
63
56
  <Typography.Text className={styles.url} type={'secondary'}>
64
57
  {host.replace('www.', '')}
65
58
  </Typography.Text>
@@ -34,7 +34,7 @@ const WebBrowsing = memo<BuiltinRenderProps<SearchContent[], SearchQuery, Search
34
34
  }
35
35
 
36
36
  return (
37
- <Flexbox gap={16}>
37
+ <Flexbox gap={8}>
38
38
  <SearchQueryView
39
39
  args={args}
40
40
  editing={editing}
@@ -1,11 +1,12 @@
1
1
  export const ENGINE_ICON_MAP: Record<string, string> = {
2
- arxiv: 'https://arxiv.org/static/browse/0.3.4/images/icons/favicon-32x32.png',
3
- bilibili: 'https://www.bilibili.com/favicon.ico',
4
- bing: 'https://www.bing.com/favicon.ico',
5
- brave: 'https://brave.com/static-assets/images/brave-favicon.png',
6
- duckduckgo: 'https://www.duckduckgo.com/favicon.ico',
7
- google: 'https://www.google.com/favicon.ico',
8
- npm: 'https://static-production.npmjs.com/da3ab40fb0861d15c83854c29f5f2962.png',
9
- qwant: 'https://www.qwant.com/favicon.ico',
10
- youtube: 'https://www.youtube.com/favicon.ico',
2
+ 'arxiv': 'https://icons.duckduckgo.com/ip3/arxiv.org.ico',
3
+ 'bilibili': 'https://icons.duckduckgo.com/ip3/bilibili.com.ico',
4
+ 'bing': 'https://icons.duckduckgo.com/ip3/www.bing.com.ico',
5
+ 'brave': 'https://icons.duckduckgo.com/ip3/brave.com.ico',
6
+ 'duckduckgo': 'https://icons.duckduckgo.com/ip3/www.duckduckgo.com.ico',
7
+ 'google': 'https://icons.duckduckgo.com/ip3/google.com.ico',
8
+ 'google scholar': 'https://icons.duckduckgo.com/ip3/scholar.google.com.ico',
9
+ 'npm': 'https://icons.duckduckgo.com/ip3/npmjs.com.ico',
10
+ 'qwant': 'https://icons.duckduckgo.com/ip3/www.qwant.com.ico',
11
+ 'youtube': 'https://icons.duckduckgo.com/ip3/youtube.com.ico',
11
12
  };