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

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 (156) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/changelog/v1.json +18 -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/__tests__/apiKey.test.ts +444 -0
  29. package/packages/database/src/models/message.ts +18 -19
  30. package/packages/types/src/aiChat.ts +2 -0
  31. package/packages/types/src/importer.ts +2 -2
  32. package/packages/types/src/message/ui/chat.ts +17 -1
  33. package/packages/types/src/message/ui/extra.ts +2 -2
  34. package/packages/types/src/message/ui/params.ts +2 -2
  35. package/packages/types/src/user/preference.ts +0 -4
  36. package/packages/utils/src/tokenizer/index.ts +3 -11
  37. package/src/app/[variants]/(main)/chat/components/conversation/features/ChatInput/Desktop/MessageFromUrl.tsx +3 -3
  38. package/src/app/[variants]/(main)/chat/components/conversation/features/ChatInput/V1Mobile/index.tsx +1 -1
  39. package/src/app/[variants]/(main)/chat/components/conversation/features/ChatInput/V1Mobile/useSend.ts +3 -3
  40. package/src/app/[variants]/(main)/chat/components/conversation/features/ChatInput/useSend.ts +6 -6
  41. package/src/app/[variants]/(main)/chat/components/conversation/features/ChatList/Content.tsx +5 -3
  42. package/src/app/[variants]/(main)/chat/components/conversation/features/ChatList/WelcomeChatItem/AgentWelcome/OpeningQuestions.tsx +2 -2
  43. package/src/app/[variants]/(main)/chat/components/conversation/features/ChatList/WelcomeChatItem/GroupWelcome/GroupUsageSuggest.tsx +2 -2
  44. package/src/app/[variants]/(main)/labs/page.tsx +0 -9
  45. package/src/features/ChatInput/ActionBar/STT/browser.tsx +3 -3
  46. package/src/features/ChatInput/ActionBar/STT/openai.tsx +3 -3
  47. package/src/features/Conversation/Error/AccessCodeForm.tsx +1 -1
  48. package/src/features/Conversation/Error/ChatInvalidApiKey.tsx +1 -1
  49. package/src/features/Conversation/Error/ClerkLogin/index.tsx +1 -1
  50. package/src/features/Conversation/Error/OAuthForm.tsx +1 -1
  51. package/src/features/Conversation/Error/index.tsx +0 -5
  52. package/src/features/Conversation/Messages/Assistant/Actions/index.tsx +13 -10
  53. package/src/features/Conversation/Messages/Assistant/Extra/index.test.tsx +3 -8
  54. package/src/features/Conversation/Messages/Assistant/Extra/index.tsx +2 -6
  55. package/src/features/Conversation/Messages/Assistant/MessageContent.tsx +7 -9
  56. package/src/features/Conversation/Messages/Assistant/Tool/Inspector/PluginResult.tsx +2 -2
  57. package/src/features/Conversation/Messages/Assistant/Tool/Inspector/PluginState.tsx +2 -2
  58. package/src/features/Conversation/Messages/Assistant/Tool/Render/PluginSettings.tsx +4 -1
  59. package/src/features/Conversation/Messages/Assistant/Tool/Render/index.tsx +2 -3
  60. package/src/features/Conversation/Messages/Assistant/index.tsx +57 -60
  61. package/src/features/Conversation/Messages/Default.tsx +1 -0
  62. package/src/features/Conversation/Messages/Group/Actions/WithContentId.tsx +38 -10
  63. package/src/features/Conversation/Messages/Group/Actions/index.tsx +1 -1
  64. package/src/features/Conversation/Messages/Group/ContentBlock.tsx +1 -3
  65. package/src/features/Conversation/Messages/Group/GroupChildren.tsx +12 -12
  66. package/src/features/Conversation/Messages/Group/MessageContent.tsx +7 -1
  67. package/src/features/Conversation/Messages/Group/Tool/Render/PluginSettings.tsx +1 -1
  68. package/src/features/Conversation/Messages/Group/index.tsx +2 -1
  69. package/src/features/Conversation/Messages/Supervisor/index.tsx +2 -2
  70. package/src/features/Conversation/Messages/User/{Actions.tsx → Actions/ActionsBar.tsx} +26 -25
  71. package/src/features/Conversation/Messages/User/Actions/MessageBranch.tsx +107 -0
  72. package/src/features/Conversation/Messages/User/Actions/index.tsx +42 -0
  73. package/src/features/Conversation/Messages/User/index.tsx +43 -44
  74. package/src/features/Conversation/Messages/index.tsx +3 -3
  75. package/src/features/Conversation/components/AutoScroll.tsx +3 -3
  76. package/src/features/Conversation/components/Extras/Usage/UsageDetail/AnimatedNumber.tsx +55 -0
  77. package/src/features/Conversation/components/Extras/Usage/UsageDetail/index.tsx +5 -2
  78. package/src/features/Conversation/components/VirtualizedList/index.tsx +29 -20
  79. package/src/features/Conversation/hooks/useChatListActionsBar.tsx +8 -10
  80. package/src/features/Portal/Thread/Chat/ChatInput/useSend.ts +3 -3
  81. package/src/hooks/useHotkeys/chatScope.ts +15 -7
  82. package/src/libs/trpc/client/lambda.ts +4 -3
  83. package/src/server/routers/lambda/__tests__/aiChat.test.ts +1 -1
  84. package/src/server/routers/lambda/__tests__/integration/message.integration.test.ts +0 -26
  85. package/src/server/routers/lambda/aiChat.ts +3 -2
  86. package/src/server/routers/lambda/message.ts +8 -16
  87. package/src/server/services/message/__tests__/index.test.ts +29 -39
  88. package/src/server/services/message/index.ts +41 -36
  89. package/src/services/electron/desktopNotification.ts +6 -6
  90. package/src/services/electron/file.ts +6 -6
  91. package/src/services/file/ClientS3/index.ts +8 -8
  92. package/src/services/message/__tests__/metadata-race-condition.test.ts +157 -0
  93. package/src/services/message/index.ts +21 -15
  94. package/src/services/upload.ts +11 -11
  95. package/src/services/utils/abortableRequest.test.ts +161 -0
  96. package/src/services/utils/abortableRequest.ts +67 -0
  97. package/src/store/chat/agents/GeneralChatAgent.ts +137 -0
  98. package/src/store/chat/agents/createAgentExecutors.ts +395 -0
  99. package/src/store/chat/helpers.test.ts +0 -99
  100. package/src/store/chat/helpers.ts +0 -11
  101. package/src/store/chat/slices/aiChat/actions/__tests__/conversationControl.test.ts +332 -0
  102. package/src/store/chat/slices/aiChat/actions/__tests__/conversationLifecycle.test.ts +257 -0
  103. package/src/store/chat/slices/aiChat/actions/__tests__/helpers.ts +11 -2
  104. package/src/store/chat/slices/aiChat/actions/__tests__/rag.test.ts +6 -6
  105. package/src/store/chat/slices/aiChat/actions/__tests__/streamingExecutor.test.ts +391 -0
  106. package/src/store/chat/slices/aiChat/actions/__tests__/streamingStates.test.ts +179 -0
  107. package/src/store/chat/slices/aiChat/actions/conversationControl.ts +157 -0
  108. package/src/store/chat/slices/aiChat/actions/conversationLifecycle.ts +329 -0
  109. package/src/store/chat/slices/aiChat/actions/generateAIGroupChat.ts +14 -14
  110. package/src/store/chat/slices/aiChat/actions/index.ts +12 -6
  111. package/src/store/chat/slices/aiChat/actions/rag.ts +9 -6
  112. package/src/store/chat/slices/aiChat/actions/streamingExecutor.ts +604 -0
  113. package/src/store/chat/slices/aiChat/actions/streamingStates.ts +84 -0
  114. package/src/store/chat/slices/builtinTool/actions/__tests__/localSystem.test.ts +4 -4
  115. package/src/store/chat/slices/builtinTool/actions/__tests__/search.test.ts +11 -11
  116. package/src/store/chat/slices/builtinTool/actions/interpreter.ts +8 -8
  117. package/src/store/chat/slices/builtinTool/actions/localSystem.ts +2 -2
  118. package/src/store/chat/slices/builtinTool/actions/search.ts +8 -8
  119. package/src/store/chat/slices/message/action.test.ts +79 -68
  120. package/src/store/chat/slices/message/actions/index.ts +39 -0
  121. package/src/store/chat/slices/message/actions/internals.ts +77 -0
  122. package/src/store/chat/slices/message/actions/optimisticUpdate.ts +260 -0
  123. package/src/store/chat/slices/message/actions/publicApi.ts +224 -0
  124. package/src/store/chat/slices/message/actions/query.ts +120 -0
  125. package/src/store/chat/slices/message/actions/runtimeState.ts +108 -0
  126. package/src/store/chat/slices/message/initialState.ts +13 -0
  127. package/src/store/chat/slices/message/reducer.test.ts +48 -370
  128. package/src/store/chat/slices/message/reducer.ts +17 -81
  129. package/src/store/chat/slices/message/selectors/chat.test.ts +13 -50
  130. package/src/store/chat/slices/message/selectors/chat.ts +78 -242
  131. package/src/store/chat/slices/message/selectors/dbMessage.ts +140 -0
  132. package/src/store/chat/slices/message/selectors/displayMessage.ts +301 -0
  133. package/src/store/chat/slices/message/selectors/messageState.ts +5 -2
  134. package/src/store/chat/slices/plugin/action.test.ts +62 -64
  135. package/src/store/chat/slices/plugin/action.ts +34 -28
  136. package/src/store/chat/slices/thread/action.test.ts +28 -31
  137. package/src/store/chat/slices/thread/action.ts +13 -10
  138. package/src/store/chat/slices/thread/selectors/index.ts +8 -6
  139. package/src/store/chat/slices/topic/reducer.ts +11 -3
  140. package/src/store/chat/store.ts +1 -1
  141. package/src/store/user/slices/preference/selectors/labPrefer.ts +0 -3
  142. package/packages/database/src/models/__tests__/message.grouping.test.ts +0 -812
  143. package/packages/database/src/utils/__tests__/groupMessages.test.ts +0 -1132
  144. package/packages/database/src/utils/groupMessages.ts +0 -361
  145. package/packages/utils/src/tokenizer/client.ts +0 -35
  146. package/packages/utils/src/tokenizer/estimated.ts +0 -4
  147. package/packages/utils/src/tokenizer/server.ts +0 -11
  148. package/packages/utils/src/tokenizer/tokenizer.worker.ts +0 -12
  149. package/src/app/(backend)/webapi/tokenizer/index.test.ts +0 -32
  150. package/src/app/(backend)/webapi/tokenizer/route.ts +0 -8
  151. package/src/features/Conversation/Error/InvalidAccessCode.tsx +0 -79
  152. package/src/store/chat/slices/aiChat/actions/__tests__/generateAIChat.test.ts +0 -975
  153. package/src/store/chat/slices/aiChat/actions/__tests__/generateAIChatV2.test.ts +0 -1050
  154. package/src/store/chat/slices/aiChat/actions/generateAIChat.ts +0 -720
  155. package/src/store/chat/slices/aiChat/actions/generateAIChatV2.ts +0 -849
  156. package/src/store/chat/slices/message/action.ts +0 -629
