@steerable/agent-shell-web 0.6.53 → 0.6.56

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 (100) hide show
  1. package/README.md +3 -4
  2. package/package.json +4 -4
  3. package/src/AppShell.tsx +3 -3
  4. package/src/brand.ts +1 -1
  5. package/src/components/AgentSidebar.tsx +64 -56
  6. package/src/components/ChatHeader.tsx +41 -25
  7. package/src/components/ConfirmDialog.tsx +4 -3
  8. package/src/components/CreateProjectModal.tsx +31 -21
  9. package/src/components/LocalLlmSettingsModal.tsx +4 -3
  10. package/src/components/SidebarRelease.tsx +6 -5
  11. package/src/components/TerminalPanel.tsx +6 -5
  12. package/src/components/TerminalView.tsx +15 -10
  13. package/src/components/chat/ApprovalModal.tsx +40 -26
  14. package/src/components/chat/ApprovalPromptProvider.tsx +5 -5
  15. package/src/components/chat/AskUserPromptProvider.tsx +4 -4
  16. package/src/components/chat/AskUserQuestionMenu.tsx +19 -14
  17. package/src/components/chat/AssistantMessage.tsx +18 -13
  18. package/src/components/chat/ChatInput.tsx +56 -40
  19. package/src/components/chat/ChatProjectBadge.tsx +27 -22
  20. package/src/components/chat/EmptyChatHero.tsx +2 -1
  21. package/src/components/chat/ExecPolicyPicker.tsx +12 -11
  22. package/src/components/chat/ExecutedActionsCard.tsx +45 -33
  23. package/src/components/chat/FilePathCode.tsx +8 -3
  24. package/src/components/chat/InterruptedTurnCard.tsx +4 -3
  25. package/src/components/chat/LocalChatPanel.tsx +2 -1
  26. package/src/components/chat/Markdown.tsx +2 -1
  27. package/src/components/chat/MessageList.tsx +3 -2
  28. package/src/components/chat/ModelPicker.tsx +25 -16
  29. package/src/components/chat/OrchestrationChildrenCard.tsx +2 -1
  30. package/src/components/chat/OrchestrationFlowCard.tsx +23 -22
  31. package/src/components/chat/PlanTodoList.tsx +2 -1
  32. package/src/components/chat/ProjectTrustBanner.tsx +19 -5
  33. package/src/components/chat/SessionOrchestrationFlow.tsx +27 -26
  34. package/src/components/chat/SessionTodoList.tsx +8 -7
  35. package/src/components/chat/SessionTreeModal.tsx +10 -9
  36. package/src/components/chat/StreamingStatus.tsx +4 -3
  37. package/src/components/chat/SuggestedReplies.tsx +3 -2
  38. package/src/components/chat/TaskOutcomeCards.tsx +8 -5
  39. package/src/components/chat/TaskPanelModal.tsx +33 -23
  40. package/src/components/chat/TaskProcessPanel.tsx +16 -15
  41. package/src/components/chat/TurnFilesCard.tsx +14 -13
  42. package/src/components/chat/UserMessage.tsx +12 -11
  43. package/src/components/chat/executed-actions-model.ts +47 -35
  44. package/src/components/chat/orchestration-flow-model.ts +16 -5
  45. package/src/components/chat/process-status.ts +8 -7
  46. package/src/components/chat/todo-list-model.ts +5 -4
  47. package/src/components/chat/turn-files.ts +8 -8
  48. package/src/components/chat/useChatTasks.ts +3 -3
  49. package/src/components/settings/AgentCapabilityFields.tsx +26 -23
  50. package/src/components/settings/AgentsSettingsPanel.tsx +33 -28
  51. package/src/components/settings/AppUpdateSettingsPanel.tsx +3 -2
  52. package/src/components/settings/AppearanceSettingsPanel.tsx +81 -37
  53. package/src/components/settings/DiagnoseSettingsPanel.tsx +11 -8
  54. package/src/components/settings/InsightsSettingsPanel.tsx +58 -43
  55. package/src/components/settings/LlmSettingsPanel.tsx +107 -86
  56. package/src/components/settings/McpSettingsPanel.tsx +72 -46
  57. package/src/components/settings/ModelIdCombobox.tsx +2 -1
  58. package/src/components/settings/OrchestrationSettingsPanel.tsx +8 -5
  59. package/src/components/settings/PortableSettingsPanel.tsx +83 -55
  60. package/src/components/settings/PythonRunnerSettingsPanel.tsx +26 -23
  61. package/src/components/settings/SecuritySettingsPanel.tsx +58 -34
  62. package/src/components/settings/SettingsNavMenu.tsx +3 -2
  63. package/src/components/settings/SettingsSaveButton.tsx +2 -1
  64. package/src/components/settings/SkillsSettingsPanel.tsx +43 -38
  65. package/src/components/settings/TelemetrySettingsPanel.tsx +20 -17
  66. package/src/components/settings/UsagePanel.tsx +22 -23
  67. package/src/components/settings/WebSearchSettingsPanel.tsx +28 -19
  68. package/src/components/settings/llm-vendors.ts +12 -7
  69. package/src/components/settings/model-capabilities.ts +16 -15
  70. package/src/components/settings/preset-choice-model.ts +6 -3
  71. package/src/fixtures/browser-dev-data.json +24 -24
  72. package/src/hooks/useChatsAndAgents.ts +9 -9
  73. package/src/hooks/useProjects.ts +3 -3
  74. package/src/i18n/agent-label.ts +15 -0
  75. package/src/i18n/index.tsx +68 -0
  76. package/src/i18n/runtime.ts +117 -0
  77. package/src/i18n/translate.ts +65 -0
  78. package/src/layouts/AgentLayout.tsx +13 -12
  79. package/src/lib/app-release.ts +16 -11
  80. package/src/lib/attachments.ts +18 -9
  81. package/src/lib/{browser-dev-electron-mock.ts → browser-dev-host-mock.ts} +42 -42
  82. package/src/lib/chat-title.ts +3 -2
  83. package/src/lib/chat-transport.ts +16 -12
  84. package/src/lib/date-groups.ts +23 -15
  85. package/src/lib/host-bridge.ts +371 -4
  86. package/src/lib/host-paste.ts +1 -1
  87. package/src/lib/http-bridge.ts +12 -9
  88. package/src/lib/insights.ts +2 -2
  89. package/src/lib/local-api.ts +5 -5
  90. package/src/lib/portable.ts +11 -10
  91. package/src/lib/show-thinking-content.ts +15 -3
  92. package/src/lib/slash-sources.ts +3 -3
  93. package/src/lib/tauri-bridge.ts +1 -1
  94. package/src/main.tsx +27 -4
  95. package/src/pages/AgentPage.tsx +39 -34
  96. package/src/pages/ChatPanelPreviewPage.tsx +46 -44
  97. package/src/pages/SettingsPage.tsx +51 -45
  98. package/src/vite-env.d.ts +3 -0
  99. package/vite.base.js +6 -2
  100. package/src/lib/electron-bridge.ts +0 -390
