@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
@@ -165,18 +165,74 @@ export class FlatListBuilder {
165
165
  childMessages,
166
166
  this.childrenMap,
167
167
  );
168
- const userWithBranches = this.createUserMessageWithBranches(message);
168
+ const activeBranchIndex = childMessages.indexOf(activeBranchId);
169
+ const userWithBranches = this.createUserMessageWithBranches(
170
+ message,
171
+ childMessages.length,
172
+ activeBranchIndex,
173
+ );
169
174
  flatList.push(userWithBranches);
170
175
  processedIds.add(message.id);
171
176
 
172
- // Continue with active branch and process its message
177
+ // Continue with active branch - check if it's an assistantGroup
173
178
  const activeBranchMsg = this.messageMap.get(activeBranchId);
174
179
  if (activeBranchMsg) {
175
- flatList.push(activeBranchMsg);
176
- processedIds.add(activeBranchId);
177
-
178
- // Continue with active branch's children
179
- this.buildFlatListRecursive(activeBranchId, flatList, processedIds, allMessages);
180
+ // Check if active branch is assistant with tools (should be assistantGroup)
181
+ if (
182
+ activeBranchMsg.role === 'assistant' &&
183
+ activeBranchMsg.tools &&
184
+ activeBranchMsg.tools.length > 0
185
+ ) {
186
+ // Collect the entire assistant group chain
187
+ const assistantChain: Message[] = [];
188
+ const allToolMessages: Message[] = [];
189
+ this.messageCollector.collectAssistantChain(
190
+ activeBranchMsg,
191
+ allMessages,
192
+ assistantChain,
193
+ allToolMessages,
194
+ processedIds,
195
+ );
196
+
197
+ // Create assistantGroup virtual message
198
+ const groupMessage = this.createAssistantGroupMessage(
199
+ assistantChain[0],
200
+ assistantChain,
201
+ allToolMessages,
202
+ );
203
+ flatList.push(groupMessage);
204
+
205
+ // Mark all as processed
206
+ assistantChain.forEach((m) => processedIds.add(m.id));
207
+ allToolMessages.forEach((m) => processedIds.add(m.id));
208
+
209
+ // Continue after the assistant chain
210
+ const lastAssistant = assistantChain.at(-1);
211
+ const toolIds = new Set(allToolMessages.map((t) => t.id));
212
+
213
+ const lastAssistantNonToolChildren = lastAssistant
214
+ ? this.childrenMap.get(lastAssistant.id)?.filter((childId) => !toolIds.has(childId))
215
+ : undefined;
216
+
217
+ if (
218
+ lastAssistantNonToolChildren &&
219
+ lastAssistantNonToolChildren.length > 0 &&
220
+ lastAssistant
221
+ ) {
222
+ this.buildFlatListRecursive(lastAssistant.id, flatList, processedIds, allMessages);
223
+ } else {
224
+ for (const toolMsg of allToolMessages) {
225
+ this.buildFlatListRecursive(toolMsg.id, flatList, processedIds, allMessages);
226
+ }
227
+ }
228
+ } else {
229
+ // Regular message (not assistantGroup)
230
+ flatList.push(activeBranchMsg);
231
+ processedIds.add(activeBranchId);
232
+
233
+ // Continue with active branch's children
234
+ this.buildFlatListRecursive(activeBranchId, flatList, processedIds, allMessages);
235
+ }
180
236
  }
181
237
  continue;
182
238
  }
