@lobehub/chat 1.80.5 → 1.81.1
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/package.json +1 -1
- package/packages/electron-client-ipc/src/events/index.ts +6 -2
- package/packages/electron-client-ipc/src/events/remoteServer.ts +28 -0
- package/packages/electron-client-ipc/src/types/index.ts +1 -0
- package/packages/electron-client-ipc/src/types/remoteServer.ts +8 -0
- package/packages/electron-server-ipc/package.json +7 -1
- package/packages/electron-server-ipc/src/ipcClient.ts +54 -20
- package/packages/electron-server-ipc/src/ipcServer.ts +42 -9
- package/packages/web-crawler/src/crawImpl/__tests__/search1api.test.ts +33 -39
- package/packages/web-crawler/src/crawImpl/search1api.ts +1 -7
- package/packages/web-crawler/src/index.ts +1 -0
- package/packages/web-crawler/src/urlRules.ts +3 -1
- package/src/config/aiModels/sensenova.ts +120 -5
- package/src/config/tools.ts +2 -0
- package/src/features/Conversation/Messages/Assistant/Tool/Inspector/Debug.tsx +9 -3
- package/src/features/Conversation/Messages/Assistant/Tool/Inspector/PluginState.tsx +21 -0
- package/src/features/Conversation/Messages/Assistant/Tool/Render/Arguments.tsx +1 -1
- package/src/libs/agent-runtime/sensenova/index.ts +17 -4
- package/src/libs/agent-runtime/utils/sensenovaHelpers.test.ts +108 -0
- package/src/libs/agent-runtime/utils/sensenovaHelpers.ts +30 -0
- package/src/locales/default/plugin.ts +1 -0
- package/src/server/routers/tools/{__test__/search.test.ts → search.test.ts} +27 -5
- package/src/server/routers/tools/search.ts +3 -44
- package/src/server/services/search/impls/index.ts +30 -0
- package/src/server/services/search/impls/search1api/index.ts +154 -0
- package/src/server/services/search/impls/search1api/type.ts +81 -0
- package/src/server/{modules/SearXNG.ts → services/search/impls/searxng/client.ts} +32 -2
- package/src/server/{routers/tools/__tests__ → services/search/impls/searxng}/fixtures/searXNG.ts +2 -2
- package/src/server/services/search/impls/searxng/index.test.ts +26 -0
- package/src/server/services/search/impls/searxng/index.ts +62 -0
- package/src/server/services/search/impls/type.ts +11 -0
- package/src/server/services/search/index.ts +59 -0
- package/src/store/chat/slices/builtinTool/actions/index.ts +1 -1
- package/src/store/chat/slices/builtinTool/actions/{searXNG.test.ts → search.test.ts} +30 -55
- package/src/store/chat/slices/builtinTool/actions/{searXNG.ts → search.ts} +25 -32
- package/src/tools/web-browsing/Portal/Search/Footer.tsx +1 -1
- package/src/tools/web-browsing/Portal/Search/ResultList/SearchItem/TitleExtra.tsx +2 -2
- package/src/tools/web-browsing/Portal/Search/ResultList/SearchItem/Video.tsx +9 -7
- package/src/tools/web-browsing/Portal/Search/ResultList/SearchItem/index.tsx +2 -2
- package/src/tools/web-browsing/Portal/Search/ResultList/index.tsx +3 -3
- package/src/tools/web-browsing/Portal/Search/index.tsx +4 -4
- package/src/tools/web-browsing/Portal/index.tsx +3 -1
- package/src/tools/web-browsing/Render/Search/SearchQuery/SearchView.tsx +4 -2
- package/src/tools/web-browsing/Render/Search/SearchQuery/index.tsx +6 -13
- package/src/tools/web-browsing/Render/Search/SearchResult/SearchResultItem.tsx +2 -2
- package/src/tools/web-browsing/Render/Search/SearchResult/index.tsx +5 -5
- package/src/tools/web-browsing/Render/Search/index.tsx +2 -2
- package/src/tools/web-browsing/Render/index.tsx +4 -3
- package/src/tools/web-browsing/components/SearchBar.tsx +4 -6
- package/src/tools/web-browsing/index.ts +54 -60
- package/src/tools/web-browsing/systemRole.ts +22 -13
- package/src/types/tool/search/index.ts +44 -0
- package/src/server/routers/tools/__tests__/search.test.ts +0 -48
- package/src/types/tool/search.ts +0 -48
@@ -6,7 +6,7 @@ import { useChatStore } from '@/store/chat';
|
|
6
6
|
import { chatSelectors } from '@/store/chat/selectors';
|
7
7
|
import { CRAWL_CONTENT_LIMITED_COUNT } from '@/tools/web-browsing/const';
|
8
8
|
import { ChatMessage } from '@/types/message';
|
9
|
-
import { SearchContent, SearchQuery,
|
9
|
+
import { SearchContent, SearchQuery, UniformSearchResponse } from '@/types/tool/search';
|
10
10
|
|
11
11
|
// Mock services
|
12
12
|
vi.mock('@/services/search', () => ({
|
@@ -22,7 +22,7 @@ vi.mock('@/store/chat/selectors', () => ({
|
|
22
22
|
},
|
23
23
|
}));
|
24
24
|
|
25
|
-
describe('
|
25
|
+
describe('search actions', () => {
|
26
26
|
beforeEach(() => {
|
27
27
|
vi.clearAllMocks();
|
28
28
|
useChatStore.setState({
|
@@ -39,47 +39,38 @@ describe('searXNG actions', () => {
|
|
39
39
|
});
|
40
40
|
});
|
41
41
|
|
42
|
-
describe('
|
42
|
+
describe('search', () => {
|
43
43
|
it('should handle successful search', async () => {
|
44
|
-
const mockResponse:
|
44
|
+
const mockResponse: UniformSearchResponse = {
|
45
45
|
results: [
|
46
46
|
{
|
47
47
|
title: 'Test Result',
|
48
48
|
content: 'Test Content',
|
49
49
|
url: 'https://test.com',
|
50
50
|
category: 'general',
|
51
|
-
engine: 'google',
|
52
51
|
engines: ['google'],
|
53
|
-
|
54
|
-
positions: [1],
|
52
|
+
parsedUrl: 'test.com',
|
55
53
|
score: 1,
|
56
|
-
template: 'default',
|
57
54
|
},
|
58
55
|
],
|
59
|
-
|
60
|
-
|
61
|
-
infoboxes: [],
|
62
|
-
number_of_results: 1,
|
56
|
+
costTime: 1,
|
57
|
+
resultNumbers: 1,
|
63
58
|
query: 'test',
|
64
|
-
suggestions: [],
|
65
|
-
unresponsive_engines: [],
|
66
59
|
};
|
67
60
|
|
68
61
|
(searchService.search as Mock).mockResolvedValue(mockResponse);
|
69
62
|
|
70
63
|
const { result } = renderHook(() => useChatStore());
|
71
|
-
const {
|
64
|
+
const { search } = result.current;
|
72
65
|
|
73
66
|
const messageId = 'test-message-id';
|
74
67
|
const query: SearchQuery = {
|
75
|
-
|
76
|
-
searchEngines: ['google'],
|
77
|
-
},
|
68
|
+
searchEngines: ['google'],
|
78
69
|
query: 'test query',
|
79
70
|
};
|
80
71
|
|
81
72
|
await act(async () => {
|
82
|
-
await
|
73
|
+
await search(messageId, query);
|
83
74
|
});
|
84
75
|
|
85
76
|
const expectedContent: SearchContent[] = [
|
@@ -101,39 +92,28 @@ describe('searXNG actions', () => {
|
|
101
92
|
});
|
102
93
|
|
103
94
|
it('should handle empty search results and retry with default engine', async () => {
|
104
|
-
const emptyResponse:
|
95
|
+
const emptyResponse: UniformSearchResponse = {
|
105
96
|
results: [],
|
106
|
-
|
107
|
-
|
108
|
-
infoboxes: [],
|
109
|
-
number_of_results: 0,
|
97
|
+
costTime: 1,
|
98
|
+
resultNumbers: 0,
|
110
99
|
query: 'test',
|
111
|
-
suggestions: [],
|
112
|
-
unresponsive_engines: [],
|
113
100
|
};
|
114
101
|
|
115
|
-
const retryResponse:
|
102
|
+
const retryResponse: UniformSearchResponse = {
|
116
103
|
results: [
|
117
104
|
{
|
118
105
|
title: 'Retry Result',
|
119
106
|
content: 'Retry Content',
|
120
107
|
url: 'https://retry.com',
|
121
108
|
category: 'general',
|
122
|
-
engine: 'google',
|
123
109
|
engines: ['google'],
|
124
|
-
|
125
|
-
positions: [1],
|
110
|
+
parsedUrl: 'retry.com',
|
126
111
|
score: 1,
|
127
|
-
template: 'default',
|
128
112
|
},
|
129
113
|
],
|
130
|
-
|
131
|
-
|
132
|
-
infoboxes: [],
|
133
|
-
number_of_results: 1,
|
114
|
+
costTime: 1,
|
115
|
+
resultNumbers: 1,
|
134
116
|
query: 'test',
|
135
|
-
suggestions: [],
|
136
|
-
unresponsive_engines: [],
|
137
117
|
};
|
138
118
|
|
139
119
|
(searchService.search as Mock)
|
@@ -142,19 +122,17 @@ describe('searXNG actions', () => {
|
|
142
122
|
.mockResolvedValueOnce(retryResponse);
|
143
123
|
|
144
124
|
const { result } = renderHook(() => useChatStore());
|
145
|
-
const {
|
125
|
+
const { search } = result.current;
|
146
126
|
|
147
127
|
const messageId = 'test-message-id';
|
148
128
|
const query: SearchQuery = {
|
149
|
-
|
150
|
-
|
151
|
-
searchTimeRange: 'year',
|
152
|
-
},
|
129
|
+
searchEngines: ['custom-engine'],
|
130
|
+
searchTimeRange: 'year',
|
153
131
|
query: 'test query',
|
154
132
|
};
|
155
133
|
|
156
134
|
await act(async () => {
|
157
|
-
await
|
135
|
+
await search(messageId, query);
|
158
136
|
});
|
159
137
|
|
160
138
|
expect(searchService.search).toHaveBeenCalledTimes(3);
|
@@ -166,9 +144,6 @@ describe('searXNG actions', () => {
|
|
166
144
|
searchTimeRange: 'year',
|
167
145
|
});
|
168
146
|
expect(result.current.updatePluginArguments).toHaveBeenCalledWith(messageId, {
|
169
|
-
optionalParams: {
|
170
|
-
searchTimeRange: 'year',
|
171
|
-
},
|
172
147
|
query: 'test query',
|
173
148
|
});
|
174
149
|
expect(searchService.search).toHaveBeenNthCalledWith(3, 'test query');
|
@@ -183,7 +158,7 @@ describe('searXNG actions', () => {
|
|
183
158
|
(searchService.search as Mock).mockRejectedValue(error);
|
184
159
|
|
185
160
|
const { result } = renderHook(() => useChatStore());
|
186
|
-
const {
|
161
|
+
const { search } = result.current;
|
187
162
|
|
188
163
|
const messageId = 'test-message-id';
|
189
164
|
const query: SearchQuery = {
|
@@ -191,7 +166,7 @@ describe('searXNG actions', () => {
|
|
191
166
|
};
|
192
167
|
|
193
168
|
await act(async () => {
|
194
|
-
await
|
169
|
+
await search(messageId, query);
|
195
170
|
});
|
196
171
|
|
197
172
|
expect(result.current.internal_updateMessagePluginError).toHaveBeenCalledWith(messageId, {
|
@@ -271,8 +246,8 @@ describe('searXNG actions', () => {
|
|
271
246
|
describe('reSearchWithSearXNG', () => {
|
272
247
|
it('should update arguments and perform search', async () => {
|
273
248
|
const { result } = renderHook(() => useChatStore());
|
274
|
-
const spy = vi.spyOn(result.current, '
|
275
|
-
const {
|
249
|
+
const spy = vi.spyOn(result.current, 'search');
|
250
|
+
const { triggerSearchAgain } = result.current;
|
276
251
|
|
277
252
|
const messageId = 'test-message-id';
|
278
253
|
const query: SearchQuery = {
|
@@ -280,7 +255,7 @@ describe('searXNG actions', () => {
|
|
280
255
|
};
|
281
256
|
|
282
257
|
await act(async () => {
|
283
|
-
await
|
258
|
+
await triggerSearchAgain(messageId, query, { aiSummary: true });
|
284
259
|
});
|
285
260
|
|
286
261
|
expect(result.current.updatePluginArguments).toHaveBeenCalledWith(messageId, query);
|
@@ -314,10 +289,10 @@ describe('searXNG actions', () => {
|
|
314
289
|
);
|
315
290
|
|
316
291
|
const { result } = renderHook(() => useChatStore());
|
317
|
-
const {
|
292
|
+
const { saveSearchResult } = result.current;
|
318
293
|
|
319
294
|
await act(async () => {
|
320
|
-
await
|
295
|
+
await saveSearchResult(messageId);
|
321
296
|
});
|
322
297
|
|
323
298
|
expect(result.current.internal_createMessage).toHaveBeenCalledWith(
|
@@ -343,10 +318,10 @@ describe('searXNG actions', () => {
|
|
343
318
|
vi.spyOn(chatSelectors, 'getMessageById').mockImplementation(() => () => undefined);
|
344
319
|
|
345
320
|
const { result } = renderHook(() => useChatStore());
|
346
|
-
const {
|
321
|
+
const { saveSearchResult } = result.current;
|
347
322
|
|
348
323
|
await act(async () => {
|
349
|
-
await
|
324
|
+
await saveSearchResult('non-existent-id');
|
350
325
|
});
|
351
326
|
|
352
327
|
expect(result.current.internal_createMessage).not.toHaveBeenCalled();
|
@@ -9,7 +9,7 @@ import {
|
|
9
9
|
SEARCH_SEARXNG_NOT_CONFIG,
|
10
10
|
SearchContent,
|
11
11
|
SearchQuery,
|
12
|
-
|
12
|
+
UniformSearchResponse,
|
13
13
|
} from '@/types/tool/search';
|
14
14
|
import { nanoid } from '@/utils/uuid';
|
15
15
|
|
@@ -24,23 +24,19 @@ export interface SearchAction {
|
|
24
24
|
params: { url: string },
|
25
25
|
aiSummary?: boolean,
|
26
26
|
) => Promise<boolean | undefined>;
|
27
|
+
saveSearchResult: (id: string) => Promise<void>;
|
28
|
+
search: (id: string, data: SearchQuery, aiSummary?: boolean) => Promise<void | boolean>;
|
29
|
+
togglePageContent: (url: string) => void;
|
30
|
+
toggleSearchLoading: (id: string, loading: boolean) => void;
|
27
31
|
/**
|
28
32
|
* 重新发起搜索
|
29
33
|
* @description 会更新插件的 arguments 参数,然后再次搜索
|
30
34
|
*/
|
31
|
-
|
35
|
+
triggerSearchAgain: (
|
32
36
|
id: string,
|
33
37
|
data: SearchQuery,
|
34
38
|
options?: { aiSummary: boolean },
|
35
39
|
) => Promise<void>;
|
36
|
-
saveSearXNGSearchResult: (id: string) => Promise<void>;
|
37
|
-
searchWithSearXNG: (
|
38
|
-
id: string,
|
39
|
-
data: SearchQuery,
|
40
|
-
aiSummary?: boolean,
|
41
|
-
) => Promise<void | boolean>;
|
42
|
-
togglePageContent: (url: string) => void;
|
43
|
-
toggleSearchLoading: (id: string, loading: boolean) => void;
|
44
40
|
}
|
45
41
|
|
46
42
|
export const searchSlice: StateCreator<
|
@@ -91,14 +87,7 @@ export const searchSlice: StateCreator<
|
|
91
87
|
return await crawlMultiPages(id, { urls: [params.url] }, aiSummary);
|
92
88
|
},
|
93
89
|
|
94
|
-
|
95
|
-
get().toggleSearchLoading(id, true);
|
96
|
-
await get().updatePluginArguments(id, data);
|
97
|
-
|
98
|
-
await get().searchWithSearXNG(id, data, options?.aiSummary);
|
99
|
-
},
|
100
|
-
|
101
|
-
saveSearXNGSearchResult: async (id) => {
|
90
|
+
saveSearchResult: async (id) => {
|
102
91
|
const message = chatSelectors.getMessageById(id)(get());
|
103
92
|
if (!message || !message.plugin) return;
|
104
93
|
|
@@ -138,34 +127,32 @@ export const searchSlice: StateCreator<
|
|
138
127
|
// 将新创建的 tool message 激活
|
139
128
|
openToolUI(newMessageId, message.plugin.identifier);
|
140
129
|
},
|
141
|
-
|
130
|
+
|
131
|
+
search: async (id, { query, ...params }, aiSummary = true) => {
|
142
132
|
get().toggleSearchLoading(id, true);
|
143
|
-
let data:
|
133
|
+
let data: UniformSearchResponse | undefined;
|
144
134
|
try {
|
145
135
|
// 首次查询
|
146
|
-
data = await searchService.search(
|
136
|
+
data = await searchService.search(query, params);
|
147
137
|
|
148
138
|
// 如果没有搜索到结果,则执行第一次重试(移除搜索引擎限制)
|
149
139
|
if (
|
150
140
|
data?.results.length === 0 &&
|
151
|
-
params
|
152
|
-
params
|
141
|
+
params?.searchEngines &&
|
142
|
+
params?.searchEngines?.length > 0
|
153
143
|
) {
|
154
144
|
const paramsExcludeSearchEngines = {
|
155
145
|
...params,
|
156
|
-
|
157
|
-
...params.optionalParams,
|
158
|
-
searchEngines: undefined,
|
159
|
-
},
|
146
|
+
searchEngines: undefined,
|
160
147
|
};
|
161
|
-
data = await searchService.search(
|
148
|
+
data = await searchService.search(query, paramsExcludeSearchEngines);
|
162
149
|
get().updatePluginArguments(id, paramsExcludeSearchEngines);
|
163
150
|
}
|
164
151
|
|
165
152
|
// 如果仍然没有搜索到结果,则执行第二次重试(移除所有限制)
|
166
153
|
if (data?.results.length === 0) {
|
167
|
-
data = await searchService.search(
|
168
|
-
get().updatePluginArguments(id, {
|
154
|
+
data = await searchService.search(query);
|
155
|
+
get().updatePluginArguments(id, { query });
|
169
156
|
}
|
170
157
|
|
171
158
|
await get().updatePluginState(id, data);
|
@@ -197,7 +184,7 @@ export const searchSlice: StateCreator<
|
|
197
184
|
url: item.url,
|
198
185
|
...(item.content && { content: item.content }),
|
199
186
|
...(item.publishedDate && { publishedDate: item.publishedDate }),
|
200
|
-
...(item.
|
187
|
+
...(item.imgSrc && { imgSrc: item.imgSrc }),
|
201
188
|
...(item.thumbnail && { thumbnail: item.thumbnail }),
|
202
189
|
}));
|
203
190
|
|
@@ -209,7 +196,6 @@ export const searchSlice: StateCreator<
|
|
209
196
|
// 如果 aiSummary 为 true,则会自动触发总结
|
210
197
|
return aiSummary;
|
211
198
|
},
|
212
|
-
|
213
199
|
togglePageContent: (url) => {
|
214
200
|
set({ activePageContentUrl: url });
|
215
201
|
},
|
@@ -221,4 +207,11 @@ export const searchSlice: StateCreator<
|
|
221
207
|
`toggleSearchLoading/${loading ? 'start' : 'end'}`,
|
222
208
|
);
|
223
209
|
},
|
210
|
+
|
211
|
+
triggerSearchAgain: async (id, data, options) => {
|
212
|
+
get().toggleSearchLoading(id, true);
|
213
|
+
await get().updatePluginArguments(id, data);
|
214
|
+
|
215
|
+
await get().search(id, data, options?.aiSummary);
|
216
|
+
},
|
224
217
|
});
|
@@ -9,7 +9,7 @@ import { EngineAvatarGroup } from '@/tools/web-browsing/components/EngineAvatar'
|
|
9
9
|
import CategoryAvatar from './CategoryAvatar';
|
10
10
|
|
11
11
|
interface TitleExtraProps {
|
12
|
-
category
|
12
|
+
category?: string;
|
13
13
|
engines: string[];
|
14
14
|
highlight?: boolean;
|
15
15
|
score: number;
|
@@ -35,7 +35,7 @@ const TitleExtra = memo<TitleExtraProps>(({ category, score, highlight, engines
|
|
35
35
|
</Typography.Text>
|
36
36
|
)}
|
37
37
|
</Tooltip>
|
38
|
-
<CategoryAvatar category={category} />
|
38
|
+
<CategoryAvatar category={category || 'general'} />
|
39
39
|
</Flexbox>
|
40
40
|
);
|
41
41
|
});
|
@@ -3,7 +3,7 @@ import { createStyles } from 'antd-style';
|
|
3
3
|
import { memo, useState } from 'react';
|
4
4
|
import { Flexbox } from 'react-layout-kit';
|
5
5
|
|
6
|
-
import {
|
6
|
+
import { UniformSearchResult } from '@/types/tool/search';
|
7
7
|
|
8
8
|
import { ENGINE_ICON_MAP } from '../../../../const';
|
9
9
|
import TitleExtra from './TitleExtra';
|
@@ -61,19 +61,21 @@ const useStyles = createStyles(({ css, token }) => {
|
|
61
61
|
};
|
62
62
|
});
|
63
63
|
|
64
|
-
interface SearchResultProps extends
|
64
|
+
interface SearchResultProps extends UniformSearchResult {
|
65
65
|
highlight?: boolean;
|
66
66
|
}
|
67
67
|
const VideoItem = memo<SearchResultProps>(
|
68
|
-
({ content, url,
|
68
|
+
({ content, url, iframeSrc, highlight, score, engines, title, category, ...res }) => {
|
69
69
|
const { styles, theme } = useStyles();
|
70
70
|
|
71
71
|
const [expand, setExpand] = useState(false);
|
72
|
+
|
73
|
+
const videoUrl = iframeSrc || (res as any).iframe_src; // iframe_src 是 SearchXNG 的字段,兼容老的数据结构
|
72
74
|
return (
|
73
75
|
<Flexbox gap={12}>
|
74
76
|
<Flexbox className={styles.container} onClick={() => setExpand(!expand)}>
|
75
77
|
<Flexbox flex={1} gap={8} horizontal padding={12}>
|
76
|
-
{
|
78
|
+
{videoUrl && (
|
77
79
|
<Flexbox>
|
78
80
|
<iframe
|
79
81
|
// alt={title}
|
@@ -86,7 +88,7 @@ const VideoItem = memo<SearchResultProps>(
|
|
86
88
|
onPlay={(e) => {
|
87
89
|
e.preventDefault();
|
88
90
|
}}
|
89
|
-
src={
|
91
|
+
src={videoUrl}
|
90
92
|
style={{
|
91
93
|
pointerEvents: 'none',
|
92
94
|
}}
|
@@ -127,9 +129,9 @@ const VideoItem = memo<SearchResultProps>(
|
|
127
129
|
</Flexbox>
|
128
130
|
</Flexbox>
|
129
131
|
</Flexbox>
|
130
|
-
{expand &&
|
132
|
+
{expand && videoUrl && (
|
131
133
|
<Flexbox>
|
132
|
-
<iframe className={styles.iframe} height={440} src={
|
134
|
+
<iframe className={styles.iframe} height={440} src={videoUrl} width={'100%'} />
|
133
135
|
</Flexbox>
|
134
136
|
)}
|
135
137
|
</Flexbox>
|
@@ -4,7 +4,7 @@ import { memo } from 'react';
|
|
4
4
|
import { Flexbox } from 'react-layout-kit';
|
5
5
|
|
6
6
|
import WebFavicon from '@/components/WebFavicon';
|
7
|
-
import {
|
7
|
+
import { UniformSearchResult } from '@/types/tool/search';
|
8
8
|
|
9
9
|
import TitleExtra from './TitleExtra';
|
10
10
|
import Video from './Video';
|
@@ -52,7 +52,7 @@ const useStyles = createStyles(({ css, token }) => {
|
|
52
52
|
};
|
53
53
|
});
|
54
54
|
|
55
|
-
interface SearchResultProps extends
|
55
|
+
interface SearchResultProps extends UniformSearchResult {
|
56
56
|
highlight?: boolean;
|
57
57
|
}
|
58
58
|
|
@@ -1,17 +1,17 @@
|
|
1
1
|
import React, { memo, useCallback } from 'react';
|
2
2
|
import { Virtuoso } from 'react-virtuoso';
|
3
3
|
|
4
|
-
import {
|
4
|
+
import { UniformSearchResult } from '@/types/tool/search';
|
5
5
|
|
6
6
|
import Item from './SearchItem';
|
7
7
|
|
8
8
|
interface ResultListProps {
|
9
|
-
dataSources:
|
9
|
+
dataSources: UniformSearchResult[];
|
10
10
|
}
|
11
11
|
|
12
12
|
const ResultList = memo<ResultListProps>(({ dataSources }) => {
|
13
13
|
const itemContent = useCallback(
|
14
|
-
(index: number, result:
|
14
|
+
(index: number, result: UniformSearchResult) => <Item {...result} highlight={index < 15} />,
|
15
15
|
[],
|
16
16
|
);
|
17
17
|
|
@@ -5,7 +5,7 @@ import { Flexbox } from 'react-layout-kit';
|
|
5
5
|
|
6
6
|
import { useChatStore } from '@/store/chat';
|
7
7
|
import { chatToolSelectors } from '@/store/chat/selectors';
|
8
|
-
import { SearchQuery,
|
8
|
+
import { SearchQuery, UniformSearchResponse } from '@/types/tool/search';
|
9
9
|
|
10
10
|
import SearchBar from '../../components/SearchBar';
|
11
11
|
import Footer from './Footer';
|
@@ -14,12 +14,12 @@ import ResultList from './ResultList';
|
|
14
14
|
interface InspectorUIProps {
|
15
15
|
messageId: string;
|
16
16
|
query: SearchQuery;
|
17
|
-
response:
|
17
|
+
response: UniformSearchResponse;
|
18
18
|
}
|
19
19
|
|
20
20
|
const Inspector = memo<InspectorUIProps>(({ query: args, messageId, response }) => {
|
21
|
-
const engines = uniq((response.results || []).
|
22
|
-
const defaultEngines = engines.length > 0 ? engines : args
|
21
|
+
const engines = uniq((response.results || []).flatMap((result) => result.engines));
|
22
|
+
const defaultEngines = engines.length > 0 ? engines : args?.searchEngines || [];
|
23
23
|
const loading = useChatStore(chatToolSelectors.isSearXNGSearching(messageId));
|
24
24
|
|
25
25
|
if (loading) {
|
@@ -11,7 +11,9 @@ import Search from './Search';
|
|
11
11
|
|
12
12
|
const Inspector = memo<BuiltinPortalProps>(({ arguments: args, messageId, state, apiName }) => {
|
13
13
|
switch (apiName) {
|
14
|
-
|
14
|
+
// 兼容旧版数据
|
15
|
+
case 'searchWithSearXNG':
|
16
|
+
case WebBrowsingApiName.search: {
|
15
17
|
return <Search messageId={messageId} query={args as SearchQuery} response={state} />;
|
16
18
|
}
|
17
19
|
|
@@ -7,6 +7,7 @@ import { useTranslation } from 'react-i18next';
|
|
7
7
|
import { Flexbox } from 'react-layout-kit';
|
8
8
|
|
9
9
|
import { useIsMobile } from '@/hooks/useIsMobile';
|
10
|
+
import { shinyTextStylish } from '@/styles/loading';
|
10
11
|
|
11
12
|
import { EngineAvatarGroup } from '../../../components/EngineAvatar';
|
12
13
|
|
@@ -29,6 +30,7 @@ const useStyles = createStyles(({ css, token }) => ({
|
|
29
30
|
background: ${token.colorFillTertiary};
|
30
31
|
}
|
31
32
|
`,
|
33
|
+
shinyText: shinyTextStylish(token),
|
32
34
|
}));
|
33
35
|
|
34
36
|
interface SearchBarProps {
|
@@ -43,7 +45,7 @@ const SearchBar = memo<SearchBarProps>(
|
|
43
45
|
({ defaultEngines, defaultQuery, resultsNumber, onEditingChange, searching }) => {
|
44
46
|
const { t } = useTranslation('tool');
|
45
47
|
const isMobile = useIsMobile();
|
46
|
-
const { styles } = useStyles();
|
48
|
+
const { styles, cx } = useStyles();
|
47
49
|
return (
|
48
50
|
<Flexbox
|
49
51
|
align={isMobile ? 'flex-start' : 'center'}
|
@@ -54,7 +56,7 @@ const SearchBar = memo<SearchBarProps>(
|
|
54
56
|
>
|
55
57
|
<Flexbox
|
56
58
|
align={'center'}
|
57
|
-
className={styles.query}
|
59
|
+
className={cx(styles.query, searching && styles.shinyText)}
|
58
60
|
gap={8}
|
59
61
|
horizontal
|
60
62
|
onClick={() => {
|
@@ -1,14 +1,12 @@
|
|
1
1
|
import { ActionIcon } from '@lobehub/ui';
|
2
|
-
import { Skeleton } from 'antd';
|
3
2
|
import { uniq } from 'lodash-es';
|
4
3
|
import { XIcon } from 'lucide-react';
|
5
4
|
import { memo } from 'react';
|
6
5
|
import { useTranslation } from 'react-i18next';
|
7
|
-
import { Flexbox } from 'react-layout-kit';
|
8
6
|
|
9
7
|
import { useChatStore } from '@/store/chat';
|
10
8
|
import { chatToolSelectors } from '@/store/chat/selectors';
|
11
|
-
import { SearchQuery,
|
9
|
+
import { SearchQuery, UniformSearchResponse } from '@/types/tool/search';
|
12
10
|
|
13
11
|
import SearchBar from '../../../components/SearchBar';
|
14
12
|
import SearchView from './SearchView';
|
@@ -17,7 +15,7 @@ interface SearchQueryViewProps {
|
|
17
15
|
args: SearchQuery;
|
18
16
|
editing: boolean;
|
19
17
|
messageId: string;
|
20
|
-
pluginState?:
|
18
|
+
pluginState?: UniformSearchResponse;
|
21
19
|
setEditing: (editing: boolean) => void;
|
22
20
|
}
|
23
21
|
|
@@ -28,15 +26,10 @@ const SearchQueryView = memo<SearchQueryViewProps>(
|
|
28
26
|
|
29
27
|
const { t } = useTranslation('common');
|
30
28
|
|
31
|
-
const engines = uniq(searchResults.
|
32
|
-
const defaultEngines = engines.length > 0 ? engines : args.
|
29
|
+
const engines = uniq(searchResults.flatMap((result) => result.engines));
|
30
|
+
const defaultEngines = engines.length > 0 ? engines : args.searchEngines || [];
|
33
31
|
|
34
|
-
return
|
35
|
-
<Flexbox align={'center'} distribution={'space-between'} height={32} horizontal>
|
36
|
-
<Skeleton.Button active style={{ borderRadius: 4, height: 32, width: 180 }} />
|
37
|
-
<Skeleton.Button active style={{ borderRadius: 4, height: 32, width: 220 }} />
|
38
|
-
</Flexbox>
|
39
|
-
) : editing ? (
|
32
|
+
return editing ? (
|
40
33
|
<SearchBar
|
41
34
|
defaultEngines={defaultEngines}
|
42
35
|
defaultQuery={args?.query}
|
@@ -52,7 +45,7 @@ const SearchQueryView = memo<SearchQueryViewProps>(
|
|
52
45
|
defaultQuery={args?.query}
|
53
46
|
onEditingChange={setEditing}
|
54
47
|
resultsNumber={searchResults.length}
|
55
|
-
searching={loading}
|
48
|
+
searching={loading || !pluginState}
|
56
49
|
/>
|
57
50
|
);
|
58
51
|
},
|
@@ -5,7 +5,7 @@ import { memo } from 'react';
|
|
5
5
|
import { Flexbox } from 'react-layout-kit';
|
6
6
|
|
7
7
|
import WebFavicon from '@/components/WebFavicon';
|
8
|
-
import {
|
8
|
+
import { UniformSearchResult } from '@/types/tool/search';
|
9
9
|
|
10
10
|
const useStyles = createStyles(({ css, token }) => ({
|
11
11
|
container: css`
|
@@ -43,7 +43,7 @@ const useStyles = createStyles(({ css, token }) => ({
|
|
43
43
|
`,
|
44
44
|
}));
|
45
45
|
|
46
|
-
const SearchResultItem = memo<
|
46
|
+
const SearchResultItem = memo<UniformSearchResult>(({ url, title }) => {
|
47
47
|
const { styles } = useStyles();
|
48
48
|
|
49
49
|
const urlObj = new URL(url);
|
@@ -9,7 +9,7 @@ import { Center, Flexbox } from 'react-layout-kit';
|
|
9
9
|
import { useIsMobile } from '@/hooks/useIsMobile';
|
10
10
|
import { useChatStore } from '@/store/chat';
|
11
11
|
import { chatToolSelectors } from '@/store/chat/selectors';
|
12
|
-
import { SearchQuery,
|
12
|
+
import { SearchQuery, UniformSearchResponse } from '@/types/tool/search';
|
13
13
|
|
14
14
|
import SearchResultItem from './SearchResultItem';
|
15
15
|
import ShowMore from './ShowMore';
|
@@ -21,7 +21,7 @@ interface SearchResultProps {
|
|
21
21
|
args: SearchQuery;
|
22
22
|
editing: boolean;
|
23
23
|
messageId: string;
|
24
|
-
pluginState?:
|
24
|
+
pluginState?: UniformSearchResponse;
|
25
25
|
setEditing: (editing: boolean) => void;
|
26
26
|
}
|
27
27
|
|
@@ -31,14 +31,14 @@ const SearchResult = memo<SearchResultProps>(
|
|
31
31
|
const searchResults = pluginState?.results || [];
|
32
32
|
const { t } = useTranslation(['tool', 'common']);
|
33
33
|
|
34
|
-
const engines = uniq(searchResults.
|
35
|
-
const defaultEngines = engines.length > 0 ? engines : args
|
34
|
+
const engines = uniq(searchResults.flatMap((result) => result.engines));
|
35
|
+
const defaultEngines = engines.length > 0 ? engines : args?.searchEngines || [];
|
36
36
|
const isMobile = useIsMobile();
|
37
37
|
|
38
38
|
if (loading || !pluginState)
|
39
39
|
return (
|
40
40
|
<Flexbox gap={12} horizontal>
|
41
|
-
{['1', '2', '3', '4'].map((id) => (
|
41
|
+
{['1', '2', '3', '4', '5'].map((id) => (
|
42
42
|
<Skeleton.Button
|
43
43
|
active
|
44
44
|
key={id}
|
@@ -3,7 +3,7 @@ import { memo, useState } from 'react';
|
|
3
3
|
import { Flexbox } from 'react-layout-kit';
|
4
4
|
|
5
5
|
import { ChatMessagePluginError } from '@/types/message';
|
6
|
-
import { SearchQuery,
|
6
|
+
import { SearchQuery, UniformSearchResponse } from '@/types/tool/search';
|
7
7
|
|
8
8
|
import ConfigForm from './ConfigForm';
|
9
9
|
import SearchQueryView from './SearchQuery';
|
@@ -13,7 +13,7 @@ interface SearchProps {
|
|
13
13
|
messageId: string;
|
14
14
|
pluginError: ChatMessagePluginError;
|
15
15
|
searchQuery: SearchQuery;
|
16
|
-
searchResponse?:
|
16
|
+
searchResponse?: UniformSearchResponse;
|
17
17
|
}
|
18
18
|
|
19
19
|
const Search = memo<SearchProps>(({ messageId, searchQuery, searchResponse, pluginError }) => {
|