@lobehub/lobehub 2.0.0-next.23 → 2.0.0-next.25

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 (82) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/changelog/v1.json +18 -0
  3. package/locales/ar/labs.json +4 -0
  4. package/locales/bg-BG/labs.json +4 -0
  5. package/locales/de-DE/labs.json +4 -0
  6. package/locales/en-US/labs.json +4 -0
  7. package/locales/es-ES/labs.json +4 -0
  8. package/locales/fa-IR/labs.json +4 -0
  9. package/locales/fr-FR/labs.json +4 -0
  10. package/locales/it-IT/labs.json +4 -0
  11. package/locales/ja-JP/labs.json +4 -0
  12. package/locales/ko-KR/labs.json +4 -0
  13. package/locales/nl-NL/labs.json +4 -0
  14. package/locales/pl-PL/labs.json +4 -0
  15. package/locales/pt-BR/labs.json +4 -0
  16. package/locales/ru-RU/labs.json +4 -0
  17. package/locales/tr-TR/labs.json +4 -0
  18. package/locales/vi-VN/labs.json +4 -0
  19. package/locales/zh-CN/labs.json +4 -0
  20. package/locales/zh-TW/labs.json +4 -0
  21. package/package.json +1 -1
  22. package/packages/const/src/user.ts +5 -2
  23. package/packages/types/src/index.ts +0 -1
  24. package/packages/types/src/user/index.ts +2 -88
  25. package/packages/types/src/user/preference.ts +105 -0
  26. package/renovate.json +1 -6
  27. package/src/app/[variants]/(main)/labs/components/LabCard.tsx +5 -5
  28. package/src/app/[variants]/(main)/labs/page.tsx +19 -22
  29. package/src/app/[variants]/(main)/settings/provider/detail/azure/index.tsx +1 -1
  30. package/src/app/[variants]/(main)/settings/provider/detail/azureai/index.tsx +1 -1
  31. package/src/app/[variants]/(main)/settings/provider/detail/bedrock/index.tsx +1 -1
  32. package/src/app/[variants]/(main)/settings/provider/detail/cloudflare/index.tsx +1 -1
  33. package/src/app/[variants]/(main)/settings/provider/detail/comfyui/index.tsx +1 -1
  34. package/src/app/[variants]/(main)/settings/provider/detail/github/index.tsx +1 -1
  35. package/src/app/[variants]/(main)/settings/provider/detail/vertexai/index.tsx +1 -1
  36. package/src/app/[variants]/(main)/settings/provider/features/ProviderConfig/index.tsx +2 -4
  37. package/src/components/Skeleton/SkeletonSwitch.tsx +13 -0
  38. package/src/components/Skeleton/index.ts +2 -0
  39. package/src/features/ChatInput/ActionBar/index.tsx +2 -2
  40. package/src/features/ChatInput/InputEditor/index.tsx +2 -2
  41. package/src/features/Conversation/Messages/Group/Actions/WithContentId.tsx +152 -0
  42. package/src/features/Conversation/Messages/Group/Actions/WithoutContentId.tsx +70 -0
  43. package/src/features/Conversation/Messages/Group/Actions/index.tsx +21 -0
  44. package/src/features/Conversation/Messages/Group/ContentBlock.tsx +91 -0
  45. package/src/features/Conversation/Messages/Group/EditState.tsx +51 -0
  46. package/src/features/Conversation/Messages/Group/Error/index.tsx +53 -0
  47. package/src/features/Conversation/Messages/Group/GroupChildren.tsx +73 -0
  48. package/src/features/Conversation/Messages/Group/MessageContent.tsx +39 -0
  49. package/src/features/Conversation/Messages/Group/Tool/Inspector/BuiltinPluginTitle.tsx +49 -0
  50. package/src/features/Conversation/Messages/Group/Tool/Inspector/Debug.tsx +70 -0
  51. package/src/features/Conversation/Messages/Group/Tool/Inspector/PluginResult.tsx +34 -0
  52. package/src/features/Conversation/Messages/Group/Tool/Inspector/PluginState.tsx +18 -0
  53. package/src/features/Conversation/Messages/Group/Tool/Inspector/Settings.tsx +40 -0
  54. package/src/features/Conversation/Messages/Group/Tool/Inspector/ToolTitle.tsx +92 -0
  55. package/src/features/Conversation/Messages/Group/Tool/Inspector/index.tsx +176 -0
  56. package/src/features/Conversation/Messages/Group/Tool/Render/Arguments/ObjectEntity.tsx +81 -0
  57. package/src/features/Conversation/Messages/Group/Tool/Render/Arguments/ValueCell.tsx +43 -0
  58. package/src/features/Conversation/Messages/Group/Tool/Render/Arguments/index.tsx +134 -0
  59. package/src/features/Conversation/Messages/Group/Tool/Render/CustomRender.tsx +88 -0
  60. package/src/features/Conversation/Messages/Group/Tool/Render/ErrorResponse.tsx +35 -0
  61. package/src/features/Conversation/Messages/Group/Tool/Render/LoadingPlaceholder/index.tsx +29 -0
  62. package/src/features/Conversation/Messages/Group/Tool/Render/PluginSettings.tsx +66 -0
  63. package/src/features/Conversation/Messages/Group/Tool/Render/index.tsx +105 -0
  64. package/src/features/Conversation/Messages/Group/Tool/index.tsx +75 -0
  65. package/src/features/Conversation/Messages/Group/Tools.tsx +46 -0
  66. package/src/features/Conversation/Messages/Group/index.tsx +140 -0
  67. package/src/features/Conversation/Messages/index.tsx +12 -0
  68. package/src/features/Conversation/components/ShareMessageModal/ShareImage/Preview.tsx +2 -2
  69. package/src/locales/default/labs.ts +4 -0
  70. package/src/server/routers/lambda/message.ts +5 -20
  71. package/src/services/chat/contextEngineering.ts +6 -5
  72. package/src/services/message/server.ts +10 -10
  73. package/src/services/message/type.ts +0 -2
  74. package/src/store/chat/slices/aiChat/actions/__tests__/generateAIChatV2.test.ts +309 -2
  75. package/src/store/chat/slices/aiChat/actions/generateAIChat.ts +2 -22
  76. package/src/store/chat/slices/aiChat/actions/generateAIChatV2.ts +272 -14
  77. package/src/store/user/selectors.ts +1 -1
  78. package/src/store/user/slices/preference/action.ts +8 -1
  79. package/src/store/user/slices/preference/selectors/index.ts +2 -0
  80. package/src/store/user/slices/preference/selectors/labPrefer.ts +13 -0
  81. package/src/store/user/slices/preference/{selectors.ts → selectors/preference.ts} +0 -2
  82. /package/src/{app/[variants]/(main)/settings/provider/features/ProviderConfig → components/Skeleton}/SkeletonInput.tsx +0 -0
