@lobehub/chat 1.2.5 → 1.2.7
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 +50 -0
- package/package.json +1 -1
- package/src/app/(main)/chat/(workspace)/@portal/features/ArtifactUI/Footer.tsx +1 -1
- package/src/app/(main)/chat/(workspace)/@portal/features/Header.tsx +1 -1
- package/src/app/(main)/chat/(workspace)/_layout/Mobile/PortalModal.tsx +35 -0
- package/src/app/(main)/chat/(workspace)/_layout/Mobile/index.tsx +3 -1
- package/src/database/client/models/__tests__/message.test.ts +13 -0
- package/src/database/client/models/message.ts +4 -0
- package/src/database/server/models/__tests__/message.test.ts +103 -0
- package/src/database/server/models/message.ts +13 -6
- package/src/features/Conversation/Actions/Tool.tsx +12 -6
- package/src/features/Conversation/Messages/Tool/Inspector/index.tsx +25 -19
- package/src/features/Conversation/Messages/Tool/Inspector/style.ts +9 -0
- package/src/layout/AuthProvider/Clerk/index.tsx +3 -3
- package/src/server/routers/lambda/message.ts +7 -1
- package/src/services/message/client.test.ts +16 -2
- package/src/services/message/client.ts +5 -1
- package/src/services/message/server.ts +7 -2
- package/src/services/message/type.ts +2 -1
- package/src/store/chat/slices/message/action.test.ts +144 -0
- package/src/store/chat/slices/message/action.ts +111 -64
- package/src/store/chat/slices/message/reducer.test.ts +200 -1
- package/src/store/chat/slices/message/reducer.ts +62 -2
- package/src/store/chat/slices/plugin/action.test.ts +42 -0
- package/src/store/chat/slices/plugin/action.ts +46 -0
- package/src/store/chat/slices/portal/action.test.ts +6 -6
- package/src/store/chat/slices/portal/action.ts +3 -3
- package/src/store/chat/slices/topic/action.test.ts +3 -2
- package/src/store/chat/slices/topic/action.ts +1 -1
- package/src/store/global/action.test.ts +13 -0
- package/src/store/global/action.ts +7 -0
- package/src/store/global/initialState.ts +1 -0
- package/src/store/global/selectors.ts +2 -0
|
@@ -2,7 +2,7 @@ import isEqual from 'fast-deep-equal';
|
|
|
2
2
|
import { produce } from 'immer';
|
|
3
3
|
|
|
4
4
|
import { CreateMessageParams } from '@/services/message';
|
|
5
|
-
import { ChatMessage, ChatPluginPayload } from '@/types/message';
|
|
5
|
+
import { ChatMessage, ChatPluginPayload, ChatToolPayload } from '@/types/message';
|
|
6
6
|
import { merge } from '@/utils/merge';
|
|
7
7
|
|
|
8
8
|
interface UpdateMessages {
|
|
@@ -16,11 +16,17 @@ interface CreateMessage {
|
|
|
16
16
|
type: 'createMessage';
|
|
17
17
|
value: CreateMessageParams;
|
|
18
18
|
}
|
|
19
|
+
|
|
19
20
|
interface DeleteMessage {
|
|
20
21
|
id: string;
|
|
21
22
|
type: 'deleteMessage';
|
|
22
23
|
}
|
|
23
24
|
|
|
25
|
+
interface DeleteMessages {
|
|
26
|
+
ids: string[];
|
|
27
|
+
type: 'deleteMessages';
|
|
28
|
+
}
|
|
29
|
+
|
|
24
30
|
interface UpdatePluginState {
|
|
25
31
|
id: string;
|
|
26
32
|
key: string;
|
|
@@ -41,6 +47,17 @@ interface UpdateMessageTools {
|
|
|
41
47
|
value: Partial<ChatPluginPayload>;
|
|
42
48
|
}
|
|
43
49
|
|
|
50
|
+
interface AddMessageTool {
|
|
51
|
+
id: string;
|
|
52
|
+
type: 'addMessageTool';
|
|
53
|
+
value: ChatToolPayload;
|
|
54
|
+
}
|
|
55
|
+
interface DeleteMessageTool {
|
|
56
|
+
id: string;
|
|
57
|
+
tool_call_id: string;
|
|
58
|
+
type: 'deleteMessageTool';
|
|
59
|
+
}
|
|
60
|
+
|
|
44
61
|
interface UpdateMessageExtra {
|
|
45
62
|
id: string;
|
|
46
63
|
key: string;
|
|
@@ -55,7 +72,10 @@ export type MessageDispatch =
|
|
|
55
72
|
| UpdateMessageExtra
|
|
56
73
|
| DeleteMessage
|
|
57
74
|
| UpdateMessagePlugin
|
|
58
|
-
| UpdateMessageTools
|
|
75
|
+
| UpdateMessageTools
|
|
76
|
+
| AddMessageTool
|
|
77
|
+
| DeleteMessageTool
|
|
78
|
+
| DeleteMessages;
|
|
59
79
|
|
|
60
80
|
export const messagesReducer = (state: ChatMessage[], payload: MessageDispatch): ChatMessage[] => {
|
|
61
81
|
switch (payload.type) {
|
|
@@ -116,6 +136,37 @@ export const messagesReducer = (state: ChatMessage[], payload: MessageDispatch):
|
|
|
116
136
|
});
|
|
117
137
|
}
|
|
118
138
|
|
|
139
|
+
case 'addMessageTool': {
|
|
140
|
+
return produce(state, (draftState) => {
|
|
141
|
+
const { id, value } = payload;
|
|
142
|
+
const message = draftState.find((i) => i.id === id);
|
|
143
|
+
if (!message || message.role !== 'assistant') return;
|
|
144
|
+
|
|
145
|
+
if (!message.tools) {
|
|
146
|
+
message.tools = [value];
|
|
147
|
+
} else {
|
|
148
|
+
const index = message.tools.findIndex((tool) => tool.id === value.id);
|
|
149
|
+
|
|
150
|
+
if (index > 0) return;
|
|
151
|
+
message.tools.push(value);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
message.updatedAt = Date.now();
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
case 'deleteMessageTool': {
|
|
159
|
+
return produce(state, (draftState) => {
|
|
160
|
+
const { id, tool_call_id } = payload;
|
|
161
|
+
const message = draftState.find((i) => i.id === id);
|
|
162
|
+
if (!message || message.role !== 'assistant' || !message.tools) return;
|
|
163
|
+
|
|
164
|
+
message.tools = message.tools.filter((tool) => tool.id !== tool_call_id);
|
|
165
|
+
|
|
166
|
+
message.updatedAt = Date.now();
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
119
170
|
case 'updateMessageTools': {
|
|
120
171
|
return produce(state, (draftState) => {
|
|
121
172
|
const { id, value, tool_call_id } = payload;
|
|
@@ -147,6 +198,15 @@ export const messagesReducer = (state: ChatMessage[], payload: MessageDispatch):
|
|
|
147
198
|
if (index >= 0) draft.splice(index, 1);
|
|
148
199
|
});
|
|
149
200
|
}
|
|
201
|
+
case 'deleteMessages': {
|
|
202
|
+
return produce(state, (draft) => {
|
|
203
|
+
const { ids } = payload;
|
|
204
|
+
|
|
205
|
+
return draft.filter((item) => {
|
|
206
|
+
return !ids.includes(item.id);
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
}
|
|
150
210
|
default: {
|
|
151
211
|
throw new Error('暂未实现的 type,请检查 reducer');
|
|
152
212
|
}
|
|
@@ -964,4 +964,46 @@ describe('ChatPluginAction', () => {
|
|
|
964
964
|
expect(result.current.refreshMessages).toHaveBeenCalled();
|
|
965
965
|
});
|
|
966
966
|
});
|
|
967
|
+
|
|
968
|
+
describe('internal_addToolToAssistantMessage', () => {
|
|
969
|
+
it('should add too to assistant messages', async () => {
|
|
970
|
+
const { result } = renderHook(() => useChatStore());
|
|
971
|
+
|
|
972
|
+
const messageId = 'message-id';
|
|
973
|
+
const toolCallId = 'tool-call-id';
|
|
974
|
+
const identifier = 'plugin';
|
|
975
|
+
|
|
976
|
+
const refreshToUpdateMessageToolsSpy = vi.spyOn(
|
|
977
|
+
result.current,
|
|
978
|
+
'internal_refreshToUpdateMessageTools',
|
|
979
|
+
);
|
|
980
|
+
|
|
981
|
+
const assistantMessage = {
|
|
982
|
+
id: messageId,
|
|
983
|
+
role: 'assistant',
|
|
984
|
+
content: 'Assistant content',
|
|
985
|
+
tools: [{ identifier: identifier, arguments: '{"oldKey":"oldValue"}', id: toolCallId }],
|
|
986
|
+
} as ChatMessage;
|
|
987
|
+
|
|
988
|
+
act(() => {
|
|
989
|
+
useChatStore.setState({
|
|
990
|
+
activeId: 'anbccfdd',
|
|
991
|
+
messagesMap: { [messageMapKey('anbccfdd')]: [assistantMessage] },
|
|
992
|
+
refreshMessages: vi.fn(),
|
|
993
|
+
});
|
|
994
|
+
});
|
|
995
|
+
|
|
996
|
+
await act(async () => {
|
|
997
|
+
await result.current.internal_addToolToAssistantMessage(messageId, {
|
|
998
|
+
identifier,
|
|
999
|
+
arguments: '{"oldKey":"oldValue"}',
|
|
1000
|
+
id: 'newId',
|
|
1001
|
+
apiName: 'test',
|
|
1002
|
+
type: 'default',
|
|
1003
|
+
});
|
|
1004
|
+
});
|
|
1005
|
+
|
|
1006
|
+
expect(refreshToUpdateMessageToolsSpy).toHaveBeenCalledWith(messageId);
|
|
1007
|
+
});
|
|
1008
|
+
});
|
|
967
1009
|
});
|
|
@@ -43,6 +43,13 @@ export interface ChatPluginAction {
|
|
|
43
43
|
updatePluginState: (id: string, value: any) => Promise<void>;
|
|
44
44
|
updatePluginArguments: <T = any>(id: string, value: T) => Promise<void>;
|
|
45
45
|
|
|
46
|
+
internal_addToolToAssistantMessage: (id: string, tool: ChatToolPayload) => Promise<void>;
|
|
47
|
+
internal_removeToolToAssistantMessage: (id: string, tool_call_id?: string) => Promise<void>;
|
|
48
|
+
/**
|
|
49
|
+
* use the optimistic update value to update the message tools to database
|
|
50
|
+
*/
|
|
51
|
+
internal_refreshToUpdateMessageTools: (id: string) => Promise<void>;
|
|
52
|
+
|
|
46
53
|
internal_callPluginApi: (id: string, payload: ChatToolPayload) => Promise<string | undefined>;
|
|
47
54
|
internal_invokeDifferentTypePlugin: (id: string, payload: ChatToolPayload) => Promise<any>;
|
|
48
55
|
internal_togglePluginApiCalling: (
|
|
@@ -281,6 +288,45 @@ export const chatPlugin: StateCreator<
|
|
|
281
288
|
await refreshMessages();
|
|
282
289
|
},
|
|
283
290
|
|
|
291
|
+
internal_addToolToAssistantMessage: async (id, tool) => {
|
|
292
|
+
const assistantMessage = chatSelectors.getMessageById(id)(get());
|
|
293
|
+
if (!assistantMessage) return;
|
|
294
|
+
|
|
295
|
+
const { internal_dispatchMessage, internal_refreshToUpdateMessageTools } = get();
|
|
296
|
+
internal_dispatchMessage({
|
|
297
|
+
type: 'addMessageTool',
|
|
298
|
+
value: tool,
|
|
299
|
+
id: assistantMessage.id,
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
await internal_refreshToUpdateMessageTools(id);
|
|
303
|
+
},
|
|
304
|
+
|
|
305
|
+
internal_removeToolToAssistantMessage: async (id, tool_call_id) => {
|
|
306
|
+
const message = chatSelectors.getMessageById(id)(get());
|
|
307
|
+
if (!message || !tool_call_id) return;
|
|
308
|
+
|
|
309
|
+
const { internal_dispatchMessage, internal_refreshToUpdateMessageTools } = get();
|
|
310
|
+
|
|
311
|
+
// optimistic update
|
|
312
|
+
internal_dispatchMessage({ type: 'deleteMessageTool', tool_call_id, id: message.id });
|
|
313
|
+
|
|
314
|
+
// update the message tools
|
|
315
|
+
await internal_refreshToUpdateMessageTools(id);
|
|
316
|
+
},
|
|
317
|
+
internal_refreshToUpdateMessageTools: async (id) => {
|
|
318
|
+
const message = chatSelectors.getMessageById(id)(get());
|
|
319
|
+
if (!message || !message.tools) return;
|
|
320
|
+
|
|
321
|
+
const { internal_toggleMessageLoading, refreshMessages } = get();
|
|
322
|
+
|
|
323
|
+
internal_toggleMessageLoading(true, id);
|
|
324
|
+
await messageService.updateMessage(id, { tools: message.tools });
|
|
325
|
+
internal_toggleMessageLoading(false, id);
|
|
326
|
+
|
|
327
|
+
await refreshMessages();
|
|
328
|
+
},
|
|
329
|
+
|
|
284
330
|
internal_callPluginApi: async (id, payload) => {
|
|
285
331
|
const { internal_updateMessageContent, refreshMessages, internal_togglePluginApiCalling } =
|
|
286
332
|
get();
|
|
@@ -48,7 +48,7 @@ describe('chatDockSlice', () => {
|
|
|
48
48
|
const { result } = renderHook(() => useChatStore());
|
|
49
49
|
|
|
50
50
|
act(() => {
|
|
51
|
-
result.current.
|
|
51
|
+
result.current.togglePortal(true);
|
|
52
52
|
});
|
|
53
53
|
|
|
54
54
|
expect(result.current.showPortal).toBe(true);
|
|
@@ -72,13 +72,13 @@ describe('chatDockSlice', () => {
|
|
|
72
72
|
expect(result.current.showPortal).toBe(false);
|
|
73
73
|
|
|
74
74
|
act(() => {
|
|
75
|
-
result.current.
|
|
75
|
+
result.current.togglePortal();
|
|
76
76
|
});
|
|
77
77
|
|
|
78
78
|
expect(result.current.showPortal).toBe(true);
|
|
79
79
|
|
|
80
80
|
act(() => {
|
|
81
|
-
result.current.
|
|
81
|
+
result.current.togglePortal();
|
|
82
82
|
});
|
|
83
83
|
|
|
84
84
|
expect(result.current.showPortal).toBe(false);
|
|
@@ -88,19 +88,19 @@ describe('chatDockSlice', () => {
|
|
|
88
88
|
const { result } = renderHook(() => useChatStore());
|
|
89
89
|
|
|
90
90
|
act(() => {
|
|
91
|
-
result.current.
|
|
91
|
+
result.current.togglePortal(true);
|
|
92
92
|
});
|
|
93
93
|
|
|
94
94
|
expect(result.current.showPortal).toBe(true);
|
|
95
95
|
|
|
96
96
|
act(() => {
|
|
97
|
-
result.current.
|
|
97
|
+
result.current.togglePortal(false);
|
|
98
98
|
});
|
|
99
99
|
|
|
100
100
|
expect(result.current.showPortal).toBe(false);
|
|
101
101
|
|
|
102
102
|
act(() => {
|
|
103
|
-
result.current.
|
|
103
|
+
result.current.togglePortal(true);
|
|
104
104
|
});
|
|
105
105
|
|
|
106
106
|
expect(result.current.showPortal).toBe(true);
|
|
@@ -5,7 +5,7 @@ import { ChatStore } from '@/store/chat/store';
|
|
|
5
5
|
export interface ChatPortalAction {
|
|
6
6
|
closeToolUI: () => void;
|
|
7
7
|
openToolUI: (messageId: string, identifier: string) => void;
|
|
8
|
-
|
|
8
|
+
togglePortal: (open?: boolean) => void;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
export const chatPortalSlice: StateCreator<
|
|
@@ -19,12 +19,12 @@ export const chatPortalSlice: StateCreator<
|
|
|
19
19
|
},
|
|
20
20
|
openToolUI: (id, identifier) => {
|
|
21
21
|
if (!get().showPortal) {
|
|
22
|
-
get().
|
|
22
|
+
get().togglePortal(true);
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
set({ portalToolMessage: { id, identifier } }, false, 'openToolUI');
|
|
26
26
|
},
|
|
27
|
-
|
|
27
|
+
togglePortal: (open) => {
|
|
28
28
|
const showInspector = open === undefined ? !get().showPortal : open;
|
|
29
29
|
set({ showPortal: showInspector }, false, 'toggleInspector');
|
|
30
30
|
},
|
|
@@ -33,6 +33,7 @@ vi.mock('@/services/topic', () => ({
|
|
|
33
33
|
vi.mock('@/services/message', () => ({
|
|
34
34
|
messageService: {
|
|
35
35
|
removeMessages: vi.fn(),
|
|
36
|
+
removeMessagesByAssistant: vi.fn(),
|
|
36
37
|
getMessages: vi.fn(),
|
|
37
38
|
},
|
|
38
39
|
}));
|
|
@@ -351,7 +352,7 @@ describe('topic action', () => {
|
|
|
351
352
|
await result.current.removeTopic(topicId);
|
|
352
353
|
});
|
|
353
354
|
|
|
354
|
-
expect(messageService.
|
|
355
|
+
expect(messageService.removeMessagesByAssistant).toHaveBeenCalledWith(activeId, topicId);
|
|
355
356
|
expect(topicService.removeTopic).toHaveBeenCalledWith(topicId);
|
|
356
357
|
expect(refreshTopicSpy).toHaveBeenCalled();
|
|
357
358
|
expect(switchTopicSpy).toHaveBeenCalled();
|
|
@@ -372,7 +373,7 @@ describe('topic action', () => {
|
|
|
372
373
|
await result.current.removeTopic(topicId);
|
|
373
374
|
});
|
|
374
375
|
|
|
375
|
-
expect(messageService.
|
|
376
|
+
expect(messageService.removeMessagesByAssistant).toHaveBeenCalledWith(activeId, topicId);
|
|
376
377
|
expect(topicService.removeTopic).toHaveBeenCalledWith(topicId);
|
|
377
378
|
expect(refreshTopicSpy).toHaveBeenCalled();
|
|
378
379
|
expect(switchTopicSpy).not.toHaveBeenCalled();
|
|
@@ -249,7 +249,7 @@ export const chatTopic: StateCreator<
|
|
|
249
249
|
|
|
250
250
|
// remove messages in the topic
|
|
251
251
|
// TODO: Need to remove because server service don't need to call it
|
|
252
|
-
await messageService.
|
|
252
|
+
await messageService.removeMessagesByAssistant(activeId, id);
|
|
253
253
|
|
|
254
254
|
// remove topic
|
|
255
255
|
await topicService.removeTopic(id);
|
|
@@ -69,6 +69,19 @@ describe('createPreferenceSlice', () => {
|
|
|
69
69
|
});
|
|
70
70
|
});
|
|
71
71
|
|
|
72
|
+
describe('toggleMobilePortal', () => {
|
|
73
|
+
it('should toggle mobile topic', () => {
|
|
74
|
+
const { result } = renderHook(() => useGlobalStore());
|
|
75
|
+
|
|
76
|
+
act(() => {
|
|
77
|
+
useGlobalStore.setState({ isStatusInit: true });
|
|
78
|
+
result.current.toggleMobilePortal();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
expect(result.current.status.mobileShowPortal).toBe(true);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
72
85
|
describe('toggleSystemRole', () => {
|
|
73
86
|
it('should toggle system role', () => {
|
|
74
87
|
const { result } = renderHook(() => useGlobalStore());
|
|
@@ -24,6 +24,7 @@ export interface GlobalStoreAction {
|
|
|
24
24
|
switchBackToChat: (sessionId?: string) => void;
|
|
25
25
|
toggleChatSideBar: (visible?: boolean) => void;
|
|
26
26
|
toggleExpandSessionGroup: (id: string, expand: boolean) => void;
|
|
27
|
+
toggleMobilePortal: (visible?: boolean) => void;
|
|
27
28
|
toggleMobileTopic: (visible?: boolean) => void;
|
|
28
29
|
toggleSystemRole: (visible?: boolean) => void;
|
|
29
30
|
updateSystemStatus: (status: Partial<SystemStatus>, action?: any) => void;
|
|
@@ -59,6 +60,12 @@ export const globalActionSlice: StateCreator<
|
|
|
59
60
|
});
|
|
60
61
|
get().updateSystemStatus({ expandSessionGroupKeys: nextExpandSessionGroup });
|
|
61
62
|
},
|
|
63
|
+
toggleMobilePortal: (newValue) => {
|
|
64
|
+
const mobileShowPortal =
|
|
65
|
+
typeof newValue === 'boolean' ? newValue : !get().status.mobileShowPortal;
|
|
66
|
+
|
|
67
|
+
get().updateSystemStatus({ mobileShowPortal }, n('toggleMobilePortal', newValue));
|
|
68
|
+
},
|
|
62
69
|
toggleMobileTopic: (newValue) => {
|
|
63
70
|
const mobileShowTopic =
|
|
64
71
|
typeof newValue === 'boolean' ? newValue : !get().status.mobileShowTopic;
|
|
@@ -7,6 +7,7 @@ const sessionGroupKeys = (s: GlobalStore): string[] =>
|
|
|
7
7
|
|
|
8
8
|
const showSystemRole = (s: GlobalStore) => s.status.showSystemRole;
|
|
9
9
|
const mobileShowTopic = (s: GlobalStore) => s.status.mobileShowTopic;
|
|
10
|
+
const mobileShowPortal = (s: GlobalStore) => s.status.mobileShowPortal;
|
|
10
11
|
const showChatSideBar = (s: GlobalStore) => s.status.showChatSideBar;
|
|
11
12
|
const showSessionPanel = (s: GlobalStore) => s.status.showSessionPanel;
|
|
12
13
|
const hidePWAInstaller = (s: GlobalStore) => s.status.hidePWAInstaller;
|
|
@@ -17,6 +18,7 @@ const inputHeight = (s: GlobalStore) => s.status.inputHeight;
|
|
|
17
18
|
export const systemStatusSelectors = {
|
|
18
19
|
hidePWAInstaller,
|
|
19
20
|
inputHeight,
|
|
21
|
+
mobileShowPortal,
|
|
20
22
|
mobileShowTopic,
|
|
21
23
|
sessionGroupKeys,
|
|
22
24
|
sessionWidth,
|