@lobehub/lobehub 2.0.0-next.8 → 2.0.0-next.9
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/.github/workflows/desktop-pr-build.yml +8 -8
- package/.github/workflows/docker.yml +17 -16
- package/.github/workflows/e2e.yml +3 -3
- package/.github/workflows/release-desktop-beta.yml +8 -8
- package/.github/workflows/release.yml +1 -1
- package/.github/workflows/test.yml +4 -4
- package/CHANGELOG.md +25 -0
- package/changelog/v1.json +9 -0
- package/package.json +1 -1
- package/packages/const/src/index.ts +0 -1
- package/packages/const/src/url.ts +1 -4
- package/packages/context-engine/src/index.ts +1 -6
- package/packages/context-engine/src/processors/GroupMessageFlatten.ts +12 -2
- package/packages/context-engine/src/processors/__tests__/GroupMessageFlatten.test.ts +73 -9
- package/packages/context-engine/src/providers/index.ts +0 -2
- package/packages/database/package.json +1 -1
- package/packages/database/src/models/__tests__/message.grouping.test.ts +812 -0
- package/packages/database/src/models/__tests__/message.test.ts +322 -170
- package/packages/database/src/models/message.ts +62 -24
- package/packages/database/src/utils/__tests__/groupMessages.test.ts +145 -2
- package/packages/database/src/utils/groupMessages.ts +7 -5
- package/packages/types/src/message/common/base.ts +13 -0
- package/packages/types/src/message/common/image.ts +8 -0
- package/packages/types/src/message/common/metadata.ts +39 -0
- package/packages/types/src/message/common/tools.ts +10 -0
- package/packages/types/src/message/db/params.ts +47 -1
- package/packages/types/src/message/ui/chat.ts +4 -1
- package/packages/types/src/search.ts +16 -0
- package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/V1Mobile/index.tsx +2 -2
- package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/V1Mobile/useSend.ts +6 -4
- package/src/app/[variants]/(main)/chat/(workspace)/@conversation/features/ChatInput/useSend.ts +15 -10
- package/src/app/[variants]/(main)/chat/@session/features/SessionListContent/List/Item/index.tsx +4 -2
- package/src/components/Thinking/index.tsx +4 -3
- package/src/features/AgentSetting/AgentPlugin/index.tsx +2 -2
- package/src/features/ChatInput/ActionBar/STT/browser.tsx +2 -2
- package/src/features/ChatInput/ActionBar/STT/openai.tsx +2 -2
- package/src/features/ChatInput/ActionBar/Tools/useControls.tsx +1 -3
- package/src/features/Conversation/Error/ErrorJsonViewer.tsx +4 -3
- package/src/features/Conversation/Error/OllamaBizError/index.tsx +7 -2
- package/src/features/Conversation/Error/index.tsx +15 -5
- package/src/features/Conversation/MarkdownElements/LobeArtifact/Render/index.tsx +2 -2
- package/src/features/Conversation/Messages/Assistant/Extra/index.tsx +2 -2
- package/src/features/Conversation/Messages/Assistant/MessageContent.tsx +5 -3
- package/src/features/Conversation/Messages/Assistant/Tool/Inspector/BuiltinPluginTitle.tsx +2 -2
- package/src/features/Conversation/Messages/Assistant/Tool/Inspector/ToolTitle.tsx +4 -2
- package/src/features/Conversation/Messages/Assistant/Tool/Render/CustomRender.tsx +2 -2
- package/src/features/Conversation/Messages/Assistant/Tool/Render/index.tsx +2 -2
- package/src/features/Conversation/Messages/Assistant/Tool/index.tsx +2 -2
- package/src/features/Conversation/Messages/Assistant/index.tsx +4 -4
- package/src/features/Conversation/Messages/Default.tsx +2 -2
- package/src/features/Conversation/Messages/User/Extra.tsx +2 -2
- package/src/features/Conversation/Messages/User/index.tsx +4 -4
- package/src/features/Conversation/Messages/index.tsx +3 -3
- package/src/features/Conversation/components/AutoScroll.tsx +2 -2
- package/src/features/Conversation/components/Extras/Usage/UsageDetail/index.tsx +9 -6
- package/src/features/PluginTag/index.tsx +1 -3
- package/src/features/PluginsUI/Render/BuiltinType/index.test.tsx +37 -28
- package/src/features/Portal/Artifacts/Body/index.tsx +2 -2
- package/src/server/modules/ModelRuntime/trace.ts +11 -4
- package/src/server/routers/lambda/message.ts +14 -3
- package/src/services/chat/chat.test.ts +1 -40
- package/src/services/chat/contextEngineering.test.ts +0 -30
- package/src/services/chat/contextEngineering.ts +1 -12
- package/src/services/chat/index.ts +2 -7
- package/src/services/chat/types.ts +1 -1
- package/src/services/message/_deprecated.ts +1 -1
- package/src/services/message/client.ts +8 -2
- package/src/services/message/server.ts +7 -2
- package/src/services/message/type.ts +6 -1
- package/src/store/chat/helpers.test.ts +99 -0
- package/src/store/chat/helpers.ts +21 -2
- package/src/store/chat/selectors.ts +1 -1
- package/src/store/chat/slices/aiChat/actions/generateAIChat.ts +3 -3
- package/src/store/chat/slices/builtinTool/actions/index.ts +1 -4
- package/src/store/chat/slices/message/action.test.ts +5 -1
- package/src/store/chat/slices/message/action.ts +102 -14
- package/src/store/chat/slices/message/reducer.test.ts +363 -5
- package/src/store/chat/slices/message/reducer.ts +87 -3
- package/src/store/chat/slices/message/{selectors.test.ts → selectors/chat.test.ts} +266 -30
- package/src/store/chat/slices/message/{selectors.ts → selectors/chat.ts} +29 -79
- package/src/store/chat/slices/message/selectors/index.ts +2 -0
- package/src/store/chat/slices/message/selectors/messageState.test.ts +36 -0
- package/src/store/chat/slices/message/selectors/messageState.ts +80 -0
- package/src/store/chat/slices/plugin/action.test.ts +34 -132
- package/src/store/chat/slices/plugin/action.ts +1 -44
- package/src/store/tool/selectors/tool.test.ts +1 -1
- package/src/store/tool/selectors/tool.ts +6 -8
- package/src/store/tool/slices/builtin/action.test.ts +83 -35
- package/src/store/tool/slices/builtin/action.ts +0 -9
- package/src/store/tool/slices/builtin/selectors.test.ts +4 -30
- package/src/store/tool/slices/builtin/selectors.ts +15 -21
- package/src/tools/index.ts +0 -6
- package/src/tools/renders.ts +0 -3
- package/src/tools/web-browsing/Portal/Search/Footer.tsx +2 -2
- package/packages/const/src/guide.ts +0 -89
- package/packages/context-engine/src/providers/InboxGuide.ts +0 -102
- package/packages/context-engine/src/providers/__tests__/InboxGuideProvider.test.ts +0 -121
- package/src/services/chat/__snapshots__/chat.test.ts.snap +0 -110
- package/src/store/chat/slices/builtinTool/actions/__tests__/dalle.test.ts +0 -121
- package/src/store/chat/slices/builtinTool/actions/dalle.ts +0 -124
- package/src/tools/dalle/Render/GalleyGrid.tsx +0 -60
- package/src/tools/dalle/Render/Item/EditMode.tsx +0 -66
- package/src/tools/dalle/Render/Item/Error.tsx +0 -49
- package/src/tools/dalle/Render/Item/Image.tsx +0 -44
- package/src/tools/dalle/Render/Item/ImageFileItem.tsx +0 -57
- package/src/tools/dalle/Render/Item/index.tsx +0 -88
- package/src/tools/dalle/Render/ToolBar.tsx +0 -56
- package/src/tools/dalle/Render/index.tsx +0 -52
- package/src/tools/dalle/index.ts +0 -92
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { ChatStoreState } from '../../../initialState';
|
|
2
|
+
import { getMessageByToolCallId, mainDisplayChatIDs } from './chat';
|
|
3
|
+
|
|
4
|
+
const isMessageEditing = (id: string) => (s: ChatStoreState) => s.messageEditingIds.includes(id);
|
|
5
|
+
const isMessageLoading = (id: string) => (s: ChatStoreState) => s.messageLoadingIds.includes(id);
|
|
6
|
+
|
|
7
|
+
const isMessageGenerating = (id: string) => (s: ChatStoreState) => s.chatLoadingIds.includes(id);
|
|
8
|
+
const isMessageInRAGFlow = (id: string) => (s: ChatStoreState) =>
|
|
9
|
+
s.messageRAGLoadingIds.includes(id);
|
|
10
|
+
const isMessageInChatReasoning = (id: string) => (s: ChatStoreState) =>
|
|
11
|
+
s.reasoningLoadingIds.includes(id);
|
|
12
|
+
|
|
13
|
+
const isPluginApiInvoking = (id: string) => (s: ChatStoreState) =>
|
|
14
|
+
s.pluginApiLoadingIds.includes(id);
|
|
15
|
+
|
|
16
|
+
const isToolCallStreaming = (id: string, index: number) => (s: ChatStoreState) => {
|
|
17
|
+
const isLoading = s.toolCallingStreamIds[id];
|
|
18
|
+
|
|
19
|
+
if (!isLoading) return false;
|
|
20
|
+
|
|
21
|
+
return isLoading[index];
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const isInToolsCalling = (id: string, index: number) => (s: ChatStoreState) => {
|
|
25
|
+
const isStreamingToolsCalling = isToolCallStreaming(id, index)(s);
|
|
26
|
+
|
|
27
|
+
const isInvokingPluginApi = s.messageInToolsCallingIds.includes(id);
|
|
28
|
+
|
|
29
|
+
return isStreamingToolsCalling || isInvokingPluginApi;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const isToolApiNameShining =
|
|
33
|
+
(messageId: string, index: number, toolCallId: string) => (s: ChatStoreState) => {
|
|
34
|
+
const toolMessageId = getMessageByToolCallId(toolCallId)(s)?.id;
|
|
35
|
+
const isStreaming = isToolCallStreaming(messageId, index)(s);
|
|
36
|
+
const isPluginInvoking = !toolMessageId ? true : isPluginApiInvoking(toolMessageId)(s);
|
|
37
|
+
|
|
38
|
+
return isStreaming || isPluginInvoking;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const isAIGenerating = (s: ChatStoreState) =>
|
|
42
|
+
s.chatLoadingIds.some((id) => mainDisplayChatIDs(s).includes(id));
|
|
43
|
+
|
|
44
|
+
const isInRAGFlow = (s: ChatStoreState) =>
|
|
45
|
+
s.messageRAGLoadingIds.some((id) => mainDisplayChatIDs(s).includes(id));
|
|
46
|
+
|
|
47
|
+
const isCreatingMessage = (s: ChatStoreState) => s.isCreatingMessage;
|
|
48
|
+
|
|
49
|
+
const isHasMessageLoading = (s: ChatStoreState) =>
|
|
50
|
+
s.messageLoadingIds.some((id) => mainDisplayChatIDs(s).includes(id));
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* this function is used to determine whether the send button should be disabled
|
|
54
|
+
*/
|
|
55
|
+
const isSendButtonDisabledByMessage = (s: ChatStoreState) =>
|
|
56
|
+
// 1. when there is message loading
|
|
57
|
+
isHasMessageLoading(s) ||
|
|
58
|
+
// 2. when is creating the topic
|
|
59
|
+
s.creatingTopic ||
|
|
60
|
+
// 3. when is creating the message
|
|
61
|
+
isCreatingMessage(s) ||
|
|
62
|
+
// 4. when the message is in RAG flow
|
|
63
|
+
isInRAGFlow(s);
|
|
64
|
+
|
|
65
|
+
export const messageStateSelectors = {
|
|
66
|
+
isAIGenerating,
|
|
67
|
+
isCreatingMessage,
|
|
68
|
+
isHasMessageLoading,
|
|
69
|
+
isInRAGFlow,
|
|
70
|
+
isInToolsCalling,
|
|
71
|
+
isMessageEditing,
|
|
72
|
+
isMessageGenerating,
|
|
73
|
+
isMessageInChatReasoning,
|
|
74
|
+
isMessageInRAGFlow,
|
|
75
|
+
isMessageLoading,
|
|
76
|
+
isPluginApiInvoking,
|
|
77
|
+
isSendButtonDisabledByMessage,
|
|
78
|
+
isToolApiNameShining,
|
|
79
|
+
isToolCallStreaming,
|
|
80
|
+
};
|
|
@@ -171,7 +171,11 @@ describe('ChatPluginAction', () => {
|
|
|
171
171
|
});
|
|
172
172
|
|
|
173
173
|
// 验证 messageService.internal_updateMessageContent 是否被正确调用
|
|
174
|
-
expect(messageService.updateMessage).toHaveBeenCalledWith(
|
|
174
|
+
expect(messageService.updateMessage).toHaveBeenCalledWith(
|
|
175
|
+
messageId,
|
|
176
|
+
{ content: newContent },
|
|
177
|
+
{ sessionId: 'inbox', topicId: null },
|
|
178
|
+
);
|
|
175
179
|
|
|
176
180
|
// 验证 refreshMessages 是否被调用
|
|
177
181
|
expect(result.current.refreshMessages).toHaveBeenCalled();
|
|
@@ -207,7 +211,11 @@ describe('ChatPluginAction', () => {
|
|
|
207
211
|
});
|
|
208
212
|
|
|
209
213
|
// 验证 messageService.internal_updateMessageContent 是否被正确调用
|
|
210
|
-
expect(messageService.updateMessage).toHaveBeenCalledWith(
|
|
214
|
+
expect(messageService.updateMessage).toHaveBeenCalledWith(
|
|
215
|
+
messageId,
|
|
216
|
+
{ content: newContent },
|
|
217
|
+
{ sessionId: 'inbox', topicId: null },
|
|
218
|
+
);
|
|
211
219
|
|
|
212
220
|
// 验证 refreshMessages 是否被调用
|
|
213
221
|
expect(result.current.refreshMessages).toHaveBeenCalled();
|
|
@@ -246,9 +254,11 @@ describe('ChatPluginAction', () => {
|
|
|
246
254
|
expect.any(String),
|
|
247
255
|
);
|
|
248
256
|
expect(runSpy).toHaveBeenCalledWith(pluginPayload, { signal: undefined, trace: {} });
|
|
249
|
-
expect(messageService.updateMessage).toHaveBeenCalledWith(
|
|
250
|
-
|
|
251
|
-
|
|
257
|
+
expect(messageService.updateMessage).toHaveBeenCalledWith(
|
|
258
|
+
messageId,
|
|
259
|
+
{ content: pluginApiResponse },
|
|
260
|
+
{ sessionId: 'inbox', topicId: null },
|
|
261
|
+
);
|
|
252
262
|
expect(storeState.refreshMessages).toHaveBeenCalled();
|
|
253
263
|
expect(storeState.internal_togglePluginApiCalling).toHaveBeenCalledWith(
|
|
254
264
|
false,
|
|
@@ -602,24 +612,18 @@ describe('ChatPluginAction', () => {
|
|
|
602
612
|
});
|
|
603
613
|
|
|
604
614
|
describe('invokeBuiltinTool', () => {
|
|
605
|
-
it('should invoke
|
|
615
|
+
it('should invoke the builtin tool action with parsed arguments', async () => {
|
|
606
616
|
const payload = {
|
|
607
|
-
apiName: '
|
|
608
|
-
arguments: JSON.stringify({
|
|
617
|
+
apiName: 'mockBuiltinAction',
|
|
618
|
+
arguments: JSON.stringify({ input: 'test', value: 123 }),
|
|
609
619
|
} as ChatToolPayload;
|
|
610
620
|
|
|
611
621
|
const messageId = 'message-id';
|
|
612
|
-
const
|
|
613
|
-
|
|
614
|
-
useToolStore.setState({
|
|
615
|
-
transformApiArgumentsToAiState: vi.fn().mockResolvedValue(toolResponse),
|
|
616
|
-
});
|
|
622
|
+
const mockActionFn = vi.fn().mockResolvedValue(undefined);
|
|
617
623
|
|
|
618
624
|
useChatStore.setState({
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
text2image: vi.fn(),
|
|
622
|
-
});
|
|
625
|
+
mockBuiltinAction: mockActionFn,
|
|
626
|
+
} as any);
|
|
623
627
|
|
|
624
628
|
const { result } = renderHook(() => useChatStore());
|
|
625
629
|
|
|
@@ -627,114 +631,39 @@ describe('ChatPluginAction', () => {
|
|
|
627
631
|
await result.current.invokeBuiltinTool(messageId, payload);
|
|
628
632
|
});
|
|
629
633
|
|
|
630
|
-
// Verify that the builtin
|
|
631
|
-
expect(
|
|
632
|
-
payload.apiName,
|
|
633
|
-
JSON.parse(payload.arguments),
|
|
634
|
-
);
|
|
635
|
-
|
|
636
|
-
// Verify that the message content was updated with the tool response
|
|
637
|
-
expect(result.current.internal_updateMessageContent).toHaveBeenCalledWith(
|
|
638
|
-
messageId,
|
|
639
|
-
toolResponse,
|
|
640
|
-
);
|
|
641
|
-
|
|
642
|
-
// Verify that loading was toggled correctly
|
|
643
|
-
expect(result.current.internal_togglePluginApiCalling).toHaveBeenCalledTimes(2);
|
|
644
|
-
|
|
645
|
-
expect(result.current.internal_togglePluginApiCalling).toHaveBeenNthCalledWith(
|
|
646
|
-
1,
|
|
647
|
-
true,
|
|
648
|
-
messageId,
|
|
649
|
-
expect.any(String),
|
|
650
|
-
);
|
|
651
|
-
expect(result.current.internal_togglePluginApiCalling).toHaveBeenNthCalledWith(
|
|
652
|
-
2,
|
|
653
|
-
false,
|
|
654
|
-
messageId,
|
|
655
|
-
expect.any(String),
|
|
656
|
-
);
|
|
657
|
-
expect(useChatStore.getState().text2image).toHaveBeenCalled();
|
|
634
|
+
// Verify that the builtin action was called with correct arguments
|
|
635
|
+
expect(mockActionFn).toHaveBeenCalledWith(messageId, { input: 'test', value: 123 });
|
|
658
636
|
});
|
|
659
637
|
|
|
660
|
-
it('should invoke
|
|
638
|
+
it('should not invoke action if apiName does not exist in store', async () => {
|
|
661
639
|
const payload = {
|
|
662
|
-
apiName: '
|
|
640
|
+
apiName: 'nonExistentAction',
|
|
663
641
|
arguments: JSON.stringify({ key: 'value' }),
|
|
664
642
|
} as ChatToolPayload;
|
|
665
643
|
|
|
666
644
|
const messageId = 'message-id';
|
|
667
|
-
const toolResponse = 'Builtin tool response';
|
|
668
|
-
|
|
669
|
-
act(() => {
|
|
670
|
-
useToolStore.setState({
|
|
671
|
-
transformApiArgumentsToAiState: vi.fn().mockResolvedValue(toolResponse),
|
|
672
|
-
text2image: vi.fn(),
|
|
673
|
-
});
|
|
674
645
|
|
|
675
|
-
useChatStore.setState({
|
|
676
|
-
internal_togglePluginApiCalling: vi.fn(),
|
|
677
|
-
text2image: vi.fn(),
|
|
678
|
-
internal_updateMessageContent: vi.fn(),
|
|
679
|
-
});
|
|
680
|
-
});
|
|
681
646
|
const { result } = renderHook(() => useChatStore());
|
|
682
647
|
|
|
683
648
|
await act(async () => {
|
|
684
649
|
await result.current.invokeBuiltinTool(messageId, payload);
|
|
685
650
|
});
|
|
686
651
|
|
|
687
|
-
//
|
|
688
|
-
expect(useToolStore.getState().transformApiArgumentsToAiState).toHaveBeenCalledWith(
|
|
689
|
-
payload.apiName,
|
|
690
|
-
JSON.parse(payload.arguments),
|
|
691
|
-
);
|
|
692
|
-
|
|
693
|
-
// Verify that the message content was updated with the tool response
|
|
694
|
-
expect(result.current.internal_togglePluginApiCalling).toHaveBeenCalledTimes(2);
|
|
695
|
-
expect(result.current.internal_updateMessageContent).toHaveBeenCalledWith(
|
|
696
|
-
messageId,
|
|
697
|
-
toolResponse,
|
|
698
|
-
);
|
|
699
|
-
|
|
700
|
-
// Verify that loading was toggled correctly
|
|
701
|
-
expect(result.current.internal_togglePluginApiCalling).toHaveBeenNthCalledWith(
|
|
702
|
-
1,
|
|
703
|
-
true,
|
|
704
|
-
messageId,
|
|
705
|
-
expect.any(String),
|
|
706
|
-
);
|
|
707
|
-
expect(result.current.internal_togglePluginApiCalling).toHaveBeenNthCalledWith(
|
|
708
|
-
2,
|
|
709
|
-
false,
|
|
710
|
-
messageId,
|
|
711
|
-
expect.any(String),
|
|
712
|
-
);
|
|
713
|
-
expect(useChatStore.getState().text2image).not.toHaveBeenCalled();
|
|
652
|
+
// Should not throw error, just return early
|
|
714
653
|
});
|
|
715
654
|
|
|
716
|
-
it('should
|
|
717
|
-
const args = { key: 'value' };
|
|
655
|
+
it('should not invoke action if arguments cannot be parsed', async () => {
|
|
718
656
|
const payload = {
|
|
719
|
-
apiName: '
|
|
720
|
-
arguments:
|
|
657
|
+
apiName: 'mockBuiltinAction',
|
|
658
|
+
arguments: 'invalid json',
|
|
721
659
|
} as ChatToolPayload;
|
|
722
660
|
|
|
723
661
|
const messageId = 'message-id';
|
|
724
|
-
|
|
725
|
-
useToolStore.setState({
|
|
726
|
-
transformApiArgumentsToAiState: vi
|
|
727
|
-
.fn()
|
|
728
|
-
.mockRejectedValue({ error: 'transformApiArgumentsToAiState throw error' }),
|
|
729
|
-
});
|
|
662
|
+
const mockActionFn = vi.fn().mockResolvedValue(undefined);
|
|
730
663
|
|
|
731
664
|
useChatStore.setState({
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
internal_updatePluginError: vi.fn(),
|
|
735
|
-
text2image: vi.fn(),
|
|
736
|
-
refreshMessages: vi.fn(),
|
|
737
|
-
});
|
|
665
|
+
mockBuiltinAction: mockActionFn,
|
|
666
|
+
} as any);
|
|
738
667
|
|
|
739
668
|
const { result } = renderHook(() => useChatStore());
|
|
740
669
|
|
|
@@ -742,35 +671,8 @@ describe('ChatPluginAction', () => {
|
|
|
742
671
|
await result.current.invokeBuiltinTool(messageId, payload);
|
|
743
672
|
});
|
|
744
673
|
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
body: {
|
|
748
|
-
message: expect.any(String),
|
|
749
|
-
stack: undefined,
|
|
750
|
-
arguments: args,
|
|
751
|
-
schema: undefined,
|
|
752
|
-
},
|
|
753
|
-
message: expect.any(String),
|
|
754
|
-
});
|
|
755
|
-
// Verify that loading was toggled correctly
|
|
756
|
-
expect(result.current.internal_togglePluginApiCalling).toHaveBeenNthCalledWith(
|
|
757
|
-
1,
|
|
758
|
-
true,
|
|
759
|
-
messageId,
|
|
760
|
-
expect.any(String),
|
|
761
|
-
);
|
|
762
|
-
expect(result.current.internal_togglePluginApiCalling).toHaveBeenNthCalledWith(
|
|
763
|
-
2,
|
|
764
|
-
false,
|
|
765
|
-
messageId,
|
|
766
|
-
expect.any(String),
|
|
767
|
-
);
|
|
768
|
-
|
|
769
|
-
// Verify that the message content was not updated
|
|
770
|
-
expect(result.current.internal_updateMessageContent).not.toHaveBeenCalled();
|
|
771
|
-
|
|
772
|
-
// Verify that messages were not refreshed
|
|
773
|
-
expect(useChatStore.getState().text2image).not.toHaveBeenCalled();
|
|
674
|
+
// Should not call the action if arguments can't be parsed
|
|
675
|
+
expect(mockActionFn).not.toHaveBeenCalled();
|
|
774
676
|
});
|
|
775
677
|
});
|
|
776
678
|
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
/* eslint-disable sort-keys-fix/sort-keys-fix, typescript-sort-keys/interface */
|
|
2
2
|
import { ToolNameResolver } from '@lobechat/context-engine';
|
|
3
3
|
import {
|
|
4
|
-
ChatErrorType,
|
|
5
4
|
ChatMessageError,
|
|
6
5
|
ChatToolPayload,
|
|
7
6
|
CreateMessageParams,
|
|
@@ -111,54 +110,12 @@ export const chatPlugin: StateCreator<
|
|
|
111
110
|
if (triggerAiMessage) await triggerAIMessage({ parentId: id });
|
|
112
111
|
},
|
|
113
112
|
invokeBuiltinTool: async (id, payload) => {
|
|
114
|
-
const {
|
|
115
|
-
internal_togglePluginApiCalling,
|
|
116
|
-
internal_updateMessageContent,
|
|
117
|
-
internal_updatePluginError,
|
|
118
|
-
} = get();
|
|
119
|
-
const params = JSON.parse(payload.arguments);
|
|
120
|
-
internal_togglePluginApiCalling(true, id, n('invokeBuiltinTool/start') as string);
|
|
121
|
-
let data;
|
|
122
|
-
try {
|
|
123
|
-
data = await useToolStore.getState().transformApiArgumentsToAiState(payload.apiName, params);
|
|
124
|
-
} catch (error) {
|
|
125
|
-
const err = error as Error;
|
|
126
|
-
console.error(err);
|
|
127
|
-
|
|
128
|
-
const tool = builtinTools.find((tool) => tool.identifier === payload.identifier);
|
|
129
|
-
const schema = tool?.manifest?.api.find((api) => api.name === payload.apiName)?.parameters;
|
|
130
|
-
|
|
131
|
-
await internal_updatePluginError(id, {
|
|
132
|
-
type: ChatErrorType.PluginFailToTransformArguments,
|
|
133
|
-
body: {
|
|
134
|
-
message:
|
|
135
|
-
"[plugin] fail to transform plugin arguments to ai state, it may due to model's limited tools calling capacity. You can refer to https://lobehub.com/docs/usage/tools-calling for more detail.",
|
|
136
|
-
stack: err.stack,
|
|
137
|
-
arguments: params,
|
|
138
|
-
schema,
|
|
139
|
-
},
|
|
140
|
-
message: '',
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
|
-
internal_togglePluginApiCalling(false, id, n('invokeBuiltinTool/end') as string);
|
|
144
|
-
|
|
145
|
-
if (!data) return;
|
|
146
|
-
|
|
147
|
-
await internal_updateMessageContent(id, data);
|
|
148
|
-
|
|
149
113
|
// run tool api call
|
|
150
|
-
// postToolCalling
|
|
151
114
|
// @ts-ignore
|
|
152
115
|
const { [payload.apiName]: action } = get();
|
|
153
116
|
if (!action) return;
|
|
154
117
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
try {
|
|
158
|
-
content = JSON.parse(data);
|
|
159
|
-
} catch {
|
|
160
|
-
/* empty block */
|
|
161
|
-
}
|
|
118
|
+
const content = safeParseJSON(payload.arguments);
|
|
162
119
|
|
|
163
120
|
if (!content) return;
|
|
164
121
|
|
|
@@ -78,7 +78,7 @@ describe('toolSelectors', () => {
|
|
|
78
78
|
|
|
79
79
|
describe('metaList and getMetaById', () => {
|
|
80
80
|
it('should return the correct list of tool metadata', () => {
|
|
81
|
-
const result = toolSelectors.metaList(
|
|
81
|
+
const result = toolSelectors.metaList(mockState);
|
|
82
82
|
expect(result).toEqual([
|
|
83
83
|
{
|
|
84
84
|
type: 'builtin',
|
|
@@ -53,18 +53,16 @@ const enabledSystemRoles =
|
|
|
53
53
|
return '';
|
|
54
54
|
};
|
|
55
55
|
|
|
56
|
-
const metaList =
|
|
57
|
-
(
|
|
58
|
-
(s: ToolStoreState): LobeToolMeta[] => {
|
|
59
|
-
const pluginList = pluginSelectors.installedPluginMetaList(s) as LobeToolMeta[];
|
|
56
|
+
const metaList = (s: ToolStoreState): LobeToolMeta[] => {
|
|
57
|
+
const pluginList = pluginSelectors.installedPluginMetaList(s) as LobeToolMeta[];
|
|
60
58
|
|
|
61
|
-
|
|
62
|
-
|
|
59
|
+
return builtinToolSelectors.metaList(s).concat(pluginList);
|
|
60
|
+
};
|
|
63
61
|
|
|
64
62
|
const getMetaById =
|
|
65
|
-
(id: string
|
|
63
|
+
(id: string) =>
|
|
66
64
|
(s: ToolStoreState): MetaData | undefined => {
|
|
67
|
-
const item = metaList(
|
|
65
|
+
const item = metaList(s).find((m) => m.identifier === id);
|
|
68
66
|
|
|
69
67
|
if (!item) return;
|
|
70
68
|
|
|
@@ -6,19 +6,20 @@ import { useToolStore } from '../../store';
|
|
|
6
6
|
vi.mock('zustand/traditional');
|
|
7
7
|
|
|
8
8
|
describe('createBuiltinToolSlice', () => {
|
|
9
|
-
describe('
|
|
10
|
-
it('should return if the tool is already loading', async () => {
|
|
9
|
+
describe('transformApiArgumentsToAiState', () => {
|
|
10
|
+
it('should return early if the tool is already loading', async () => {
|
|
11
11
|
// Given
|
|
12
|
-
const key = '
|
|
13
|
-
const params = {};
|
|
12
|
+
const key = 'mockTool';
|
|
13
|
+
const params = { test: 'data' };
|
|
14
14
|
|
|
15
15
|
const mockFn = vi.fn();
|
|
16
16
|
const { result } = renderHook(() => useToolStore());
|
|
17
17
|
|
|
18
18
|
act(() => {
|
|
19
19
|
useToolStore.setState({
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
builtinToolLoading: { [key]: true },
|
|
21
|
+
mockTool: mockFn,
|
|
22
|
+
} as any);
|
|
22
23
|
});
|
|
23
24
|
|
|
24
25
|
await act(async () => {
|
|
@@ -27,61 +28,108 @@ describe('createBuiltinToolSlice', () => {
|
|
|
27
28
|
expect(data).toBeUndefined();
|
|
28
29
|
});
|
|
29
30
|
|
|
30
|
-
// Then
|
|
31
|
-
expect(mockFn).toHaveBeenCalled();
|
|
31
|
+
// Then - should not call the action if already loading
|
|
32
|
+
expect(mockFn).not.toHaveBeenCalled();
|
|
32
33
|
});
|
|
33
34
|
|
|
34
35
|
it('should invoke the specified tool action and return the stringified result', async () => {
|
|
35
36
|
// Given
|
|
36
|
-
const key = '
|
|
37
|
-
|
|
38
|
-
const mockFn = vi.fn();
|
|
37
|
+
const key = 'mockTool';
|
|
38
|
+
const mockResult = { success: true, data: 'test result' };
|
|
39
|
+
const mockFn = vi.fn().mockResolvedValue(mockResult);
|
|
39
40
|
const { result } = renderHook(() => useToolStore());
|
|
40
41
|
|
|
41
42
|
const params = {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
quality: 'standard',
|
|
45
|
-
style: 'vivid',
|
|
43
|
+
input: 'test input',
|
|
44
|
+
option: 'value',
|
|
46
45
|
};
|
|
47
46
|
|
|
48
47
|
act(() => {
|
|
49
48
|
useToolStore.setState({
|
|
50
49
|
builtinToolLoading: { [key]: false },
|
|
51
|
-
|
|
50
|
+
mockTool: mockFn,
|
|
51
|
+
} as any);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// When
|
|
55
|
+
let resultData: string | undefined;
|
|
56
|
+
await act(async () => {
|
|
57
|
+
resultData = await result.current.transformApiArgumentsToAiState(key, params);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// Then
|
|
61
|
+
expect(mockFn).toHaveBeenCalledWith({
|
|
62
|
+
input: 'test input',
|
|
63
|
+
option: 'value',
|
|
64
|
+
});
|
|
65
|
+
expect(resultData).toBe(JSON.stringify(mockResult));
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('should return stringified params if action does not exist', async () => {
|
|
69
|
+
// Given
|
|
70
|
+
const key = 'nonExistentTool';
|
|
71
|
+
const params = { test: 'data' };
|
|
72
|
+
const { result } = renderHook(() => useToolStore());
|
|
73
|
+
|
|
74
|
+
act(() => {
|
|
75
|
+
useToolStore.setState({
|
|
76
|
+
builtinToolLoading: {},
|
|
52
77
|
});
|
|
53
78
|
});
|
|
79
|
+
|
|
54
80
|
// When
|
|
81
|
+
let resultData: string | undefined;
|
|
55
82
|
await act(async () => {
|
|
56
|
-
await result.current.transformApiArgumentsToAiState(key, params);
|
|
83
|
+
resultData = await result.current.transformApiArgumentsToAiState(key, params);
|
|
57
84
|
});
|
|
58
85
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
86
|
+
// Then
|
|
87
|
+
expect(resultData).toBe(JSON.stringify(params));
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('should handle errors and toggle loading state', async () => {
|
|
91
|
+
// Given
|
|
92
|
+
const key = 'mockTool';
|
|
93
|
+
const params = { test: 'data' };
|
|
94
|
+
const error = new Error('Tool execution failed');
|
|
95
|
+
const mockFn = vi.fn().mockRejectedValue(error);
|
|
96
|
+
const { result } = renderHook(() => useToolStore());
|
|
97
|
+
|
|
98
|
+
act(() => {
|
|
99
|
+
useToolStore.setState({
|
|
100
|
+
builtinToolLoading: { [key]: false },
|
|
101
|
+
mockTool: mockFn,
|
|
102
|
+
} as any);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// When/Then
|
|
106
|
+
await act(async () => {
|
|
107
|
+
await expect(result.current.transformApiArgumentsToAiState(key, params)).rejects.toThrow(
|
|
108
|
+
'Tool execution failed',
|
|
109
|
+
);
|
|
64
110
|
});
|
|
111
|
+
|
|
112
|
+
// Should have toggled loading state back to false
|
|
113
|
+
expect(result.current.builtinToolLoading[key]).toBe(false);
|
|
65
114
|
});
|
|
66
115
|
});
|
|
67
116
|
|
|
68
|
-
describe('
|
|
69
|
-
it('should
|
|
70
|
-
// When
|
|
117
|
+
describe('toggleBuiltinToolLoading', () => {
|
|
118
|
+
it('should toggle the loading state for a tool', () => {
|
|
71
119
|
const { result } = renderHook(() => useToolStore());
|
|
120
|
+
const key = 'testTool';
|
|
121
|
+
|
|
122
|
+
act(() => {
|
|
123
|
+
result.current.toggleBuiltinToolLoading(key, true);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
expect(result.current.builtinToolLoading[key]).toBe(true);
|
|
72
127
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
size: '1024x1024',
|
|
76
|
-
quality: 'standard',
|
|
77
|
-
style: 'vivid',
|
|
128
|
+
act(() => {
|
|
129
|
+
result.current.toggleBuiltinToolLoading(key, false);
|
|
78
130
|
});
|
|
79
131
|
|
|
80
|
-
|
|
81
|
-
expect(data).toEqual([
|
|
82
|
-
{ prompt: 'prompt1', quality: 'standard', size: '1024x1024', style: 'vivid' },
|
|
83
|
-
{ prompt: 'prompt2', quality: 'standard', size: '1024x1024', style: 'vivid' },
|
|
84
|
-
]);
|
|
132
|
+
expect(result.current.builtinToolLoading[key]).toBe(false);
|
|
85
133
|
});
|
|
86
134
|
});
|
|
87
135
|
});
|
|
@@ -1,22 +1,15 @@
|
|
|
1
1
|
import { StateCreator } from 'zustand/vanilla';
|
|
2
2
|
|
|
3
|
-
import { OpenAIImagePayload } from '@/types/openai/image';
|
|
4
|
-
import { DallEImageItem } from '@/types/tool/dalle';
|
|
5
3
|
import { setNamespace } from '@/utils/storeDebug';
|
|
6
4
|
|
|
7
5
|
import { ToolStore } from '../../store';
|
|
8
6
|
|
|
9
7
|
const n = setNamespace('builtinTool');
|
|
10
8
|
|
|
11
|
-
interface Text2ImageParams extends Pick<OpenAIImagePayload, 'quality' | 'style' | 'size'> {
|
|
12
|
-
prompts: string[];
|
|
13
|
-
}
|
|
14
|
-
|
|
15
9
|
/**
|
|
16
10
|
* 代理行为接口
|
|
17
11
|
*/
|
|
18
12
|
export interface BuiltinToolAction {
|
|
19
|
-
text2image: (params: Text2ImageParams) => DallEImageItem[];
|
|
20
13
|
toggleBuiltinToolLoading: (key: string, value: boolean) => void;
|
|
21
14
|
transformApiArgumentsToAiState: (key: string, params: any) => Promise<string | undefined>;
|
|
22
15
|
}
|
|
@@ -27,8 +20,6 @@ export const createBuiltinToolSlice: StateCreator<
|
|
|
27
20
|
[],
|
|
28
21
|
BuiltinToolAction
|
|
29
22
|
> = (set, get) => ({
|
|
30
|
-
text2image: ({ prompts, size = '1024x1024' as const, quality = 'standard', style = 'vivid' }) =>
|
|
31
|
-
prompts.map((p) => ({ prompt: p, quality, size, style })),
|
|
32
23
|
toggleBuiltinToolLoading: (key, value) => {
|
|
33
24
|
set({ builtinToolLoading: { [key]: value } }, false, n('toggleBuiltinToolLoading'));
|
|
34
25
|
},
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
2
|
|
|
3
|
-
import { DalleManifest } from '@/tools/dalle';
|
|
4
|
-
|
|
5
3
|
import { ToolStoreState, initialState } from '../../initialState';
|
|
6
4
|
import { builtinToolSelectors } from './selectors';
|
|
7
5
|
|
|
@@ -10,34 +8,11 @@ describe('builtinToolSelectors', () => {
|
|
|
10
8
|
it('should return meta list excluding Dalle when showDalle is false', () => {
|
|
11
9
|
const state = {
|
|
12
10
|
...initialState,
|
|
13
|
-
builtinTools: [
|
|
14
|
-
{ identifier: 'tool-1', manifest: { meta: { title: 'Tool 1' } } },
|
|
15
|
-
{ identifier: DalleManifest.identifier, manifest: { meta: { title: 'Dalle' } } },
|
|
16
|
-
],
|
|
17
|
-
} as ToolStoreState;
|
|
18
|
-
const result = builtinToolSelectors.metaList(false)(state);
|
|
19
|
-
expect(result).toEqual([
|
|
20
|
-
{ author: 'LobeHub', identifier: 'tool-1', meta: { title: 'Tool 1' }, type: 'builtin' },
|
|
21
|
-
]);
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
it('should include Dalle when showDalle is true', () => {
|
|
25
|
-
const state = {
|
|
26
|
-
...initialState,
|
|
27
|
-
builtinTools: [
|
|
28
|
-
{ identifier: 'tool-1', manifest: { meta: { title: 'Tool 1' } } },
|
|
29
|
-
{ identifier: DalleManifest.identifier, manifest: { meta: { title: 'Dalle' } } },
|
|
30
|
-
],
|
|
11
|
+
builtinTools: [{ identifier: 'tool-1', manifest: { meta: { title: 'Tool 1' } } }],
|
|
31
12
|
} as ToolStoreState;
|
|
32
|
-
const result = builtinToolSelectors.metaList(
|
|
13
|
+
const result = builtinToolSelectors.metaList(state);
|
|
33
14
|
expect(result).toEqual([
|
|
34
15
|
{ author: 'LobeHub', identifier: 'tool-1', meta: { title: 'Tool 1' }, type: 'builtin' },
|
|
35
|
-
{
|
|
36
|
-
author: 'LobeHub',
|
|
37
|
-
identifier: DalleManifest.identifier,
|
|
38
|
-
meta: { title: 'Dalle' },
|
|
39
|
-
type: 'builtin',
|
|
40
|
-
},
|
|
41
16
|
]);
|
|
42
17
|
});
|
|
43
18
|
|
|
@@ -46,10 +21,9 @@ describe('builtinToolSelectors', () => {
|
|
|
46
21
|
...initialState,
|
|
47
22
|
builtinTools: [
|
|
48
23
|
{ identifier: 'tool-1', hidden: true, manifest: { meta: { title: 'Tool 1' } } },
|
|
49
|
-
{ identifier: DalleManifest.identifier, manifest: { meta: { title: 'Dalle' } } },
|
|
50
24
|
],
|
|
51
25
|
} as ToolStoreState;
|
|
52
|
-
const result = builtinToolSelectors.metaList(
|
|
26
|
+
const result = builtinToolSelectors.metaList(state);
|
|
53
27
|
expect(result).toEqual([]);
|
|
54
28
|
});
|
|
55
29
|
|
|
@@ -58,7 +32,7 @@ describe('builtinToolSelectors', () => {
|
|
|
58
32
|
...initialState,
|
|
59
33
|
builtinTools: [],
|
|
60
34
|
};
|
|
61
|
-
const result = builtinToolSelectors.metaList(
|
|
35
|
+
const result = builtinToolSelectors.metaList(state);
|
|
62
36
|
expect(result).toEqual([]);
|
|
63
37
|
});
|
|
64
38
|
});
|