@@ -364,58 +420,80 @@ export class FlatListBuilder {
364
420
  assistant.tools?.map((tool) => {
365
421
  const toolMsg = toolMap.get(tool.id);
366
422
  if (toolMsg) {
423
+ const result: any = {
424
+ content: toolMsg.content || '',
425
+ id: toolMsg.id,
426
+ };
427
+ if (toolMsg.error) result.error = toolMsg.error;
428
+ if (toolMsg.pluginState) result.state = toolMsg.pluginState;
429
+
367
430
  return {
368
431
  ...tool,
369
- result: {
370
- content: toolMsg.content || '',
371
- error: toolMsg.error,
372
- id: toolMsg.id,
373
- state: toolMsg.pluginState,
374
- },
432
+ result,
375
433
  result_msg_id: toolMsg.id,
376
434
  };
377
435
  }
378
436
  return tool;
379
437
  }) || [];
380
438
 
381
- const { usage: msgUsage, performance: msgPerformance } =
439
+ // Prefer top-level usage/performance fields, fall back to metadata
440
+ const { usage: metaUsage, performance: metaPerformance } =
382
441
  this.messageTransformer.splitMetadata(assistant.metadata);
442
+ const msgUsage = assistant.usage || metaUsage;
443
+ const msgPerformance = assistant.performance || metaPerformance;
383
444
 
384
- children.push({
445
+ const childBlock: AssistantContentBlock = {
385
446
  content: assistant.content || '',
386
- error: assistant.error,
387
447
  id: assistant.id,
388
- imageList:
389
- assistant.imageList && assistant.imageList.length > 0 ? assistant.imageList : undefined,
390
- performance: msgPerformance,
391
- reasoning: assistant.reasoning || undefined,
392
- tools: toolsWithResults.length > 0 ? toolsWithResults : undefined,
393
- usage: msgUsage,
394
- });
448
+ } as AssistantContentBlock;
449
+
450
+ if (assistant.error) childBlock.error = assistant.error;
451
+ if (assistant.imageList && assistant.imageList.length > 0)
452
+ childBlock.imageList = assistant.imageList;
453
+ if (msgPerformance) childBlock.performance = msgPerformance;
454
+ if (assistant.reasoning) childBlock.reasoning = assistant.reasoning;
455
+ if (toolsWithResults.length > 0) childBlock.tools = toolsWithResults;
456
+ if (msgUsage) childBlock.usage = msgUsage;
457
+
458
+ children.push(childBlock);
395
459
  }
396
460
 
397
461
  const aggregated = this.messageTransformer.aggregateMetadata(children);
398
462
 
399
- return {
463
+ const result: Message = {
400
464
  ...firstAssistant,
401
465
  children,
402
466
  content: '',
403
- imageList: undefined,
404
- metadata: undefined,
405
- performance: aggregated.performance,
406
- reasoning: undefined,
407
467
  role: 'assistantGroup' as any,
408
- tools: undefined,
409
- usage: aggregated.usage,
410
468
  };
469
+
470
+ // Remove fields that should not be in assistantGroup
471
+ delete result.imageList;
472
+ delete result.metadata;
473
+ delete result.reasoning;
474
+ delete result.tools;
475
+
476
+ // Add aggregated fields if they exist
477
+ if (aggregated.performance) result.performance = aggregated.performance;
478
+ if (aggregated.usage) result.usage = aggregated.usage;
479
+
480
+ return result;
411
481
  }
412
482
 
413
483
  /**
414
484
  * Create user message with branch metadata
415
485
  */
416
- private createUserMessageWithBranches(user: Message): Message {
417
- // Just return the original user message with its metadata.activeBranchId
418
- // No need to add extra.branches
419
- return { ...user };
486
+ private createUserMessageWithBranches(
487
+ user: Message,
488
+ count: number,
489
+ activeBranchIndex: number,
490
+ ): Message {
491
+ return {
492
+ ...user,
493
+ branch: {
494
+ activeBranchIndex,
495
+ count,
496
+ },
497
+ } as Message;
420
498
  }
421
499
  }
@@ -41,22 +41,10 @@ export type FlatMessageRole =
41
41
  */
42
42
  export type FlatMessage = UIChatMessage;
43
43
 
44
- /**
45
- * Branch metadata attached to user messages
46
- */
47
- export interface BranchMetadata {
48
- /** Active branch message ID */
49
- activeId: string;
50
- /** All branch message IDs */
51
- branchIds: string[];
52
- }
53
-
54
44
  /**
55
45
  * Virtual message extra fields for flat list
56
46
  */
