@lobehub/lobehub 2.0.0-next.50 → 2.0.0-next.51

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 (118) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/apps/desktop/src/main/controllers/ShellCommandCtr.ts +242 -0
  3. package/apps/desktop/src/main/controllers/__tests__/ShellCommandCtr.test.ts +499 -0
  4. package/changelog/v1.json +9 -0
  5. package/locales/ar/chat.json +20 -0
  6. package/locales/ar/common.json +1 -0
  7. package/locales/ar/components.json +6 -0
  8. package/locales/ar/plugin.json +1 -0
  9. package/locales/bg-BG/chat.json +20 -0
  10. package/locales/bg-BG/common.json +1 -0
  11. package/locales/bg-BG/components.json +6 -0
  12. package/locales/bg-BG/plugin.json +1 -0
  13. package/locales/de-DE/chat.json +20 -0
  14. package/locales/de-DE/common.json +1 -0
  15. package/locales/de-DE/components.json +6 -0
  16. package/locales/de-DE/plugin.json +1 -0
  17. package/locales/en-US/chat.json +20 -0
  18. package/locales/en-US/common.json +1 -0
  19. package/locales/en-US/components.json +6 -0
  20. package/locales/en-US/plugin.json +1 -0
  21. package/locales/es-ES/chat.json +20 -0
  22. package/locales/es-ES/common.json +1 -0
  23. package/locales/es-ES/components.json +6 -0
  24. package/locales/es-ES/plugin.json +1 -0
  25. package/locales/fa-IR/chat.json +20 -0
  26. package/locales/fa-IR/common.json +1 -0
  27. package/locales/fa-IR/components.json +6 -0
  28. package/locales/fa-IR/plugin.json +1 -0
  29. package/locales/fr-FR/chat.json +20 -0
  30. package/locales/fr-FR/common.json +1 -0
  31. package/locales/fr-FR/components.json +6 -0
  32. package/locales/fr-FR/plugin.json +1 -0
  33. package/locales/it-IT/chat.json +20 -0
  34. package/locales/it-IT/common.json +1 -0
  35. package/locales/it-IT/components.json +6 -0
  36. package/locales/it-IT/plugin.json +1 -0
  37. package/locales/ja-JP/chat.json +20 -0
  38. package/locales/ja-JP/common.json +1 -0
  39. package/locales/ja-JP/components.json +6 -0
  40. package/locales/ja-JP/plugin.json +1 -0
  41. package/locales/ko-KR/chat.json +20 -0
  42. package/locales/ko-KR/common.json +1 -0
  43. package/locales/ko-KR/components.json +6 -0
  44. package/locales/ko-KR/plugin.json +1 -0
  45. package/locales/nl-NL/chat.json +20 -0
  46. package/locales/nl-NL/common.json +1 -0
  47. package/locales/nl-NL/components.json +6 -0
  48. package/locales/nl-NL/plugin.json +1 -0
  49. package/locales/pl-PL/chat.json +20 -0
  50. package/locales/pl-PL/common.json +1 -0
  51. package/locales/pl-PL/components.json +6 -0
  52. package/locales/pl-PL/plugin.json +1 -0
  53. package/locales/pt-BR/chat.json +20 -0
  54. package/locales/pt-BR/common.json +1 -0
  55. package/locales/pt-BR/components.json +6 -0
  56. package/locales/pt-BR/plugin.json +1 -0
  57. package/locales/ru-RU/chat.json +20 -0
  58. package/locales/ru-RU/common.json +1 -0
  59. package/locales/ru-RU/components.json +6 -0
  60. package/locales/ru-RU/plugin.json +1 -0
  61. package/locales/tr-TR/chat.json +20 -0
  62. package/locales/tr-TR/common.json +1 -0
  63. package/locales/tr-TR/components.json +6 -0
  64. package/locales/tr-TR/plugin.json +1 -0
  65. package/locales/vi-VN/chat.json +20 -0
  66. package/locales/vi-VN/common.json +1 -0
  67. package/locales/vi-VN/components.json +6 -0
  68. package/locales/vi-VN/plugin.json +1 -0
  69. package/locales/zh-CN/chat.json +20 -0
  70. package/locales/zh-CN/common.json +1 -0
  71. package/locales/zh-CN/components.json +6 -0
  72. package/locales/zh-CN/plugin.json +1 -0
  73. package/locales/zh-TW/chat.json +20 -0
  74. package/locales/zh-TW/common.json +1 -0
  75. package/locales/zh-TW/components.json +6 -0
  76. package/locales/zh-TW/plugin.json +1 -0
  77. package/package.json +1 -1
  78. package/packages/agent-runtime/src/core/InterventionChecker.ts +1 -1
  79. package/packages/agent-runtime/src/core/__tests__/InterventionChecker.test.ts +23 -23
  80. package/packages/agent-runtime/src/types/state.ts +7 -1
  81. package/packages/const/src/settings/tool.ts +1 -5
  82. package/packages/file-loaders/src/loaders/docx/index.ts +1 -1
  83. package/packages/model-bank/src/aiModels/wenxin.ts +1348 -291
  84. package/packages/model-runtime/src/providers/wenxin/index.ts +22 -1
  85. package/packages/model-runtime/src/utils/modelParse.ts +6 -0
  86. package/packages/types/src/tool/builtin.ts +9 -0
  87. package/packages/types/src/tool/intervention.ts +32 -2
  88. package/packages/types/src/user/settings/tool.ts +3 -27
  89. package/src/config/modelProviders/wenxin.ts +2 -3
  90. package/src/features/Conversation/MarkdownElements/remarkPlugins/__snapshots__/createRemarkSelfClosingTagPlugin.test.ts.snap +133 -0
  91. package/src/features/Conversation/MarkdownElements/remarkPlugins/createRemarkSelfClosingTagPlugin.test.ts +48 -0
  92. package/src/features/Conversation/MarkdownElements/remarkPlugins/createRemarkSelfClosingTagPlugin.ts +2 -1
  93. package/src/features/Conversation/Messages/Group/Tool/Render/Intervention/Fallback.tsx +98 -0
  94. package/src/features/Conversation/Messages/Group/Tool/Render/Intervention/ModeSelector.tsx +5 -6
  95. package/src/features/Conversation/Messages/Group/Tool/Render/Intervention/index.tsx +40 -36
  96. package/src/features/Conversation/Messages/Group/Tool/Render/index.tsx +25 -18
  97. package/src/features/LocalFile/LocalFile.tsx +55 -5
  98. package/src/locales/default/components.ts +6 -0
  99. package/src/locales/default/plugin.ts +1 -0
  100. package/src/services/electron/localFileService.ts +4 -0
  101. package/src/store/chat/agents/GeneralChatAgent.ts +26 -1
  102. package/src/store/chat/agents/__tests__/GeneralChatAgent.test.ts +173 -0
  103. package/src/store/chat/slices/aiChat/actions/conversationControl.ts +8 -40
  104. package/src/store/chat/slices/aiChat/actions/streamingExecutor.ts +91 -34
  105. package/src/store/user/selectors.ts +1 -0
  106. package/src/store/user/slices/settings/action.ts +12 -0
  107. package/src/store/user/slices/settings/selectors/__snapshots__/settings.test.ts.snap +0 -7
  108. package/src/store/user/slices/settings/selectors/index.ts +1 -0
  109. package/src/store/user/slices/settings/selectors/settings.test.ts +0 -37
  110. package/src/store/user/slices/settings/selectors/settings.ts +0 -5
  111. package/src/store/user/slices/settings/selectors/toolIntervention.ts +17 -0
  112. package/src/tools/interventions.ts +8 -0
  113. package/src/tools/local-system/Intervention/RunCommand/index.tsx +56 -0
  114. package/src/tools/local-system/Intervention/index.tsx +17 -0
  115. package/src/tools/local-system/Render/RunCommand/index.tsx +100 -21
  116. package/src/tools/local-system/Render/index.tsx +2 -0
  117. package/src/tools/local-system/index.ts +180 -0
  118. package/src/tools/local-system/systemRole.ts +61 -7
