@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.
Files changed (93) hide show
  1. package/package.json +4 -4
  2. package/src/brand.ts +1 -1
  3. package/src/components/AgentSidebar.tsx +60 -52
  4. package/src/components/ChatHeader.tsx +41 -25
  5. package/src/components/ConfirmDialog.tsx +4 -3
  6. package/src/components/CreateProjectModal.tsx +29 -19
  7. package/src/components/LocalLlmSettingsModal.tsx +4 -3
  8. package/src/components/SidebarRelease.tsx +2 -1
  9. package/src/components/TerminalPanel.tsx +3 -2
  10. package/src/components/TerminalView.tsx +12 -7
  11. package/src/components/chat/ApprovalModal.tsx +39 -25
  12. package/src/components/chat/ApprovalPromptProvider.tsx +1 -1
  13. package/src/components/chat/AskUserPromptProvider.tsx +1 -1
  14. package/src/components/chat/AskUserQuestionMenu.tsx +19 -14
  15. package/src/components/chat/AssistantMessage.tsx +18 -13
  16. package/src/components/chat/ChatInput.tsx +138 -42
  17. package/src/components/chat/ChatProjectBadge.tsx +24 -19
  18. package/src/components/chat/EmptyChatHero.tsx +2 -1
  19. package/src/components/chat/ExecPolicyPicker.tsx +12 -11
  20. package/src/components/chat/ExecutedActionsCard.tsx +45 -33
  21. package/src/components/chat/FilePathCode.tsx +8 -3
  22. package/src/components/chat/InterruptedTurnCard.tsx +4 -3
  23. package/src/components/chat/LocalChatPanel.tsx +2 -1
  24. package/src/components/chat/Markdown.tsx +2 -1
  25. package/src/components/chat/MessageList.tsx +3 -2
  26. package/src/components/chat/ModelPicker.tsx +25 -16
  27. package/src/components/chat/OrchestrationChildrenCard.tsx +2 -1
  28. package/src/components/chat/OrchestrationFlowCard.tsx +23 -22
  29. package/src/components/chat/PlanTodoList.tsx +2 -1
  30. package/src/components/chat/ProjectTrustBanner.tsx +19 -5
  31. package/src/components/chat/SessionOrchestrationFlow.tsx +27 -26
  32. package/src/components/chat/SessionTodoList.tsx +8 -7
  33. package/src/components/chat/SessionTreeModal.tsx +10 -9
  34. package/src/components/chat/StreamingStatus.tsx +4 -3
  35. package/src/components/chat/SuggestedReplies.tsx +3 -2
  36. package/src/components/chat/TaskOutcomeCards.tsx +8 -5
  37. package/src/components/chat/TaskPanelModal.tsx +31 -21
  38. package/src/components/chat/TaskProcessPanel.tsx +14 -13
  39. package/src/components/chat/TurnFilesCard.tsx +14 -13
  40. package/src/components/chat/UserMessage.tsx +12 -11
  41. package/src/components/chat/executed-actions-model.ts +47 -35
  42. package/src/components/chat/orchestration-flow-model.ts +16 -5
  43. package/src/components/chat/process-status.ts +8 -7
  44. package/src/components/chat/todo-list-model.ts +5 -4
  45. package/src/components/chat/turn-files.ts +8 -8
  46. package/src/components/settings/AgentCapabilityFields.tsx +24 -21
  47. package/src/components/settings/AgentsSettingsPanel.tsx +30 -25
  48. package/src/components/settings/AppUpdateSettingsPanel.tsx +3 -2
  49. package/src/components/settings/AppearanceSettingsPanel.tsx +81 -37
  50. package/src/components/settings/DiagnoseSettingsPanel.tsx +11 -8
  51. package/src/components/settings/InsightsSettingsPanel.tsx +43 -28
  52. package/src/components/settings/LlmSettingsPanel.tsx +104 -83
  53. package/src/components/settings/McpSettingsPanel.tsx +193 -71
  54. package/src/components/settings/ModelIdCombobox.tsx +2 -1
  55. package/src/components/settings/OrchestrationSettingsPanel.tsx +8 -5
  56. package/src/components/settings/PortableSettingsPanel.tsx +83 -55
  57. package/src/components/settings/PythonRunnerSettingsPanel.tsx +22 -19
  58. package/src/components/settings/SecuritySettingsPanel.tsx +55 -31
  59. package/src/components/settings/SettingsNavMenu.tsx +3 -2
  60. package/src/components/settings/SettingsSaveButton.tsx +2 -1
  61. package/src/components/settings/SkillsSettingsPanel.tsx +36 -31
  62. package/src/components/settings/TelemetrySettingsPanel.tsx +15 -12
  63. package/src/components/settings/UsagePanel.tsx +19 -20
  64. package/src/components/settings/WebSearchSettingsPanel.tsx +23 -14
  65. package/src/components/settings/llm-vendors.ts +12 -7
  66. package/src/components/settings/model-capabilities.ts +16 -15
  67. package/src/components/settings/preset-choice-model.ts +6 -3
  68. package/src/fixtures/browser-dev-data.json +24 -24
  69. package/src/hooks/useProjects.ts +1 -1
  70. package/src/i18n/agent-label.ts +15 -0
  71. package/src/i18n/index.tsx +68 -0
  72. package/src/i18n/runtime.ts +117 -0
  73. package/src/i18n/translate.ts +65 -0
  74. package/src/layouts/AgentLayout.tsx +7 -6
  75. package/src/lib/app-release.ts +15 -10
  76. package/src/lib/attachments.ts +24 -7
  77. package/src/lib/browser-dev-electron-mock.ts +34 -34
  78. package/src/lib/chat-title.ts +3 -2
  79. package/src/lib/chat-transport.ts +6 -2
  80. package/src/lib/date-groups.ts +23 -15
  81. package/src/lib/electron-bridge.ts +19 -6
  82. package/src/lib/host-file-drop.ts +68 -0
  83. package/src/lib/host-paste.ts +38 -9
  84. package/src/lib/http-bridge.ts +4 -1
  85. package/src/lib/portable.ts +8 -7
  86. package/src/lib/show-thinking-content.ts +15 -3
  87. package/src/lib/tauri-bridge.ts +5 -0
  88. package/src/main.tsx +25 -2
  89. package/src/pages/AgentPage.tsx +17 -12
  90. package/src/pages/ChatPanelPreviewPage.tsx +46 -44
  91. package/src/pages/SettingsPage.tsx +48 -42
  92. package/src/vite-env.d.ts +3 -0
  93. 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
