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

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 (171) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/apps/desktop/src/main/controllers/LocalFileCtr.ts +25 -5
  3. package/apps/desktop/src/main/controllers/ShellCommandCtr.ts +242 -0
  4. package/apps/desktop/src/main/controllers/__tests__/LocalFileCtr.test.ts +4 -1
  5. package/apps/desktop/src/main/controllers/__tests__/ShellCommandCtr.test.ts +499 -0
  6. package/apps/desktop/src/main/modules/fileSearch/__tests__/macOS.integration.test.ts +357 -0
  7. package/apps/desktop/src/main/modules/fileSearch/impl/macOS.ts +30 -22
  8. package/changelog/v1.json +18 -0
  9. package/locales/ar/chat.json +20 -0
  10. package/locales/ar/common.json +1 -0
  11. package/locales/ar/components.json +6 -0
  12. package/locales/ar/models.json +119 -126
  13. package/locales/ar/plugin.json +2 -1
  14. package/locales/bg-BG/chat.json +20 -0
  15. package/locales/bg-BG/common.json +1 -0
  16. package/locales/bg-BG/components.json +6 -0
  17. package/locales/bg-BG/models.json +104 -132
  18. package/locales/bg-BG/plugin.json +2 -1
  19. package/locales/de-DE/chat.json +20 -0
  20. package/locales/de-DE/common.json +1 -0
  21. package/locales/de-DE/components.json +6 -0
  22. package/locales/de-DE/models.json +119 -126
  23. package/locales/de-DE/plugin.json +2 -1
  24. package/locales/en-US/chat.json +20 -0
  25. package/locales/en-US/common.json +1 -0
  26. package/locales/en-US/components.json +6 -0
  27. package/locales/en-US/models.json +167 -126
  28. package/locales/en-US/plugin.json +2 -1
  29. package/locales/es-ES/chat.json +20 -0
  30. package/locales/es-ES/common.json +1 -0
  31. package/locales/es-ES/components.json +6 -0
  32. package/locales/es-ES/models.json +119 -126
  33. package/locales/es-ES/plugin.json +2 -1
  34. package/locales/fa-IR/chat.json +20 -0
  35. package/locales/fa-IR/common.json +1 -0
  36. package/locales/fa-IR/components.json +6 -0
  37. package/locales/fa-IR/models.json +119 -126
  38. package/locales/fa-IR/plugin.json +2 -1
  39. package/locales/fr-FR/chat.json +20 -0
  40. package/locales/fr-FR/common.json +1 -0
  41. package/locales/fr-FR/components.json +6 -0
  42. package/locales/fr-FR/models.json +119 -126
  43. package/locales/fr-FR/plugin.json +2 -1
  44. package/locales/it-IT/chat.json +20 -0
  45. package/locales/it-IT/common.json +1 -0
  46. package/locales/it-IT/components.json +6 -0
  47. package/locales/it-IT/models.json +119 -126
  48. package/locales/it-IT/plugin.json +2 -1
  49. package/locales/ja-JP/chat.json +20 -0
  50. package/locales/ja-JP/common.json +1 -0
  51. package/locales/ja-JP/components.json +6 -0
  52. package/locales/ja-JP/models.json +119 -126
  53. package/locales/ja-JP/plugin.json +2 -1
  54. package/locales/ko-KR/chat.json +20 -0
  55. package/locales/ko-KR/common.json +1 -0
  56. package/locales/ko-KR/components.json +6 -0
  57. package/locales/ko-KR/models.json +119 -126
  58. package/locales/ko-KR/plugin.json +2 -1
  59. package/locales/nl-NL/chat.json +20 -0
  60. package/locales/nl-NL/common.json +1 -0
  61. package/locales/nl-NL/components.json +6 -0
  62. package/locales/nl-NL/models.json +119 -126
  63. package/locales/nl-NL/plugin.json +2 -1
  64. package/locales/pl-PL/chat.json +20 -0
  65. package/locales/pl-PL/common.json +1 -0
  66. package/locales/pl-PL/components.json +6 -0
  67. package/locales/pl-PL/models.json +119 -126
  68. package/locales/pl-PL/plugin.json +2 -1
  69. package/locales/pt-BR/chat.json +20 -0
  70. package/locales/pt-BR/common.json +1 -0
  71. package/locales/pt-BR/components.json +6 -0
  72. package/locales/pt-BR/models.json +119 -126
  73. package/locales/pt-BR/plugin.json +2 -1
  74. package/locales/ru-RU/chat.json +20 -0
  75. package/locales/ru-RU/common.json +1 -0
  76. package/locales/ru-RU/components.json +6 -0
  77. package/locales/ru-RU/models.json +119 -126
  78. package/locales/ru-RU/plugin.json +2 -1
  79. package/locales/tr-TR/chat.json +20 -0
  80. package/locales/tr-TR/common.json +1 -0
  81. package/locales/tr-TR/components.json +6 -0
  82. package/locales/tr-TR/models.json +119 -126
  83. package/locales/tr-TR/plugin.json +2 -1
  84. package/locales/vi-VN/chat.json +20 -0
  85. package/locales/vi-VN/common.json +1 -0
  86. package/locales/vi-VN/components.json +6 -0
  87. package/locales/vi-VN/models.json +119 -126
  88. package/locales/vi-VN/plugin.json +2 -1
  89. package/locales/zh-CN/chat.json +20 -0
  90. package/locales/zh-CN/common.json +1 -0
  91. package/locales/zh-CN/components.json +6 -0
  92. package/locales/zh-CN/models.json +173 -80
  93. package/locales/zh-CN/plugin.json +2 -1
  94. package/locales/zh-TW/chat.json +20 -0
  95. package/locales/zh-TW/common.json +1 -0
  96. package/locales/zh-TW/components.json +6 -0
  97. package/locales/zh-TW/models.json +119 -126
  98. package/locales/zh-TW/plugin.json +2 -1
  99. package/package.json +1 -1
  100. package/packages/agent-runtime/src/core/InterventionChecker.ts +1 -1
  101. package/packages/agent-runtime/src/core/__tests__/InterventionChecker.test.ts +23 -23
  102. package/packages/agent-runtime/src/types/state.ts +7 -1
  103. package/packages/const/src/settings/tool.ts +1 -5
  104. package/packages/electron-client-ipc/src/types/localSystem.ts +26 -2
  105. package/packages/file-loaders/src/loaders/docx/index.ts +1 -1
  106. package/packages/model-bank/src/aiModels/wenxin.ts +1348 -291
  107. package/packages/model-runtime/src/core/contextBuilders/openai.test.ts +58 -0
  108. package/packages/model-runtime/src/core/contextBuilders/openai.ts +24 -10
  109. package/packages/model-runtime/src/core/openaiCompatibleFactory/index.ts +3 -2
  110. package/packages/model-runtime/src/providers/openai/index.test.ts +44 -0
  111. package/packages/model-runtime/src/providers/wenxin/index.ts +22 -1
  112. package/packages/model-runtime/src/utils/modelParse.ts +6 -0
  113. package/packages/types/src/tool/builtin.ts +15 -4
  114. package/packages/types/src/tool/intervention.ts +32 -2
  115. package/packages/types/src/user/settings/tool.ts +3 -27
  116. package/src/config/modelProviders/wenxin.ts +2 -3
  117. package/src/features/Conversation/MarkdownElements/remarkPlugins/__snapshots__/createRemarkSelfClosingTagPlugin.test.ts.snap +133 -0
  118. package/src/features/Conversation/MarkdownElements/remarkPlugins/createRemarkSelfClosingTagPlugin.test.ts +48 -0
  119. package/src/features/Conversation/MarkdownElements/remarkPlugins/createRemarkSelfClosingTagPlugin.ts +2 -1
  120. package/src/features/Conversation/Messages/Assistant/Tool/Render/LoadingPlaceholder/index.tsx +3 -3
  121. package/src/features/Conversation/Messages/Group/Tool/Render/Intervention/Fallback.tsx +98 -0
  122. package/src/features/Conversation/Messages/Group/Tool/Render/Intervention/ModeSelector.tsx +5 -6
  123. package/src/features/Conversation/Messages/Group/Tool/Render/Intervention/index.tsx +40 -36
  124. package/src/features/Conversation/Messages/Group/Tool/Render/LoadingPlaceholder/index.tsx +3 -3
  125. package/src/features/Conversation/Messages/Group/Tool/Render/index.tsx +25 -18
  126. package/src/features/LocalFile/LocalFile.tsx +55 -5
  127. package/src/features/PluginsUI/Render/BuiltinType/index.test.tsx +10 -4
  128. package/src/features/PluginsUI/Render/BuiltinType/index.tsx +2 -2
  129. package/src/locales/default/components.ts +6 -0
  130. package/src/locales/default/plugin.ts +2 -1
  131. package/src/services/chat/chat.test.ts +1 -0
  132. package/src/services/electron/localFileService.ts +4 -0
  133. package/src/store/aiInfra/slices/aiProvider/__tests__/selectors.test.ts +62 -0
  134. package/src/store/aiInfra/slices/aiProvider/selectors.ts +1 -1
  135. package/src/store/chat/agents/GeneralChatAgent.ts +26 -1
  136. package/src/store/chat/agents/__tests__/GeneralChatAgent.test.ts +173 -0
  137. package/src/store/chat/slices/aiChat/actions/conversationControl.ts +8 -40
  138. package/src/store/chat/slices/aiChat/actions/streamingExecutor.ts +91 -34
  139. package/src/store/user/selectors.ts +1 -0
  140. package/src/store/user/slices/settings/action.ts +12 -0
  141. package/src/store/user/slices/settings/selectors/__snapshots__/settings.test.ts.snap +0 -7
  142. package/src/store/user/slices/settings/selectors/index.ts +1 -0
  143. package/src/store/user/slices/settings/selectors/settings.test.ts +0 -37
  144. package/src/store/user/slices/settings/selectors/settings.ts +0 -5
  145. package/src/store/user/slices/settings/selectors/toolIntervention.ts +17 -0
  146. package/src/tools/code-interpreter/Render/index.tsx +1 -1
  147. package/src/tools/interventions.ts +32 -0
  148. package/src/tools/local-system/Intervention/RunCommand/index.tsx +56 -0
  149. package/src/tools/local-system/Placeholder/ListFiles.tsx +3 -5
  150. package/src/tools/local-system/Placeholder/SearchFiles.tsx +2 -5
  151. package/src/tools/local-system/Render/ListFiles/index.tsx +16 -21
  152. package/src/tools/local-system/Render/RenameLocalFile/index.tsx +15 -20
  153. package/src/tools/local-system/Render/RunCommand/index.tsx +103 -27
  154. package/src/tools/local-system/Render/SearchFiles/SearchQuery/index.tsx +0 -1
  155. package/src/tools/local-system/Render/SearchFiles/index.tsx +15 -20
  156. package/src/tools/local-system/Render/WriteFile/index.tsx +2 -8
  157. package/src/tools/local-system/index.ts +184 -4
  158. package/src/tools/local-system/systemRole.ts +62 -8
  159. package/src/tools/placeholders.ts +39 -8
  160. package/src/tools/renders.ts +56 -9
  161. package/src/tools/web-browsing/Placeholder/{PageContent.tsx → CrawlMultiPages.tsx} +4 -1
  162. package/src/tools/web-browsing/Placeholder/CrawlSinglePage.tsx +12 -0
  163. package/src/tools/web-browsing/Placeholder/Search.tsx +4 -4
  164. package/src/tools/web-browsing/Render/CrawlMultiPages.tsx +15 -0
  165. package/src/tools/web-browsing/Render/CrawlSinglePage.tsx +15 -0
  166. package/src/tools/web-browsing/Render/Search/index.tsx +39 -44
  167. package/packages/database/migrations/0044_add_tool_intervention.sql +0 -1
  168. package/src/tools/local-system/Placeholder/index.tsx +0 -25
  169. package/src/tools/local-system/Render/index.tsx +0 -40
  170. package/src/tools/web-browsing/Placeholder/index.tsx +0 -40
  171. package/src/tools/web-browsing/Render/index.tsx +0 -57