@@ -32,7 +32,8 @@ import { ModelCapabilityChips } from '@/components/settings/ModelCapabilityChips
32
32
  import { modelCapabilityChips } from '@/components/settings/model-capabilities';
33
33
  import { ModelIdCombobox } from '@/components/settings/ModelIdCombobox';
34
34
  import { SettingsSaveButton } from '@/components/settings/SettingsSaveButton';
35
- import { isElectron } from '@/lib/electron-bridge';
35
+ import { t } from '@/i18n';
36
+ import { hasHostBridge } from '@/lib/host-bridge';
36
37
  import {
37
38
  COMPAT_AUTO,
38
39
  formStateFromOverrides,
@@ -123,8 +124,8 @@ export const LlmSettingsPanel = forwardRef<LlmSettingsPanelHandle, LlmSettingsPa
123
124
  const [resolvedPreset, setResolvedPreset] = useState<ProviderPresetOverride | null>(null);
124
125
 
125
126
  const reload = useCallback(async () => {
126
- if (!isElectron()) {
127
- setError('需要在桌面客户端中打开');
127
+ if (!hasHostBridge()) {
128
+ setError(t('Open this in the desktop app'));
128
129
  return;
129
130
  }
130
131
  setLoading(true);
@@ -136,7 +137,7 @@ export const LlmSettingsPanel = forwardRef<LlmSettingsPanelHandle, LlmSettingsPa
136
137
  const { providers } = await getCatalogProviders();
137
138
  if (providers.length > 0) nextVendors = mergeVendorOptions(providers);
138
139
  } catch (err) {
139
- console.warn('读取服务商目录失败:', err);
140
+ console.warn('Failed to read provider catalog:', err);
140
141
  }
141
142
  setVendors(nextVendors);
142
143
  const vendorId = inferVendorId(data, nextVendors);
@@ -154,7 +155,7 @@ export const LlmSettingsPanel = forwardRef<LlmSettingsPanelHandle, LlmSettingsPa
154
155
  setCompatFlags(flags);
155
156
  setCompatForm(formStateFromOverrides(data.compat, flags));
156
157
  } catch (err) {
157
- console.warn('读取 compat 旗标词汇表失败:', err);
158
+ console.warn('Failed to read compat flag vocabulary:', err);
158
159
  setCompatFlags([]);
159
160
  setCompatForm({});
160
161
  }
@@ -174,7 +175,7 @@ export const LlmSettingsPanel = forwardRef<LlmSettingsPanelHandle, LlmSettingsPa
174
175
  setPresetPinnedIdx(-1);
175
176
  }
176
177
  } catch (err) {
177
- console.warn('读取厂商预制注册表失败:', err);
178
+ console.warn('Failed to read vendor preset registry:', err);
178
179
  setPresetList([]);
179
180
  setPresetMode(presetModeFromChoice(data.presets));
180
181
  }