@@ -1,12 +1,11 @@
1
1
  /* eslint-disable sort-keys-fix/sort-keys-fix, typescript-sort-keys/interface */
2
2
  // Disable the auto sort key eslint rule to make the code more logic and readable
3
- import { LOADING_FLAT, MESSAGE_CANCEL_FLAT, isDesktop, isServerMode } from '@lobechat/const';
3
+ import { LOADING_FLAT, MESSAGE_CANCEL_FLAT, isDesktop } from '@lobechat/const';
4
4
  import { knowledgeBaseQAPrompts } from '@lobechat/prompts';
5
5
  import {
6
6
  ChatImageItem,
7
7
  CreateMessageParams,
8
8
  MessageSemanticSearchChunk,
9
- SendMessageParams,
10
9
  TraceEventType,
11
10
  TraceNameMap,
12
11
  UIChatMessage,
@@ -49,10 +48,6 @@ interface ProcessMessageParams {
49
48
  }
50
49
 
51
50
  export interface AIGenerateAction {
52
- /**
53
- * Sends a new message to the AI chat system
54
- */
55
- sendMessage: (params: SendMessageParams) => Promise<void>;
56
51
  /**
57
52
  * Regenerates a specific message in the chat
58
53
  */
@@ -156,21 +151,6 @@ export const generateAIChat: StateCreator<
156
151
  get().internal_traceMessage(id, { eventType: TraceEventType.RegenerateMessage });
157
152
  },
158
153
 
159
- sendMessage: async ({ message, files, onlyAddUserMessage, isWelcomeQuestion }) => {
160
- const { activeId, sendMessageInServer } = get();
161
- if (!activeId) return;
162
-
163
- const fileIdList = files?.map((f) => f.id);
164
-
165
- const hasFile = !!fileIdList && fileIdList.length > 0;
166
-
167
- // if message is empty or no files, then stop
168
- if (!message && !hasFile) return;
169
-
170
- // router to server mode send message
171
- if (isServerMode)
172
- return sendMessageInServer({ message, files, onlyAddUserMessage, isWelcomeQuestion });
173
- },
174
154
  stopGenerateMessage: () => {
175
155
  const { chatLoadingIdsAbortController, internal_toggleChatLoading } = get();
176
156
 
@@ -487,7 +467,7 @@ export const generateAIChat: StateCreator<
487
467
  // if there is traceId, update it
488
468
  if (traceId) {
489
469
  msgTraceId = traceId;
490
- await messageService.updateMessage(messageId, {
470
+ messageService.updateMessage(messageId, {
491
471
  traceId,
492
472
  observationId: observationId ?? undefined,
493
473
  });
@@ -1,11 +1,17 @@
1
1
  /* eslint-disable sort-keys-fix/sort-keys-fix, typescript-sort-keys/interface */
2
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, isDesktop } from '@lobechat/const';
3
+ import {
4
+ DEFAULT_AGENT_CHAT_CONFIG,
5
+ INBOX_SESSION_ID,
6
+ LOADING_FLAT,
7
+ isDesktop,
8
+ } from '@lobechat/const';
4
9
  import { knowledgeBaseQAPrompts } from '@lobechat/prompts';
5
10
  import {
6
11
  ChatImageItem,
7
12
  ChatTopic,
8
13
  ChatVideoItem,
14
+ CreateNewMessageParams,
9
15
  MessageSemanticSearchChunk,
10
16
  SendMessageParams,
11
17
  SendMessageServerResponse,
@@ -13,8 +19,10 @@ import {
13
19
  UIChatMessage,
14
20
  } from '@lobechat/types';
15
21
  import { TRPCClientError } from '@trpc/client';
22
+ import debug from 'debug';
16
23
  import { t } from 'i18next';
17
24
  import { produce } from 'immer';
25
+ import pMap from 'p-map';
18
26
  import { StateCreator } from 'zustand/vanilla';
19
27
 
20
28
  import { aiChatService } from '@/services/aiChat';
@@ -30,21 +38,36 @@ import { getSessionStoreState } from '@/store/session';
30
38
  import { WebBrowsingManifest } from '@/tools/web-browsing';
31
39
  import { setNamespace } from '@/utils/storeDebug';
32
40
 
33
- import { chatSelectors, topicSelectors } from '../../../selectors';
41
+ import { chatSelectors, threadSelectors, topicSelectors } from '../../../selectors';
34
42
  import { messageMapKey } from '../../../utils/messageMapKey';
35
43
 
36
44
  const n = setNamespace('ai');
45
+ const log = debug('lobe-store:ai-chat-v2');
37
46
 
38
47
  export interface AIGenerateV2Action {
39
48
  /**
40
49
  * Sends a new message to the AI chat system
41
50
  */
42
- sendMessageInServer: (params: SendMessageParams) => Promise<void>;
51
+ sendMessage: (params: SendMessageParams) => Promise<void>;
43
52
  /**
44
- * Cancels sendMessageInServer operation for a specific topic/session
53
+ * Cancels sendMessage operation for a specific topic/session
45
54
  */
46
55
  cancelSendMessageInServer: (topicId?: string) => void;
47
56
  clearSendMessageError: () => void;
57
+ /**
58
+ */
59
+ triggerToolsCalling: (
60
+ id: string,
61
+ params?: { threadId?: string; inPortalThread?: boolean; inSearchWorkflow?: boolean },
62
+ ) => Promise<void>;
63
+ callToolFollowAssistantMessage: (params: {
64
+ parentId: string;
65
+ traceId?: string;
66
+ threadId?: string;
67
+ inPortalThread?: boolean;
68
+ inSearchWorkflow?: boolean;
69
+ }) => Promise<void>;
70
+
48
71
  internal_refreshAiChat: (params: {
49
72
  topics?: ChatTopic[];
50
73
  messages: UIChatMessage[];
@@ -57,7 +80,7 @@ export interface AIGenerateV2Action {
57
80
  */
58
81
  internal_execAgentRuntime: (params: {
59
82
  messages: UIChatMessage[];
60
- userMessageId: string;
83
+ userMessageId?: string;
61
84
  assistantMessageId: string;
62
85
  isWelcomeQuestion?: boolean;
63
86
  inSearchWorkflow?: boolean;
@@ -70,7 +93,7 @@ export interface AIGenerateV2Action {
70
93
  traceId?: string;
71
94
  }) => Promise<void>;
72
95
  /**
73
- * Toggle sendMessageInServer operation state
96
+ * Toggle sendMessage operation state
74
97
  */
75
98
  internal_toggleSendMessageOperation: (
76
99
  key: string | { sessionId: string; topicId?: string | null },
@@ -90,7 +113,7 @@ export const generateAIChatV2: StateCreator<
90
113
  [],
91
114
  AIGenerateV2Action
92
115
  > = (set, get) => ({
93
- sendMessageInServer: async ({ message, files, onlyAddUserMessage, isWelcomeQuestion }) => {
116
+ sendMessage: async ({ message, files, onlyAddUserMessage, isWelcomeQuestion }) => {
94
117
  const { activeTopicId, activeId, activeThreadId, internal_execAgentRuntime, mainInputEditor } =
95
118
  get();
96
119
  if (!activeId) return;
@@ -151,7 +174,7 @@ export const generateAIChatV2: StateCreator<
151
174
 
152
175
  const operationKey = messageMapKey(activeId, activeTopicId);
153
176
 
154
- // Start tracking sendMessageInServer operation with AbortController
177
+ // Start tracking sendMessage operation with AbortController
155
178
  const abortController = get().internal_toggleSendMessageOperation(operationKey, true)!;
156
179
 
157
180
  const jsonState = mainInputEditor?.getJSONState();
@@ -205,7 +228,7 @@ export const generateAIChatV2: StateCreator<
205
228
  }
206
229
  }
207
230
  } finally {
208
- // Stop tracking sendMessageInServer operation
231
+ // Stop tracking sendMessage operation
209
232
  get().internal_toggleSendMessageOperation(operationKey, false);
210
233
  }
211
234
 
@@ -290,7 +313,7 @@ export const generateAIChatV2: StateCreator<
290
313
  get().internal_toggleSendMessageOperation(
291
314
  operationKey,
292
315
  false,
293
- 'User cancelled sendMessageInServer operation',
316
+ 'User cancelled sendMessage operation',
294
317
  );
295
318
 
296
319
  // Only clear creating message state if it's the active session
@@ -325,9 +348,16 @@ export const generateAIChatV2: StateCreator<
325
348
  ragQuery,
326
349
  messages: originalMessages,
327
350
  } = params;
351
+
352
+ log(
353
+ '[internal_execAgentRuntime] start, assistantId: %s, messages count: %d',
354
+ assistantId,
355
+ originalMessages.length,
356
+ );
357
+
328
358
  const {
329
359
  internal_fetchAIChatMessage,
330
- triggerToolCalls,
360
+ triggerToolsCalling,
331
361
  refreshMessages,
332
362
  internal_updateMessageRAG,
333
363
  } = get();
@@ -338,11 +368,14 @@ export const generateAIChatV2: StateCreator<
338
368
  const agentStoreState = getAgentStoreState();
339
369
  const { model, provider, chatConfig } = agentSelectors.currentAgentConfig(agentStoreState);
340
370
 
371
+ log('[internal_execAgentRuntime] Agent config: model=%s, provider=%s', model, provider);
372
+
341
373
  let fileChunks: MessageSemanticSearchChunk[] | undefined;
342
374
  let ragQueryId;
343
375
 
344
376
  // go into RAG flow if there is ragQuery flag
345
- if (ragQuery) {
377
+ if (ragQuery && userMessageId) {
378
+ log('[internal_execAgentRuntime] Entering RAG flow with query: %s', ragQuery);
346
379
  // 1. get the relative chunks from semantic search
347
380
  const { chunks, queryId, rewriteQuery } = await get().internal_retrieveChunks(
348
381
  userMessageId,
@@ -470,7 +503,7 @@ export const generateAIChatV2: StateCreator<
470
503
  if (isToolsCalling) {
471
504
  get().internal_toggleMessageInToolsCalling(true, assistantId);
472
505
  await refreshMessages();
473
- await triggerToolCalls(assistantId, {
506
+ await triggerToolsCalling(assistantId, {
474
507
  threadId: params?.threadId,
475
508
  inPortalThread: params?.inPortalThread,
476
509
  });
@@ -481,6 +514,7 @@ export const generateAIChatV2: StateCreator<
481
514
  }
482
515
 
483
516
  // 4. fetch the AI response
517
+ log('[internal_execAgentRuntime] Fetching AI response for assistantId: %s', assistantId);
484
518
  const { isFunctionCall, content } = await internal_fetchAIChatMessage({
485
519
  messages,
486
520
  messageId: assistantId,
@@ -491,13 +525,18 @@ export const generateAIChatV2: StateCreator<
491
525
 
492
526
  // 5. if it's the function call message, trigger the function method
493
527
  if (isFunctionCall) {
528
+ log('[internal_execAgentRuntime] AI response is function call, triggering tools calling');
494
529
  get().internal_toggleMessageInToolsCalling(true, assistantId);
495
530
  await refreshMessages();
496
- await triggerToolCalls(assistantId, {
531
+ await triggerToolsCalling(assistantId, {
497
532
  threadId: params?.threadId,
498
533
  inPortalThread: params?.inPortalThread,
499
534
  });
500
535
  } else {
536
+ log(
537
+ '[internal_execAgentRuntime] AI response completed, content length: %d',
538
+ content?.length || 0,
539
+ );
501
540
  // 显示桌面通知(仅在桌面端且窗口隐藏时)
502
541
  if (isDesktop) {
503
542
  try {
@@ -534,6 +573,225 @@ export const generateAIChatV2: StateCreator<
534
573
  await get().internal_summaryHistory(historyMessages);
535
574
  }
536
575
  },
576
+ triggerToolsCalling: async (assistantId, { threadId, inPortalThread, inSearchWorkflow } = {}) => {
577
+ log('[triggerToolsCalling] start, assistantId (block ID): %s', assistantId);
578
+
579
+ const foundMessage = chatSelectors.getMessageById(assistantId)(get());
580
+ if (!foundMessage) {
581
+ log('[triggerToolsCalling] Message not found, returning');
582
+ return;
583
+ }
584
+
585
+ // Determine if this is a group message or a block
586
+ let groupMessage: UIChatMessage;
587
+ let latestBlock: UIChatMessage;
588
+
589
+ if (foundMessage.role === 'group') {
590
+ // Case 1: assistantId matches a group message ID directly
591
+ // Find the block within children that matches assistantId
592
+ groupMessage = foundMessage;
593
+ const block = foundMessage.children?.find((item) => item.id === assistantId);
594
+
595
+ if (!block) {
596
+ log(
597
+ '[triggerToolsCalling] Block with id %s not found in group message children, returning',
598
+ assistantId,
599
+ );
600
+ return;
601
+ }
602
+ latestBlock = block as UIChatMessage;
603
+ } else if (foundMessage.parentId) {
604
+ // Case 2: assistantId is a block ID, need to get parent group message
605
+ const parentMsg = chatSelectors.getMessageById(foundMessage.parentId)(get());
606
+ if (!parentMsg || parentMsg.role !== 'group') {
607
+ log('[triggerToolsCalling] Parent group message not found, returning');
608
+ return;
609
+ }
610
+ groupMessage = parentMsg;
611
+ latestBlock = foundMessage;
612
+ } else {
613
+ log(
614
+ '[triggerToolsCalling] Message is neither a group message nor a block with parentId, returning',
615
+ );
616
+ return;
617
+ }
618
+
619
+ log('[triggerToolsCalling] Found group message: %O', {
620
+ id: groupMessage.id,
621
+ groupId: groupMessage.groupId,
622
+ childrenCount: groupMessage.children?.length,
623
+ latestBlockId: latestBlock.id,
624
+ });
625
+
626
+ if (!latestBlock.tools) {
627
+ log('[triggerToolsCalling] Latest block has no tools, returning');
628
+ return;
629
+ }
630
+
631
+ log(
632
+ '[triggerToolsCalling] Latest block found with %d tools: %O',
633
+ latestBlock.tools.length,
634
+ latestBlock.tools.map((t) => ({ id: t.id, type: t.type, identifier: t.identifier })),
635
+ );
636
+
637
+ let shouldCreateMessage = false;
638
+ let latestToolId = '';
639
+
640
+ await pMap(
641
+ latestBlock.tools,
642
+ async (payload) => {
643
+ log(
644
+ '[triggerToolsCalling] Processing tool: %s (type: %s)',
645
+ payload.identifier,
646
+ payload.type,
647
+ );
648
+
649
+ // 2. 使用 createNewMessage 创建 tool 消息
650
+ const toolMessage: CreateNewMessageParams = {
651
+ content: '',
652
+ parentId: assistantId,
653
+ plugin: payload,
654
+ role: 'tool',
655
+ sessionId: get().activeId,
656
+ tool_call_id: payload.id,
657
+ threadId,
658
+ topicId: get().activeTopicId, // if there is activeTopicId,then add it to topicId
659
+ groupId: groupMessage.groupId, // Propagate groupId from parent message for group chat
660
+ };
661
+
662
+ const result = await get().internal_createNewMessage(toolMessage);
663
+
664
+ if (!result) {
665
+ log('[triggerToolsCalling] Failed to create tool message for %s', payload.identifier);
666
+ return;
667
+ }
668
+
669
+ log('[triggerToolsCalling] Tool message created: %s', result.id);
670
+
671
+ // 3. 执行 tool(这时 tool 消息已经创建,且 UI 已更新)
672
+ const data = await get().internal_invokeDifferentTypePlugin(result.id, payload);
673
+
674
+ if (data && !['markdown', 'standalone'].includes(payload.type)) {
675
+ shouldCreateMessage = true;
676
+ latestToolId = result.id;
677
+ log(
678
+ '[triggerToolsCalling] Tool %s requires follow-up assistant message',
679
+ payload.identifier,
680
+ );
681
+ } else {
682
+ log('[triggerToolsCalling] Tool %s completed without follow-up', payload.identifier);
683
+ }
684
+ },
685
+ { concurrency: 5 },
686
+ );
687
+
688
+ await get().internal_toggleMessageInToolsCalling(false, assistantId);
689
+
690
+ if (!shouldCreateMessage) {
691
+ log('[triggerToolsCalling] No follow-up message needed, completed');
692
+ return;
693
+ }
694
+
695
+ const traceId = chatSelectors.getTraceIdByMessageId(latestToolId)(get());
696
+ log(
697
+ '[triggerToolsCalling] Calling follow-up assistant message with latestToolId: %s',
698
+ latestToolId,
699
+ );
700
+
701
+ await get().callToolFollowAssistantMessage({
702
+ traceId,
703
+ threadId,
704
+ inPortalThread,
705
+ inSearchWorkflow,
706
+ parentId: latestToolId,
707
+ });
708
+ log('[triggerToolsCalling] completed');
709
+ },
710
+
711
+ callToolFollowAssistantMessage: async ({
712
+ parentId,
713
+ traceId,
714
+ threadId,
715
+ inPortalThread,
716
+ inSearchWorkflow,
717
+ }) => {
718
+ log('[callToolFollowAssistantMessage] start, parentId: %s', parentId);
719
+
720
+ const chats = inPortalThread
721
+ ? threadSelectors.portalAIChatsWithHistoryConfig(get())
722
+ : chatSelectors.mainAIChatsWithHistoryConfig(get());
723
+
724
+ let assistantMessageId: string;
725
+
726
+ // 获取 agent 配置
727
+ const agentStoreState = getAgentStoreState();
728
+ const { model, provider } = agentSelectors.currentAgentConfig(agentStoreState);
729
+
730
+ // 查找包含 parentId 的 group message
731
+ // parentId 是 tool result message 的 id,它存储在 assistant block 的 tools[].result_msg_id 中
732
+ let groupMessageId: string | undefined;
733
+
734
+ // 遍历所有 group messages,找到包含该 tool result 的那个
735
+ for (const msg of chats) {
736
+ if (msg.role === 'group' && msg.children) {
737
+ for (const child of msg.children) {
738
+ // 检查 child 的 tools 中是否有 result_msg_id === parentId
739
+ if (child.tools?.some((tool) => tool.result_msg_id === parentId)) {
740
+ groupMessageId = msg.id;
741
+ log('[callToolFollowAssistantMessage] Found group message: %s', groupMessageId);
742
+ break;
743
+ }
744
+ }
745
+ if (groupMessageId) break;
746
+ }
747
+ }
748
+
749
+ // 创建新的 assistant message,作为 group message 的新 block
750
+ const assistantMessage: CreateNewMessageParams = {
751
+ role: 'assistant',
752
+ content: LOADING_FLAT,
753
+ parentId,
754
+ sessionId: get().activeId,
755
+ topicId: get().activeTopicId,
756
+ threadId,
757
+ traceId,
758
+ model,
759
+ provider,
760
+ };
761
+
762
+ log('[callToolFollowAssistantMessage] Creating new assistant message block with params: %O', {
763
+ parentId,
764
+ groupMessageId,
765
+ model,
766
+ provider,
767
+ sessionId: get().activeId,
768
+ topicId: get().activeTopicId,
769
+ });
770
+
771
+ const result = await get().internal_createNewMessage(assistantMessage, { groupMessageId });
772
+
773
+ if (!result) {
774
+ log('[callToolFollowAssistantMessage] Failed to create assistant message');
775
+ return;
776
+ }
777
+
778
+ assistantMessageId = result.id;
779
+ log(
780
+ '[callToolFollowAssistantMessage] Assistant message created successfully, id: %s',
781
+ assistantMessageId,
782
+ );
783
+
784
+ log('[callToolFollowAssistantMessage] Starting agent runtime with %d messages', chats.length);
785
+ await get().internal_execAgentRuntime({
786
+ messages: chats,
787
+ assistantMessageId,
788
+ traceId,
789
+ threadId,
790
+ inPortalThread,
791
+ inSearchWorkflow,
792
+ });
793
+ log('[callToolFollowAssistantMessage] completed');
794
+ },
537
795
 
538
796
  internal_updateSendMessageOperation: (key, value, actionName) => {
539
797
  const operationKey = typeof key === 'string' ? key : messageMapKey(key.sessionId, key.topicId);
@@ -1,5 +1,5 @@
1
1
  export { authSelectors, userProfileSelectors } from './slices/auth/selectors';
2
- export { preferenceSelectors } from './slices/preference/selectors';
2
+ export { labPreferSelectors, preferenceSelectors } from './slices/preference/selectors';
3
3
  export {
4
4
  keyVaultsConfigSelectors,
5
5
  settingsSelectors,
@@ -2,7 +2,7 @@ import type { StateCreator } from 'zustand/vanilla';
2
2
 
3
3
  import { userService } from '@/services/user';
4
4
  import type { UserStore } from '@/store/user';
5
- import { UserGuide, UserPreference } from '@/types/user';
5
+ import { UserGuide, UserLab, UserPreference } from '@/types/user';
6
6
  import { merge } from '@/utils/merge';
7
7
  import { setNamespace } from '@/utils/storeDebug';
8
8
 
@@ -10,6 +10,7 @@ const n = setNamespace('preference');
10
10
 
11
11
  export interface PreferenceAction {
12
12
  updateGuideState: (guide: Partial<UserGuide>) => Promise<void>;
13
+ updateLab: (lab: Partial<UserLab>) => Promise<void>;
13
14
  updatePreference: (preference: Partial<UserPreference>, action?: any) => Promise<void>;
14
15
  }
15
16
 
@@ -25,6 +26,12 @@ export const createPreferenceSlice: StateCreator<
25
26
  await updatePreference({ guide: nextGuide });
26
27
  },
27
28
 
29
+ updateLab: async (lab) => {
30
+ const { updatePreference } = get();
31
+ const nextLab = merge(get().preference.lab, lab);
32
+ await updatePreference({ lab: nextLab }, n('updateLab'));
33
+ },
34
+
28
35
  updatePreference: async (preference, action) => {
29
36
  const nextPreference = merge(get().preference, preference);
30
37
 
@@ -0,0 +1,2 @@
1
+ export * from './labPrefer';
2
+ export * from './preference';
@@ -0,0 +1,13 @@
1
+ import { DEFAULT_PREFERENCE } from '@lobechat/const';
2
+
3
+ import type { UserState } from '@/store/user/initialState';
4
+
5
+ export const labPreferSelectors = {
6
+ enableAssistantMessageGroup: (s: UserState): boolean =>
7
+ s.preference.lab?.enableAssistantMessageGroup ??
8
+ DEFAULT_PREFERENCE.lab!.enableAssistantMessageGroup!,
9
+ enableGroupChat: (s: UserState): boolean =>
10
+ s.preference.lab?.enableGroupChat ?? DEFAULT_PREFERENCE.lab!.enableGroupChat!,
11
+ enableInputMarkdown: (s: UserState): boolean =>
12
+ s.preference.lab?.enableInputMarkdown ?? DEFAULT_PREFERENCE.lab!.enableInputMarkdown!,
13
+ };
@@ -21,10 +21,8 @@ const shouldTriggerFileInKnowledgeBaseTip = (s: UserStore) =>
21
21
  const isPreferenceInit = (s: UserStore) => s.isUserStateInit;
22
22
 
23
23
  export const preferenceSelectors = {
24
- enableGroupChat: (s: UserStore) => s.preference.enableGroupChat || false,
25
24
  hideSettingsMoveGuide,
26
25
  hideSyncAlert,
27
- inputMarkdownRender: (s: UserStore) => !s.preference.disableInputMarkdownRender,
28
26
  isPreferenceInit,
29
27
  shouldTriggerFileInKnowledgeBaseTip,
30
28
  showUploadFileInKnowledgeBaseTip,