57
47
  export interface FlatMessageExtra {
58
- /** Branch information for user messages with multiple children */
59
- branches?: BranchMetadata;
60
48
  /** Optional description for groups */
61
49
  description?: string;
62
50
  /** Group mode for messageGroup and compare virtual messages */
@@ -16,21 +16,10 @@ export type {
16
16
  CompareNode,
17
17
  ContextNode,
18
18
  MessageNode,
19
- } from './types/contextTree';
19
+ } from './contextTree';
20
20
 
21
21
  // Flat Message List Types
22
- export type {
23
- BranchMetadata,
24
- FlatMessage,
25
- FlatMessageExtra,
26
- FlatMessageRole,
27
- } from './types/flatMessageList';
22
+ export type { FlatMessage, FlatMessageExtra, FlatMessageRole } from './flatMessageList';
28
23
 
29
24
  // Shared Types
30
- export type {
31
- HelperMaps,
32
- IdNode,
33
- Message,
34
- MessageGroupMetadata,
35
- ParseResult,
36
- } from './types/shared';
25
+ export type { HelperMaps, IdNode, Message, MessageGroupMetadata, ParseResult } from './shared';
@@ -40,7 +40,6 @@ import {
40
40
  } from '../schemas';
41
41
  import { LobeChatDatabase } from '../type';
42
42
  import { genEndDateWhere, genRangeWhere, genStartDateWhere, genWhere } from '../utils/genWhere';
43
- import { groupAssistantMessages } from '../utils/groupMessages';
44
43
  import { idGenerator } from '../utils/idGenerator';
45
44
 
46
45
  export class MessageModel {
@@ -227,8 +226,8 @@ export class MessageModel {
227
226
  })),
228
227
 
229
228
  extra: {
230
- fromModel: model,
231
- fromProvider: provider,
229
+ model: model,
230
+ provider: provider,
232
231
  translate,
233
232
  tts: ttsId
234
233
  ? {
@@ -269,9 +268,7 @@ export class MessageModel {
269
268
  },
270
269
  );
271
270
 
272
- // Group assistant messages with their tool results
273
- const { groupAssistantMessages: useGroup = false } = options;
274
- return useGroup ? groupAssistantMessages(mappedMessages) : mappedMessages;
271
+ return mappedMessages;
275
272
  };
276
273
 
