@lobehub/chat 1.63.3 → 1.64.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 +25 -0
- package/changelog/v1.json +9 -0
- package/locales/ar/models.json +25 -16
- package/locales/ar/plugin.json +16 -0
- package/locales/ar/portal.json +0 -5
- package/locales/ar/tool.json +18 -0
- package/locales/bg-BG/models.json +25 -16
- package/locales/bg-BG/plugin.json +16 -0
- package/locales/bg-BG/portal.json +0 -5
- package/locales/bg-BG/tool.json +18 -0
- package/locales/de-DE/models.json +25 -16
- package/locales/de-DE/plugin.json +16 -0
- package/locales/de-DE/portal.json +0 -5
- package/locales/de-DE/tool.json +18 -0
- package/locales/en-US/models.json +24 -15
- package/locales/en-US/plugin.json +16 -0
- package/locales/en-US/portal.json +0 -5
- package/locales/en-US/tool.json +18 -0
- package/locales/es-ES/models.json +25 -16
- package/locales/es-ES/plugin.json +16 -0
- package/locales/es-ES/portal.json +0 -5
- package/locales/es-ES/tool.json +18 -0
- package/locales/fa-IR/models.json +25 -16
- package/locales/fa-IR/plugin.json +16 -0
- package/locales/fa-IR/portal.json +0 -5
- package/locales/fa-IR/tool.json +18 -0
- package/locales/fr-FR/models.json +25 -16
- package/locales/fr-FR/plugin.json +16 -0
- package/locales/fr-FR/portal.json +0 -5
- package/locales/fr-FR/tool.json +18 -0
- package/locales/it-IT/models.json +25 -16
- package/locales/it-IT/plugin.json +16 -0
- package/locales/it-IT/portal.json +0 -5
- package/locales/it-IT/tool.json +18 -0
- package/locales/ja-JP/models.json +24 -15
- package/locales/ja-JP/plugin.json +16 -0
- package/locales/ja-JP/portal.json +0 -5
- package/locales/ja-JP/tool.json +18 -0
- package/locales/ko-KR/models.json +25 -16
- package/locales/ko-KR/plugin.json +16 -0
- package/locales/ko-KR/portal.json +0 -5
- package/locales/ko-KR/tool.json +18 -0
- package/locales/nl-NL/models.json +25 -16
- package/locales/nl-NL/plugin.json +16 -0
- package/locales/nl-NL/portal.json +0 -5
- package/locales/nl-NL/tool.json +18 -0
- package/locales/pl-PL/models.json +25 -16
- package/locales/pl-PL/plugin.json +16 -0
- package/locales/pl-PL/portal.json +0 -5
- package/locales/pl-PL/tool.json +18 -0
- package/locales/pt-BR/models.json +24 -15
- package/locales/pt-BR/plugin.json +16 -0
- package/locales/pt-BR/portal.json +0 -5
- package/locales/pt-BR/tool.json +18 -0
- package/locales/ru-RU/models.json +25 -16
- package/locales/ru-RU/plugin.json +16 -0
- package/locales/ru-RU/portal.json +0 -5
- package/locales/ru-RU/tool.json +18 -0
- package/locales/tr-TR/models.json +25 -16
- package/locales/tr-TR/plugin.json +16 -0
- package/locales/tr-TR/portal.json +0 -5
- package/locales/tr-TR/tool.json +18 -0
- package/locales/vi-VN/models.json +24 -15
- package/locales/vi-VN/plugin.json +16 -0
- package/locales/vi-VN/portal.json +0 -5
- package/locales/vi-VN/tool.json +18 -0
- package/locales/zh-CN/models.json +30 -21
- package/locales/zh-CN/plugin.json +16 -0
- package/locales/zh-CN/portal.json +1 -6
- package/locales/zh-CN/tool.json +19 -1
- package/locales/zh-TW/models.json +23 -14
- package/locales/zh-TW/plugin.json +16 -0
- package/locales/zh-TW/portal.json +0 -5
- package/locales/zh-TW/tool.json +18 -0
- package/package.json +1 -1
- package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/Mobile/index.tsx +1 -0
- package/src/app/[variants]/(main)/chat/(workspace)/_layout/Desktop/ChatHeader/SearchTags.tsx +17 -0
- package/src/app/[variants]/(main)/chat/(workspace)/_layout/Desktop/ChatHeader/Tags.tsx +8 -2
- package/src/config/tools.ts +16 -0
- package/src/features/ChatInput/ActionBar/Search/index.tsx +6 -15
- package/src/features/Conversation/Messages/Assistant/Tool/Inspector/ToolTitle.tsx +76 -0
- package/src/features/Conversation/Messages/Assistant/Tool/Inspector/index.tsx +8 -21
- package/src/features/Conversation/Messages/Assistant/Tool/Render/CustomRender.tsx +62 -50
- package/src/features/PluginsUI/Render/BuiltinType/index.tsx +11 -1
- package/src/features/PluginsUI/Render/index.tsx +3 -0
- package/src/features/Portal/Plugins/Body/index.tsx +3 -7
- package/src/features/Portal/Plugins/Header.tsx +14 -2
- package/src/hooks/useAgentEnableSearch.ts +27 -0
- package/src/libs/trpc/client/index.ts +1 -0
- package/src/libs/trpc/client/tools.ts +20 -0
- package/src/locales/default/plugin.ts +16 -0
- package/src/locales/default/portal.ts +0 -5
- package/src/locales/default/tool.ts +18 -0
- package/src/server/modules/SearXNG.ts +33 -0
- package/src/server/routers/lambda/message.ts +11 -0
- package/src/server/routers/tools/__tests__/fixtures/searXNG.ts +668 -0
- package/src/server/routers/tools/__tests__/search.test.ts +47 -0
- package/src/server/routers/tools/index.ts +3 -0
- package/src/server/routers/tools/search.ts +38 -0
- package/src/services/__tests__/__snapshots__/chat.test.ts.snap +1 -0
- package/src/services/_auth.ts +4 -4
- package/src/services/chat.ts +31 -10
- package/src/services/message/_deprecated.ts +4 -0
- package/src/services/message/client.ts +4 -0
- package/src/services/message/server.ts +5 -5
- package/src/services/message/type.ts +2 -0
- package/src/services/search.ts +9 -0
- package/src/store/aiInfra/slices/aiModel/selectors.ts +12 -5
- package/src/store/chat/slices/builtinTool/action.ts +121 -0
- package/src/store/chat/slices/builtinTool/initialState.ts +2 -0
- package/src/store/chat/slices/builtinTool/selectors.ts +3 -0
- package/src/store/chat/slices/message/action.ts +11 -0
- package/src/store/chat/slices/plugin/action.test.ts +2 -2
- package/src/store/chat/slices/plugin/action.ts +2 -2
- package/src/store/tool/selectors/tool.ts +5 -12
- package/src/store/tool/slices/builtin/selectors.ts +1 -1
- package/src/store/user/slices/modelList/action.ts +6 -0
- package/src/store/user/slices/modelList/selectors/keyVaults.ts +1 -0
- package/src/tools/index.ts +7 -0
- package/src/tools/portals.ts +6 -1
- package/src/tools/renders.ts +3 -0
- package/src/{features/Portal/Plugins → tools/web-browsing/Portal}/Footer.tsx +13 -10
- package/src/tools/web-browsing/Portal/ResultList/SearchItem/CategoryAvatar.tsx +70 -0
- package/src/tools/web-browsing/Portal/ResultList/SearchItem/TitleExtra.tsx +38 -0
- package/src/tools/web-browsing/Portal/ResultList/SearchItem/Video.tsx +135 -0
- package/src/tools/web-browsing/Portal/ResultList/SearchItem/index.tsx +91 -0
- package/src/tools/web-browsing/Portal/ResultList/index.tsx +21 -0
- package/src/tools/web-browsing/Portal/index.tsx +65 -0
- package/src/tools/web-browsing/Render/ConfigForm/Form.tsx +110 -0
- package/src/tools/web-browsing/Render/ConfigForm/SearchXNGIcon.tsx +20 -0
- package/src/tools/web-browsing/Render/ConfigForm/index.tsx +67 -0
- package/src/tools/web-browsing/Render/ConfigForm/style.tsx +63 -0
- package/src/tools/web-browsing/Render/SearchQuery/SearchView.tsx +88 -0
- package/src/tools/web-browsing/Render/SearchQuery/index.tsx +61 -0
- package/src/tools/web-browsing/Render/SearchResult/SearchResultItem.tsx +72 -0
- package/src/tools/web-browsing/Render/SearchResult/ShowMore.tsx +68 -0
- package/src/tools/web-browsing/Render/SearchResult/index.tsx +105 -0
- package/src/tools/web-browsing/Render/index.tsx +57 -0
- package/src/tools/web-browsing/components/EngineAvatar.tsx +32 -0
- package/src/tools/web-browsing/components/SearchBar.tsx +134 -0
- package/src/tools/web-browsing/const.ts +11 -0
- package/src/tools/web-browsing/index.ts +102 -0
- package/src/types/message/chat.ts +1 -0
- package/src/types/message/tools.ts +10 -0
- package/src/types/tool/builtin.ts +2 -0
- package/src/types/tool/search.ts +38 -0
- package/src/types/user/settings/keyVaults.ts +8 -1
- package/src/utils/toolManifest.ts +20 -0
@@ -0,0 +1,47 @@
|
|
1
|
+
// @vitest-environment node
|
2
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
3
|
+
|
4
|
+
import { toolsEnv } from '@/config/tools';
|
5
|
+
import { isServerMode } from '@/const/version';
|
6
|
+
/**
|
7
|
+
* This file contains the root router of your tRPC-backend
|
8
|
+
*/
|
9
|
+
import { createCallerFactory } from '@/libs/trpc';
|
10
|
+
import { AuthContext, createContextInner } from '@/server/context';
|
11
|
+
import { SearXNGClient } from '@/server/modules/SearXNG';
|
12
|
+
|
13
|
+
import { searchRouter } from '../search';
|
14
|
+
import { hetongxue } from './fixtures/searXNG';
|
15
|
+
|
16
|
+
vi.mock('@/config/tools', () => ({
|
17
|
+
toolsEnv: {
|
18
|
+
SEARXNG_URL: 'https://demo.com',
|
19
|
+
},
|
20
|
+
}));
|
21
|
+
|
22
|
+
vi.mock('@/const/version', () => ({
|
23
|
+
isServerMode: true,
|
24
|
+
}));
|
25
|
+
|
26
|
+
const createCaller = createCallerFactory(searchRouter);
|
27
|
+
let ctx: AuthContext;
|
28
|
+
let router: ReturnType<typeof createCaller>;
|
29
|
+
|
30
|
+
beforeEach(async () => {
|
31
|
+
vi.resetAllMocks();
|
32
|
+
ctx = await createContextInner({ userId: 'mock' });
|
33
|
+
router = createCaller(ctx);
|
34
|
+
});
|
35
|
+
|
36
|
+
describe('searchRouter', () => {
|
37
|
+
describe('search', () => {
|
38
|
+
it('搜索结果超过10个', async () => {
|
39
|
+
vi.spyOn(SearXNGClient.prototype, 'search').mockResolvedValueOnce(hetongxue);
|
40
|
+
|
41
|
+
const results = await router.query({ query: '何同学' });
|
42
|
+
|
43
|
+
// Assert
|
44
|
+
expect(results.results.length).toEqual(43);
|
45
|
+
});
|
46
|
+
});
|
47
|
+
});
|
@@ -1,7 +1,10 @@
|
|
1
1
|
import { publicProcedure, router } from '@/libs/trpc';
|
2
2
|
|
3
|
+
import { searchRouter } from './search';
|
4
|
+
|
3
5
|
export const toolsRouter = router({
|
4
6
|
healthcheck: publicProcedure.query(() => "i'm live!"),
|
7
|
+
search: searchRouter,
|
5
8
|
});
|
6
9
|
|
7
10
|
export type ToolsRouter = typeof toolsRouter;
|
@@ -0,0 +1,38 @@
|
|
1
|
+
import { TRPCError } from '@trpc/server';
|
2
|
+
import { z } from 'zod';
|
3
|
+
|
4
|
+
import { toolsEnv } from '@/config/tools';
|
5
|
+
import { isServerMode } from '@/const/version';
|
6
|
+
import { authedProcedure, passwordProcedure, router } from '@/libs/trpc';
|
7
|
+
import { SearXNGClient } from '@/server/modules/SearXNG';
|
8
|
+
import { SEARCH_SEARXNG_NOT_CONFIG } from '@/types/tool/search';
|
9
|
+
|
10
|
+
const searchProcedure = isServerMode ? authedProcedure : passwordProcedure;
|
11
|
+
|
12
|
+
export const searchRouter = router({
|
13
|
+
query: searchProcedure
|
14
|
+
.input(
|
15
|
+
z.object({
|
16
|
+
query: z.string(),
|
17
|
+
searchEngine: z.array(z.string()).optional(),
|
18
|
+
}),
|
19
|
+
)
|
20
|
+
.query(async ({ input }) => {
|
21
|
+
if (!toolsEnv.SEARXNG_URL) {
|
22
|
+
throw new TRPCError({ code: 'NOT_IMPLEMENTED', message: SEARCH_SEARXNG_NOT_CONFIG });
|
23
|
+
}
|
24
|
+
|
25
|
+
const client = new SearXNGClient(toolsEnv.SEARXNG_URL);
|
26
|
+
|
27
|
+
try {
|
28
|
+
return await client.search(input.query, input.searchEngine);
|
29
|
+
} catch (e) {
|
30
|
+
console.error(e);
|
31
|
+
|
32
|
+
throw new TRPCError({
|
33
|
+
code: 'SERVICE_UNAVAILABLE',
|
34
|
+
message: (e as Error).message,
|
35
|
+
});
|
36
|
+
}
|
37
|
+
}),
|
38
|
+
});
|
package/src/services/_auth.ts
CHANGED
@@ -48,10 +48,10 @@ export const getProviderAuthPayload = (
|
|
48
48
|
case ModelProvider.Azure: {
|
49
49
|
return {
|
50
50
|
apiKey: keyVaults.apiKey,
|
51
|
-
|
51
|
+
|
52
52
|
apiVersion: keyVaults.apiVersion,
|
53
53
|
/** @deprecated */
|
54
|
-
azureApiVersion: keyVaults.apiVersion,
|
54
|
+
azureApiVersion: keyVaults.apiVersion,
|
55
55
|
baseURL: keyVaults.baseURL || keyVaults.endpoint,
|
56
56
|
};
|
57
57
|
}
|
@@ -63,10 +63,10 @@ azureApiVersion: keyVaults.apiVersion,
|
|
63
63
|
case ModelProvider.Cloudflare: {
|
64
64
|
return {
|
65
65
|
apiKey: keyVaults?.apiKey,
|
66
|
-
|
66
|
+
|
67
67
|
baseURLOrAccountID: keyVaults?.baseURLOrAccountID,
|
68
68
|
/** @deprecated */
|
69
|
-
cloudflareBaseURLOrAccountID: keyVaults?.baseURLOrAccountID,
|
69
|
+
cloudflareBaseURLOrAccountID: keyVaults?.baseURLOrAccountID,
|
70
70
|
};
|
71
71
|
}
|
72
72
|
|
package/src/services/chat.ts
CHANGED
@@ -30,6 +30,7 @@ import {
|
|
30
30
|
preferenceSelectors,
|
31
31
|
userProfileSelectors,
|
32
32
|
} from '@/store/user/selectors';
|
33
|
+
import { WebBrowsingManifest } from '@/tools/web-browsing';
|
33
34
|
import { ChatErrorType } from '@/types/fetch';
|
34
35
|
import { ChatMessage, MessageToolCall } from '@/types/message';
|
35
36
|
import type { ChatStreamPayload, OpenAIChatMessage } from '@/types/openai/chat';
|
@@ -168,6 +169,25 @@ class ChatService {
|
|
168
169
|
},
|
169
170
|
params,
|
170
171
|
);
|
172
|
+
|
173
|
+
// =================== 0. process search =================== //
|
174
|
+
const chatConfig = getAgentChatConfig();
|
175
|
+
|
176
|
+
const enabledSearch = chatConfig.searchMode !== 'off';
|
177
|
+
const isModelHasBuiltinSearch = aiModelSelectors.isModelHasBuiltinSearch(
|
178
|
+
payload.model,
|
179
|
+
payload.provider!,
|
180
|
+
)(useAiInfraStore.getState());
|
181
|
+
|
182
|
+
const useApplicationBuiltinSearchTool =
|
183
|
+
enabledSearch && !(isModelHasBuiltinSearch && chatConfig.useModelBuiltinSearch);
|
184
|
+
|
185
|
+
const pluginIds = [...(enabledPlugins || [])];
|
186
|
+
|
187
|
+
if (useApplicationBuiltinSearchTool) {
|
188
|
+
pluginIds.push(WebBrowsingManifest.identifier);
|
189
|
+
}
|
190
|
+
|
171
191
|
// ============ 1. preprocess messages ============ //
|
172
192
|
|
173
193
|
const oaiMessages = this.processMessages(
|
@@ -175,14 +195,14 @@ class ChatService {
|
|
175
195
|
messages,
|
176
196
|
model: payload.model,
|
177
197
|
provider: payload.provider!,
|
178
|
-
tools:
|
198
|
+
tools: pluginIds,
|
179
199
|
},
|
180
200
|
options,
|
181
201
|
);
|
182
202
|
|
183
203
|
// ============ 2. preprocess tools ============ //
|
184
204
|
|
185
|
-
|
205
|
+
let filterTools = toolSelectors.enabledSchema(pluginIds)(useToolStore.getState());
|
186
206
|
|
187
207
|
// check this model can use function call
|
188
208
|
const canUseFC = isCanUseFC(payload.model, payload.provider!);
|
@@ -194,7 +214,15 @@ class ChatService {
|
|
194
214
|
|
195
215
|
const tools = shouldUseTools ? filterTools : undefined;
|
196
216
|
|
197
|
-
return this.getChatCompletion(
|
217
|
+
return this.getChatCompletion(
|
218
|
+
{
|
219
|
+
...params,
|
220
|
+
enabledSearch: enabledSearch && isModelHasBuiltinSearch ? true : undefined,
|
221
|
+
messages: oaiMessages,
|
222
|
+
tools,
|
223
|
+
},
|
224
|
+
options,
|
225
|
+
);
|
198
226
|
};
|
199
227
|
|
200
228
|
createAssistantMessageStream = async ({
|
@@ -241,13 +269,6 @@ class ChatService {
|
|
241
269
|
model = findDeploymentName(model, provider);
|
242
270
|
}
|
243
271
|
|
244
|
-
// =================== process search =================== //
|
245
|
-
// ===================================================== //
|
246
|
-
const chatConfig = getAgentChatConfig();
|
247
|
-
if (chatConfig.searchMode !== 'off') {
|
248
|
-
res.enabledSearch = true;
|
249
|
-
}
|
250
|
-
|
251
272
|
const payload = merge(
|
252
273
|
{ model: DEFAULT_AGENT_CONFIG.model, stream: true, ...DEFAULT_AGENT_CONFIG.params },
|
253
274
|
{ ...res, model },
|
@@ -92,6 +92,10 @@ export class ClientService extends BaseClientService implements IMessageService
|
|
92
92
|
return this.messageModel.updatePluginState(id, value);
|
93
93
|
};
|
94
94
|
|
95
|
+
updateMessagePluginError: IMessageService['updateMessagePluginError'] = async (id, value) => {
|
96
|
+
return this.messageModel.updateMessagePlugin(id, { error: value });
|
97
|
+
};
|
98
|
+
|
95
99
|
updateMessagePluginArguments: IMessageService['updateMessagePluginArguments'] = async (
|
96
100
|
id,
|
97
101
|
value,
|
@@ -1,7 +1,7 @@
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
2
2
|
import { INBOX_SESSION_ID } from '@/const/session';
|
3
3
|
import { lambdaClient } from '@/libs/trpc/client';
|
4
|
-
import { ChatMessage,
|
4
|
+
import { ChatMessage, ChatTranslate } from '@/types/message';
|
5
5
|
|
6
6
|
import { IMessageService } from './type';
|
7
7
|
|
@@ -56,10 +56,6 @@ export class ServerService implements IMessageService {
|
|
56
56
|
return lambdaClient.message.update.mutate({ id, value: { error } });
|
57
57
|
};
|
58
58
|
|
59
|
-
updateMessagePluginError = async (id: string, error: ChatMessageError): Promise<any> => {
|
60
|
-
return lambdaClient.message.update.mutate({ id, value: { pluginError: error } });
|
61
|
-
};
|
62
|
-
|
63
59
|
updateMessagePluginArguments: IMessageService['updateMessagePluginArguments'] = async (
|
64
60
|
id,
|
65
61
|
value,
|
@@ -84,6 +80,10 @@ export class ServerService implements IMessageService {
|
|
84
80
|
return lambdaClient.message.updatePluginState.mutate({ id, value });
|
85
81
|
};
|
86
82
|
|
83
|
+
updateMessagePluginError: IMessageService['updateMessagePluginError'] = async (id, error) => {
|
84
|
+
return lambdaClient.message.updatePluginError.mutate({ id, value: error as any });
|
85
|
+
};
|
86
|
+
|
87
87
|
removeMessage: IMessageService['removeMessage'] = async (id) => {
|
88
88
|
return lambdaClient.message.removeMessage.mutate({ id });
|
89
89
|
};
|
@@ -3,6 +3,7 @@ import type { HeatmapsProps } from '@lobehub/charts';
|
|
3
3
|
import {
|
4
4
|
ChatMessage,
|
5
5
|
ChatMessageError,
|
6
|
+
ChatMessagePluginError,
|
6
7
|
ChatTTS,
|
7
8
|
ChatTranslate,
|
8
9
|
CreateMessageParams,
|
@@ -36,6 +37,7 @@ export interface IMessageService {
|
|
36
37
|
updateMessageTTS(id: string, tts: Partial<ChatTTS> | false): Promise<any>;
|
37
38
|
updateMessageTranslate(id: string, translate: Partial<ChatTranslate> | false): Promise<any>;
|
38
39
|
updateMessagePluginState(id: string, value: Record<string, any>): Promise<any>;
|
40
|
+
updateMessagePluginError(id: string, value: ChatMessagePluginError | null): Promise<any>;
|
39
41
|
updateMessagePluginArguments(id: string, value: string | Record<string, any>): Promise<any>;
|
40
42
|
removeMessage(id: string): Promise<any>;
|
41
43
|
removeMessages(ids: string[]): Promise<any>;
|
@@ -82,20 +82,26 @@ const isModelHasExtendControls = (id: string, provider: string) => (s: AIProvide
|
|
82
82
|
return !!controls && controls.length > 0;
|
83
83
|
};
|
84
84
|
|
85
|
-
const
|
85
|
+
const modelBuiltinSearchImpl = (id: string, provider: string) => (s: AIProviderStoreState) => {
|
86
86
|
const model = getEnabledModelById(id, provider)(s);
|
87
87
|
|
88
|
-
return
|
88
|
+
return model?.settings?.searchImpl;
|
89
|
+
};
|
90
|
+
|
91
|
+
const isModelHasBuiltinSearch = (id: string, provider: string) => (s: AIProviderStoreState) => {
|
92
|
+
const searchImpl = modelBuiltinSearchImpl(id, provider)(s);
|
93
|
+
|
94
|
+
return !!searchImpl;
|
89
95
|
};
|
90
96
|
|
91
97
|
const isModelHasBuiltinSearchConfig =
|
92
98
|
(id: string, provider: string) => (s: AIProviderStoreState) => {
|
93
|
-
const
|
99
|
+
const searchImpl = modelBuiltinSearchImpl(id, provider)(s);
|
94
100
|
|
95
101
|
return (
|
96
|
-
!!
|
102
|
+
!!searchImpl &&
|
97
103
|
[ModelSearchImplement.Tool, ModelSearchImplement.Params].includes(
|
98
|
-
|
104
|
+
searchImpl as ModelSearchImplement,
|
99
105
|
)
|
100
106
|
);
|
101
107
|
};
|
@@ -118,6 +124,7 @@ export const aiModelSelectors = {
|
|
118
124
|
isModelSupportReasoning,
|
119
125
|
isModelSupportToolUse,
|
120
126
|
isModelSupportVision,
|
127
|
+
modelBuiltinSearchImpl,
|
121
128
|
modelContextWindowTokens,
|
122
129
|
modelExtendControls,
|
123
130
|
totalAiProviderModelList,
|
@@ -5,13 +5,22 @@ import { StateCreator } from 'zustand/vanilla';
|
|
5
5
|
|
6
6
|
import { useClientDataSWR } from '@/libs/swr';
|
7
7
|
import { fileService } from '@/services/file';
|
8
|
+
import { searchService } from '@/services/search';
|
8
9
|
import { imageGenerationService } from '@/services/textToImage';
|
9
10
|
import { uploadService } from '@/services/upload';
|
10
11
|
import { chatSelectors } from '@/store/chat/selectors';
|
11
12
|
import { ChatStore } from '@/store/chat/store';
|
12
13
|
import { useFileStore } from '@/store/file';
|
14
|
+
import { CreateMessageParams } from '@/types/message';
|
13
15
|
import { DallEImageItem } from '@/types/tool/dalle';
|
16
|
+
import {
|
17
|
+
SEARCH_SEARXNG_NOT_CONFIG,
|
18
|
+
SearchContent,
|
19
|
+
SearchQuery,
|
20
|
+
SearchResponse,
|
21
|
+
} from '@/types/tool/search';
|
14
22
|
import { setNamespace } from '@/utils/storeDebug';
|
23
|
+
import { nanoid } from '@/utils/uuid';
|
15
24
|
|
16
25
|
const n = setNamespace('tool');
|
17
26
|
|
@@ -21,8 +30,25 @@ const SWR_FETCH_KEY = 'FetchImageItem';
|
|
21
30
|
*/
|
22
31
|
export interface ChatBuiltinToolAction {
|
23
32
|
generateImageFromPrompts: (items: DallEImageItem[], id: string) => Promise<void>;
|
33
|
+
/**
|
34
|
+
* 重新发起搜索
|
35
|
+
* @description 会更新插件的 arguments 参数,然后再次搜索
|
36
|
+
*/
|
37
|
+
reSearchWithSearXNG: (
|
38
|
+
id: string,
|
39
|
+
data: SearchQuery,
|
40
|
+
options?: { aiSummary: boolean },
|
41
|
+
) => Promise<void>;
|
42
|
+
saveSearXNGSearchResult: (id: string) => Promise<void>;
|
43
|
+
searchWithSearXNG: (
|
44
|
+
id: string,
|
45
|
+
data: SearchQuery,
|
46
|
+
aiSummary?: boolean,
|
47
|
+
) => Promise<void | boolean>;
|
24
48
|
text2image: (id: string, data: DallEImageItem[]) => Promise<void>;
|
49
|
+
|
25
50
|
toggleDallEImageLoading: (key: string, value: boolean) => void;
|
51
|
+
toggleSearchLoading: (id: string, loading: boolean) => void;
|
26
52
|
updateImageItem: (id: string, updater: (data: DallEImageItem[]) => void) => Promise<void>;
|
27
53
|
useFetchDalleImageItem: (id: string) => SWRResponse;
|
28
54
|
}
|
@@ -82,12 +108,101 @@ export const chatToolSlice: StateCreator<
|
|
82
108
|
});
|
83
109
|
});
|
84
110
|
},
|
111
|
+
reSearchWithSearXNG: async (id, data, options) => {
|
112
|
+
get().toggleSearchLoading(id, true);
|
113
|
+
await get().updatePluginArguments(id, data);
|
114
|
+
|
115
|
+
await get().searchWithSearXNG(id, data, options?.aiSummary);
|
116
|
+
},
|
117
|
+
saveSearXNGSearchResult: async (id) => {
|
118
|
+
const message = chatSelectors.getMessageById(id)(get());
|
119
|
+
if (!message || !message.plugin) return;
|
120
|
+
|
121
|
+
const { internal_addToolToAssistantMessage, internal_createMessage, openToolUI } = get();
|
122
|
+
// 1. 创建一个新的 tool call message
|
123
|
+
const newToolCallId = `tool_call_${nanoid()}`;
|
124
|
+
|
125
|
+
const toolMessage: CreateMessageParams = {
|
126
|
+
content: message.content,
|
127
|
+
id: undefined,
|
128
|
+
parentId: message.parentId,
|
129
|
+
plugin: message.plugin,
|
130
|
+
pluginState: message.pluginState,
|
131
|
+
role: 'tool',
|
132
|
+
sessionId: get().activeId,
|
133
|
+
tool_call_id: newToolCallId,
|
134
|
+
topicId: get().activeTopicId,
|
135
|
+
};
|
136
|
+
|
137
|
+
const addToolItem = async () => {
|
138
|
+
if (!message.parentId || !message.plugin) return;
|
139
|
+
|
140
|
+
await internal_addToolToAssistantMessage(message.parentId, {
|
141
|
+
id: newToolCallId,
|
142
|
+
...message.plugin,
|
143
|
+
});
|
144
|
+
};
|
145
|
+
|
146
|
+
const [newMessageId] = await Promise.all([
|
147
|
+
// 1. 添加 tool message
|
148
|
+
internal_createMessage(toolMessage),
|
149
|
+
// 2. 将这条 tool call message 插入到 ai 消息的 tools 中
|
150
|
+
addToolItem(),
|
151
|
+
]);
|
152
|
+
|
153
|
+
// 将新创建的 tool message 激活
|
154
|
+
openToolUI(newMessageId, message.plugin.identifier);
|
155
|
+
},
|
156
|
+
searchWithSearXNG: async (id, params, aiSummary = true) => {
|
157
|
+
get().toggleSearchLoading(id, true);
|
158
|
+
let data: SearchResponse | undefined;
|
159
|
+
try {
|
160
|
+
data = await searchService.search(params.query, params.searchEngines);
|
161
|
+
await get().updatePluginState(id, data);
|
162
|
+
} catch (e) {
|
163
|
+
if ((e as Error).message === SEARCH_SEARXNG_NOT_CONFIG) {
|
164
|
+
await get().internal_updateMessagePluginError(id, {
|
165
|
+
body: {
|
166
|
+
provider: 'searxng',
|
167
|
+
},
|
168
|
+
message: 'SearXNG is not configured',
|
169
|
+
type: 'PluginSettingsInvalid',
|
170
|
+
});
|
171
|
+
} else {
|
172
|
+
await get().internal_updateMessagePluginError(id, {
|
173
|
+
body: e,
|
174
|
+
message: (e as Error).message,
|
175
|
+
type: 'PluginServerError',
|
176
|
+
});
|
177
|
+
}
|
178
|
+
}
|
179
|
+
|
180
|
+
get().toggleSearchLoading(id, false);
|
181
|
+
|
182
|
+
if (!data) return;
|
183
|
+
|
184
|
+
// 只取前 5 个结果作为上下文
|
185
|
+
const searchContent: SearchContent[] = data.results.slice(0, 5).map((item) => ({
|
186
|
+
content: item.content,
|
187
|
+
title: item.title,
|
188
|
+
url: item.url,
|
189
|
+
}));
|
190
|
+
|
191
|
+
await get().internal_updateMessageContent(id, JSON.stringify(searchContent));
|
192
|
+
|
193
|
+
// 如果没搜索到结果,那么不触发 ai 总结
|
194
|
+
if (searchContent.length === 0) return;
|
195
|
+
|
196
|
+
// 如果 aiSummary 为 true,则会自动触发总结
|
197
|
+
return aiSummary;
|
198
|
+
},
|
85
199
|
text2image: async (id, data) => {
|
86
200
|
// const isAutoGen = settingsSelectors.isDalleAutoGenerating(useGlobalStore.getState());
|
87
201
|
// if (!isAutoGen) return;
|
88
202
|
|
89
203
|
await get().generateImageFromPrompts(data, id);
|
90
204
|
},
|
205
|
+
|
91
206
|
toggleDallEImageLoading: (key, value) => {
|
92
207
|
set(
|
93
208
|
{ dalleImageLoading: { ...get().dalleImageLoading, [key]: value } },
|
@@ -95,6 +210,11 @@ export const chatToolSlice: StateCreator<
|
|
95
210
|
n('toggleDallEImageLoading'),
|
96
211
|
);
|
97
212
|
},
|
213
|
+
|
214
|
+
toggleSearchLoading: (id, loading) => {
|
215
|
+
set({ searchLoading: { ...get().searchLoading, [id]: loading } }, false, 'toggleSearchLoading');
|
216
|
+
},
|
217
|
+
|
98
218
|
updateImageItem: async (id, updater) => {
|
99
219
|
const message = chatSelectors.getMessageById(id)(get());
|
100
220
|
if (!message) return;
|
@@ -104,6 +224,7 @@ export const chatToolSlice: StateCreator<
|
|
104
224
|
const nextContent = produce(data, updater);
|
105
225
|
await get().internal_updateMessageContent(id, JSON.stringify(nextContent));
|
106
226
|
},
|
227
|
+
|
107
228
|
useFetchDalleImageItem: (id) =>
|
108
229
|
useClientDataSWR([SWR_FETCH_KEY, id], async () => {
|
109
230
|
const item = await fileService.getFile(id);
|
@@ -3,9 +3,11 @@ import { FileItem } from '@/types/files';
|
|
3
3
|
export interface ChatToolState {
|
4
4
|
dalleImageLoading: Record<string, boolean>;
|
5
5
|
dalleImageMap: Record<string, FileItem>;
|
6
|
+
searchLoading: Record<string, boolean>;
|
6
7
|
}
|
7
8
|
|
8
9
|
export const initialToolState: ChatToolState = {
|
9
10
|
dalleImageLoading: {},
|
10
11
|
dalleImageMap: {},
|
12
|
+
searchLoading: {},
|
11
13
|
};
|
@@ -5,7 +5,10 @@ const isDallEImageGenerating = (id: string) => (s: ChatStoreState) => s.dalleIma
|
|
5
5
|
const isGeneratingDallEImage = (s: ChatStoreState) =>
|
6
6
|
Object.values(s.dalleImageLoading).some(Boolean);
|
7
7
|
|
8
|
+
const isSearXNGSearching = (id: string) => (s: ChatStoreState) => s.searchLoading[id];
|
9
|
+
|
8
10
|
export const chatToolSelectors = {
|
9
11
|
isDallEImageGenerating,
|
10
12
|
isGeneratingDallEImage,
|
13
|
+
isSearXNGSearching,
|
11
14
|
};
|
@@ -15,6 +15,7 @@ import { messageMapKey } from '@/store/chat/utils/messageMapKey';
|
|
15
15
|
import {
|
16
16
|
ChatMessage,
|
17
17
|
ChatMessageError,
|
18
|
+
ChatMessagePluginError,
|
18
19
|
CreateMessageParams,
|
19
20
|
MessageToolCall,
|
20
21
|
ModelReasoning,
|
@@ -84,6 +85,10 @@ export interface ChatMessageAction {
|
|
84
85
|
* update the message error with optimistic update
|
85
86
|
*/
|
86
87
|
internal_updateMessageError: (id: string, error: ChatMessageError | null) => Promise<void>;
|
88
|
+
internal_updateMessagePluginError: (
|
89
|
+
id: string,
|
90
|
+
error: ChatMessagePluginError | null,
|
91
|
+
) => Promise<void>;
|
87
92
|
/**
|
88
93
|
* create a message with optimistic update
|
89
94
|
*/
|
@@ -276,6 +281,12 @@ export const chatMessage: StateCreator<
|
|
276
281
|
await messageService.updateMessage(id, { error });
|
277
282
|
await get().refreshMessages();
|
278
283
|
},
|
284
|
+
|
285
|
+
internal_updateMessagePluginError: async (id, error) => {
|
286
|
+
await messageService.updateMessagePluginError(id, error);
|
287
|
+
await get().refreshMessages();
|
288
|
+
},
|
289
|
+
|
279
290
|
internal_updateMessageContent: async (id, content, extra) => {
|
280
291
|
const { internal_dispatchMessage, refreshMessages, internal_transformToolCalls } = get();
|
281
292
|
|
@@ -855,7 +855,7 @@ describe('ChatPluginAction', () => {
|
|
855
855
|
arguments: '{}',
|
856
856
|
},
|
857
857
|
tool_call_id: 'tool-id',
|
858
|
-
|
858
|
+
pluginError: { message: 'Previous error', type: 'ProviderBizError' },
|
859
859
|
} as ChatMessage;
|
860
860
|
|
861
861
|
const internal_updateMessageErrorMock = vi.fn();
|
@@ -865,7 +865,7 @@ describe('ChatPluginAction', () => {
|
|
865
865
|
activeId: 'session-id',
|
866
866
|
messagesMap: { [messageMapKey('session-id')]: [message] },
|
867
867
|
internal_invokeDifferentTypePlugin: vi.fn(),
|
868
|
-
|
868
|
+
internal_updateMessagePluginError: internal_updateMessageErrorMock,
|
869
869
|
});
|
870
870
|
});
|
871
871
|
|
@@ -200,8 +200,8 @@ export const chatPlugin: StateCreator<
|
|
200
200
|
if (!message || message.role !== 'tool' || !message.plugin) return;
|
201
201
|
|
202
202
|
// if there is error content, then clear the error
|
203
|
-
if (!!message.
|
204
|
-
get().
|
203
|
+
if (!!message.pluginError) {
|
204
|
+
get().internal_updateMessagePluginError(id, null);
|
205
205
|
}
|
206
206
|
|
207
207
|
const payload: ChatToolPayload = { ...message.plugin, id: message.tool_call_id! };
|
@@ -1,11 +1,11 @@
|
|
1
1
|
import { LobeChatPluginManifest } from '@lobehub/chat-plugin-sdk';
|
2
|
-
import { uniqBy } from 'lodash-es';
|
3
2
|
|
4
3
|
import { pluginPrompts } from '@/prompts/plugin';
|
5
4
|
import { MetaData } from '@/types/meta';
|
6
5
|
import { ChatCompletionTool } from '@/types/openai/chat';
|
7
6
|
import { LobeToolMeta } from '@/types/tool/tool';
|
8
7
|
import { genToolCallingName } from '@/utils/toolCall';
|
8
|
+
import { convertPluginManifestToToolsCalling } from '@/utils/toolManifest';
|
9
9
|
|
10
10
|
import { pluginHelpers } from '../helpers';
|
11
11
|
import { ToolStoreState } from '../initialState';
|
@@ -15,20 +15,13 @@ import { pluginSelectors } from '../slices/plugin/selectors';
|
|
15
15
|
const enabledSchema =
|
16
16
|
(tools: string[] = []) =>
|
17
17
|
(s: ToolStoreState): ChatCompletionTool[] => {
|
18
|
-
const
|
18
|
+
const manifests = pluginSelectors
|
19
19
|
.installedPluginManifestList(s)
|
20
20
|
.concat(s.builtinTools.map((b) => b.manifest as LobeChatPluginManifest))
|
21
21
|
// 如果存在 enabledPlugins,那么只启用 enabledPlugins 中的插件
|
22
|
-
.filter((m) => tools.includes(m?.identifier))
|
23
|
-
|
24
|
-
|
25
|
-
description: m.description,
|
26
|
-
name: genToolCallingName(manifest.identifier, m.name, manifest.type),
|
27
|
-
parameters: m.parameters,
|
28
|
-
})),
|
29
|
-
);
|
30
|
-
|
31
|
-
return uniqBy(list, 'name').map((i) => ({ function: i, type: 'function' }));
|
22
|
+
.filter((m) => tools.includes(m?.identifier));
|
23
|
+
|
24
|
+
return convertPluginManifestToToolsCalling(manifests);
|
32
25
|
};
|
33
26
|
|
34
27
|
const enabledSystemRoles =
|
@@ -7,7 +7,7 @@ const metaList =
|
|
7
7
|
(showDalle?: boolean) =>
|
8
8
|
(s: ToolStoreState): LobeToolMeta[] =>
|
9
9
|
s.builtinTools
|
10
|
-
.filter((item) => (!showDalle ? item.identifier !== DalleManifest.identifier :
|
10
|
+
.filter((item) => (!showDalle ? item.identifier !== DalleManifest.identifier : !item.hidden))
|
11
11
|
.map((t) => ({
|
12
12
|
author: 'LobeHub',
|
13
13
|
identifier: t.identifier,
|
@@ -50,6 +50,8 @@ export interface ModelListAction {
|
|
50
50
|
config: Partial<UserKeyVaults[T]>,
|
51
51
|
) => Promise<void>;
|
52
52
|
|
53
|
+
updateKeyVaultSettings: (key: string, config: any) => Promise<void>;
|
54
|
+
|
53
55
|
useFetchProviderModelList: (
|
54
56
|
provider: GlobalLLMProviderKey,
|
55
57
|
enabledAutoFetch: boolean,
|
@@ -189,6 +191,10 @@ export const createModelListSlice: StateCreator<
|
|
189
191
|
await get().setSettings({ keyVaults: { [provider]: config } });
|
190
192
|
},
|
191
193
|
|
194
|
+
updateKeyVaultSettings: async (provider, config) => {
|
195
|
+
await get().setSettings({ keyVaults: { [provider]: config } });
|
196
|
+
},
|
197
|
+
|
192
198
|
useFetchProviderModelList: (provider, enabledAutoFetch) =>
|
193
199
|
useSWR<ChatModelCard[] | undefined>(
|
194
200
|
[provider, enabledAutoFetch],
|