@lobehub/lobehub 2.0.0-next.84 → 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.
Files changed (89) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/apps/desktop/src/main/modules/networkProxy/dispatcher.ts +16 -16
  3. package/apps/desktop/src/main/modules/networkProxy/tester.ts +11 -11
  4. package/apps/desktop/src/main/modules/networkProxy/urlBuilder.ts +3 -3
  5. package/apps/desktop/src/main/modules/networkProxy/validator.ts +10 -10
  6. package/changelog/v1.json +18 -0
  7. package/package.json +1 -1
  8. package/packages/agent-runtime/src/core/runtime.ts +36 -1
  9. package/packages/agent-runtime/src/types/event.ts +1 -0
  10. package/packages/agent-runtime/src/types/generalAgent.ts +16 -0
  11. package/packages/agent-runtime/src/types/instruction.ts +30 -0
  12. package/packages/agent-runtime/src/types/runtime.ts +7 -0
  13. package/packages/types/src/message/common/metadata.ts +3 -0
  14. package/packages/types/src/message/common/tools.ts +2 -2
  15. package/packages/types/src/tool/search/index.ts +8 -2
  16. package/src/app/[variants]/(main)/chat/components/conversation/features/ChatInput/V1Mobile/index.tsx +2 -2
  17. package/src/app/[variants]/(main)/chat/components/conversation/features/ChatInput/V1Mobile/useSend.ts +7 -2
  18. package/src/app/[variants]/(main)/chat/components/conversation/features/ChatInput/useSend.ts +15 -14
  19. package/src/app/[variants]/(main)/chat/session/features/SessionListContent/List/Item/index.tsx +2 -2
  20. package/src/app/[variants]/(main)/discover/(list)/features/Pagination.tsx +1 -1
  21. package/src/features/ChatInput/ActionBar/STT/browser.tsx +2 -2
  22. package/src/features/ChatInput/ActionBar/STT/openai.tsx +2 -2
  23. package/src/features/Conversation/Messages/Group/Tool/Inspector/index.tsx +1 -1
  24. package/src/features/Conversation/Messages/User/index.tsx +3 -3
  25. package/src/features/Conversation/Messages/index.tsx +3 -3
  26. package/src/features/Conversation/components/AutoScroll.tsx +2 -2
  27. package/src/services/search.ts +2 -2
  28. package/src/store/chat/agents/GeneralChatAgent.ts +98 -0
  29. package/src/store/chat/agents/__tests__/GeneralChatAgent.test.ts +366 -0
  30. package/src/store/chat/agents/__tests__/createAgentExecutors/call-llm.test.ts +1217 -0
  31. package/src/store/chat/agents/__tests__/createAgentExecutors/call-tool.test.ts +1976 -0
  32. package/src/store/chat/agents/__tests__/createAgentExecutors/finish.test.ts +453 -0
  33. package/src/store/chat/agents/__tests__/createAgentExecutors/fixtures/index.ts +4 -0
  34. package/src/store/chat/agents/__tests__/createAgentExecutors/fixtures/mockInstructions.ts +126 -0
  35. package/src/store/chat/agents/__tests__/createAgentExecutors/fixtures/mockMessages.ts +94 -0
  36. package/src/store/chat/agents/__tests__/createAgentExecutors/fixtures/mockOperations.ts +96 -0
  37. package/src/store/chat/agents/__tests__/createAgentExecutors/fixtures/mockStore.ts +138 -0
  38. package/src/store/chat/agents/__tests__/createAgentExecutors/helpers/assertions.ts +185 -0
  39. package/src/store/chat/agents/__tests__/createAgentExecutors/helpers/index.ts +3 -0
  40. package/src/store/chat/agents/__tests__/createAgentExecutors/helpers/operationTestUtils.ts +94 -0
  41. package/src/store/chat/agents/__tests__/createAgentExecutors/helpers/testExecutor.ts +139 -0
  42. package/src/store/chat/agents/__tests__/createAgentExecutors/request-human-approve.test.ts +545 -0
  43. package/src/store/chat/agents/__tests__/createAgentExecutors/resolve-aborted-tools.test.ts +686 -0
  44. package/src/store/chat/agents/createAgentExecutors.ts +313 -80
  45. package/src/store/chat/selectors.ts +1 -0
  46. package/src/store/chat/slices/aiChat/__tests__/ai-chat.integration.test.ts +667 -0
  47. package/src/store/chat/slices/aiChat/actions/__tests__/cancel-functionality.test.ts +137 -27
  48. package/src/store/chat/slices/aiChat/actions/__tests__/conversationControl.test.ts +163 -125
  49. package/src/store/chat/slices/aiChat/actions/__tests__/conversationLifecycle.test.ts +12 -2
  50. package/src/store/chat/slices/aiChat/actions/__tests__/fixtures.ts +0 -2
  51. package/src/store/chat/slices/aiChat/actions/__tests__/helpers.ts +0 -2
  52. package/src/store/chat/slices/aiChat/actions/__tests__/streamingExecutor.test.ts +286 -19
  53. package/src/store/chat/slices/aiChat/actions/__tests__/streamingStates.test.ts +0 -112
  54. package/src/store/chat/slices/aiChat/actions/conversationControl.ts +42 -99
  55. package/src/store/chat/slices/aiChat/actions/conversationLifecycle.ts +90 -57
  56. package/src/store/chat/slices/aiChat/actions/generateAIGroupChat.ts +5 -25
  57. package/src/store/chat/slices/aiChat/actions/streamingExecutor.ts +220 -98
  58. package/src/store/chat/slices/aiChat/actions/streamingStates.ts +0 -34
  59. package/src/store/chat/slices/aiChat/initialState.ts +0 -28
  60. package/src/store/chat/slices/aiChat/selectors.test.ts +280 -0
  61. package/src/store/chat/slices/aiChat/selectors.ts +31 -7
  62. package/src/store/chat/slices/builtinTool/actions/__tests__/localSystem.test.ts +21 -30
  63. package/src/store/chat/slices/builtinTool/actions/__tests__/search.test.ts +29 -49
  64. package/src/store/chat/slices/builtinTool/actions/interpreter.ts +83 -48
  65. package/src/store/chat/slices/builtinTool/actions/localSystem.ts +78 -28
  66. package/src/store/chat/slices/builtinTool/actions/search.ts +146 -59
  67. package/src/store/chat/slices/builtinTool/selectors.test.ts +258 -0
  68. package/src/store/chat/slices/builtinTool/selectors.ts +25 -4
  69. package/src/store/chat/slices/message/action.test.ts +134 -16
  70. package/src/store/chat/slices/message/actions/internals.ts +33 -7
  71. package/src/store/chat/slices/message/actions/optimisticUpdate.ts +85 -52
  72. package/src/store/chat/slices/message/initialState.ts +0 -10
  73. package/src/store/chat/slices/message/selectors/messageState.ts +34 -12
  74. package/src/store/chat/slices/operation/__tests__/actions.test.ts +712 -16
  75. package/src/store/chat/slices/operation/__tests__/integration.test.ts +342 -0
  76. package/src/store/chat/slices/operation/__tests__/selectors.test.ts +257 -17
  77. package/src/store/chat/slices/operation/actions.ts +218 -11
  78. package/src/store/chat/slices/operation/selectors.ts +135 -6
  79. package/src/store/chat/slices/operation/types.ts +29 -3
  80. package/src/store/chat/slices/plugin/action.test.ts +30 -322
  81. package/src/store/chat/slices/plugin/actions/internals.ts +0 -14
  82. package/src/store/chat/slices/plugin/actions/optimisticUpdate.ts +21 -19
  83. package/src/store/chat/slices/plugin/actions/pluginTypes.ts +45 -27
  84. package/src/store/chat/slices/plugin/actions/publicApi.ts +3 -4
  85. package/src/store/chat/slices/plugin/actions/workflow.ts +0 -55
  86. package/src/store/chat/slices/thread/selectors/index.ts +4 -2
  87. package/src/store/chat/slices/translate/action.ts +54 -41
  88. package/src/tools/web-browsing/ExecutionRuntime/index.ts +5 -2
  89. package/src/tools/web-browsing/Portal/Search/Footer.tsx +11 -9
