@lobehub/lobehub 2.0.0-next.38 → 2.0.0-next.39
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 +17 -0
- package/apps/desktop/src/main/modules/networkProxy/__tests__/dispatcher.test.ts +401 -0
- package/apps/desktop/src/main/modules/networkProxy/__tests__/tester.test.ts +531 -0
- package/apps/desktop/src/main/modules/networkProxy/__tests__/urlBuilder.test.ts +349 -0
- package/apps/desktop/src/main/modules/networkProxy/__tests__/validator.test.ts +492 -0
- package/changelog/v1.json +5 -0
- package/locales/ar/auth.json +45 -1
- package/locales/bg-BG/auth.json +45 -1
- package/locales/de-DE/auth.json +45 -1
- package/locales/en-US/auth.json +45 -1
- package/locales/es-ES/auth.json +45 -1
- package/locales/fa-IR/auth.json +45 -1
- package/locales/fr-FR/auth.json +45 -1
- package/locales/it-IT/auth.json +45 -1
- package/locales/ja-JP/auth.json +45 -1
- package/locales/ko-KR/auth.json +45 -1
- package/locales/nl-NL/auth.json +45 -1
- package/locales/pl-PL/auth.json +45 -1
- package/locales/pt-BR/auth.json +45 -1
- package/locales/ru-RU/auth.json +45 -1
- package/locales/tr-TR/auth.json +45 -1
- package/locales/vi-VN/auth.json +45 -1
- package/locales/zh-CN/auth.json +45 -1
- package/locales/zh-TW/auth.json +45 -1
- package/package.json +1 -1
- package/packages/context-engine/src/processors/MessageCleanup.ts +1 -0
- package/packages/context-engine/src/processors/__tests__/MessageCleanup.test.ts +28 -0
- package/packages/obervability-otel/package.json +3 -1
- package/packages/obervability-otel/src/api.ts +2 -0
- package/packages/obervability-otel/src/trpc/convention.ts +16 -0
- package/packages/obervability-otel/src/trpc/index.test.ts +38 -0
- package/packages/obervability-otel/src/trpc/index.ts +62 -0
- package/packages/obervability-otel/src/trpc/metrics.ts +31 -0
- package/packages/types/src/usage/usageRecord.ts +54 -0
- package/src/app/[variants]/(main)/profile/hooks/useCategory.tsx +10 -1
- package/src/app/[variants]/(main)/profile/usage/Client.tsx +114 -0
- package/src/app/[variants]/(main)/profile/usage/features/UsageCards/ActiveModels/ModelTable.tsx +175 -0
- package/src/app/[variants]/(main)/profile/usage/features/UsageCards/ActiveModels/index.tsx +126 -0
- package/src/app/[variants]/(main)/profile/usage/features/UsageCards/MonthSpend.tsx +53 -0
- package/src/app/[variants]/(main)/profile/usage/features/UsageCards/TodaySpend.tsx +67 -0
- package/src/app/[variants]/(main)/profile/usage/features/UsageCards/index.tsx +19 -0
- package/src/app/[variants]/(main)/profile/usage/features/UsageTable.tsx +145 -0
- package/src/app/[variants]/(main)/profile/usage/features/UsageTrends.tsx +107 -0
- package/src/app/[variants]/(main)/profile/usage/features/components/UsageBarChart.tsx +48 -0
- package/src/app/[variants]/(main)/profile/usage/page.tsx +23 -0
- package/src/features/Conversation/Messages/Assistant/Tool/Render/CustomRender.tsx +3 -3
- package/src/features/Conversation/Messages/Group/Actions/WithoutContentId.tsx +37 -14
- package/src/features/Conversation/Messages/Group/Error/index.tsx +1 -1
- package/src/features/Conversation/Messages/Group/GroupChildren.tsx +13 -35
- package/src/features/Conversation/Messages/Group/GroupItem.tsx +43 -0
- package/src/features/Conversation/Messages/Group/Tool/Inspector/index.tsx +1 -2
- package/src/features/Conversation/Messages/Group/Tool/Render/CustomRender.tsx +1 -1
- package/src/features/Conversation/Messages/Group/Tool/index.tsx +0 -2
- package/src/features/Conversation/Messages/Group/index.tsx +7 -2
- package/src/features/Conversation/hooks/useChatListActionsBar.tsx +21 -7
- package/src/features/PluginsUI/Render/BuiltinType/index.tsx +1 -1
- package/src/features/PluginsUI/Render/MCPType/index.tsx +52 -0
- package/src/features/PluginsUI/Render/StandaloneType/Iframe.tsx +2 -2
- package/src/features/PluginsUI/Render/index.tsx +17 -0
- package/src/libs/mcp/client.ts +3 -2
- package/src/libs/mcp/types.ts +71 -0
- package/src/libs/trpc/lambda/index.ts +5 -2
- package/src/libs/trpc/middleware/openTelemetry.ts +141 -0
- package/src/locales/default/auth.ts +44 -0
- package/src/locales/default/chat.ts +1 -0
- package/src/server/routers/desktop/mcp.ts +1 -3
- package/src/server/routers/lambda/index.ts +2 -0
- package/src/server/routers/lambda/usage.ts +36 -0
- package/src/server/routers/tools/mcp.ts +1 -3
- package/src/server/services/mcp/index.test.ts +28 -15
- package/src/server/services/mcp/index.ts +29 -18
- package/src/server/services/usage/index.test.ts +310 -0
- package/src/server/services/usage/index.ts +164 -0
- package/src/services/chat/contextEngineering.test.ts +4 -0
- package/src/services/mcp.test.ts +7 -1
- package/src/services/mcp.ts +13 -12
- package/src/services/usage.ts +13 -0
- package/src/store/chat/agents/createAgentExecutors.ts +2 -3
- package/src/store/chat/slices/aiChat/actions/conversationLifecycle.ts +40 -1
- package/src/store/chat/slices/aiChat/actions/streamingExecutor.ts +13 -5
- package/src/store/chat/slices/builtinTool/actions/__tests__/localSystem.test.ts +3 -3
- package/src/store/chat/slices/builtinTool/actions/__tests__/search.test.ts +6 -6
- package/src/store/chat/slices/builtinTool/actions/interpreter.ts +2 -2
- package/src/store/chat/slices/builtinTool/actions/localSystem.ts +2 -2
- package/src/store/chat/slices/builtinTool/actions/search.ts +6 -6
- package/src/store/chat/slices/message/actions/publicApi.ts +19 -1
- package/src/store/chat/slices/message/initialState.ts +5 -0
- package/src/store/chat/slices/message/selectors/chat.test.ts +22 -602
- package/src/store/chat/slices/message/selectors/chat.ts +0 -2
- package/src/store/chat/slices/message/selectors/dbMessage.test.ts +51 -0
- package/src/store/chat/slices/message/selectors/displayMessage.test.ts +818 -0
- package/src/store/chat/slices/message/selectors/displayMessage.ts +52 -1
- package/src/store/chat/slices/message/selectors/messageState.ts +2 -0
- package/src/store/chat/slices/plugin/action.test.ts +4 -4
- package/src/store/chat/slices/plugin/actions/index.ts +39 -0
- package/src/store/chat/slices/plugin/actions/internals.ts +83 -0
- package/src/store/chat/slices/plugin/actions/optimisticUpdate.ts +188 -0
- package/src/store/chat/slices/plugin/actions/pluginTypes.ts +213 -0
- package/src/store/chat/slices/plugin/actions/publicApi.ts +115 -0
- package/src/store/chat/slices/plugin/actions/workflow.ts +121 -0
- package/src/store/chat/store.ts +1 -1
- package/src/store/global/initialState.ts +1 -0
- package/src/store/chat/slices/plugin/action.ts +0 -539
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { UIChatMessage } from '@lobechat/types';
|
|
1
|
+
import { AssistantContentBlock, UIChatMessage } from '@lobechat/types';
|
|
2
2
|
|
|
3
3
|
import { DEFAULT_USER_AVATAR } from '@/const/meta';
|
|
4
4
|
import { INBOX_SESSION_ID } from '@/const/session';
|
|
@@ -250,12 +250,62 @@ const getGroupLatestMessageWithoutTools = (id: string) => (s: ChatStoreState) =>
|
|
|
250
250
|
|
|
251
251
|
// Return the last child only if it doesn't have tools
|
|
252
252
|
if (!lastChild.tools || lastChild.tools.length === 0) {
|
|
253
|
+
if (!lastChild.content) return;
|
|
254
|
+
|
|
253
255
|
return lastChild;
|
|
254
256
|
}
|
|
255
257
|
|
|
256
258
|
return;
|
|
257
259
|
};
|
|
258
260
|
|
|
261
|
+
/**
|
|
262
|
+
* Helper to find last message ID in an AssistantContentBlock
|
|
263
|
+
*/
|
|
264
|
+
const findLastBlockId = (block: AssistantContentBlock | undefined): string | undefined => {
|
|
265
|
+
if (!block) return undefined;
|
|
266
|
+
|
|
267
|
+
// Check tools for result message ID
|
|
268
|
+
if (block.tools && block.tools.length > 0) {
|
|
269
|
+
const lastTool = block.tools.at(-1);
|
|
270
|
+
return lastTool?.result_msg_id;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Return block ID
|
|
274
|
+
return block.id;
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Recursively finds the last message ID in a message tree
|
|
279
|
+
* Priority: children > tools > self
|
|
280
|
+
*/
|
|
281
|
+
const findLastMessageIdRecursive = (node: UIChatMessage | undefined): string | undefined => {
|
|
282
|
+
if (!node) return undefined;
|
|
283
|
+
|
|
284
|
+
// Priority 1: Dive into children recursively
|
|
285
|
+
if (node.children && node.children.length > 0) {
|
|
286
|
+
const lastChild = node.children.at(-1);
|
|
287
|
+
return findLastBlockId(lastChild);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Priority 2: Check tools for result message ID
|
|
291
|
+
if (node.tools && node.tools.length > 0) {
|
|
292
|
+
const lastTool = node.tools.at(-1);
|
|
293
|
+
return lastTool?.result_msg_id;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Priority 3: Return self ID
|
|
297
|
+
return node.id;
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Finds the last (deepest) message ID from a display message
|
|
302
|
+
* Recursively traverses children and tools to find the actual last message
|
|
303
|
+
*/
|
|
304
|
+
const findLastMessageId = (id: string) => (s: ChatStoreState) => {
|
|
305
|
+
const message = getDisplayMessageById(id)(s);
|
|
306
|
+
return findLastMessageIdRecursive(message);
|
|
307
|
+
};
|
|
308
|
+
|
|
259
309
|
// ============= Supervisor Selectors ========== //
|
|
260
310
|
|
|
261
311
|
const isSupervisorLoading = (groupId: string) => (s: ChatStoreState) =>
|
|
@@ -281,6 +331,7 @@ export const displayMessageSelectors = {
|
|
|
281
331
|
activeDisplayMessages,
|
|
282
332
|
currentChatLoadingState,
|
|
283
333
|
currentDisplayChatKey,
|
|
334
|
+
findLastMessageId,
|
|
284
335
|
getDisplayMessageById,
|
|
285
336
|
getDisplayMessagesByKey,
|
|
286
337
|
getGroupLatestMessageWithoutTools,
|
|
@@ -5,6 +5,7 @@ import { getDbMessageByToolCallId } from './dbMessage';
|
|
|
5
5
|
const isMessageEditing = (id: string) => (s: ChatStoreState) => s.messageEditingIds.includes(id);
|
|
6
6
|
const isMessageLoading = (id: string) => (s: ChatStoreState) => s.messageLoadingIds.includes(id);
|
|
7
7
|
const isMessageRegenerating = (id: string) => (s: ChatStoreState) => s.regeneratingIds.includes(id);
|
|
8
|
+
const isMessageContinuing = (id: string) => (s: ChatStoreState) => s.continuingIds.includes(id);
|
|
8
9
|
|
|
9
10
|
const isMessageGenerating = (id: string) => (s: ChatStoreState) => s.chatLoadingIds.includes(id);
|
|
10
11
|
const isMessageInRAGFlow = (id: string) => (s: ChatStoreState) =>
|
|
@@ -70,6 +71,7 @@ export const messageStateSelectors = {
|
|
|
70
71
|
isHasMessageLoading,
|
|
71
72
|
isInRAGFlow,
|
|
72
73
|
isInToolsCalling,
|
|
74
|
+
isMessageContinuing,
|
|
73
75
|
isMessageEditing,
|
|
74
76
|
isMessageGenerating,
|
|
75
77
|
isMessageInChatReasoning,
|
|
@@ -544,7 +544,7 @@ describe('ChatPluginAction', () => {
|
|
|
544
544
|
const { result } = renderHook(() => useChatStore());
|
|
545
545
|
|
|
546
546
|
await act(async () => {
|
|
547
|
-
await result.current.
|
|
547
|
+
await result.current.optimisticUpdatePluginState(messageId, pluginStateValue);
|
|
548
548
|
});
|
|
549
549
|
|
|
550
550
|
expect(messageService.updateMessagePluginState).toHaveBeenCalledWith(
|
|
@@ -887,7 +887,7 @@ describe('ChatPluginAction', () => {
|
|
|
887
887
|
const { result } = renderHook(() => useChatStore());
|
|
888
888
|
|
|
889
889
|
await act(async () => {
|
|
890
|
-
await result.current.
|
|
890
|
+
await result.current.optimisticUpdatePluginArguments(messageId, newArguments);
|
|
891
891
|
});
|
|
892
892
|
|
|
893
893
|
expect(messageService.updateMessagePluginArguments).toHaveBeenCalledWith(
|
|
@@ -1103,7 +1103,7 @@ describe('ChatPluginAction', () => {
|
|
|
1103
1103
|
const { result } = renderHook(() => useChatStore());
|
|
1104
1104
|
|
|
1105
1105
|
await act(async () => {
|
|
1106
|
-
await result.current.
|
|
1106
|
+
await result.current.optimisticUpdatePluginError(messageId, error);
|
|
1107
1107
|
});
|
|
1108
1108
|
|
|
1109
1109
|
expect(messageService.updateMessage).toHaveBeenCalledWith(
|
|
@@ -1144,7 +1144,7 @@ describe('ChatPluginAction', () => {
|
|
|
1144
1144
|
});
|
|
1145
1145
|
|
|
1146
1146
|
await act(async () => {
|
|
1147
|
-
await result.current.
|
|
1147
|
+
await result.current.optimisticAddToolToAssistantMessage(messageId, {
|
|
1148
1148
|
identifier,
|
|
1149
1149
|
arguments: '{"oldKey":"oldValue"}',
|
|
1150
1150
|
id: 'newId',
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { StateCreator } from 'zustand/vanilla';
|
|
2
|
+
|
|
3
|
+
import { ChatStore } from '@/store/chat/store';
|
|
4
|
+
|
|
5
|
+
import { PluginInternalsAction, pluginInternals } from './internals';
|
|
6
|
+
import { PluginOptimisticUpdateAction, pluginOptimisticUpdate } from './optimisticUpdate';
|
|
7
|
+
import { PluginTypesAction, pluginTypes } from './pluginTypes';
|
|
8
|
+
import { PluginPublicApiAction, pluginPublicApi } from './publicApi';
|
|
9
|
+
import { PluginWorkflowAction, pluginWorkflow } from './workflow';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Combined plugin action interface
|
|
13
|
+
* Aggregates all plugin-related actions
|
|
14
|
+
*/
|
|
15
|
+
export interface ChatPluginAction
|
|
16
|
+
extends PluginPublicApiAction,
|
|
17
|
+
PluginOptimisticUpdateAction,
|
|
18
|
+
PluginTypesAction,
|
|
19
|
+
PluginWorkflowAction,
|
|
20
|
+
PluginInternalsAction {
|
|
21
|
+
/**/
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Combined plugin action creator
|
|
26
|
+
* Merges all plugin action modules
|
|
27
|
+
*/
|
|
28
|
+
export const chatPlugin: StateCreator<
|
|
29
|
+
ChatStore,
|
|
30
|
+
[['zustand/devtools', never]],
|
|
31
|
+
[],
|
|
32
|
+
ChatPluginAction
|
|
33
|
+
> = (...params) => ({
|
|
34
|
+
...pluginPublicApi(...params),
|
|
35
|
+
...pluginOptimisticUpdate(...params),
|
|
36
|
+
...pluginTypes(...params),
|
|
37
|
+
...pluginWorkflow(...params),
|
|
38
|
+
...pluginInternals(...params),
|
|
39
|
+
});
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/* eslint-disable sort-keys-fix/sort-keys-fix, typescript-sort-keys/interface */
|
|
2
|
+
import { ToolNameResolver } from '@lobechat/context-engine';
|
|
3
|
+
import { MessageToolCall, ToolsCallingContext } from '@lobechat/types';
|
|
4
|
+
import { LobeChatPluginManifest } from '@lobehub/chat-plugin-sdk';
|
|
5
|
+
import { StateCreator } from 'zustand/vanilla';
|
|
6
|
+
|
|
7
|
+
import { ChatStore } from '@/store/chat/store';
|
|
8
|
+
import { useToolStore } from '@/store/tool';
|
|
9
|
+
import { pluginSelectors } from '@/store/tool/selectors';
|
|
10
|
+
import { builtinTools } from '@/tools';
|
|
11
|
+
import { Action } from '@/utils/storeDebug';
|
|
12
|
+
|
|
13
|
+
import { displayMessageSelectors } from '../../message/selectors';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Internal utility methods and runtime state management
|
|
17
|
+
* These are building blocks used by other actions
|
|
18
|
+
*/
|
|
19
|
+
export interface PluginInternalsAction {
|
|
20
|
+
/**
|
|
21
|
+
* Toggle plugin API calling state
|
|
22
|
+
*/
|
|
23
|
+
internal_togglePluginApiCalling: (
|
|
24
|
+
loading: boolean,
|
|
25
|
+
id?: string,
|
|
26
|
+
action?: Action,
|
|
27
|
+
) => AbortController | undefined;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Transform tool calls from runtime format to storage format
|
|
31
|
+
*/
|
|
32
|
+
internal_transformToolCalls: (toolCalls: MessageToolCall[]) => any[];
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Construct tools calling context for plugin invocation
|
|
36
|
+
*/
|
|
37
|
+
internal_constructToolsCallingContext: (id: string) => ToolsCallingContext | undefined;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export const pluginInternals: StateCreator<
|
|
41
|
+
ChatStore,
|
|
42
|
+
[['zustand/devtools', never]],
|
|
43
|
+
[],
|
|
44
|
+
PluginInternalsAction
|
|
45
|
+
> = (set, get) => ({
|
|
46
|
+
internal_togglePluginApiCalling: (loading, id, action) => {
|
|
47
|
+
return get().internal_toggleLoadingArrays('pluginApiLoadingIds', loading, id, action);
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
internal_transformToolCalls: (toolCalls) => {
|
|
51
|
+
const toolNameResolver = new ToolNameResolver();
|
|
52
|
+
|
|
53
|
+
// Build manifests map from tool store
|
|
54
|
+
const toolStoreState = useToolStore.getState();
|
|
55
|
+
const manifests: Record<string, LobeChatPluginManifest> = {};
|
|
56
|
+
|
|
57
|
+
// Get all installed plugins
|
|
58
|
+
const installedPlugins = pluginSelectors.installedPlugins(toolStoreState);
|
|
59
|
+
for (const plugin of installedPlugins) {
|
|
60
|
+
if (plugin.manifest) {
|
|
61
|
+
manifests[plugin.identifier] = plugin.manifest as LobeChatPluginManifest;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Get all builtin tools
|
|
66
|
+
for (const tool of builtinTools) {
|
|
67
|
+
if (tool.manifest) {
|
|
68
|
+
manifests[tool.identifier] = tool.manifest as LobeChatPluginManifest;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return toolNameResolver.resolve(toolCalls, manifests);
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
internal_constructToolsCallingContext: (id: string) => {
|
|
76
|
+
const message = displayMessageSelectors.getDisplayMessageById(id)(get());
|
|
77
|
+
if (!message) return;
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
topicId: message.topicId,
|
|
81
|
+
};
|
|
82
|
+
},
|
|
83
|
+
});
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/* eslint-disable sort-keys-fix/sort-keys-fix, typescript-sort-keys/interface */
|
|
2
|
+
import { ChatMessageError, ChatToolPayload } from '@lobechat/types';
|
|
3
|
+
import isEqual from 'fast-deep-equal';
|
|
4
|
+
import { StateCreator } from 'zustand/vanilla';
|
|
5
|
+
|
|
6
|
+
import { messageService } from '@/services/message';
|
|
7
|
+
import { ChatStore } from '@/store/chat/store';
|
|
8
|
+
import { merge } from '@/utils/merge';
|
|
9
|
+
import { safeParseJSON } from '@/utils/safeParseJSON';
|
|
10
|
+
|
|
11
|
+
import { displayMessageSelectors } from '../../message/selectors';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Optimistic update operations for plugin-related data
|
|
15
|
+
* All methods follow the pattern: update frontend first, then persist to database
|
|
16
|
+
*/
|
|
17
|
+
export interface PluginOptimisticUpdateAction {
|
|
18
|
+
/**
|
|
19
|
+
* Update plugin state with optimistic update
|
|
20
|
+
*/
|
|
21
|
+
optimisticUpdatePluginState: (id: string, value: any) => Promise<void>;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Update plugin arguments with optimistic update
|
|
25
|
+
*/
|
|
26
|
+
optimisticUpdatePluginArguments: <T = any>(
|
|
27
|
+
id: string,
|
|
28
|
+
value: T,
|
|
29
|
+
replace?: boolean,
|
|
30
|
+
) => Promise<void>;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Add tool to assistant message with optimistic update
|
|
34
|
+
*/
|
|
35
|
+
optimisticAddToolToAssistantMessage: (id: string, tool: ChatToolPayload) => Promise<void>;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Remove tool from assistant message with optimistic update
|
|
39
|
+
*/
|
|
40
|
+
optimisticRemoveToolFromAssistantMessage: (id: string, tool_call_id?: string) => Promise<void>;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Update plugin error with optimistic update
|
|
44
|
+
*/
|
|
45
|
+
optimisticUpdatePluginError: (id: string, error: ChatMessageError) => Promise<void>;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Use the optimistic update value to update the message tools to database
|
|
49
|
+
*/
|
|
50
|
+
internal_refreshToUpdateMessageTools: (id: string) => Promise<void>;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export const pluginOptimisticUpdate: StateCreator<
|
|
54
|
+
ChatStore,
|
|
55
|
+
[['zustand/devtools', never]],
|
|
56
|
+
[],
|
|
57
|
+
PluginOptimisticUpdateAction
|
|
58
|
+
> = (set, get) => ({
|
|
59
|
+
optimisticUpdatePluginState: async (id, value) => {
|
|
60
|
+
const { replaceMessages } = get();
|
|
61
|
+
|
|
62
|
+
// optimistic update
|
|
63
|
+
get().internal_dispatchMessage({ id, type: 'updateMessage', value: { pluginState: value } });
|
|
64
|
+
|
|
65
|
+
const result = await messageService.updateMessagePluginState(id, value, {
|
|
66
|
+
sessionId: get().activeId,
|
|
67
|
+
topicId: get().activeTopicId,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
if (result?.success && result.messages) {
|
|
71
|
+
replaceMessages(result.messages);
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
optimisticUpdatePluginArguments: async (id, value, replace = false) => {
|
|
76
|
+
const { refreshMessages } = get();
|
|
77
|
+
const toolMessage = displayMessageSelectors.getDisplayMessageById(id)(get());
|
|
78
|
+
if (!toolMessage || !toolMessage?.tool_call_id) return;
|
|
79
|
+
|
|
80
|
+
let assistantMessage = displayMessageSelectors.getDisplayMessageById(
|
|
81
|
+
toolMessage?.parentId || '',
|
|
82
|
+
)(get());
|
|
83
|
+
|
|
84
|
+
const prevArguments = toolMessage?.plugin?.arguments;
|
|
85
|
+
const prevJson = safeParseJSON(prevArguments || '');
|
|
86
|
+
const nextValue = replace ? (value as any) : merge(prevJson || {}, value);
|
|
87
|
+
if (isEqual(prevJson, nextValue)) return;
|
|
88
|
+
|
|
89
|
+
// optimistic update
|
|
90
|
+
get().internal_dispatchMessage({
|
|
91
|
+
id,
|
|
92
|
+
type: 'updateMessagePlugin',
|
|
93
|
+
value: { arguments: JSON.stringify(nextValue) },
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// 同样需要更新 assistantMessage 的 pluginArguments
|
|
97
|
+
if (assistantMessage) {
|
|
98
|
+
get().internal_dispatchMessage({
|
|
99
|
+
id: assistantMessage.id,
|
|
100
|
+
type: 'updateMessageTools',
|
|
101
|
+
tool_call_id: toolMessage?.tool_call_id,
|
|
102
|
+
value: { arguments: JSON.stringify(nextValue) },
|
|
103
|
+
});
|
|
104
|
+
assistantMessage = displayMessageSelectors.getDisplayMessageById(assistantMessage?.id)(get());
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const updateAssistantMessage = async () => {
|
|
108
|
+
if (!assistantMessage) return;
|
|
109
|
+
await messageService.updateMessage(assistantMessage!.id, {
|
|
110
|
+
tools: assistantMessage?.tools,
|
|
111
|
+
});
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
await Promise.all([
|
|
115
|
+
messageService.updateMessagePluginArguments(id, nextValue),
|
|
116
|
+
updateAssistantMessage(),
|
|
117
|
+
]);
|
|
118
|
+
|
|
119
|
+
await refreshMessages();
|
|
120
|
+
},
|
|
121
|
+
|
|
122
|
+
optimisticAddToolToAssistantMessage: async (id, tool) => {
|
|
123
|
+
const assistantMessage = displayMessageSelectors.getDisplayMessageById(id)(get());
|
|
124
|
+
if (!assistantMessage) return;
|
|
125
|
+
|
|
126
|
+
const { internal_dispatchMessage, internal_refreshToUpdateMessageTools } = get();
|
|
127
|
+
internal_dispatchMessage({
|
|
128
|
+
type: 'addMessageTool',
|
|
129
|
+
value: tool,
|
|
130
|
+
id: assistantMessage.id,
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
await internal_refreshToUpdateMessageTools(id);
|
|
134
|
+
},
|
|
135
|
+
|
|
136
|
+
optimisticRemoveToolFromAssistantMessage: async (id, tool_call_id) => {
|
|
137
|
+
const message = displayMessageSelectors.getDisplayMessageById(id)(get());
|
|
138
|
+
if (!message || !tool_call_id) return;
|
|
139
|
+
|
|
140
|
+
const { internal_dispatchMessage, internal_refreshToUpdateMessageTools } = get();
|
|
141
|
+
|
|
142
|
+
// optimistic update
|
|
143
|
+
internal_dispatchMessage({ type: 'deleteMessageTool', tool_call_id, id: message.id });
|
|
144
|
+
|
|
145
|
+
// update the message tools
|
|
146
|
+
await internal_refreshToUpdateMessageTools(id);
|
|
147
|
+
},
|
|
148
|
+
|
|
149
|
+
optimisticUpdatePluginError: async (id, error) => {
|
|
150
|
+
const { replaceMessages } = get();
|
|
151
|
+
|
|
152
|
+
get().internal_dispatchMessage({ id, type: 'updateMessage', value: { error } });
|
|
153
|
+
const result = await messageService.updateMessage(
|
|
154
|
+
id,
|
|
155
|
+
{ error },
|
|
156
|
+
{
|
|
157
|
+
sessionId: get().activeId,
|
|
158
|
+
topicId: get().activeTopicId,
|
|
159
|
+
},
|
|
160
|
+
);
|
|
161
|
+
if (result?.success && result.messages) {
|
|
162
|
+
replaceMessages(result.messages);
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
|
|
166
|
+
internal_refreshToUpdateMessageTools: async (id) => {
|
|
167
|
+
const { dbMessageSelectors } = await import('../../message/selectors');
|
|
168
|
+
const message = dbMessageSelectors.getDbMessageById(id)(get());
|
|
169
|
+
if (!message || !message.tools) return;
|
|
170
|
+
|
|
171
|
+
const { internal_toggleMessageLoading, replaceMessages } = get();
|
|
172
|
+
|
|
173
|
+
internal_toggleMessageLoading(true, id);
|
|
174
|
+
const result = await messageService.updateMessage(
|
|
175
|
+
id,
|
|
176
|
+
{ tools: message.tools },
|
|
177
|
+
{
|
|
178
|
+
sessionId: get().activeId,
|
|
179
|
+
topicId: get().activeTopicId,
|
|
180
|
+
},
|
|
181
|
+
);
|
|
182
|
+
internal_toggleMessageLoading(false, id);
|
|
183
|
+
|
|
184
|
+
if (result?.success && result.messages) {
|
|
185
|
+
replaceMessages(result.messages);
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
});
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
/* eslint-disable sort-keys-fix/sort-keys-fix, typescript-sort-keys/interface */
|
|
2
|
+
import { ChatToolPayload } from '@lobechat/types';
|
|
3
|
+
import { PluginErrorType } from '@lobehub/chat-plugin-sdk';
|
|
4
|
+
import { t } from 'i18next';
|
|
5
|
+
import { StateCreator } from 'zustand/vanilla';
|
|
6
|
+
|
|
7
|
+
import { MCPToolCallResult } from '@/libs/mcp';
|
|
8
|
+
import { chatService } from '@/services/chat';
|
|
9
|
+
import { mcpService } from '@/services/mcp';
|
|
10
|
+
import { messageService } from '@/services/message';
|
|
11
|
+
import { ChatStore } from '@/store/chat/store';
|
|
12
|
+
import { useToolStore } from '@/store/tool';
|
|
13
|
+
import { safeParseJSON } from '@/utils/safeParseJSON';
|
|
14
|
+
import { setNamespace } from '@/utils/storeDebug';
|
|
15
|
+
|
|
16
|
+
import { displayMessageSelectors } from '../../message/selectors';
|
|
17
|
+
|
|
18
|
+
const n = setNamespace('plugin');
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Plugin type-specific implementations
|
|
22
|
+
* Each method handles a specific type of plugin invocation
|
|
23
|
+
*/
|
|
24
|
+
export interface PluginTypesAction {
|
|
25
|
+
/**
|
|
26
|
+
* Invoke builtin tool
|
|
27
|
+
*/
|
|
28
|
+
invokeBuiltinTool: (id: string, payload: ChatToolPayload) => Promise<void>;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Invoke default type plugin (returns data)
|
|
32
|
+
*/
|
|
33
|
+
invokeDefaultTypePlugin: (id: string, payload: any) => Promise<string | undefined>;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Invoke markdown type plugin
|
|
37
|
+
*/
|
|
38
|
+
invokeMarkdownTypePlugin: (id: string, payload: ChatToolPayload) => Promise<void>;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Invoke MCP type plugin
|
|
42
|
+
*/
|
|
43
|
+
invokeMCPTypePlugin: (id: string, payload: ChatToolPayload) => Promise<string | undefined>;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Invoke standalone type plugin
|
|
47
|
+
*/
|
|
48
|
+
invokeStandaloneTypePlugin: (id: string, payload: ChatToolPayload) => Promise<void>;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Internal method to call plugin API
|
|
52
|
+
*/
|
|
53
|
+
internal_callPluginApi: (id: string, payload: ChatToolPayload) => Promise<string | undefined>;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export const pluginTypes: StateCreator<
|
|
57
|
+
ChatStore,
|
|
58
|
+
[['zustand/devtools', never]],
|
|
59
|
+
[],
|
|
60
|
+
PluginTypesAction
|
|
61
|
+
> = (set, get) => ({
|
|
62
|
+
invokeBuiltinTool: async (id, payload) => {
|
|
63
|
+
// run tool api call
|
|
64
|
+
// @ts-ignore
|
|
65
|
+
const { [payload.apiName]: action } = get();
|
|
66
|
+
if (!action) return;
|
|
67
|
+
|
|
68
|
+
const content = safeParseJSON(payload.arguments);
|
|
69
|
+
|
|
70
|
+
if (!content) return;
|
|
71
|
+
|
|
72
|
+
return await action(id, content);
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
invokeDefaultTypePlugin: async (id, payload) => {
|
|
76
|
+
const { internal_callPluginApi } = get();
|
|
77
|
+
|
|
78
|
+
const data = await internal_callPluginApi(id, payload);
|
|
79
|
+
|
|
80
|
+
if (!data) return;
|
|
81
|
+
|
|
82
|
+
return data;
|
|
83
|
+
},
|
|
84
|
+
|
|
85
|
+
invokeMarkdownTypePlugin: async (id, payload) => {
|
|
86
|
+
const { internal_callPluginApi } = get();
|
|
87
|
+
|
|
88
|
+
await internal_callPluginApi(id, payload);
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
invokeStandaloneTypePlugin: async (id, payload) => {
|
|
92
|
+
const result = await useToolStore.getState().validatePluginSettings(payload.identifier);
|
|
93
|
+
if (!result) return;
|
|
94
|
+
|
|
95
|
+
// if the plugin settings is not valid, then set the message with error type
|
|
96
|
+
if (!result.valid) {
|
|
97
|
+
const updateResult = await messageService.updateMessageError(id, {
|
|
98
|
+
body: {
|
|
99
|
+
error: result.errors,
|
|
100
|
+
message: '[plugin] your settings is invalid with plugin manifest setting schema',
|
|
101
|
+
},
|
|
102
|
+
message: t('response.PluginSettingsInvalid', { ns: 'error' }),
|
|
103
|
+
type: PluginErrorType.PluginSettingsInvalid as any,
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
if (updateResult?.success && updateResult.messages) {
|
|
107
|
+
get().replaceMessages(updateResult.messages);
|
|
108
|
+
}
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
|
|
113
|
+
invokeMCPTypePlugin: async (id, payload) => {
|
|
114
|
+
const {
|
|
115
|
+
optimisticUpdateMessageContent,
|
|
116
|
+
internal_togglePluginApiCalling,
|
|
117
|
+
internal_constructToolsCallingContext,
|
|
118
|
+
optimisticUpdatePluginState,
|
|
119
|
+
optimisticUpdateMessagePluginError,
|
|
120
|
+
} = get();
|
|
121
|
+
let data: MCPToolCallResult | undefined;
|
|
122
|
+
|
|
123
|
+
try {
|
|
124
|
+
const abortController = internal_togglePluginApiCalling(
|
|
125
|
+
true,
|
|
126
|
+
id,
|
|
127
|
+
n('fetchPlugin/start') as string,
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
const context = internal_constructToolsCallingContext(id);
|
|
131
|
+
const result = await mcpService.invokeMcpToolCall(payload, {
|
|
132
|
+
signal: abortController?.signal,
|
|
133
|
+
topicId: context?.topicId,
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
if (!!result) data = result;
|
|
137
|
+
} catch (error) {
|
|
138
|
+
console.log(error);
|
|
139
|
+
const err = error as Error;
|
|
140
|
+
|
|
141
|
+
// ignore the aborted request error
|
|
142
|
+
if (!err.message.includes('The user aborted a request.')) {
|
|
143
|
+
const result = await messageService.updateMessageError(id, error as any);
|
|
144
|
+
if (result?.success && result.messages) {
|
|
145
|
+
get().replaceMessages(result.messages);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
internal_togglePluginApiCalling(false, id, n('fetchPlugin/end') as string);
|
|
151
|
+
|
|
152
|
+
// 如果报错则结束了
|
|
153
|
+
|
|
154
|
+
if (!data) return;
|
|
155
|
+
|
|
156
|
+
await Promise.all([
|
|
157
|
+
optimisticUpdateMessageContent(id, data.content),
|
|
158
|
+
(async () => {
|
|
159
|
+
if (data.success) await optimisticUpdatePluginState(id, data.state);
|
|
160
|
+
else await optimisticUpdateMessagePluginError(id, data.error);
|
|
161
|
+
})(),
|
|
162
|
+
]);
|
|
163
|
+
|
|
164
|
+
return data.content;
|
|
165
|
+
},
|
|
166
|
+
|
|
167
|
+
internal_callPluginApi: async (id, payload) => {
|
|
168
|
+
const { optimisticUpdateMessageContent, internal_togglePluginApiCalling } = get();
|
|
169
|
+
let data: string;
|
|
170
|
+
|
|
171
|
+
try {
|
|
172
|
+
const abortController = internal_togglePluginApiCalling(
|
|
173
|
+
true,
|
|
174
|
+
id,
|
|
175
|
+
n('fetchPlugin/start') as string,
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
const message = displayMessageSelectors.getDisplayMessageById(id)(get());
|
|
179
|
+
|
|
180
|
+
const res = await chatService.runPluginApi(payload, {
|
|
181
|
+
signal: abortController?.signal,
|
|
182
|
+
trace: { observationId: message?.observationId, traceId: message?.traceId },
|
|
183
|
+
});
|
|
184
|
+
data = res.text;
|
|
185
|
+
|
|
186
|
+
// save traceId
|
|
187
|
+
if (res.traceId) {
|
|
188
|
+
await messageService.updateMessage(id, { traceId: res.traceId });
|
|
189
|
+
}
|
|
190
|
+
} catch (error) {
|
|
191
|
+
console.log(error);
|
|
192
|
+
const err = error as Error;
|
|
193
|
+
|
|
194
|
+
// ignore the aborted request error
|
|
195
|
+
if (!err.message.includes('The user aborted a request.')) {
|
|
196
|
+
const result = await messageService.updateMessageError(id, error as any);
|
|
197
|
+
if (result?.success && result.messages) {
|
|
198
|
+
get().replaceMessages(result.messages);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
data = '';
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
internal_togglePluginApiCalling(false, id, n('fetchPlugin/end') as string);
|
|
206
|
+
// 如果报错则结束了
|
|
207
|
+
if (!data) return;
|
|
208
|
+
|
|
209
|
+
await optimisticUpdateMessageContent(id, data);
|
|
210
|
+
|
|
211
|
+
return data;
|
|
212
|
+
},
|
|
213
|
+
});
|