@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
@@ -24,7 +24,7 @@ import { merge } from '@/utils/merge';
24
24
  import { safeParseJSON } from '@/utils/safeParseJSON';
25
25
  import { setNamespace } from '@/utils/storeDebug';
26
26
 
27
- import { chatSelectors } from '../message/selectors';
27
+ import { dbMessageSelectors, displayMessageSelectors } from '../message/selectors';
28
28
  import { threadSelectors } from '../thread/selectors';
29
29
 
30
30
  const n = setNamespace('plugin');
@@ -103,9 +103,9 @@ export const chatPlugin: StateCreator<
103
103
  },
104
104
 
105
105
  fillPluginMessageContent: async (id, content, triggerAiMessage) => {
106
- const { triggerAIMessage, internal_updateMessageContent } = get();
106
+ const { triggerAIMessage, optimisticUpdateMessageContent } = get();
107
107
 
108
- await internal_updateMessageContent(id, content);
108
+ await optimisticUpdateMessageContent(id, content);
109
109
 
110
110
  if (triggerAiMessage) await triggerAIMessage({ parentId: id });
111
111
  },
@@ -161,12 +161,12 @@ export const chatPlugin: StateCreator<
161
161
  },
162
162
 
163
163
  reInvokeToolMessage: async (id) => {
164
- const message = chatSelectors.getMessageById(id)(get());
164
+ const message = displayMessageSelectors.getDisplayMessageById(id)(get());
165
165
  if (!message || message.role !== 'tool' || !message.plugin) return;
166
166
 
167
167
  // if there is error content, then clear the error
168
168
  if (!!message.pluginError) {
169
- get().internal_updateMessagePluginError(id, null);
169
+ get().optimisticUpdateMessagePluginError(id, null);
170
170
  }
171
171
 
172
172
  const payload: ChatToolPayload = { ...message.plugin, id: message.tool_call_id! };
@@ -175,13 +175,16 @@ export const chatPlugin: StateCreator<
175
175
  },
176
176
 