@@ -327,7 +328,7 @@ export const LlmSettingsPanel = forwardRef<LlmSettingsPanelHandle, LlmSettingsPa
327
328
  if (catalog.catalogStatus === 'offline') {
328
329
  setKeyTest({
329
330
  status: 'fail',
330
- detail: catalog.error || '网关目录不可用',
331
+ detail: catalog.error || t('Gateway catalog unavailable'),
331
332
  });
332
333
  return;
333
334
  }
@@ -343,8 +344,8 @@ export const LlmSettingsPanel = forwardRef<LlmSettingsPanelHandle, LlmSettingsPa
343
344
  };
344
345
 
345
346
  const handleSave = async () => {
346
- if (!isElectron()) {
347
- setError('需要在桌面客户端中打开');
347
+ if (!hasHostBridge()) {
348
+ setError(t('Open this in the desktop app'));
348
349
  return;
349
350
  }
350
351
  setSaving(true);
@@ -401,13 +402,13 @@ export const LlmSettingsPanel = forwardRef<LlmSettingsPanelHandle, LlmSettingsPa
401
402
  {loading ? (
402
403
  <div className="flex items-center gap-2 py-2 text-xs text-agent-muted-foreground">
403
404
  <LuLoaderCircle className="h-4 w-4 animate-spin" />
404
- 加载当前配置...
405
+ {t('Loading current settings...')}
405
406
  </div>
406
407
  ) : (
407
408
  <>
408
409
  <div>
409
410
  <label className="mb-1.5 block text-xs font-medium text-agent-muted-foreground">
410
- 服务商
411
+ {t('Provider')}
411
412
  </label>
412
413
  <select
413
414
  data-testid="llm-vendor-select"
@@ -416,7 +417,7 @@ export const LlmSettingsPanel = forwardRef<LlmSettingsPanelHandle, LlmSettingsPa
416
417
  className="h-8 w-full rounded-agent-md border border-agent-border bg-agent-canvas px-2 text-xs text-agent-foreground focus:outline-none focus:ring-2 focus:ring-agent-foreground/30"
417
418
  >
418
419
  {featuredVendors.length > 0 && (
419
- <optgroup label="常用">
420
+ <optgroup label={t('Common')}>
420
421
  {featuredVendors.map((v) => (
421
422
  <option key={v.id} value={v.id}>
422
423
  {v.label}
@@ -425,7 +426,7 @@ export const LlmSettingsPanel = forwardRef<LlmSettingsPanelHandle, LlmSettingsPa
425
426
  </optgroup>
426
427
  )}
427
428
  {otherVendors.length > 0 && (
428
- <optgroup label="全部">
429
+ <optgroup label={t('All')}>
429
430
  {otherVendors.map((v) => (
430
431
  <option key={v.id} value={v.id}>
431
432
  {v.label}
@@ -436,16 +437,16 @@ export const LlmSettingsPanel = forwardRef<LlmSettingsPanelHandle, LlmSettingsPa
436
437
  </select>
437
438
  <p className="mt-1.5 text-[11px] text-agent-muted-foreground">
438
439
  {selectedVendor?.id === 'ollama'
439
- ? '请确认本机已运行 `ollama serve` 并且执行过 `ollama pull <model>`。'
440
+ ? t('Make sure `ollama serve` is running on this computer and you have run `ollama pull <model>`.')
440
441
  : selectedVendor?.id === 'custom'
441
- ? '任意兼容 OpenAI Chat Completions 的网关。Anthropic / Gemini 请从上方列表选择对应服务商。'
442
+ ? t('Any gateway compatible with OpenAI Chat Completions. For Anthropic / Gemini, pick the matching provider from the list above.')
442
443
  : selectedVendor?.wireKind === 'anthropic'
443
- ? '走 Anthropic 原生 Messages 协议。'
444
+ ? t('Uses the native Anthropic Messages protocol.')
444
445
  : selectedVendor?.wireKind === 'google'
445
- ? '走 Google Gemini 原生协议。'
446
+ ? t('Uses the native Google Gemini protocol.')
446
447
  : selectedVendor?.wireKind === 'openai-responses'
447
- ? '走 OpenAI Responses API(如 xAI Grok)。'
448
- : '兼容 OpenAI Chat Completions 的云端或本地网关。'}
448
+ ? t('Uses the OpenAI Responses API (e.g. xAI Grok).')
449
+ : t('A cloud or local gateway compatible with OpenAI Chat Completions.')}
449
450
  </p>
450
451
  </div>
451
452
 
@@ -463,14 +464,14 @@ export const LlmSettingsPanel = forwardRef<LlmSettingsPanelHandle, LlmSettingsPa
463
464
  selectedVendor?.apiBaseUrl ||
464
465
  (selectedVendor?.id === 'custom'
465
466
  ? 'https://your-gateway.example/v1'
466
- : '该服务商没有默认地址,请手动填写')
467
+ : t('This provider has no default URL. Enter it manually.'))
467
468
  }
468
469
  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"
469
470
  />
470
471
  <p className="mt-1.5 text-[11px] text-agent-muted-foreground">
471
472
  {selectedVendor?.apiBaseUrl
472
- ? '已按服务商填入默认地址,私有部署可改。'
473
- : '目录里没有默认 URL,需要手动填写。'}
473
+ ? t('The provider default URL is filled in. Change it for a private deployment.')
474
+ : t('The catalog has no default URL. Enter it manually.')}
474
475
  </p>
475
476
  </div>
476
477
 
@@ -488,15 +489,15 @@ export const LlmSettingsPanel = forwardRef<LlmSettingsPanelHandle, LlmSettingsPa
488
489
  }
489
490
  placeholder={
490
491
  selectedVendor?.id === 'custom'
491
- ? 'sk-... (可选,私有部署可留空)'
492
- : 'sk-... (必填,到服务商控制台创建)'
492
+ ? t('sk-... (optional, can be blank for a private deployment)')
493
+ : t('sk-... (required, create one in the provider console)')
493
494
  }
494
495
  className="h-8 min-w-0 flex-1 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"
495
496
  />
496
497
  <button
497
498
  type="button"
498
499
  data-testid="llm-key-test"
499
- title="用当前 URL 和 Key 向网关拉一次模型目录"
500
+ title={t('Fetch the model catalog from the gateway once with the current URL and key')}
500
501
  disabled={!settings.baseUrl?.trim() || keyTest.status === 'testing'}
501
502
  onClick={() => void handleTestKey()}
502
503
  className="inline-flex h-8 shrink-0 items-center justify-center rounded-agent-md border border-agent-border bg-agent-canvas px-3 text-xs font-medium text-agent-foreground transition-colors hover:bg-agent-foreground/5 disabled:cursor-not-allowed disabled:opacity-50"
@@ -504,7 +505,7 @@ export const LlmSettingsPanel = forwardRef<LlmSettingsPanelHandle, LlmSettingsPa
504
505
  {keyTest.status === 'testing' ? (
505
506
  <LuLoaderCircle className="h-4 w-4 animate-spin" />
506
507
  ) : (
507
- '测试'
508
+ t('Test')
508
509
  )}
509
510
  </button>
510
511
  </div>
@@ -519,36 +520,40 @@ export const LlmSettingsPanel = forwardRef<LlmSettingsPanelHandle, LlmSettingsPa
519
520
  ].join(' ')}
520
521
  >
521
522
  {keyTest.status === 'testing'
522
- ? '正在验证凭证…'
523
+ ? t('Verifying credentials…')
523
524
  : keyTest.status === 'ok'
524
- ? `凭证可用,网关返回 ${keyTest.count} 个模型。`
525
+ ? t('Credentials work. The gateway returned {count} models.', { count: keyTest.count })
525
526
  : keyTest.status === 'fail'
526
- ? `验证失败:${keyTest.detail}`
527
+ ? t('Verification failed: {detail}', { detail: keyTest.detail })
527
528
  : !settings.apiKey?.trim()
528
- ? '尚未配置 API Key:到服务商控制台创建密钥(DeepSeek:platform.deepseek.com → API Keys),粘贴到上方输入框,点「测试」验证通过后保存。'
529
- : '网络搜索在设置页配置:可选用免费搜索或 Tavily 钥;OpenAI 可用这把聊天钥走托管搜索。'}
529
+ ? t(
530
+ 'No API key yet: create a key in the provider console (DeepSeek: platform.deepseek.com → API Keys), paste it in the box above, click "Test", and save once it passes.',
531
+ )
532
+ : t(
533
+ 'Web search is configured on the settings page: choose free search or a Tavily key. OpenAI can use this chat key for hosted search.',
534
+ )}
530
535
  </p>
531
536
  </div>
532
537
  )}
533
538
 
534
539
  <div>
535
540
  <label className="mb-1.5 block text-xs font-medium text-agent-muted-foreground">
536
- 模型
541
+ {t('Model')}
537
542
  </label>
538
543
  <div className="flex items-center gap-2">
539
544
  <div className="min-w-0 flex-1">
540
545
  <ModelIdCombobox
541
546
  value={settings.model}
542
547
  options={modelOptions}
543
- placeholder={liveEntries[0]?.id || catalogModels[0] || '选择或填写模型 id'}
548
+ placeholder={liveEntries[0]?.id || catalogModels[0] || t('Choose or enter a model id')}
544
549
  onChange={(model) => setSettings((prev) => ({ ...prev, model }))}
545
550
  />
546
551
  </div>
547
552
  <button
548
553
  type="button"
549
554
  data-testid="llm-model-refresh"
550
- title="从网关重新拉取模型列表"
551
- aria-label="刷新模型目录"
555
+ title={t('Fetch the model list from the gateway again')}
556
+ aria-label={t('Refresh model catalog')}
552
557
  disabled={!settings.baseUrl?.trim() || modelsRefreshing}
553
558
  onClick={() => void handleRefreshModels()}
554
559
  className="inline-flex h-8 w-8 shrink-0 items-center justify-center rounded-agent-md border border-agent-border bg-agent-canvas text-agent-muted-foreground transition-colors hover:bg-agent-foreground/5 hover:text-agent-foreground disabled:cursor-not-allowed disabled:opacity-50"
@@ -561,30 +566,32 @@ export const LlmSettingsPanel = forwardRef<LlmSettingsPanelHandle, LlmSettingsPa
561
566
  data-testid="llm-model-capabilities"
562
567
  className="mt-1.5 flex flex-wrap items-center gap-1.5 text-[11px] text-agent-muted-foreground"
563
568
  >
564
- <span>当前</span>
569
+ <span>{t('Current')}</span>
565
570
  {selectedEntry ? (
566
571
  selectedKnownEmpty ? (
567
- <span>文本对话</span>
572
+ <span>{t('Text chat')}</span>
568
573
  ) : (
569
574
  <ModelCapabilityChips entry={selectedEntry} detail />
570
575
  )
571
576
  ) : (
572
- <span>能力未识别(不在网关目录)</span>
577
+ <span>{t('Capabilities unrecognized (not in the gateway catalog)')}</span>
573
578
  )}
574
579
  </div>
575
580
  )}