@@ -1,6 +1,7 @@
1
1
  /* eslint-disable sort-keys-fix/sort-keys-fix, typescript-sort-keys/interface */
2
2
  import { ChatToolPayload } from '@lobechat/types';
3
3
  import { PluginErrorType } from '@lobehub/chat-plugin-sdk';
4
+ import debug from 'debug';
4
5
  import { t } from 'i18next';
5
6
  import { StateCreator } from 'zustand/vanilla';
6
7
 
@@ -11,11 +12,10 @@ import { messageService } from '@/services/message';
11
12
  import { ChatStore } from '@/store/chat/store';
12
13
  import { useToolStore } from '@/store/tool';
13
14
  import { safeParseJSON } from '@/utils/safeParseJSON';
14
- import { setNamespace } from '@/utils/storeDebug';
15
15
 
16
16
  import { dbMessageSelectors } from '../../message/selectors';
17
17
 
18
- const n = setNamespace('plugin');
18
+ const log = debug('lobe-store:plugin-types');
19
19
 
20
20
  /**
21
21
  * Plugin type-specific implementations
@@ -125,7 +125,6 @@ export const pluginTypes: StateCreator<
125
125
  invokeMCPTypePlugin: async (id, payload) => {
126
126
  const {
127
127
  optimisticUpdateMessageContent,
128
- internal_togglePluginApiCalling,
129
128
  internal_constructToolsCallingContext,
130
129
  optimisticUpdatePluginState,
131
130
  optimisticUpdateMessagePluginError,
@@ -135,13 +134,20 @@ export const pluginTypes: StateCreator<
135
134
  // Get message to extract sessionId/topicId
136
135
  const message = dbMessageSelectors.getDbMessageById(id)(get());
137
136
 
138
- try {
139
- const abortController = internal_togglePluginApiCalling(
140
- true,
141
- id,
142
- n('fetchPlugin/start') as string,
143
- );
137
+ // Get abort controller from operation
138
+ const operationId = get().messageOperationMap[id];
139
+ const operation = operationId ? get().operations[operationId] : undefined;
140
+ const abortController = operation?.abortController;
141
+
142
+ log(
143
+ '[invokeMCPTypePlugin] messageId=%s, tool=%s, operationId=%s, aborted=%s',
144
+ id,
145
+ payload.apiName,
146
+ operationId,
147
+ abortController?.signal.aborted,
148
+ );
144
149
 
150
+ try {
145
151
  const context = internal_constructToolsCallingContext(id);
146
152
  const result = await mcpService.invokeMcpToolCall(payload, {
147
153
  signal: abortController?.signal,
@@ -154,7 +160,9 @@ export const pluginTypes: StateCreator<
154
160
  const err = error as Error;
155
161
 
156
162
  // ignore the aborted request error
157
- if (!err.message.includes('The user aborted a request.')) {
163
+ if (err.message.includes('The user aborted a request.')) {
164
+ log('[invokeMCPTypePlugin] Request aborted: messageId=%s, tool=%s', id, payload.apiName);
165
+ } else {
158
166
  const result = await messageService.updateMessageError(id, error as any, {
159
167
  sessionId: message?.sessionId,
160
168
  topicId: message?.topicId,
@@ -168,13 +176,12 @@ export const pluginTypes: StateCreator<
168
176
  }
169
177
  }
170
178
 
171
- internal_togglePluginApiCalling(false, id, n('fetchPlugin/end') as string);
172
-
173
179
  // 如果报错则结束了
174
180
 
175
181
  if (!data) return;
176
182
 
177
- const context = { sessionId: message?.sessionId, topicId: message?.topicId };
183
+ // operationId already declared above, reuse it
184
+ const context = operationId ? { operationId } : undefined;
178
185
 
179
186
  await Promise.all([
180
187
  optimisticUpdateMessageContent(id, data.content, undefined, context),
@@ -188,19 +195,26 @@ export const pluginTypes: StateCreator<
188
195
  },
189
196
 
190
197
  internal_callPluginApi: async (id, payload) => {
191
- const { optimisticUpdateMessageContent, internal_togglePluginApiCalling } = get();
198
+ const { optimisticUpdateMessageContent } = get();
192
199
  let data: string;
193
200
 
194
201
  // Get message to extract sessionId/topicId
195
202
  const message = dbMessageSelectors.getDbMessageById(id)(get());
196
203
 
197
- try {
198
- const abortController = internal_togglePluginApiCalling(
199
- true,
200
- id,
201
- n('fetchPlugin/start') as string,
202
- );
204
+ // Get abort controller from operation
205
+ const operationId = get().messageOperationMap[id];
206
+ const operation = operationId ? get().operations[operationId] : undefined;
207
+ const abortController = operation?.abortController;
208
+
209
+ log(
210
+ '[internal_callPluginApi] messageId=%s, plugin=%s, operationId=%s, aborted=%s',
211
+ id,
212
+ payload.identifier,
213
+ operationId,
214
+ abortController?.signal.aborted,
215
+ );
203
216
 
217
+ try {
204
218
  const res = await chatService.runPluginApi(payload, {
205
219
  signal: abortController?.signal,
206
220
  trace: { observationId: message?.observationId, traceId: message?.traceId },
@@ -216,7 +230,13 @@ export const pluginTypes: StateCreator<
216
230
  const err = error as Error;
217
231
 
218
232
  // ignore the aborted request error
219
- if (!err.message.includes('The user aborted a request.')) {
233
+ if (err.message.includes('The user aborted a request.')) {
234
+ log(
235
+ '[internal_callPluginApi] Request aborted: messageId=%s, plugin=%s',
236
+ id,
237
+ payload.identifier,
238
+ );
239
+ } else {
220
240
  const result = await messageService.updateMessageError(id, error as any, {
221
241
  sessionId: message?.sessionId,
222
242
  topicId: message?.topicId,
@@ -231,15 +251,13 @@ export const pluginTypes: StateCreator<
231
251
 
232
252
  data = '';
233
253
  }
234
-
235
- internal_togglePluginApiCalling(false, id, n('fetchPlugin/end') as string);
236
254
  // 如果报错则结束了
237
255
  if (!data) return;
238
256
 
239
- await optimisticUpdateMessageContent(id, data, undefined, {
240
- sessionId: message?.sessionId,
241
- topicId: message?.topicId,
242
- });
257
+ // operationId already declared above, reuse it
258
+ const context = operationId ? { operationId } : undefined;
259
+
260
+ await optimisticUpdateMessageContent(id, data, undefined, context);
243
261
 
244
262
  return data;
245
263
  },
@@ -55,10 +55,9 @@ export const pluginPublicApi: StateCreator<
55
55
  const message = displayMessageSelectors.getDisplayMessageById(id)(get());
56
56
  if (!message || message.role !== 'tool' || !message.plugin) return;
57
57
 
58
- const context = {
59
- sessionId: message.sessionId,
60
- topicId: message.topicId,
61
- };
58
+ // Get operationId from messageOperationMap
59
+ const operationId = get().messageOperationMap[id];
60
+ const context = operationId ? { operationId } : undefined;
62
61
 
63
62
  // if there is error content, then clear the error
64
63
  if (!!message.pluginError) {
@@ -28,15 +28,6 @@ export interface PluginWorkflowAction {
28
28
  inPortalThread?: boolean;
29
29
  inSearchWorkflow?: boolean;
30
30
  }) => Promise<void>;
31
-
32
- /**
33
- * Trigger tool calls (V1 deprecated method)
34
- * @deprecated
35
- */
36
- triggerToolCalls: (
37
- id: string,
38
- params?: { threadId?: string; inPortalThread?: boolean; inSearchWorkflow?: boolean },
39
- ) => Promise<void>;
40
31
  }