@@ -15,6 +15,10 @@ export interface ChatMessageState {
15
15
  * Derived from session.type, used for caching to avoid repeated lookups
16
16
  */
17
17
  activeSessionType?: 'agent' | 'group';
18
+ /**
19
+ * Raw messages from database (flat structure)
20
+ */
21
+ dbMessagesMap: Record<string, UIChatMessage[]>;
18
22
  /**
19
23
  * Group agents maps by group ID
20
24
  */
@@ -40,7 +44,14 @@ export interface ChatMessageState {
40
44
  * whether messages have fetched
41
45
  */
42
46
  messagesInit: boolean;
47
+ /**
48
+ * Parsed messages for display (includes assistantGroup from conversation-flow)
49
+ */
43
50
  messagesMap: Record<string, UIChatMessage[]>;
51
+ /**
52
+ * is the message is regenerating (used for disable regenerate button)
53
+ */
54
+ regeneratingIds: string[];
44
55
  /**
45
56
  * Supervisor decision debounce timers by group ID
46
57
  */
@@ -62,6 +73,7 @@ export interface ChatMessageState {
62
73
  export const initialMessageState: ChatMessageState = {
63
74
  activeId: 'inbox',
64
75
  activeSessionType: undefined,
76
+ dbMessagesMap: {},
65
77
  groupAgentMaps: {},
66
78
  groupMaps: {},
67
79
  groupsInit: false,
@@ -70,6 +82,7 @@ export const initialMessageState: ChatMessageState = {
70
82
  messageLoadingIds: [],
71
83
  messagesInit: false,
72
84
  messagesMap: {},
85
+ regeneratingIds: [],
73
86
  supervisorDebounceTimers: {},
74
87
  supervisorDecisionAbortControllers: {},
75
88
  supervisorDecisionLoading: [],
@@ -56,133 +56,6 @@ describe('messagesReducer', () => {
56
56
 
57
57
  expect(newState).toEqual(initialState);
58
58
  });
59
-
60
- it('should update a block in group message children when id matches a block', () => {
61
- const stateWithGroup: UIChatMessage[] = [
62
- ...initialState,
63
- {
64
- id: 'group1',
65
- role: 'group',
66
- content: '',
67
- createdAt: 1629264000000,
68
- updatedAt: 1629264000000,
69
- meta: {},
70
- children: [
71
- {
72
- id: 'block1',
73
- content: 'Original block content',
74
- tools: [
75
- {
76
- id: 'tool1',
77
- identifier: 'search',
78
- apiName: 'search',
79
- type: 'builtin',
80
- arguments: '{"query": "test"}',
81
- },
82
- ],
83
- },
84
- ],
85
- } as UIChatMessage,
86
- ];
87
-
88
- const payload: MessageDispatch = {
89
- type: 'updateMessage',
90
- id: 'block1',
91
- value: { content: 'Updated block content' },
92
- };
93
-
94
- const newState = messagesReducer(stateWithGroup, payload);
95
- const groupMessage = newState.find((m) => m.id === 'group1');
96
- const block = groupMessage?.children?.find((b) => b.id === 'block1');
97
-
98
- expect(block?.content).toBe('Updated block content');
99
- expect(groupMessage?.updatedAt).toBeGreaterThan(1629264000000);
100
- });
101
-
102
- it('should update block tools in group message children', () => {
103
- const stateWithGroup: UIChatMessage[] = [
104
- ...initialState,
105
- {
106
- id: 'group1',
107
- role: 'group',
108
- content: '',
109
- createdAt: 1629264000000,
110
- updatedAt: 1629264000000,
111
- meta: {},
112
- children: [
113
- {
114
- id: 'block1',
115
- content: 'Block content',
116
- tools: [
117
- {
118
- id: 'tool1',
119
- identifier: 'search',
120
- apiName: 'search',
121
- type: 'builtin',
122
- arguments: '{"query": "test"}',
123
- },
124
- ],
125
- },
126
- ],
127
- } as UIChatMessage,
128
- ];
129
-
130
- const newTools = [
131
- {
132
- id: 'tool1',
133
- identifier: 'search',
134
- apiName: 'search',
135
- type: 'builtin',
136
- arguments: '{"query": "updated"}',
137
- result: {
138
- id: 'result1',
139
- content: 'Search result',
140
- },
141
- },
142
- ];
143
-
144
- const payload: MessageDispatch = {
145
- type: 'updateMessage',
146
- id: 'block1',
147
- value: { tools: newTools as any },
148
- };
149
-
150
- const newState = messagesReducer(stateWithGroup, payload);
151
- const groupMessage = newState.find((m) => m.id === 'group1');
152
- const block = groupMessage?.children?.find((b) => b.id === 'block1');
153
-
154
- expect(block?.tools).toEqual(newTools);
155
- expect(groupMessage?.updatedAt).toBeGreaterThan(1629264000000);
156
- });
157
-
158
- it('should not modify state when updating non-existent block in group message', () => {
159
- const stateWithGroup: UIChatMessage[] = [
160
- ...initialState,
161
- {
162
- id: 'group1',
163
- role: 'group',
164
- content: '',
165
- createdAt: 1629264000000,
166
- updatedAt: 1629264000000,
167
- meta: {},
168
- children: [
169
- {
170
- id: 'block1',
171
- content: 'Block content',
172
- },
173
- ],
174
- } as UIChatMessage,
175
- ];
176
-
177
- const payload: MessageDispatch = {
178
- type: 'updateMessage',
179
- id: 'nonexistentBlock',
180
- value: { content: 'Updated content' },
181
- };
182
-
183
- const newState = messagesReducer(stateWithGroup, payload);
184
- expect(newState).toEqual(stateWithGroup);
185
- });
186
59
  });
187
60
 
188
61
  describe('unimplemented type', () => {
@@ -254,6 +127,54 @@ describe('messagesReducer', () => {
254
127
  });
255
128
  });
256
129
 
130
+ describe('updateMessageMetadata', () => {
131
+ it('should merge update the metadata field of a message', () => {
132
+ const payload: MessageDispatch = {
133
+ type: 'updateMessageMetadata',
134
+ id: 'message1',
135
+ value: { activeBranchIndex: 1 },
136
+ };
137
+
138
+ const newState = messagesReducer(initialState, payload);
139
+ const updatedMessage = newState.find((m) => m.id === 'message1');
140
+
141
+ expect(updatedMessage?.metadata).toEqual({ activeBranchIndex: 1 });
142
+ expect(updatedMessage?.updatedAt).toBeGreaterThan(initialState[0].updatedAt);
143
+ });
144
+
145
+ it('should merge update the metadata field if metadata already exists', () => {
146
+ const state = [
147
+ {
148
+ ...initialState[0],
149
+ metadata: { activeBranchIndex: 0 },
150
+ },
151
+ ];
152
+
153
+ const payload: MessageDispatch = {
154
+ type: 'updateMessageMetadata',
155
+ id: 'message1',
156
+ value: { activeBranchIndex: 2 },
157
+ };
158
+
159
+ const newState = messagesReducer(state, payload);
160
+ const updatedMessage = newState.find((m) => m.id === 'message1');
161
+
162
+ expect(updatedMessage?.metadata).toEqual({ activeBranchIndex: 2 });
163
+ expect(updatedMessage?.updatedAt).toBeGreaterThan(initialState[0].updatedAt);
164
+ });
165
+
166
+ it('should not modify state if message is not found', () => {
167
+ const payload: MessageDispatch = {
168
+ type: 'updateMessageMetadata',
169
+ id: 'nonexistent',
170
+ value: { activeBranchIndex: 1 },
171
+ };
172
+
173
+ const newState = messagesReducer(initialState, payload);
174
+ expect(newState).toEqual(initialState);
175
+ });
176
+ });
177
+
257
178
  describe('updatePluginState', () => {
258
179
  it('should update the plugin state of a message', () => {
259
180
  const payload: MessageDispatch = {
@@ -617,247 +538,4 @@ describe('messagesReducer', () => {
617
538
  expect(newState).toEqual(initialState);
618
539
  });
619
540
  });
620
-
621
- describe('updateGroupBlockToolResult', () => {
622
- it('should update a tool result in a group message block', () => {
623
- const stateWithGroup: UIChatMessage[] = [
624
- ...initialState,
625
- {
626
- id: 'group1',
627
- role: 'group',
628
- content: '',
629
- createdAt: 1629264000000,
630
- updatedAt: 1629264000000,
631
- meta: {},
632
- children: [
633
- {
634
- id: 'block1',
635
- content: 'Assistant response',
636
- tools: [
637
- {
638
- id: 'tool1',
639
- identifier: 'search',
640
- apiName: 'search',
641
- type: 'builtin',
642
- arguments: '{"query": "test"}',
643
- result: {
644
- id: 'result1',
645
- content: 'Initial result',
646
- },
647
- },
648
- ],
649
- },
650
- ],
651
- } as UIChatMessage,
652
- ];
653
-
654
- const payload: MessageDispatch = {
655
- type: 'updateGroupBlockToolResult',
656
- groupMessageId: 'group1',
657
- blockId: 'block1',
658
- toolId: 'tool1',
659
- toolResult: {
660
- id: 'result1',
661
- content: 'Updated result content',
662
- state: { foo: 'bar' },
663
- },
664
- };
665
-
666
- const newState = messagesReducer(stateWithGroup, payload);
667
- const groupMessage = newState.find((m) => m.id === 'group1');
668
- const block = groupMessage?.children?.find((b) => b.id === 'block1');
669
- const tool = block?.tools?.find((t) => t.id === 'tool1');
670
-
671
- expect(tool?.result).toEqual({
672
- id: 'result1',
673
- content: 'Updated result content',
674
- state: { foo: 'bar' },
675
- });
676
- expect(groupMessage?.updatedAt).toBeGreaterThan(1629264000000);
677
- });
678
-
679
- it('should not modify state if group message is not found', () => {
680
- const payload: MessageDispatch = {
681
- type: 'updateGroupBlockToolResult',
682
- groupMessageId: 'nonexistent',
683
- blockId: 'block1',
684
- toolId: 'tool1',
685
- toolResult: {
686
- id: 'result1',
687
- content: 'Updated result',
688
- },
689
- };
690
-
691
- const newState = messagesReducer(initialState, payload);
692
- expect(newState).toEqual(initialState);
693
- });
694
-
695
- it('should not modify state if block is not found', () => {
696
- const stateWithGroup: UIChatMessage[] = [
697
- ...initialState,
698
- {
699
- id: 'group1',
700
- role: 'group',
701
- content: '',
702
- createdAt: 1629264000000,
703
- updatedAt: 1629264000000,
704
- meta: {},
705
- children: [
706
- {
707
- id: 'block1',
708
- content: 'Assistant response',
709
- tools: [],
710
- },
711
- ],
712
- } as UIChatMessage,
713
- ];
714
-
715
- const payload: MessageDispatch = {
716
- type: 'updateGroupBlockToolResult',
717
- groupMessageId: 'group1',
718
- blockId: 'nonexistentBlock',
719
- toolId: 'tool1',
720
- toolResult: {
721
- id: 'result1',
722
- content: 'Updated result',
723
- },
724
- };
725
-
726
- const newState = messagesReducer(stateWithGroup, payload);
727
- expect(newState).toEqual(stateWithGroup);
728
- });
729
-
730
- it('should not modify state if tool is not found', () => {
731
- const stateWithGroup: UIChatMessage[] = [
732
- ...initialState,
733
- {
734
- id: 'group1',
735
- role: 'group',
736
- content: '',
737
- createdAt: 1629264000000,
738
- updatedAt: 1629264000000,
739
- meta: {},
740
- children: [
741
- {
742
- id: 'block1',
743
- content: 'Assistant response',
744
- tools: [
745
- {
746
- id: 'tool1',
747
- identifier: 'search',
748
- apiName: 'search',
749
- type: 'builtin',
750
- arguments: '{"query": "test"}',
751
- },
752
- ],
753
- },
754
- ],
755
- } as UIChatMessage,
756
- ];
757
-
758
- const payload: MessageDispatch = {
759
- type: 'updateGroupBlockToolResult',
760
- groupMessageId: 'group1',
761
- blockId: 'block1',
762
- toolId: 'nonexistentTool',
763
- toolResult: {
764
- id: 'result1',
765
- content: 'Updated result',
766
- },
767
- };
768
-
769
- const newState = messagesReducer(stateWithGroup, payload);
770
- expect(newState).toEqual(stateWithGroup);
771
- });
772
- });
773
-
774
- describe('addGroupBlock', () => {
775
- it('should add a new block to group message children', () => {
776
- const stateWithGroup: UIChatMessage[] = [
777
- ...initialState,
778
- {
779
- id: 'group1',
780
- role: 'group',
781
- content: '',
782
- createdAt: 1629264000000,
783
- updatedAt: 1629264000000,
784
- meta: {},
785
- children: [
786
- {
787
- id: 'block1',
788
- content: 'First block',
789
- },
790
- ],
791
- } as UIChatMessage,
792
- ];
793
-
794
- const payload: MessageDispatch = {
795
- type: 'addGroupBlock',
796
- groupMessageId: 'group1',
797
- blockId: 'block2',
798
- value: {
799
- id: 'block2',
800
- content: 'Second block',
801
- },
802
- };
803
-
804
- const newState = messagesReducer(stateWithGroup, payload);
805
- const groupMessage = newState.find((m) => m.id === 'group1');
806
-
807
- expect(groupMessage?.children).toHaveLength(2);
808
- expect(groupMessage?.children?.[1]).toEqual({
809
- id: 'block2',
810
- content: 'Second block',
811
- });
812
- expect(groupMessage?.updatedAt).toBeGreaterThan(1629264000000);
813
- });
814
-
815
- it('should not modify state if group message is not found', () => {
816
- const stateWithGroup: UIChatMessage[] = [
817
- ...initialState,
818
- {
819
- id: 'group1',
820
- role: 'group',
821
- content: '',
822
- createdAt: 1629264000000,
823
- updatedAt: 1629264000000,
824
- meta: {},
825
- children: [
826
- {
827
- id: 'block1',
828
- content: 'First block',
829
- },
830
- ],
831
- } as UIChatMessage,
832
- ];
833
-
834
- const payload: MessageDispatch = {
835
- type: 'addGroupBlock',
836
- groupMessageId: 'nonexistentGroup',
837
- blockId: 'block2',
838
- value: {
839
- id: 'block2',
840
- content: 'Second block',
841
- },
842
- };
843
-
844
- const newState = messagesReducer(stateWithGroup, payload);
845
- expect(newState).toEqual(stateWithGroup);
846
- });
847
-
848
- it('should not modify state if message is not a group message', () => {
849
- const payload: MessageDispatch = {
850
- type: 'addGroupBlock',
851
- groupMessageId: 'message1',
852
- blockId: 'block2',
853
- value: {
854
- id: 'block2',
855
- content: 'Second block',
856
- },
857
- };
858
-
859
- const newState = messagesReducer(initialState, payload);
860
- expect(newState).toEqual(initialState);
861
- });
862
- });
863
541
  });
@@ -75,27 +75,10 @@ interface UpdateMessageExtra {
75
75
  value: any;
76
76
  }
77
77
 
78
- interface UpdateGroupBlockToolResult {
79
- blockId: string;
80
- groupMessageId: string;
81
- toolId: string;
82
- toolResult: {
83
- content: string;
84
- error?: any;
85
- id: string;
86
- state?: any;
87
- };
88
- type: 'updateGroupBlockToolResult';
89
- }
90
-
91
- interface AddGroupBlock {
92
- blockId: string;
93
- groupMessageId: string;
94
- type: 'addGroupBlock';
95
- value: {
96
- content: string;
97
- id: string;
98
- };
78
+ interface UpdateMessageMetadata {
79
+ id: string;
80
+ type: 'updateMessageMetadata';
81
+ value: Partial<UIChatMessage['metadata']>;
99
82
  }
100
83
 
101
84
  export type MessageDispatch =
@@ -104,14 +87,13 @@ export type MessageDispatch =
104
87
  | UpdateMessages
105
88
  | UpdatePluginState
106
89
  | UpdateMessageExtra
90
+ | UpdateMessageMetadata
107
91
  | DeleteMessage
108
92
  | UpdateMessagePlugin
109
93
  | UpdateMessageTools
110
94
  | AddMessageTool
111
95
  | DeleteMessageTool
112
- | DeleteMessages
113
- | UpdateGroupBlockToolResult
114
- | AddGroupBlock;
96
+ | DeleteMessages;
115
97
 
116
98
  export const messagesReducer = (
117
99
  state: UIChatMessage[],
@@ -122,25 +104,9 @@ export const messagesReducer = (
122
104
  return produce(state, (draftState) => {
123
105
  const { id, value } = payload;
124
106
 
125
- // First, try to find in top-level messages
126
107
  const index = draftState.findIndex((i) => i.id === id);
127
108
  if (index >= 0) {
128
109
  draftState[index] = merge(draftState[index], { ...value, updatedAt: Date.now() });
129
- return;
130
- }
131
-
132
- // If not found, search in group message children (blocks)
133
- for (const message of draftState) {
134
- if (message.role === 'group' && message.children) {
135
- const blockIndex = message.children.findIndex((block) => block.id === id);
136
- if (blockIndex >= 0) {
137
- message.children[blockIndex] = merge(message.children[blockIndex], {
138
- ...value,
139
- });
140
- message.updatedAt = Date.now();
141
- return;
142
- }
143
- }
144
110
  }
145
111
  });
146
112
  }
@@ -161,6 +127,17 @@ export const messagesReducer = (
161
127
  });
162
128
  }
163
129
 
130
+ case 'updateMessageMetadata': {
131
+ return produce(state, (draftState) => {
132
+ const { id, value } = payload;
133
+ const message = draftState.find((i) => i.id === id);
134
+ if (!message) return;
135
+
136
+ message.metadata = merge(message.metadata, value);
137
+ message.updatedAt = Date.now();
138
+ });
139
+ }
140
+
164
141
  case 'updatePluginState': {
165
142
  return produce(state, (draftState) => {
166
143
  const { id, key, value } = payload;
@@ -269,47 +246,6 @@ export const messagesReducer = (
269
246
  });
270
247
  }
271
248
 
272
- case 'updateGroupBlockToolResult': {
273
- return produce(state, (draftState) => {
274
- const { groupMessageId, blockId, toolId, toolResult } = payload;
275
-
276
- // Find the group message
277
- const msg = draftState.find((m) => m.id === groupMessageId);
278
- if (!msg || msg.role !== 'group' || !msg.children) return;
279
-
280
- // Find the block within children
281
- const block = msg.children.find((b) => b.id === blockId);
282
- if (!block || !block.tools) return;
283
-
284
- // Find the tool and update its result
285
- const tool = block.tools.find((t) => t.id === toolId);
286
- if (!tool) return;
287
-
288
- // Update tool result (optimistic update)
289
- tool.result = toolResult;
290
-
291
- msg.updatedAt = Date.now();
292
- });
293
- }
294
-
295
- case 'addGroupBlock': {
296
- return produce(state, (draftState) => {
297
- const { groupMessageId, blockId, value } = payload;
298
-
299
- // Find the group message
300
- const msg = draftState.find((m) => m.id === groupMessageId);
301
- if (!msg || msg.role !== 'group' || !msg.children) return;
302
-
303
- // Add new block to children
304
- msg.children.push({
305
- content: value.content,
306
- id: blockId,
307
- });
308
-
309
- msg.updatedAt = Date.now();
310
- });
311
- }
312
-
313
249
  default: {
314
250
  throw new Error('暂未实现的 type,请检查 reducer');
315
251
  }