@lobehub/lobehub 2.0.0-next.35 → 2.0.0-next.36

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (154) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/changelog/v1.json +9 -0
  3. package/next.config.ts +5 -6
  4. package/package.json +2 -2
  5. package/packages/agent-runtime/src/core/__tests__/runtime.test.ts +112 -77
  6. package/packages/agent-runtime/src/core/runtime.ts +63 -18
  7. package/packages/agent-runtime/src/types/generalAgent.ts +55 -0
  8. package/packages/agent-runtime/src/types/index.ts +1 -0
  9. package/packages/agent-runtime/src/types/instruction.ts +10 -3
  10. package/packages/const/src/user.ts +0 -1
  11. package/packages/context-engine/src/processors/GroupMessageFlatten.ts +8 -6
  12. package/packages/context-engine/src/processors/__tests__/GroupMessageFlatten.test.ts +12 -12
  13. package/packages/conversation-flow/src/__tests__/fixtures/inputs/branch/assistant-group-branches.json +249 -0
  14. package/packages/conversation-flow/src/__tests__/fixtures/inputs/branch/index.ts +4 -0
  15. package/packages/conversation-flow/src/__tests__/fixtures/inputs/branch/multi-assistant-group.json +260 -0
  16. package/packages/conversation-flow/src/__tests__/fixtures/outputs/branch/active-index-1.json +4 -0
  17. package/packages/conversation-flow/src/__tests__/fixtures/outputs/branch/assistant-group-branches.json +481 -0
  18. package/packages/conversation-flow/src/__tests__/fixtures/outputs/branch/conversation.json +5 -1
  19. package/packages/conversation-flow/src/__tests__/fixtures/outputs/branch/index.ts +4 -0
  20. package/packages/conversation-flow/src/__tests__/fixtures/outputs/branch/multi-assistant-group.json +407 -0
  21. package/packages/conversation-flow/src/__tests__/fixtures/outputs/branch/nested.json +18 -2
  22. package/packages/conversation-flow/src/__tests__/fixtures/outputs/complex-scenario.json +25 -3
  23. package/packages/conversation-flow/src/__tests__/parse.test.ts +12 -0
  24. package/packages/conversation-flow/src/index.ts +1 -1
  25. package/packages/conversation-flow/src/transformation/FlatListBuilder.ts +112 -34
  26. package/packages/conversation-flow/src/types/flatMessageList.ts +0 -12
  27. package/packages/conversation-flow/src/{types.ts → types/index.ts} +3 -14
  28. package/packages/database/src/models/message.ts +18 -19
  29. package/packages/types/src/aiChat.ts +2 -0
  30. package/packages/types/src/importer.ts +2 -2
  31. package/packages/types/src/message/ui/chat.ts +17 -1
  32. package/packages/types/src/message/ui/extra.ts +2 -2
  33. package/packages/types/src/message/ui/params.ts +2 -2
  34. package/packages/types/src/user/preference.ts +0 -4
  35. package/packages/utils/src/tokenizer/index.ts +3 -11
  36. package/src/app/[variants]/(main)/chat/components/conversation/features/ChatInput/Desktop/MessageFromUrl.tsx +3 -3
  37. package/src/app/[variants]/(main)/chat/components/conversation/features/ChatInput/V1Mobile/index.tsx +1 -1
  38. package/src/app/[variants]/(main)/chat/components/conversation/features/ChatInput/V1Mobile/useSend.ts +3 -3
  39. package/src/app/[variants]/(main)/chat/components/conversation/features/ChatInput/useSend.ts +6 -6
  40. package/src/app/[variants]/(main)/chat/components/conversation/features/ChatList/Content.tsx +5 -3
  41. package/src/app/[variants]/(main)/chat/components/conversation/features/ChatList/WelcomeChatItem/AgentWelcome/OpeningQuestions.tsx +2 -2
  42. package/src/app/[variants]/(main)/chat/components/conversation/features/ChatList/WelcomeChatItem/GroupWelcome/GroupUsageSuggest.tsx +2 -2
  43. package/src/app/[variants]/(main)/labs/page.tsx +0 -9
  44. package/src/features/ChatInput/ActionBar/STT/browser.tsx +3 -3
  45. package/src/features/ChatInput/ActionBar/STT/openai.tsx +3 -3
  46. package/src/features/Conversation/Error/AccessCodeForm.tsx +1 -1
  47. package/src/features/Conversation/Error/ChatInvalidApiKey.tsx +1 -1
  48. package/src/features/Conversation/Error/ClerkLogin/index.tsx +1 -1
  49. package/src/features/Conversation/Error/OAuthForm.tsx +1 -1
  50. package/src/features/Conversation/Error/index.tsx +0 -5
  51. package/src/features/Conversation/Messages/Assistant/Actions/index.tsx +13 -10
  52. package/src/features/Conversation/Messages/Assistant/Extra/index.test.tsx +3 -8
  53. package/src/features/Conversation/Messages/Assistant/Extra/index.tsx +2 -6
  54. package/src/features/Conversation/Messages/Assistant/MessageContent.tsx +7 -9
  55. package/src/features/Conversation/Messages/Assistant/Tool/Inspector/PluginResult.tsx +2 -2
  56. package/src/features/Conversation/Messages/Assistant/Tool/Inspector/PluginState.tsx +2 -2
  57. package/src/features/Conversation/Messages/Assistant/Tool/Render/PluginSettings.tsx +4 -1
  58. package/src/features/Conversation/Messages/Assistant/Tool/Render/index.tsx +2 -3
  59. package/src/features/Conversation/Messages/Assistant/index.tsx +57 -60
  60. package/src/features/Conversation/Messages/Default.tsx +1 -0
  61. package/src/features/Conversation/Messages/Group/Actions/WithContentId.tsx +38 -10
  62. package/src/features/Conversation/Messages/Group/Actions/index.tsx +1 -1
  63. package/src/features/Conversation/Messages/Group/ContentBlock.tsx +1 -3
  64. package/src/features/Conversation/Messages/Group/GroupChildren.tsx +12 -12
  65. package/src/features/Conversation/Messages/Group/MessageContent.tsx +7 -1
  66. package/src/features/Conversation/Messages/Group/Tool/Render/PluginSettings.tsx +1 -1
  67. package/src/features/Conversation/Messages/Group/index.tsx +2 -1
  68. package/src/features/Conversation/Messages/Supervisor/index.tsx +2 -2
  69. package/src/features/Conversation/Messages/User/{Actions.tsx → Actions/ActionsBar.tsx} +26 -25
  70. package/src/features/Conversation/Messages/User/Actions/MessageBranch.tsx +107 -0
  71. package/src/features/Conversation/Messages/User/Actions/index.tsx +42 -0
  72. package/src/features/Conversation/Messages/User/index.tsx +43 -44
  73. package/src/features/Conversation/Messages/index.tsx +3 -3
  74. package/src/features/Conversation/components/AutoScroll.tsx +3 -3
  75. package/src/features/Conversation/components/Extras/Usage/UsageDetail/AnimatedNumber.tsx +55 -0
  76. package/src/features/Conversation/components/Extras/Usage/UsageDetail/index.tsx +5 -2
  77. package/src/features/Conversation/components/VirtualizedList/index.tsx +29 -20
  78. package/src/features/Conversation/hooks/useChatListActionsBar.tsx +8 -10
  79. package/src/features/Portal/Thread/Chat/ChatInput/useSend.ts +3 -3
  80. package/src/hooks/useHotkeys/chatScope.ts +15 -7
  81. package/src/server/routers/lambda/__tests__/aiChat.test.ts +1 -1
  82. package/src/server/routers/lambda/__tests__/integration/message.integration.test.ts +0 -26
  83. package/src/server/routers/lambda/aiChat.ts +3 -2
  84. package/src/server/routers/lambda/message.ts +8 -16
  85. package/src/server/services/message/__tests__/index.test.ts +29 -39
  86. package/src/server/services/message/index.ts +41 -36
  87. package/src/services/electron/desktopNotification.ts +6 -6
  88. package/src/services/electron/file.ts +6 -6
  89. package/src/services/file/ClientS3/index.ts +8 -8
  90. package/src/services/message/__tests__/metadata-race-condition.test.ts +157 -0
  91. package/src/services/message/index.ts +21 -15
  92. package/src/services/upload.ts +11 -11
  93. package/src/services/utils/abortableRequest.test.ts +161 -0
  94. package/src/services/utils/abortableRequest.ts +67 -0
  95. package/src/store/chat/agents/GeneralChatAgent.ts +137 -0
  96. package/src/store/chat/agents/createAgentExecutors.ts +395 -0
  97. package/src/store/chat/helpers.test.ts +0 -99
  98. package/src/store/chat/helpers.ts +0 -11
  99. package/src/store/chat/slices/aiChat/actions/__tests__/conversationControl.test.ts +332 -0
  100. package/src/store/chat/slices/aiChat/actions/__tests__/conversationLifecycle.test.ts +257 -0
  101. package/src/store/chat/slices/aiChat/actions/__tests__/helpers.ts +11 -2
  102. package/src/store/chat/slices/aiChat/actions/__tests__/rag.test.ts +6 -6
  103. package/src/store/chat/slices/aiChat/actions/__tests__/streamingExecutor.test.ts +391 -0
  104. package/src/store/chat/slices/aiChat/actions/__tests__/streamingStates.test.ts +179 -0
  105. package/src/store/chat/slices/aiChat/actions/conversationControl.ts +157 -0
  106. package/src/store/chat/slices/aiChat/actions/conversationLifecycle.ts +329 -0
  107. package/src/store/chat/slices/aiChat/actions/generateAIGroupChat.ts +14 -14
  108. package/src/store/chat/slices/aiChat/actions/index.ts +12 -6
  109. package/src/store/chat/slices/aiChat/actions/rag.ts +9 -6
  110. package/src/store/chat/slices/aiChat/actions/streamingExecutor.ts +604 -0
  111. package/src/store/chat/slices/aiChat/actions/streamingStates.ts +84 -0
  112. package/src/store/chat/slices/builtinTool/actions/__tests__/localSystem.test.ts +4 -4
  113. package/src/store/chat/slices/builtinTool/actions/__tests__/search.test.ts +11 -11
  114. package/src/store/chat/slices/builtinTool/actions/interpreter.ts +8 -8
  115. package/src/store/chat/slices/builtinTool/actions/localSystem.ts +2 -2
  116. package/src/store/chat/slices/builtinTool/actions/search.ts +8 -8
  117. package/src/store/chat/slices/message/action.test.ts +79 -68
  118. package/src/store/chat/slices/message/actions/index.ts +39 -0
  119. package/src/store/chat/slices/message/actions/internals.ts +77 -0
  120. package/src/store/chat/slices/message/actions/optimisticUpdate.ts +260 -0
  121. package/src/store/chat/slices/message/actions/publicApi.ts +224 -0
  122. package/src/store/chat/slices/message/actions/query.ts +120 -0
  123. package/src/store/chat/slices/message/actions/runtimeState.ts +108 -0
  124. package/src/store/chat/slices/message/initialState.ts +13 -0
  125. package/src/store/chat/slices/message/reducer.test.ts +48 -370
  126. package/src/store/chat/slices/message/reducer.ts +17 -81
  127. package/src/store/chat/slices/message/selectors/chat.test.ts +13 -50
  128. package/src/store/chat/slices/message/selectors/chat.ts +78 -242
  129. package/src/store/chat/slices/message/selectors/dbMessage.ts +140 -0
  130. package/src/store/chat/slices/message/selectors/displayMessage.ts +301 -0
  131. package/src/store/chat/slices/message/selectors/messageState.ts +5 -2
  132. package/src/store/chat/slices/plugin/action.test.ts +62 -64
  133. package/src/store/chat/slices/plugin/action.ts +34 -28
  134. package/src/store/chat/slices/thread/action.test.ts +28 -31
  135. package/src/store/chat/slices/thread/action.ts +13 -10
  136. package/src/store/chat/slices/thread/selectors/index.ts +8 -6
  137. package/src/store/chat/slices/topic/reducer.ts +11 -3
  138. package/src/store/chat/store.ts +1 -1
  139. package/src/store/user/slices/preference/selectors/labPrefer.ts +0 -3
  140. package/packages/database/src/models/__tests__/message.grouping.test.ts +0 -812
  141. package/packages/database/src/utils/__tests__/groupMessages.test.ts +0 -1132
  142. package/packages/database/src/utils/groupMessages.ts +0 -361
  143. package/packages/utils/src/tokenizer/client.ts +0 -35
  144. package/packages/utils/src/tokenizer/estimated.ts +0 -4
  145. package/packages/utils/src/tokenizer/server.ts +0 -11
  146. package/packages/utils/src/tokenizer/tokenizer.worker.ts +0 -12
  147. package/src/app/(backend)/webapi/tokenizer/index.test.ts +0 -32
  148. package/src/app/(backend)/webapi/tokenizer/route.ts +0 -8
  149. package/src/features/Conversation/Error/InvalidAccessCode.tsx +0 -79
  150. package/src/store/chat/slices/aiChat/actions/__tests__/generateAIChat.test.ts +0 -975
  151. package/src/store/chat/slices/aiChat/actions/__tests__/generateAIChatV2.test.ts +0 -1050
  152. package/src/store/chat/slices/aiChat/actions/generateAIChat.ts +0 -720
  153. package/src/store/chat/slices/aiChat/actions/generateAIChatV2.ts +0 -849
  154. package/src/store/chat/slices/message/action.ts +0 -629