@@ -4,12 +4,17 @@ import {
4
4
  OpenAICompatibleFactoryOptions,
5
5
  createOpenAICompatibleRuntime,
6
6
  } from '../../core/openaiCompatibleFactory';
7
+ import { processMultiProviderModelList } from '../../utils/modelParse';
8
+
9
+ export interface WenxinModelCard {
10
+ id: string;
11
+ }
7
12
 
8
13
  export const params = {
9
14
  baseURL: 'https://qianfan.baidubce.com/v2',
10
15
  chatCompletion: {
11
16
  handlePayload: (payload) => {
12
- const { enabledSearch, ...rest } = payload;
17
+ const { enabledSearch, thinking, ...rest } = payload;
13
18
 
14
19
  return {
15
20
  ...rest,
@@ -21,12 +26,28 @@ export const params = {
21
26
  enable_trace: true,
22
27
  },
23
28
  }),
29
+ ...(thinking && {
30
+ enable_thinking: { disabled: false, enabled: true }[thinking.type],
31
+ ...(thinking?.budget_tokens !== 0 && {
32
+ thinking_budget: Math.min(Math.max(thinking?.budget_tokens, 100), 16_384),
33
+ }),
34
+ }),
24
35
  } as any;
25
36
  },
26
37
  },
27
38
  debug: {
28
39
  chatCompletion: () => process.env.DEBUG_WENXIN_CHAT_COMPLETION === '1',
29
40
  },
