@lobehub/lobehub 2.0.0-next.28 → 2.0.0-next.29
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.
- package/CHANGELOG.md +25 -0
- package/changelog/v1.json +9 -0
- package/package.json +1 -1
- package/packages/database/package.json +1 -1
- package/packages/database/src/models/message.ts +29 -2
- package/packages/types/src/message/ui/params.ts +0 -49
- package/src/server/routers/lambda/__tests__/integration/message.integration.test.ts +35 -35
- package/src/server/routers/lambda/message.ts +104 -16
- package/src/services/message/index.ts +63 -35
- package/src/store/chat/slices/aiChat/actions/__tests__/generateAIChat.test.ts +17 -10
- package/src/store/chat/slices/aiChat/actions/__tests__/generateAIChatV2.test.ts +4 -4
- package/src/store/chat/slices/aiChat/actions/__tests__/helpers.ts +2 -2
- package/src/store/chat/slices/aiChat/actions/generateAIChat.ts +3 -2
- package/src/store/chat/slices/aiChat/actions/generateAIChatV2.ts +2 -2
- package/src/store/chat/slices/aiChat/actions/generateAIGroupChat.ts +7 -2
- package/src/store/chat/slices/builtinTool/actions/search.ts +3 -3
- package/src/store/chat/slices/message/action.test.ts +152 -15
- package/src/store/chat/slices/message/action.ts +70 -82
- package/src/store/chat/slices/plugin/action.test.ts +84 -25
- package/src/store/chat/slices/plugin/action.ts +52 -24
- package/src/store/chat/slices/thread/action.test.ts +13 -4
- package/src/store/chat/slices/thread/action.ts +3 -1
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
import {
|
|
3
3
|
ChatMessageError,
|
|
4
4
|
ChatMessagePluginError,
|
|
5
|
-
ChatTranslate,
|
|
6
5
|
ChatTTS,
|
|
6
|
+
ChatTranslate,
|
|
7
7
|
CreateMessageParams,
|
|
8
8
|
CreateMessageResult,
|
|
9
9
|
ModelRankItem,
|
|
@@ -20,12 +20,9 @@ import { useUserStore } from '@/store/user';
|
|
|
20
20
|
import { labPreferSelectors } from '@/store/user/selectors';
|
|
21
21
|
|
|
22
22
|
export class MessageService {
|
|
23
|
-
|
|
24
|
-
return
|
|
25
|
-
|
|
26
|
-
sessionId: sessionId ? this.toDbSessionId(sessionId) : undefined,
|
|
27
|
-
});
|
|
28
|
-
};
|
|
23
|
+
private get useGroup() {
|
|
24
|
+
return labPreferSelectors.enableAssistantMessageGroup(useUserStore.getState());
|
|
25
|
+
}
|
|
29
26
|
|
|
30
27
|
createNewMessage = async ({
|
|
31
28
|
sessionId,
|
|
@@ -42,27 +39,21 @@ export class MessageService {
|
|
|
42
39
|
topicId?: string,
|
|
43
40
|
groupId?: string,
|
|
44
41
|
): Promise<UIChatMessage[]> => {
|
|
45
|
-
// Get user lab preference for message grouping
|
|
46
|
-
const useGroup = labPreferSelectors.enableAssistantMessageGroup(useUserStore.getState());
|
|
47
|
-
|
|
48
42
|
const data = await lambdaClient.message.getMessages.query({
|
|
49
43
|
groupId,
|
|
50
44
|
sessionId: this.toDbSessionId(sessionId),
|
|
51
45
|
topicId,
|
|
52
|
-
useGroup,
|
|
46
|
+
useGroup: this.useGroup,
|
|
53
47
|
});
|
|
54
48
|
|
|
55
49
|
return data as unknown as UIChatMessage[];
|
|
56
50
|
};
|
|
57
51
|
|
|
58
52
|
getGroupMessages = async (groupId: string, topicId?: string): Promise<UIChatMessage[]> => {
|
|
59
|
-
// Get user lab preference for message grouping
|
|
60
|
-
const useGroup = labPreferSelectors.enableAssistantMessageGroup(useUserStore.getState());
|
|
61
|
-
|
|
62
53
|
const data = await lambdaClient.message.getMessages.query({
|
|
63
54
|
groupId,
|
|
64
55
|
topicId,
|
|
65
|
-
useGroup,
|
|
56
|
+
useGroup: this.useGroup,
|
|
66
57
|
});
|
|
67
58
|
return data as unknown as UIChatMessage[];
|
|
68
59
|
};
|
|
@@ -109,6 +100,7 @@ export class MessageService {
|
|
|
109
100
|
id,
|
|
110
101
|
sessionId: options?.sessionId,
|
|
111
102
|
topicId: options?.topicId,
|
|
103
|
+
useGroup: this.useGroup,
|
|
112
104
|
value,
|
|
113
105
|
});
|
|
114
106
|
};
|
|
@@ -121,24 +113,70 @@ export class MessageService {
|
|
|
121
113
|
return lambdaClient.message.updateTTS.mutate({ id, value: tts });
|
|
122
114
|
};
|
|
123
115
|
|
|
124
|
-
updateMessagePluginState = async (
|
|
125
|
-
|
|
116
|
+
updateMessagePluginState = async (
|
|
117
|
+
id: string,
|
|
118
|
+
value: Record<string, any>,
|
|
119
|
+
options?: { sessionId?: string | null; topicId?: string | null },
|
|
120
|
+
): Promise<UpdateMessageResult> => {
|
|
121
|
+
return lambdaClient.message.updatePluginState.mutate({
|
|
122
|
+
id,
|
|
123
|
+
sessionId: options?.sessionId,
|
|
124
|
+
topicId: options?.topicId,
|
|
125
|
+
useGroup: this.useGroup,
|
|
126
|
+
value,
|
|
127
|
+
});
|
|
126
128
|
};
|
|
127
129
|
|
|
128
|
-
updateMessagePluginError = async (
|
|
129
|
-
|
|
130
|
+
updateMessagePluginError = async (
|
|
131
|
+
id: string,
|
|
132
|
+
error: ChatMessagePluginError | null,
|
|
133
|
+
options?: { sessionId?: string | null; topicId?: string | null },
|
|
134
|
+
): Promise<UpdateMessageResult> => {
|
|
135
|
+
return lambdaClient.message.updatePluginError.mutate({
|
|
136
|
+
id,
|
|
137
|
+
sessionId: options?.sessionId,
|
|
138
|
+
topicId: options?.topicId,
|
|
139
|
+
useGroup: this.useGroup,
|
|
140
|
+
value: error as any,
|
|
141
|
+
});
|
|
130
142
|
};
|
|
131
143
|
|
|
132
|
-
updateMessageRAG = async (
|
|
133
|
-
|
|
144
|
+
updateMessageRAG = async (
|
|
145
|
+
id: string,
|
|
146
|
+
data: UpdateMessageRAGParams,
|
|
147
|
+
options?: { sessionId?: string | null; topicId?: string | null },
|
|
148
|
+
): Promise<UpdateMessageResult> => {
|
|
149
|
+
return lambdaClient.message.updateMessageRAG.mutate({
|
|
150
|
+
id,
|
|
151
|
+
sessionId: options?.sessionId,
|
|
152
|
+
topicId: options?.topicId,
|
|
153
|
+
useGroup: this.useGroup,
|
|
154
|
+
value: data,
|
|
155
|
+
});
|
|
134
156
|
};
|
|
135
157
|
|
|
136
|
-
removeMessage = async (
|
|
137
|
-
|
|
158
|
+
removeMessage = async (
|
|
159
|
+
id: string,
|
|
160
|
+
options?: { sessionId?: string | null; topicId?: string | null },
|
|
161
|
+
): Promise<UpdateMessageResult> => {
|
|
162
|
+
return lambdaClient.message.removeMessage.mutate({
|
|
163
|
+
id,
|
|
164
|
+
sessionId: options?.sessionId,
|
|
165
|
+
topicId: options?.topicId,
|
|
166
|
+
useGroup: this.useGroup,
|
|
167
|
+
});
|
|
138
168
|
};
|
|
139
169
|
|
|
140
|
-
removeMessages = async (
|
|
141
|
-
|
|
170
|
+
removeMessages = async (
|
|
171
|
+
ids: string[],
|
|
172
|
+
options?: { sessionId?: string | null; topicId?: string | null },
|
|
173
|
+
): Promise<UpdateMessageResult> => {
|
|
174
|
+
return lambdaClient.message.removeMessages.mutate({
|
|
175
|
+
ids,
|
|
176
|
+
sessionId: options?.sessionId,
|
|
177
|
+
topicId: options?.topicId,
|
|
178
|
+
useGroup: this.useGroup,
|
|
179
|
+
});
|
|
142
180
|
};
|
|
143
181
|
|
|
144
182
|
removeMessagesByAssistant = async (sessionId: string, topicId?: string) => {
|
|
@@ -162,16 +200,6 @@ export class MessageService {
|
|
|
162
200
|
private toDbSessionId = (sessionId: string | undefined) => {
|
|
163
201
|
return sessionId === INBOX_SESSION_ID ? null : sessionId;
|
|
164
202
|
};
|
|
165
|
-
|
|
166
|
-
hasMessages = async (): Promise<boolean> => {
|
|
167
|
-
const number = await this.countMessages();
|
|
168
|
-
return number > 0;
|
|
169
|
-
};
|
|
170
|
-
|
|
171
|
-
messageCountToCheckTrace = async (): Promise<boolean> => {
|
|
172
|
-
const number = await this.countMessages();
|
|
173
|
-
return number >= 4;
|
|
174
|
-
};
|
|
175
203
|
}
|
|
176
204
|
|
|
177
205
|
export const messageService = new MessageService();
|
|
@@ -61,7 +61,7 @@ describe('chatMessage actions', () => {
|
|
|
61
61
|
await result.current.sendMessage({ message: TEST_CONTENT.USER_MESSAGE });
|
|
62
62
|
});
|
|
63
63
|
|
|
64
|
-
expect(messageService.
|
|
64
|
+
expect(messageService.createNewMessage).not.toHaveBeenCalled();
|
|
65
65
|
expect(result.current.internal_coreProcessMessage).not.toHaveBeenCalled();
|
|
66
66
|
});
|
|
67
67
|
|
|
@@ -72,7 +72,7 @@ describe('chatMessage actions', () => {
|
|
|
72
72
|
await result.current.sendMessage({ message: TEST_CONTENT.EMPTY });
|
|
73
73
|
});
|
|
74
74
|
|
|
75
|
-
expect(messageService.
|
|
75
|
+
expect(messageService.createNewMessage).not.toHaveBeenCalled();
|
|
76
76
|
});
|
|
77
77
|
|
|
78
78
|
it('should not send when message is empty with empty files array', async () => {
|
|
@@ -82,7 +82,7 @@ describe('chatMessage actions', () => {
|
|
|
82
82
|
await result.current.sendMessage({ message: TEST_CONTENT.EMPTY, files: [] });
|
|
83
83
|
});
|
|
84
84
|
|
|
85
|
-
expect(messageService.
|
|
85
|
+
expect(messageService.createNewMessage).not.toHaveBeenCalled();
|
|
86
86
|
});
|
|
87
87
|
});
|
|
88
88
|
|
|
@@ -97,13 +97,13 @@ describe('chatMessage actions', () => {
|
|
|
97
97
|
});
|
|
98
98
|
});
|
|
99
99
|
|
|
100
|
-
expect(messageService.
|
|
100
|
+
expect(messageService.createNewMessage).toHaveBeenCalled();
|
|
101
101
|
expect(result.current.internal_coreProcessMessage).not.toHaveBeenCalled();
|
|
102
102
|
});
|
|
103
103
|
|
|
104
104
|
it('should handle message creation errors gracefully', async () => {
|
|
105
105
|
const { result } = renderHook(() => useChatStore());
|
|
106
|
-
vi.spyOn(messageService, '
|
|
106
|
+
vi.spyOn(messageService, 'createNewMessage').mockRejectedValue(
|
|
107
107
|
new Error('create message error'),
|
|
108
108
|
);
|
|
109
109
|
|
|
@@ -210,6 +210,8 @@ describe('chatMessage actions', () => {
|
|
|
210
210
|
|
|
211
211
|
describe('internal_coreProcessMessage', () => {
|
|
212
212
|
it('should process user message and generate AI response', async () => {
|
|
213
|
+
const mockMessages = [{ id: 'msg-1', content: 'test' }] as any;
|
|
214
|
+
|
|
213
215
|
act(() => {
|
|
214
216
|
useChatStore.setState({ internal_coreProcessMessage: realCoreProcessMessage });
|
|
215
217
|
});
|
|
@@ -227,8 +229,10 @@ describe('chatMessage actions', () => {
|
|
|
227
229
|
.mockResolvedValue({ isFunctionCall: false, content: 'AI response' });
|
|
228
230
|
|
|
229
231
|
const createMessageSpy = vi
|
|
230
|
-
.spyOn(messageService, '
|
|
231
|
-
.mockResolvedValue(TEST_IDS.ASSISTANT_MESSAGE_ID);
|
|
232
|
+
.spyOn(messageService, 'createNewMessage')
|
|
233
|
+
.mockResolvedValue({ id: TEST_IDS.ASSISTANT_MESSAGE_ID, messages: mockMessages });
|
|
234
|
+
|
|
235
|
+
const replaceMessagesSpy = vi.spyOn(result.current, 'replaceMessages');
|
|
232
236
|
|
|
233
237
|
await act(async () => {
|
|
234
238
|
await result.current.internal_coreProcessMessage([userMessage], userMessage.id);
|
|
@@ -245,7 +249,7 @@ describe('chatMessage actions', () => {
|
|
|
245
249
|
);
|
|
246
250
|
|
|
247
251
|
expect(fetchAIChatSpy).toHaveBeenCalled();
|
|
248
|
-
expect(
|
|
252
|
+
expect(replaceMessagesSpy).toHaveBeenCalledWith(mockMessages);
|
|
249
253
|
});
|
|
250
254
|
|
|
251
255
|
it('should handle RAG flow when ragQuery is provided', async () => {
|
|
@@ -268,7 +272,10 @@ describe('chatMessage actions', () => {
|
|
|
268
272
|
rewriteQuery: 'rewritten query',
|
|
269
273
|
});
|
|
270
274
|
|
|
271
|
-
vi.spyOn(messageService, '
|
|
275
|
+
vi.spyOn(messageService, 'createNewMessage').mockResolvedValue({
|
|
276
|
+
id: TEST_IDS.ASSISTANT_MESSAGE_ID,
|
|
277
|
+
messages: [],
|
|
278
|
+
});
|
|
272
279
|
|
|
273
280
|
await act(async () => {
|
|
274
281
|
await result.current.internal_coreProcessMessage([userMessage], userMessage.id, {
|
|
@@ -299,7 +306,7 @@ describe('chatMessage actions', () => {
|
|
|
299
306
|
.spyOn(result.current, 'internal_fetchAIChatMessage')
|
|
300
307
|
.mockResolvedValue({ isFunctionCall: false, content: '' });
|
|
301
308
|
|
|
302
|
-
vi.spyOn(messageService, '
|
|
309
|
+
vi.spyOn(messageService, 'createNewMessage').mockResolvedValue(undefined as any);
|
|
303
310
|
|
|
304
311
|
await act(async () => {
|
|
305
312
|
await result.current.internal_coreProcessMessage([userMessage], userMessage.id);
|
|
@@ -131,7 +131,7 @@ describe('generateAIChatV2 actions', () => {
|
|
|
131
131
|
await result.current.sendMessage({ message: TEST_CONTENT.USER_MESSAGE });
|
|
132
132
|
});
|
|
133
133
|
|
|
134
|
-
expect(messageService.
|
|
134
|
+
expect(messageService.createNewMessage).not.toHaveBeenCalled();
|
|
135
135
|
expect(result.current.internal_execAgentRuntime).not.toHaveBeenCalled();
|
|
136
136
|
});
|
|
137
137
|
|
|
@@ -142,7 +142,7 @@ describe('generateAIChatV2 actions', () => {
|
|
|
142
142
|
await result.current.sendMessage({ message: TEST_CONTENT.EMPTY });
|
|
143
143
|
});
|
|
144
144
|
|
|
145
|
-
expect(messageService.
|
|
145
|
+
expect(messageService.createNewMessage).not.toHaveBeenCalled();
|
|
146
146
|
});
|
|
147
147
|
|
|
148
148
|
it('should not send when message is empty with empty files array', async () => {
|
|
@@ -152,7 +152,7 @@ describe('generateAIChatV2 actions', () => {
|
|
|
152
152
|
await result.current.sendMessage({ message: TEST_CONTENT.EMPTY, files: [] });
|
|
153
153
|
});
|
|
154
154
|
|
|
155
|
-
expect(messageService.
|
|
155
|
+
expect(messageService.createNewMessage).not.toHaveBeenCalled();
|
|
156
156
|
});
|
|
157
157
|
});
|
|
158
158
|
|
|
@@ -312,7 +312,7 @@ describe('generateAIChatV2 actions', () => {
|
|
|
312
312
|
});
|
|
313
313
|
});
|
|
314
314
|
|
|
315
|
-
expect(messageService.
|
|
315
|
+
expect(messageService.createNewMessage).toHaveBeenCalled();
|
|
316
316
|
expect(result.current.internal_execAgentRuntime).not.toHaveBeenCalled();
|
|
317
317
|
});
|
|
318
318
|
|
|
@@ -58,8 +58,8 @@ export const createMockAbortController = () => {
|
|
|
58
58
|
*/
|
|
59
59
|
export const spyOnMessageService = () => {
|
|
60
60
|
const createMessageSpy = vi
|
|
61
|
-
.spyOn(messageService, '
|
|
62
|
-
.mockResolvedValue(TEST_IDS.NEW_MESSAGE_ID);
|
|
61
|
+
.spyOn(messageService, 'createNewMessage')
|
|
62
|
+
.mockResolvedValue({ id: TEST_IDS.NEW_MESSAGE_ID, messages: [] });
|
|
63
63
|
const updateMessageSpy = vi
|
|
64
64
|
.spyOn(messageService, 'updateMessage')
|
|
65
65
|
.mockResolvedValue({ messages: [], success: true });
|
|
@@ -220,9 +220,10 @@ export const generateAIChat: StateCreator<
|
|
|
220
220
|
ragQueryId,
|
|
221
221
|
};
|
|
222
222
|
|
|
223
|
-
const
|
|
223
|
+
const result = await get().internal_createMessage(assistantMessage);
|
|
224
224
|
|
|
225
|
-
if (!
|
|
225
|
+
if (!result) return;
|
|
226
|
+
const assistantId = result.id;
|
|
226
227
|
|
|
227
228
|
// 3. place a search with the search working model if this model is not support tool use
|
|
228
229
|
const aiInfraStoreState = getAiInfraStoreState();
|
|
@@ -659,7 +659,7 @@ export const generateAIChatV2: StateCreator<
|
|
|
659
659
|
groupId: groupMessage.groupId, // Propagate groupId from parent message for group chat
|
|
660
660
|
};
|
|
661
661
|
|
|
662
|
-
const result = await get().
|
|
662
|
+
const result = await get().internal_createMessage(toolMessage);
|
|
663
663
|
|
|
664
664
|
if (!result) {
|
|
665
665
|
log('[triggerToolsCalling] Failed to create tool message for %s', payload.identifier);
|
|
@@ -768,7 +768,7 @@ export const generateAIChatV2: StateCreator<
|
|
|
768
768
|
topicId: get().activeTopicId,
|
|
769
769
|
});
|
|
770
770
|
|
|
771
|
-
const result = await get().
|
|
771
|
+
const result = await get().internal_createMessage(assistantMessage, { groupMessageId });
|
|
772
772
|
|
|
773
773
|
if (!result) {
|
|
774
774
|
log('[callToolFollowAssistantMessage] Failed to create assistant message');
|
|
@@ -294,7 +294,7 @@ export const chatAiGroupChat: StateCreator<
|
|
|
294
294
|
targetId: targetMemberId,
|
|
295
295
|
};
|
|
296
296
|
|
|
297
|
-
const
|
|
297
|
+
const result = await internal_createMessage(userMessage);
|
|
298
298
|
|
|
299
299
|
// if only add user message, then stop
|
|
300
300
|
if (onlyAddUserMessage) {
|
|
@@ -302,6 +302,9 @@ export const chatAiGroupChat: StateCreator<
|
|
|
302
302
|
return;
|
|
303
303
|
}
|
|
304
304
|
|
|
305
|
+
if (!result) return;
|
|
306
|
+
const messageId = result.id;
|
|
307
|
+
|
|
305
308
|
if (messageId) {
|
|
306
309
|
// Use the specific group's config rather than relying on active session
|
|
307
310
|
const groupConfig = selectGroupConfig(groupId);
|
|
@@ -668,7 +671,9 @@ export const chatAiGroupChat: StateCreator<
|
|
|
668
671
|
|
|
669
672
|
console.log('DEBUG: Creating agent message with:', agentMessage);
|
|
670
673
|
|
|
671
|
-
const
|
|
674
|
+
const result = await internal_createMessage(agentMessage);
|
|
675
|
+
if (!result) return;
|
|
676
|
+
const assistantId = result.id;
|
|
672
677
|
|
|
673
678
|
const systemMessage: UIChatMessage = {
|
|
674
679
|
id: 'group-system',
|
|
@@ -106,16 +106,16 @@ export const searchSlice: StateCreator<
|
|
|
106
106
|
});
|
|
107
107
|
};
|
|
108
108
|
|
|
109
|
-
const [
|
|
109
|
+
const [result] = await Promise.all([
|
|
110
110
|
// 1. 添加 tool message
|
|
111
111
|
internal_createMessage(toolMessage),
|
|
112
112
|
// 2. 将这条 tool call message 插入到 ai 消息的 tools 中
|
|
113
113
|
addToolItem(),
|
|
114
114
|
]);
|
|
115
|
-
if (!
|
|
115
|
+
if (!result) return;
|
|
116
116
|
|
|
117
117
|
// 将新创建的 tool message 激活
|
|
118
|
-
openToolUI(
|
|
118
|
+
openToolUI(result.id, message.plugin.identifier);
|
|
119
119
|
},
|
|
120
120
|
|
|
121
121
|
search: async (id, params, aiSummary = true) => {
|
|
@@ -25,7 +25,7 @@ vi.mock('@/services/message', () => ({
|
|
|
25
25
|
removeMessage: vi.fn(),
|
|
26
26
|
removeMessagesByAssistant: vi.fn(),
|
|
27
27
|
removeMessages: vi.fn(() => Promise.resolve()),
|
|
28
|
-
|
|
28
|
+
createNewMessage: vi.fn(() => Promise.resolve({ id: 'new-message-id', messages: [] })),
|
|
29
29
|
updateMessage: vi.fn(),
|
|
30
30
|
removeAllMessages: vi.fn(() => Promise.resolve()),
|
|
31
31
|
},
|
|
@@ -71,7 +71,7 @@ describe('chatMessage actions', () => {
|
|
|
71
71
|
await result.current.addAIMessage();
|
|
72
72
|
});
|
|
73
73
|
|
|
74
|
-
expect(messageService.
|
|
74
|
+
expect(messageService.createNewMessage).not.toHaveBeenCalled();
|
|
75
75
|
expect(updateInputMessageSpy).not.toHaveBeenCalled();
|
|
76
76
|
});
|
|
77
77
|
|
|
@@ -84,7 +84,7 @@ describe('chatMessage actions', () => {
|
|
|
84
84
|
await result.current.addAIMessage();
|
|
85
85
|
});
|
|
86
86
|
|
|
87
|
-
expect(messageService.
|
|
87
|
+
expect(messageService.createNewMessage).toHaveBeenCalledWith({
|
|
88
88
|
content: inputMessage,
|
|
89
89
|
role: 'assistant',
|
|
90
90
|
sessionId: mockState.activeId,
|
|
@@ -113,7 +113,7 @@ describe('chatMessage actions', () => {
|
|
|
113
113
|
await result.current.addUserMessage({ message: 'test message' });
|
|
114
114
|
});
|
|
115
115
|
|
|
116
|
-
expect(messageService.
|
|
116
|
+
expect(messageService.createNewMessage).not.toHaveBeenCalled();
|
|
117
117
|
expect(updateInputMessageSpy).not.toHaveBeenCalled();
|
|
118
118
|
});
|
|
119
119
|
|
|
@@ -130,7 +130,7 @@ describe('chatMessage actions', () => {
|
|
|
130
130
|
await result.current.addUserMessage({ message, fileList });
|
|
131
131
|
});
|
|
132
132
|
|
|
133
|
-
expect(messageService.
|
|
133
|
+
expect(messageService.createNewMessage).toHaveBeenCalledWith({
|
|
134
134
|
content: message,
|
|
135
135
|
files: fileList,
|
|
136
136
|
role: 'user',
|
|
@@ -154,7 +154,7 @@ describe('chatMessage actions', () => {
|
|
|
154
154
|
await result.current.addUserMessage({ message });
|
|
155
155
|
});
|
|
156
156
|
|
|
157
|
-
expect(messageService.
|
|
157
|
+
expect(messageService.createNewMessage).toHaveBeenCalledWith({
|
|
158
158
|
content: message,
|
|
159
159
|
files: undefined,
|
|
160
160
|
role: 'user',
|
|
@@ -184,7 +184,7 @@ describe('chatMessage actions', () => {
|
|
|
184
184
|
await result.current.addUserMessage({ message });
|
|
185
185
|
});
|
|
186
186
|
|
|
187
|
-
expect(messageService.
|
|
187
|
+
expect(messageService.createNewMessage).toHaveBeenCalledWith({
|
|
188
188
|
content: message,
|
|
189
189
|
files: undefined,
|
|
190
190
|
role: 'user',
|
|
@@ -200,6 +200,15 @@ describe('chatMessage actions', () => {
|
|
|
200
200
|
const { result } = renderHook(() => useChatStore());
|
|
201
201
|
const messageId = 'message-id';
|
|
202
202
|
const deleteSpy = vi.spyOn(result.current, 'deleteMessage');
|
|
203
|
+
const mockMessages = [{ id: 'other-message' }] as any;
|
|
204
|
+
|
|
205
|
+
// Mock the service to return messages
|
|
206
|
+
(messageService.removeMessages as Mock).mockResolvedValue({
|
|
207
|
+
success: true,
|
|
208
|
+
messages: mockMessages,
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
const replaceMessagesSpy = vi.spyOn(result.current, 'replaceMessages');
|
|
203
212
|
|
|
204
213
|
act(() => {
|
|
205
214
|
useChatStore.setState({
|
|
@@ -215,13 +224,22 @@ describe('chatMessage actions', () => {
|
|
|
215
224
|
});
|
|
216
225
|
|
|
217
226
|
expect(deleteSpy).toHaveBeenCalledWith(messageId);
|
|
218
|
-
expect(
|
|
227
|
+
expect(replaceMessagesSpy).toHaveBeenCalledWith(mockMessages);
|
|
219
228
|
});
|
|
220
229
|
|
|
221
230
|
it('deleteMessage should remove messages with tools', async () => {
|
|
222
231
|
const { result } = renderHook(() => useChatStore());
|
|
223
232
|
const messageId = 'message-id';
|
|
224
233
|
const removeMessagesSpy = vi.spyOn(messageService, 'removeMessages');
|
|
234
|
+
const mockMessages = [{ id: 'remaining-message' }] as any;
|
|
235
|
+
|
|
236
|
+
// Mock the service to return messages
|
|
237
|
+
(messageService.removeMessages as Mock).mockResolvedValue({
|
|
238
|
+
success: true,
|
|
239
|
+
messages: mockMessages,
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
const replaceMessagesSpy = vi.spyOn(result.current, 'replaceMessages');
|
|
225
243
|
|
|
226
244
|
act(() => {
|
|
227
245
|
useChatStore.setState({
|
|
@@ -240,8 +258,120 @@ describe('chatMessage actions', () => {
|
|
|
240
258
|
await result.current.deleteMessage(messageId);
|
|
241
259
|
});
|
|
242
260
|
|
|
243
|
-
expect(removeMessagesSpy).toHaveBeenCalledWith([messageId, '2', '3']
|
|
244
|
-
|
|
261
|
+
expect(removeMessagesSpy).toHaveBeenCalledWith([messageId, '2', '3'], {
|
|
262
|
+
sessionId: 'session-id',
|
|
263
|
+
topicId: undefined,
|
|
264
|
+
});
|
|
265
|
+
expect(replaceMessagesSpy).toHaveBeenCalledWith(mockMessages);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it('deleteMessage should remove group message with all children', async () => {
|
|
269
|
+
const { result } = renderHook(() => useChatStore());
|
|
270
|
+
const groupMessageId = 'group-message-id';
|
|
271
|
+
const removeMessagesSpy = vi.spyOn(messageService, 'removeMessages');
|
|
272
|
+
const mockMessages = [{ id: 'remaining-message' }] as any;
|
|
273
|
+
|
|
274
|
+
// Mock the service to return messages
|
|
275
|
+
(messageService.removeMessages as Mock).mockResolvedValue({
|
|
276
|
+
success: true,
|
|
277
|
+
messages: mockMessages,
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
const replaceMessagesSpy = vi.spyOn(result.current, 'replaceMessages');
|
|
281
|
+
|
|
282
|
+
act(() => {
|
|
283
|
+
useChatStore.setState({
|
|
284
|
+
activeId: 'session-id',
|
|
285
|
+
activeTopicId: undefined,
|
|
286
|
+
messagesMap: {
|
|
287
|
+
[messageMapKey('session-id')]: [
|
|
288
|
+
{ id: groupMessageId, role: 'group', content: 'Group message' } as UIChatMessage,
|
|
289
|
+
{
|
|
290
|
+
id: 'child-1',
|
|
291
|
+
parentId: groupMessageId,
|
|
292
|
+
role: 'assistant',
|
|
293
|
+
content: 'Child 1',
|
|
294
|
+
} as UIChatMessage,
|
|
295
|
+
{
|
|
296
|
+
id: 'child-2',
|
|
297
|
+
parentId: groupMessageId,
|
|
298
|
+
role: 'assistant',
|
|
299
|
+
content: 'Child 2',
|
|
300
|
+
} as UIChatMessage,
|
|
301
|
+
{ id: 'other-message', role: 'user', content: 'Other' } as UIChatMessage,
|
|
302
|
+
],
|
|
303
|
+
},
|
|
304
|
+
});
|
|
305
|
+
});
|
|
306
|
+
await act(async () => {
|
|
307
|
+
await result.current.deleteMessage(groupMessageId);
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
expect(removeMessagesSpy).toHaveBeenCalledWith([groupMessageId, 'child-1', 'child-2'], {
|
|
311
|
+
sessionId: 'session-id',
|
|
312
|
+
topicId: undefined,
|
|
313
|
+
});
|
|
314
|
+
expect(replaceMessagesSpy).toHaveBeenCalledWith(mockMessages);
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
it('deleteMessage should remove group message with children that have tool calls', async () => {
|
|
318
|
+
const { result } = renderHook(() => useChatStore());
|
|
319
|
+
const groupMessageId = 'group-message-id';
|
|
320
|
+
const removeMessagesSpy = vi.spyOn(messageService, 'removeMessages');
|
|
321
|
+
const mockMessages = [{ id: 'remaining-message' }] as any;
|
|
322
|
+
|
|
323
|
+
// Mock the service to return messages
|
|
324
|
+
(messageService.removeMessages as Mock).mockResolvedValue({
|
|
325
|
+
success: true,
|
|
326
|
+
messages: mockMessages,
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
const replaceMessagesSpy = vi.spyOn(result.current, 'replaceMessages');
|
|
330
|
+
|
|
331
|
+
act(() => {
|
|
332
|
+
useChatStore.setState({
|
|
333
|
+
activeId: 'session-id',
|
|
334
|
+
activeTopicId: undefined,
|
|
335
|
+
messagesMap: {
|
|
336
|
+
[messageMapKey('session-id')]: [
|
|
337
|
+
{ id: groupMessageId, role: 'group', content: 'Group message' } as UIChatMessage,
|
|
338
|
+
{
|
|
339
|
+
id: 'child-1',
|
|
340
|
+
parentId: groupMessageId,
|
|
341
|
+
role: 'assistant',
|
|
342
|
+
content: 'Child with tools',
|
|
343
|
+
tools: [{ id: 'tool1' }],
|
|
344
|
+
} as UIChatMessage,
|
|
345
|
+
{
|
|
346
|
+
id: 'tool-result-1',
|
|
347
|
+
tool_call_id: 'tool1',
|
|
348
|
+
role: 'tool',
|
|
349
|
+
content: 'Tool result',
|
|
350
|
+
} as UIChatMessage,
|
|
351
|
+
{
|
|
352
|
+
id: 'child-2',
|
|
353
|
+
parentId: groupMessageId,
|
|
354
|
+
role: 'assistant',
|
|
355
|
+
content: 'Child 2',
|
|
356
|
+
} as UIChatMessage,
|
|
357
|
+
{ id: 'other-message', role: 'user', content: 'Other' } as UIChatMessage,
|
|
358
|
+
],
|
|
359
|
+
},
|
|
360
|
+
});
|
|
361
|
+
});
|
|
362
|
+
await act(async () => {
|
|
363
|
+
await result.current.deleteMessage(groupMessageId);
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
// Should delete group message + all children + tool results of children
|
|
367
|
+
expect(removeMessagesSpy).toHaveBeenCalledWith(
|
|
368
|
+
[groupMessageId, 'child-1', 'child-2', 'tool-result-1'],
|
|
369
|
+
{
|
|
370
|
+
sessionId: 'session-id',
|
|
371
|
+
topicId: undefined,
|
|
372
|
+
},
|
|
373
|
+
);
|
|
374
|
+
expect(replaceMessagesSpy).toHaveBeenCalledWith(mockMessages);
|
|
245
375
|
});
|
|
246
376
|
});
|
|
247
377
|
|
|
@@ -309,10 +439,16 @@ describe('chatMessage actions', () => {
|
|
|
309
439
|
});
|
|
310
440
|
|
|
311
441
|
expect(removeMessageSpy).toHaveBeenCalled();
|
|
312
|
-
expect(updateMessageSpy).toHaveBeenCalledWith(
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
442
|
+
expect(updateMessageSpy).toHaveBeenCalledWith(
|
|
443
|
+
'message-id',
|
|
444
|
+
{
|
|
445
|
+
tools: [{ id: 'tool2' }],
|
|
446
|
+
},
|
|
447
|
+
{
|
|
448
|
+
sessionId: 'session-id',
|
|
449
|
+
topicId: undefined,
|
|
450
|
+
},
|
|
451
|
+
);
|
|
316
452
|
});
|
|
317
453
|
});
|
|
318
454
|
|
|
@@ -320,13 +456,14 @@ describe('chatMessage actions', () => {
|
|
|
320
456
|
it('clearAllMessages should remove all messages', async () => {
|
|
321
457
|
const { result } = renderHook(() => useChatStore());
|
|
322
458
|
const clearAllSpy = vi.spyOn(result.current, 'clearAllMessages');
|
|
459
|
+
const replaceMessagesSpy = vi.spyOn(result.current, 'replaceMessages');
|
|
323
460
|
|
|
324
461
|
await act(async () => {
|
|
325
462
|
await result.current.clearAllMessages();
|
|
326
463
|
});
|
|
327
464
|
|
|
328
465
|
expect(clearAllSpy).toHaveBeenCalled();
|
|
329
|
-
expect(
|
|
466
|
+
expect(replaceMessagesSpy).toHaveBeenCalledWith([]);
|
|
330
467
|
});
|
|
331
468
|
});
|
|
332
469
|
|