277
274
  findById = async (id: string) => {
@@ -464,8 +461,8 @@ export class MessageModel {
464
461
 
465
462
  create = async (
466
463
  {
467
- fromModel,
468
- fromProvider,
464
+ model: fromModel,
465
+ provider: fromProvider,
469
466
  files,
470
467
  plugin,
471
468
  pluginState,
@@ -771,17 +768,19 @@ export class MessageModel {
771
768
  sessionId?: string | null,
772
769
  topicId?: string | null,
773
770
  groupId?: string | null,
774
- ) =>
775
- this.db
776
- .delete(messages)
777
- .where(
778
- and(
779
- eq(messages.userId, this.userId),
780
- this.matchSession(sessionId),
781
- this.matchTopic(topicId),
782
- this.matchGroup(groupId),
783
- ),
784
- );
771
+ ) => {
772
+ const conditions = [eq(messages.userId, this.userId), this.matchSession(sessionId)];
773
+
774
+ // For deletion: only filter by topicId/groupId if explicitly provided
775
+ if (topicId !== undefined && topicId !== null) {
776
+ conditions.push(eq(messages.topicId, topicId));
777
+ }
778
+ if (groupId !== undefined && groupId !== null) {
779
+ conditions.push(eq(messages.groupId, groupId));
780
+ }
781
+
782
+ return this.db.delete(messages).where(and(...conditions));
783
+ };
785
784
 
786
785
  deleteAllMessages = async () => {
787
786
  return this.db.delete(messages).where(eq(messages.userId, this.userId));
@@ -9,6 +9,7 @@ export interface SendNewMessage {
9
9
  content: string;
10
10
  // if message has attached with files, then add files to message and the agent
11
11
  files?: string[];
12
+ parentId?: string;
12
13
  }
13
14
 
14
15
  export interface SendMessageServerParams {
@@ -41,6 +42,7 @@ export const AiSendMessageServerSchema = z.object({
41
42
  newUserMessage: z.object({
42
43
  content: z.string(),
43
44
  files: z.array(z.string()).optional(),
45
+ parentId: z.string().optional(),
44
46
  }),
45
47
  sessionId: z.string().optional(),
46
48
  threadId: z.string().optional(),
@@ -28,8 +28,8 @@ export interface ImportMessage {
28
28
 
29
29
  // 扩展字段
30
30
  extra?: {
31
- fromModel?: string;
32
- fromProvider?: string;
31
+ model?: string;
32
+ provider?: string;
33
33
  // 翻译
34
34
  translate?: ChatTranslate | false | null;
35
35
  // TTS
@@ -13,7 +13,13 @@ import { ChatMessageExtra } from './extra';
13
13
  import { ChatFileChunk } from './rag';
14
14
  import { ChatVideoItem } from './video';
15
15
 
16
- export type UIMessageRoleType = 'user' | 'system' | 'assistant' | 'tool' | 'supervisor' | 'group';
16
+ export type UIMessageRoleType =
17
+ | 'user'
18
+ | 'system'
19
+ | 'assistant'
20
+ | 'tool'
21
+ | 'supervisor'
22
+ | 'assistantGroup';
17
23
 
18
24
  export interface ChatFileItem {
19
25
  content?: string;
@@ -34,10 +40,20 @@ export interface AssistantContentBlock {
34
40
  tools?: ChatToolPayloadWithResult[];
35
41
  usage?: ModelUsage;
36
42
  }
43
+ interface UIMessageBranch {
44
+ /** Index of the active branch (0-based) */
45
+ activeBranchIndex: number;
46
+ /** Total number of branches */
47
+ count: number;
48
+ }
37
49
 
38
50
  export interface UIChatMessage {
39
51
  // Group chat fields (alphabetically before other fields)
40
52
  agentId?: string | 'supervisor';
53
+ /**
54
+ * Branch information for user messages with multiple children
55
+ */
56
+ branch?: UIMessageBranch;
41
57
  /**
42
58
  * children messages for grouped display
43
59
  * Used to group tool messages under their parent assistant message
@@ -7,8 +7,8 @@ export interface ChatTTS {
7
7
  }
8
8
 
9
9
  export interface ChatMessageExtra {
10
- fromModel?: string;
11
- fromProvider?: string;
10
+ model?: string;
11
+ provider?: string;
12
12
  // 翻译
13
13
  translate?: ChatTranslate | false | null;
14
14
  // TTS
@@ -16,8 +16,8 @@ export interface CreateMessageParams
16
16
  error?: ChatMessageError | null;
17
17
  fileChunks?: MessageSemanticSearchChunk[];
18
18
  files?: string[];
19
- fromModel?: string;
20
- fromProvider?: string;
19
+ model?: string;
20
+ provider?: string;
21
21
  groupId?: string;
22
22
  role: CreateMessageRoleType;
23
23
  sessionId: string;
@@ -35,10 +35,6 @@ export const UserGuideSchema = z.object({
35
35
  export type UserGuide = z.infer<typeof UserGuideSchema>;
36
36
 
37
37
  export const UserLabSchema = z.object({
38
- /**
39
- * enable assistant message grouping in chat display
40
- */
41
- enableAssistantMessageGroup: z.boolean().optional(),
42
38
  /**
43
39
  * enable multi-agent group chat mode
44
40
  */
@@ -1,15 +1,7 @@
1
+ import { estimateTokenCount } from 'tokenx';
2
+
1
3
  export const encodeAsync = async (str: string): Promise<number> => {
2
4
  if (str.length === 0) return 0;
3
5
 
4
- // use gpt-tokenizer under 10000 str
5
- // use approximation way if large then 10000
6
- if (str.length <= 10_000) {
7
- const { clientEncodeAsync } = await import('./client');
8
-
9
- return await clientEncodeAsync(str);
10
- } else {
11
- const { estimatedEncodeAsync } = await import('./estimated');
12
-
13
- return await estimatedEncodeAsync(str);
14
- }
6
+ return estimateTokenCount(str);
15
7
  };
@@ -8,7 +8,7 @@ import { useChatStore } from '@/store/chat';
8
8
  import { useSend } from '../useSend';
9
9
 
10
10
  const MessageFromUrl = () => {
11
- const updateInputMessage = useChatStore((s) => s.updateInputMessage);
11
+ const updateMessageInput = useChatStore((s) => s.updateMessageInput);
12
12
  const { send: sendMessage } = useSend();
13
13
  const searchParams = useSearchParams();
14
14
 
@@ -21,10 +21,10 @@ const MessageFromUrl = () => {
21
21
  const newUrl = `${window.location.pathname}?${params.toString()}`;
22
22
  window.history.replaceState({}, '', newUrl);
23
23
 
24
- updateInputMessage(message);
24
+ updateMessageInput(message);
25
25
  sendMessage();
26
26
  }
27
- }, [searchParams, updateInputMessage, sendMessage]);
27
+ }, [searchParams, updateMessageInput, sendMessage]);
28
28
 
29
29
  return null;
30
30
  };
@@ -39,7 +39,7 @@ const MobileChatInput = memo(() => {
39
39
  const [loading, value, onInput, onStop] = useChatStore((s) => [
40
40
  messageStateSelectors.isAIGenerating(s),
41
41
  s.inputMessage,
42
- s.updateInputMessage,
42
+ s.updateMessageInput,
43
43
  s.stopGenerateMessage,
44
44
  ]);
45
45
 
@@ -16,9 +16,9 @@ export type UseSendMessageParams = Pick<
16
16
  >;
17
17
 
18
18
  export const useSendMessage = () => {
19
- const [sendMessage, updateInputMessage] = useChatStore((s) => [
19
+ const [sendMessage, updateMessageInput] = useChatStore((s) => [
20
20
  s.sendMessage,
21
- s.updateInputMessage,
21
+ s.updateMessageInput,
22
22
  ]);
23
23
  const { analytics } = useAnalytics();
24
24
  const checkGeminiChineseWarning = useGeminiChineseWarning();
@@ -66,7 +66,7 @@ export const useSendMessage = () => {
66
66
  ...params,
67
67
  });
68
68
 
69
- updateInputMessage('');
69
+ updateMessageInput('');
70
70
  clearChatUploadFileList();
71
71
 
72
72
  // 获取分析数据
@@ -161,14 +161,14 @@ export const useSendGroupMessage = () => {
161
161
  const [
162
162
  isContentEmpty,
163
163
  sendGroupMessage,
164
- updateInputMessage,
164
+ updateMessageInput,
165
165
  stopGenerateMessage,
166
166
  isSendButtonDisabledByMessage,
167
167
  isCreatingMessage,
168
168
  ] = useChatStore((s) => [
169
169
  !s.inputMessage,
170
170
  s.sendGroupMessage,
171
- s.updateInputMessage,
171
+ s.updateMessageInput,
172
172
  s.stopGenerateMessage,
173
173
  messageStateSelectors.isSendButtonDisabledByMessage(s),
174
174
  messageStateSelectors.isCreatingMessage(s),
@@ -255,7 +255,7 @@ export const useSendGroupMessage = () => {
255
255
  mainInputEditor.setExpand(false);
256
256
  mainInputEditor.clearContent();
257
257
  mainInputEditor.focus();
258
- updateInputMessage('');
258
+ updateMessageInput('');
259
259
  // clear mentioned users after sending
260
260
  mentionState.clearMentionedUsers();
261
261
 
@@ -284,7 +284,7 @@ export const useSendGroupMessage = () => {
284
284
  canNotSend,
285
285
  fileList,
286
286
  clearChatUploadFileList,
287
- updateInputMessage,
287
+ updateMessageInput,
288
288
  analytics,
289
289
  checkGeminiChineseWarning,
290
290
  ],
@@ -314,8 +314,8 @@ export const useSendGroupMessage = () => {
314
314
  generating: isSupervisorThinking || isCreatingMessage,
315
315
  send: handleSend,
316
316
  stop,
317
- updateInputMessage,
317
+ updateMessageInput,
318
318
  }),
319
- [canNotSend, isSupervisorThinking, isCreatingMessage, handleSend, stop, updateInputMessage],
319
+ [canNotSend, isSupervisorThinking, isCreatingMessage, handleSend, stop, updateMessageInput],
320
320
  );
321
321
  };
@@ -6,7 +6,7 @@ import { SkeletonList, VirtualizedList } from '@/features/Conversation';
6
6
  import WideScreenContainer from '@/features/Conversation/components/WideScreenContainer';
7
7
  import { useFetchMessages } from '@/hooks/useFetchMessages';
8
8
  import { useChatStore } from '@/store/chat';
9
- import { chatSelectors } from '@/store/chat/selectors';
9
+ import { displayMessageSelectors } from '@/store/chat/selectors';
10
10
 
11
11
  import MainChatItem from './ChatItem';
12
12
  import Welcome from './WelcomeChatItem';
@@ -16,10 +16,12 @@ interface ListProps {
16
16
  }
17
17
 
18
18
  const Content = memo<ListProps>(({ mobile }) => {
19
- const [isCurrentChatLoaded] = useChatStore((s) => [chatSelectors.isCurrentChatLoaded(s)]);
19
+ const [isCurrentChatLoaded] = useChatStore((s) => [
20
+ displayMessageSelectors.isCurrentDisplayChatLoaded(s),
21
+ ]);
20
22
 
21
23
  useFetchMessages();
22
- const data = useChatStore(chatSelectors.mainDisplayChatIDs);
24
+ const data = useChatStore(displayMessageSelectors.mainDisplayChatIDs);
23
25
 
24
26
  const itemContent = useCallback(
25
27
  (index: number, id: string) => <MainChatItem id={id} index={index} />,
@@ -40,7 +40,7 @@ interface OpeningQuestionsProps {
40
40
 
41
41
  const OpeningQuestions = memo<OpeningQuestionsProps>(({ mobile, questions }) => {
42
42
  const { t } = useTranslation('welcome');
43
- const [updateInputMessage] = useChatStore((s) => [s.updateInputMessage]);
43
+ const [updateMessageInput] = useChatStore((s) => [s.updateMessageInput]);
44
44
 
45
45
  const { styles } = useStyles();
46
46
  const { send: sendMessage } = useSend();
@@ -56,7 +56,7 @@ const OpeningQuestions = memo<OpeningQuestionsProps>(({ mobile, questions }) =>
56
56
  clickable
57
57
  key={question}
58
58
  onClick={() => {
59
- updateInputMessage(question);
59
+ updateMessageInput(question);
60
60
  sendMessage({ isWelcomeQuestion: true });
61
61
  }}
62
62
  paddingBlock={8}
@@ -77,7 +77,7 @@ const getFallbackActivities = (t: any) => {
77
77
  const GroupUsageSuggest = memo<{ mobile?: boolean }>(({ mobile }) => {
78
78
  const { t } = useTranslation('welcome');
79
79
  const { styles } = useStyles();
80
- const { updateInputMessage, send } = useSendGroupMessage();
80
+ const { updateMessageInput, send } = useSendGroupMessage();
81
81
  const templateMatch = useTemplateMatching();
82
82
 
83
83
  const itemsPerPage = mobile ? 2 : 4;
@@ -139,7 +139,7 @@ const GroupUsageSuggest = memo<{ mobile?: boolean }>(({ mobile }) => {
139
139
  horizontal
140
140
  key={activityKey}
141
141
  onClick={() => {
142
- updateInputMessage(prompt);
142
+ updateMessageInput(prompt);
143
143
  send();
144
144
  }}
145
145
  variant={'outlined'}
@@ -24,13 +24,11 @@ const LabsPage = memo(() => {
24
24
  const [
25
25
  isPreferenceInit,
26
26
  enableInputMarkdown,
27
- enableAssistantMessageGroup,
28
27
  // enableGroupChat,
29
28
  updateLab,
30
29
  ] = useUserStore((s) => [
31
30
  preferenceSelectors.isPreferenceInit(s),
32
31
  labPreferSelectors.enableInputMarkdown(s),
33
- labPreferSelectors.enableAssistantMessageGroup(s),
34
32
  // labPreferSelectors.enableGroupChat(s),
35
33
  s.updateLab,
36
34
  ]);
@@ -43,13 +41,6 @@ const LabsPage = memo(() => {
43
41
  key: 'enableInputMarkdown',
44
42
  title: t('features.inputMarkdown.title'),
45
43
  },
46
- {
47
- checked: enableAssistantMessageGroup,
48
- cover: 'https://github.com/user-attachments/assets/ba517751-1f3b-4269-979e-f8471e3ebb89',
49
- desc: t('features.assistantMessageGroup.desc'),
50
- key: 'enableAssistantMessageGroup',
51
- title: t('features.assistantMessageGroup.title'),
52
- },
53
44
  // {
54
45
  // checked: enableGroupChat,
55
46
  // cover: 'https://github.com/user-attachments/assets/72894d24-a96a-4d7c-a823-ff9e6a1a8b6d',
@@ -42,9 +42,9 @@ const BrowserSTT = memo<{ mobile?: boolean }>(({ mobile }) => {
42
42
  const [error, setError] = useState<ChatMessageError>();
43
43
  const { t } = useTranslation('chat');
44
44
 
45
- const [loading, updateInputMessage] = useChatStore((s) => [
45
+ const [loading, updateMessageInput] = useChatStore((s) => [
46
46
  messageStateSelectors.isAIGenerating(s),
47
- s.updateInputMessage,
47
+ s.updateMessageInput,
48
48
  ]);
49
49
 
50
50
  const setDefaultError = useCallback(
@@ -76,7 +76,7 @@ const BrowserSTT = memo<{ mobile?: boolean }>(({ mobile }) => {
76
76
  },
77
77
  onTextChange: (text) => {
78
78
  if (loading) stop();
79
- if (text) updateInputMessage(text);
79
+ if (text) updateMessageInput(text);
80
80
  },
81
81
  });
82
82
 
@@ -53,9 +53,9 @@ const OpenaiSTT = memo<{ mobile?: boolean }>(({ mobile }) => {
53
53
  const [error, setError] = useState<ChatMessageError>();
54
54
  const { t } = useTranslation('chat');
55
55
 
56
- const [loading, updateInputMessage] = useChatStore((s) => [
56
+ const [loading, updateMessageInput] = useChatStore((s) => [
57
57
  messageStateSelectors.isAIGenerating(s),
58
- s.updateInputMessage,
58
+ s.updateMessageInput,
59
59
  ]);
60
60
 
61
61
  const setDefaultError = useCallback(
@@ -87,7 +87,7 @@ const OpenaiSTT = memo<{ mobile?: boolean }>(({ mobile }) => {
87
87
  },
88
88
  onTextChange: (text) => {
89
89
  if (loading) stop();
90
- if (text) updateInputMessage(text);
90
+ if (text) updateMessageInput(text);
91
91
  },
92
92
  });
93
93