41
+ models: async ({ client }) => {
42
+ const modelsPage = (await client.models.list()) as any;
43
+ const modelList: WenxinModelCard[] = modelsPage.data;
44
+
45
+ const standardModelList = modelList.map((model) => ({
46
+ id: model.id,
47
+ }));
48
+
49
+ return processMultiProviderModelList(standardModelList, 'wenxin');
50
+ },
30
51
  provider: ModelProvider.Wenxin,
31
52
  } satisfies OpenAICompatibleFactoryOptions;
32
53
 
@@ -98,6 +98,11 @@ export const MODEL_LIST_CONFIGS = {
98
98
  reasoningKeywords: ['thinking', 'seed', 'ui-tars'],
99
99
  visionKeywords: ['vision', '-m', 'seed', 'ui-tars'],
100
100
  },
101
+ wenxin: {
102
+ functionCallKeywords: ['ernie-5', 'ernie-x1', 'pro', 'ernie-4.5-21b-a3b-thinking'],
103
+ reasoningKeywords: ['thinking', 'ernie-x', 'ernie-4.5-vl-28b-a3b'],
104
+ visionKeywords: ['-vl', 'ernie-5.0', 'picocr', 'qianfan-composition'],
105
+ },
101
106
  xai: {
102
107
  functionCallKeywords: ['grok'],
103
108
  reasoningKeywords: ['mini', 'grok-4', 'grok-code-fast', '!non-reasoning'],
@@ -129,6 +134,7 @@ export const MODEL_OWNER_DETECTION_CONFIG = {
129
134
  qwen: ['qwen', 'qwq', 'qvq'],
130
135
  v0: ['v0'],
131
136
  volcengine: ['doubao'],
137
+ wenxin: ['ernie', 'qianfan'],
132
138
  xai: ['grok'],
133
139
  zeroone: ['yi-'],
134
140
  zhipu: ['glm'],
@@ -151,3 +151,12 @@ export interface BuiltinServerRuntimeOutput {
151
151
  state?: any;
152
152
  success: boolean;
153
153
  }
154
+
155
+ export interface BuiltinInterventionProps<Arguments = any> {
156
+ apiName?: string;
157
+ args: Arguments;
158
+ identifier?: string;
159
+ messageId: string;
160
+ }
161
+
162
+ export type BuiltinIntervention = (props: BuiltinInterventionProps) => ReactNode;
@@ -5,9 +5,9 @@ import { z } from 'zod';
5
5
  */
6
6
  export type HumanInterventionPolicy =
7
7
  | 'never' // Never intervene, auto-execute
8
- | 'require'; // Always require intervention
8
+ | 'required'; // Always require intervention
9
9
 
10
- export const HumanInterventionPolicySchema = z.enum(['never', 'require']);
10
+ export const HumanInterventionPolicySchema = z.enum(['never', 'required']);
11
11
 
12
12
  /**
13
13
  * Argument Matcher for parameter-level filtering
@@ -116,6 +116,36 @@ export const HumanInterventionResponseSchema = z.object({
116
116
  .optional(),
117
117
  });
118
118
 
119
+ /**
120
+ * User's global intervention configuration
121
+ * Applied across all tools in the session
122
+ */
123
+ export interface UserInterventionConfig {
124
+ /**
125
+ * Allow list of approved tools (used in 'allow-list' mode)
126
+ * Format: "identifier/apiName"
127
+ *
128
+ * Examples:
129
+ * - "bash/bash"
130
+ * - "web-browsing/crawlSinglePage"
131
+ * - "search/search"
132
+ */
133
+ allowList?: string[];
134
+
135
+ /**
136
+ * Tool approval mode
137
+ * - auto-run: Automatically approve all tools without user consent
138
+ * - allow-list: Only approve tools in the allow list
139
+ * - manual: Use tool's own humanIntervention config (default)
140
+ */
141
+ approvalMode: 'auto-run' | 'allow-list' | 'manual';
142
+ }
143
+
144
+ export const UserInterventionConfigSchema = z.object({
145
+ allowList: z.array(z.string()).optional(),
146
+ approvalMode: z.enum(['auto-run', 'allow-list', 'manual']),
147
+ });
148
+
119
149
  /**
120
150
  * Parameters for shouldIntervene method
121
151
  */
@@ -1,29 +1,5 @@
1
- export interface UserToolConfig {
2
- /**
3
- * Tool approval mode
4
- * - auto-run: Automatically approve all tools without user consent
5
- * - allow-list: Only approve tools in the allow list
6
- * - manual: Require manual approval for each tool execution
7
- */
8
- approvalMode?: 'auto-run' | 'allow-list' | 'manual';
9
-
10
- dalle: {
11
- autoGenerate: boolean;
12
- };
1
+ import { UserInterventionConfig } from '../../tool';
13
2
 
14
- /**
15
- * Tool intervention configuration
16
- */
17
- humanIntervention?: {
18
- /**
19
- * Allow list of approved tools (used in 'allow-list' mode)
20
- * Format: "identifier/apiName"
21
- *
22
- * Examples:
23
- * - "web-browsing/crawlSinglePage"
24
- * - "bash/bash"
25
- * - "search/search"
26
- */
27
- allowList?: string[];
28
- };
3
+ export interface UserToolConfig {
4
+ humanIntervention?: UserInterventionConfig;
29
5
  }
@@ -1,13 +1,12 @@
1
1
  import { ModelProviderCard } from '@/types/llm';
2
2
 
3
- // ref https://cloud.baidu.com/doc/WENXINWORKSHOP/s/Nlks5zkzu
4
3
  const BaiduWenxin: ModelProviderCard = {
5
4
  chatModels: [],
6
- checkModel: 'ernie-speed-128k',
5
+ checkModel: 'ernie-4.5-turbo-latest',
7
6
  description:
8
7
  '企业级一站式大模型与AI原生应用开发及服务平台,提供最全面易用的生成式人工智能模型开发、应用开发全流程工具链',
9
8
  id: 'wenxin',
10
- modelsUrl: 'https://cloud.baidu.com/doc/WENXINWORKSHOP/s/Nlks5zkzu#%E5%AF%B9%E8%AF%9Dchat',
9
+ modelsUrl: 'https://console.bce.baidu.com/qianfan/modelcenter/model/buildIn/list',
11
10
  name: 'Wenxin',
12
11
  settings: {
13
12
  proxyUrl: {
@@ -1,5 +1,138 @@
1
1
  // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
2
 
3
+ exports[`createRemarkSelfClosingTagPlugin > should handle multiple tags in unordered list with directories and files 1`] = `
4
+ {
5
+ "children": [
6
+ {
7
+ "children": [
8
+ {
9
+ "position": {
10
+ "end": {
11
+ "column": 32,
12
+ "line": 1,
13
+ "offset": 31,
14
+ },
15
+ "start": {
16
+ "column": 1,
17
+ "line": 1,
18
+ "offset": 0,
19
+ },
20
+ },
21
+ "type": "text",
22
+ "value": "我已查看了你桌面上 test 文件夹的目录,里面包含以下项目:",
23
+ },
24
+ ],
25
+ "position": {
26
+ "end": {
27
+ "column": 32,
28
+ "line": 1,
29
+ "offset": 31,
30
+ },
31
+ "start": {
32
+ "column": 1,
33
+ "line": 1,
34
+ "offset": 0,
35
+ },
36
+ },
37
+ "type": "paragraph",
38
+ },
39
+ {
40
+ "children": [
41
+ {
42
+ "checked": null,
43
+ "children": [
44
+ {
45
+ "data": {
46
+ "hName": "localFile",
47
+ "hProperties": {
48
+ "isDirectory": true,
49
+ "name": ".config",
50
+ "path": "/Users/user/Desktop/test/.config",
51
+ },
52
+ },
53
+ "type": "localFile",
54
+ },
55
+ ],
56
+ "position": {
57
+ "end": {
58
+ "column": 87,
59
+ "line": 3,
60
+ "offset": 119,
61
+ },
62
+ "start": {
63
+ "column": 1,
64
+ "line": 3,
65
+ "offset": 33,
66
+ },
67
+ },
68
+ "spread": false,
69
+ "type": "listItem",
70
+ },
71
+ {
72
+ "checked": null,
73
+ "children": [
74
+ {
75
+ "data": {
76
+ "hName": "localFile",
77
+ "hProperties": {
78
+ "isDirectory": true,
79
+ "name": ".venv",
80
+ "path": "/Users/user/Desktop/test/.venv",
81
+ },
82
+ },
83
+ "type": "localFile",
84
+ },
85
+ ],
86
+ "position": {
87
+ "end": {
88
+ "column": 79,
89
+ "line": 4,
90
+ "offset": 198,
91
+ },
92
+ "start": {
93
+ "column": 1,
94
+ "line": 4,
95
+ "offset": 120,
96
+ },
97
+ },
98
+ "spread": false,
99
+ "type": "listItem",
100
+ },
101
+ ],
102
+ "ordered": false,
103
+ "position": {
104
+ "end": {
105
+ "column": 79,
106
+ "line": 4,
107
+ "offset": 198,
108
+ },
109
+ "start": {
110
+ "column": 1,
111
+ "line": 3,
112
+ "offset": 33,
113
+ },
114
+ },
115
+ "spread": false,
116
+ "start": null,
117
+ "type": "list",
118
+ },
119
+ ],
120
+ "position": {
121
+ "end": {
122
+ "column": 79,
123
+ "line": 4,
124
+ "offset": 198,
125
+ },
126
+ "start": {
127
+ "column": 1,
128
+ "line": 1,
129
+ "offset": 0,
130
+ },
131
+ },
132
+ "type": "root",
133
+ }
134
+ `;
135
+
3
136
  exports[`createRemarkSelfClosingTagPlugin > should handle tag within a list item and generate snapshot 1`] = `
4
137
  {
5
138
  "children": [
@@ -201,4 +201,52 @@ describe('createRemarkSelfClosingTagPlugin', () => {
201
201
  const tree = processMarkdown(markdown, tagName);
202
202
  expect(tree).toMatchSnapshot();
203
203
  });
204
+
205
+ it('should handle multiple tags in unordered list with directories and files', () => {
206
+ const markdown = [
207
+ '我已查看了你桌面上 test 文件夹的目录,里面包含以下项目:',
208
+ '',
209
+ '- <localFile name=".config" path="/Users/user/Desktop/test/.config" isDirectory /> ', // 注意:行尾有 4 个空格
210
+ '- <localFile name=".venv" path="/Users/user/Desktop/test/.venv" isDirectory />',
211
+ ].join('\n');
212
+ const tree = processMarkdown(markdown, tagName);
213
+
214
+ // Should have 2 children: a paragraph and a list
215
+ expect(tree.children).toHaveLength(2);
216
+
217
+ // First child should be the introductory paragraph
218
+ expect(tree.children[0].type).toBe('paragraph');
219
+
220
+ // Second child should be the unordered list
221
+ const listNode = tree.children[1];
222
+ expect(listNode.type).toBe('list');
223
+ expect(listNode.ordered).toBe(false);
224
+
225
+ // The list should have 2 items
226
+ expect(listNode.children).toHaveLength(2);
227
+
228
+ // Verify first item (.config directory) - this one has trailing spaces after />
229
+ // When a tag is standalone in a list item, remark doesn't wrap it in a paragraph
230
+ const firstItem = listNode.children[0];
231
+ expect(firstItem.type).toBe('listItem');
232
+ const firstTag = firstItem.children[0];
233
+ expect(firstTag.type).toBe(tagName);
234
+ expect(firstTag.data?.hProperties).toEqual({
235
+ name: '.config',
236
+ path: '/Users/user/Desktop/test/.config',
237
+ isDirectory: true,
238
+ });
239
+
240
+ // Verify second item (.venv directory)
241
+ const secondItem = listNode.children[1];
242
+ const secondTag = secondItem.children[0];
243
+ expect(secondTag.type).toBe(tagName);
244
+ expect(secondTag.data?.hProperties).toEqual({
245
+ name: '.venv',
246
+ path: '/Users/user/Desktop/test/.venv',
247
+ isDirectory: true,
248
+ });
249
+
250
+ expect(tree).toMatchSnapshot();
251
+ });
204
252
  });
@@ -25,7 +25,8 @@ export const createRemarkSelfClosingTagPlugin =
25
25
  (tagName: string): Plugin<[], any> =>
26
26
  () => {
27
27
  // Regex for the specific tag, ensure it matches the entire string for HTML check
28
- const exactTagRegex = new RegExp(`^<${tagName}(\\s+[^>]*?)?\\s*\\/>$`);
28
+ // Allow trailing whitespace after /> to handle cases where markdown parsers include it
29
+ const exactTagRegex = new RegExp(`^<${tagName}(\\s+[^>]*?)?\\s*\\/>\\s*$`);
29
30
  // Regex for finding tags within text
30
31
  const textTagRegex = new RegExp(`<${tagName}(\\s+[^>]*?)?\\s*\\/>`, 'g');
31
32
 
@@ -0,0 +1,98 @@
1
+ import { safeParseJSON } from '@lobechat/utils';
2
+ import { ActionIcon } from '@lobehub/ui';
3
+ import { Edit3Icon } from 'lucide-react';
4
+ import { Suspense, memo, useCallback, useState } from 'react';
5
+ import { useTranslation } from 'react-i18next';
6
+ import { Flexbox } from 'react-layout-kit';
7
+
8
+ import { useChatStore } from '@/store/chat';
9
+ import { useUserStore } from '@/store/user';
10
+ import { toolInterventionSelectors } from '@/store/user/selectors';
11
+
12
+ import Arguments from '../Arguments';
13
+ import ApprovalActions from './ApprovalActions';
14
+ import KeyValueEditor from './KeyValueEditor';
15
+ import ModeSelector from './ModeSelector';
16
+
17
+ interface FallbackInterventionProps {
18
+ apiName: string;
19
+ id: string;
20
+ identifier: string;
21
+ requestArgs: string;
22
+ toolCallId: string;
23
+ }
24
+
25
+ const FallbackIntervention = memo<FallbackInterventionProps>(
26
+ ({ requestArgs, id, identifier, apiName, toolCallId }) => {
27
+ const { t } = useTranslation('chat');
28
+ const approvalMode = useUserStore(toolInterventionSelectors.approvalMode);
29
+ const [isEditing, setIsEditing] = useState(false);
30
+ const [optimisticUpdatePluginArguments] = useChatStore((s) => [
31
+ s.optimisticUpdatePluginArguments,
32
+ ]);
33
+
34
+ const handleCancel = useCallback(() => {
35
+ setIsEditing(false);
36
+ }, []);
37
+
38
+ const handleFinish = useCallback(
39
+ async (editedObject: Record<string, any>) => {
40
+ if (!id) return;
41
+
42
+ try {
43
+ const newArgsString = JSON.stringify(editedObject, null, 2);
44
+
45
+ if (newArgsString !== requestArgs) {
46
+ await optimisticUpdatePluginArguments(id, editedObject, true);
47
+ }
48
+ setIsEditing(false);
49
+ } catch (error) {
50
+ console.error('Error stringifying arguments:', error);
51
+ }
52
+ },
53
+ [requestArgs, id],
54
+ );
55
+
56
+ if (isEditing)
57
+ return (
58
+ <Suspense fallback={<Arguments arguments={requestArgs} />}>
59
+ <KeyValueEditor
60
+ initialValue={safeParseJSON(requestArgs || '')}
61
+ onCancel={handleCancel}
62
+ onFinish={handleFinish}
63
+ />
64
+ </Suspense>
65
+ );
66
+
67
+ return (
68
+ <Flexbox gap={12}>
69
+ <Arguments
70
+ actions={
71
+ <ActionIcon
72
+ icon={Edit3Icon}
73
+ onClick={() => {
74
+ setIsEditing(true);
75
+ }}
76
+ size={'small'}
77
+ title={t('edit', { ns: 'common' })}
78
+ />
79
+ }
80
+ arguments={requestArgs}
81
+ />
82
+
83
+ <Flexbox horizontal justify={'space-between'}>
84
+ <ModeSelector />
85
+ <ApprovalActions
86
+ apiName={apiName}
87
+ approvalMode={approvalMode}
88
+ identifier={identifier}
89
+ messageId={id}
90
+ toolCallId={toolCallId}
91
+ />
92
+ </Flexbox>
93
+ </Flexbox>
94
+ );
95
+ },
96
+ );
97
+
98
+ export default FallbackIntervention;
@@ -6,6 +6,7 @@ import { useTranslation } from 'react-i18next';
6
6
  import { Center } from 'react-layout-kit';
7
7
 
8
8
  import { useUserStore } from '@/store/user';
9
+ import { toolInterventionSelectors } from '@/store/user/selectors';
9
10
 
10
11
  import { ApprovalMode } from './index';
11
12
 
@@ -39,10 +40,8 @@ const useStyles = createStyles(({ css, token }) => ({
39
40
  const ModeSelector = memo(() => {
40
41
  const { t } = useTranslation('chat');
41
42
  const { styles } = useStyles();
42
- const [approvalMode, setSettings] = useUserStore((s) => [
43
- s.settings.tool?.approvalMode || 'manual',
44
- s.setSettings,
45
- ]);
43
+ const approvalMode = useUserStore(toolInterventionSelectors.approvalMode);
44
+ const updateHumanIntervention = useUserStore((s) => s.updateHumanIntervention);
46
45
 
47
46
  const modeLabels = useMemo(
48
47
  () => ({
@@ -55,9 +54,9 @@ const ModeSelector = memo(() => {
55
54
 
56
55
  const handleModeChange = useCallback(
57
56
  async (mode: ApprovalMode) => {
58
- await setSettings({ tool: { approvalMode: mode } });
57
+ await updateHumanIntervention({ approvalMode: mode });
59
58
  },
60
- [setSettings],
59
+ [updateHumanIntervention],
61
60
  );
62
61
 
63
62
  const menuItems = useMemo<MenuProps['items']>(
@@ -1,15 +1,15 @@
1
1
  import { safeParseJSON } from '@lobechat/utils';
2
- import { ActionIcon } from '@lobehub/ui';
3
- import { Edit3Icon } from 'lucide-react';
4
2
  import { Suspense, memo, useCallback, useState } from 'react';
5
- import { useTranslation } from 'react-i18next';
6
3
  import { Flexbox } from 'react-layout-kit';
7
4
 
8
5
  import { useChatStore } from '@/store/chat';
9
6
  import { useUserStore } from '@/store/user';
7
+ import { toolInterventionSelectors } from '@/store/user/selectors';
8
+ import { BuiltinToolInterventions } from '@/tools/interventions';
10
9
 
11
10
  import Arguments from '../Arguments';
12
11
  import ApprovalActions from './ApprovalActions';
12
+ import Fallback from './Fallback';
13
13
  import KeyValueEditor from './KeyValueEditor';
14
14
  import ModeSelector from './ModeSelector';
15
15
 
@@ -25,8 +25,7 @@ interface InterventionProps {
25
25
 
26
26
  const Intervention = memo<InterventionProps>(
27
27
  ({ requestArgs, id, identifier, apiName, toolCallId }) => {
28
- const { t } = useTranslation('chat');
29
- const approvalMode = useUserStore((s) => s.settings.tool?.approvalMode || 'manual');
28
+ const approvalMode = useUserStore(toolInterventionSelectors.approvalMode);
30
29
  const [isEditing, setIsEditing] = useState(false);
31
30
  const [optimisticUpdatePluginArguments] = useChatStore((s) => [
32
31
  s.optimisticUpdatePluginArguments,
@@ -53,45 +52,50 @@ const Intervention = memo<InterventionProps>(
53
52
  },
54
53
  [requestArgs, id],
55
54
  );
55
+ const BuiltinToolInterventionRender = BuiltinToolInterventions[identifier];
56
56
 
57
- if (isEditing)
58
- return (
59
- <Suspense fallback={<Arguments arguments={requestArgs} />}>
60
- <KeyValueEditor
61
- initialValue={safeParseJSON(requestArgs || '')}
62
- onCancel={handleCancel}
63
- onFinish={handleFinish}
64
- />
65
- </Suspense>
66
- );
67
-
68
- return (
69
- <Flexbox gap={12}>
70
- <Arguments
71
- actions={
72
- <ActionIcon
73
- icon={Edit3Icon}
74
- onClick={() => {
75
- setIsEditing(true);
76
- }}
77
- size={'small'}
78
- title={t('edit', { ns: 'common' })}
57
+ if (BuiltinToolInterventionRender) {
58
+ if (isEditing)
59
+ return (
60
+ <Suspense fallback={<Arguments arguments={requestArgs} />}>
61
+ <KeyValueEditor
62
+ initialValue={safeParseJSON(requestArgs || '')}
63
+ onCancel={handleCancel}
64
+ onFinish={handleFinish}
79
65
  />
80
- }
81
- arguments={requestArgs}
82
- />
66
+ </Suspense>
67
+ );
83
68
 
84
- <Flexbox horizontal justify={'space-between'}>
85
- <ModeSelector />
86
- <ApprovalActions
69
+ return (
70
+ <Flexbox gap={12}>
71
+ <BuiltinToolInterventionRender
87
72
  apiName={apiName}
88
- approvalMode={approvalMode}
73
+ args={safeParseJSON(requestArgs || '')}
89
74
  identifier={identifier}
90
75
  messageId={id}
91
- toolCallId={toolCallId}
92
76
  />
77
+ <Flexbox horizontal justify={'space-between'}>
78
+ <ModeSelector />
79
+ <ApprovalActions
80
+ apiName={apiName}
81
+ approvalMode={approvalMode}
82
+ identifier={identifier}
83
+ messageId={id}
84
+ toolCallId={toolCallId}
85
+ />
86
+ </Flexbox>
93
87
  </Flexbox>
94
- </Flexbox>
88
+ );
89
+ }
90
+
91
+ return (
92
+ <Fallback
93
+ apiName={apiName}
94
+ id={id}
95
+ identifier={identifier}
96
+ requestArgs={requestArgs}
97
+ toolCallId={toolCallId}
98
+ />
95
99
  );
96
100
  },
97
101
  );