576
581
  <p className="mt-1.5 text-[11px] text-agent-muted-foreground">
577
582
  {liveCatalogStatus === 'live'
578
- ? `已从网关拉取 ${liveEntries.length} 个模型,也可直接填写。`
583
+ ? t('Fetched {count} models from the gateway. You can also type one in.', {
584
+ count: liveEntries.length,
585
+ })
579
586
  : liveCatalogStatus === 'stale'
580
- ? '网关目录刷新失败,正在用上次拉取的缓存;也可直接填写。'
587
+ ? t('Failed to refresh the gateway catalog. Using the last cached copy; you can also type one in.')
581
588
  : liveCatalogStatus === 'idle' && (settings.baseUrl || '').trim()
582
- ? '正在从网关拉取模型目录…'
589
+ ? t('Fetching the model catalog from the gateway…')
583
590
  : liveCatalogStatus === 'idle'
584
- ? '填写 Base URL 后可拉取网关模型目录,也可直接填写。'
591
+ ? t('Enter a Base URL to fetch the gateway model catalog, or type a model in.')
585
592
  : catalogModels.length > 0
586
- ? '网关目录不可用,暂用服务商内置列表;也可直接填写。'
587
- : '网关目录不可用,请直接填写模型 id。'}
593
+ ? t('Gateway catalog unavailable. Using the provider built-in list for now; you can also type one in.')
594
+ : t('Gateway catalog unavailable. Enter the model id directly.')}
588
595
  </p>