177
177
  triggerAIMessage: async ({ parentId, traceId, threadId, inPortalThread, inSearchWorkflow }) => {
178
- const { internal_coreProcessMessage } = get();
178
+ const { internal_execAgentRuntime } = get();
179
179
 
180
180
  const chats = inPortalThread
181
181
  ? threadSelectors.portalAIChatsWithHistoryConfig(get())
182
- : chatSelectors.mainAIChatsWithHistoryConfig(get());
182
+ : displayMessageSelectors.mainAIChatsWithHistoryConfig(get());
183
183
 
184
- await internal_coreProcessMessage(chats, parentId ?? chats.at(-1)!.id, {
184
+ await internal_execAgentRuntime({
185
+ messages: chats,
186
+ parentMessageId: parentId ?? chats.at(-1)!.id,
187
+ parentMessageType: 'user',
185
188
  traceId,
186
189
  threadId,
187
190
  inPortalThread,
@@ -190,11 +193,11 @@ export const chatPlugin: StateCreator<
190
193
  },
191
194
 
192
195
  summaryPluginContent: async (id) => {
193
- const message = chatSelectors.getMessageById(id)(get());
196
+ const message = displayMessageSelectors.getDisplayMessageById(id)(get());
194
197
  if (!message || message.role !== 'tool') return;
195
198
 
196
- await get().internal_coreProcessMessage(
197
- [
199
+ await get().internal_execAgentRuntime({
200
+ messages: [
198
201
  {
199
202
  role: 'assistant',
200
203
  content: '作为一名总结专家,请结合以上系统提示词,将以下内容进行总结:',
@@ -207,12 +210,13 @@ export const chatPlugin: StateCreator<
207
210
  tool_call_id: undefined,
208
211
  },
209
212
  ] as UIChatMessage[],
210
- message.id,
211
- );
213
+ parentMessageId: message.id,
214
+ parentMessageType: 'assistant',
215
+ });
212
216
  },
213
217
 
214
218
  triggerToolCalls: async (assistantId, { threadId, inPortalThread, inSearchWorkflow } = {}) => {
215
- const message = chatSelectors.getMessageById(assistantId)(get());
219
+ const message = displayMessageSelectors.getDisplayMessageById(assistantId)(get());
216
220
  if (!message || !message.tools) return;
217
221
 
218
222
  let shouldCreateMessage = false;
@@ -230,7 +234,7 @@ export const chatPlugin: StateCreator<
230
234
  groupId: message.groupId, // Propagate groupId from parent message for group chat
231
235
  };
232
236
 
233
- const result = await get().internal_createMessage(toolMessage);
237
+ const result = await get().optimisticCreateMessage(toolMessage);
234
238
  if (!result) return;
235
239
 
236
240
  // trigger the plugin call
@@ -249,7 +253,7 @@ export const chatPlugin: StateCreator<
249
253
  // only default type tool calls should trigger AI message
250
254
  if (!shouldCreateMessage) return;
251
255
 
252
- const traceId = chatSelectors.getTraceIdByMessageId(latestToolId)(get());
256
+ const traceId = dbMessageSelectors.getTraceIdByDbMessageId(latestToolId)(get());
253
257
 
254
258
  await get().triggerAIMessage({ traceId, threadId, inPortalThread, inSearchWorkflow });
255
259
  },
@@ -271,10 +275,12 @@ export const chatPlugin: StateCreator<
271
275
 
272
276
  updatePluginArguments: async (id, value, replace = false) => {
273
277
  const { refreshMessages } = get();
274
- const toolMessage = chatSelectors.getMessageById(id)(get());
278
+ const toolMessage = displayMessageSelectors.getDisplayMessageById(id)(get());
275
279
  if (!toolMessage || !toolMessage?.tool_call_id) return;
276
280
 
277
- let assistantMessage = chatSelectors.getMessageById(toolMessage?.parentId || '')(get());
281
+ let assistantMessage = displayMessageSelectors.getDisplayMessageById(
282
+ toolMessage?.parentId || '',
283
+ )(get());
278
284
 
279
285
  const prevArguments = toolMessage?.plugin?.arguments;
280
286
  const prevJson = safeParseJSON(prevArguments || '');
@@ -296,7 +302,7 @@ export const chatPlugin: StateCreator<
296
302
  tool_call_id: toolMessage?.tool_call_id,
297
303
  value: { arguments: JSON.stringify(nextValue) },
298
304
  });
299
- assistantMessage = chatSelectors.getMessageById(assistantMessage?.id)(get());
305
+ assistantMessage = displayMessageSelectors.getDisplayMessageById(assistantMessage?.id)(get());
300
306
  }
301
307
 
302
308
  const updateAssistantMessage = async () => {
@@ -315,7 +321,7 @@ export const chatPlugin: StateCreator<
315
321
  },
316
322
 
317
323
  internal_addToolToAssistantMessage: async (id, tool) => {
318
- const assistantMessage = chatSelectors.getMessageById(id)(get());
324
+ const assistantMessage = displayMessageSelectors.getDisplayMessageById(id)(get());
319
325
  if (!assistantMessage) return;
320
326
 
321
327
  const { internal_dispatchMessage, internal_refreshToUpdateMessageTools } = get();
@@ -329,7 +335,7 @@ export const chatPlugin: StateCreator<
329
335
  },
330
336
 
331
337
  internal_removeToolToAssistantMessage: async (id, tool_call_id) => {
332
- const message = chatSelectors.getMessageById(id)(get());
338
+ const message = displayMessageSelectors.getDisplayMessageById(id)(get());
333
339
  if (!message || !tool_call_id) return;
334
340
 
335
341
  const { internal_dispatchMessage, internal_refreshToUpdateMessageTools } = get();
@@ -341,7 +347,7 @@ export const chatPlugin: StateCreator<
341
347
  await internal_refreshToUpdateMessageTools(id);
342
348
  },
343
349
  internal_refreshToUpdateMessageTools: async (id) => {
344
- const message = chatSelectors.getMessageById(id)(get());
350
+ const message = dbMessageSelectors.getDbMessageById(id)(get());
345
351
  if (!message || !message.tools) return;
346
352
 
347
353
  const { internal_toggleMessageLoading, replaceMessages } = get();
@@ -363,7 +369,7 @@ export const chatPlugin: StateCreator<
363
369
  },
364
370
 
365
371
  internal_callPluginApi: async (id, payload) => {
366
- const { internal_updateMessageContent, internal_togglePluginApiCalling } = get();
372
+ const { optimisticUpdateMessageContent, internal_togglePluginApiCalling } = get();
367
373
  let data: string;
368
374
 
369
375
  try {
@@ -373,7 +379,7 @@ export const chatPlugin: StateCreator<
373
379
  n('fetchPlugin/start') as string,
374
380
  );
375
381
 
376
- const message = chatSelectors.getMessageById(id)(get());
382
+ const message = displayMessageSelectors.getDisplayMessageById(id)(get());
377
383
 
378
384
  const res = await chatService.runPluginApi(payload, {
379
385
  signal: abortController?.signal,
@@ -404,7 +410,7 @@ export const chatPlugin: StateCreator<
404
410
  // 如果报错则结束了
405
411
  if (!data) return;
406
412
 
407
- await internal_updateMessageContent(id, data);
413
+ await optimisticUpdateMessageContent(id, data);
408
414
 
409
415
  return data;
410
416
  },
@@ -435,7 +441,7 @@ export const chatPlugin: StateCreator<
435
441
  },
436
442
  invokeMCPTypePlugin: async (id, payload) => {
437
443
  const {
438
- internal_updateMessageContent,
444
+ optimisticUpdateMessageContent,
439
445
  internal_togglePluginApiCalling,
440
446
  internal_constructToolsCallingContext,
441
447
  } = get();
@@ -472,7 +478,7 @@ export const chatPlugin: StateCreator<
472
478
  // 如果报错则结束了
473
479
  if (!data) return;
474
480
 
475
- await internal_updateMessageContent(id, data);
481
+ await optimisticUpdateMessageContent(id, data);
476
482
 
477
483
  return data;
478
484
  },
@@ -523,7 +529,7 @@ export const chatPlugin: StateCreator<
523
529
  },
524
530
 
525
531
  internal_constructToolsCallingContext: (id: string) => {
526
- const message = chatSelectors.getMessageById(id)(get());
532
+ const message = displayMessageSelectors.getDisplayMessageById(id)(get());
527
533
  if (!message) return;
528
534
 
529
535
  return {
@@ -620,9 +620,9 @@ describe('thread action', () => {
620
620
  const refreshMessagesSpy = vi.spyOn(result.current, 'refreshMessages').mockResolvedValue();
621
621
  const openThreadSpy = vi.spyOn(result.current, 'openThreadInPortal');
622
622
  const coreProcessSpy = vi
623
- .spyOn(result.current, 'internal_coreProcessMessage')
623
+ .spyOn(result.current, 'internal_execAgentRuntime')
624
624
  .mockResolvedValue();
625
- vi.spyOn(result.current, 'internal_createTmpMessage');
625
+ vi.spyOn(result.current, 'optimisticCreateTmpMessage');
626
626
  vi.spyOn(result.current, 'internal_toggleMessageLoading');
627
627
 
628
628
  await act(async () => {
@@ -659,7 +659,7 @@ describe('thread action', () => {
659
659
  });
660
660
 
661
661
  const createTmpSpy = vi
662
- .spyOn(result.current, 'internal_createTmpMessage')
662
+ .spyOn(result.current, 'optimisticCreateTmpMessage')
663
663
  .mockReturnValue('temp-msg-id');
664
664
 
665
665
  vi.spyOn(result.current, 'createThread').mockResolvedValue({
@@ -669,7 +669,7 @@ describe('thread action', () => {
669
669
  vi.spyOn(result.current, 'refreshThreads').mockResolvedValue();
670
670
  vi.spyOn(result.current, 'refreshMessages').mockResolvedValue();
671
671
  vi.spyOn(result.current, 'openThreadInPortal');
672
- vi.spyOn(result.current, 'internal_coreProcessMessage').mockResolvedValue();
672
+ vi.spyOn(result.current, 'internal_execAgentRuntime').mockResolvedValue();
673
673
  vi.spyOn(result.current, 'internal_toggleMessageLoading');
674
674
 
675
675
  await act(async () => {
@@ -738,8 +738,8 @@ describe('thread action', () => {
738
738
  useChatStore.setState({ portalThreadId: threadId });
739
739
  });
740
740
  });
741
- vi.spyOn(result.current, 'internal_coreProcessMessage').mockResolvedValue();
742
- vi.spyOn(result.current, 'internal_createTmpMessage').mockReturnValue('temp-msg-id');
741
+ vi.spyOn(result.current, 'internal_execAgentRuntime').mockResolvedValue();
742
+ vi.spyOn(result.current, 'optimisticCreateTmpMessage').mockReturnValue('temp-msg-id');
743
743
  vi.spyOn(result.current, 'internal_toggleMessageLoading');
744
744
 
745
745
  const summaryTitleSpy = vi.spyOn(result.current, 'summaryThreadTitle').mockResolvedValue();
@@ -763,12 +763,12 @@ describe('thread action', () => {
763
763
  });
764
764
 
765
765
  const createMessageSpy = vi
766
- .spyOn(result.current, 'internal_createMessage')
766
+ .spyOn(result.current, 'optimisticCreateMessage')
767
767
  .mockResolvedValue({ id: 'new-msg-id', messages: [] });
768
768
  const coreProcessSpy = vi
769
- .spyOn(result.current, 'internal_coreProcessMessage')
769
+ .spyOn(result.current, 'internal_execAgentRuntime')
770
770
  .mockResolvedValue();
771
- vi.spyOn(result.current, 'internal_createTmpMessage').mockReturnValue('temp-msg-id');
771
+ vi.spyOn(result.current, 'optimisticCreateTmpMessage').mockReturnValue('temp-msg-id');
772
772
  vi.spyOn(result.current, 'internal_toggleMessageLoading');
773
773
 
774
774
  await act(async () => {
@@ -785,9 +785,10 @@ describe('thread action', () => {
785
785
  );
786
786
 
787
787
  expect(coreProcessSpy).toHaveBeenCalledWith(
788
- expect.any(Array),
789
- 'new-msg-id',
790
788
  expect.objectContaining({
789
+ messages: expect.any(Array),
790
+ parentMessageId: 'new-msg-id',
791
+ parentMessageType: 'user',
791
792
  inPortalThread: true,
792
793
  threadId: 'existing-thread-id',
793
794
  }),
@@ -803,12 +804,12 @@ describe('thread action', () => {
803
804
  });
804
805
  });
805
806
 
806
- vi.spyOn(result.current, 'internal_createMessage').mockResolvedValue({
807
+ vi.spyOn(result.current, 'optimisticCreateMessage').mockResolvedValue({
807
808
  id: 'new-msg-id',
808
809
  messages: [],
809
810
  });
810
- vi.spyOn(result.current, 'internal_coreProcessMessage').mockResolvedValue();
811
- vi.spyOn(result.current, 'internal_createTmpMessage').mockReturnValue('temp-msg-id');
811
+ vi.spyOn(result.current, 'internal_execAgentRuntime').mockResolvedValue();
812
+ vi.spyOn(result.current, 'optimisticCreateTmpMessage').mockReturnValue('temp-msg-id');
812
813
  vi.spyOn(result.current, 'internal_toggleMessageLoading');
813
814
 
814
815
  const summaryTitleSpy = vi.spyOn(result.current, 'summaryThreadTitle').mockResolvedValue();
@@ -836,12 +837,12 @@ describe('thread action', () => {
836
837
  });
837
838
  });
838
839
 
839
- vi.spyOn(result.current, 'internal_createMessage').mockResolvedValue({
840
+ vi.spyOn(result.current, 'optimisticCreateMessage').mockResolvedValue({
840
841
  id: 'new-msg-id',
841
842
  messages: [],
842
843
  });
843
- vi.spyOn(result.current, 'internal_coreProcessMessage').mockResolvedValue();
844
- vi.spyOn(result.current, 'internal_createTmpMessage').mockReturnValue('temp-msg-id');
844
+ vi.spyOn(result.current, 'internal_execAgentRuntime').mockResolvedValue();
845
+ vi.spyOn(result.current, 'optimisticCreateTmpMessage').mockReturnValue('temp-msg-id');
845
846
  vi.spyOn(result.current, 'internal_toggleMessageLoading');
846
847
 
847
848
  await act(async () => {
@@ -861,15 +862,15 @@ describe('thread action', () => {
861
862
  });
862
863
 
863
864
  vi.spyOn(result.current, 'internal_shouldUseRAG').mockReturnValue(true);
864
- vi.spyOn(result.current, 'internal_createMessage').mockResolvedValue({
865
+ vi.spyOn(result.current, 'optimisticCreateMessage').mockResolvedValue({
865
866
  id: 'new-msg-id',
866
867
  messages: [],
867
868
  });
868
- vi.spyOn(result.current, 'internal_createTmpMessage').mockReturnValue('temp-msg-id');
869
+ vi.spyOn(result.current, 'optimisticCreateTmpMessage').mockReturnValue('temp-msg-id');
869
870
  vi.spyOn(result.current, 'internal_toggleMessageLoading');
870
871
 
871
872
  const coreProcessSpy = vi
872
- .spyOn(result.current, 'internal_coreProcessMessage')
873
+ .spyOn(result.current, 'internal_execAgentRuntime')
873
874
  .mockResolvedValue();
874
875
 
875
876
  await act(async () => {
@@ -877,9 +878,10 @@ describe('thread action', () => {
877
878
  });
878
879
 
879
880
  expect(coreProcessSpy).toHaveBeenCalledWith(
880
- expect.any(Array),
881
- 'new-msg-id',
882
881
  expect.objectContaining({
882
+ messages: expect.any(Array),
883
+ parentMessageId: 'new-msg-id',
884
+ parentMessageType: 'user',
883
885
  inPortalThread: true,
884
886
  ragQuery: 'test with rag',
885
887
  threadId: 'existing-thread-id',
@@ -899,20 +901,15 @@ describe('thread action', () => {
899
901
  });
900
902
  });
901
903
 
902
- const resendSpy = vi.spyOn(result.current, 'internal_resendMessage').mockResolvedValue();
904
+ const resendSpy = vi
905
+ .spyOn(result.current, 'regenerateUserMessage')
906
+ .mockResolvedValue(undefined);
903
907
 
904
908
  await act(async () => {
905
909
  await result.current.resendThreadMessage('message-id');
906
910
  });
907
911
 
908
- expect(resendSpy).toHaveBeenCalledWith(
909
- 'message-id',
910
- expect.objectContaining({
911
- inPortalThread: true,
912
- messages: expect.any(Array),
913
- threadId: 'thread-id',
914
- }),
915
- );
912
+ expect(resendSpy).toHaveBeenCalledWith('message-id', {});
916
913
  });
917
914
  });
918
915
 
@@ -100,7 +100,7 @@ export const chatThreadMessage: StateCreator<
100
100
  },
101
101
  sendThreadMessage: async ({ message }) => {
102
102
  const {
103
- internal_coreProcessMessage,
103
+ internal_execAgentRuntime,
104
104
  activeTopicId,
105
105
  activeId,
106
106
  threadStartMessageId,
@@ -132,7 +132,7 @@ export const chatThreadMessage: StateCreator<
132
132
  if (!portalThreadId) {
133
133
  if (!threadStartMessageId) return;
134
134
  // we need to create a temp message for optimistic update
135
- tempMessageId = get().internal_createTmpMessage({
135
+ tempMessageId = get().optimisticCreateTmpMessage({
136
136
  ...newMessage,
137
137
  threadId: THREAD_DRAFT_ID,
138
138
  });
@@ -155,10 +155,10 @@ export const chatThreadMessage: StateCreator<
155
155
  } else {
156
156
  // if there is a thread, just append message
157
157
  // we need to create a temp message for optimistic update
158
- tempMessageId = get().internal_createTmpMessage(newMessage);
158
+ tempMessageId = get().optimisticCreateTmpMessage(newMessage);
159
159
  get().internal_toggleMessageLoading(true, tempMessageId);
160
160
 
161
- const result = await get().internal_createMessage(newMessage, { tempMessageId });
161
+ const result = await get().optimisticCreateMessage(newMessage, { tempMessageId });
162
162
  if (!result) return;
163
163
  parentMessageId = result.id;
164
164
  }
@@ -172,7 +172,10 @@ export const chatThreadMessage: StateCreator<
172
172
  // Get the current messages to generate AI response
173
173
  const messages = threadSelectors.portalAIChats(get());
174
174
 
175
- await internal_coreProcessMessage(messages, parentMessageId, {
175
+ await internal_execAgentRuntime({
176
+ messages,
177
+ parentMessageId,
178
+ parentMessageType: 'user',
176
179
  ragQuery: get().internal_shouldUseRAG() ? message : undefined,
177
180
  threadId: get().portalThreadId,
178
181
  inPortalThread: true,
@@ -191,12 +194,12 @@ export const chatThreadMessage: StateCreator<
191
194
  }
192
195
  },
193
196
  resendThreadMessage: async (messageId) => {
194
- const chats = threadSelectors.portalAIChats(get());
197
+ // const chats = threadSelectors.portalAIChats(get());
195
198
 
196
- await get().internal_resendMessage(messageId, {
197
- messages: chats,
198
- threadId: get().portalThreadId,
199
- inPortalThread: true,
199
+ await get().regenerateUserMessage(messageId, {
200
+ // messages: chats,
201
+ // threadId: get().portalThreadId,
202
+ // inPortalThread: true,
200
203
  });
201
204
  },
202
205
  delAndResendThreadMessage: async (id) => {
@@ -6,7 +6,7 @@ import { agentChatConfigSelectors } from '@/store/agent/selectors';
6
6
  import type { ChatStoreState } from '@/store/chat';
7
7
  import { chatHelpers } from '@/store/chat/helpers';
8
8
 
9
- import { chatSelectors } from '../../message/selectors';
9
+ import { displayMessageSelectors } from '../../message/selectors';
10
10
  import { genMessage } from './util';
11
11
 
12
12
  const currentTopicThreads = (s: ChatStoreState) => {
@@ -52,7 +52,7 @@ const getTheadParentMessages = (s: ChatStoreState, data: UIChatMessage[]) => {
52
52
  * 获取当前 thread 的父级消息
53
53
  */
54
54
  const portalDisplayParentMessages = (s: ChatStoreState): UIChatMessage[] => {
55
- const data = chatSelectors.activeBaseChatsWithoutTool(s);
55
+ const data = displayMessageSelectors.activeDisplayMessages(s);
56
56
 
57
57
  return getTheadParentMessages(s, data);
58
58
  };
@@ -65,7 +65,7 @@ const portalDisplayChildChatsByThreadId =
65
65
  (id?: string) =>
66
66
  (s: ChatStoreState): UIChatMessage[] => {
67
67
  // skip tool message
68
- const data = chatSelectors.activeBaseChatsWithoutTool(s);
68
+ const data = displayMessageSelectors.activeDisplayMessages(s);
69
69
 
70
70
  return data.filter((m) => !!id && m.threadId === id);
71
71
  };
@@ -74,7 +74,9 @@ const portalDisplayChats = (s: ChatStoreState) => {
74
74
  const parentMessages = portalDisplayParentMessages(s);
75
75
  const afterMessages = portalDisplayChildChatsByThreadId(s.portalThreadId)(s);
76
76
  // use for optimistic update
77
- const draftMessage = chatSelectors.activeBaseChats(s).find((m) => m.threadId === THREAD_DRAFT_ID);
77
+ const draftMessage = displayMessageSelectors
78
+ .activeDisplayMessages(s)
79
+ .find((m) => m.threadId === THREAD_DRAFT_ID);
78
80
 
79
81
  return [...parentMessages, draftMessage, ...afterMessages].filter(Boolean) as UIChatMessage[];
80
82
  };
@@ -96,7 +98,7 @@ const portalDisplayChatIDs = (s: ChatStoreState): string[] =>
96
98
  // ========================================== //
97
99
 
98
100
  const portalAIParentMessages = (s: ChatStoreState): UIChatMessage[] => {
99
- const data = chatSelectors.activeBaseChats(s);
101
+ const data = displayMessageSelectors.activeDisplayMessages(s);
100
102
 
101
103
  return getTheadParentMessages(s, data);
102
104
  };
@@ -105,7 +107,7 @@ const portalAIChildChatsByThreadId =
105
107
  (id?: string) =>
106
108
  (s: ChatStoreState): UIChatMessage[] => {
107
109
  // skip tool message
108
- const data = chatSelectors.activeBaseChats(s);
110
+ const data = displayMessageSelectors.activeDisplayMessages(s);
109
111
 
110
112
  return data.filter((m) => !!id && m.threadId === id);
111
113
  };
@@ -1,3 +1,4 @@
1
+ import isEqual from 'fast-deep-equal';
1
2
  import { produce } from 'immer';
2
3
 
3
4
  import { ChatTopic, CreateTopicParams } from '@/types/topic';
@@ -52,9 +53,16 @@ export const topicReducer = (state: ChatTopic[] = [], payload: ChatTopicDispatch
52
53
  const topicIndex = draftState.findIndex((topic) => topic.id === id);
53
54
 
54
55
  if (topicIndex !== -1) {
55
- // TODO: updatedAt 类型后续需要修改为 Date
56
- // @ts-ignore
57
- draftState[topicIndex] = { ...draftState[topicIndex], ...value, updatedAt: new Date() };
56
+ const currentTopic = draftState[topicIndex];
57
+ const mergedTopic = { ...currentTopic, ...value };
58
+
59
+ // Only update if the merged value is different from current (excluding updatedAt)
60
+
61
+ if (!isEqual(currentTopic, mergedTopic)) {
62
+ // TODO: updatedAt 类型后续需要修改为 Date
63
+ // @ts-ignore
64
+ draftState[topicIndex] = { ...mergedTopic, updatedAt: new Date() };
65
+ }
58
66
  }
59
67
  });
60
68
  }
@@ -9,7 +9,7 @@ import { ChatStoreState, initialState } from './initialState';
9
9
  import { ChatBuiltinToolAction, chatToolSlice } from './slices/builtinTool/actions';
10
10
  import { ChatPortalAction, chatPortalSlice } from './slices/portal/action';
11
11
  import { ChatTranslateAction, chatTranslate } from './slices/translate/action';
12
- import { ChatMessageAction, chatMessage } from './slices/message/action';
12
+ import { ChatMessageAction, chatMessage } from './slices/message/actions';
13
13
  import { ChatPluginAction, chatPlugin } from './slices/plugin/action';
14
14
  import { ChatTopicAction, chatTopic } from './slices/topic/action';
15
15
  import { ChatAIChatAction, chatAiChat } from './slices/aiChat/actions';
@@ -3,9 +3,6 @@ import { DEFAULT_PREFERENCE } from '@lobechat/const';
3
3
  import type { UserState } from '@/store/user/initialState';
4
4
 
5
5
  export const labPreferSelectors = {
6
- enableAssistantMessageGroup: (s: UserState): boolean =>
7
- s.preference.lab?.enableAssistantMessageGroup ??
8
- DEFAULT_PREFERENCE.lab!.enableAssistantMessageGroup!,
9
6
  enableGroupChat: (s: UserState): boolean =>
10
7
  s.preference.lab?.enableGroupChat ?? DEFAULT_PREFERENCE.lab!.enableGroupChat!,
11
8
  enableInputMarkdown: (s: UserState): boolean =>