@steerable/agent-shell-web 0.6.52 → 0.6.55
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/package.json +4 -4
- package/src/brand.ts +1 -1
- package/src/components/AgentSidebar.tsx +60 -52
- package/src/components/ChatHeader.tsx +41 -25
- package/src/components/ConfirmDialog.tsx +4 -3
- package/src/components/CreateProjectModal.tsx +29 -19
- package/src/components/LocalLlmSettingsModal.tsx +4 -3
- package/src/components/SidebarRelease.tsx +2 -1
- package/src/components/TerminalPanel.tsx +3 -2
- package/src/components/TerminalView.tsx +12 -7
- package/src/components/chat/ApprovalModal.tsx +39 -25
- package/src/components/chat/ApprovalPromptProvider.tsx +1 -1
- package/src/components/chat/AskUserPromptProvider.tsx +1 -1
- package/src/components/chat/AskUserQuestionMenu.tsx +19 -14
- package/src/components/chat/AssistantMessage.tsx +18 -13
- package/src/components/chat/ChatInput.tsx +138 -42
- package/src/components/chat/ChatProjectBadge.tsx +24 -19
- package/src/components/chat/EmptyChatHero.tsx +2 -1
- package/src/components/chat/ExecPolicyPicker.tsx +12 -11
- package/src/components/chat/ExecutedActionsCard.tsx +45 -33
- package/src/components/chat/FilePathCode.tsx +8 -3
- package/src/components/chat/InterruptedTurnCard.tsx +4 -3
- package/src/components/chat/LocalChatPanel.tsx +2 -1
- package/src/components/chat/Markdown.tsx +2 -1
- package/src/components/chat/MessageList.tsx +3 -2
- package/src/components/chat/ModelPicker.tsx +25 -16
- package/src/components/chat/OrchestrationChildrenCard.tsx +2 -1
- package/src/components/chat/OrchestrationFlowCard.tsx +23 -22
- package/src/components/chat/PlanTodoList.tsx +2 -1
- package/src/components/chat/ProjectTrustBanner.tsx +19 -5
- package/src/components/chat/SessionOrchestrationFlow.tsx +27 -26
- package/src/components/chat/SessionTodoList.tsx +8 -7
- package/src/components/chat/SessionTreeModal.tsx +10 -9
- package/src/components/chat/StreamingStatus.tsx +4 -3
- package/src/components/chat/SuggestedReplies.tsx +3 -2
- package/src/components/chat/TaskOutcomeCards.tsx +8 -5
- package/src/components/chat/TaskPanelModal.tsx +31 -21
- package/src/components/chat/TaskProcessPanel.tsx +14 -13
- package/src/components/chat/TurnFilesCard.tsx +14 -13
- package/src/components/chat/UserMessage.tsx +12 -11
- package/src/components/chat/executed-actions-model.ts +47 -35
- package/src/components/chat/orchestration-flow-model.ts +16 -5
- package/src/components/chat/process-status.ts +8 -7
- package/src/components/chat/todo-list-model.ts +5 -4
- package/src/components/chat/turn-files.ts +8 -8
- package/src/components/settings/AgentCapabilityFields.tsx +24 -21
- package/src/components/settings/AgentsSettingsPanel.tsx +30 -25
- package/src/components/settings/AppUpdateSettingsPanel.tsx +3 -2
- package/src/components/settings/AppearanceSettingsPanel.tsx +81 -37
- package/src/components/settings/DiagnoseSettingsPanel.tsx +11 -8
- package/src/components/settings/InsightsSettingsPanel.tsx +43 -28
- package/src/components/settings/LlmSettingsPanel.tsx +104 -83
- package/src/components/settings/McpSettingsPanel.tsx +193 -71
- package/src/components/settings/ModelIdCombobox.tsx +2 -1
- package/src/components/settings/OrchestrationSettingsPanel.tsx +8 -5
- package/src/components/settings/PortableSettingsPanel.tsx +83 -55
- package/src/components/settings/PythonRunnerSettingsPanel.tsx +22 -19
- package/src/components/settings/SecuritySettingsPanel.tsx +55 -31
- package/src/components/settings/SettingsNavMenu.tsx +3 -2
- package/src/components/settings/SettingsSaveButton.tsx +2 -1
- package/src/components/settings/SkillsSettingsPanel.tsx +36 -31
- package/src/components/settings/TelemetrySettingsPanel.tsx +15 -12
- package/src/components/settings/UsagePanel.tsx +19 -20
- package/src/components/settings/WebSearchSettingsPanel.tsx +23 -14
- package/src/components/settings/llm-vendors.ts +12 -7
- package/src/components/settings/model-capabilities.ts +16 -15
- package/src/components/settings/preset-choice-model.ts +6 -3
- package/src/fixtures/browser-dev-data.json +24 -24
- package/src/hooks/useProjects.ts +1 -1
- package/src/i18n/agent-label.ts +15 -0
- package/src/i18n/index.tsx +68 -0
- package/src/i18n/runtime.ts +117 -0
- package/src/i18n/translate.ts +65 -0
- package/src/layouts/AgentLayout.tsx +7 -6
- package/src/lib/app-release.ts +15 -10
- package/src/lib/attachments.ts +24 -7
- package/src/lib/browser-dev-electron-mock.ts +34 -34
- package/src/lib/chat-title.ts +3 -2
- package/src/lib/chat-transport.ts +6 -2
- package/src/lib/date-groups.ts +23 -15
- package/src/lib/electron-bridge.ts +19 -6
- package/src/lib/host-file-drop.ts +68 -0
- package/src/lib/host-paste.ts +38 -9
- package/src/lib/http-bridge.ts +4 -1
- package/src/lib/portable.ts +8 -7
- package/src/lib/show-thinking-content.ts +15 -3
- package/src/lib/tauri-bridge.ts +5 -0
- package/src/main.tsx +25 -2
- package/src/pages/AgentPage.tsx +17 -12
- package/src/pages/ChatPanelPreviewPage.tsx +46 -44
- package/src/pages/SettingsPage.tsx +48 -42
- package/src/vite-env.d.ts +3 -0
- package/vite.base.js +6 -2
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
LuToggleRight,
|
|
10
10
|
LuTrash2,
|
|
11
11
|
} from 'react-icons/lu';
|
|
12
|
+
import { t } from '@/i18n';
|
|
12
13
|
import { getElectronBridge, isElectron } from '@/lib/electron-bridge';
|
|
13
14
|
|
|
14
15
|
/**
|
|
@@ -26,10 +27,15 @@ import { getElectronBridge, isElectron } from '@/lib/electron-bridge';
|
|
|
26
27
|
export interface McpServerInfo {
|
|
27
28
|
id: string;
|
|
28
29
|
name: string;
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
transport: 'stdio' | 'streamable-http';
|
|
31
|
+
command?: string;
|
|
32
|
+
args?: string[];
|
|
33
|
+
envKeys?: string[];
|
|
32
34
|
cwd?: string;
|
|
35
|
+
url?: string;
|
|
36
|
+
headerNames?: string[];
|
|
37
|
+
headersFromEnv?: Record<string, string>;
|
|
38
|
+
bearerTokenEnvVar?: string;
|
|
33
39
|
enabled: boolean;
|
|
34
40
|
serverKey: string;
|
|
35
41
|
toolCount: number;
|
|
@@ -46,11 +52,16 @@ export function McpSettingsPanel() {
|
|
|
46
52
|
const [mcpImportStatus, setMcpImportStatus] = useState<string | null>(null);
|
|
47
53
|
const [mcpFormOpen, setMcpFormOpen] = useState(false);
|
|
48
54
|
const [mcpEditingId, setMcpEditingId] = useState<string | null>(null);
|
|
55
|
+
const [mcpFormTransport, setMcpFormTransport] = useState<'stdio' | 'streamable-http'>('stdio');
|
|
49
56
|
const [mcpFormName, setMcpFormName] = useState('');
|
|
50
57
|
const [mcpFormCommand, setMcpFormCommand] = useState('');
|
|
51
58
|
const [mcpFormArgs, setMcpFormArgs] = useState('');
|
|
52
59
|
const [mcpFormEnv, setMcpFormEnv] = useState('');
|
|
53
60
|
const [mcpFormCwd, setMcpFormCwd] = useState('');
|
|
61
|
+
const [mcpFormUrl, setMcpFormUrl] = useState('');
|
|
62
|
+
const [mcpFormHeaders, setMcpFormHeaders] = useState('');
|
|
63
|
+
const [mcpFormHeadersFromEnv, setMcpFormHeadersFromEnv] = useState('');
|
|
64
|
+
const [mcpFormBearerEnv, setMcpFormBearerEnv] = useState('');
|
|
54
65
|
const [mcpSaving, setMcpSaving] = useState(false);
|
|
55
66
|
const [mcpTestingId, setMcpTestingId] = useState<string | null>(null);
|
|
56
67
|
const [mcpDeletingId, setMcpDeletingId] = useState<string | null>(null);
|
|
@@ -67,7 +78,7 @@ export function McpSettingsPanel() {
|
|
|
67
78
|
});
|
|
68
79
|
setMcpServers(res.servers || []);
|
|
69
80
|
} catch (err) {
|
|
70
|
-
console.error('
|
|
81
|
+
console.error('Failed to load MCP server list:', err);
|
|
71
82
|
setError(err instanceof Error ? err.message : String(err));
|
|
72
83
|
} finally {
|
|
73
84
|
setMcpLoading(false);
|
|
@@ -80,11 +91,16 @@ export function McpSettingsPanel() {
|
|
|
80
91
|
|
|
81
92
|
const resetMcpForm = () => {
|
|
82
93
|
setMcpEditingId(null);
|
|
94
|
+
setMcpFormTransport('stdio');
|
|
83
95
|
setMcpFormName('');
|
|
84
96
|
setMcpFormCommand('');
|
|
85
97
|
setMcpFormArgs('');
|
|
86
98
|
setMcpFormEnv('');
|
|
87
99
|
setMcpFormCwd('');
|
|
100
|
+
setMcpFormUrl('');
|
|
101
|
+
setMcpFormHeaders('');
|
|
102
|
+
setMcpFormHeadersFromEnv('');
|
|
103
|
+
setMcpFormBearerEnv('');
|
|
88
104
|
};
|
|
89
105
|
|
|
90
106
|
const parseEnvLines = (raw: string): Record<string, string> => {
|
|
@@ -104,13 +120,27 @@ export function McpSettingsPanel() {
|
|
|
104
120
|
setMcpSaving(true);
|
|
105
121
|
setError(null);
|
|
106
122
|
try {
|
|
107
|
-
const body =
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
123
|
+
const body = mcpFormTransport === 'stdio'
|
|
124
|
+
? {
|
|
125
|
+
name: mcpFormName.trim(),
|
|
126
|
+
transport: 'stdio',
|
|
127
|
+
command: mcpFormCommand.trim(),
|
|
128
|
+
args: mcpFormArgs.split('\n').map((s) => s.trim()).filter(Boolean),
|
|
129
|
+
...(!mcpEditingId || mcpFormEnv.trim()
|
|
130
|
+
? { env: parseEnvLines(mcpFormEnv) }
|
|
131
|
+
: {}),
|
|
132
|
+
cwd: mcpFormCwd.trim() || undefined,
|
|
133
|
+
}
|
|
134
|
+
: {
|
|
135
|
+
name: mcpFormName.trim(),
|
|
136
|
+
transport: 'streamable-http',
|
|
137
|
+
url: mcpFormUrl.trim(),
|
|
138
|
+
...(!mcpEditingId || mcpFormHeaders.trim()
|
|
139
|
+
? { headers: parseEnvLines(mcpFormHeaders) }
|
|
140
|
+
: {}),
|
|
141
|
+
headersFromEnv: parseEnvLines(mcpFormHeadersFromEnv),
|
|
142
|
+
bearerTokenEnvVar: mcpFormBearerEnv.trim() || undefined,
|
|
143
|
+
};
|
|
114
144
|
if (mcpEditingId) {
|
|
115
145
|
await getElectronBridge()!.localBackend.request({
|
|
116
146
|
method: 'PUT',
|
|
@@ -136,15 +166,22 @@ export function McpSettingsPanel() {
|
|
|
136
166
|
|
|
137
167
|
const handleEditMcpServer = (server: McpServerInfo) => {
|
|
138
168
|
setMcpEditingId(server.id);
|
|
169
|
+
setMcpFormTransport(server.transport ?? 'stdio');
|
|
139
170
|
setMcpFormName(server.name);
|
|
140
|
-
setMcpFormCommand(server.command);
|
|
171
|
+
setMcpFormCommand(server.command ?? '');
|
|
141
172
|
setMcpFormArgs((server.args || []).join('\n'));
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
173
|
+
// Stored values never return through the settings API. Blank preserves
|
|
174
|
+
// the existing env map; entering lines replaces it deliberately.
|
|
175
|
+
setMcpFormEnv('');
|
|
176
|
+
setMcpFormCwd(server.cwd || '');
|
|
177
|
+
setMcpFormUrl(server.url || '');
|
|
178
|
+
setMcpFormHeaders('');
|
|
179
|
+
setMcpFormHeadersFromEnv(
|
|
180
|
+
Object.entries(server.headersFromEnv ?? {})
|
|
181
|
+
.map(([header, variable]) => `${header}=${variable}`)
|
|
145
182
|
.join('\n'),
|
|
146
183
|
);
|
|
147
|
-
|
|
184
|
+
setMcpFormBearerEnv(server.bearerTokenEnvVar || '');
|
|
148
185
|
setMcpFormOpen(true);
|
|
149
186
|
};
|
|
150
187
|
|
|
@@ -192,7 +229,7 @@ export function McpSettingsPanel() {
|
|
|
192
229
|
path: `/api/v2/mcp/servers/${encodeURIComponent(server.id)}/test`,
|
|
193
230
|
});
|
|
194
231
|
if (!res.success && res.error) {
|
|
195
|
-
setError(
|
|
232
|
+
setError(t('"{name}" failed to connect: {error}', { name: server.name, error: res.error }));
|
|
196
233
|
}
|
|
197
234
|
await fetchMcpServers();
|
|
198
235
|
} catch (err) {
|
|
@@ -217,9 +254,23 @@ export function McpSettingsPanel() {
|
|
|
217
254
|
body: { json },
|
|
218
255
|
});
|
|
219
256
|
const parts: string[] = [];
|
|
220
|
-
if (res.added.length > 0)
|
|
221
|
-
|
|
222
|
-
|
|
257
|
+
if (res.added.length > 0) {
|
|
258
|
+
parts.push(
|
|
259
|
+
t('Imported {count}: {names}', {
|
|
260
|
+
count: res.added.length,
|
|
261
|
+
names: res.added.map((s) => s.name).join(t(', ')),
|
|
262
|
+
}),
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
if (res.skipped.length > 0) {
|
|
266
|
+
parts.push(
|
|
267
|
+
t('Skipped {count} (duplicate name or missing command): {names}', {
|
|
268
|
+
count: res.skipped.length,
|
|
269
|
+
names: res.skipped.join(t(', ')),
|
|
270
|
+
}),
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
setMcpImportStatus(parts.join(t('; ')) || t('No servers to import'));
|
|
223
274
|
if (res.added.length > 0) setMcpImportJson('');
|
|
224
275
|
await fetchMcpServers();
|
|
225
276
|
} catch (err) {
|
|
@@ -235,7 +286,7 @@ export function McpSettingsPanel() {
|
|
|
235
286
|
<div className="bg-agent-muted/30 border border-agent-border/60 rounded-agent-md p-2.5 space-y-2">
|
|
236
287
|
<h4 className="text-xs font-semibold text-agent-foreground flex items-center gap-1.5">
|
|
237
288
|
<LuPlug className="h-3.5 w-3.5 text-agent-muted-foreground" />
|
|
238
|
-
|
|
289
|
+
{t('Paste JSON to import (Claude Desktop format)')}
|
|
239
290
|
</h4>
|
|
240
291
|
<textarea
|
|
241
292
|
value={mcpImportJson}
|
|
@@ -247,7 +298,7 @@ export function McpSettingsPanel() {
|
|
|
247
298
|
/>
|
|
248
299
|
<div className="flex items-center justify-between gap-2">
|
|
249
300
|
<p className="text-[10px] text-agent-muted-foreground">
|
|
250
|
-
|
|
301
|
+
{t('Same mcpServers format as Claude Desktop / Cursor. You can paste the whole block.')}
|
|
251
302
|
</p>
|
|
252
303
|
<button
|
|
253
304
|
type="button"
|
|
@@ -259,7 +310,7 @@ export function McpSettingsPanel() {
|
|
|
259
310
|
: 'bg-agent-foreground text-agent-canvas hover:opacity-90'
|
|
260
311
|
}`}
|
|
261
312
|
>
|
|
262
|
-
{mcpImporting ? <LuLoaderCircle className="h-3 w-3 animate-spin" /> : '
|
|
313
|
+
{mcpImporting ? <LuLoaderCircle className="h-3 w-3 animate-spin" /> : t('Import')}
|
|
263
314
|
</button>
|
|
264
315
|
</div>
|
|
265
316
|
{mcpImportStatus && (
|
|
@@ -273,64 +324,130 @@ export function McpSettingsPanel() {
|
|
|
273
324
|
{mcpFormOpen ? (
|
|
274
325
|
<div className="bg-agent-muted/30 border border-agent-border/60 rounded-agent-md p-2.5 space-y-2.5">
|
|
275
326
|
<h4 className="text-xs font-semibold text-agent-foreground">
|
|
276
|
-
{mcpEditingId ? '
|
|
327
|
+
{mcpEditingId ? t('Edit MCP server') : t('Add MCP server manually')}
|
|
277
328
|
</h4>
|
|
329
|
+
<select
|
|
330
|
+
value={mcpFormTransport}
|
|
331
|
+
onChange={(event) =>
|
|
332
|
+
setMcpFormTransport(event.target.value as 'stdio' | 'streamable-http')
|
|
333
|
+
}
|
|
334
|
+
className="h-8 w-full rounded-agent-md border border-agent-border bg-agent-canvas px-3 text-xs text-agent-foreground focus:outline-none focus:ring-2 focus:ring-agent-foreground/30"
|
|
335
|
+
>
|
|
336
|
+
<option value="stdio">{t('Local process (stdio)')}</option>
|
|
337
|
+
<option value="streamable-http">{t('Remote server (Streamable HTTP)')}</option>
|
|
338
|
+
</select>
|
|
278
339
|
<div className="grid grid-cols-2 gap-2">
|
|
279
340
|
<input
|
|
280
341
|
type="text"
|
|
281
342
|
value={mcpFormName}
|
|
282
343
|
onChange={(e) => setMcpFormName(e.target.value)}
|
|
283
|
-
placeholder=
|
|
284
|
-
className="h-8 rounded-agent-md border border-agent-border bg-agent-canvas px-3 text-xs text-agent-foreground focus:outline-none focus:ring-2 focus:ring-agent-foreground/30"
|
|
285
|
-
/>
|
|
286
|
-
<input
|
|
287
|
-
type="text"
|
|
288
|
-
value={mcpFormCommand}
|
|
289
|
-
onChange={(e) => setMcpFormCommand(e.target.value)}
|
|
290
|
-
placeholder="启动命令,如 npx / uvx / node"
|
|
344
|
+
placeholder={t('Name, e.g. filesystem')}
|
|
291
345
|
className="h-8 rounded-agent-md border border-agent-border bg-agent-canvas px-3 text-xs text-agent-foreground focus:outline-none focus:ring-2 focus:ring-agent-foreground/30"
|
|
292
346
|
/>
|
|
347
|
+
{mcpFormTransport === 'stdio' ? (
|
|
348
|
+
<input
|
|
349
|
+
type="text"
|
|
350
|
+
value={mcpFormCommand}
|
|
351
|
+
onChange={(e) => setMcpFormCommand(e.target.value)}
|
|
352
|
+
placeholder={t('Launch command, e.g. npx / uvx / node')}
|
|
353
|
+
className="h-8 rounded-agent-md border border-agent-border bg-agent-canvas px-3 text-xs text-agent-foreground focus:outline-none focus:ring-2 focus:ring-agent-foreground/30"
|
|
354
|
+
/>
|
|
355
|
+
) : (
|
|
356
|
+
<input
|
|
357
|
+
type="url"
|
|
358
|
+
value={mcpFormUrl}
|
|
359
|
+
onChange={(e) => setMcpFormUrl(e.target.value)}
|
|
360
|
+
placeholder="https://example.com/mcp"
|
|
361
|
+
className="h-8 rounded-agent-md border border-agent-border bg-agent-canvas px-3 text-xs text-agent-foreground focus:outline-none focus:ring-2 focus:ring-agent-foreground/30"
|
|
362
|
+
/>
|
|
363
|
+
)}
|
|
293
364
|
</div>
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
365
|
+
{mcpFormTransport === 'stdio' ? (
|
|
366
|
+
<>
|
|
367
|
+
<textarea
|
|
368
|
+
value={mcpFormArgs}
|
|
369
|
+
onChange={(e) => setMcpFormArgs(e.target.value)}
|
|
370
|
+
placeholder={t('Arguments (one per line)')}
|
|
371
|
+
rows={3}
|
|
372
|
+
className="w-full rounded-agent-md border border-agent-border bg-agent-canvas px-3 py-2 font-mono text-[11px] text-agent-foreground focus:outline-none focus:ring-2 focus:ring-agent-foreground/30"
|
|
373
|
+
/>
|
|
374
|
+
<textarea
|
|
375
|
+
value={mcpFormEnv}
|
|
376
|
+
onChange={(e) => setMcpFormEnv(e.target.value)}
|
|
377
|
+
placeholder={
|
|
378
|
+
mcpEditingId
|
|
379
|
+
? t('Environment variables; leave blank to keep current values')
|
|
380
|
+
: t('Environment variables (one KEY=VALUE per line)')
|
|
381
|
+
}
|
|
382
|
+
rows={2}
|
|
383
|
+
className="w-full rounded-agent-md border border-agent-border bg-agent-canvas px-3 py-2 font-mono text-[11px] text-agent-foreground focus:outline-none focus:ring-2 focus:ring-agent-foreground/30"
|
|
384
|
+
/>
|
|
385
|
+
<input
|
|
386
|
+
type="text"
|
|
387
|
+
value={mcpFormCwd}
|
|
388
|
+
onChange={(e) => setMcpFormCwd(e.target.value)}
|
|
389
|
+
placeholder={t('Working directory (optional)')}
|
|
390
|
+
className="h-8 w-full rounded-agent-md border border-agent-border bg-agent-canvas px-3 text-xs text-agent-foreground focus:outline-none focus:ring-2 focus:ring-agent-foreground/30"
|
|
391
|
+
/>
|
|
392
|
+
</>
|
|
393
|
+
) : (
|
|
394
|
+
<>
|
|
395
|
+
<textarea
|
|
396
|
+
value={mcpFormHeaders}
|
|
397
|
+
onChange={(e) => setMcpFormHeaders(e.target.value)}
|
|
398
|
+
placeholder={
|
|
399
|
+
mcpEditingId
|
|
400
|
+
? t('Plain headers; leave blank to keep current values')
|
|
401
|
+
: t('Plain headers (one Header=Value per line)')
|
|
402
|
+
}
|
|
403
|
+
rows={2}
|
|
404
|
+
className="w-full rounded-agent-md border border-agent-border bg-agent-canvas px-3 py-2 font-mono text-[11px] text-agent-foreground focus:outline-none focus:ring-2 focus:ring-agent-foreground/30"
|
|
405
|
+
/>
|
|
406
|
+
<textarea
|
|
407
|
+
value={mcpFormHeadersFromEnv}
|
|
408
|
+
onChange={(e) => setMcpFormHeadersFromEnv(e.target.value)}
|
|
409
|
+
placeholder={t('Headers from environment variables (one Header=ENV_NAME per line)')}
|
|
410
|
+
rows={2}
|
|
411
|
+
className="w-full rounded-agent-md border border-agent-border bg-agent-canvas px-3 py-2 font-mono text-[11px] text-agent-foreground focus:outline-none focus:ring-2 focus:ring-agent-foreground/30"
|
|
412
|
+
/>
|
|
413
|
+
<input
|
|
414
|
+
type="text"
|
|
415
|
+
value={mcpFormBearerEnv}
|
|
416
|
+
onChange={(e) => setMcpFormBearerEnv(e.target.value)}
|
|
417
|
+
placeholder={t('Bearer token environment variable name (optional)')}
|
|
418
|
+
className="h-8 w-full rounded-agent-md border border-agent-border bg-agent-canvas px-3 text-xs text-agent-foreground focus:outline-none focus:ring-2 focus:ring-agent-foreground/30"
|
|
419
|
+
/>
|
|
420
|
+
</>
|
|
421
|
+
)}
|
|
315
422
|
<div className="flex justify-end gap-2">
|
|
316
423
|
<button
|
|
317
424
|
type="button"
|
|
318
425
|
onClick={() => { resetMcpForm(); setMcpFormOpen(false); }}
|
|
319
426
|
className="h-8 rounded-full px-4 text-xs font-medium text-agent-muted-foreground transition-colors hover:bg-agent-muted hover:text-agent-foreground"
|
|
320
427
|
>
|
|
321
|
-
|
|
428
|
+
{t('Cancel')}
|
|
322
429
|
</button>
|
|
323
430
|
<button
|
|
324
431
|
type="button"
|
|
325
432
|
onClick={handleSaveMcpServer}
|
|
326
|
-
disabled={
|
|
433
|
+
disabled={
|
|
434
|
+
mcpSaving ||
|
|
435
|
+
!mcpFormName.trim() ||
|
|
436
|
+
(mcpFormTransport === 'stdio'
|
|
437
|
+
? !mcpFormCommand.trim()
|
|
438
|
+
: !mcpFormUrl.trim())
|
|
439
|
+
}
|
|
327
440
|
className={`h-8 px-4 rounded-full text-xs font-medium transition-all ${
|
|
328
|
-
mcpSaving ||
|
|
441
|
+
mcpSaving ||
|
|
442
|
+
!mcpFormName.trim() ||
|
|
443
|
+
(mcpFormTransport === 'stdio'
|
|
444
|
+
? !mcpFormCommand.trim()
|
|
445
|
+
: !mcpFormUrl.trim())
|
|
329
446
|
? 'bg-agent-muted text-agent-muted-foreground cursor-not-allowed'
|
|
330
447
|
: 'bg-agent-foreground text-agent-canvas hover:opacity-90'
|
|
331
448
|
}`}
|
|
332
449
|
>
|
|
333
|
-
{mcpSaving ? <LuLoaderCircle className="h-3 w-3 animate-spin" /> : '
|
|
450
|
+
{mcpSaving ? <LuLoaderCircle className="h-3 w-3 animate-spin" /> : t('Save')}
|
|
334
451
|
</button>
|
|
335
452
|
</div>
|
|
336
453
|
</div>
|
|
@@ -342,23 +459,23 @@ export function McpSettingsPanel() {
|
|
|
342
459
|
data-testid="mcp-add-manual"
|
|
343
460
|
>
|
|
344
461
|
<LuPlus className="h-3.5 w-3.5" />
|
|
345
|
-
|
|
462
|
+
{t('Add server manually')}
|
|
346
463
|
</button>
|
|
347
464
|
)}
|
|
348
465
|
|
|
349
466
|
{/* 服务列表 */}
|
|
350
467
|
<div className="space-y-2">
|
|
351
468
|
<h4 className="text-xs font-semibold text-agent-muted-foreground uppercase tracking-wide">
|
|
352
|
-
|
|
469
|
+
{t('Registered servers')}
|
|
353
470
|
</h4>
|
|
354
471
|
{mcpLoading ? (
|
|
355
472
|
<div className="flex items-center gap-2 py-4 text-xs text-agent-muted-foreground">
|
|
356
473
|
<LuLoaderCircle className="h-3.5 w-3.5 animate-spin" />
|
|
357
|
-
|
|
474
|
+
{t('Loading MCP servers...')}
|
|
358
475
|
</div>
|
|
359
476
|
) : mcpServers.length === 0 ? (
|
|
360
477
|
<div className="text-center py-6 text-xs text-agent-muted-foreground">
|
|
361
|
-
|
|
478
|
+
{t('No MCP servers yet. Paste JSON or add one manually.')}
|
|
362
479
|
</div>
|
|
363
480
|
) : (
|
|
364
481
|
<div className="divide-y divide-agent-border/40 pr-1">
|
|
@@ -377,25 +494,28 @@ export function McpSettingsPanel() {
|
|
|
377
494
|
: 'bg-agent-muted text-agent-muted-foreground'
|
|
378
495
|
}`}
|
|
379
496
|
>
|
|
380
|
-
{server.enabled ? '
|
|
497
|
+
{server.enabled ? t('Enabled') : t('Disabled')}
|
|
381
498
|
</span>
|
|
382
499
|
{server.enabled && server.toolCount > 0 && (
|
|
383
500
|
<span className="px-1 py-0.2 rounded bg-agent-muted text-[9px] text-agent-muted-foreground font-medium flex-shrink-0 scale-95 origin-left">
|
|
384
|
-
{server.toolCount}
|
|
501
|
+
{t('{count} tools', { count: server.toolCount })}
|
|
385
502
|
</span>
|
|
386
503
|
)}
|
|
387
504
|
</div>
|
|
388
505
|
<p className="text-[11px] text-agent-muted-foreground font-mono truncate">
|
|
389
|
-
{server.
|
|
506
|
+
{server.transport === 'streamable-http'
|
|
507
|
+
? server.url
|
|
508
|
+
: `${server.command ?? ''} ${(server.args ?? []).join(' ')}`}
|
|
390
509
|
</p>
|
|
391
510
|
{server.lastError && (
|
|
392
511
|
<p className="text-[10px] text-agent-destructive line-clamp-2">
|
|
393
|
-
|
|
512
|
+
{t('Connection failed: {error}', { error: server.lastError })}
|
|
394
513
|
</p>
|
|
395
514
|
)}
|
|
396
515
|
{!server.lastError && server.toolsPreview.length > 0 && (
|
|
397
516
|
<p className="text-[10px] text-agent-muted-foreground/80 line-clamp-1">
|
|
398
|
-
|
|
517
|
+
{t('Tools: {names}', { names: server.toolsPreview.join(t(', ')) })}
|
|
518
|
+
{server.toolCount > server.toolsPreview.length ? ' …' : ''}
|
|
399
519
|
</p>
|
|
400
520
|
)}
|
|
401
521
|
</div>
|
|
@@ -405,7 +525,7 @@ export function McpSettingsPanel() {
|
|
|
405
525
|
onClick={() => handleTestMcpServer(server)}
|
|
406
526
|
disabled={mcpTestingId === server.id}
|
|
407
527
|
className="text-agent-muted-foreground hover:text-agent-foreground p-1 rounded-full hover:bg-agent-muted transition-colors"
|
|
408
|
-
title=
|
|
528
|
+
title={t('Test connection and refresh tool list')}
|
|
409
529
|
>
|
|
410
530
|
{mcpTestingId === server.id ? (
|
|
411
531
|
<LuLoaderCircle className="h-3.5 w-3.5 animate-spin" />
|
|
@@ -417,7 +537,7 @@ export function McpSettingsPanel() {
|
|
|
417
537
|
type="button"
|
|
418
538
|
onClick={() => handleEditMcpServer(server)}
|
|
419
539
|
className="text-agent-muted-foreground hover:text-agent-foreground p-1 rounded-full hover:bg-agent-muted transition-colors"
|
|
420
|
-
title=
|
|
540
|
+
title={t('Edit')}
|
|
421
541
|
>
|
|
422
542
|
<LuPencil className="h-3.5 w-3.5" />
|
|
423
543
|
</button>
|
|
@@ -425,7 +545,7 @@ export function McpSettingsPanel() {
|
|
|
425
545
|
type="button"
|
|
426
546
|
onClick={() => handleToggleMcpServer(server)}
|
|
427
547
|
className="text-agent-muted-foreground hover:text-agent-foreground p-1 rounded-full hover:bg-agent-muted transition-colors"
|
|
428
|
-
title={server.enabled ? '
|
|
548
|
+
title={server.enabled ? t('Disable') : t('Enable')}
|
|
429
549
|
>
|
|
430
550
|
{server.enabled ? (
|
|
431
551
|
<LuToggleRight className="h-3.5 w-3.5" />
|
|
@@ -438,7 +558,7 @@ export function McpSettingsPanel() {
|
|
|
438
558
|
onClick={() => handleDeleteMcpServer(server)}
|
|
439
559
|
disabled={mcpDeletingId === server.id}
|
|
440
560
|
className="text-agent-muted-foreground hover:text-agent-destructive p-1 rounded-full hover:bg-agent-muted transition-colors"
|
|
441
|
-
title=
|
|
561
|
+
title={t('Delete')}
|
|
442
562
|
>
|
|
443
563
|
{mcpDeletingId === server.id ? (
|
|
444
564
|
<LuLoaderCircle className="h-3 w-3 animate-spin" />
|
|
@@ -453,7 +573,9 @@ export function McpSettingsPanel() {
|
|
|
453
573
|
)}
|
|
454
574
|
</div>
|
|
455
575
|
<p className="text-[10px] text-agent-muted-foreground">
|
|
456
|
-
|
|
576
|
+
{t(
|
|
577
|
+
'Supports stdio and Streamable HTTP. Enter secrets as environment variable names, not in plain headers. Tools from enabled servers reach the assistant through tool_search.',
|
|
578
|
+
)}
|
|
457
579
|
</p>
|
|
458
580
|
|
|
459
581
|
{error && (
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
2
2
|
import { createPortal } from 'react-dom';
|
|
3
3
|
import { LuChevronDown } from 'react-icons/lu';
|
|
4
|
+
import { t } from '@/i18n';
|
|
4
5
|
import { ModelCapabilityChips } from '@/components/settings/ModelCapabilityChips';
|
|
5
6
|
import type { ModelPickerRow } from '@/components/settings/llm-vendors';
|
|
6
7
|
|
|
@@ -164,7 +165,7 @@ export function ModelIdCombobox({
|
|
|
164
165
|
<button
|
|
165
166
|
type="button"
|
|
166
167
|
tabIndex={-1}
|
|
167
|
-
aria-label=
|
|
168
|
+
aria-label={t('Open model list')}
|
|
168
169
|
onClick={() => {
|
|
169
170
|
if (open) setOpen(false);
|
|
170
171
|
else show();
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { LuGitFork } from 'react-icons/lu';
|
|
2
2
|
import { useOrchestrationSetting } from '@/lib/orchestration-settings';
|
|
3
|
+
import { t } from '@/i18n';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* OrchestrationSettingsPanel — 多智能体协同编排设置面板。
|
|
@@ -17,10 +18,12 @@ export function OrchestrationSettingsPanel() {
|
|
|
17
18
|
<div className="space-y-1">
|
|
18
19
|
<div className="flex items-center gap-1.5 text-xs font-semibold text-agent-foreground">
|
|
19
20
|
<LuGitFork className="h-3.5 w-3.5 text-violet-600 dark:text-violet-400" />
|
|
20
|
-
<span
|
|
21
|
+
<span>{t('Multi-agent orchestration (Fork-Join)')}</span>
|
|
21
22
|
</div>
|
|
22
23
|
<p className="text-xs leading-relaxed text-agent-muted-foreground">
|
|
23
|
-
|
|
24
|
+
{t(
|
|
25
|
+
'Let the main agent spawn subtasks (agent_spawn) to reason in parallel, and show a flow chart and live progress board above the input box.',
|
|
26
|
+
)}
|
|
24
27
|
</p>
|
|
25
28
|
</div>
|
|
26
29
|
|
|
@@ -28,7 +31,7 @@ export function OrchestrationSettingsPanel() {
|
|
|
28
31
|
<div
|
|
29
32
|
className="flex shrink-0 rounded-full border border-agent-border bg-agent-canvas p-0.5"
|
|
30
33
|
role="radiogroup"
|
|
31
|
-
aria-label=
|
|
34
|
+
aria-label={t('Multi-agent orchestration toggle')}
|
|
32
35
|
>
|
|
33
36
|
<button
|
|
34
37
|
type="button"
|
|
@@ -42,7 +45,7 @@ export function OrchestrationSettingsPanel() {
|
|
|
42
45
|
: 'text-agent-muted-foreground hover:text-agent-foreground',
|
|
43
46
|
].join(' ')}
|
|
44
47
|
>
|
|
45
|
-
|
|
48
|
+
{t('On')}
|
|
46
49
|
</button>
|
|
47
50
|
<button
|
|
48
51
|
type="button"
|
|
@@ -56,7 +59,7 @@ export function OrchestrationSettingsPanel() {
|
|
|
56
59
|
: 'text-agent-muted-foreground hover:text-agent-foreground',
|
|
57
60
|
].join(' ')}
|
|
58
61
|
>
|
|
59
|
-
|
|
62
|
+
{t('Off')}
|
|
60
63
|
</button>
|
|
61
64
|
</div>
|
|
62
65
|
</div>
|