589
596
  </div>
590
597
 
@@ -592,19 +599,23 @@ export const LlmSettingsPanel = forwardRef<LlmSettingsPanelHandle, LlmSettingsPa
592
599
  <div className="rounded-agent-md border border-agent-border/60 p-3 space-y-3">
593
600
  <div>
594
601
  <h4 className="text-xs font-semibold text-agent-muted-foreground uppercase tracking-wide">
595
- 厂商参数预制
602
+ {t('Vendor parameter presets')}
596
603
  </h4>
597
604
  <p className="mt-1 text-[11px] text-agent-muted-foreground">
598
- 按厂商文档的最优采样参数(temperature / top_p / top_k 等)自动填充请求。
599
- “自动”按 Base URL + 模型名匹配内置注册表;手动 Temperature 等显式设置永远优先于预制。
605
+ {t(
606
+ 'Fill requests automatically with the best sampling parameters from vendor docs (temperature / top_p / top_k, etc.).',
607
+ )}{' '}
608
+ {t(
609
+ '"Auto" matches the built-in registry by Base URL + model name. Explicit manual settings such as Temperature always take precedence over presets.',
610
+ )}
600
611
  </p>
601
612
  </div>
602
613
  <div className="flex gap-1.5">
603
614
  {(
604
615
  [
605
- { value: 'auto', label: '自动(推荐)' },
606
- { value: 'off', label: '关闭' },
607
- { value: 'pinned', label: '指定预制' },
616
+ { value: 'auto', label: 'Auto (recommended)' },
617
+ { value: 'off', label: 'Off' },
618
+ { value: 'pinned', label: 'Pick a preset' },
608
619
  ] as { value: PresetMode; label: string }[]
609
620
  ).map((opt) => (
610
621
  <button
@@ -618,7 +629,7 @@ export const LlmSettingsPanel = forwardRef<LlmSettingsPanelHandle, LlmSettingsPa
618
629
  : 'bg-agent-muted text-agent-muted-foreground hover:text-agent-foreground'
619
630
  }`}
620
631
  >
621
- {opt.label}
632
+ {t(opt.label)}
622
633
  </button>
623
634
  ))}
624
635
  </div>
@@ -631,7 +642,7 @@ export const LlmSettingsPanel = forwardRef<LlmSettingsPanelHandle, LlmSettingsPa
631
642
  >
632
643
  {presetPinnedIdx === -1 && (
633
644
  <option value={-1}>
634
- {settings.presets?.override ? '自定义(已保存的覆盖)' : '请选择…'}
645
+ {settings.presets?.override ? t('Custom (saved override)') : t('Select…')}
635
646
  </option>
636
647
  )}
637
648
  {presetList.map((d, i) => (
@@ -642,21 +653,23 @@ export const LlmSettingsPanel = forwardRef<LlmSettingsPanelHandle, LlmSettingsPa
642
653
  </select>
643
654
  ) : (
644
655
  <p className="text-[11px] text-agent-muted-foreground">
645
- 预制注册表由 sidecar 提供;sidecar 未就绪时无可选条目。
656
+ {t('The preset registry comes from the sidecar. Nothing is available while the sidecar is not ready.')}
646
657
  </p>
647
658
  ))}
648
659
  <p className="text-[11px] text-agent-muted-foreground/80" data-preset-preview>
649
660
  {presetMode === 'off'
650
- ? '已关闭:不下发厂商预制参数。'
661
+ ? t('Off: vendor preset parameters are not sent.')
651
662
  : presetMode === 'pinned'
652
- ? `生效参数:${summarizePreset(
653
- presetPinnedIdx >= 0 && presetList[presetPinnedIdx]
654
- ? overrideFromDescriptor(presetList[presetPinnedIdx])
655
- : (settings.presets?.override ?? null),
656
- )}`
663
+ ? t('Effective parameters: {summary}', {
664
+ summary: summarizePreset(
665
+ presetPinnedIdx >= 0 && presetList[presetPinnedIdx]
666
+ ? overrideFromDescriptor(presetList[presetPinnedIdx])
667
+ : (settings.presets?.override ?? null),
668
+ ),
669
+ })
657
670
  : resolvedPreset
658
- ? `命中预制:${summarizePreset(resolvedPreset)}`
659
- : '当前 Base URL + 模型未命中预制,不下发额外采样参数。'}
671
+ ? t('Matched preset: {summary}', { summary: summarizePreset(resolvedPreset) })
672
+ : t('The current Base URL + model match no preset. No extra sampling parameters are sent.')}
660
673
  </p>
661
674
  </div>
662
675
  )}
@@ -665,17 +678,17 @@ export const LlmSettingsPanel = forwardRef<LlmSettingsPanelHandle, LlmSettingsPa
665
678
  <div className="rounded-agent-md border border-agent-border/60 p-3 space-y-3">
666
679
  <div>
667
680
  <h4 className="text-xs font-semibold text-agent-muted-foreground uppercase tracking-wide">
668
- 高级兼容旗标(可选)
681
+ {t('Advanced compatibility flags (optional)')}
669
682
  </h4>
670
683
  <p className="mt-1 text-[11px] text-agent-muted-foreground">
671
- 仅当厂商网关与 OpenAI 协议有出入、且自动探测未覆盖时才需要改。“自动”
672
- = 框架按 Base URL 主机名匹配已知厂商(DeepSeek / Moonshot / OpenRouter /
673
- DashScope),未命中走 OpenAI 参考行为。
684
+ {t(
685
+ 'Change these only when the vendor gateway differs from the OpenAI protocol and auto-detection does not cover it. "Auto" = the framework matches known vendors by Base URL host name (DeepSeek / Moonshot / OpenRouter / DashScope) and falls back to OpenAI reference behavior otherwise.',
686
+ )}
674
687
  </p>
675
688
  </div>
676
689
  {compatFlags.length === 0 ? (
677
690
  <p className="text-[11px] text-agent-muted-foreground">
678
- 旗标词汇表由 sidecar 提供;sidecar 未就绪时此处为空,保存不受影响。
691
+ {t('The flag vocabulary comes from the sidecar. This stays empty while the sidecar is not ready; saving is not affected.')}
679
692
  </p>
680
693
  ) : (
681
694
  compatFlags.map((flag) => (
@@ -686,9 +699,9 @@ export const LlmSettingsPanel = forwardRef<LlmSettingsPanelHandle, LlmSettingsPa
686
699
  {flag.kind === 'bool' ? (
687
700
  <div className="flex gap-1.5">
688
701
  {[
689
- { value: COMPAT_AUTO, label: '自动' },
690
- { value: 'true', label: '开' },
691
- { value: 'false', label: '关' },
702
+ { value: COMPAT_AUTO, label: 'Auto' },
703
+ { value: 'true', label: 'Yes' },
704
+ { value: 'false', label: 'No' },
692
705
  ].map((opt) => (
693
706
  <button
694
707
  key={opt.value}
@@ -702,7 +715,7 @@ export const LlmSettingsPanel = forwardRef<LlmSettingsPanelHandle, LlmSettingsPa
702
715
  : 'bg-agent-muted text-agent-muted-foreground hover:text-agent-foreground'
703
716
  }`}
704
717
  >
705
- {opt.label}
718
+ {t(opt.label)}
706
719
  </button>
707
720
  ))}