41
32
 
42
33
  export const pluginWorkflow: StateCreator<
@@ -83,50 +74,4 @@ export const pluginWorkflow: StateCreator<
83
74
  inSearchWorkflow,
84
75
  });
85
76
  },
86
-
87
- triggerToolCalls: async (assistantId, { threadId, inPortalThread, inSearchWorkflow } = {}) => {
88
- const message = displayMessageSelectors.getDisplayMessageById(assistantId)(get());
89
- if (!message || !message.tools) return;
90
-
91
- let shouldCreateMessage = false;
92
- let latestToolId = '';
93
- const messagePools = message.tools.map(async (payload) => {
94
- const toolMessage: CreateMessageParams = {
95
- content: '',
96
- parentId: assistantId,
97
- plugin: payload,
98
- role: 'tool',
99
- sessionId: message.sessionId ?? get().activeId,
100
- tool_call_id: payload.id,
101
- threadId,
102
- topicId: message.topicId !== undefined ? message.topicId : get().activeTopicId,
103
- groupId: message.groupId, // Propagate groupId from parent message for group chat
104
- };
105
-
106
- const result = await get().optimisticCreateMessage(toolMessage, {
107
- sessionId: toolMessage.sessionId,
108
- topicId: toolMessage.topicId,
109
- });
110
- if (!result) return;
111
-
112
- // trigger the plugin call
113
- const data = await get().internal_invokeDifferentTypePlugin(result.id, payload);
114
-
115
- if (data && !['markdown', 'standalone'].includes(payload.type)) {
116
- shouldCreateMessage = true;
117
- latestToolId = result.id;
118
- }
119
- });
120
-
121
- await Promise.all(messagePools);
122
-
123
- await get().internal_toggleMessageInToolsCalling(false, assistantId);
124
-
125
- // only default type tool calls should trigger AI message
126
- if (!shouldCreateMessage) return;
127
-
128
- const traceId = dbMessageSelectors.getTraceIdByDbMessageId(latestToolId)(get());
129
-
130
- await get().triggerAIMessage({ traceId, threadId, inPortalThread, inSearchWorkflow });
131
- },
132
77
  });