- command: string;
30
- args: string[];
31
- env: Record<string, string>;
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('获取 MCP 服务列表失败:', err);
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
- name: mcpFormName.trim(),
109
- command: mcpFormCommand.trim(),
110
- args: mcpFormArgs.split('\n').map((s) => s.trim()).filter(Boolean),
111
- env: parseEnvLines(mcpFormEnv),
112
- cwd: mcpFormCwd.trim() || undefined,
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
- setMcpFormEnv(
143
- Object.entries(server.env || {})
144
- .map(([k, v]) => `${k}=${v}`)
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
- setMcpFormCwd(server.cwd || '');
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(`「${server.name}」连接失败:${res.error}`);
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) parts.push(`已导入 ${res.added.length} 个:${res.added.map((s) => s.name).join('、')}`);
221
- if (res.skipped.length > 0) parts.push(`跳过 ${res.skipped.length} 个(重名或缺 command):${res.skipped.join('、')}`);
222
- setMcpImportStatus(parts.join(';') || '没有可导入的服务');
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
- 粘贴 JSON 导入 (Claude Desktop 格式)
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
- 与 Claude Desktop / Cursor 的 mcpServers 配置格式一致,可整段粘贴。
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 ? '编辑 MCP 服务' : '手动添加 MCP 服务'}
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="名称,如 filesystem"
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
- <textarea
295
- value={mcpFormArgs}
296
- onChange={(e) => setMcpFormArgs(e.target.value)}
297
- placeholder="参数(每行一个),如:&#10;-y&#10;@modelcontextprotocol/server-filesystem&#10;C:/"
298
- rows={3}
299
- 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"
300
- />
301
- <textarea
302
- value={mcpFormEnv}
303
- onChange={(e) => setMcpFormEnv(e.target.value)}
304
- placeholder="环境变量(每行一个 KEY=VALUE,可留空)"
305
- rows={2}
306
- 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"
307
- />
308
- <input
309
- type="text"
310
- value={mcpFormCwd}
311
- onChange={(e) => setMcpFormCwd(e.target.value)}
312
- placeholder="工作目录(可选)"
313
- 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"
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={mcpSaving || !mcpFormName.trim() || !mcpFormCommand.trim()}
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 || !mcpFormName.trim() || !mcpFormCommand.trim()
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
- 获取 MCP 服务...
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
- 暂无 MCP 服务,粘贴 JSON 或手动添加
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.command} {(server.args || []).join(' ')}
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
- 连接失败:{server.lastError}
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
- 工具:{server.toolsPreview.join('、')}{server.toolCount > server.toolsPreview.length ? ' …' : ''}
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
- 已启用服务的工具会以 mcp__服务名__工具名 的形式直接提供给助手调用;新导入或修改后点「测试连接」即可生效。
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>多智能体协同编排 (Fork-Join)</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
- 允许主智能体通过派生子任务(agent_spawn)并行推理计算,并在输入框上方呈现流程图与实时协同进度看板。
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>