708
721
  </div>
@@ -714,7 +727,7 @@ export const LlmSettingsPanel = forwardRef<LlmSettingsPanelHandle, LlmSettingsPa
714
727
  }
715
728
  className="h-8 w-full rounded-agent-md border border-agent-border bg-agent-canvas px-2 text-xs text-agent-foreground focus:outline-none focus:ring-2 focus:ring-agent-foreground/30"
716
729
  >
717
- <option value={COMPAT_AUTO}>自动</option>
730
+ <option value={COMPAT_AUTO}>{t('Auto')}</option>
718
731
  {flag.kind
719
732
  .slice('enum:'.length)
720
733
  .split(',')
@@ -734,7 +747,7 @@ export const LlmSettingsPanel = forwardRef<LlmSettingsPanelHandle, LlmSettingsPa
734
747
  [flag.key]: e.target.value || COMPAT_AUTO,
735
748
  }))
736
749
  }
737
- placeholder="逗号分隔,留空 = 自动"
750
+ placeholder={t('Comma-separated; blank = auto')}
738
751
  className="h-8 w-full rounded-agent-md border border-agent-border bg-agent-canvas px-2 text-xs text-agent-foreground focus:outline-none focus:ring-2 focus:ring-agent-foreground/30"
739
752
  />
740
753
  )}
