@lobehub/chat 1.64.0 → 1.64.2

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 CHANGED
@@ -2,6 +2,56 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ### [Version 1.64.2](https://github.com/lobehub/lobe-chat/compare/v1.64.1...v1.64.2)
6
+
7
+ <sup>Released on **2025-02-25**</sup>
8
+
9
+ #### 🐛 Bug Fixes
10
+
11
+ - **misc**: Fix 0 search results with specific search engine.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### What's fixed
19
+
20
+ - **misc**: Fix 0 search results with specific search engine, closes [#6487](https://github.com/lobehub/lobe-chat/issues/6487) ([74a09e2](https://github.com/lobehub/lobe-chat/commit/74a09e2))
21
+
22
+ </details>
23
+
24
+ <div align="right">
25
+
26
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
27
+
28
+ </div>
29
+
30
+ ### [Version 1.64.1](https://github.com/lobehub/lobe-chat/compare/v1.64.0...v1.64.1)
31
+
32
+ <sup>Released on **2025-02-25**</sup>
33
+
34
+ #### 🐛 Bug Fixes
35
+
36
+ - **misc**: Disable fc for ds-v3 series.
37
+
38
+ <br/>
39
+
40
+ <details>
41
+ <summary><kbd>Improvements and Fixes</kbd></summary>
42
+
43
+ #### What's fixed
44
+
45
+ - **misc**: Disable fc for ds-v3 series, closes [#6486](https://github.com/lobehub/lobe-chat/issues/6486) ([0092213](https://github.com/lobehub/lobe-chat/commit/0092213))
46
+
47
+ </details>
48
+
49
+ <div align="right">
50
+
51
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
52
+
53
+ </div>
54
+
5
55
  ## [Version 1.64.0](https://github.com/lobehub/lobe-chat/compare/v1.63.3...v1.64.0)
6
56
 
7
57
  <sup>Released on **2025-02-24**</sup>
package/changelog/v1.json CHANGED
@@ -1,4 +1,22 @@
1
1
  [
2
+ {
3
+ "children": {
4
+ "fixes": [
5
+ "Fix 0 search results with specific search engine."
6
+ ]
7
+ },
8
+ "date": "2025-02-25",
9
+ "version": "1.64.2"
10
+ },
11
+ {
12
+ "children": {
13
+ "fixes": [
14
+ "Disable fc for ds-v3 series."
15
+ ]
16
+ },
17
+ "date": "2025-02-25",
18
+ "version": "1.64.1"
19
+ },
2
20
  {
3
21
  "children": {
4
22
  "features": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "1.64.0",
3
+ "version": "1.64.2",
4
4
  "description": "Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
5
5
  "keywords": [
6
6
  "framework",
@@ -0,0 +1,26 @@
1
+ import Image from 'next/image';
2
+
3
+ interface WebFaviconProps {
4
+ alt?: string;
5
+ size?: number;
6
+ title?: string;
7
+ url: string;
8
+ }
9
+
10
+ const WebFavicon = ({ url, title, alt, size = 14 }: WebFaviconProps) => {
11
+ const urlObj = new URL(url);
12
+ const host = urlObj.hostname;
13
+
14
+ return (
15
+ <Image
16
+ alt={alt || title || url}
17
+ height={size}
18
+ src={`https://icons.duckduckgo.com/ip3/${host}.ico`}
19
+ style={{ borderRadius: 4 }}
20
+ unoptimized
21
+ width={size}
22
+ />
23
+ );
24
+ };
25
+
26
+ export default WebFavicon;
@@ -22,7 +22,8 @@ const siliconcloudChatModels: AIChatModelCard[] = [
22
22
  },
23
23
  {
24
24
  abilities: {
25
- functionCall: true,
25
+ // Not support tool use, ref: https://cloud.siliconflow.cn/models?target=deepseek-ai%2FDeepSeek-V3
26
+ functionCall: false,
26
27
  },
27
28
  contextWindowTokens: 65_536,
28
29
  description:
@@ -67,7 +67,8 @@ const doubaoChatModels: AIChatModelCard[] = [
67
67
  },
68
68
  {
69
69
  abilities: {
70
- functionCall: true,
70
+ // FC not supported yet, ref: https://www.volcengine.com/docs/82379/1262342#8c325d45
71
+ functionCall: false,
71
72
  },
72
73
  config: {
73
74
  deploymentName: 'deepseek-v3-241226',
@@ -38,6 +38,7 @@ const CustomRender = memo<
38
38
  const { t } = useTranslation('plugin');
39
39
 
40
40
  const theme = useTheme();
41
+
41
42
  useEffect(() => {
42
43
  if (!plugin?.type || loading) return;
43
44
 
@@ -1,5 +1,6 @@
1
1
  import { Suspense, memo } from 'react';
2
2
 
3
+ import { LOADING_FLAT } from '@/const/message';
3
4
  import ErrorResponse from '@/features/Conversation/Messages/Assistant/Tool/Render/ErrorResponse';
4
5
  import { useChatStore } from '@/store/chat';
5
6
  import { chatSelectors } from '@/store/chat/selectors';
@@ -28,6 +29,11 @@ const Render = memo<RenderProps>(
28
29
  return <ErrorResponse {...toolMessage.error} id={messageId} plugin={toolMessage.plugin} />;
29
30
  }
30
31
 
32
+ // 如果是 LOADING_FLAT 则说明还在加载中
33
+ // 而 standalone 模式的插件 content 应该始终是 LOADING_FLAT
34
+ if (toolMessage.content === LOADING_FLAT && toolMessage.plugin?.type !== 'standalone')
35
+ return <Arguments arguments={requestArgs} shine />;
36
+
31
37
  return (
32
38
  <Suspense fallback={<Arguments arguments={requestArgs} shine />}>
33
39
  <CustomRender
@@ -626,7 +626,7 @@ export const generateAIChat: StateCreator<
626
626
  },
627
627
 
628
628
  false,
629
- 'toggleToolCallingStreaming',
629
+ `toggleToolCallingStreaming/${!!streaming ? 'start' : 'end'}`,
630
630
  );
631
631
  },
632
632
  });
@@ -10,9 +10,9 @@ import { chatSelectors } from '@/store/chat/selectors';
10
10
  import { ChatMessage } from '@/types/message';
11
11
  import { DallEImageItem } from '@/types/tool/dalle';
12
12
 
13
- import { useChatStore } from '../../store';
13
+ import { useChatStore } from '../../../store';
14
14
 
15
- describe('chatToolSlice', () => {
15
+ describe('chatToolSlice - dalle', () => {
16
16
  describe('generateImageFromPrompts', () => {
17
17
  it('should generate images from prompts, update items, and upload images', async () => {
18
18
  const { result } = renderHook(() => useChatStore());
@@ -0,0 +1,126 @@
1
+ import { produce } from 'immer';
2
+ import pMap from 'p-map';
3
+ import { SWRResponse } from 'swr';
4
+ import { StateCreator } from 'zustand/vanilla';
5
+
6
+ import { useClientDataSWR } from '@/libs/swr';
7
+ import { fileService } from '@/services/file';
8
+ import { imageGenerationService } from '@/services/textToImage';
9
+ import { uploadService } from '@/services/upload';
10
+ import { chatSelectors } from '@/store/chat/selectors';
11
+ import { ChatStore } from '@/store/chat/store';
12
+ import { useFileStore } from '@/store/file';
13
+ import { DallEImageItem } from '@/types/tool/dalle';
14
+
15
+
16
+ import { setNamespace } from '@/utils/storeDebug';
17
+
18
+ const n = setNamespace('tool');
19
+
20
+ const SWR_FETCH_KEY = 'FetchImageItem';
21
+
22
+ export interface ChatDallEAction {
23
+ generateImageFromPrompts: (items: DallEImageItem[], id: string) => Promise<void>;
24
+ text2image: (id: string, data: DallEImageItem[]) => Promise<void>;
25
+ toggleDallEImageLoading: (key: string, value: boolean) => void;
26
+ updateImageItem: (id: string, updater: (data: DallEImageItem[]) => void) => Promise<void>;
27
+ useFetchDalleImageItem: (id: string) => SWRResponse;
28
+ }
29
+
30
+ export const dalleSlice: StateCreator<
31
+ ChatStore,
32
+ [['zustand/devtools', never]],
33
+ [],
34
+ ChatDallEAction
35
+ > = (set, get) => ({
36
+ generateImageFromPrompts: async (items, messageId) => {
37
+ const { toggleDallEImageLoading, updateImageItem } = get();
38
+ // eslint-disable-next-line unicorn/consistent-function-scoping
39
+ const getMessageById = (id: string) => chatSelectors.getMessageById(id)(get());
40
+
41
+ const message = getMessageById(messageId);
42
+ if (!message) return;
43
+
44
+ const parent = getMessageById(message!.parentId!);
45
+ const originPrompt = parent?.content;
46
+ let errorArray: any[] = [];
47
+
48
+ await pMap(items, async (params, index) => {
49
+ toggleDallEImageLoading(messageId + params.prompt, true);
50
+
51
+ let url = '';
52
+ try {
53
+ url = await imageGenerationService.generateImage(params);
54
+ } catch (e) {
55
+ toggleDallEImageLoading(messageId + params.prompt, false);
56
+ errorArray[index] = e;
57
+
58
+ await get().updatePluginState(messageId, { error: errorArray });
59
+ }
60
+
61
+ if (!url) return;
62
+
63
+ await updateImageItem(messageId, (draft) => {
64
+ draft[index].previewUrl = url;
65
+ });
66
+
67
+ toggleDallEImageLoading(messageId + params.prompt, false);
68
+ const imageFile = await uploadService.getImageFileByUrlWithCORS(
69
+ url,
70
+ `${originPrompt || params.prompt}_${index}.png`,
71
+ );
72
+
73
+ const data = await useFileStore.getState().uploadWithProgress({
74
+ file: imageFile,
75
+ });
76
+
77
+ if (!data) return;
78
+
79
+ await updateImageItem(messageId, (draft) => {
80
+ draft[index].imageId = data.id;
81
+ draft[index].previewUrl = undefined;
82
+ });
83
+ });
84
+ },
85
+ text2image: async (id, data) => {
86
+ // const isAutoGen = settingsSelectors.isDalleAutoGenerating(useGlobalStore.getState());
87
+ // if (!isAutoGen) return;
88
+
89
+ await get().generateImageFromPrompts(data, id);
90
+ },
91
+
92
+ toggleDallEImageLoading: (key, value) => {
93
+ set(
94
+ { dalleImageLoading: { ...get().dalleImageLoading, [key]: value } },
95
+ false,
96
+ n('toggleDallEImageLoading'),
97
+ );
98
+ },
99
+
100
+ updateImageItem: async (id, updater) => {
101
+ const message = chatSelectors.getMessageById(id)(get());
102
+ if (!message) return;
103
+
104
+ const data: DallEImageItem[] = JSON.parse(message.content);
105
+
106
+ const nextContent = produce(data, updater);
107
+ await get().internal_updateMessageContent(id, JSON.stringify(nextContent));
108
+ },
109
+
110
+ useFetchDalleImageItem: (id) =>
111
+ useClientDataSWR([SWR_FETCH_KEY, id], async () => {
112
+ const item = await fileService.getFile(id);
113
+
114
+ set(
115
+ produce((draft) => {
116
+ if (draft.dalleImageMap[id]) return;
117
+
118
+ draft.dalleImageMap[id] = item;
119
+ }),
120
+ false,
121
+ n('useFetchFile'),
122
+ );
123
+
124
+ return item;
125
+ }),
126
+ });
@@ -0,0 +1,18 @@
1
+ import { StateCreator } from 'zustand/vanilla';
2
+
3
+ import { ChatStore } from '@/store/chat/store';
4
+
5
+ import { ChatDallEAction, dalleSlice } from './dalle';
6
+ import { SearchAction, searchSlice } from './searXNG';
7
+
8
+ export interface ChatBuiltinToolAction extends ChatDallEAction, SearchAction {}
9
+
10
+ export const chatToolSlice: StateCreator<
11
+ ChatStore,
12
+ [['zustand/devtools', never]],
13
+ [],
14
+ ChatBuiltinToolAction
15
+ > = (...params) => ({
16
+ ...dalleSlice(...params),
17
+ ...searchSlice(...params),
18
+ });
@@ -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
  };