@lobehub/chat 0.154.6 → 0.154.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 +25 -0
- package/package.json +1 -1
- package/src/features/Conversation/Error/APIKeyForm/index.tsx +1 -1
- package/src/features/Conversation/Error/AccessCodeForm.tsx +1 -1
- package/src/features/Conversation/Error/ClerkLogin/index.tsx +1 -1
- package/src/features/Conversation/Error/OAuthForm.tsx +1 -1
- package/src/features/Conversation/Error/PluginSettings.tsx +1 -1
- package/src/store/chat/slices/enchance/action.ts +4 -4
- package/src/store/chat/slices/message/action.test.ts +56 -39
- package/src/store/chat/slices/message/action.ts +66 -66
- package/src/store/chat/slices/plugin/action.test.ts +26 -26
- package/src/store/chat/slices/plugin/action.ts +14 -14
- package/src/store/chat/slices/tool/action.test.ts +3 -3
- package/src/store/chat/slices/tool/action.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,31 @@
|
|
|
2
2
|
|
|
3
3
|
# Changelog
|
|
4
4
|
|
|
5
|
+
### [Version 0.154.7](https://github.com/lobehub/lobe-chat/compare/v0.154.6...v0.154.7)
|
|
6
|
+
|
|
7
|
+
<sup>Released on **2024-05-07**</sup>
|
|
8
|
+
|
|
9
|
+
#### ♻ Code Refactoring
|
|
10
|
+
|
|
11
|
+
- **misc**: Refactor the message slice internal method name.
|
|
12
|
+
|
|
13
|
+
<br/>
|
|
14
|
+
|
|
15
|
+
<details>
|
|
16
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
|
17
|
+
|
|
18
|
+
#### Code refactoring
|
|
19
|
+
|
|
20
|
+
- **misc**: Refactor the message slice internal method name, closes [#2407](https://github.com/lobehub/lobe-chat/issues/2407) ([8c70bdd](https://github.com/lobehub/lobe-chat/commit/8c70bdd))
|
|
21
|
+
|
|
22
|
+
</details>
|
|
23
|
+
|
|
24
|
+
<div align="right">
|
|
25
|
+
|
|
26
|
+
[](#readme-top)
|
|
27
|
+
|
|
28
|
+
</div>
|
|
29
|
+
|
|
5
30
|
### [Version 0.154.6](https://github.com/lobehub/lobe-chat/compare/v0.154.5...v0.154.6)
|
|
6
31
|
|
|
7
32
|
<sup>Released on **2024-05-07**</sup>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lobehub/chat",
|
|
3
|
-
"version": "0.154.
|
|
3
|
+
"version": "0.154.7",
|
|
4
4
|
"description": "Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"framework",
|
|
@@ -19,7 +19,7 @@ interface APIKeyFormProps {
|
|
|
19
19
|
const APIKeyForm = memo<APIKeyFormProps>(({ id, provider }) => {
|
|
20
20
|
const { t } = useTranslation('error');
|
|
21
21
|
|
|
22
|
-
const [resend, deleteMessage] = useChatStore((s) => [s.
|
|
22
|
+
const [resend, deleteMessage] = useChatStore((s) => [s.regenerateMessage, s.deleteMessage]);
|
|
23
23
|
|
|
24
24
|
const apiKeyPlaceholder = useMemo(() => {
|
|
25
25
|
switch (provider) {
|
|
@@ -19,7 +19,7 @@ const AccessCodeForm = memo<AccessCodeFormProps>(({ id }) => {
|
|
|
19
19
|
settingsSelectors.currentSettings(s).password,
|
|
20
20
|
s.setSettings,
|
|
21
21
|
]);
|
|
22
|
-
const [resend, deleteMessage] = useChatStore((s) => [s.
|
|
22
|
+
const [resend, deleteMessage] = useChatStore((s) => [s.regenerateMessage, s.deleteMessage]);
|
|
23
23
|
|
|
24
24
|
return (
|
|
25
25
|
<>
|
|
@@ -15,7 +15,7 @@ const ClerkLogin = memo<{ id: string }>(({ id }) => {
|
|
|
15
15
|
const [openSignIn, isSignedIn] = useUserStore((s) => [s.openLogin, s.isSignedIn]);
|
|
16
16
|
const greeting = useGreeting();
|
|
17
17
|
const nickName = useUserStore(userProfileSelectors.nickName);
|
|
18
|
-
const [resend, deleteMessage] = useChatStore((s) => [s.
|
|
18
|
+
const [resend, deleteMessage] = useChatStore((s) => [s.regenerateMessage, s.deleteMessage]);
|
|
19
19
|
|
|
20
20
|
return (
|
|
21
21
|
<ErrorActionContainer>
|
|
@@ -16,7 +16,7 @@ const OAuthForm = memo<{ id: string }>(({ id }) => {
|
|
|
16
16
|
|
|
17
17
|
const { user, isOAuthLoggedIn } = useOAuthSession();
|
|
18
18
|
|
|
19
|
-
const [resend, deleteMessage] = useChatStore((s) => [s.
|
|
19
|
+
const [resend, deleteMessage] = useChatStore((s) => [s.regenerateMessage, s.deleteMessage]);
|
|
20
20
|
|
|
21
21
|
const { message, modal } = App.useApp();
|
|
22
22
|
|
|
@@ -23,7 +23,7 @@ const PluginSettings = memo<PluginSettingsProps>(({ id, plugin }) => {
|
|
|
23
23
|
const { styles } = useStyles();
|
|
24
24
|
const { t } = useTranslation('error');
|
|
25
25
|
const theme = useTheme();
|
|
26
|
-
const [resend, deleteMessage] = useChatStore((s) => [s.
|
|
26
|
+
const [resend, deleteMessage] = useChatStore((s) => [s.regenerateMessage, s.deleteMessage]);
|
|
27
27
|
const pluginIdentifier = plugin?.identifier as string;
|
|
28
28
|
const pluginMeta = useToolStore(pluginSelectors.getPluginMetaById(pluginIdentifier), isEqual);
|
|
29
29
|
const manifest = useToolStore(pluginSelectors.getPluginManifestById(pluginIdentifier), isEqual);
|
|
@@ -49,7 +49,7 @@ export const chatEnhance: StateCreator<
|
|
|
49
49
|
...data,
|
|
50
50
|
}),
|
|
51
51
|
translateMessage: async (id, targetLang) => {
|
|
52
|
-
const {
|
|
52
|
+
const { internal_toggleChatLoading, updateMessageTranslate, internal_dispatchMessage } = get();
|
|
53
53
|
|
|
54
54
|
const message = chatSelectors.getMessageById(id)(get());
|
|
55
55
|
if (!message) return;
|
|
@@ -57,7 +57,7 @@ export const chatEnhance: StateCreator<
|
|
|
57
57
|
// create translate extra
|
|
58
58
|
await updateMessageTranslate(id, { content: '', from: '', to: targetLang });
|
|
59
59
|
|
|
60
|
-
|
|
60
|
+
internal_toggleChatLoading(true, id, n('translateMessage(start)', { id }) as string);
|
|
61
61
|
|
|
62
62
|
let content = '';
|
|
63
63
|
let from = '';
|
|
@@ -77,7 +77,7 @@ export const chatEnhance: StateCreator<
|
|
|
77
77
|
// translate to target language
|
|
78
78
|
await chatService.fetchPresetTaskResult({
|
|
79
79
|
onMessageHandle: (text) => {
|
|
80
|
-
|
|
80
|
+
internal_dispatchMessage({
|
|
81
81
|
id,
|
|
82
82
|
key: 'translate',
|
|
83
83
|
type: 'updateMessageExtra',
|
|
@@ -93,7 +93,7 @@ export const chatEnhance: StateCreator<
|
|
|
93
93
|
|
|
94
94
|
await updateMessageTranslate(id, { content, from, to: targetLang });
|
|
95
95
|
|
|
96
|
-
|
|
96
|
+
internal_toggleChatLoading(false);
|
|
97
97
|
},
|
|
98
98
|
|
|
99
99
|
ttsMessage: async (id, state = {}) => {
|
|
@@ -53,7 +53,7 @@ vi.mock('@/store/chat/selectors', () => ({
|
|
|
53
53
|
},
|
|
54
54
|
}));
|
|
55
55
|
|
|
56
|
-
const realCoreProcessMessage = useChatStore.getState().
|
|
56
|
+
const realCoreProcessMessage = useChatStore.getState().internal_coreProcessMessage;
|
|
57
57
|
const realRefreshMessages = useChatStore.getState().refreshMessages;
|
|
58
58
|
// Mock state
|
|
59
59
|
const mockState = {
|
|
@@ -62,7 +62,7 @@ const mockState = {
|
|
|
62
62
|
messages: [],
|
|
63
63
|
refreshMessages: vi.fn(),
|
|
64
64
|
refreshTopic: vi.fn(),
|
|
65
|
-
|
|
65
|
+
internal_coreProcessMessage: vi.fn(),
|
|
66
66
|
saveToTopic: vi.fn(),
|
|
67
67
|
};
|
|
68
68
|
|
|
@@ -191,7 +191,7 @@ describe('chatMessage actions', () => {
|
|
|
191
191
|
|
|
192
192
|
expect(messageService.createMessage).not.toHaveBeenCalled();
|
|
193
193
|
expect(result.current.refreshMessages).not.toHaveBeenCalled();
|
|
194
|
-
expect(result.current.
|
|
194
|
+
expect(result.current.internal_coreProcessMessage).not.toHaveBeenCalled();
|
|
195
195
|
});
|
|
196
196
|
|
|
197
197
|
it('should not send message if message is empty and there are no files', async () => {
|
|
@@ -204,7 +204,7 @@ describe('chatMessage actions', () => {
|
|
|
204
204
|
|
|
205
205
|
expect(messageService.createMessage).not.toHaveBeenCalled();
|
|
206
206
|
expect(result.current.refreshMessages).not.toHaveBeenCalled();
|
|
207
|
-
expect(result.current.
|
|
207
|
+
expect(result.current.internal_coreProcessMessage).not.toHaveBeenCalled();
|
|
208
208
|
});
|
|
209
209
|
|
|
210
210
|
it('should not send message if message is empty and there are empty files', async () => {
|
|
@@ -217,10 +217,10 @@ describe('chatMessage actions', () => {
|
|
|
217
217
|
|
|
218
218
|
expect(messageService.createMessage).not.toHaveBeenCalled();
|
|
219
219
|
expect(result.current.refreshMessages).not.toHaveBeenCalled();
|
|
220
|
-
expect(result.current.
|
|
220
|
+
expect(result.current.internal_coreProcessMessage).not.toHaveBeenCalled();
|
|
221
221
|
});
|
|
222
222
|
|
|
223
|
-
it('should create message and call
|
|
223
|
+
it('should create message and call internal_coreProcessMessage if message or files are provided', async () => {
|
|
224
224
|
const { result } = renderHook(() => useChatStore());
|
|
225
225
|
const message = 'Test message';
|
|
226
226
|
const files = [{ id: 'file-id', url: 'file-url' }];
|
|
@@ -240,7 +240,7 @@ describe('chatMessage actions', () => {
|
|
|
240
240
|
topicId: mockState.activeTopicId,
|
|
241
241
|
});
|
|
242
242
|
expect(result.current.refreshMessages).toHaveBeenCalled();
|
|
243
|
-
expect(result.current.
|
|
243
|
+
expect(result.current.internal_coreProcessMessage).toHaveBeenCalled();
|
|
244
244
|
});
|
|
245
245
|
|
|
246
246
|
describe('auto-create topic', () => {
|
|
@@ -365,7 +365,7 @@ describe('chatMessage actions', () => {
|
|
|
365
365
|
});
|
|
366
366
|
});
|
|
367
367
|
|
|
368
|
-
describe('
|
|
368
|
+
describe('internal_resendMessage action', () => {
|
|
369
369
|
it('should resend a message by id and refresh messages', async () => {
|
|
370
370
|
const { result } = renderHook(() => useChatStore());
|
|
371
371
|
const messageId = 'message-id';
|
|
@@ -377,15 +377,19 @@ describe('chatMessage actions', () => {
|
|
|
377
377
|
// ... other messages
|
|
378
378
|
]);
|
|
379
379
|
|
|
380
|
-
// Mock the
|
|
381
|
-
mockState.
|
|
380
|
+
// Mock the internal_coreProcessMessage function to resolve immediately
|
|
381
|
+
mockState.internal_coreProcessMessage.mockResolvedValue(undefined);
|
|
382
382
|
|
|
383
383
|
await act(async () => {
|
|
384
|
-
await result.current.
|
|
384
|
+
await result.current.internal_resendMessage(messageId);
|
|
385
385
|
});
|
|
386
386
|
|
|
387
387
|
expect(messageService.removeMessage).not.toHaveBeenCalledWith(messageId);
|
|
388
|
-
expect(mockState.
|
|
388
|
+
expect(mockState.internal_coreProcessMessage).toHaveBeenCalledWith(
|
|
389
|
+
expect.any(Array),
|
|
390
|
+
messageId,
|
|
391
|
+
{},
|
|
392
|
+
);
|
|
389
393
|
});
|
|
390
394
|
|
|
391
395
|
it('should not perform any action if the message id does not exist', async () => {
|
|
@@ -398,23 +402,23 @@ describe('chatMessage actions', () => {
|
|
|
398
402
|
]);
|
|
399
403
|
|
|
400
404
|
await act(async () => {
|
|
401
|
-
await result.current.
|
|
405
|
+
await result.current.internal_resendMessage(messageId);
|
|
402
406
|
});
|
|
403
407
|
|
|
404
408
|
expect(messageService.removeMessage).not.toHaveBeenCalledWith(messageId);
|
|
405
|
-
expect(mockState.
|
|
409
|
+
expect(mockState.internal_coreProcessMessage).not.toHaveBeenCalled();
|
|
406
410
|
expect(mockState.refreshMessages).not.toHaveBeenCalled();
|
|
407
411
|
});
|
|
408
412
|
});
|
|
409
413
|
|
|
410
|
-
describe('
|
|
411
|
-
it('should call messageService.
|
|
414
|
+
describe('internal_updateMessageContent action', () => {
|
|
415
|
+
it('should call messageService.internal_updateMessageContent with correct parameters', async () => {
|
|
412
416
|
const { result } = renderHook(() => useChatStore());
|
|
413
417
|
const messageId = 'message-id';
|
|
414
418
|
const newContent = 'Updated content';
|
|
415
419
|
|
|
416
420
|
await act(async () => {
|
|
417
|
-
await result.current.
|
|
421
|
+
await result.current.internal_updateMessageContent(messageId, newContent);
|
|
418
422
|
});
|
|
419
423
|
|
|
420
424
|
expect(messageService.updateMessage).toHaveBeenCalledWith(messageId, { content: newContent });
|
|
@@ -424,13 +428,13 @@ describe('chatMessage actions', () => {
|
|
|
424
428
|
const { result } = renderHook(() => useChatStore());
|
|
425
429
|
const messageId = 'message-id';
|
|
426
430
|
const newContent = 'Updated content';
|
|
427
|
-
const
|
|
431
|
+
const internal_dispatchMessageSpy = vi.spyOn(result.current, 'internal_dispatchMessage');
|
|
428
432
|
|
|
429
433
|
await act(async () => {
|
|
430
|
-
await result.current.
|
|
434
|
+
await result.current.internal_updateMessageContent(messageId, newContent);
|
|
431
435
|
});
|
|
432
436
|
|
|
433
|
-
expect(
|
|
437
|
+
expect(internal_dispatchMessageSpy).toHaveBeenCalledWith({
|
|
434
438
|
id: messageId,
|
|
435
439
|
key: 'content',
|
|
436
440
|
type: 'updateMessage',
|
|
@@ -444,16 +448,16 @@ describe('chatMessage actions', () => {
|
|
|
444
448
|
const newContent = 'Updated content';
|
|
445
449
|
|
|
446
450
|
await act(async () => {
|
|
447
|
-
await result.current.
|
|
451
|
+
await result.current.internal_updateMessageContent(messageId, newContent);
|
|
448
452
|
});
|
|
449
453
|
|
|
450
454
|
expect(result.current.refreshMessages).toHaveBeenCalled();
|
|
451
455
|
});
|
|
452
456
|
});
|
|
453
457
|
|
|
454
|
-
describe('
|
|
458
|
+
describe('internal_coreProcessMessage action', () => {
|
|
455
459
|
it('should handle the core AI message processing', async () => {
|
|
456
|
-
useChatStore.setState({
|
|
460
|
+
useChatStore.setState({ internal_coreProcessMessage: realCoreProcessMessage });
|
|
457
461
|
|
|
458
462
|
const { result } = renderHook(() => useChatStore());
|
|
459
463
|
const userMessage = {
|
|
@@ -473,7 +477,7 @@ describe('chatMessage actions', () => {
|
|
|
473
477
|
(messageService.createMessage as Mock).mockResolvedValue('assistant-message-id');
|
|
474
478
|
|
|
475
479
|
await act(async () => {
|
|
476
|
-
await result.current.
|
|
480
|
+
await result.current.internal_coreProcessMessage(messages, userMessage.id);
|
|
477
481
|
});
|
|
478
482
|
|
|
479
483
|
// 验证是否创建了代表 AI 响应的消息
|
|
@@ -499,7 +503,7 @@ describe('chatMessage actions', () => {
|
|
|
499
503
|
describe('stopGenerateMessage action', () => {
|
|
500
504
|
it('should stop generating message and set loading states correctly', async () => {
|
|
501
505
|
const { result } = renderHook(() => useChatStore());
|
|
502
|
-
const
|
|
506
|
+
const internal_toggleChatLoadingSpy = vi.spyOn(result.current, 'internal_toggleChatLoading');
|
|
503
507
|
const abortController = new AbortController();
|
|
504
508
|
|
|
505
509
|
act(() => {
|
|
@@ -511,7 +515,11 @@ describe('chatMessage actions', () => {
|
|
|
511
515
|
});
|
|
512
516
|
|
|
513
517
|
expect(abortController.signal.aborted).toBe(true);
|
|
514
|
-
expect(
|
|
518
|
+
expect(internal_toggleChatLoadingSpy).toHaveBeenCalledWith(
|
|
519
|
+
false,
|
|
520
|
+
undefined,
|
|
521
|
+
expect.any(String),
|
|
522
|
+
);
|
|
515
523
|
});
|
|
516
524
|
|
|
517
525
|
it('should not do anything if there is no abortController', async () => {
|
|
@@ -598,7 +606,7 @@ describe('chatMessage actions', () => {
|
|
|
598
606
|
});
|
|
599
607
|
});
|
|
600
608
|
|
|
601
|
-
describe('
|
|
609
|
+
describe('internal_fetchAIChatMessage', () => {
|
|
602
610
|
it('should fetch AI chat message and return content', async () => {
|
|
603
611
|
const { result } = renderHook(() => useChatStore());
|
|
604
612
|
const messages = [{ id: 'message-id', content: 'Hello', role: 'user' }] as ChatMessage[];
|
|
@@ -608,7 +616,10 @@ describe('chatMessage actions', () => {
|
|
|
608
616
|
(fetch as Mock).mockResolvedValueOnce(new Response(aiResponse));
|
|
609
617
|
|
|
610
618
|
await act(async () => {
|
|
611
|
-
const response = await result.current.
|
|
619
|
+
const response = await result.current.internal_fetchAIChatMessage(
|
|
620
|
+
messages,
|
|
621
|
+
assistantMessageId,
|
|
622
|
+
);
|
|
612
623
|
expect(response.content).toEqual(aiResponse);
|
|
613
624
|
expect(response.isFunctionCall).toEqual(false);
|
|
614
625
|
expect(response.functionCallAtEnd).toEqual(false);
|
|
@@ -627,7 +638,10 @@ describe('chatMessage actions', () => {
|
|
|
627
638
|
vi.mocked(fetch).mockResolvedValueOnce(new Response(aiResponse));
|
|
628
639
|
|
|
629
640
|
await act(async () => {
|
|
630
|
-
const response = await result.current.
|
|
641
|
+
const response = await result.current.internal_fetchAIChatMessage(
|
|
642
|
+
messages,
|
|
643
|
+
assistantMessageId,
|
|
644
|
+
);
|
|
631
645
|
expect(response.content).toEqual(aiResponse);
|
|
632
646
|
expect(response.isFunctionCall).toEqual(true);
|
|
633
647
|
expect(response.functionCallAtEnd).toEqual(false);
|
|
@@ -646,7 +660,10 @@ describe('chatMessage actions', () => {
|
|
|
646
660
|
vi.mocked(fetch).mockResolvedValue(new Response(aiResponse));
|
|
647
661
|
|
|
648
662
|
await act(async () => {
|
|
649
|
-
const response = await result.current.
|
|
663
|
+
const response = await result.current.internal_fetchAIChatMessage(
|
|
664
|
+
messages,
|
|
665
|
+
assistantMessageId,
|
|
666
|
+
);
|
|
650
667
|
expect(response.content).toEqual(aiResponse);
|
|
651
668
|
expect(response.isFunctionCall).toEqual(true);
|
|
652
669
|
expect(response.functionCallAtEnd).toEqual(true);
|
|
@@ -667,19 +684,19 @@ describe('chatMessage actions', () => {
|
|
|
667
684
|
|
|
668
685
|
await act(async () => {
|
|
669
686
|
await expect(
|
|
670
|
-
result.current.
|
|
687
|
+
result.current.internal_fetchAIChatMessage(messages, assistantMessageId),
|
|
671
688
|
).rejects.toThrow(errorMessage);
|
|
672
689
|
});
|
|
673
690
|
});
|
|
674
691
|
});
|
|
675
692
|
|
|
676
|
-
describe('
|
|
693
|
+
describe('internal_toggleChatLoading', () => {
|
|
677
694
|
it('should set loading state and create an AbortController when loading is true', () => {
|
|
678
695
|
const { result } = renderHook(() => useChatStore());
|
|
679
696
|
const action = 'loading-action';
|
|
680
697
|
|
|
681
698
|
act(() => {
|
|
682
|
-
result.current.
|
|
699
|
+
result.current.internal_toggleChatLoading(true, 'message-id', action);
|
|
683
700
|
});
|
|
684
701
|
|
|
685
702
|
const state = useChatStore.getState();
|
|
@@ -693,12 +710,12 @@ describe('chatMessage actions', () => {
|
|
|
693
710
|
|
|
694
711
|
// Set initial loading state
|
|
695
712
|
act(() => {
|
|
696
|
-
result.current.
|
|
713
|
+
result.current.internal_toggleChatLoading(true, 'message-id', 'start-loading-action');
|
|
697
714
|
});
|
|
698
715
|
|
|
699
716
|
// Stop loading
|
|
700
717
|
act(() => {
|
|
701
|
-
result.current.
|
|
718
|
+
result.current.internal_toggleChatLoading(false, undefined, action);
|
|
702
719
|
});
|
|
703
720
|
|
|
704
721
|
const state = useChatStore.getState();
|
|
@@ -711,7 +728,7 @@ describe('chatMessage actions', () => {
|
|
|
711
728
|
const addEventListenerSpy = vi.spyOn(window, 'addEventListener');
|
|
712
729
|
|
|
713
730
|
act(() => {
|
|
714
|
-
result.current.
|
|
731
|
+
result.current.internal_toggleChatLoading(true, 'message-id', 'loading-action');
|
|
715
732
|
});
|
|
716
733
|
|
|
717
734
|
expect(addEventListenerSpy).toHaveBeenCalledWith('beforeunload', expect.any(Function));
|
|
@@ -723,8 +740,8 @@ describe('chatMessage actions', () => {
|
|
|
723
740
|
|
|
724
741
|
// Start and then stop loading to trigger the removal of the event listener
|
|
725
742
|
act(() => {
|
|
726
|
-
result.current.
|
|
727
|
-
result.current.
|
|
743
|
+
result.current.internal_toggleChatLoading(true, 'message-id', 'start-loading-action');
|
|
744
|
+
result.current.internal_toggleChatLoading(false, undefined, 'stop-loading-action');
|
|
728
745
|
});
|
|
729
746
|
|
|
730
747
|
expect(removeEventListenerSpy).toHaveBeenCalledWith('beforeunload', expect.any(Function));
|
|
@@ -736,7 +753,7 @@ describe('chatMessage actions', () => {
|
|
|
736
753
|
|
|
737
754
|
act(() => {
|
|
738
755
|
useChatStore.setState({ abortController });
|
|
739
|
-
result.current.
|
|
756
|
+
result.current.internal_toggleChatLoading(true, 'message-id', 'loading-action');
|
|
740
757
|
});
|
|
741
758
|
|
|
742
759
|
const state = useChatStore.getState();
|
|
@@ -71,20 +71,26 @@ export interface ChatMessageAction {
|
|
|
71
71
|
useFetchMessages: (sessionId: string, topicId?: string) => SWRResponse<ChatMessage[]>;
|
|
72
72
|
stopGenerateMessage: () => void;
|
|
73
73
|
copyMessage: (id: string, content: string) => Promise<void>;
|
|
74
|
+
refreshMessages: () => Promise<void>;
|
|
74
75
|
|
|
75
76
|
// ========= ↓ Internal Method ↓ ========== //
|
|
76
77
|
// ========================================== //
|
|
77
78
|
// ========================================== //
|
|
78
|
-
|
|
79
|
+
internal_toggleChatLoading: (
|
|
80
|
+
loading: boolean,
|
|
81
|
+
id?: string,
|
|
82
|
+
action?: string,
|
|
83
|
+
) => AbortController | undefined;
|
|
84
|
+
internal_toggleMessageLoading: (loading: boolean, id: string) => void;
|
|
79
85
|
/**
|
|
80
86
|
* update message at the frontend point
|
|
81
87
|
* this method will not update messages to database
|
|
82
88
|
*/
|
|
83
|
-
|
|
89
|
+
internal_dispatchMessage: (payload: MessageDispatch) => void;
|
|
84
90
|
/**
|
|
85
91
|
* core process of the AI message (include preprocess and postprocess)
|
|
86
92
|
*/
|
|
87
|
-
|
|
93
|
+
internal_coreProcessMessage: (
|
|
88
94
|
messages: ChatMessage[],
|
|
89
95
|
parentId: string,
|
|
90
96
|
params?: ProcessMessageParams,
|
|
@@ -94,7 +100,7 @@ export interface ChatMessageAction {
|
|
|
94
100
|
* @param messages - 聊天消息数组
|
|
95
101
|
* @param options - 获取 SSE 选项
|
|
96
102
|
*/
|
|
97
|
-
|
|
103
|
+
internal_fetchAIChatMessage: (
|
|
98
104
|
messages: ChatMessage[],
|
|
99
105
|
assistantMessageId: string,
|
|
100
106
|
params?: ProcessMessageParams,
|
|
@@ -105,15 +111,8 @@ export interface ChatMessageAction {
|
|
|
105
111
|
isFunctionCall: boolean;
|
|
106
112
|
traceId?: string;
|
|
107
113
|
}>;
|
|
108
|
-
toggleChatLoading: (
|
|
109
|
-
loading: boolean,
|
|
110
|
-
id?: string,
|
|
111
|
-
action?: string,
|
|
112
|
-
) => AbortController | undefined;
|
|
113
|
-
toggleMessageLoading: (loading: boolean, id: string) => void;
|
|
114
|
-
refreshMessages: () => Promise<void>;
|
|
115
114
|
// TODO: 后续 smoothMessage 实现考虑落到 sse 这一层
|
|
116
|
-
|
|
115
|
+
internal_createSmoothMessage: (id: string) => {
|
|
117
116
|
startAnimation: (speed?: number) => Promise<void>;
|
|
118
117
|
stopAnimation: () => void;
|
|
119
118
|
outputQueue: string[];
|
|
@@ -124,10 +123,10 @@ export interface ChatMessageAction {
|
|
|
124
123
|
* @param id
|
|
125
124
|
* @param content
|
|
126
125
|
*/
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
126
|
+
internal_updateMessageContent: (id: string, content: string) => Promise<void>;
|
|
127
|
+
internal_createMessage: (params: CreateMessageParams) => Promise<string>;
|
|
128
|
+
internal_resendMessage: (id: string, traceId?: string) => Promise<void>;
|
|
129
|
+
internal_traceMessage: (id: string, payload: TraceEventPayloads) => Promise<void>;
|
|
131
130
|
}
|
|
132
131
|
|
|
133
132
|
const getAgentConfig = () => agentSelectors.currentAgentConfig(useAgentStore.getState());
|
|
@@ -145,24 +144,24 @@ export const chatMessage: StateCreator<
|
|
|
145
144
|
ChatMessageAction
|
|
146
145
|
> = (set, get) => ({
|
|
147
146
|
deleteMessage: async (id) => {
|
|
148
|
-
get().
|
|
147
|
+
get().internal_dispatchMessage({ type: 'deleteMessage', id });
|
|
149
148
|
await messageService.removeMessage(id);
|
|
150
149
|
await get().refreshMessages();
|
|
151
150
|
},
|
|
152
151
|
delAndRegenerateMessage: async (id) => {
|
|
153
152
|
const traceId = chatSelectors.getTraceIdByMessageId(id)(get());
|
|
154
|
-
get().
|
|
153
|
+
get().internal_resendMessage(id, traceId);
|
|
155
154
|
get().deleteMessage(id);
|
|
156
155
|
|
|
157
156
|
// trace the delete and regenerate message
|
|
158
|
-
get().
|
|
157
|
+
get().internal_traceMessage(id, { eventType: TraceEventType.DeleteAndRegenerateMessage });
|
|
159
158
|
},
|
|
160
159
|
regenerateMessage: async (id: string) => {
|
|
161
160
|
const traceId = chatSelectors.getTraceIdByMessageId(id)(get());
|
|
162
|
-
await get().
|
|
161
|
+
await get().internal_resendMessage(id, traceId);
|
|
163
162
|
|
|
164
163
|
// trace the delete and regenerate message
|
|
165
|
-
get().
|
|
164
|
+
get().internal_traceMessage(id, { eventType: TraceEventType.RegenerateMessage });
|
|
166
165
|
},
|
|
167
166
|
clearMessage: async () => {
|
|
168
167
|
const { activeId, activeTopicId, refreshMessages, refreshTopic, switchTopic } = get();
|
|
@@ -184,7 +183,7 @@ export const chatMessage: StateCreator<
|
|
|
184
183
|
await refreshMessages();
|
|
185
184
|
},
|
|
186
185
|
sendMessage: async ({ message, files, onlyAddUserMessage, isWelcomeQuestion }) => {
|
|
187
|
-
const {
|
|
186
|
+
const { internal_coreProcessMessage, activeTopicId, activeId } = get();
|
|
188
187
|
if (!activeId) return;
|
|
189
188
|
|
|
190
189
|
const fileIdList = files?.map((f) => f.id);
|
|
@@ -202,7 +201,7 @@ export const chatMessage: StateCreator<
|
|
|
202
201
|
topicId: activeTopicId,
|
|
203
202
|
};
|
|
204
203
|
|
|
205
|
-
const id = await get().
|
|
204
|
+
const id = await get().internal_createMessage(newMessage);
|
|
206
205
|
|
|
207
206
|
// if only add user message, then stop
|
|
208
207
|
if (onlyAddUserMessage) return;
|
|
@@ -210,7 +209,7 @@ export const chatMessage: StateCreator<
|
|
|
210
209
|
// Get the current messages to generate AI response
|
|
211
210
|
const messages = chatSelectors.currentChats(get());
|
|
212
211
|
|
|
213
|
-
await
|
|
212
|
+
await internal_coreProcessMessage(messages, id, { isWelcomeQuestion });
|
|
214
213
|
|
|
215
214
|
// check activeTopic and then auto create topic
|
|
216
215
|
const chats = chatSelectors.currentChats(get());
|
|
@@ -226,11 +225,11 @@ export const chatMessage: StateCreator<
|
|
|
226
225
|
}
|
|
227
226
|
},
|
|
228
227
|
addAIMessage: async () => {
|
|
229
|
-
const {
|
|
228
|
+
const { internal_createMessage, updateInputMessage, activeTopicId, activeId, inputMessage } =
|
|
230
229
|
get();
|
|
231
230
|
if (!activeId) return;
|
|
232
231
|
|
|
233
|
-
await
|
|
232
|
+
await internal_createMessage({
|
|
234
233
|
content: inputMessage,
|
|
235
234
|
role: 'assistant',
|
|
236
235
|
sessionId: activeId,
|
|
@@ -243,16 +242,16 @@ export const chatMessage: StateCreator<
|
|
|
243
242
|
copyMessage: async (id, content) => {
|
|
244
243
|
await copyToClipboard(content);
|
|
245
244
|
|
|
246
|
-
get().
|
|
245
|
+
get().internal_traceMessage(id, { eventType: TraceEventType.CopyMessage });
|
|
247
246
|
},
|
|
248
247
|
|
|
249
248
|
stopGenerateMessage: () => {
|
|
250
|
-
const { abortController,
|
|
249
|
+
const { abortController, internal_toggleChatLoading } = get();
|
|
251
250
|
if (!abortController) return;
|
|
252
251
|
|
|
253
252
|
abortController.abort();
|
|
254
253
|
|
|
255
|
-
|
|
254
|
+
internal_toggleChatLoading(false, undefined, n('stopGenerateMessage') as string);
|
|
256
255
|
},
|
|
257
256
|
updateInputMessage: (message) => {
|
|
258
257
|
set({ inputMessage: message }, false, n('updateInputMessage', message));
|
|
@@ -260,12 +259,12 @@ export const chatMessage: StateCreator<
|
|
|
260
259
|
modifyMessageContent: async (id, content) => {
|
|
261
260
|
// tracing the diff of update
|
|
262
261
|
// due to message content will change, so we need send trace before update,or will get wrong data
|
|
263
|
-
get().
|
|
262
|
+
get().internal_traceMessage(id, {
|
|
264
263
|
eventType: TraceEventType.ModifyMessage,
|
|
265
264
|
nextContent: content,
|
|
266
265
|
});
|
|
267
266
|
|
|
268
|
-
await get().
|
|
267
|
+
await get().internal_updateMessageContent(id, content);
|
|
269
268
|
},
|
|
270
269
|
useFetchMessages: (sessionId, activeTopicId) =>
|
|
271
270
|
useClientDataSWR<ChatMessage[]>(
|
|
@@ -292,8 +291,9 @@ export const chatMessage: StateCreator<
|
|
|
292
291
|
},
|
|
293
292
|
|
|
294
293
|
// the internal process method of the AI message
|
|
295
|
-
|
|
296
|
-
const {
|
|
294
|
+
internal_coreProcessMessage: async (messages, userMessageId, params) => {
|
|
295
|
+
const { internal_fetchAIChatMessage, triggerFunctionCall, refreshMessages, activeTopicId } =
|
|
296
|
+
get();
|
|
297
297
|
|
|
298
298
|
const { model, provider } = getAgentConfig();
|
|
299
299
|
|
|
@@ -309,11 +309,11 @@ export const chatMessage: StateCreator<
|
|
|
309
309
|
topicId: activeTopicId, // if there is activeTopicId,then add it to topicId
|
|
310
310
|
};
|
|
311
311
|
|
|
312
|
-
const mid = await get().
|
|
312
|
+
const mid = await get().internal_createMessage(assistantMessage);
|
|
313
313
|
|
|
314
314
|
// 2. fetch the AI response
|
|
315
315
|
const { isFunctionCall, content, functionCallAtEnd, functionCallContent, traceId } =
|
|
316
|
-
await
|
|
316
|
+
await internal_fetchAIChatMessage(messages, mid, params);
|
|
317
317
|
|
|
318
318
|
// 3. if it's the function call message, trigger the function method
|
|
319
319
|
if (isFunctionCall) {
|
|
@@ -323,7 +323,7 @@ export const chatMessage: StateCreator<
|
|
|
323
323
|
if (functionCallAtEnd) {
|
|
324
324
|
// create a new separate message and remove the function call from the prev message
|
|
325
325
|
|
|
326
|
-
await get().
|
|
326
|
+
await get().internal_updateMessageContent(mid, content.replace(functionCallContent, ''));
|
|
327
327
|
|
|
328
328
|
const functionMessage: CreateMessageParams = {
|
|
329
329
|
role: 'function',
|
|
@@ -337,14 +337,14 @@ export const chatMessage: StateCreator<
|
|
|
337
337
|
traceId,
|
|
338
338
|
};
|
|
339
339
|
|
|
340
|
-
functionId = await get().
|
|
340
|
+
functionId = await get().internal_createMessage(functionMessage);
|
|
341
341
|
}
|
|
342
342
|
|
|
343
343
|
await refreshMessages();
|
|
344
344
|
await triggerFunctionCall(functionId);
|
|
345
345
|
}
|
|
346
346
|
},
|
|
347
|
-
|
|
347
|
+
internal_dispatchMessage: (payload) => {
|
|
348
348
|
const { activeId } = get();
|
|
349
349
|
|
|
350
350
|
if (!activeId) return;
|
|
@@ -353,16 +353,16 @@ export const chatMessage: StateCreator<
|
|
|
353
353
|
|
|
354
354
|
set({ messages }, false, n(`dispatchMessage/${payload.type}`, payload));
|
|
355
355
|
},
|
|
356
|
-
|
|
356
|
+
internal_fetchAIChatMessage: async (messages, assistantId, params) => {
|
|
357
357
|
const {
|
|
358
|
-
|
|
358
|
+
internal_toggleChatLoading,
|
|
359
359
|
refreshMessages,
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
360
|
+
internal_updateMessageContent,
|
|
361
|
+
internal_dispatchMessage,
|
|
362
|
+
internal_createSmoothMessage,
|
|
363
363
|
} = get();
|
|
364
364
|
|
|
365
|
-
const abortController =
|
|
365
|
+
const abortController = internal_toggleChatLoading(
|
|
366
366
|
true,
|
|
367
367
|
assistantId,
|
|
368
368
|
n('generateMessage(start)', { assistantId, messages }) as string,
|
|
@@ -421,7 +421,7 @@ export const chatMessage: StateCreator<
|
|
|
421
421
|
let msgTraceId: string | undefined;
|
|
422
422
|
|
|
423
423
|
const { startAnimation, stopAnimation, outputQueue, isAnimationActive } =
|
|
424
|
-
|
|
424
|
+
internal_createSmoothMessage(assistantId);
|
|
425
425
|
|
|
426
426
|
await chatService.createAssistantMessageStream({
|
|
427
427
|
abortController,
|
|
@@ -465,7 +465,7 @@ export const chatMessage: StateCreator<
|
|
|
465
465
|
}
|
|
466
466
|
|
|
467
467
|
// update the content after fetch result
|
|
468
|
-
await
|
|
468
|
+
await internal_updateMessageContent(assistantId, content);
|
|
469
469
|
},
|
|
470
470
|
onMessageHandle: async (text) => {
|
|
471
471
|
output += text;
|
|
@@ -474,7 +474,7 @@ export const chatMessage: StateCreator<
|
|
|
474
474
|
// is this message is just a function call
|
|
475
475
|
if (isFunctionMessageAtStart(output)) {
|
|
476
476
|
stopAnimation();
|
|
477
|
-
|
|
477
|
+
internal_dispatchMessage({
|
|
478
478
|
id: assistantId,
|
|
479
479
|
key: 'content',
|
|
480
480
|
type: 'updateMessage',
|
|
@@ -490,7 +490,7 @@ export const chatMessage: StateCreator<
|
|
|
490
490
|
},
|
|
491
491
|
});
|
|
492
492
|
|
|
493
|
-
|
|
493
|
+
internal_toggleChatLoading(false, undefined, n('generateMessage(end)') as string);
|
|
494
494
|
|
|
495
495
|
// also exist message like this:
|
|
496
496
|
// 请稍等,我帮您查询一下。{"tool_calls":[{"id":"call_sbca","type":"function","function":{"name":"pluginName____apiName","arguments":{"key":"value"}}}]}
|
|
@@ -513,7 +513,7 @@ export const chatMessage: StateCreator<
|
|
|
513
513
|
traceId: msgTraceId,
|
|
514
514
|
};
|
|
515
515
|
},
|
|
516
|
-
|
|
516
|
+
internal_toggleChatLoading: (loading, id, action) => {
|
|
517
517
|
if (loading) {
|
|
518
518
|
window.addEventListener('beforeunload', preventLeavingFn);
|
|
519
519
|
|
|
@@ -527,7 +527,7 @@ export const chatMessage: StateCreator<
|
|
|
527
527
|
window.removeEventListener('beforeunload', preventLeavingFn);
|
|
528
528
|
}
|
|
529
529
|
},
|
|
530
|
-
|
|
530
|
+
internal_toggleMessageLoading: (loading, id) => {
|
|
531
531
|
set(
|
|
532
532
|
{
|
|
533
533
|
messageLoadingIds: produce(get().messageLoadingIds, (draft) => {
|
|
@@ -541,11 +541,11 @@ export const chatMessage: StateCreator<
|
|
|
541
541
|
}),
|
|
542
542
|
},
|
|
543
543
|
false,
|
|
544
|
-
'
|
|
544
|
+
'internal_toggleMessageLoading',
|
|
545
545
|
);
|
|
546
546
|
},
|
|
547
547
|
|
|
548
|
-
|
|
548
|
+
internal_resendMessage: async (messageId, traceId) => {
|
|
549
549
|
// 1. 构造所有相关的历史记录
|
|
550
550
|
const chats = chatSelectors.currentChats(get());
|
|
551
551
|
|
|
@@ -574,45 +574,45 @@ export const chatMessage: StateCreator<
|
|
|
574
574
|
|
|
575
575
|
if (contextMessages.length <= 0) return;
|
|
576
576
|
|
|
577
|
-
const {
|
|
577
|
+
const { internal_coreProcessMessage } = get();
|
|
578
578
|
|
|
579
579
|
const latestMsg = contextMessages.filter((s) => s.role === 'user').at(-1);
|
|
580
580
|
|
|
581
581
|
if (!latestMsg) return;
|
|
582
582
|
|
|
583
|
-
await
|
|
583
|
+
await internal_coreProcessMessage(contextMessages, latestMsg.id, { traceId });
|
|
584
584
|
},
|
|
585
585
|
|
|
586
|
-
|
|
587
|
-
const {
|
|
586
|
+
internal_updateMessageContent: async (id, content) => {
|
|
587
|
+
const { internal_dispatchMessage, refreshMessages } = get();
|
|
588
588
|
|
|
589
589
|
// Due to the async update method and refresh need about 100ms
|
|
590
590
|
// we need to update the message content at the frontend to avoid the update flick
|
|
591
591
|
// refs: https://medium.com/@kyledeguzmanx/what-are-optimistic-updates-483662c3e171
|
|
592
|
-
|
|
592
|
+
internal_dispatchMessage({ id, key: 'content', type: 'updateMessage', value: content });
|
|
593
593
|
|
|
594
594
|
await messageService.updateMessage(id, { content });
|
|
595
595
|
await refreshMessages();
|
|
596
596
|
},
|
|
597
597
|
|
|
598
|
-
|
|
599
|
-
const {
|
|
598
|
+
internal_createMessage: async (message) => {
|
|
599
|
+
const { internal_dispatchMessage, refreshMessages, internal_toggleMessageLoading } = get();
|
|
600
600
|
|
|
601
601
|
// use optimistic update to avoid the slow waiting
|
|
602
602
|
const tempId = 'tmp_' + nanoid();
|
|
603
|
-
|
|
603
|
+
internal_dispatchMessage({ type: 'createMessage', id: tempId, value: message });
|
|
604
604
|
|
|
605
|
-
|
|
605
|
+
internal_toggleMessageLoading(true, tempId);
|
|
606
606
|
const id = await messageService.createMessage(message);
|
|
607
607
|
|
|
608
608
|
await refreshMessages();
|
|
609
|
-
|
|
609
|
+
internal_toggleMessageLoading(false, tempId);
|
|
610
610
|
|
|
611
611
|
return id;
|
|
612
612
|
},
|
|
613
613
|
|
|
614
|
-
|
|
615
|
-
const {
|
|
614
|
+
internal_createSmoothMessage: (id) => {
|
|
615
|
+
const { internal_dispatchMessage } = get();
|
|
616
616
|
|
|
617
617
|
let buffer = '';
|
|
618
618
|
// why use queue: https://shareg.pt/GLBrjpK
|
|
@@ -658,7 +658,7 @@ export const chatMessage: StateCreator<
|
|
|
658
658
|
buffer += charsToAdd;
|
|
659
659
|
|
|
660
660
|
// 更新消息内容,这里可能需要结合实际情况调整
|
|
661
|
-
|
|
661
|
+
internal_dispatchMessage({ id, key: 'content', type: 'updateMessage', value: buffer });
|
|
662
662
|
|
|
663
663
|
// 设置下一个字符的延迟
|
|
664
664
|
animationTimeoutId = setTimeout(updateText, 16); // 16 毫秒的延迟模拟打字机效果
|
|
@@ -676,7 +676,7 @@ export const chatMessage: StateCreator<
|
|
|
676
676
|
return { startAnimation, stopAnimation, outputQueue, isAnimationActive };
|
|
677
677
|
},
|
|
678
678
|
|
|
679
|
-
|
|
679
|
+
internal_traceMessage: async (id, payload) => {
|
|
680
680
|
// tracing the diff of update
|
|
681
681
|
const message = chatSelectors.getMessageById(id)(get());
|
|
682
682
|
if (!message) return;
|
|
@@ -37,7 +37,7 @@ describe('ChatPluginAction', () => {
|
|
|
37
37
|
// 设置初始状态
|
|
38
38
|
const initialState = {
|
|
39
39
|
messages: [],
|
|
40
|
-
|
|
40
|
+
internal_coreProcessMessage: vi.fn(),
|
|
41
41
|
refreshMessages: vi.fn(),
|
|
42
42
|
};
|
|
43
43
|
useChatStore.setState(initialState);
|
|
@@ -51,14 +51,14 @@ describe('ChatPluginAction', () => {
|
|
|
51
51
|
await result.current.fillPluginMessageContent(messageId, newContent, true);
|
|
52
52
|
});
|
|
53
53
|
|
|
54
|
-
// 验证 messageService.
|
|
54
|
+
// 验证 messageService.internal_updateMessageContent 是否被正确调用
|
|
55
55
|
expect(messageService.updateMessage).toHaveBeenCalledWith(messageId, { content: newContent });
|
|
56
56
|
|
|
57
57
|
// 验证 refreshMessages 是否被调用
|
|
58
58
|
expect(result.current.refreshMessages).toHaveBeenCalled();
|
|
59
59
|
|
|
60
60
|
// 验证 coreProcessMessage 是否被正确调用
|
|
61
|
-
expect(result.current.
|
|
61
|
+
expect(result.current.internal_coreProcessMessage).toHaveBeenCalledWith(
|
|
62
62
|
mockCurrentChats,
|
|
63
63
|
messageId,
|
|
64
64
|
{},
|
|
@@ -86,14 +86,14 @@ describe('ChatPluginAction', () => {
|
|
|
86
86
|
await result.current.fillPluginMessageContent(messageId, newContent);
|
|
87
87
|
});
|
|
88
88
|
|
|
89
|
-
// 验证 messageService.
|
|
89
|
+
// 验证 messageService.internal_updateMessageContent 是否被正确调用
|
|
90
90
|
expect(messageService.updateMessage).toHaveBeenCalledWith(messageId, { content: newContent });
|
|
91
91
|
|
|
92
92
|
// 验证 refreshMessages 是否被调用
|
|
93
93
|
expect(result.current.refreshMessages).toHaveBeenCalled();
|
|
94
94
|
|
|
95
95
|
// 验证 coreProcessMessage 没有被正确调用
|
|
96
|
-
expect(result.current.
|
|
96
|
+
expect(result.current.internal_coreProcessMessage).not.toHaveBeenCalled();
|
|
97
97
|
});
|
|
98
98
|
});
|
|
99
99
|
|
|
@@ -107,7 +107,7 @@ describe('ChatPluginAction', () => {
|
|
|
107
107
|
|
|
108
108
|
vi.spyOn(storeState, 'refreshMessages');
|
|
109
109
|
vi.spyOn(storeState, 'triggerAIMessage').mockResolvedValue(undefined);
|
|
110
|
-
vi.spyOn(storeState, '
|
|
110
|
+
vi.spyOn(storeState, 'internal_toggleChatLoading').mockReturnValue(undefined);
|
|
111
111
|
|
|
112
112
|
const runSpy = vi.spyOn(chatService, 'runPluginApi').mockResolvedValue({
|
|
113
113
|
text: pluginApiResponse,
|
|
@@ -120,7 +120,7 @@ describe('ChatPluginAction', () => {
|
|
|
120
120
|
await result.current.invokeDefaultTypePlugin(messageId, pluginPayload);
|
|
121
121
|
});
|
|
122
122
|
|
|
123
|
-
expect(storeState.
|
|
123
|
+
expect(storeState.internal_toggleChatLoading).toHaveBeenCalledWith(
|
|
124
124
|
true,
|
|
125
125
|
messageId,
|
|
126
126
|
expect.any(String),
|
|
@@ -131,7 +131,7 @@ describe('ChatPluginAction', () => {
|
|
|
131
131
|
});
|
|
132
132
|
expect(storeState.refreshMessages).toHaveBeenCalled();
|
|
133
133
|
expect(storeState.triggerAIMessage).toHaveBeenCalled();
|
|
134
|
-
expect(storeState.
|
|
134
|
+
expect(storeState.internal_toggleChatLoading).toHaveBeenCalledWith(false);
|
|
135
135
|
});
|
|
136
136
|
|
|
137
137
|
it('should handle errors when the plugin API call fails', async () => {
|
|
@@ -142,7 +142,7 @@ describe('ChatPluginAction', () => {
|
|
|
142
142
|
const storeState = useChatStore.getState();
|
|
143
143
|
vi.spyOn(storeState, 'refreshMessages');
|
|
144
144
|
vi.spyOn(storeState, 'triggerAIMessage').mockResolvedValue(undefined);
|
|
145
|
-
vi.spyOn(storeState, '
|
|
145
|
+
vi.spyOn(storeState, 'internal_toggleChatLoading').mockReturnValue(undefined);
|
|
146
146
|
|
|
147
147
|
vi.spyOn(chatService, 'runPluginApi').mockRejectedValue(error);
|
|
148
148
|
|
|
@@ -151,7 +151,7 @@ describe('ChatPluginAction', () => {
|
|
|
151
151
|
await result.current.invokeDefaultTypePlugin(messageId, pluginPayload);
|
|
152
152
|
});
|
|
153
153
|
|
|
154
|
-
expect(storeState.
|
|
154
|
+
expect(storeState.internal_toggleChatLoading).toHaveBeenCalledWith(
|
|
155
155
|
true,
|
|
156
156
|
messageId,
|
|
157
157
|
expect.any(String),
|
|
@@ -159,7 +159,7 @@ describe('ChatPluginAction', () => {
|
|
|
159
159
|
expect(chatService.runPluginApi).toHaveBeenCalledWith(pluginPayload, { trace: {} });
|
|
160
160
|
expect(messageService.updateMessageError).toHaveBeenCalledWith(messageId, error);
|
|
161
161
|
expect(storeState.refreshMessages).toHaveBeenCalled();
|
|
162
|
-
expect(storeState.
|
|
162
|
+
expect(storeState.internal_toggleChatLoading).toHaveBeenCalledWith(false);
|
|
163
163
|
expect(storeState.triggerAIMessage).not.toHaveBeenCalled(); // 确保在错误情况下不调用此方法
|
|
164
164
|
});
|
|
165
165
|
});
|
|
@@ -512,8 +512,8 @@ describe('ChatPluginAction', () => {
|
|
|
512
512
|
});
|
|
513
513
|
|
|
514
514
|
useChatStore.setState({
|
|
515
|
-
|
|
516
|
-
|
|
515
|
+
internal_toggleChatLoading: vi.fn(),
|
|
516
|
+
internal_updateMessageContent: vi.fn(),
|
|
517
517
|
text2image: vi.fn(),
|
|
518
518
|
});
|
|
519
519
|
|
|
@@ -530,18 +530,18 @@ describe('ChatPluginAction', () => {
|
|
|
530
530
|
);
|
|
531
531
|
|
|
532
532
|
// Verify that the message content was updated with the tool response
|
|
533
|
-
expect(result.current.
|
|
533
|
+
expect(result.current.internal_updateMessageContent).toHaveBeenCalledWith(
|
|
534
534
|
messageId,
|
|
535
535
|
toolResponse,
|
|
536
536
|
);
|
|
537
537
|
|
|
538
538
|
// Verify that loading was toggled correctly
|
|
539
|
-
expect(result.current.
|
|
539
|
+
expect(result.current.internal_toggleChatLoading).toHaveBeenCalledWith(
|
|
540
540
|
true,
|
|
541
541
|
messageId,
|
|
542
542
|
expect.any(String),
|
|
543
543
|
);
|
|
544
|
-
expect(result.current.
|
|
544
|
+
expect(result.current.internal_toggleChatLoading).toHaveBeenCalledWith(false);
|
|
545
545
|
expect(useChatStore.getState().text2image).toHaveBeenCalled();
|
|
546
546
|
});
|
|
547
547
|
|
|
@@ -561,9 +561,9 @@ describe('ChatPluginAction', () => {
|
|
|
561
561
|
});
|
|
562
562
|
|
|
563
563
|
useChatStore.setState({
|
|
564
|
-
|
|
564
|
+
internal_toggleChatLoading: vi.fn(),
|
|
565
565
|
text2image: vi.fn(),
|
|
566
|
-
|
|
566
|
+
internal_updateMessageContent: vi.fn(),
|
|
567
567
|
});
|
|
568
568
|
});
|
|
569
569
|
const { result } = renderHook(() => useChatStore());
|
|
@@ -579,18 +579,18 @@ describe('ChatPluginAction', () => {
|
|
|
579
579
|
);
|
|
580
580
|
|
|
581
581
|
// Verify that the message content was updated with the tool response
|
|
582
|
-
expect(result.current.
|
|
582
|
+
expect(result.current.internal_updateMessageContent).toHaveBeenCalledWith(
|
|
583
583
|
messageId,
|
|
584
584
|
toolResponse,
|
|
585
585
|
);
|
|
586
586
|
|
|
587
587
|
// Verify that loading was toggled correctly
|
|
588
|
-
expect(result.current.
|
|
588
|
+
expect(result.current.internal_toggleChatLoading).toHaveBeenCalledWith(
|
|
589
589
|
true,
|
|
590
590
|
messageId,
|
|
591
591
|
expect.any(String),
|
|
592
592
|
);
|
|
593
|
-
expect(result.current.
|
|
593
|
+
expect(result.current.internal_toggleChatLoading).toHaveBeenCalledWith(false);
|
|
594
594
|
expect(useChatStore.getState().text2image).not.toHaveBeenCalled();
|
|
595
595
|
});
|
|
596
596
|
|
|
@@ -608,8 +608,8 @@ describe('ChatPluginAction', () => {
|
|
|
608
608
|
});
|
|
609
609
|
|
|
610
610
|
useChatStore.setState({
|
|
611
|
-
|
|
612
|
-
|
|
611
|
+
internal_toggleChatLoading: vi.fn(),
|
|
612
|
+
internal_updateMessageContent: vi.fn(),
|
|
613
613
|
text2image: vi.fn(),
|
|
614
614
|
refreshMessages: vi.fn(),
|
|
615
615
|
});
|
|
@@ -621,15 +621,15 @@ describe('ChatPluginAction', () => {
|
|
|
621
621
|
});
|
|
622
622
|
|
|
623
623
|
// Verify that loading was toggled correctly
|
|
624
|
-
expect(result.current.
|
|
624
|
+
expect(result.current.internal_toggleChatLoading).toHaveBeenCalledWith(
|
|
625
625
|
true,
|
|
626
626
|
messageId,
|
|
627
627
|
expect.any(String),
|
|
628
628
|
);
|
|
629
|
-
expect(result.current.
|
|
629
|
+
expect(result.current.internal_toggleChatLoading).toHaveBeenCalledWith(false);
|
|
630
630
|
|
|
631
631
|
// Verify that the message content was not updated
|
|
632
|
-
expect(result.current.
|
|
632
|
+
expect(result.current.internal_updateMessageContent).not.toHaveBeenCalled();
|
|
633
633
|
|
|
634
634
|
// Verify that messages were not refreshed
|
|
635
635
|
expect(result.current.refreshMessages).not.toHaveBeenCalled();
|
|
@@ -54,28 +54,28 @@ export const chatPlugin: StateCreator<
|
|
|
54
54
|
},
|
|
55
55
|
|
|
56
56
|
fillPluginMessageContent: async (id, content, triggerAiMessage) => {
|
|
57
|
-
const { triggerAIMessage,
|
|
57
|
+
const { triggerAIMessage, internal_updateMessageContent } = get();
|
|
58
58
|
|
|
59
|
-
await
|
|
59
|
+
await internal_updateMessageContent(id, content);
|
|
60
60
|
|
|
61
61
|
if (triggerAiMessage) await triggerAIMessage(id);
|
|
62
62
|
},
|
|
63
63
|
|
|
64
64
|
invokeBuiltinTool: async (id, payload) => {
|
|
65
|
-
const {
|
|
65
|
+
const { internal_toggleChatLoading, internal_updateMessageContent } = get();
|
|
66
66
|
const params = JSON.parse(payload.arguments);
|
|
67
|
-
|
|
67
|
+
internal_toggleChatLoading(true, id, n('invokeBuiltinTool') as string);
|
|
68
68
|
let data;
|
|
69
69
|
try {
|
|
70
70
|
data = await useToolStore.getState().invokeBuiltinTool(payload.apiName, params);
|
|
71
71
|
} catch (error) {
|
|
72
72
|
console.log(error);
|
|
73
73
|
}
|
|
74
|
-
|
|
74
|
+
internal_toggleChatLoading(false);
|
|
75
75
|
|
|
76
76
|
if (!data) return;
|
|
77
77
|
|
|
78
|
-
await
|
|
78
|
+
await internal_updateMessageContent(id, data);
|
|
79
79
|
|
|
80
80
|
// postToolCalling
|
|
81
81
|
// @ts-ignore
|
|
@@ -131,11 +131,11 @@ export const chatPlugin: StateCreator<
|
|
|
131
131
|
},
|
|
132
132
|
|
|
133
133
|
runPluginApi: async (id, payload) => {
|
|
134
|
-
const {
|
|
134
|
+
const { internal_updateMessageContent, refreshMessages, internal_toggleChatLoading } = get();
|
|
135
135
|
let data: string;
|
|
136
136
|
|
|
137
137
|
try {
|
|
138
|
-
const abortController =
|
|
138
|
+
const abortController = internal_toggleChatLoading(true, id, n('fetchPlugin') as string);
|
|
139
139
|
|
|
140
140
|
const message = chatSelectors.getMessageById(id)(get());
|
|
141
141
|
|
|
@@ -162,19 +162,19 @@ export const chatPlugin: StateCreator<
|
|
|
162
162
|
data = '';
|
|
163
163
|
}
|
|
164
164
|
|
|
165
|
-
|
|
165
|
+
internal_toggleChatLoading(false);
|
|
166
166
|
// 如果报错则结束了
|
|
167
167
|
if (!data) return;
|
|
168
168
|
|
|
169
|
-
await
|
|
169
|
+
await internal_updateMessageContent(id, data);
|
|
170
170
|
|
|
171
171
|
return data;
|
|
172
172
|
},
|
|
173
173
|
|
|
174
174
|
triggerAIMessage: async (id, traceId) => {
|
|
175
|
-
const {
|
|
175
|
+
const { internal_coreProcessMessage } = get();
|
|
176
176
|
const chats = chatSelectors.currentChats(get());
|
|
177
|
-
await
|
|
177
|
+
await internal_coreProcessMessage(chats, id, { traceId });
|
|
178
178
|
},
|
|
179
179
|
|
|
180
180
|
triggerFunctionCall: async (id) => {
|
|
@@ -187,7 +187,7 @@ export const chatPlugin: StateCreator<
|
|
|
187
187
|
invokeStandaloneTypePlugin,
|
|
188
188
|
invokeBuiltinTool,
|
|
189
189
|
refreshMessages,
|
|
190
|
-
|
|
190
|
+
internal_resendMessage,
|
|
191
191
|
deleteMessage,
|
|
192
192
|
} = get();
|
|
193
193
|
|
|
@@ -213,7 +213,7 @@ export const chatPlugin: StateCreator<
|
|
|
213
213
|
|
|
214
214
|
// fix https://github.com/lobehub/lobe-chat/issues/1094, remove and retry after experiencing plugin illusion
|
|
215
215
|
if (!apiName) {
|
|
216
|
-
|
|
216
|
+
internal_resendMessage(id);
|
|
217
217
|
deleteMessage(id);
|
|
218
218
|
return;
|
|
219
219
|
}
|
|
@@ -60,7 +60,7 @@ describe('chatToolSlice', () => {
|
|
|
60
60
|
draft[0].previewUrl = 'new-url';
|
|
61
61
|
draft[0].imageId = 'new-id';
|
|
62
62
|
};
|
|
63
|
-
vi.spyOn(result.current, '
|
|
63
|
+
vi.spyOn(result.current, 'internal_updateMessageContent');
|
|
64
64
|
|
|
65
65
|
// 模拟 getMessageById 返回消息内容
|
|
66
66
|
vi.spyOn(chatSelectors, 'getMessageById').mockImplementationOnce(
|
|
@@ -75,8 +75,8 @@ describe('chatToolSlice', () => {
|
|
|
75
75
|
await result.current.updateImageItem(messageId, updateFunction);
|
|
76
76
|
});
|
|
77
77
|
|
|
78
|
-
// 验证
|
|
79
|
-
expect(result.current.
|
|
78
|
+
// 验证 internal_updateMessageContent 是否被正确调用以更新内容
|
|
79
|
+
expect(result.current.internal_updateMessageContent).toHaveBeenCalledWith(
|
|
80
80
|
messageId,
|
|
81
81
|
JSON.stringify([{ prompt: 'test prompt', previewUrl: 'new-url', imageId: 'new-id' }]),
|
|
82
82
|
);
|
|
@@ -81,6 +81,6 @@ export const chatToolSlice: StateCreator<
|
|
|
81
81
|
const data: DallEImageItem[] = JSON.parse(message.content);
|
|
82
82
|
|
|
83
83
|
const nextContent = produce(data, updater);
|
|
84
|
-
await get().
|
|
84
|
+
await get().internal_updateMessageContent(id, JSON.stringify(nextContent));
|
|
85
85
|
},
|
|
86
86
|
});
|