@@ -164,8 +164,10 @@ const hasThreadBySourceMsgId = (id: string) => (s: ChatStoreState) => {
164
164
  return threads.some((t) => t.sourceMessageId === id);
165
165
  };
166
166
 
167
- const isThreadAIGenerating = (s: ChatStoreState) =>
168
- s.chatLoadingIds.some((id) => portalDisplayChatIDs(s).includes(id));
167
+ const isThreadAIGenerating = (s: ChatStoreState) => {
168
+ const { operationSelectors } = require('../../operation/selectors');
169
+ return operationSelectors.isAnyMessageLoading(portalDisplayChatIDs(s))(s);
170
+ };
169
171
 
170
172
  const isInRAGFlow = (s: ChatStoreState) =>
171
173
  s.messageRAGLoadingIds.some((id) => portalDisplayChatIDs(s).includes(id));
@@ -1,7 +1,6 @@
1
1
  import { chainLangDetect, chainTranslate } from '@lobechat/prompts';
2
2
  import { ChatTranslate, TraceNameMap, TracePayload } from '@lobechat/types';
3
3
  import { merge } from '@lobechat/utils';
4
- import { produce } from 'immer';
5
4
  import { StateCreator } from 'zustand/vanilla';
6
5
 
7
6
  import { supportLocales } from '@/locales/resources';
