@steerable/agent-shell-web 0.6.53 → 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 (90) 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 +56 -40
  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 +60 -34
  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 +16 -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 +4 -5
  82. package/src/lib/http-bridge.ts +4 -1
  83. package/src/lib/portable.ts +8 -7
  84. package/src/lib/show-thinking-content.ts +15 -3
  85. package/src/main.tsx +25 -2
  86. package/src/pages/AgentPage.tsx +17 -12
  87. package/src/pages/ChatPanelPreviewPage.tsx +46 -44
  88. package/src/pages/SettingsPage.tsx +48 -42
  89. package/src/vite-env.d.ts +3 -0
  90. package/vite.base.js +6 -2
@@ -9,6 +9,7 @@
9
9
  */
10
10
  import { useCallback, useEffect, useState } from 'react';
11
11
  import { LuShieldAlert, LuShieldCheck } from 'react-icons/lu';
12
+ import { t } from '@/i18n';
12
13
  import type { LocalProject } from '@/lib/local-api';
13
14
  import { getChatProjectContext, setProjectTrusted } from '@/lib/local-api';
14
15
 
@@ -51,13 +52,21 @@ export function ProjectTrustBanner({ chatId, project, onTrustChanged }: ProjectT
51
52
  };
52
53
 
53
54
  if (!trusted) {
55
+ const [untrustedBefore, untrustedAfter] = t(
56
+ 'Until trusted, these project instructions {willNot} be injected into the model.',
57
+ ).split('{willNot}');
54
58
  return (
55
59
  <div className="mx-3 mb-1 flex items-center justify-between gap-3 rounded-agent-md border border-sky-400/40 bg-sky-400/10 px-3 py-2 text-xs">
56
60
  <div className="flex items-center gap-2 text-sky-700 dark:text-sky-300">
57
61
  <LuShieldAlert className="h-4 w-4 shrink-0" />
58
62
  <span>
59
- 项目「{project.name}」包含 {ruleFileCount} 个规则文件(AGENTS.md / CLAUDE.md)。
60
- 信任前这些项目方指令<strong>不会</strong>注入模型。
63
+ {t('Project "{name}" has {count} rule files (AGENTS.md / CLAUDE.md).', {
64
+ name: project.name,
65
+ count: ruleFileCount,
66
+ })}{' '}
67
+ {untrustedBefore}
68
+ <strong>{t('will not')}</strong>
69
+ {untrustedAfter}
61
70
  </span>
62
71
  </div>
63
72
  <button
@@ -66,7 +75,7 @@ export function ProjectTrustBanner({ chatId, project, onTrustChanged }: ProjectT
66
75
  onClick={() => void toggle()}
67
76
  className="inline-flex shrink-0 items-center gap-1 rounded-full bg-agent-foreground px-3 py-1 font-medium text-agent-canvas transition hover:opacity-90 disabled:opacity-50"
68
77
  >
69
- 信任并加载
78
+ {t('Trust and load')}
70
79
  </button>
71
80
  </div>
72
81
  );
@@ -76,7 +85,12 @@ export function ProjectTrustBanner({ chatId, project, onTrustChanged }: ProjectT
76
85
  <div className="mx-3 mb-1 flex items-center justify-between gap-3 rounded-agent-md border border-agent-border bg-agent-muted/30 px-3 py-1.5 text-[11px] text-agent-muted-foreground">
77
86
  <div className="flex items-center gap-1.5">
78
87
  <LuShieldCheck className="h-3.5 w-3.5 shrink-0 text-emerald-500" />
79
- <span>已加载项目「{project.name}」的 {ruleFileCount} 个规则文件</span>
88
+ <span>
89
+ {t('Loaded {count} rule files from project "{name}"', {
90
+ name: project.name,
91
+ count: ruleFileCount,
92
+ })}
93
+ </span>
80
94
  </div>
81
95
  <button
82
96
  type="button"
@@ -84,7 +98,7 @@ export function ProjectTrustBanner({ chatId, project, onTrustChanged }: ProjectT
84
98
  onClick={() => void toggle()}
85
99
  className="shrink-0 rounded px-1.5 py-0.5 transition-colors hover:text-agent-destructive disabled:opacity-50"
86
100
  >
87
- 撤销信任
101
+ {t('Revoke trust')}
88
102
  </button>
89
103
  </div>
90
104
  );
@@ -21,6 +21,7 @@ import {
21
21
  } from './orchestration-flow-model';
22
22
  import type { InspectTaskInput } from './executed-actions-model';
23
23
  import { useExclusiveExpand } from './composer-status-stack';
24
+ import { t } from '@/i18n';
24
25
 
25
26
  function FlowNodeStatusIcon({
26
27
  status,
@@ -51,21 +52,21 @@ function FlowNodeStatusIcon({
51
52
  function statusBadgeText(status: OrchestrationNodeStatus): string {
52
53
  switch (status) {
53
54
  case 'completed':
54
- return '已完成';
55
+ return t('Completed');
55
56
  case 'running':
56
- return '执行中';
57
+ return t('Running');
57
58
  case 'interrupted':
58
- return '已暂停';
59
+ return t('Paused');
59
60
  case 'cancelled':
60
- return '已停止';
61
+ return t('Stopped');
61
62
  case 'closed':
62
- return '已关闭';
63
+ return t('Closed');
63
64
  case 'failed':
64
- return '失败';
65
+ return t('Failed');
65
66
  case 'pending':
66
- return '等待中';
67
+ return t('Waiting');
67
68
  default:
68
- return '等待中';
69
+ return t('Waiting');
69
70
  }
70
71
  }
71
72
 
@@ -349,10 +350,10 @@ export function SessionOrchestrationFlow({
349
350
 
350
351
  <span className="min-w-0 flex-1 truncate font-medium">
351
352
  {isAllCompleted
352
- ? '协同编排已就绪'
353
+ ? t('Orchestration ready')
353
354
  : hasActive
354
- ? (activeNode?.task ? activeNode.task : '协同编排流程')
355
- : '协同编排已停止'}
355
+ ? (activeNode?.task ? activeNode.task : t('Orchestration flow'))
356
+ : t('Orchestration stopped')}
356
357
  </span>
357
358
 
358
359
  {summaryCopy ? (
@@ -389,7 +390,7 @@ export function SessionOrchestrationFlow({
389
390
  <div className="flex items-center justify-between border-b border-agent-border/60 bg-agent-muted/30 px-3 py-2">
390
391
  <div className="flex items-center gap-1.5 text-xs font-semibold text-agent-foreground">
391
392
  <LuGitFork className="h-3.5 w-3.5 text-violet-600 dark:text-violet-400" />
392
- <span>多智能体协同流程</span>
393
+ <span>{t('Multi-agent flow')}</span>
393
394
  <span className="rounded-full bg-violet-500/10 px-1.5 py-0.2 text-[10px] font-medium text-violet-700 dark:text-violet-300">
394
395
  Fork-Join
395
396
  </span>
@@ -410,7 +411,7 @@ export function SessionOrchestrationFlow({
410
411
  type="button"
411
412
  onClick={() => setUserToggled(false)}
412
413
  className="rounded p-0.5 text-agent-muted-foreground hover:bg-agent-foreground/10 hover:text-agent-foreground"
413
- aria-label="收起编排流程"
414
+ aria-label={t('Collapse orchestration flow')}
414
415
  >
415
416
  <LuX className="h-3 w-3" />
416
417
  </button>
@@ -423,9 +424,9 @@ export function SessionOrchestrationFlow({
423
424
  {/* 1. 起点:目标分发 (Fork) 居中节点 */}
424
425
  <div className="inline-flex items-center gap-1.5 rounded-full border border-violet-500/30 bg-violet-500/10 px-3.5 py-1 text-xs font-semibold text-violet-800 dark:text-violet-200 shadow-2xs">
425
426
  <LuBot className="h-3.5 w-3.5 text-violet-600 dark:text-violet-400" />
426
- <span>目标分发 (Fork)</span>
427
+ <span>{t('Goal dispatch (Fork)')}</span>
427
428
  <span className="rounded-full bg-violet-500/20 px-1.5 py-0.2 text-[10px] font-medium text-violet-700 dark:text-violet-300">
428
- 并发 {nodes.length} 分支
429
+ {t('{count} parallel branches', { count: nodes.length })}
429
430
  </span>
430
431
  </div>
431
432
 
@@ -451,7 +452,7 @@ export function SessionOrchestrationFlow({
451
452
  <div className="flex items-center gap-1 min-w-0 flex-1">
452
453
  <FlowNodeStatusIcon status={node.status} />
453
454
  <span className="font-semibold text-agent-foreground text-[11px] truncate whitespace-nowrap">
454
- 分支 #{index + 1}
455
+ {t('Branch #{index}', { index: index + 1 })}
455
456
  </span>
456
457
  </div>
457
458
 
@@ -478,14 +479,14 @@ export function SessionOrchestrationFlow({
478
479
  chatId,
479
480
  recordId: node.recordId,
480
481
  live: node.status === 'running',
481
- title: node.task || '子代理执行过程',
482
+ title: node.task || t('Subagent run'),
482
483
  })
483
484
  }
484
485
  className="inline-flex shrink-0 whitespace-nowrap items-center gap-0.5 rounded px-1 text-[10px] text-agent-muted-foreground hover:bg-agent-muted hover:text-agent-foreground"
485
- title="查看过程"
486
+ title={t('View process')}
486
487
  >
487
488
  <LuExternalLink className="h-2.5 w-2.5 shrink-0" />
488
- <span>过程</span>
489
+ <span>{t('Process')}</span>
489
490
  </button>
490
491
  ) : null}
491
492
  </div>
@@ -503,7 +504,7 @@ export function SessionOrchestrationFlow({
503
504
  }`}
504
505
  title={node.task}
505
506
  >
506
- {node.task || `子任务 (${node.childId})`}
507
+ {node.task || t('Subtask ({id})', { id: node.childId })}
507
508
  </p>
508
509
  </div>
509
510
 
@@ -525,7 +526,7 @@ export function SessionOrchestrationFlow({
525
526
  <div className="mt-2 rounded bg-emerald-500/10 border border-emerald-500/20 px-2 py-1 text-[11px]">
526
527
  <div className="flex items-center gap-1 font-semibold text-emerald-800 dark:text-emerald-200 text-[10px]">
527
528
  <LuCheck className="h-3 w-3" />
528
- <span>结论:</span>
529
+ <span>{t('Conclusion:')}</span>
529
530
  <span className="font-mono text-emerald-950 dark:text-emerald-50 truncate">
530
531
  {node.answer}
531
532
  </span>
@@ -533,7 +534,7 @@ export function SessionOrchestrationFlow({
533
534
  </div>
534
535
  ) : node.error ? (
535
536
  <div className="mt-2 rounded bg-agent-destructive/10 px-1.5 py-1 text-[10px] text-agent-destructive truncate">
536
- 失败: {node.error}
537
+ {t('Failed: {error}', { error: node.error })}
537
538
  </div>
538
539
  ) : null}
539
540
  </div>
@@ -553,13 +554,13 @@ export function SessionOrchestrationFlow({
553
554
  }`}
554
555
  >
555
556
  <LuCircleCheck className="h-3.5 w-3.5 text-emerald-600 dark:text-emerald-400" />
556
- <span>结果汇聚 (Join)</span>
557
+ <span>{t('Result merge (Join)')}</span>
557
558
  <span className="text-[10px] opacity-80">
558
559
  {isAllCompleted
559
- ? '所有分支已汇聚并完成回答'
560
+ ? t('All branches merged and answered')
560
561
  : hasActive
561
- ? '等待分支就绪...'
562
- : '已停止'}
562
+ ? t('Waiting for branches...')
563
+ : t('Stopped')}
563
564
  </span>
564
565
  </div>
565
566
  </div>
@@ -9,6 +9,7 @@ import {
9
9
  LuX,
10
10
  } from 'react-icons/lu';
11
11
  import { useExclusiveExpand } from './composer-status-stack';
12
+ import { t } from '@/i18n';
12
13
  import {
13
14
  summarizeTodos,
14
15
  type SessionTodo,
@@ -45,12 +46,12 @@ export function todoProgressCopy(todos: SessionTodo[]): string {
45
46
  const summary = summarizeTodos(todos);
46
47
  if (summary.total === 0) return '';
47
48
  if (summary.completed === summary.total) {
48
- return `${summary.completed}/${summary.total} 已完成`;
49
+ return t('{completed}/{total} done', { completed: summary.completed, total: summary.total });
49
50
  }
50
51
  const currentIndex = todos.findIndex((todo) => todo.status === 'in_progress');
51
52
  const step =
52
53
  currentIndex >= 0 ? currentIndex + 1 : Math.min(summary.completed + 1, summary.total);
53
- return `执行到 ${step}/${summary.total}`;
54
+ return t('On step {step}/{total}', { step, total: summary.total });
54
55
  }
55
56
 
56
57
  export function TodoItems({
@@ -99,7 +100,7 @@ export function SessionTodoList({ todos }: { todos: SessionTodo[] }) {
99
100
  const progressLabel = useMemo(() => todoProgressCopy(todos), [todos]);
100
101
 
101
102
  const hasActive = useMemo(
102
- () => todos.some((t) => t.status === 'in_progress'),
103
+ () => todos.some((todo) => todo.status === 'in_progress'),
103
104
  [todos],
104
105
  );
105
106
  const isAllCompleted = useMemo(() => {
@@ -177,7 +178,7 @@ export function SessionTodoList({ todos }: { todos: SessionTodo[] }) {
177
178
  <LuListChecks className="h-3.5 w-3.5 shrink-0 text-agent-foreground" />
178
179
  )}
179
180
  <span className="min-w-0 flex-1 truncate font-medium">
180
- {isAllCompleted ? '任务清单' : (current?.content ?? '任务清单')}
181
+ {isAllCompleted ? t('Task list') : (current?.content ?? t('Task list'))}
181
182
  </span>
182
183
  {progressLabel ? (
183
184
  <span
@@ -207,7 +208,7 @@ export function SessionTodoList({ todos }: { todos: SessionTodo[] }) {
207
208
  <div className="flex items-center justify-between border-b border-agent-border/60 bg-agent-muted/30 px-2.5 py-1.5">
208
209
  <div className="flex items-center gap-1.5 text-[11px] font-medium text-agent-foreground">
209
210
  <LuListChecks className="h-3.5 w-3.5 text-agent-foreground" />
210
- <span>任务清单</span>
211
+ <span>{t('Task list')}</span>
211
212
  </div>
212
213
  <div className="flex items-center gap-1.5">
213
214
  {progressLabel && (
@@ -225,8 +226,8 @@ export function SessionTodoList({ todos }: { todos: SessionTodo[] }) {
225
226
  type="button"
226
227
  onClick={() => setUserToggled(false)}
227
228
  className="flex h-5 w-5 items-center justify-center rounded text-agent-muted-foreground transition-colors hover:bg-agent-foreground/10 hover:text-agent-foreground"
228
- title="收起清单"
229
- aria-label="收起清单"
229
+ title={t('Collapse list')}
230
+ aria-label={t('Collapse list')}
230
231
  >
231
232
  <LuX className="h-3 w-3" />
232
233
  </button>
@@ -7,6 +7,7 @@ import {
7
7
  type ChatBranchTreeNode,
8
8
  type ChatBranchTreeResponse,
9
9
  } from '@/lib/local-api';
10
+ import { t } from '@/i18n';
10
11
 
11
12
  /**
12
13
  * SessionTreeModal — pi 式全树分支视图(session tree)。
@@ -129,7 +130,7 @@ export function SessionTreeModal({ chatId, onClose, onBranchSwitched }: SessionT
129
130
  className="fixed inset-0 z-50 flex items-center justify-center bg-black/40 p-4"
130
131
  role="dialog"
131
132
  aria-modal="true"
132
- aria-label="会话分支树"
133
+ aria-label={t('Chat branch tree')}
133
134
  onMouseDown={(e) => {
134
135
  if (e.target === e.currentTarget) onClose();
135
136
  }}
@@ -139,10 +140,10 @@ export function SessionTreeModal({ chatId, onClose, onBranchSwitched }: SessionT
139
140
  <div className="flex items-center gap-2 border-b border-agent-border px-3 py-2">
140
141
  <LuGitBranch className="h-4 w-4 shrink-0 text-agent-muted-foreground" />
141
142
  <span className="min-w-0 flex-1 truncate text-xs font-medium text-agent-foreground">
142
- 会话分支树
143
+ {t('Chat branch tree')}
143
144
  {data && data.nodeCount > 1 && (
144
145
  <span className="ml-1.5 text-xs font-normal text-agent-muted-foreground">
145
- {data.nodeCount} 个分支
146
+ {t('{count} branches', { count: data.nodeCount })}
146
147
  </span>
147
148
  )}
148
149
  </span>
@@ -150,7 +151,7 @@ export function SessionTreeModal({ chatId, onClose, onBranchSwitched }: SessionT
150
151
  type="button"
151
152
  onClick={onClose}
152
153
  className="flex h-6 w-6 shrink-0 items-center justify-center rounded-full text-agent-muted-foreground transition-colors hover:bg-agent-foreground/5 hover:text-agent-foreground"
153
- aria-label="关闭"
154
+ aria-label={t('Close')}
154
155
  >
155
156
  <LuX className="h-3.5 w-3.5" />
156
157
  </button>
@@ -160,11 +161,11 @@ export function SessionTreeModal({ chatId, onClose, onBranchSwitched }: SessionT
160
161
  {loading ? (
161
162
  <div className="flex items-center justify-center gap-2 px-3 py-6 text-xs text-agent-muted-foreground">
162
163
  <LuLoaderCircle className="h-3.5 w-3.5 animate-spin" />
163
- 正在加载分支…
164
+ {t('Loading branches...')}
164
165
  </div>
165
166
  ) : isEmpty ? (
166
167
  <div className="px-3 py-6 text-center text-xs text-agent-muted-foreground">
167
- 暂无分支 — 重新生成回复后,旧版本会保留在这里。
168
+ {t('No branches yet. After you regenerate a reply, the old version is kept here.')}
168
169
  </div>
169
170
  ) : (
170
171
  rows.map((row, index) => {
@@ -194,7 +195,7 @@ export function SessionTreeModal({ chatId, onClose, onBranchSwitched }: SessionT
194
195
  </span>
195
196
  )}
196
197
  <span className="min-w-0 flex-1 truncate">
197
- {row.node.label || '(空分支)'}
198
+ {row.node.label || t('(empty branch)')}
198
199
  </span>
199
200
  {active && (
200
201
  <LuCheck className="h-3 w-3 shrink-0 text-emerald-600 dark:text-emerald-400" />
@@ -208,8 +209,8 @@ export function SessionTreeModal({ chatId, onClose, onBranchSwitched }: SessionT
208
209
  {!loading && !isEmpty && (
209
210
  <div className="border-t border-agent-border px-3 py-1.5 text-[10px] text-agent-muted-foreground/80">
210
211
  {data?.truncated
211
- ? '分支过多,树已被截断(仅显示部分节点)。'
212
- : '点击切换分支;↑↓ 移动,Enter 切换,Esc 关闭。'}
212
+ ? t('Too many branches. The tree is truncated (only some nodes are shown).')
213
+ : t('Click to switch branches. ↑↓ to move, Enter to switch, Esc to close.')}
213
214
  </div>
214
215
  )}
215
216
  </div>
@@ -1,4 +1,5 @@
1
1
  import { motion } from 'framer-motion';
2
+ import { t } from '@/i18n';
2
3
 
3
4
  /**
4
5
  * StreamingStatus — informative placeholder for an assistant bubble that has
@@ -71,12 +72,12 @@ export function StreamingStatus({
71
72
 
72
73
  function selectLabel(round: number, actionCount: number): string {
73
74
  if (round >= 2 && actionCount === 0) {
74
- return `Round ${round} · 继续推理...`;
75
+ return t('Round {round} · Continuing reasoning...', { round });
75
76
  }
76
77
  if (actionCount > 0) {
77
- return `已调用 ${actionCount} 个工具,正在分析...`;
78
+ return t('Called {count} tools, analyzing...', { count: actionCount });
78
79
  }
79
- return '正在思考...';
80
+ return t('Now thinking...');
80
81
  }
81
82
 
82
83
  function Dots() {
@@ -1,4 +1,5 @@
1
1
  import { LuSparkles } from 'react-icons/lu';
2
+ import { t } from '@/i18n';
2
3
 
3
4
  /**
4
5
  * WorkBuddy-style follow-up chips: next-turn user inputs rendered under the
@@ -22,8 +23,8 @@ export function SuggestedReplies({
22
23
  >
23
24
  <div
24
25
  className="mr-0.5 inline-flex items-center select-none"
25
- title="建议"
26
- aria-label="建议"
26
+ title={t('Suggestions')}
27
+ aria-label={t('Suggestions')}
27
28
  >
28
29
  <LuSparkles className="h-3 w-3 text-agent-muted-foreground/80" />
29
30
  </div>
@@ -10,6 +10,7 @@
10
10
  * 不写库。worktree 的合并/丢弃仍归任务弹层——卡片只通知,不重复那套操作。
11
11
  */
12
12
  import { LuCheck, LuGitBranch, LuX } from 'react-icons/lu';
13
+ import { t } from '@/i18n';
13
14
  import type { LocalTask } from '@/lib/local-api';
14
15
 
15
16
  /** 同时挤在消息列尾部的卡片上限;更多的只留一行汇总指回角标。 */
@@ -41,7 +42,9 @@ export function TaskOutcomeCards({ tasks, onInspect, onDismiss }: TaskOutcomeCar
41
42
  ))}
42
43
  {overflow > 0 && (
43
44
  <div className="px-1 text-[11px] text-agent-muted-foreground">
44
- 另有 {overflow} 个后台任务已结束 — 见标题栏的任务按钮。
45
+ {t('{count} more background tasks finished. See the tasks button in the title bar.', {
46
+ count: overflow,
47
+ })}
45
48
  </div>
46
49
  )}
47
50
  </div>
@@ -86,7 +89,7 @@ function TaskOutcomeCard({
86
89
  : 'text-emerald-700 dark:text-emerald-300'
87
90
  }
88
91
  >
89
- {failed ? '后台任务失败:' : '后台任务已完成:'}
92
+ {failed ? t('Background task failed: ') : t('Background task completed: ')}
90
93
  <span className="font-medium">{task.task}</span>
91
94
  </div>
92
95
  {detail && (
@@ -100,7 +103,7 @@ function TaskOutcomeCard({
100
103
  <span className="min-w-0 truncate font-mono text-[10px]">
101
104
  {task.worktreeBranch}
102
105
  </span>
103
- <span>待合并 — 在标题栏的任务按钮里处理。</span>
106
+ <span>{t('Awaiting merge. Handle it from the tasks button in the title bar.')}</span>
104
107
  </div>
105
108
  )}
106
109
  </div>
@@ -111,14 +114,14 @@ function TaskOutcomeCard({
111
114
  onClick={onDismiss}
112
115
  className="rounded px-1.5 py-0.5 text-agent-muted-foreground transition-colors hover:text-agent-foreground"
113
116
  >
114
- 忽略
117
+ {t('Dismiss')}
115
118
  </button>
116
119
  <button
117
120
  type="button"
118
121
  onClick={onInspect}
119
122
  className="rounded-full bg-agent-foreground px-3 py-1 font-medium text-agent-canvas transition hover:opacity-90"
120
123
  >
121
- 查看过程
124
+ {t('View process')}
122
125
  </button>
123
126
  </div>
124
127
  </div>
@@ -19,6 +19,7 @@ import {
19
19
  type LocalTask,
20
20
  } from '@/lib/local-api';
21
21
  import { getElectronBridge } from '@/lib/electron-bridge';
22
+ import { t } from '@/i18n';
22
23
 
23
24
  /**
24
25
  * TaskPanelModal — 跨 turn 后台任务面板(4.6a/4.6c)。
@@ -121,14 +122,14 @@ export function TaskPanelModal({
121
122
  }
122
123
  };
123
124
 
124
- const runningCount = tasks?.filter((t) => t.status === 'running').length ?? 0;
125
+ const runningCount = tasks?.filter((task) => task.status === 'running').length ?? 0;
125
126
 
126
127
  return createPortal(
127
128
  <div
128
129
  className="fixed inset-0 z-50 flex items-center justify-center bg-black/40 p-4"
129
130
  role="dialog"
130
131
  aria-modal="true"
131
- aria-label="后台任务"
132
+ aria-label={t('Background tasks')}
132
133
  onMouseDown={(e) => {
133
134
  if (e.target === e.currentTarget) onClose();
134
135
  }}
@@ -138,10 +139,15 @@ export function TaskPanelModal({
138
139
  <div className="flex items-center gap-2 border-b border-agent-border px-3 py-2">
139
140
  <LuListTodo className="h-4 w-4 shrink-0 text-agent-muted-foreground" />
140
141
  <span className="min-w-0 flex-1 truncate text-xs font-medium text-agent-foreground">
141
- 后台任务
142
+ {t('Background tasks')}
142
143
  {tasks && tasks.length > 0 && (
143
144
  <span className="ml-1.5 text-xs font-normal text-agent-muted-foreground">
144
- {tasks.length} 个任务{runningCount > 0 ? `,${runningCount} 个运行中` : ''}
145
+ {runningCount > 0
146
+ ? t('{count} tasks, {running} running', {
147
+ count: tasks.length,
148
+ running: runningCount,
149
+ })
150
+ : t('{count} tasks', { count: tasks.length })}
145
151
  </span>
146
152
  )}
147
153
  </span>
@@ -149,7 +155,7 @@ export function TaskPanelModal({
149
155
  type="button"
150
156
  onClick={onClose}
151
157
  className="flex h-6 w-6 shrink-0 items-center justify-center rounded-full text-agent-muted-foreground transition-colors hover:bg-agent-foreground/5 hover:text-agent-foreground"
152
- aria-label="关闭"
158
+ aria-label={t('Close')}
153
159
  >
154
160
  <LuX className="h-3.5 w-3.5" />
155
161
  </button>
@@ -159,11 +165,13 @@ export function TaskPanelModal({
159
165
  {loading ? (
160
166
  <div className="flex items-center justify-center gap-2 px-3 py-6 text-xs text-agent-muted-foreground">
161
167
  <LuLoaderCircle className="h-3.5 w-3.5 animate-spin" />
162
- 正在加载任务…
168
+ {t('Loading tasks...')}
163
169
  </div>
164
170
  ) : !tasks || tasks.length === 0 ? (
165
171
  <div className="px-3 py-6 text-center text-xs text-agent-muted-foreground">
166
- 暂无后台任务 — 让助手用 task_run 启动一个跨轮任务后会出现在这里。
172
+ {t(
173
+ 'No background tasks yet. When the assistant starts a cross-turn task with task_run, it appears here.',
174
+ )}
167
175
  </div>
168
176
  ) : (
169
177
  tasks.map((task) => {
@@ -190,7 +198,7 @@ export function TaskPanelModal({
190
198
  type="button"
191
199
  onClick={() => onInspect?.(task)}
192
200
  className="min-w-0 flex-1 truncate text-left text-xs font-medium text-agent-foreground hover:underline"
193
- title="在右侧查看推理过程"
201
+ title={t('View the reasoning process on the right')}
194
202
  >
195
203
  {task.task}
196
204
  </button>
@@ -202,9 +210,9 @@ export function TaskPanelModal({
202
210
  <LuGitBranch className="h-3 w-3" />
203
211
  {task.worktreeBranch}
204
212
  {task.worktreeState === 'merged'
205
- ? ' · 已合并'
213
+ ? ` · ${t('Merged')}`
206
214
  : task.worktreeState === 'discarded'
207
- ? ' · 已丢弃'
215
+ ? ` · ${t('Discarded')}`
208
216
  : ''}
209
217
  </span>
210
218
  )}
@@ -212,7 +220,7 @@ export function TaskPanelModal({
212
220
  type="button"
213
221
  onClick={() => setExpandedId(expanded ? null : task.id)}
214
222
  className="flex h-5 w-5 shrink-0 items-center justify-center rounded text-agent-muted-foreground transition-colors hover:bg-agent-foreground/5 hover:text-agent-foreground"
215
- aria-label={expanded ? '收起' : '展开'}
223
+ aria-label={expanded ? t('Collapse') : t('Expand')}
216
224
  aria-expanded={expanded}
217
225
  >
218
226
  {expanded ? (
@@ -225,12 +233,12 @@ export function TaskPanelModal({
225
233
 
226
234
  <div className="mt-1 pl-5 text-[10px] text-agent-muted-foreground/80">
227
235
  {task.status === 'running'
228
- ? '运行中'
236
+ ? t('In progress')
229
237
  : task.status === 'blocked'
230
- ? '等待依赖'
238
+ ? t('Waiting on dependencies')
231
239
  : task.status === 'completed'
232
- ? '已完成'
233
- : '失败'}
240
+ ? t('Completed')
241
+ : t('Failed')}
234
242
  {' · '}
235
243
  {formatTime(task.updatedAt)}
236
244
  </div>
@@ -250,10 +258,10 @@ export function TaskPanelModal({
250
258
  !task.error && (
251
259
  <div className="text-xs text-agent-muted-foreground">
252
260
  {task.status === 'running'
253
- ? '任务还在运行,完成后结果会出现在这里。'
261
+ ? t('The task is still running. The result appears here when it finishes.')
254
262
  : task.status === 'blocked'
255
- ? '等待依赖任务完成,就绪后会自动开始。'
256
- : '无结果。'}
263
+ ? t('Waiting for dependent tasks to finish. It starts automatically when ready.')
264
+ : t('No result.')}
257
265
  </div>
258
266
  )
259
267
  )}
@@ -271,7 +279,7 @@ export function TaskPanelModal({
271
279
  ) : (
272
280
  <LuGitMerge className="h-3 w-3" />
273
281
  )}
274
- 合并到主仓
282
+ {t('Merge into main repo')}
275
283
  </button>
276
284
  <button
277
285
  type="button"
@@ -281,7 +289,7 @@ export function TaskPanelModal({
281
289
  data-task-discard
282
290
  >
283
291
  <LuTrash2 className="h-3 w-3" />
284
- 丢弃
292
+ {t('Discard')}
285
293
  </button>
286
294
  </div>
287
295
  )}
@@ -299,7 +307,9 @@ export function TaskPanelModal({
299
307
  </div>
300
308
 
301
309
  <div className="border-t border-agent-border px-3 py-1.5 text-[10px] text-agent-muted-foreground/80">
302
- 任务由后台智能体独立执行。点任务名可在右侧看推理过程;Esc 关闭。
310
+ {t(
311
+ 'Background agents run tasks on their own. Click a task name to see its reasoning on the right. Press Esc to close.',
312
+ )}
303
313
  </div>
304
314
  </div>
305
315
  </div>,