@@ -756,8 +769,8 @@ export const LlmSettingsPanel = forwardRef<LlmSettingsPanelHandle, LlmSettingsPa
756
769
  <div className="flex gap-1">
757
770
  {(
758
771
  [
759
- { value: 'auto', label: '自动' },
760
- { value: 'manual', label: '手动' },
772
+ { value: 'auto', label: 'Auto' },
773
+ { value: 'manual', label: 'Manual' },
761
774
  ] as const
762
775
  ).map((opt) => (
763
776
  <button
@@ -779,18 +792,22 @@ export const LlmSettingsPanel = forwardRef<LlmSettingsPanelHandle, LlmSettingsPa
779
792
  : 'bg-agent-muted text-agent-muted-foreground hover:text-agent-foreground'
780
793
  }`}
781
794
  >
782
- {opt.label}
795
+ {t(opt.label)}
783
796
  </button>
784
797
  ))}
785
798
  </div>
786
799
  </div>
787
800
  {settings.temperature === undefined ? (
788
801
  <p className="text-[11px] text-agent-muted-foreground">
789
- 自动:命中厂商预制时用预制温度
790
- {presetMode === 'auto' && resolvedPreset?.temperature != null
791
- ? `(当前命中:${resolvedPreset.temperature})`
792
- : ''}
793
- ,未命中则不下发(走厂商服务端默认)。手动显式值优先于预制。
802
+ {t(
803
+ 'Auto: uses the preset temperature when a vendor preset matches{match}. Otherwise nothing is sent (the vendor server default applies). An explicit manual value takes precedence over the preset.',
804
+ {
805
+ match:
806
+ presetMode === 'auto' && resolvedPreset?.temperature != null
807
+ ? t(' (current match: {value})', { value: resolvedPreset.temperature })
808
+ : '',
809
+ },
810
+ )}
794
811
  </p>
795
812
  ) : (
796
813
  <input
@@ -809,7 +826,7 @@ export const LlmSettingsPanel = forwardRef<LlmSettingsPanelHandle, LlmSettingsPa
809
826
 
810
827
  <div>
811
828
  <label className="mb-1.5 block text-xs font-medium text-agent-muted-foreground">
812
- Token 预算限制 (Token Budget Limit)
829
+ {t('Token budget limit')}
813
830
  </label>
814
831
  <input
815
832
  type="number"
@@ -822,30 +839,34 @@ export const LlmSettingsPanel = forwardRef<LlmSettingsPanelHandle, LlmSettingsPa
822
839
  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"
823
840
  />
824
841
  <p className="mt-1.5 text-[11px] text-agent-muted-foreground">
825
- 单次对话中累积消耗的最大 Token 数量(默认 60,000)。超限后将自动停止,防止模型死循环或意外消耗过多 Token。
842
+ {t(
843
+ 'Maximum tokens a single conversation may consume in total (default 60,000). It stops automatically when the limit is exceeded, to prevent endless model loops or unexpected token usage.',
844
+ )}
826
845
  </p>
827
846
  </div>
828
847
 
829
848
  {/* ───── 超时设置 ───── */}
830
849
  <div className="border-t border-agent-border/60 pt-3 space-y-3">
831
850
  <h4 className="text-xs font-semibold text-agent-muted-foreground uppercase tracking-wide">
832
- 超时设置
851
+ {t('Timeouts')}
833
852
  </h4>
834
853
 
835
854
  <div>
836
855
  <label className="mb-1.5 block text-xs font-medium text-agent-muted-foreground">
837
- 本地命令默认超时(秒)
856
+ {t('Default local command timeout (seconds)')}
838
857
  </label>
839
858
  <input
840
859
  type="number"
841
860
  min={1}
842
861
  value={execTimeoutInput}
843
862
  onChange={(e) => setExecTimeoutInput(e.target.value)}
844
- placeholder="留空 = 默认(后台 30s / 终端 60s)"
863
+ placeholder={t('Blank = default (background 30s / terminal 60s)')}
845
864
  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"
846
865
  />
847
866
  <p className="mt-1.5 text-[11px] text-agent-muted-foreground">
848
- 执行本地命令时未显式指定超时的默认等待时长。命令行带 “gui” 的启动命令不受此限制:超时后按“程序已启动、仍在运行”处理,不会被判为失败或重复启动。
867
+ {t(
868
+ 'Default wait time for local commands that do not set a timeout explicitly. Launch commands containing "gui" are exempt: on timeout they are treated as "program started and still running", not as a failure or a repeated launch.',
869
+ )}
849
870
  </p>
850
871
  </div>
851
872