@@ -11,9 +10,6 @@ import { dbMessageSelectors } from '@/store/chat/selectors';
11
10
  import { ChatStore } from '@/store/chat/store';
12
11
  import { useUserStore } from '@/store/user';
13
12
  import { systemAgentSelectors } from '@/store/user/selectors';
14
- import { setNamespace } from '@/utils/storeDebug';
15
-
16
- const n = setNamespace('enhance');
17
13
 
18
14
  /**
19
15
  * chat translate
@@ -41,7 +37,7 @@ export const chatTranslate: StateCreator<
41
37
  }),
42
38
 
43
39
  translateMessage: async (id, targetLang) => {
44
- const { internal_toggleChatLoading, updateMessageTranslate, internal_dispatchMessage } = get();
40
+ const { updateMessageTranslate, internal_dispatchMessage } = get();
45
41
 
46
42
  const message = dbMessageSelectors.getDbMessageById(id)(get());
47
43
  if (!message) return;
@@ -52,47 +48,64 @@ export const chatTranslate: StateCreator<
52
48
  // create translate extra
53
49
  await updateMessageTranslate(id, { content: '', from: '', to: targetLang });
54
50
 
55
- internal_toggleChatLoading(true, id, n('translateMessage(start)', { id }));
51
+ // Create translate operation
52
+ const { operationId } = get().startOperation({
53
+ context: { messageId: id, sessionId: message.sessionId, topicId: message.topicId },
54
+ label: 'Translating message',
55
+ type: 'translate',
56
+ });
56
57
 
57
- let content = '';
58
- let from = '';
58
+ // Associate message with operation
59
+ get().associateMessageWithOperation(id, operationId);
59
60
 
60
- // detect from language
61
- chatService.fetchPresetTaskResult({
62
- onFinish: async (data) => {
63
- if (data && supportLocales.includes(data)) from = data;
61
+ try {
62
+ let content = '';
63
+ let from = '';
64
64
 
65
- await updateMessageTranslate(id, { content, from, to: targetLang });
66
- },
67
- params: merge(translationSetting, chainLangDetect(message.content)),
68
- trace: get().getCurrentTracePayload({ traceName: TraceNameMap.LanguageDetect }),
69
- });
65
+ // detect from language
66
+ chatService.fetchPresetTaskResult({
67
+ onFinish: async (data) => {
68
+ if (data && supportLocales.includes(data)) from = data;
70
69
 
71
- // translate to target language
72
- await chatService.fetchPresetTaskResult({
73
- onFinish: async (content) => {
74
- await updateMessageTranslate(id, { content, from, to: targetLang });
75
- internal_toggleChatLoading(false, id);
76
- },
77
- onMessageHandle: (chunk) => {
78
- switch (chunk.type) {
79
- case 'text': {
80
- internal_dispatchMessage({
81
- id,
82
- key: 'translate',
83
- type: 'updateMessageExtra',
84
- value: produce({ content: '', from, to: targetLang }, (draft) => {
85
- content += chunk.text;
86
- draft.content += content;
87
- }),
88
- });
89
- break;
70
+ await updateMessageTranslate(id, { content, from, to: targetLang });
71
+ },
72
+ params: merge(translationSetting, chainLangDetect(message.content)),
73
+ trace: get().getCurrentTracePayload({ traceName: TraceNameMap.LanguageDetect }),
74
+ });
75
+
76
+ // translate to target language
77
+ await chatService.fetchPresetTaskResult({
78
+ onFinish: async (translatedContent) => {
79
+ await updateMessageTranslate(id, { content: translatedContent, from, to: targetLang });
80
+ get().completeOperation(operationId);
81
+ },
82
+ onMessageHandle: (chunk) => {
83
+ switch (chunk.type) {
84
+ case 'text': {
85
+ content += chunk.text;
86
+ internal_dispatchMessage(
87
+ {
88
+ id,
89
+ key: 'translate',
90
+ type: 'updateMessageExtra',
91
+ value: { content, from, to: targetLang },
92
+ },
93
+ { operationId },
94
+ );
95
+ break;
96
+ }
90
97
  }
91
- }
92
- },
93
- params: merge(translationSetting, chainTranslate(message.content, targetLang)),
94
- trace: get().getCurrentTracePayload({ traceName: TraceNameMap.Translator }),
95
- });
98
+ },
99
+ params: merge(translationSetting, chainTranslate(message.content, targetLang)),
100
+ trace: get().getCurrentTracePayload({ traceName: TraceNameMap.Translator }),
101
+ });
102
+ } catch (error) {
103
+ get().failOperation(operationId, {
104
+ message: error instanceof Error ? error.message : String(error),
105
+ type: 'TranslateError',
106
+ });
107
+ throw error;
108
+ }
96
109
  },
97
110
 
98
111
  updateMessageTranslate: async (id, data) => {
@@ -17,9 +17,12 @@ export class WebBrowsingExecutionRuntime {
17
17
  this.searchService = options.searchService;
18
18
  }
19
19
 
20
- async search(args: SearchQuery): Promise<BuiltinServerRuntimeOutput> {
20
+ async search(
21
+ args: SearchQuery,
22
+ options?: { signal?: AbortSignal },
23
+ ): Promise<BuiltinServerRuntimeOutput> {
21
24
  try {
22
- const data = await this.searchService.webSearch(args as SearchQuery);
25
+ const data = await this.searchService.webSearch(args as SearchQuery, options);
23
26
 
24
27
  // add LIMITED_COUNT search results to message content
25
28
  const searchContent: SearchContent[] = data.results
@@ -4,15 +4,17 @@ import { useTranslation } from 'react-i18next';
4
4
  import { Flexbox } from 'react-layout-kit';
5
5
 
6
6
  import { useChatStore } from '@/store/chat';
7
- import { chatPortalSelectors, messageStateSelectors } from '@/store/chat/selectors';
7
+ import { chatPortalSelectors, operationSelectors } from '@/store/chat/selectors';
8
8
 
9
9
  const Footer = () => {
10
- const [messageId, isAIGenerating, triggerAIMessage, saveSearchResult] = useChatStore((s) => [
11
- chatPortalSelectors.toolMessageId(s),
12
- messageStateSelectors.isAIGenerating(s),
13
- s.triggerAIMessage,
14
- s.saveSearchResult,
15
- ]);
10
+ const [messageId, isAgentRuntimeRunning, triggerAIMessage, saveSearchResult] = useChatStore(
11
+ (s) => [
12
+ chatPortalSelectors.toolMessageId(s),
13
+ operationSelectors.isAgentRuntimeRunning(s),
14
+ s.triggerAIMessage,
15
+ s.saveSearchResult,
16
+ ],
17
+ );
16
18
 
17
19
  const { t } = useTranslation('tool');
18
20
 
@@ -20,7 +22,7 @@ const Footer = () => {
20
22
  <Flexbox gap={8} horizontal paddingBlock={12} paddingInline={12}>
21
23
  <Button
22
24
  icon={LucideNotepadText}
23
- loading={isAIGenerating}
25
+ loading={isAgentRuntimeRunning}
24
26
  onClick={() => {
25
27
  if (!messageId) return;
26
28
 
@@ -31,7 +33,7 @@ const Footer = () => {
31
33
  </Button>
32
34
  <ActionIcon
33
35
  icon={PlusSquareIcon}
34
- loading={isAIGenerating}
36
+ loading={isAgentRuntimeRunning}
35
37
  onClick={() => {
36
38
  if (!messageId) return;
37
39