@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
@@ -1,629 +0,0 @@
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 {
4
- ChatErrorType,
5
- ChatImageItem,
6
- ChatMessageError,
7
- ChatMessagePluginError,
8
- CreateMessageParams,
9
- GroundingSearch,
10
- MessageMetadata,
11
- MessageToolCall,
12
- ModelReasoning,
13
- TraceEventPayloads,
14
- TraceEventType,
15
- UIChatMessage,
16
- UpdateMessageRAGParams,
17
- } from '@lobechat/types';
18
- import { nanoid } from '@lobechat/utils';
19
- import { copyToClipboard } from '@lobehub/ui';
20
- import isEqual from 'fast-deep-equal';
21
- import { SWRResponse, mutate } from 'swr';
22
- import { StateCreator } from 'zustand/vanilla';
23
-
24
- import { useClientDataSWR } from '@/libs/swr';
25
- import { messageService } from '@/services/message';
26
- import { topicService } from '@/services/topic';
27
- import { traceService } from '@/services/trace';
28
- import { ChatStore } from '@/store/chat/store';
29
- import { messageMapKey } from '@/store/chat/utils/messageMapKey';
30
- import { useSessionStore } from '@/store/session';
31
- import { sessionSelectors } from '@/store/session/selectors';
32
- import { Action, setNamespace } from '@/utils/storeDebug';
33
-
34
- import type { ChatStoreState } from '../../initialState';
35
- import { chatSelectors } from '../../selectors';
36
- import { preventLeavingFn, toggleBooleanList } from '../../utils';
37
- import { MessageDispatch, messagesReducer } from './reducer';
38
-
39
- const n = setNamespace('m');
40
-
41
- const SWR_USE_FETCH_MESSAGES = 'SWR_USE_FETCH_MESSAGES';
42
-
43
- export interface ChatMessageAction {
44
- // create
45
- addAIMessage: () => Promise<void>;
46
- addUserMessage: (params: { message: string; fileList?: string[] }) => Promise<void>;
47
- // delete
48
- /**
49
- * clear message on the active session
50
- */
51
- clearMessage: () => Promise<void>;
52
- deleteMessage: (id: string) => Promise<void>;
53
- deleteToolMessage: (id: string) => Promise<void>;
54
- clearAllMessages: () => Promise<void>;
55
- // update
56
- updateInputMessage: (message: string) => void;
57
- modifyMessageContent: (id: string, content: string) => Promise<void>;
58
- toggleMessageEditing: (id: string, editing: boolean) => void;
59
- // query
60
- useFetchMessages: (
61
- enable: boolean,
62
- messageContextId: string,
63
- activeTopicId?: string,
64
- type?: 'session' | 'group',
65
- ) => SWRResponse<UIChatMessage[]>;
66
- copyMessage: (id: string, content: string) => Promise<void>;
67
- refreshMessages: () => Promise<void>;
68
- replaceMessages: (messages: UIChatMessage[]) => void;
69
- // ========= ↓ Internal Method ↓ ========== //
70
- // ========================================== //
71
- // ========================================== //
72
- internal_updateMessageRAG: (id: string, input: UpdateMessageRAGParams) => Promise<void>;
73
-
74
- /**
75
- * update message at the frontend
76
- * this method will not update messages to database
77
- */
78
- internal_dispatchMessage: (
79
- payload: MessageDispatch,
80
- context?: { topicId?: string | null; sessionId: string },
81
- ) => void;
82
-
83
- /**
84
- * update the message content with optimistic update
85
- * a method used by other action
86
- */
87
- internal_updateMessageContent: (
88
- id: string,
89
- content: string,
90
- extra?: {
91
- toolCalls?: MessageToolCall[];
92
- reasoning?: ModelReasoning;
93
- search?: GroundingSearch;
94
- metadata?: MessageMetadata;
95
- imageList?: ChatImageItem[];
96
- model?: string;
97
- provider?: string;
98
- },
99
- ) => Promise<void>;
100
- /**
101
- * update the message error with optimistic update
102
- */
103
- internal_updateMessageError: (id: string, error: ChatMessageError | null) => Promise<void>;
104
- internal_updateMessagePluginError: (
105
- id: string,
106
- error: ChatMessagePluginError | null,
107
- ) => Promise<void>;
108
- /**
109
- * create a message with optimistic update
110
- * returns the created message ID and updated message list
111
- */
112
- internal_createMessage: (
113
- params: CreateMessageParams,
114
- context?: { tempMessageId?: string; skipRefresh?: boolean; groupMessageId?: string },
115
- ) => Promise<{ id: string; messages: UIChatMessage[] } | undefined>;
116
- /**
117
- * create a temp message for optimistic update
118
- * otherwise the message will be too slow to show
119
- */
120
- internal_createTmpMessage: (params: CreateMessageParams) => string;
121
- /**
122
- * delete the message content with optimistic update
123
- */
124
- internal_deleteMessage: (id: string) => Promise<void>;
125
-
126
- internal_fetchMessages: () => Promise<void>;
127
- internal_traceMessage: (id: string, payload: TraceEventPayloads) => Promise<void>;
128
-
129
- /**
130
- * method to toggle message create loading state
131
- * the AI message status is creating -> generating
132
- * other message role like user and tool , only this method need to be called
133
- */
134
- internal_toggleMessageLoading: (loading: boolean, id: string) => void;
135
-
136
- /**
137
- * helper to toggle the loading state of the array,used by these three toggleXXXLoading
138
- */
139
- internal_toggleLoadingArrays: (
140
- key: keyof ChatStoreState,
141
- loading: boolean,
142
- id?: string,
143
- action?: Action,
144
- ) => AbortController | undefined;
145
-
146
- /**
147
- * Update active session type
148
- */
149
- internal_updateActiveSessionType: (sessionType?: 'agent' | 'group') => void;
150
- /**
151
- * Update active session ID with cleanup of pending operations
152
- */
153
- internal_updateActiveId: (activeId: string) => void;
154
- }
155
-
156
- export const chatMessage: StateCreator<
157
- ChatStore,
158
- [['zustand/devtools', never]],
159
- [],
160
- ChatMessageAction
161
- > = (set, get) => ({
162
- deleteMessage: async (id) => {
163
- const message = chatSelectors.getMessageById(id)(get());
164
- if (!message) return;
165
-
166
- let ids = [message.id];
167
- const allMessages = chatSelectors.activeBaseChats(get());
168
-
169
- // if the message is a tool calls, then delete all the related tool messages
170
- if (message.tools) {
171
- const toolMessageIds = message.tools.flatMap((tool) => {
172
- const messages = allMessages.filter((m) => m.tool_call_id === tool.id);
173
- return messages.map((m) => m.id);
174
- });
175
- ids = ids.concat(toolMessageIds);
176
- }
177
-
178
- // if the message is a group message, find all children messages (via parentId)
179
- if (message.role === 'group') {
180
- const childMessages = allMessages.filter((m) => m.parentId === message.id);
181
- const childMessageIds = childMessages.map((m) => m.id);
182
- ids = ids.concat(childMessageIds);
183
-
184
- // Also delete tool results of children messages
185
- const childToolMessageIds = childMessages.flatMap((child) => {
186
- if (!child.tools) return [];
187
- return child.tools.flatMap((tool) => {
188
- const toolMessages = allMessages.filter((m) => m.tool_call_id === tool.id);
189
- return toolMessages.map((m) => m.id);
190
- });
191
- });
192
- ids = ids.concat(childToolMessageIds);
193
- }
194
-
195
- get().internal_dispatchMessage({ type: 'deleteMessages', ids });
196
- const result = await messageService.removeMessages(ids, {
197
- sessionId: get().activeId,
198
- topicId: get().activeTopicId,
199
- });
200
- if (result?.success && result.messages) {
201
- get().replaceMessages(result.messages);
202
- }
203
- },
204
-
205
- deleteToolMessage: async (id) => {
206
- const message = chatSelectors.getMessageById(id)(get());
207
- if (!message || message.role !== 'tool') return;
208
-
209
- const removeToolInAssistantMessage = async () => {
210
- if (!message.parentId) return;
211
- await get().internal_removeToolToAssistantMessage(message.parentId, message.tool_call_id);
212
- };
213
-
214
- await Promise.all([
215
- // 1. remove tool message
216
- get().internal_deleteMessage(id),
217
- // 2. remove the tool item in the assistant tools
218
- removeToolInAssistantMessage(),
219
- ]);
220
- },
221
-
222
- clearMessage: async () => {
223
- const { activeId, activeTopicId, refreshTopic, switchTopic, activeSessionType } = get();
224
-
225
- // Check if this is a group session - use activeSessionType if available, otherwise check session store
226
- let isGroupSession = activeSessionType === 'group';
227
- if (activeSessionType === undefined) {
228
- // Fallback: check session store directly
229
- const sessionStore = useSessionStore.getState();
230
- isGroupSession = sessionSelectors.isCurrentSessionGroupSession(sessionStore);
231
- }
232
-
233
- // For group sessions, we need to clear group messages using groupId
234
- // For regular sessions, we clear session messages using sessionId
235
- if (isGroupSession) {
236
- // For group chat, activeId is the groupId
237
- await messageService.removeMessagesByGroup(activeId, activeTopicId);
238
- } else {
239
- // For regular session, activeId is the sessionId
240
- await messageService.removeMessagesByAssistant(activeId, activeTopicId);
241
- }
242
-
243
- if (activeTopicId) {
244
- await topicService.removeTopic(activeTopicId);
245
- }
246
- await refreshTopic();
247
-
248
- // Clear messages directly since all messages are deleted
249
- get().replaceMessages([]);
250
-
251
- // after remove topic , go back to default topic
252
- switchTopic();
253
- },
254
- clearAllMessages: async () => {
255
- await messageService.removeAllMessages();
256
- // Clear messages directly since all messages are deleted
257
- get().replaceMessages([]);
258
- },
259
- addAIMessage: async () => {
260
- const { internal_createMessage, updateInputMessage, activeTopicId, activeId, inputMessage } =
261
- get();
262
- if (!activeId) return;
263
-
264
- const result = await internal_createMessage({
265
- content: inputMessage,
266
- role: 'assistant',
267
- sessionId: activeId,
268
- // if there is activeTopicId,then add topicId to message
269
- topicId: activeTopicId,
270
- });
271
-
272
- if (result) {
273
- updateInputMessage('');
274
- }
275
- },
276
- addUserMessage: async ({ message, fileList }) => {
277
- const { internal_createMessage, updateInputMessage, activeTopicId, activeId, activeThreadId } =
278
- get();
279
- if (!activeId) return;
280
-
281
- const result = await internal_createMessage({
282
- content: message,
283
- files: fileList,
284
- role: 'user',
285
- sessionId: activeId,
286
- // if there is activeTopicId,then add topicId to message
287
- topicId: activeTopicId,
288
- threadId: activeThreadId,
289
- });
290
-
291
- if (result) {
292
- updateInputMessage('');
293
- }
294
- },
295
- copyMessage: async (id, content) => {
296
- await copyToClipboard(content);
297
-
298
- get().internal_traceMessage(id, { eventType: TraceEventType.CopyMessage });
299
- },
300
- toggleMessageEditing: (id, editing) => {
301
- set(
302
- { messageEditingIds: toggleBooleanList(get().messageEditingIds, id, editing) },
303
- false,
304
- 'toggleMessageEditing',
305
- );
306
- },
307
-
308
- updateInputMessage: (message) => {
309
- if (isEqual(message, get().inputMessage)) return;
310
-
311
- set({ inputMessage: message }, false, n('updateInputMessage', message));
312
- },
313
- modifyMessageContent: async (id, content) => {
314
- // tracing the diff of update
315
- // due to message content will change, so we need send trace before update,or will get wrong data
316
- get().internal_traceMessage(id, {
317
- eventType: TraceEventType.ModifyMessage,
318
- nextContent: content,
319
- });
320
-
321
- await get().internal_updateMessageContent(id, content);
322
- },
323
-
324
- /**
325
- * @param enable - whether to enable the fetch
326
- * @param messageContextId - Can be sessionId or groupId
327
- */
328
- useFetchMessages: (enable, messageContextId, activeTopicId, type = 'session') =>
329
- useClientDataSWR<UIChatMessage[]>(
330
- enable ? [SWR_USE_FETCH_MESSAGES, messageContextId, activeTopicId, type] : null,
331
- async ([, sessionId, topicId, type]: [string, string, string | undefined, string]) =>
332
- type === 'session'
333
- ? messageService.getMessages(sessionId, topicId)
334
- : messageService.getGroupMessages(sessionId, topicId),
335
- {
336
- onSuccess: (messages, key) => {
337
- const nextMap = {
338
- ...get().messagesMap,
339
- [messageMapKey(messageContextId || '', activeTopicId)]: messages,
340
- };
341
-
342
- // no need to update map if the messages have been init and the map is the same
343
- if (get().messagesInit && isEqual(nextMap, get().messagesMap)) return;
344
-
345
- set(
346
- { messagesInit: true, messagesMap: nextMap },
347
- false,
348
- n('useFetchMessages', { messages, queryKey: key }),
349
- );
350
- },
351
- },
352
- ),
353
- // TODO: The mutate should only be called once, but since we haven't merge session and group,
354
- // we need to call it twice
355
- refreshMessages: async () => {
356
- await mutate([SWR_USE_FETCH_MESSAGES, get().activeId, get().activeTopicId, 'session']);
357
- await mutate([SWR_USE_FETCH_MESSAGES, get().activeId, get().activeTopicId, 'group']);
358
- },
359
- replaceMessages: (messages) => {
360
- set(
361
- {
362
- messagesMap: {
363
- ...get().messagesMap,
364
- [messageMapKey(get().activeId, get().activeTopicId)]: messages,
365
- },
366
- },
367
- false,
368
- 'replaceMessages',
369
- );
370
- },
371
-
372
- internal_updateMessageRAG: async (id, data) => {
373
- const result = await messageService.updateMessageRAG(id, data, {
374
- sessionId: get().activeId,
375
- topicId: get().activeTopicId,
376
- });
377
- if (result?.success && result.messages) {
378
- get().replaceMessages(result.messages);
379
- }
380
- },
381
-
382
- // the internal process method of the AI message
383
- internal_dispatchMessage: (payload, context) => {
384
- const activeId = typeof context !== 'undefined' ? context.sessionId : get().activeId;
385
- const topicId = typeof context !== 'undefined' ? context.topicId : get().activeTopicId;
386
-
387
- const messagesKey = messageMapKey(activeId, topicId);
388
-
389
- const messages = messagesReducer(chatSelectors.getBaseChatsByKey(messagesKey)(get()), payload);
390
-
391
- const nextMap = { ...get().messagesMap, [messagesKey]: messages };
392
-
393
- if (isEqual(nextMap, get().messagesMap)) return;
394
-
395
- set({ messagesMap: nextMap }, false, { type: `dispatchMessage/${payload.type}`, payload });
396
- },
397
-
398
- internal_updateMessageError: async (id, error) => {
399
- get().internal_dispatchMessage({ id, type: 'updateMessage', value: { error } });
400
- const result = await messageService.updateMessage(
401
- id,
402
- { error },
403
- { topicId: get().activeTopicId, sessionId: get().activeId },
404
- );
405
- if (result?.success && result.messages) {
406
- get().replaceMessages(result.messages);
407
- } else {
408
- await get().refreshMessages();
409
- }
410
- },
411
-
412
- internal_updateMessagePluginError: async (id, error) => {
413
- const result = await messageService.updateMessagePluginError(id, error, {
414
- sessionId: get().activeId,
415
- topicId: get().activeTopicId,
416
- });
417
- if (result?.success && result.messages) {
418
- get().replaceMessages(result.messages);
419
- }
420
- },
421
-
422
- internal_updateMessageContent: async (id, content, extra) => {
423
- const {
424
- internal_dispatchMessage,
425
- refreshMessages,
426
- internal_transformToolCalls,
427
- replaceMessages,
428
- } = get();
429
-
430
- // Due to the async update method and refresh need about 100ms
431
- // we need to update the message content at the frontend to avoid the update flick
432
- // refs: https://medium.com/@kyledeguzmanx/what-are-optimistic-updates-483662c3e171
433
- if (extra?.toolCalls) {
434
- internal_dispatchMessage({
435
- id,
436
- type: 'updateMessage',
437
- value: { tools: internal_transformToolCalls(extra?.toolCalls) },
438
- });
439
- } else {
440
- internal_dispatchMessage({
441
- id,
442
- type: 'updateMessage',
443
- value: { content },
444
- });
445
- }
446
-
447
- const result = await messageService.updateMessage(
448
- id,
449
- {
450
- content,
451
- tools: extra?.toolCalls ? internal_transformToolCalls(extra?.toolCalls) : undefined,
452
- reasoning: extra?.reasoning,
453
- search: extra?.search,
454
- metadata: extra?.metadata,
455
- model: extra?.model,
456
- provider: extra?.provider,
457
- imageList: extra?.imageList,
458
- },
459
- { topicId: get().activeTopicId, sessionId: get().activeId },
460
- );
461
-
462
- if (result && result.success && result.messages) {
463
- replaceMessages(result.messages);
464
- } else {
465
- await refreshMessages();
466
- }
467
- },
468
-
469
- internal_fetchMessages: async () => {
470
- const messages = await messageService.getMessages(get().activeId, get().activeTopicId);
471
- const nextMap = { ...get().messagesMap, [chatSelectors.currentChatKey(get())]: messages };
472
- // no need to update map if the messages have been init and the map is the same
473
- if (get().messagesInit && isEqual(nextMap, get().messagesMap)) return;
474
-
475
- set(
476
- { messagesInit: true, messagesMap: nextMap },
477
- false,
478
- n('internal_fetchMessages', { messages }),
479
- );
480
- },
481
- internal_createTmpMessage: (message) => {
482
- const { internal_dispatchMessage } = get();
483
-
484
- // use optimistic update to avoid the slow waiting
485
- const tempId = 'tmp_' + nanoid();
486
- internal_dispatchMessage({ type: 'createMessage', id: tempId, value: message });
487
-
488
- return tempId;
489
- },
490
- internal_createMessage: async (message, context) => {
491
- const {
492
- internal_createTmpMessage,
493
- internal_toggleMessageLoading,
494
- internal_dispatchMessage,
495
- replaceMessages,
496
- } = get();
497
-
498
- let tempId = context?.tempMessageId;
499
- if (!tempId) {
500
- tempId = 'tmp_' + nanoid();
501
-
502
- // Check if should add as group block (explicitly controlled by caller)
503
- if (context?.groupMessageId) {
504
- internal_dispatchMessage({
505
- type: 'addGroupBlock',
506
- groupMessageId: context.groupMessageId,
507
- blockId: tempId,
508
- value: {
509
- id: tempId,
510
- content: message.content,
511
- },
512
- });
513
- internal_toggleMessageLoading(true, tempId);
514
- } else {
515
- // Regular message creation at top level
516
- tempId = internal_createTmpMessage(message as any);
517
- internal_toggleMessageLoading(true, tempId);
518
- }
519
- }
520
-
521
- try {
522
- const result = await messageService.createMessage(message);
523
-
524
- if (!context?.skipRefresh) {
525
- // Use the messages returned from createMessage (already grouped)
526
- replaceMessages(result.messages);
527
- }
528
-
529
- internal_toggleMessageLoading(false, tempId);
530
- return result;
531
- } catch (e) {
532
- internal_toggleMessageLoading(false, tempId);
533
- internal_dispatchMessage({
534
- id: tempId,
535
- type: 'updateMessage',
536
- value: {
537
- error: {
538
- type: ChatErrorType.CreateMessageError,
539
- message: (e as Error).message,
540
- body: e,
541
- },
542
- },
543
- });
544
- }
545
- },
546
-
547
- internal_deleteMessage: async (id: string) => {
548
- get().internal_dispatchMessage({ type: 'deleteMessage', id });
549
- const result = await messageService.removeMessage(id, {
550
- sessionId: get().activeId,
551
- topicId: get().activeTopicId,
552
- });
553
- if (result?.success && result.messages) {
554
- get().replaceMessages(result.messages);
555
- }
556
- },
557
- internal_traceMessage: async (id, payload) => {
558
- // tracing the diff of update
559
- const message = chatSelectors.getMessageById(id)(get());
560
- if (!message) return;
561
-
562
- const traceId = message?.traceId;
563
- const observationId = message?.observationId;
564
-
565
- if (traceId && message?.role === 'assistant') {
566
- traceService
567
- .traceEvent({ traceId, observationId, content: message.content, ...payload })
568
- .catch();
569
- }
570
- },
571
-
572
- // ----- Loading ------- //
573
- internal_toggleMessageLoading: (loading, id) => {
574
- set(
575
- {
576
- messageLoadingIds: toggleBooleanList(get().messageLoadingIds, id, loading),
577
- },
578
- false,
579
- `internal_toggleMessageLoading/${loading ? 'start' : 'end'}`,
580
- );
581
- },
582
- internal_toggleLoadingArrays: (key, loading, id, action) => {
583
- const abortControllerKey = `${key}AbortController`;
584
- if (loading) {
585
- window.addEventListener('beforeunload', preventLeavingFn);
586
-
587
- const abortController = new AbortController();
588
- set(
589
- {
590
- [abortControllerKey]: abortController,
591
- [key]: toggleBooleanList(get()[key] as string[], id!, loading),
592
- },
593
- false,
594
- action,
595
- );
596
-
597
- return abortController;
598
- } else {
599
- if (!id) {
600
- set({ [abortControllerKey]: undefined, [key]: [] }, false, action);
601
- } else
602
- set(
603
- {
604
- [abortControllerKey]: undefined,
605
- [key]: toggleBooleanList(get()[key] as string[], id, loading),
606
- },
607
- false,
608
- action,
609
- );
610
-
611
- window.removeEventListener('beforeunload', preventLeavingFn);
612
- }
613
- },
614
- internal_updateActiveSessionType: (sessionType?: 'agent' | 'group') => {
615
- if (get().activeSessionType === sessionType) return;
616
-
617
- set({ activeSessionType: sessionType }, false, n('updateActiveSessionType'));
618
- },
619
-
620
- internal_updateActiveId: (activeId: string) => {
621
- const currentActiveId = get().activeId;
622
- if (currentActiveId === activeId) return;
623
-
624
- // Before switching sessions, cancel all pending supervisor decisions
625
- get().internal_cancelAllSupervisorDecisions();
626
-
627
- set({ activeId }, false, n(`updateActiveId/${activeId}`));
628
- },
629
- });