@@ -0,0 +1,157 @@
1
+ /* eslint-disable sort-keys-fix/sort-keys-fix, typescript-sort-keys/interface */
2
+ // Disable the auto sort key eslint rule to make the code more logic and readable
3
+ import { MESSAGE_CANCEL_FLAT } from '@lobechat/const';
4
+ import { produce } from 'immer';
5
+ import { StateCreator } from 'zustand/vanilla';
6
+
7
+ import { ChatStore } from '@/store/chat/store';
8
+ import { setNamespace } from '@/utils/storeDebug';
9
+
10
+ import { messageMapKey } from '../../../utils/messageMapKey';
11
+ import { MainSendMessageOperation } from '../initialState';
12
+
13
+ const n = setNamespace('ai');
14
+
15
+ /**
16
+ * Actions for controlling conversation operations like cancellation and error handling
17
+ */
18
+ export interface ConversationControlAction {
19
+ /**
20
+ * Interrupts the ongoing ai message generation process
21
+ */
22
+ stopGenerateMessage: () => void;
23
+ /**
24
+ * Cancels sendMessage operation for a specific topic/session
25
+ */
26
+ cancelSendMessageInServer: (topicId?: string) => void;
27
+ /**
28
+ * Clears any error messages from the send message operation
29
+ */
30
+ clearSendMessageError: () => void;
31
+ /**
32
+ * Switches to a different branch of a message
33
+ */
34
+ switchMessageBranch: (messageId: string, branchIndex: number) => Promise<void>;
35
+ /**
36
+ * Toggle sendMessage operation state
37
+ */
38
+ internal_toggleSendMessageOperation: (
39
+ key: string | { sessionId: string; topicId?: string | null },
40
+ loading: boolean,
41
+ cancelReason?: string,
42
+ ) => AbortController | undefined;
43
+ /**
44
+ * Update sendMessage operation metadata
45
+ */
46
+ internal_updateSendMessageOperation: (
47
+ key: string | { sessionId: string; topicId?: string | null },
48
+ value: Partial<MainSendMessageOperation> | null,
49
+ actionName?: any,
50
+ ) => void;
51
+ }
52
+
53
+ export const conversationControl: StateCreator<
54
+ ChatStore,
55
+ [['zustand/devtools', never]],
56
+ [],
57
+ ConversationControlAction
58
+ > = (set, get) => ({
59
+ stopGenerateMessage: () => {
60
+ const { chatLoadingIdsAbortController, internal_toggleChatLoading } = get();
61
+
62
+ if (!chatLoadingIdsAbortController) return;
63
+
64
+ chatLoadingIdsAbortController.abort(MESSAGE_CANCEL_FLAT);
65
+
66
+ internal_toggleChatLoading(false, undefined, n('stopGenerateMessage') as string);
67
+ },
68
+
69
+ cancelSendMessageInServer: (topicId?: string) => {
70
+ const { activeId, activeTopicId } = get();
71
+
72
+ // Determine which operation to cancel
73
+ const targetTopicId = topicId ?? activeTopicId;
74
+ const operationKey = messageMapKey(activeId, targetTopicId);
75
+
76
+ // Cancel the specific operation
77
+ get().internal_toggleSendMessageOperation(
78
+ operationKey,
79
+ false,
80
+ 'User cancelled sendMessage operation',
81
+ );
82
+
83
+ // Only clear creating message state if it's the active session
84
+ if (operationKey === messageMapKey(activeId, activeTopicId)) {
85
+ const editorTempState = get().mainSendMessageOperations[operationKey]?.inputEditorTempState;
86
+
87
+ if (editorTempState) get().mainInputEditor?.setJSONState(editorTempState);
88
+ }
89
+ },
90
+
91
+ clearSendMessageError: () => {
92
+ get().internal_updateSendMessageOperation(
93
+ { sessionId: get().activeId, topicId: get().activeTopicId },
94
+ null,
95
+ 'clearSendMessageError',
96
+ );
97
+ },
98
+
99
+ switchMessageBranch: async (messageId, branchIndex) => {
100
+ await get().optimisticUpdateMessageMetadata(messageId, { activeBranchIndex: branchIndex });
101
+ },
102
+
103
+ internal_toggleSendMessageOperation: (key, loading: boolean, cancelReason?: string) => {
104
+ if (loading) {
105
+ const abortController = new AbortController();
106
+
107
+ get().internal_updateSendMessageOperation(
108
+ key,
109
+ { isLoading: true, abortController },
110
+ n('toggleSendMessageOperation(start)', { key }),
111
+ );
112
+
113
+ return abortController;
114
+ } else {
115
+ const operationKey =
116
+ typeof key === 'string' ? key : messageMapKey(key.sessionId, key.topicId);
117
+
118
+ const operation = get().mainSendMessageOperations[operationKey];
119
+
120
+ // If cancelReason is provided, abort the operation first
121
+ if (cancelReason && operation?.isLoading) {
122
+ operation.abortController?.abort(cancelReason);
123
+ }
124
+
125
+ get().internal_updateSendMessageOperation(
126
+ key,
127
+ { isLoading: false, abortController: null },
128
+ n('toggleSendMessageOperation(stop)', { key, cancelReason }),
129
+ );
130
+
131
+ return undefined;
132
+ }
133
+ },
134
+
135
+ internal_updateSendMessageOperation: (key, value, actionName) => {
136
+ const operationKey = typeof key === 'string' ? key : messageMapKey(key.sessionId, key.topicId);
137
+
138
+ set(
139
+ produce((draft) => {
140
+ if (!draft.mainSendMessageOperations[operationKey])
141
+ draft.mainSendMessageOperations[operationKey] = value;
142
+ else {
143
+ if (value === null) {
144
+ delete draft.mainSendMessageOperations[operationKey];
145
+ } else {
146
+ draft.mainSendMessageOperations[operationKey] = {
147
+ ...draft.mainSendMessageOperations[operationKey],
148
+ ...value,
149
+ };
150
+ }
151
+ }
152
+ }),
153
+ false,
154
+ actionName ?? n('updateSendMessageOperation', { operationKey, value }),
155
+ );
156
+ },
157
+ });
@@ -0,0 +1,329 @@
1
+ /* eslint-disable sort-keys-fix/sort-keys-fix, typescript-sort-keys/interface */
2
+ // Disable the auto sort key eslint rule to make the code more logic and readable
3
+ import { DEFAULT_AGENT_CHAT_CONFIG, INBOX_SESSION_ID } from '@lobechat/const';
4
+ import {
5
+ ChatImageItem,
6
+ ChatVideoItem,
7
+ SendMessageParams,
8
+ SendMessageServerResponse,
9
+ TraceEventType,
10
+ } from '@lobechat/types';
11
+ import { TRPCClientError } from '@trpc/client';
12
+ import { t } from 'i18next';
13
+ import { StateCreator } from 'zustand/vanilla';
14
+
15
+ import { aiChatService } from '@/services/aiChat';
16
+ import { getAgentStoreState } from '@/store/agent';
17
+ import { agentChatConfigSelectors, agentSelectors } from '@/store/agent/slices/chat';
18
+ import { ChatStore } from '@/store/chat/store';
19
+ import { getFileStoreState } from '@/store/file/store';
20
+ import { getSessionStoreState } from '@/store/session';
21
+
22
+ import {
23
+ dbMessageSelectors,
24
+ displayMessageSelectors,
25
+ messageStateSelectors,
26
+ topicSelectors,
27
+ } from '../../../selectors';
28
+ import { messageMapKey } from '../../../utils/messageMapKey';
29
+
30
+ /**
31
+ * Actions managing the complete lifecycle of conversations including sending,
32
+ * regenerating, and resending messages
33
+ */
34
+ export interface ConversationLifecycleAction {
35
+ /**
36
+ * Sends a new message to the AI chat system
37
+ */
38
+ sendMessage: (params: SendMessageParams) => Promise<void>;
39
+ regenerateUserMessage: (
40
+ id: string,
41
+ params?: { skipTrace?: boolean; traceId?: string },
42
+ ) => Promise<void>;
43
+ regenerateAssistantMessage: (
44
+ id: string,
45
+ params?: { skipTrace?: boolean; traceId?: string },
46
+ ) => Promise<void>;
47
+ /**
48
+ * Deletes an existing message and generates a new one in its place
49
+ */
50
+ delAndRegenerateMessage: (id: string) => Promise<void>;
51
+ }
52
+
53
+ export const conversationLifecycle: StateCreator<
54
+ ChatStore,
55
+ [['zustand/devtools', never]],
56
+ [],
57
+ ConversationLifecycleAction
58
+ > = (set, get) => ({
59
+ sendMessage: async ({ message, files, onlyAddUserMessage }) => {
60
+ const { activeTopicId, activeId, activeThreadId, internal_execAgentRuntime, mainInputEditor } =
61
+ get();
62
+ if (!activeId) return;
63
+
64
+ const fileIdList = files?.map((f) => f.id);
65
+
66
+ const hasFile = !!fileIdList && fileIdList.length > 0;
67
+
68
+ // if message is empty or no files, then stop
69
+ if (!message && !hasFile) return;
70
+
71
+ if (onlyAddUserMessage) {
72
+ await get().addUserMessage({ message, fileList: fileIdList });
73
+
74
+ return;
75
+ }
76
+
77
+ const messages = displayMessageSelectors.activeDisplayMessages(get());
78
+ const parentId = displayMessageSelectors.lastDisplayMessageId(get());
79
+
80
+ const chatConfig = agentChatConfigSelectors.currentChatConfig(getAgentStoreState());
81
+ const autoCreateThreshold =
82
+ chatConfig.autoCreateTopicThreshold ?? DEFAULT_AGENT_CHAT_CONFIG.autoCreateTopicThreshold;
83
+ const shouldCreateNewTopic =
84
+ !activeTopicId &&
85
+ !!chatConfig.enableAutoCreateTopic &&
86
+ messages.length + 2 >= autoCreateThreshold;
87
+
88
+ // 构造服务端模式临时消息的本地媒体预览(优先使用 S3 URL)
89
+ const filesInStore = getFileStoreState().chatUploadFileList;
90
+ const tempImages: ChatImageItem[] = filesInStore
91
+ .filter((f) => f.file?.type?.startsWith('image'))
92
+ .map((f) => ({
93
+ id: f.id,
94
+ url: f.fileUrl || f.base64Url || f.previewUrl || '',
95
+ alt: f.file?.name || f.id,
96
+ }));
97
+ const tempVideos: ChatVideoItem[] = filesInStore
98
+ .filter((f) => f.file?.type?.startsWith('video'))
99
+ .map((f) => ({
100
+ id: f.id,
101
+ url: f.fileUrl || f.base64Url || f.previewUrl || '',
102
+ alt: f.file?.name || f.id,
103
+ }));
104
+
105
+ // use optimistic update to avoid the slow waiting
106
+ const tempId = get().optimisticCreateTmpMessage({
107
+ content: message,
108
+ // if message has attached with files, then add files to message and the agent
109
+ files: fileIdList,
110
+ role: 'user',
111
+ sessionId: activeId,
112
+ // if there is activeTopicId,then add topicId to message
113
+ topicId: activeTopicId,
114
+ threadId: activeThreadId,
115
+ imageList: tempImages.length > 0 ? tempImages : undefined,
116
+ videoList: tempVideos.length > 0 ? tempVideos : undefined,
117
+ });
118
+ get().internal_toggleMessageLoading(true, tempId);
119
+
120
+ const operationKey = messageMapKey(activeId, activeTopicId);
121
+
122
+ // Start tracking sendMessage operation with AbortController
123
+ const abortController = get().internal_toggleSendMessageOperation(operationKey, true)!;
124
+
125
+ const jsonState = mainInputEditor?.getJSONState();
126
+ get().internal_updateSendMessageOperation(
127
+ operationKey,
128
+ { inputSendErrorMsg: undefined, inputEditorTempState: jsonState },
129
+ 'creatingMessage/start',
130
+ );
131
+
132
+ let data: SendMessageServerResponse | undefined;
133
+ try {
134
+ const { model, provider } = agentSelectors.currentAgentConfig(getAgentStoreState());
135
+
136
+ data = await aiChatService.sendMessageInServer(
137
+ {
138
+ newUserMessage: { content: message, files: fileIdList, parentId },
139
+ // if there is activeTopicId,then add topicId to message
140
+ topicId: activeTopicId,
141
+ threadId: activeThreadId,
142
+ newTopic: shouldCreateNewTopic
143
+ ? {
144
+ topicMessageIds: messages.map((m) => m.id),
145
+ title: t('defaultTitle', { ns: 'topic' }),
146
+ }
147
+ : undefined,
148
+ sessionId: activeId === INBOX_SESSION_ID ? undefined : activeId,
149
+ newAssistantMessage: { model, provider: provider! },
150
+ },
151
+ abortController,
152
+ );
153
+ let topicId = activeTopicId;
154
+ // refresh the total data
155
+ if (data?.topics) {
156
+ get().internal_dispatchTopic({ type: 'updateTopics', value: data.topics });
157
+ topicId = data.topicId;
158
+ }
159
+
160
+ get().replaceMessages(data.messages, { sessionId: activeId, topicId: topicId });
161
+
162
+ if (data.isCreateNewTopic && data.topicId) {
163
+ await get().switchTopic(data.topicId, true);
164
+ }
165
+ } catch (e) {
166
+ if (e instanceof TRPCClientError) {
167
+ const isAbort = e.message.includes('aborted') || e.name === 'AbortError';
168
+ // Check if error is due to cancellation
169
+ if (!isAbort) {
170
+ get().internal_updateSendMessageOperation(operationKey, { inputSendErrorMsg: e.message });
171
+ get().mainInputEditor?.setJSONState(jsonState);
172
+ }
173
+ }
174
+ } finally {
175
+ // Stop tracking sendMessage operation
176
+ get().internal_toggleSendMessageOperation(operationKey, false);
177
+ }
178
+
179
+ // remove temporally message
180
+ if (data?.isCreateNewTopic) {
181
+ get().internal_dispatchMessage(
182
+ { type: 'deleteMessage', id: tempId },
183
+ { topicId: activeTopicId, sessionId: activeId },
184
+ );
185
+ }
186
+
187
+ get().internal_toggleMessageLoading(false, tempId);
188
+ get().internal_updateSendMessageOperation(
189
+ operationKey,
190
+ { inputEditorTempState: null },
191
+ 'creatingMessage/finished',
192
+ );
193
+
194
+ if (!data) return;
195
+
196
+ // update assistant update to make it rerank
197
+ getSessionStoreState().triggerSessionUpdate(get().activeId);
198
+
199
+ if (data.topicId) get().internal_updateTopicLoading(data.topicId, true);
200
+
201
+ const summaryTitle = async () => {
202
+ // check activeTopic and then auto update topic title
203
+ if (data.isCreateNewTopic) {
204
+ await get().summaryTopicTitle(data.topicId, data.messages);
205
+ return;
206
+ }
207
+
208
+ if (!data.topicId) return;
209
+
210
+ const topic = topicSelectors.getTopicById(data.topicId)(get());
211
+
212
+ if (topic && !topic.title) {
213
+ const chats = displayMessageSelectors
214
+ .getDisplayMessagesByKey(messageMapKey(activeId, topic.id))(get())
215
+ .filter((item) => item.id !== data.assistantMessageId);
216
+
217
+ await get().summaryTopicTitle(topic.id, chats);
218
+ }
219
+ };
220
+
221
+ summaryTitle().catch(console.error);
222
+
223
+ // Get the current messages to generate AI response
224
+ const displayMessages = displayMessageSelectors.activeDisplayMessages(get());
225
+
226
+ try {
227
+ await internal_execAgentRuntime({
228
+ messages: displayMessages,
229
+ parentMessageId: data.assistantMessageId,
230
+ parentMessageType: 'assistant',
231
+ ragQuery: get().internal_shouldUseRAG() ? message : undefined,
232
+ threadId: activeThreadId,
233
+ });
234
+
235
+ //
236
+ // // if there is relative files, then add files to agent
237
+ // // only available in server mode
238
+ const userFiles = dbMessageSelectors
239
+ .dbUserFiles(get())
240
+ .map((f) => f?.id)
241
+ .filter(Boolean) as string[];
242
+
243
+ await getAgentStoreState().addFilesToAgent(userFiles, false);
244
+ } catch (e) {
245
+ console.error(e);
246
+ } finally {
247
+ if (data.topicId) get().internal_updateTopicLoading(data.topicId, false);
248
+ }
249
+ },
250
+
251
+ regenerateUserMessage: async (id, params) => {
252
+ const isRegenerating = messageStateSelectors.isMessageRegenerating(id)(get());
253
+ if (isRegenerating) return;
254
+
255
+ const item = displayMessageSelectors.getDisplayMessageById(id)(get());
256
+ if (!item) return;
257
+
258
+ const chats = displayMessageSelectors.mainAIChats(get());
259
+
260
+ const currentIndex = chats.findIndex((c) => c.id === id);
261
+ const contextMessages = chats.slice(0, currentIndex + 1);
262
+
263
+ if (contextMessages.length <= 0) return;
264
+
265
+ try {
266
+ const { internal_execAgentRuntime, activeThreadId } = get();
267
+
268
+ // Mark message as regenerating
269
+ set(
270
+ { regeneratingIds: [...get().regeneratingIds, id] },
271
+ false,
272
+ 'regenerateUserMessage/start',
273
+ );
274
+
275
+ const traceId = params?.traceId ?? dbMessageSelectors.getTraceIdByDbMessageId(id)(get());
276
+
277
+ // 切一个新的激活分支
278
+ await get().switchMessageBranch(id, item.branch ? item.branch.count : 1);
279
+
280
+ await internal_execAgentRuntime({
281
+ messages: contextMessages,
282
+ parentMessageId: id,
283
+ parentMessageType: 'user',
284
+ traceId,
285
+ ragQuery: get().internal_shouldUseRAG() ? item.content : undefined,
286
+ threadId: activeThreadId,
287
+ });
288
+
289
+ // trace the regenerate message
290
+ if (!params?.skipTrace)
291
+ get().internal_traceMessage(id, { eventType: TraceEventType.RegenerateMessage });
292
+ } finally {
293
+ // Remove message from regenerating state
294
+ set(
295
+ { regeneratingIds: get().regeneratingIds.filter((msgId) => msgId !== id) },
296
+ false,
297
+ 'regenerateUserMessage/end',
298
+ );
299
+ }
300
+ },
301
+
302
+ regenerateAssistantMessage: async (id, params) => {
303
+ const isRegenerating = messageStateSelectors.isMessageRegenerating(id)(get());
304
+ if (isRegenerating) return;
305
+
306
+ const chats = displayMessageSelectors.mainAIChats(get());
307
+ const currentIndex = chats.findIndex((c) => c.id === id);
308
+ const currentMessage = chats[currentIndex];
309
+
310
+ // 消息是 AI 发出的因此需要找到它的 user 消息
311
+ const userId = currentMessage.parentId;
312
+ const userIndex = chats.findIndex((c) => c.id === userId);
313
+ // 如果消息没有 parentId,那么同 user 模式
314
+ const contextMessages = chats.slice(0, userIndex < 0 ? currentIndex + 1 : userIndex + 1);
315
+
316
+ if (contextMessages.length <= 0 || !userId) return;
317
+
318
+ await get().regenerateUserMessage(userId, params);
319
+ },
320
+
321
+ delAndRegenerateMessage: async (id) => {
322
+ const traceId = dbMessageSelectors.getTraceIdByDbMessageId(id)(get());
323
+ get().regenerateAssistantMessage(id, { skipTrace: true, traceId });
324
+ get().deleteMessage(id);
325
+
326
+ // trace the delete and regenerate message
327
+ get().internal_traceMessage(id, { eventType: TraceEventType.DeleteAndRegenerateMessage });
328
+ },
329
+ });
@@ -271,7 +271,7 @@ export const chatAiGroupChat: StateCreator<
271
271
  return {
272
272
  sendGroupMessage: async ({ groupId, message, files, onlyAddUserMessage, targetMemberId }) => {
273
273
  const {
274
- internal_createMessage,
274
+ optimisticCreateMessage,
275
275
  internal_triggerSupervisorDecisionDebounced,
276
276
  internal_setActiveGroup,
277
277
  activeTopicId,
@@ -294,7 +294,7 @@ export const chatAiGroupChat: StateCreator<
294
294
  targetId: targetMemberId,
295
295
  };
296
296
 
297
- const result = await internal_createMessage(userMessage);
297
+ const result = await optimisticCreateMessage(userMessage);
298
298
 
299
299
  // if only add user message, then stop
300
300
  if (onlyAddUserMessage) {
@@ -379,7 +379,7 @@ export const chatAiGroupChat: StateCreator<
379
379
  const {
380
380
  messagesMap,
381
381
  internal_toggleSupervisorLoading,
382
- internal_createMessage,
382
+ optimisticCreateMessage,
383
383
  supervisorTodos,
384
384
  } = get();
385
385
 
@@ -398,8 +398,8 @@ export const chatAiGroupChat: StateCreator<
398
398
  const content = formatSupervisorTodoContent(todoList);
399
399
  const supervisorMessage: CreateMessageParams = {
400
400
  content,
401
- fromModel: groupConfig.orchestratorModel,
402
- fromProvider: groupConfig.orchestratorProvider,
401
+ model: groupConfig.orchestratorModel,
402
+ provider: groupConfig.orchestratorProvider,
403
403
  groupId,
404
404
  role: 'supervisor',
405
405
  sessionId,
@@ -408,7 +408,7 @@ export const chatAiGroupChat: StateCreator<
408
408
 
409
409
  console.log('Creating supervisor todo message:', supervisorMessage);
410
410
 
411
- await internal_createMessage(supervisorMessage);
411
+ await optimisticCreateMessage(supervisorMessage);
412
412
  };
413
413
 
414
414
  const messages = messagesMap[messageMapKey(groupId, currentTopicId)] || [];
@@ -597,7 +597,7 @@ export const chatAiGroupChat: StateCreator<
597
597
  });
598
598
  const {
599
599
  messagesMap,
600
- internal_createMessage,
600
+ optimisticCreateMessage,
601
601
  internal_fetchAIChatMessage,
602
602
  refreshMessages,
603
603
  activeTopicId,
@@ -659,10 +659,10 @@ export const chatAiGroupChat: StateCreator<
659
659
  // Create agent message using real agent config
660
660
  const agentMessage: CreateMessageParams = {
661
661
  role: 'assistant',
662
- fromModel: agentModel,
662
+ model: agentModel,
663
663
  groupId,
664
664
  content: LOADING_FLAT,
665
- fromProvider: agentProvider,
665
+ provider: agentProvider,
666
666
  agentId,
667
667
  sessionId: useSessionStore.getState().activeId,
668
668
  topicId: activeTopicId,
@@ -671,7 +671,7 @@ export const chatAiGroupChat: StateCreator<
671
671
 
672
672
  console.log('DEBUG: Creating agent message with:', agentMessage);
673
673
 
674
- const result = await internal_createMessage(agentMessage);
674
+ const result = await optimisticCreateMessage(agentMessage);
675
675
  if (!result) return;
676
676
  const assistantId = result.id;
677
677
 
@@ -936,7 +936,7 @@ export const chatAiGroupChat: StateCreator<
936
936
  },
937
937
 
938
938
  internal_createSupervisorErrorMessage: async (groupId: string, error: Error | string) => {
939
- const { internal_createTmpMessage, activeTopicId } = get();
939
+ const { optimisticCreateTmpMessage, activeTopicId } = get();
940
940
 
941
941
  try {
942
942
  const errorMessage = error instanceof Error ? error.message : error;
@@ -944,8 +944,8 @@ export const chatAiGroupChat: StateCreator<
944
944
 
945
945
  const supervisorMessage: CreateMessageParams = {
946
946
  role: 'supervisor',
947
- fromModel: groupConfig.orchestratorModel,
948
- fromProvider: groupConfig.orchestratorProvider,
947
+ model: groupConfig.orchestratorModel,
948
+ provider: groupConfig.orchestratorProvider,
949
949
  groupId,
950
950
  sessionId: useSessionStore.getState().activeId || groupId,
951
951
  topicId: activeTopicId,
@@ -957,7 +957,7 @@ export const chatAiGroupChat: StateCreator<
957
957
  };
958
958
 
959
959
  // Create a temporary message that only exists in UI state, no API call
960
- internal_createTmpMessage(supervisorMessage);
960
+ optimisticCreateTmpMessage(supervisorMessage);
961
961
  } catch (createError) {
962
962
  console.error('Failed to create supervisor error message:', createError);
963
963
  }
@@ -2,16 +2,20 @@ import { StateCreator } from 'zustand/vanilla';
2
2
 
3
3
  import { ChatStore } from '@/store/chat/store';
4
4
 
5
- import { AIGenerateAction, generateAIChat } from './generateAIChat';
6
- import { AIGenerateV2Action, generateAIChatV2 } from './generateAIChatV2';
5
+ import { ConversationControlAction, conversationControl } from './conversationControl';
6
+ import { ConversationLifecycleAction, conversationLifecycle } from './conversationLifecycle';
7
7
  import { ChatMemoryAction, chatMemory } from './memory';
8
8
  import { ChatRAGAction, chatRag } from './rag';
9
+ import { StreamingExecutorAction, streamingExecutor } from './streamingExecutor';
10
+ import { StreamingStatesAction, streamingStates } from './streamingStates';
9
11
 
10
12
  export interface ChatAIChatAction
11
13
  extends ChatRAGAction,
12
14
  ChatMemoryAction,
13
- AIGenerateAction,
14
- AIGenerateV2Action {
15
+ ConversationLifecycleAction,
16
+ ConversationControlAction,
17
+ StreamingExecutorAction,
18
+ StreamingStatesAction {
15
19
  /**/
16
20
  }
17
21
 
@@ -22,7 +26,9 @@ export const chatAiChat: StateCreator<
22
26
  ChatAIChatAction
23
27
  > = (...params) => ({
24
28
  ...chatRag(...params),
25
- ...generateAIChat(...params),
26
29
  ...chatMemory(...params),
27
- ...generateAIChatV2(...params),
30
+ ...conversationLifecycle(...params),
31
+ ...conversationControl(...params),
32
+ ...streamingExecutor(...params),
33
+ ...streamingStates(...params),
28
34
  });
@@ -6,7 +6,7 @@ import { ragService } from '@/services/rag';
6
6
  import { useAgentStore } from '@/store/agent';
7
7
  import { agentSelectors } from '@/store/agent/selectors';
8
8
  import { ChatStore } from '@/store/chat';
9
- import { chatSelectors } from '@/store/chat/selectors';
9
+ import { dbMessageSelectors, displayMessageSelectors } from '@/store/chat/selectors';
10
10
  import { toggleBooleanList } from '@/store/chat/utils';
11
11
  import { useUserStore } from '@/store/user';
12
12
  import { systemAgentSelectors } from '@/store/user/selectors';
@@ -43,7 +43,7 @@ export const chatRag: StateCreator<ChatStore, [['zustand/devtools', never]], [],
43
43
  get,
44
44
  ) => ({
45
45
  deleteUserMessageRagQuery: async (id) => {
46
- const message = chatSelectors.getMessageById(id)(get());
46
+ const message = dbMessageSelectors.getDbMessageById(id)(get());
47
47
 
48
48
  if (!message || !message.ragQueryId) return;
49
49
 
@@ -61,7 +61,7 @@ export const chatRag: StateCreator<ChatStore, [['zustand/devtools', never]], [],
61
61
  internal_retrieveChunks: async (id, userQuery, messages) => {
62
62
  get().internal_toggleMessageRAGLoading(true, id);
63
63
 
64
- const message = chatSelectors.getMessageById(id)(get());
64
+ const message = dbMessageSelectors.getDbMessageById(id)(get());
65
65
 
66
66
  // 1. get the rewrite query
67
67
  let rewriteQuery = message?.ragQuery as string | undefined;
@@ -73,7 +73,10 @@ export const chatRag: StateCreator<ChatStore, [['zustand/devtools', never]], [],
73
73
  }
74
74
 
75
75
  // 2. retrieve chunks from semantic search
76
- const files = chatSelectors.currentUserFiles(get()).map((f) => f.id);
76
+ const files = dbMessageSelectors
77
+ .dbUserFiles(get())
78
+ .map((f) => f?.id)
79
+ .filter(Boolean) as string[];
77
80
  try {
78
81
  const { chunks, queryId } = await ragService.semanticSearchForChat({
79
82
  fileIds: knowledgeIds().fileIds.concat(files),
@@ -145,13 +148,13 @@ export const chatRag: StateCreator<ChatStore, [['zustand/devtools', never]], [],
145
148
  },
146
149
 
147
150
  rewriteQuery: async (id) => {
148
- const message = chatSelectors.getMessageById(id)(get());
151
+ const message = dbMessageSelectors.getDbMessageById(id)(get());
149
152
  if (!message) return;
150
153
 
151
154
  // delete the current ragQuery
152
155
  await get().deleteUserMessageRagQuery(id);
153
156
 
154
- const chats = chatSelectors.mainAIChatsWithHistoryConfig(get());
157
+ const chats = displayMessageSelectors.mainAIChatsWithHistoryConfig(get());
155
158
 
156
159
  await get().internal_rewriteQuery(
157
160
  id,