@@ -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 { getBuiltinIntervention } 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 = getBuiltinIntervention(identifier, apiName);
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
  );
@@ -1,7 +1,7 @@
1
1
  import { safeParseJSON } from '@lobechat/utils';
2
2
  import { memo } from 'react';
3
3
 
4
- import { BuiltinToolPlaceholders } from '@/tools/placeholders';
4
+ import { getBuiltinPlaceholder } from '@/tools/placeholders';
5
5
 
6
6
  import Arguments from '../Arguments';
7
7
 
@@ -14,9 +14,9 @@ interface LoadingPlaceholderProps {
14
14
 
15
15
  const LoadingPlaceholder = memo<LoadingPlaceholderProps>(
16
16
  ({ identifier, requestArgs, apiName, loading }) => {
17
- const Render = BuiltinToolPlaceholders[identifier || ''];
17
+ const Render = getBuiltinPlaceholder(identifier, apiName);
18
18
 
19
- if (identifier && Render) {
19
+ if (Render) {
20
20
  return (
21
21
  <Render apiName={apiName} args={safeParseJSON(requestArgs) || {}} identifier={identifier} />
22
22
  );
@@ -1,10 +1,12 @@
1
1
  import { LOADING_FLAT } from '@lobechat/const';
2
2
  import { ChatToolResult, ToolIntervention } from '@lobechat/types';
3
3
  import { Suspense, memo } from 'react';
4
+ import { Flexbox } from 'react-layout-kit';
4
5
 
5
6
  import CustomRender from './CustomRender';
6
7
  import ErrorResponse from './ErrorResponse';
7
8
  import Intervention from './Intervention';
9
+ import ModeSelector from './Intervention/ModeSelector';
8
10
  import LoadingPlaceholder from './LoadingPlaceholder';
9
11
  import RejectedResponse from './RejectedResponse';
10
12
 
@@ -99,24 +101,29 @@ const Render = memo<RenderProps>(
99
101
 
100
102
  return (
101
103
  <Suspense fallback={placeholder}>
102
- <CustomRender
103
- content={result.content || ''}
104
- id={toolCallId}
105
- plugin={
106
- type
107
- ? ({
108
- apiName,
109
- arguments: requestArgs || '',
110
- identifier,
111
- type,
112
- } as any)
113
- : undefined
114
- }
115
- pluginState={result.state}
116
- requestArgs={requestArgs}
117
- setShowPluginRender={setShowPluginRender}
118
- showPluginRender={showPluginRender}
119
- />
104
+ <Flexbox gap={8}>
105
+ <CustomRender
106
+ content={result.content || ''}
107
+ id={toolCallId}
108
+ plugin={
109
+ type
110
+ ? ({
111
+ apiName,
112
+ arguments: requestArgs || '',
113
+ identifier,
114
+ type,
115
+ } as any)
116
+ : undefined
117
+ }
118
+ pluginState={result.state}
119
+ requestArgs={requestArgs}
120
+ setShowPluginRender={setShowPluginRender}
121
+ showPluginRender={showPluginRender}
122
+ />
123
+ <div>
124
+ <ModeSelector />
125
+ </div>
126
+ </Flexbox>
120
127
  </Suspense>
121
128
  );
122
129
  },
@@ -1,5 +1,9 @@
1
+ import { Button } from '@lobehub/ui';
2
+ import { Popover, Space } from 'antd';
1
3
  import { createStyles } from 'antd-style';
4
+ import { ExternalLink, FolderOpen } from 'lucide-react';
2
5
  import React from 'react';
6
+ import { useTranslation } from 'react-i18next';
3
7
  import { Flexbox } from 'react-layout-kit';
4
8
 
5
9
  import FileIcon from '@/components/FileIcon';
@@ -13,7 +17,7 @@ const useStyles = createStyles(({ css, token }) => ({
13
17
  padding-inline: 4px 8px;
14
18
  border-radius: 4px;
15
19
 
16
- color: ${token.colorTextSecondary};
20
+ color: ${token.colorText};
17
21
 
18
22
  :hover {
19
23
  color: ${token.colorText};
@@ -39,19 +43,25 @@ interface LocalFileProps {
39
43
 
40
44
  export const LocalFile = ({ name, path, isDirectory = false }: LocalFileProps) => {
41
45
  const { styles } = useStyles();
42
- const handleClick = () => {
43
- if (!path) return;
46
+ const { t } = useTranslation('components');
44
47
 
48
+ const handleOpenFile = () => {
49
+ if (!path) return;
45
50
  localFileService.openLocalFileOrFolder(path, isDirectory);
46
51
  };
47
52
 
48
- return (
53
+ const handleOpenFolder = () => {
54
+ if (!path) return;
55
+ localFileService.openFileFolder(path);
56
+ };
57
+
58
+ const fileContent = (
49
59
  <Flexbox
50
60
  align={'center'}
51
61
  className={styles.container}
52
62
  gap={4}
53
63
  horizontal
54
- onClick={handleClick}
64
+ onClick={isDirectory ? handleOpenFile : undefined}
55
65
  style={{ display: 'inline-flex', verticalAlign: 'middle' }}
56
66
  >
57
67
  <FileIcon fileName={name} isDirectory={isDirectory} size={22} variant={'raw'} />
@@ -60,4 +70,44 @@ export const LocalFile = ({ name, path, isDirectory = false }: LocalFileProps) =
60
70
  </Flexbox>
61
71
  </Flexbox>
62
72
  );
73
+
74
+ // Directory: no popover, just click to open
75
+ if (isDirectory) {
76
+ return fileContent;
77
+ }
78
+
79
+ // File: show popover with two actions
80
+ const popoverContent = (
81
+ <Space.Compact>
82
+ <Button
83
+ icon={ExternalLink}
84
+ onClick={handleOpenFile}
85
+ size="small"
86
+ title={t('LocalFile.action.open')}
87
+ >
88
+ {t('LocalFile.action.open')}
89
+ </Button>
90
+ <Button
91
+ icon={FolderOpen}
92
+ onClick={handleOpenFolder}
93
+ size="small"
94
+ title={t('LocalFile.action.showInFolder')}
95
+ >
96
+ {t('LocalFile.action.showInFolder')}
97
+ </Button>
98
+ </Space.Compact>
99
+ );
100
+
101
+ return (
102
+ <Popover
103
+ arrow={false}
104
+ content={popoverContent}
105
+ styles={{
106
+ body: { padding: 0 },
107
+ }}
108
+ trigger={['hover']}
109
+ >
110
+ {fileContent}
111
+ </Popover>
112
+ );
63
113
  };
@@ -4,11 +4,17 @@ import { describe, expect, it, vi } from 'vitest';
4
4
  import BuiltinType from './index';
5
5
 
6
6
  // Mock renders module
7
+ const mockWebBrowsingRender = vi.fn(({ content }) => <div>WebBrowsingRender: {content}</div>);
8
+ const mockCodeInterpreterRender = vi.fn(({ content }) => (
9
+ <div>CodeInterpreterRender: {content}</div>
10
+ ));
11
+
7
12
  vi.mock('@/tools/renders', () => ({
8
- BuiltinToolsRenders: {
9
- 'lobe-web-browsing': vi.fn(({ content }) => <div>WebBrowsingRender: {content}</div>),
10
- 'lobe-code-interpreter': vi.fn(({ content }) => <div>CodeInterpreterRender: {content}</div>),
11
- },
13
+ getBuiltinRender: vi.fn((identifier, apiName) => {
14
+ if (identifier === 'lobe-web-browsing') return mockWebBrowsingRender;
15
+ if (identifier === 'lobe-code-interpreter') return mockCodeInterpreterRender;
16
+ return undefined;
17
+ }),
12
18
  }));
13
19
 
14
20
  // Mock useParseContent hook
@@ -1,7 +1,7 @@
1
1
  import { safeParseJSON } from '@lobechat/utils';
2
2
  import { memo } from 'react';
3
3
 
4
- import { BuiltinToolsRenders } from '@/tools/renders';
4
+ import { getBuiltinRender } from '@/tools/renders';
5
5
 
6
6
  import { useParseContent } from '../useParseContent';
7
7
 
@@ -28,7 +28,7 @@ const BuiltinType = memo<BuiltinTypeProps>(
28
28
  }) => {
29
29
  const { data } = useParseContent(content);
30
30
 
31
- const Render = BuiltinToolsRenders[identifier || ''];
31
+ const Render = getBuiltinRender(identifier, apiName);
32
32
 
33
33
  if (!Render) return;
34
34
 
@@ -108,6 +108,12 @@ export default {
108
108
  keyPlaceholder: '键',
109
109
  valuePlaceholder: '值',
110
110
  },
111
+ LocalFile: {
112
+ action: {
113
+ open: '打开',
114
+ showInFolder: '在文件夹中显示',
115
+ },
116
+ },
111
117
  MaxTokenSlider: {
112
118
  unlimited: '无限制',
113
119
  },
@@ -256,10 +256,11 @@ export default {
256
256
  moveLocalFiles: '移动文件',
257
257
  readLocalFile: '读取文件内容',
258
258
  renameLocalFile: '重命名',
259
+ runCommand: '执行代码',
259
260
  searchLocalFiles: '搜索文件',
260
261
  writeLocalFile: '写入文件',
261
262
  },
262
- title: '本地文件',
263
+ title: '本地系统',
263
264
  },
264
265
  mcpInstall: {
265
266
  CHECKING_INSTALLATION: '检查安装环境...',
@@ -1004,6 +1004,7 @@ describe('ChatService', () => {
1004
1004
  stream: true,
1005
1005
  ...DEFAULT_AGENT_CONFIG.params,
1006
1006
  ...params,
1007
+ apiMode: 'responses',
1007
1008
  };
1008
1009
 
1009
1010
  await chatService.getChatCompletion(params, options);
@@ -98,6 +98,10 @@ class LocalFileService {
98
98
  return this.openLocalFile({ path });
99
99
  }
100
100
  }
101
+
102
+ async openFileFolder(path: string) {
103
+ return this.openLocalFolder({ isDirectory: false, path });
104
+ }
101
105
  }
102
106
 
103
107
  export const localFileService = new LocalFileService();
@@ -246,4 +246,66 @@ describe('aiProviderSelectors', () => {
246
246
  );
247
247
  });
248
248
  });
249
+
250
+ describe('isProviderEnableResponseApi', () => {
251
+ it('should return true when config explicitly sets enableResponseApi to true', () => {
252
+ const state = {
253
+ ...mockState,
254
+ aiProviderRuntimeConfig: {
255
+ test: {
256
+ config: { enableResponseApi: true },
257
+ keyVaults: {},
258
+ settings: {},
259
+ },
260
+ },
261
+ };
262
+ expect(aiProviderSelectors.isProviderEnableResponseApi('test')(state)).toBe(true);
263
+ });
264
+
265
+ it('should return false when config explicitly sets enableResponseApi to false', () => {
266
+ const state = {
267
+ ...mockState,
268
+ aiProviderRuntimeConfig: {
269
+ test: {
270
+ config: { enableResponseApi: false },
271
+ keyVaults: {},
272
+ settings: {},
273
+ },
274
+ },
275
+ };
276
+ expect(aiProviderSelectors.isProviderEnableResponseApi('test')(state)).toBe(false);
277
+ });
278
+
279
+ it('should return true by default for openai provider', () => {
280
+ const state = {
281
+ ...mockState,
282
+ aiProviderRuntimeConfig: {
283
+ openai: {
284
+ keyVaults: {},
285
+ settings: {},
286
+ },
287
+ },
288
+ };
289
+ expect(aiProviderSelectors.isProviderEnableResponseApi('openai')(state)).toBe(true);
290
+ });
291
+
292
+ it('should return false by default for non-openai provider', () => {
293
+ const state = {
294
+ ...mockState,
295
+ aiProviderRuntimeConfig: {
296
+ anthropic: {
297
+ keyVaults: {},
298
+ settings: {},
299
+ },
300
+ },
301
+ };
302
+ expect(aiProviderSelectors.isProviderEnableResponseApi('anthropic')(state)).toBe(false);
303
+ });
304
+
305
+ it('should return false for provider without config', () => {
306
+ expect(aiProviderSelectors.isProviderEnableResponseApi('non-existing')(mockState)).toBe(
307
+ false,
308
+ );
309
+ });
310
+ });
249
311
  });
@@ -108,7 +108,7 @@ const isProviderEnableResponseApi = (id: string) => (s: AIProviderStoreState) =>
108
108
 
109
109
  if (typeof enableResponseApi === 'boolean') return enableResponseApi;
110
110
 
111
- return false;
111
+ return id === 'openai';
112
112
  };
113
113
 
114
114
  const isInitAiProviderRuntimeState = (s: AIProviderStoreState) => !!s.isInitAiProviderRuntimeState;
@@ -54,6 +54,7 @@ export class GeneralChatAgent implements Agent {
54
54
 
55
55
  /**
56
56
  * Check if tool calls need human intervention
57
+ * Combines user's global config with tool's own config
57
58
  * Returns [toolsNeedingIntervention, toolsToExecute]
58
59
  */
59
60
  private checkInterventionNeeded(
@@ -63,7 +64,31 @@ export class GeneralChatAgent implements Agent {
63
64
  const toolsNeedingIntervention: ChatToolPayload[] = [];
64
65
  const toolsToExecute: ChatToolPayload[] = [];
65
66
 
67
+ // Get user config (default to 'manual' mode)
68
+ const userConfig = state.userInterventionConfig || { approvalMode: 'manual' };
69
+ const { approvalMode, allowList = [] } = userConfig;
70
+
66
71
  for (const toolCalling of toolsCalling) {
72
+ const { identifier, apiName } = toolCalling;
73
+ const toolKey = `${identifier}/${apiName}`;
74
+
75
+ // Priority 1: User config is 'auto-run', all tools execute directly
76
+ if (approvalMode === 'auto-run') {
77
+ toolsToExecute.push(toolCalling);
78
+ continue;
79
+ }
80
+
81
+ // Priority 2: User config is 'allow-list', check if tool is in whitelist
82
+ if (approvalMode === 'allow-list') {
83
+ if (allowList.includes(toolKey)) {
84
+ toolsToExecute.push(toolCalling);
85
+ } else {
86
+ toolsNeedingIntervention.push(toolCalling);
87
+ }
88
+ continue;
89
+ }
90
+
91
+ // Priority 3: User config is 'manual' (default), use tool's own config
67
92
  const config = this.getToolInterventionConfig(toolCalling, state);
68
93
 
69
94
  // Parse arguments for intervention checking
@@ -82,7 +107,7 @@ export class GeneralChatAgent implements Agent {
82
107
  if (policy === 'never') {
83
108
  toolsToExecute.push(toolCalling);
84
109
  } else {
85
- // 'require' or 'first' (when not confirmed) requires intervention
110
+ // 'required' or undefined requires intervention
86
111
  toolsNeedingIntervention.push(toolCalling);
87
112
  }
88
113
  }