@lobehub/lobehub 2.0.0-next.85 → 2.0.0-next.86
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/apps/desktop/src/main/modules/networkProxy/dispatcher.ts +16 -16
- package/apps/desktop/src/main/modules/networkProxy/tester.ts +11 -11
- package/apps/desktop/src/main/modules/networkProxy/urlBuilder.ts +3 -3
- package/apps/desktop/src/main/modules/networkProxy/validator.ts +10 -10
- package/changelog/v1.json +9 -0
- package/package.json +1 -1
- package/packages/agent-runtime/src/core/runtime.ts +36 -1
- package/packages/agent-runtime/src/types/event.ts +1 -0
- package/packages/agent-runtime/src/types/generalAgent.ts +16 -0
- package/packages/agent-runtime/src/types/instruction.ts +30 -0
- package/packages/agent-runtime/src/types/runtime.ts +7 -0
- package/packages/types/src/message/common/metadata.ts +3 -0
- package/packages/types/src/message/common/tools.ts +2 -2
- package/packages/types/src/tool/search/index.ts +8 -2
- package/src/app/[variants]/(main)/chat/components/conversation/features/ChatInput/V1Mobile/index.tsx +2 -2
- package/src/app/[variants]/(main)/chat/components/conversation/features/ChatInput/V1Mobile/useSend.ts +7 -2
- package/src/app/[variants]/(main)/chat/components/conversation/features/ChatInput/useSend.ts +15 -14
- package/src/app/[variants]/(main)/chat/session/features/SessionListContent/List/Item/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/Conversation/Messages/Group/Tool/Inspector/index.tsx +1 -1
- package/src/features/Conversation/Messages/User/index.tsx +3 -3
- package/src/features/Conversation/Messages/index.tsx +3 -3
- package/src/features/Conversation/components/AutoScroll.tsx +2 -2
- package/src/services/search.ts +2 -2
- package/src/store/chat/agents/GeneralChatAgent.ts +98 -0
- package/src/store/chat/agents/__tests__/GeneralChatAgent.test.ts +366 -0
- package/src/store/chat/agents/__tests__/createAgentExecutors/call-llm.test.ts +1217 -0
- package/src/store/chat/agents/__tests__/createAgentExecutors/call-tool.test.ts +1976 -0
- package/src/store/chat/agents/__tests__/createAgentExecutors/finish.test.ts +453 -0
- package/src/store/chat/agents/__tests__/createAgentExecutors/fixtures/index.ts +4 -0
- package/src/store/chat/agents/__tests__/createAgentExecutors/fixtures/mockInstructions.ts +126 -0
- package/src/store/chat/agents/__tests__/createAgentExecutors/fixtures/mockMessages.ts +94 -0
- package/src/store/chat/agents/__tests__/createAgentExecutors/fixtures/mockOperations.ts +96 -0
- package/src/store/chat/agents/__tests__/createAgentExecutors/fixtures/mockStore.ts +138 -0
- package/src/store/chat/agents/__tests__/createAgentExecutors/helpers/assertions.ts +185 -0
- package/src/store/chat/agents/__tests__/createAgentExecutors/helpers/index.ts +3 -0
- package/src/store/chat/agents/__tests__/createAgentExecutors/helpers/operationTestUtils.ts +94 -0
- package/src/store/chat/agents/__tests__/createAgentExecutors/helpers/testExecutor.ts +139 -0
- package/src/store/chat/agents/__tests__/createAgentExecutors/request-human-approve.test.ts +545 -0
- package/src/store/chat/agents/__tests__/createAgentExecutors/resolve-aborted-tools.test.ts +686 -0
- package/src/store/chat/agents/createAgentExecutors.ts +313 -80
- package/src/store/chat/selectors.ts +1 -0
- package/src/store/chat/slices/aiChat/__tests__/ai-chat.integration.test.ts +667 -0
- package/src/store/chat/slices/aiChat/actions/__tests__/cancel-functionality.test.ts +137 -27
- package/src/store/chat/slices/aiChat/actions/__tests__/conversationControl.test.ts +163 -125
- package/src/store/chat/slices/aiChat/actions/__tests__/conversationLifecycle.test.ts +12 -2
- package/src/store/chat/slices/aiChat/actions/__tests__/fixtures.ts +0 -2
- package/src/store/chat/slices/aiChat/actions/__tests__/helpers.ts +0 -2
- package/src/store/chat/slices/aiChat/actions/__tests__/streamingExecutor.test.ts +286 -19
- package/src/store/chat/slices/aiChat/actions/__tests__/streamingStates.test.ts +0 -112
- package/src/store/chat/slices/aiChat/actions/conversationControl.ts +42 -99
- package/src/store/chat/slices/aiChat/actions/conversationLifecycle.ts +90 -57
- package/src/store/chat/slices/aiChat/actions/generateAIGroupChat.ts +5 -25
- package/src/store/chat/slices/aiChat/actions/streamingExecutor.ts +220 -98
- package/src/store/chat/slices/aiChat/actions/streamingStates.ts +0 -34
- package/src/store/chat/slices/aiChat/initialState.ts +0 -28
- package/src/store/chat/slices/aiChat/selectors.test.ts +280 -0
- package/src/store/chat/slices/aiChat/selectors.ts +31 -7
- package/src/store/chat/slices/builtinTool/actions/__tests__/localSystem.test.ts +21 -30
- package/src/store/chat/slices/builtinTool/actions/__tests__/search.test.ts +29 -49
- package/src/store/chat/slices/builtinTool/actions/interpreter.ts +83 -48
- package/src/store/chat/slices/builtinTool/actions/localSystem.ts +78 -28
- package/src/store/chat/slices/builtinTool/actions/search.ts +146 -59
- package/src/store/chat/slices/builtinTool/selectors.test.ts +258 -0
- package/src/store/chat/slices/builtinTool/selectors.ts +25 -4
- package/src/store/chat/slices/message/action.test.ts +134 -16
- package/src/store/chat/slices/message/actions/internals.ts +33 -7
- package/src/store/chat/slices/message/actions/optimisticUpdate.ts +85 -52
- package/src/store/chat/slices/message/initialState.ts +0 -10
- package/src/store/chat/slices/message/selectors/messageState.ts +34 -12
- package/src/store/chat/slices/operation/__tests__/actions.test.ts +712 -16
- package/src/store/chat/slices/operation/__tests__/integration.test.ts +342 -0
- package/src/store/chat/slices/operation/__tests__/selectors.test.ts +257 -17
- package/src/store/chat/slices/operation/actions.ts +218 -11
- package/src/store/chat/slices/operation/selectors.ts +135 -6
- package/src/store/chat/slices/operation/types.ts +29 -3
- package/src/store/chat/slices/plugin/action.test.ts +30 -322
- package/src/store/chat/slices/plugin/actions/internals.ts +0 -14
- package/src/store/chat/slices/plugin/actions/optimisticUpdate.ts +21 -19
- package/src/store/chat/slices/plugin/actions/pluginTypes.ts +45 -27
- package/src/store/chat/slices/plugin/actions/publicApi.ts +3 -4
- package/src/store/chat/slices/plugin/actions/workflow.ts +0 -55
- package/src/store/chat/slices/thread/selectors/index.ts +4 -2
- package/src/store/chat/slices/translate/action.ts +54 -41
- package/src/tools/web-browsing/ExecutionRuntime/index.ts +5 -2
- package/src/tools/web-browsing/Portal/Search/Footer.tsx +11 -9
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
CodeInterpreterParams,
|
|
4
4
|
CodeInterpreterResponse,
|
|
5
5
|
} from '@lobechat/types';
|
|
6
|
+
import debug from 'debug';
|
|
6
7
|
import { produce } from 'immer';
|
|
7
8
|
import pMap from 'p-map';
|
|
8
9
|
import { SWRResponse } from 'swr';
|
|
@@ -18,12 +19,12 @@ import { CodeInterpreterIdentifier } from '@/tools/code-interpreter';
|
|
|
18
19
|
import { setNamespace } from '@/utils/storeDebug';
|
|
19
20
|
|
|
20
21
|
const n = setNamespace('codeInterpreter');
|
|
22
|
+
const log = debug('lobe-store:builtin-tool');
|
|
21
23
|
|
|
22
24
|
const SWR_FETCH_INTERPRETER_FILE_KEY = 'FetchCodeInterpreterFileItem';
|
|
23
25
|
|
|
24
26
|
export interface ChatCodeInterpreterAction {
|
|
25
27
|
python: (id: string, params: CodeInterpreterParams) => Promise<boolean | undefined>;
|
|
26
|
-
toggleInterpreterExecuting: (id: string, loading: boolean) => void;
|
|
27
28
|
updateInterpreterFileItem: (
|
|
28
29
|
id: string,
|
|
29
30
|
updater: (data: CodeInterpreterResponse) => void,
|
|
@@ -39,66 +40,100 @@ export const codeInterpreterSlice: StateCreator<
|
|
|
39
40
|
ChatCodeInterpreterAction
|
|
40
41
|
> = (set, get) => ({
|
|
41
42
|
python: async (id: string, params: CodeInterpreterParams) => {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
} = get()
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
43
|
+
// Get parent operationId from messageOperationMap (should be executeToolCall)
|
|
44
|
+
const parentOperationId = get().messageOperationMap[id];
|
|
45
|
+
|
|
46
|
+
// Create child operation for interpreter execution
|
|
47
|
+
// Auto-associates message with this operation via messageId in context
|
|
48
|
+
const { operationId: interpreterOpId, abortController } = get().startOperation({
|
|
49
|
+
context: {
|
|
50
|
+
messageId: id,
|
|
51
|
+
},
|
|
52
|
+
metadata: {
|
|
53
|
+
startTime: Date.now(),
|
|
54
|
+
},
|
|
55
|
+
parentOperationId,
|
|
56
|
+
type: 'builtinToolInterpreter',
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
log(
|
|
60
|
+
'[python] messageId=%s, parentOpId=%s, interpreterOpId=%s, aborted=%s',
|
|
61
|
+
id,
|
|
62
|
+
parentOperationId,
|
|
63
|
+
interpreterOpId,
|
|
64
|
+
abortController.signal.aborted,
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
const context = { operationId: interpreterOpId };
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
// TODO: 应该只下载 AI 用到的文件
|
|
71
|
+
const files: File[] = [];
|
|
72
|
+
for (const message of dbMessageSelectors.dbUserMessages(get())) {
|
|
73
|
+
for (const file of message.fileList ?? []) {
|
|
74
|
+
const blob = await fetch(file.url).then((res) => res.blob());
|
|
75
|
+
files.push(new File([blob], file.name));
|
|
76
|
+
}
|
|
77
|
+
for (const image of message.imageList ?? []) {
|
|
78
|
+
const blob = await fetch(image.url).then((res) => res.blob());
|
|
79
|
+
files.push(new File([blob], image.alt));
|
|
80
|
+
}
|
|
81
|
+
for (const tool of message.tools ?? []) {
|
|
82
|
+
if (tool.identifier === CodeInterpreterIdentifier) {
|
|
83
|
+
const message = dbMessageSelectors.getDbMessageByToolCallId(tool.id)(get());
|
|
84
|
+
if (message?.content) {
|
|
85
|
+
const content = JSON.parse(message.content) as CodeInterpreterResponse;
|
|
86
|
+
for (const file of content.files ?? []) {
|
|
87
|
+
const item = await fileService.getFile(file.fileId!);
|
|
88
|
+
const blob = await fetch(item.url).then((res) => res.blob());
|
|
89
|
+
files.push(new File([blob], file.filename));
|
|
90
|
+
}
|
|
71
91
|
}
|
|
72
92
|
}
|
|
73
93
|
}
|
|
74
94
|
}
|
|
75
|
-
}
|
|
76
95
|
|
|
77
|
-
try {
|
|
78
96
|
const result = await pythonService.runPython(params.code, params.packages, files);
|
|
97
|
+
|
|
98
|
+
// Complete interpreter operation
|
|
99
|
+
get().completeOperation(interpreterOpId);
|
|
100
|
+
|
|
79
101
|
if (result?.files) {
|
|
80
|
-
await optimisticUpdateMessageContent(id, JSON.stringify(result));
|
|
81
|
-
await uploadInterpreterFiles(id, result.files);
|
|
102
|
+
await get().optimisticUpdateMessageContent(id, JSON.stringify(result), undefined, context);
|
|
103
|
+
await get().uploadInterpreterFiles(id, result.files);
|
|
82
104
|
} else {
|
|
83
|
-
await optimisticUpdateMessageContent(id, JSON.stringify(result));
|
|
105
|
+
await get().optimisticUpdateMessageContent(id, JSON.stringify(result), undefined, context);
|
|
84
106
|
}
|
|
107
|
+
|
|
108
|
+
return true;
|
|
85
109
|
} catch (error) {
|
|
86
|
-
|
|
110
|
+
const err = error as Error;
|
|
111
|
+
|
|
112
|
+
log('[python] Error: messageId=%s, error=%s', id, err.message);
|
|
113
|
+
|
|
114
|
+
// Check if it's an abort error
|
|
115
|
+
if (err.message.includes('The user aborted a request.') || err.name === 'AbortError') {
|
|
116
|
+
log('[python] Request aborted: messageId=%s', id);
|
|
117
|
+
// Fail interpreter operation for abort
|
|
118
|
+
get().failOperation(interpreterOpId, {
|
|
119
|
+
message: 'User cancelled the request',
|
|
120
|
+
type: 'UserAborted',
|
|
121
|
+
});
|
|
122
|
+
// Don't update error message for user aborts
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Fail interpreter operation for other errors
|
|
127
|
+
get().failOperation(interpreterOpId, {
|
|
128
|
+
message: err.message,
|
|
129
|
+
type: 'PluginServerError',
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
// For other errors, update message
|
|
133
|
+
await get().optimisticUpdatePluginState(id, { error }, context);
|
|
87
134
|
// 如果调用过程中出现了错误,不要触发 AI 消息
|
|
88
135
|
return;
|
|
89
|
-
} finally {
|
|
90
|
-
toggleInterpreterExecuting(id, false);
|
|
91
136
|
}
|
|
92
|
-
|
|
93
|
-
return true;
|
|
94
|
-
},
|
|
95
|
-
|
|
96
|
-
toggleInterpreterExecuting: (id: string, executing: boolean) => {
|
|
97
|
-
set(
|
|
98
|
-
{ codeInterpreterExecuting: { ...get().codeInterpreterExecuting, [id]: executing } },
|
|
99
|
-
false,
|
|
100
|
-
n('toggleInterpreterExecuting'),
|
|
101
|
-
);
|
|
102
137
|
},
|
|
103
138
|
|
|
104
139
|
updateInterpreterFileItem: async (
|
|
@@ -13,11 +13,14 @@ import {
|
|
|
13
13
|
RunCommandParams,
|
|
14
14
|
WriteLocalFileParams,
|
|
15
15
|
} from '@lobechat/electron-client-ipc';
|
|
16
|
+
import debug from 'debug';
|
|
16
17
|
import { StateCreator } from 'zustand/vanilla';
|
|
17
18
|
|
|
18
19
|
import { ChatStore } from '@/store/chat/store';
|
|
19
20
|
import { LocalSystemExecutionRuntime } from '@/tools/local-system/ExecutionRuntime';
|
|
20
21
|
|
|
22
|
+
const log = debug('lobe-store:builtin-tool');
|
|
23
|
+
|
|
21
24
|
/* eslint-disable typescript-sort-keys/interface */
|
|
22
25
|
export interface LocalFileAction {
|
|
23
26
|
internal_triggerLocalFileToolCalling: (
|
|
@@ -33,7 +36,6 @@ export interface LocalFileAction {
|
|
|
33
36
|
renameLocalFile: (id: string, params: RenameLocalFileParams) => Promise<boolean>;
|
|
34
37
|
reSearchLocalFiles: (id: string, params: LocalSearchFilesParams) => Promise<boolean>;
|
|
35
38
|
searchLocalFiles: (id: string, params: LocalSearchFilesParams) => Promise<boolean>;
|
|
36
|
-
toggleLocalFileLoading: (id: string, loading: boolean) => void;
|
|
37
39
|
writeLocalFile: (id: string, params: WriteLocalFileParams) => Promise<boolean>;
|
|
38
40
|
|
|
39
41
|
// Shell Commands
|
|
@@ -106,8 +108,6 @@ export const localSystemSlice: StateCreator<
|
|
|
106
108
|
},
|
|
107
109
|
|
|
108
110
|
reSearchLocalFiles: async (id, params) => {
|
|
109
|
-
get().toggleLocalFileLoading(id, true);
|
|
110
|
-
|
|
111
111
|
await get().optimisticUpdatePluginArguments(id, params);
|
|
112
112
|
|
|
113
113
|
return get().searchLocalFiles(id, params);
|
|
@@ -144,44 +144,94 @@ export const localSystemSlice: StateCreator<
|
|
|
144
144
|
|
|
145
145
|
// ==================== utils ====================
|
|
146
146
|
|
|
147
|
-
toggleLocalFileLoading: (id, loading) => {
|
|
148
|
-
// Assuming a loading state structure similar to searchLoading
|
|
149
|
-
set(
|
|
150
|
-
(state) => ({
|
|
151
|
-
localFileLoading: { ...state.localFileLoading, [id]: loading },
|
|
152
|
-
}),
|
|
153
|
-
false,
|
|
154
|
-
`toggleLocalFileLoading/${loading ? 'start' : 'end'}`,
|
|
155
|
-
);
|
|
156
|
-
},
|
|
157
147
|
internal_triggerLocalFileToolCalling: async (id, callingService) => {
|
|
158
|
-
|
|
148
|
+
// Get parent operationId from messageOperationMap (should be executeToolCall)
|
|
149
|
+
const parentOperationId = get().messageOperationMap[id];
|
|
150
|
+
|
|
151
|
+
// Create child operation for local system execution
|
|
152
|
+
// Auto-associates message with this operation via messageId in context
|
|
153
|
+
const { operationId: localSystemOpId, abortController } = get().startOperation({
|
|
154
|
+
type: 'builtinToolLocalSystem',
|
|
155
|
+
context: {
|
|
156
|
+
messageId: id,
|
|
157
|
+
},
|
|
158
|
+
parentOperationId,
|
|
159
|
+
metadata: {
|
|
160
|
+
startTime: Date.now(),
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
log(
|
|
165
|
+
'[localSystem] messageId=%s, parentOpId=%s, localSystemOpId=%s, aborted=%s',
|
|
166
|
+
id,
|
|
167
|
+
parentOperationId,
|
|
168
|
+
localSystemOpId,
|
|
169
|
+
abortController.signal.aborted,
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
const context = { operationId: localSystemOpId };
|
|
173
|
+
|
|
159
174
|
try {
|
|
160
175
|
const { state, content, success, error } = await callingService();
|
|
161
176
|
|
|
177
|
+
// Complete local system operation
|
|
178
|
+
get().completeOperation(localSystemOpId);
|
|
179
|
+
|
|
162
180
|
if (success) {
|
|
163
181
|
if (state) {
|
|
164
|
-
await get().optimisticUpdatePluginState(id, state);
|
|
182
|
+
await get().optimisticUpdatePluginState(id, state, context);
|
|
165
183
|
}
|
|
166
|
-
await get().optimisticUpdateMessageContent(id, content);
|
|
184
|
+
await get().optimisticUpdateMessageContent(id, content, undefined, context);
|
|
167
185
|
} else {
|
|
168
|
-
await get().optimisticUpdateMessagePluginError(
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
186
|
+
await get().optimisticUpdateMessagePluginError(
|
|
187
|
+
id,
|
|
188
|
+
{
|
|
189
|
+
body: error,
|
|
190
|
+
message: error?.message || 'Operation failed',
|
|
191
|
+
type: 'PluginServerError',
|
|
192
|
+
},
|
|
193
|
+
context,
|
|
194
|
+
);
|
|
173
195
|
// Still update content even if failed, to show error message
|
|
174
|
-
await get().optimisticUpdateMessageContent(id, content);
|
|
196
|
+
await get().optimisticUpdateMessageContent(id, content, undefined, context);
|
|
175
197
|
}
|
|
198
|
+
|
|
199
|
+
return true;
|
|
176
200
|
} catch (error) {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
201
|
+
const err = error as Error;
|
|
202
|
+
|
|
203
|
+
log('[localSystem] Error: messageId=%s, error=%s', id, err.message);
|
|
204
|
+
|
|
205
|
+
// Check if it's an abort error
|
|
206
|
+
if (err.message.includes('The user aborted a request.') || err.name === 'AbortError') {
|
|
207
|
+
log('[localSystem] Request aborted: messageId=%s', id);
|
|
208
|
+
// Fail local system operation for abort
|
|
209
|
+
get().failOperation(localSystemOpId, {
|
|
210
|
+
message: 'User cancelled the request',
|
|
211
|
+
type: 'UserAborted',
|
|
212
|
+
});
|
|
213
|
+
// Don't update error message for user aborts
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Fail local system operation for other errors
|
|
218
|
+
get().failOperation(localSystemOpId, {
|
|
219
|
+
message: err.message,
|
|
180
220
|
type: 'PluginServerError',
|
|
181
221
|
});
|
|
182
|
-
}
|
|
183
|
-
get().toggleLocalFileLoading(id, false);
|
|
184
222
|
|
|
185
|
-
|
|
223
|
+
// For other errors, update message
|
|
224
|
+
await get().optimisticUpdateMessagePluginError(
|
|
225
|
+
id,
|
|
226
|
+
{
|
|
227
|
+
body: error,
|
|
228
|
+
message: err.message,
|
|
229
|
+
type: 'PluginServerError',
|
|
230
|
+
},
|
|
231
|
+
context,
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
return false;
|
|
235
|
+
}
|
|
186
236
|
},
|
|
187
237
|
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { crawlResultsPrompt } from '@lobechat/prompts';
|
|
2
2
|
import { CreateMessageParams, SEARCH_SEARXNG_NOT_CONFIG, SearchQuery } from '@lobechat/types';
|
|
3
3
|
import { nanoid } from '@lobechat/utils';
|
|
4
|
+
import debug from 'debug';
|
|
4
5
|
import { StateCreator } from 'zustand/vanilla';
|
|
5
6
|
|
|
6
7
|
import { searchService } from '@/services/search';
|
|
@@ -8,6 +9,8 @@ import { dbMessageSelectors } from '@/store/chat/selectors';
|
|
|
8
9
|
import { ChatStore } from '@/store/chat/store';
|
|
9
10
|
import { WebBrowsingExecutionRuntime } from '@/tools/web-browsing/ExecutionRuntime';
|
|
10
11
|
|
|
12
|
+
const log = debug('lobe-store:builtin-tool');
|
|
13
|
+
|
|
11
14
|
export interface SearchAction {
|
|
12
15
|
crawlMultiPages: (
|
|
13
16
|
id: string,
|
|
@@ -22,7 +25,6 @@ export interface SearchAction {
|
|
|
22
25
|
saveSearchResult: (id: string) => Promise<void>;
|
|
23
26
|
search: (id: string, data: SearchQuery, aiSummary?: boolean) => Promise<void | boolean>;
|
|
24
27
|
togglePageContent: (url: string) => void;
|
|
25
|
-
toggleSearchLoading: (id: string, loading: boolean) => void;
|
|
26
28
|
/**
|
|
27
29
|
* 重新发起搜索
|
|
28
30
|
* @description 会更新插件的 arguments 参数,然后再次搜索
|
|
@@ -43,36 +45,79 @@ export const searchSlice: StateCreator<
|
|
|
43
45
|
SearchAction
|
|
44
46
|
> = (set, get) => ({
|
|
45
47
|
crawlMultiPages: async (id, params, aiSummary = true) => {
|
|
46
|
-
|
|
47
|
-
get().
|
|
48
|
+
// Get parent operationId from messageOperationMap (should be executeToolCall)
|
|
49
|
+
const parentOperationId = get().messageOperationMap[id];
|
|
50
|
+
|
|
51
|
+
// Create child operation for crawl execution
|
|
52
|
+
// Auto-associates message with this operation via messageId in context
|
|
53
|
+
const { operationId: crawlOpId, abortController } = get().startOperation({
|
|
54
|
+
context: {
|
|
55
|
+
messageId: id,
|
|
56
|
+
},
|
|
57
|
+
metadata: {
|
|
58
|
+
startTime: Date.now(),
|
|
59
|
+
urls: params.urls,
|
|
60
|
+
},
|
|
61
|
+
parentOperationId,
|
|
62
|
+
type: 'builtinToolSearch',
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
log(
|
|
66
|
+
'[crawlMultiPages] messageId=%s, parentOpId=%s, crawlOpId=%s, urls=%o, aborted=%s',
|
|
67
|
+
id,
|
|
68
|
+
parentOperationId,
|
|
69
|
+
crawlOpId,
|
|
70
|
+
params.urls,
|
|
71
|
+
abortController.signal.aborted,
|
|
72
|
+
);
|
|
48
73
|
|
|
49
|
-
|
|
50
|
-
const message = dbMessageSelectors.getDbMessageById(id)(get());
|
|
51
|
-
const context = { sessionId: message?.sessionId, topicId: message?.topicId };
|
|
74
|
+
const context = { operationId: crawlOpId };
|
|
52
75
|
|
|
53
76
|
try {
|
|
54
77
|
const { content, success, error, state } = await runtime.crawlMultiPages(params);
|
|
55
78
|
|
|
56
|
-
|
|
79
|
+
// Complete crawl operation
|
|
80
|
+
get().completeOperation(crawlOpId);
|
|
81
|
+
|
|
82
|
+
await get().optimisticUpdateMessageContent(id, content, undefined, context);
|
|
57
83
|
|
|
58
84
|
if (success) {
|
|
59
85
|
await get().optimisticUpdatePluginState(id, state, context);
|
|
60
86
|
} else {
|
|
61
87
|
await get().optimisticUpdatePluginError(id, error, context);
|
|
62
88
|
}
|
|
63
|
-
get().toggleSearchLoading(id, false);
|
|
64
|
-
|
|
65
|
-
// Convert to XML format to save tokens
|
|
66
89
|
|
|
67
90
|
// if aiSummary is true, then trigger ai message
|
|
68
91
|
return aiSummary;
|
|
69
92
|
} catch (e) {
|
|
70
93
|
const err = e as Error;
|
|
94
|
+
|
|
95
|
+
log('[crawlMultiPages] Error: messageId=%s, error=%s', id, err.message);
|
|
96
|
+
|
|
97
|
+
// Check if it's an abort error
|
|
98
|
+
if (err.message.includes('The user aborted a request.') || err.name === 'AbortError') {
|
|
99
|
+
log('[crawlMultiPages] Request aborted: messageId=%s', id);
|
|
100
|
+
// Fail crawl operation for abort
|
|
101
|
+
get().failOperation(crawlOpId, {
|
|
102
|
+
message: 'User cancelled the request',
|
|
103
|
+
type: 'UserAborted',
|
|
104
|
+
});
|
|
105
|
+
// Don't update error message for user aborts
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Fail crawl operation for other errors
|
|
110
|
+
get().failOperation(crawlOpId, {
|
|
111
|
+
message: err.message,
|
|
112
|
+
type: 'PluginServerError',
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// For other errors, update message
|
|
71
116
|
console.error(e);
|
|
72
117
|
const content = [{ errorMessage: err.message, errorType: err.name }];
|
|
73
118
|
|
|
74
119
|
const xmlContent = crawlResultsPrompt(content);
|
|
75
|
-
await optimisticUpdateMessageContent(id, xmlContent, undefined, context);
|
|
120
|
+
await get().optimisticUpdateMessageContent(id, xmlContent, undefined, context);
|
|
76
121
|
}
|
|
77
122
|
},
|
|
78
123
|
|
|
@@ -88,7 +133,9 @@ export const searchSlice: StateCreator<
|
|
|
88
133
|
|
|
89
134
|
const { optimisticAddToolToAssistantMessage, optimisticCreateMessage, openToolUI } = get();
|
|
90
135
|
|
|
91
|
-
|
|
136
|
+
// Get operationId from messageOperationMap
|
|
137
|
+
const operationId = get().messageOperationMap[id];
|
|
138
|
+
const context = operationId ? { operationId } : undefined;
|
|
92
139
|
|
|
93
140
|
// 1. 创建一个新的 tool call message
|
|
94
141
|
const newToolCallId = `tool_call_${nanoid()}`;
|
|
@@ -120,10 +167,7 @@ export const searchSlice: StateCreator<
|
|
|
120
167
|
|
|
121
168
|
const [result] = await Promise.all([
|
|
122
169
|
// 1. 添加 tool message
|
|
123
|
-
optimisticCreateMessage(toolMessage,
|
|
124
|
-
sessionId: toolMessage.sessionId,
|
|
125
|
-
topicId: toolMessage.topicId,
|
|
126
|
-
}),
|
|
170
|
+
optimisticCreateMessage(toolMessage, context),
|
|
127
171
|
// 2. 将这条 tool call message 插入到 ai 消息的 tools 中
|
|
128
172
|
addToolItem(),
|
|
129
173
|
]);
|
|
@@ -134,61 +178,104 @@ export const searchSlice: StateCreator<
|
|
|
134
178
|
},
|
|
135
179
|
|
|
136
180
|
search: async (id, params, aiSummary = true) => {
|
|
137
|
-
|
|
181
|
+
// Get parent operationId from messageOperationMap (should be executeToolCall)
|
|
182
|
+
const parentOperationId = get().messageOperationMap[id];
|
|
183
|
+
|
|
184
|
+
// Create child operation for search execution
|
|
185
|
+
// Auto-associates message with this operation via messageId in context
|
|
186
|
+
const { operationId: searchOpId, abortController } = get().startOperation({
|
|
187
|
+
context: {
|
|
188
|
+
messageId: id,
|
|
189
|
+
},
|
|
190
|
+
metadata: {
|
|
191
|
+
query: params.query,
|
|
192
|
+
startTime: Date.now(),
|
|
193
|
+
},
|
|
194
|
+
parentOperationId,
|
|
195
|
+
type: 'builtinToolSearch',
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
log(
|
|
199
|
+
'[search] messageId=%s, parentOpId=%s, searchOpId=%s, aborted=%s',
|
|
200
|
+
id,
|
|
201
|
+
parentOperationId,
|
|
202
|
+
searchOpId,
|
|
203
|
+
abortController.signal.aborted,
|
|
204
|
+
);
|
|
138
205
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
{
|
|
152
|
-
body: { provider: 'searxng' },
|
|
153
|
-
message: 'SearXNG is not configured',
|
|
154
|
-
type: 'PluginSettingsInvalid',
|
|
155
|
-
},
|
|
156
|
-
context,
|
|
157
|
-
);
|
|
206
|
+
const context = { operationId: searchOpId };
|
|
207
|
+
|
|
208
|
+
try {
|
|
209
|
+
const { content, success, error, state } = await runtime.search(params, {
|
|
210
|
+
signal: abortController.signal,
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
// Complete search operation
|
|
214
|
+
get().completeOperation(searchOpId);
|
|
215
|
+
|
|
216
|
+
if (success) {
|
|
217
|
+
await get().optimisticUpdatePluginState(id, state, context);
|
|
158
218
|
} else {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
219
|
+
if ((error as Error).message === SEARCH_SEARXNG_NOT_CONFIG) {
|
|
220
|
+
await get().optimisticUpdateMessagePluginError(
|
|
221
|
+
id,
|
|
222
|
+
{
|
|
223
|
+
body: { provider: 'searxng' },
|
|
224
|
+
message: 'SearXNG is not configured',
|
|
225
|
+
type: 'PluginSettingsInvalid',
|
|
226
|
+
},
|
|
227
|
+
context,
|
|
228
|
+
);
|
|
229
|
+
} else {
|
|
230
|
+
await get().optimisticUpdateMessagePluginError(
|
|
231
|
+
id,
|
|
232
|
+
{
|
|
233
|
+
body: error,
|
|
234
|
+
message: (error as Error).message,
|
|
235
|
+
type: 'PluginServerError',
|
|
236
|
+
},
|
|
237
|
+
context,
|
|
238
|
+
);
|
|
239
|
+
}
|
|
168
240
|
}
|
|
169
|
-
}
|
|
170
241
|
|
|
171
|
-
|
|
242
|
+
await get().optimisticUpdateMessageContent(id, content, undefined, context);
|
|
243
|
+
|
|
244
|
+
// 如果 aiSummary 为 true,则会自动触发总结
|
|
245
|
+
return aiSummary;
|
|
246
|
+
} catch (error) {
|
|
247
|
+
const err = error as Error;
|
|
248
|
+
|
|
249
|
+
log('[search] Error: messageId=%s, error=%s', id, err.message);
|
|
250
|
+
|
|
251
|
+
// Check if it's an abort error
|
|
252
|
+
if (err.message.includes('The user aborted a request.') || err.name === 'AbortError') {
|
|
253
|
+
log('[search] Request aborted: messageId=%s', id);
|
|
254
|
+
// Fail search operation for abort
|
|
255
|
+
get().failOperation(searchOpId, {
|
|
256
|
+
message: 'User cancelled the request',
|
|
257
|
+
type: 'UserAborted',
|
|
258
|
+
});
|
|
259
|
+
// Don't update error message for user aborts
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
172
262
|
|
|
173
|
-
|
|
263
|
+
// Fail search operation for other errors
|
|
264
|
+
get().failOperation(searchOpId, { message: err.message, type: 'PluginServerError' });
|
|
174
265
|
|
|
175
|
-
|
|
176
|
-
|
|
266
|
+
// For other errors, update message
|
|
267
|
+
await get().optimisticUpdateMessagePluginError(
|
|
268
|
+
id,
|
|
269
|
+
{ body: error, message: err.message, type: 'PluginServerError' },
|
|
270
|
+
context,
|
|
271
|
+
);
|
|
272
|
+
}
|
|
177
273
|
},
|
|
178
274
|
togglePageContent: (url) => {
|
|
179
275
|
set({ activePageContentUrl: url });
|
|
180
276
|
},
|
|
181
277
|
|
|
182
|
-
toggleSearchLoading: (id, loading) => {
|
|
183
|
-
set(
|
|
184
|
-
{ searchLoading: { ...get().searchLoading, [id]: loading } },
|
|
185
|
-
false,
|
|
186
|
-
`toggleSearchLoading/${loading ? 'start' : 'end'}`,
|
|
187
|
-
);
|
|
188
|
-
},
|
|
189
|
-
|
|
190
278
|
triggerSearchAgain: async (id, data, options) => {
|
|
191
|
-
get().toggleSearchLoading(id, true);
|
|
192
279
|
await get().optimisticUpdatePluginArguments(id, data);
|
|
193
280
|
|
|
194
281
|
await get().search(id, data, options?.aiSummary);
|