@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
@@ -6,7 +6,8 @@ import {
6
6
  LuTrash2,
7
7
  } from 'react-icons/lu';
8
8
  import { ConfirmDialog } from '@/components/ConfirmDialog';
9
- import { isElectron } from '@/lib/electron-bridge';
9
+ import { t } from '@/i18n';
10
+ import { hasHostBridge } from '@/lib/host-bridge';
10
11
  import {
11
12
  archiveChatAgent,
12
13
  createChatAgent,
@@ -61,15 +62,15 @@ function agentInitial(agent: LocalChatAgent): string {
61
62
  function capabilitySummary(agent: LocalChatAgent): string[] {
62
63
  const labels: string[] = [];
63
64
  const skillCount = agent.skillIds?.length ?? 0;
64
- if (skillCount > 0) labels.push(`技能 ${skillCount}`);
65
- if (agent.allowExternalSkills === false) labels.push('仅限所选技能');
66
- if (agent.loadAllSkills) labels.push('全部技能');
65
+ if (skillCount > 0) labels.push(t('Skills {count}', { count: skillCount }));
66
+ if (agent.allowExternalSkills === false) labels.push(t('Selected skills only'));
67
+ if (agent.loadAllSkills) labels.push(t('All skills'));
67
68
  const policy = agent.toolPolicy;
68
69
  if (policy && policy.mode !== 'all' && policy.tools.length > 0) {
69
70
  labels.push(
70
71
  policy.mode === 'allowlist'
71
- ? `仅 ${policy.tools.length} 个工具`
72
- : `禁用 ${policy.tools.length} 个工具`,
72
+ ? t('Only {count} tools', { count: policy.tools.length })
73
+ : t('{count} tools blocked', { count: policy.tools.length }),
73
74
  );
74
75
  }
75
76
  return labels;
@@ -100,7 +101,7 @@ export function AgentsSettingsPanel({
100
101
  const [error, setError] = useState<string | null>(null);
101
102
 
102
103
  const fetchAgents = useCallback(async () => {
103
- if (!isElectron()) return;
104
+ if (!hasHostBridge()) return;
104
105
  setLoading(true);
105
106
  setError(null);
106
107
  try {
@@ -150,7 +151,7 @@ export function AgentsSettingsPanel({
150
151
 
151
152
  const handleSave = async () => {
152
153
  const trimmedName = name.trim();
153
- if (!trimmedName || !isElectron()) return;
154
+ if (!trimmedName || !hasHostBridge()) return;
154
155
  setSaving(true);
155
156
  setError(null);
156
157
  try {
@@ -158,7 +159,7 @@ export function AgentsSettingsPanel({
158
159
  name: trimmedName,
159
160
  color,
160
161
  description: description.trim() || null,
161
- rolePrompt: rolePrompt.trim() || `你是 **${trimmedName}**。`,
162
+ rolePrompt: rolePrompt.trim() || t('You are **{name}**.', { name: trimmedName }),
162
163
  skillIds: capability.skillIds,
163
164
  allowExternalSkills: capability.allowExternalSkills,
164
165
  loadAllSkills: capability.loadAllSkills,
@@ -203,20 +204,21 @@ export function AgentsSettingsPanel({
203
204
  return (
204
205
  <div className="space-y-3">
205
206
  <p className="text-[11px] text-agent-muted-foreground">
206
- 自定义智能体会出现在输入框上方的专家选择器里。角色设定会作为对话的人设前言,
207
- 勾选的技能与工具权限在每一轮真实生效。内置智能体可以改文案和能力,但不能删除。
207
+ {t(
208
+ 'Custom agents appear in the expert picker above the input box. The role prompt opens the conversation as its persona, and the checked skills and tool permissions apply on every turn. Built-in agents can have their text and capabilities edited, but cannot be deleted.',
209
+ )}
208
210
  </p>
209
211
 
210
212
  {formOpen ? (
211
213
  <div className="space-y-2 rounded-agent-md border border-agent-border/60 bg-agent-muted/30 p-2.5">
212
214
  <h4 className="text-xs font-semibold text-agent-foreground">
213
- {editingId ? '编辑智能体' : '新建智能体'}
215
+ {editingId ? t('Edit agent') : t('New agent')}
214
216
  </h4>
215
217
  <input
216
218
  type="text"
217
219
  value={name}
218
220
  onChange={(event) => setName(event.target.value)}
219
- placeholder="名称,如 地质顾问"
221
+ placeholder={t('Name, e.g. Geology advisor')}
220
222
  maxLength={40}
221
223
  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"
222
224
  data-testid="agent-form-name"
@@ -225,13 +227,13 @@ export function AgentsSettingsPanel({
225
227
  type="text"
226
228
  value={description}
227
229
  onChange={(event) => setDescription(event.target.value)}
228
- placeholder="一句话简介(可选)"
230
+ placeholder={t('One-line summary (optional)')}
229
231
  maxLength={200}
230
232
  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"
231
233
  data-testid="agent-form-description"
232
234
  />
233
235
  <div>
234
- <p className="mb-1.5 text-[10px] font-medium text-agent-muted-foreground">颜色</p>
236
+ <p className="mb-1.5 text-[10px] font-medium text-agent-muted-foreground">{t('Color')}</p>
235
237
  <div className="flex flex-wrap gap-1.5" data-testid="agent-form-colors">
236
238
  {AGENT_COLORS.map((item) => (
237
239
  <button
@@ -245,7 +247,7 @@ export function AgentsSettingsPanel({
245
247
  }`}
246
248
  style={{ backgroundColor: item }}
247
249
  title={item}
248
- aria-label={`颜色 ${item}`}
250
+ aria-label={t('Color {color}', { color: item })}
249
251
  />
250
252
  ))}
251
253
  </div>
@@ -253,7 +255,7 @@ export function AgentsSettingsPanel({
253
255
  <textarea
254
256
  value={rolePrompt}
255
257
  onChange={(event) => setRolePrompt(event.target.value)}
256
- placeholder="角色设定。例如:你是地质顾问,回答简洁,先给结论再给依据。"
258
+ placeholder={t('Role prompt. For example: You are a geology advisor. Answer concisely, giving the conclusion first and then the evidence.')}
257
259
  rows={6}
258
260
  className="w-full rounded-agent-md border border-agent-border bg-agent-canvas px-3 py-2 text-xs text-agent-foreground focus:outline-none focus:ring-2 focus:ring-agent-foreground/30"
259
261
  data-testid="agent-form-role"
@@ -268,7 +270,7 @@ export function AgentsSettingsPanel({
268
270
  }}
269
271
  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"
270
272
  >
271
- 取消
273
+ {t('Cancel')}
272
274
  </button>
273
275
  <button
274
276
  type="button"
@@ -281,7 +283,7 @@ export function AgentsSettingsPanel({
281
283
  : 'bg-agent-foreground text-agent-canvas hover:opacity-90'
282
284
  }`}
283
285
  >
284
- {saving ? <LuLoaderCircle className="h-3 w-3 animate-spin" /> : '保存'}
286
+ {saving ? <LuLoaderCircle className="h-3 w-3 animate-spin" /> : t('Save')}
285
287
  </button>
286
288
  </div>
287
289
  </div>
@@ -293,13 +295,13 @@ export function AgentsSettingsPanel({
293
295
  data-testid="agent-add"
294
296
  >
295
297
  <LuPlus className="h-3.5 w-3.5" />
296
- 添加智能体
298
+ {t('Add agent')}
297
299
  </button>
298
300
  )}
299
301
 
300
302
  <div className="space-y-2">
301
303
  <h4 className="text-xs font-semibold uppercase tracking-wide text-agent-muted-foreground">
302
- 已有智能体
304
+ {t('Existing agents')}
303
305
  {agents.length > 0 && (
304
306
  <span className="ml-1.5 font-normal normal-case">· {agents.length}</span>
305
307
  )}
@@ -307,11 +309,11 @@ export function AgentsSettingsPanel({
307
309
  {loading ? (
308
310
  <div className="flex items-center gap-2 py-4 text-xs text-agent-muted-foreground">
309
311
  <LuLoaderCircle className="h-3.5 w-3.5 animate-spin" />
310
- 获取智能体...
312
+ {t('Loading agents...')}
311
313
  </div>
312
314
  ) : agents.length === 0 ? (
313
315
  <div className="py-6 text-center text-xs text-agent-muted-foreground">
314
- 暂无智能体
316
+ {t('No agents yet')}
315
317
  </div>
316
318
  ) : (
317
319
  <div className="divide-y divide-agent-border/40 pr-1">
@@ -334,7 +336,7 @@ export function AgentsSettingsPanel({
334
336
  </span>
335
337
  {agent.isBuiltin && (
336
338
  <span className="flex-shrink-0 rounded bg-agent-muted px-1 py-0.5 text-[9px] font-medium text-agent-muted-foreground">
337
- 内置
339
+ {t('Built-in')}
338
340
  </span>
339
341
  )}
340
342
  </div>
@@ -364,7 +366,7 @@ export function AgentsSettingsPanel({
364
366
  type="button"
365
367
  onClick={() => openEdit(agent)}
366
368
  className="rounded-full p-1 text-agent-muted-foreground transition-colors hover:bg-agent-muted hover:text-agent-foreground"
367
- title="编辑"
369
+ title={t('Edit')}
368
370
  data-testid={`agent-edit-${agent.id}`}
369
371
  >
370
372
  <LuPencil className="h-3.5 w-3.5" />
@@ -377,7 +379,7 @@ export function AgentsSettingsPanel({
377
379
  }
378
380
  disabled={archivingId === agent.id}
379
381
  className="rounded-full p-1 text-agent-muted-foreground transition-colors hover:bg-agent-muted hover:text-agent-destructive"
380
- title="删除"
382
+ title={t('Delete')}
381
383
  data-testid={`agent-archive-${agent.id}`}
382
384
  >
383
385
  {archivingId === agent.id ? (
@@ -404,10 +406,13 @@ export function AgentsSettingsPanel({
404
406
  )}
405
407
  <ConfirmDialog
406
408
  open={pendingArchive !== null}
407
- title="删除智能体"
409
+ title={t('Delete agent')}
408
410
  description={
409
411
  pendingArchive
410
- ? `确定删除「${pendingArchive.name}」?它会从专家选择器中移除,已有会话不受影响。`
412
+ ? t(
413
+ 'Delete "{name}"? It will be removed from the expert picker. Existing conversations are not affected.',
414
+ { name: pendingArchive.name },
415
+ )
411
416
  : ''
412
417
  }
413
418
  pending={pendingArchive !== null && archivingId === pendingArchive.id}
@@ -1,5 +1,6 @@
1
1
  import { LuInfo } from 'react-icons/lu';
2
2
  import { useAppRelease } from '@/components/SidebarRelease';
3
+ import { t } from '@/i18n';
3
4
 
4
5
  /**
5
6
  * 综合设置里的版本与更新。侧栏只显示版本号;检查、下载和安装在这里。
@@ -17,11 +18,11 @@ export function AppUpdateSettingsPanel() {
17
18
  <section className="space-y-2" data-testid="settings-section-update">
18
19
  <h2 className="flex items-center gap-1.5 text-xs font-semibold text-agent-foreground">
19
20
  <LuInfo className="h-3.5 w-3.5 text-agent-muted-foreground" />
20
- 关于
21
+ {t('About')}
21
22
  </h2>
22
23
  <div className="flex items-center gap-3 rounded-agent-md border border-agent-border bg-agent-card p-2.5">
23
24
  <div className="min-w-0 flex-1">
24
- <p className="text-xs font-medium text-agent-foreground">当前版本</p>
25
+ <p className="text-xs font-medium text-agent-foreground">{t('Current version')}</p>
25
26
  <p
26
27
  className="mt-1 text-xs tabular-nums text-agent-muted-foreground"
27
28
  data-testid="settings-app-version"
@@ -3,54 +3,98 @@ import {
3
3
  persistThinkingDisplay,
4
4
  useThinkingDisplay,
5
5
  } from '@/lib/show-thinking-content';
6
+ import { localeEndonym, supportedLocales, useI18n } from '@/i18n';
6
7
 
7
8
  /**
8
9
  * 界面偏好:思考过程正文按「隐藏 / 显示5行 / 完整显示」三档。
9
- * 立即写入 localStorage,不走设置页顶部的模型保存按钮。
10
+ * 产品声明了多于一种语言时,在这里切换。立即写入 localStorage。
10
11
  */
11
12
  export function AppearanceSettingsPanel() {
13
+ const { t, locale, setLocale } = useI18n();
12
14
  const mode = useThinkingDisplay();
13
15
  const current = THINKING_DISPLAY_OPTIONS.find((item) => item.mode === mode);
16
+ const locales = supportedLocales();
14
17
 
15
18
  return (
16
- <div
17
- className="space-y-2 rounded-agent-md border border-agent-border bg-agent-card p-2.5"
18
- data-testid="appearance-settings-panel"
19
- >
20
- <div>
21
- <p className="text-xs font-medium text-agent-foreground">显示思考内容</p>
22
- <p className="mt-1 text-xs leading-relaxed text-agent-muted-foreground">
23
- {current?.hint} 这三档只控制思考正文,不影响工具卡片和工作行。工作行只显示「工作中」或结束后的次数与用时。
24
- </p>
25
- </div>
19
+ <div className="space-y-2">
20
+ {locales.length > 1 ? (
21
+ <div
22
+ className="space-y-2 rounded-agent-md border border-agent-border bg-agent-card p-2.5"
23
+ data-testid="locale-settings"
24
+ >
25
+ <p className="text-xs font-medium text-agent-foreground">{t('Language')}</p>
26
+ <div
27
+ className="flex w-full rounded-full border border-agent-border bg-agent-canvas p-0.5"
28
+ role="radiogroup"
29
+ aria-label={t('Language')}
30
+ data-testid="locale-toggle"
31
+ >
32
+ {locales.map((item) => {
33
+ const selected = locale === item;
34
+ return (
35
+ <button
36
+ key={item}
37
+ type="button"
38
+ role="radio"
39
+ aria-checked={selected}
40
+ data-testid={`locale-option-${item}`}
41
+ onClick={() => setLocale(item)}
42
+ className={[
43
+ 'h-7 flex-1 rounded-full px-2 text-xs transition-colors',
44
+ selected
45
+ ? 'bg-agent-foreground/10 text-agent-foreground'
46
+ : 'text-agent-muted-foreground hover:text-agent-foreground',
47
+ ].join(' ')}
48
+ >
49
+ {localeEndonym(item)}
50
+ </button>
51
+ );
52
+ })}
53
+ </div>
54
+ </div>
55
+ ) : null}
26
56
  <div
27
- className="flex w-full rounded-full border border-agent-border bg-agent-canvas p-0.5"
28
- role="radiogroup"
29
- aria-label="显示思考内容"
30
- data-testid="thinking-display-toggle"
57
+ className="space-y-2 rounded-agent-md border border-agent-border bg-agent-card p-2.5"
58
+ data-testid="appearance-settings-panel"
31
59
  >
32
- {THINKING_DISPLAY_OPTIONS.map((item) => {
33
- const selected = mode === item.mode;
34
- return (
35
- <button
36
- key={item.mode}
37
- type="button"
38
- role="radio"
39
- aria-checked={selected}
40
- aria-label={item.label}
41
- data-testid={`thinking-display-${item.mode}`}
42
- onClick={() => persistThinkingDisplay(item.mode)}
43
- className={[
44
- 'h-7 flex-1 rounded-full px-2 text-xs transition-colors',
45
- selected
46
- ? 'bg-agent-foreground/10 text-agent-foreground'
47
- : 'text-agent-muted-foreground hover:text-agent-foreground',
48
- ].join(' ')}
49
- >
50
- {item.label}
51
- </button>
52
- );
53
- })}
60
+ <div>
61
+ <p className="text-xs font-medium text-agent-foreground">{t('Show thinking')}</p>
62
+ <p className="mt-1 text-xs leading-relaxed text-agent-muted-foreground">
63
+ {current ? t(current.hint) : null}{' '}
64
+ {t(
65
+ 'These options only control the thinking text. Tool cards and work rows stay as they are. A work row shows "Working", or the count and elapsed time after it finishes.',
66
+ )}
67
+ </p>
68
+ </div>
69
+ <div
70
+ className="flex w-full rounded-full border border-agent-border bg-agent-canvas p-0.5"
71
+ role="radiogroup"
72
+ aria-label={t('Show thinking')}
73
+ data-testid="thinking-display-toggle"
74
+ >
75
+ {THINKING_DISPLAY_OPTIONS.map((item) => {
76
+ const selected = mode === item.mode;
77
+ return (
78
+ <button
79
+ key={item.mode}
80
+ type="button"
81
+ role="radio"
82
+ aria-checked={selected}
83
+ aria-label={t(item.label)}
84
+ data-testid={`thinking-display-${item.mode}`}
85
+ onClick={() => persistThinkingDisplay(item.mode)}
86
+ className={[
87
+ 'h-7 flex-1 rounded-full px-2 text-xs transition-colors',
88
+ selected
89
+ ? 'bg-agent-foreground/10 text-agent-foreground'
90
+ : 'text-agent-muted-foreground hover:text-agent-foreground',
91
+ ].join(' ')}
92
+ >
93
+ {t(item.label)}
94
+ </button>
95
+ );
96
+ })}
97
+ </div>
54
98
  </div>
55
99
  </div>
56
100
  );
@@ -1,6 +1,7 @@
1
1
  import { useState } from 'react';
2
2
  import { LuCircleCheck, LuCircleX, LuLoader, LuPlay } from 'react-icons/lu';
3
3
  import { diagnoseLlmConnection, type DiagnoseResult } from '@/lib/local-api';
4
+ import { t } from '@/i18n';
4
5
 
5
6
  /**
6
7
  * 设置页「链路诊断」面板:一键探测当前 LLM 配置的连通性
@@ -29,8 +30,10 @@ export function DiagnoseSettingsPanel() {
29
30
  return (
30
31
  <div className="space-y-2 rounded-agent-md border border-agent-border bg-agent-card p-2.5">
31
32
  <p className="text-xs text-agent-muted-foreground">
32
- 诊断当前模型服务的网络连通性,包括 DNS 解析、TCP 连接、TLS 握手、HTTP 接口与对话补全。
33
- 诊断在宿主进程内执行,会同时报告系统/环境代理配置。
33
+ {t(
34
+ 'Diagnose network connectivity to the current model service, including DNS resolution, TCP connection, TLS handshake, the HTTP endpoint, and chat completion.',
35
+ )}{' '}
36
+ {t('The diagnosis runs in the host process and also reports system and environment proxy settings.')}
34
37
  </p>
35
38
 
36
39
  <button
@@ -42,19 +45,19 @@ export function DiagnoseSettingsPanel() {
42
45
  {running ? (
43
46
  <>
44
47
  <LuLoader className="h-3.5 w-3.5 animate-spin" />
45
- 诊断中…
48
+ {t('Diagnosing…')}
46
49
  </>
47
50
  ) : (
48
51
  <>
49
52
  <LuPlay className="h-3.5 w-3.5" />
50
- 开始诊断
53
+ {t('Start diagnosis')}
51
54
  </>
52
55
  )}
53
56
  </button>
54
57
 
55
58
  {error && (
56
59
  <p className="rounded-agent-md border border-agent-destructive/20 bg-agent-destructive/10 p-2.5 text-xs text-agent-destructive">
57
- 诊断请求失败:{error}
60
+ {t('Diagnosis request failed: {error}', { error })}
58
61
  </p>
59
62
  )}
60
63
 
@@ -64,12 +67,12 @@ export function DiagnoseSettingsPanel() {
64
67
  {result.ok ? (
65
68
  <span className="inline-flex items-center gap-1 text-agent-success">
66
69
  <LuCircleCheck className="h-4 w-4" />
67
- 全部通过
70
+ {t('All checks passed')}
68
71
  </span>
69
72
  ) : (
70
73
  <span className="inline-flex items-center gap-1 text-agent-destructive">
71
74
  <LuCircleX className="h-4 w-4" />
72
- 诊断失败
75
+ {t('Diagnosis failed')}
73
76
  </span>
74
77
  )}
75
78
  </div>
@@ -98,7 +101,7 @@ export function DiagnoseSettingsPanel() {
98
101
 
99
102
  {result.ambientProxies.length > 0 && (
100
103
  <div className="rounded-agent-sm border border-agent-border bg-agent-muted/30 p-2.5 text-xs">
101
- <p className="font-medium text-agent-foreground">检测到的代理配置</p>
104
+ <p className="font-medium text-agent-foreground">{t('Detected proxy settings')}</p>
102
105
  <ul className="mt-1 list-inside list-disc space-y-0.5 text-agent-muted-foreground">
103
106
  {result.ambientProxies.map((proxy) => (
104
107
  <li key={proxy}>{proxy}</li>