@steerable/agent-shell-web 0.6.52 → 0.6.55
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +4 -4
- package/src/brand.ts +1 -1
- package/src/components/AgentSidebar.tsx +60 -52
- package/src/components/ChatHeader.tsx +41 -25
- package/src/components/ConfirmDialog.tsx +4 -3
- package/src/components/CreateProjectModal.tsx +29 -19
- package/src/components/LocalLlmSettingsModal.tsx +4 -3
- package/src/components/SidebarRelease.tsx +2 -1
- package/src/components/TerminalPanel.tsx +3 -2
- package/src/components/TerminalView.tsx +12 -7
- package/src/components/chat/ApprovalModal.tsx +39 -25
- package/src/components/chat/ApprovalPromptProvider.tsx +1 -1
- package/src/components/chat/AskUserPromptProvider.tsx +1 -1
- package/src/components/chat/AskUserQuestionMenu.tsx +19 -14
- package/src/components/chat/AssistantMessage.tsx +18 -13
- package/src/components/chat/ChatInput.tsx +138 -42
- package/src/components/chat/ChatProjectBadge.tsx +24 -19
- package/src/components/chat/EmptyChatHero.tsx +2 -1
- package/src/components/chat/ExecPolicyPicker.tsx +12 -11
- package/src/components/chat/ExecutedActionsCard.tsx +45 -33
- package/src/components/chat/FilePathCode.tsx +8 -3
- package/src/components/chat/InterruptedTurnCard.tsx +4 -3
- package/src/components/chat/LocalChatPanel.tsx +2 -1
- package/src/components/chat/Markdown.tsx +2 -1
- package/src/components/chat/MessageList.tsx +3 -2
- package/src/components/chat/ModelPicker.tsx +25 -16
- package/src/components/chat/OrchestrationChildrenCard.tsx +2 -1
- package/src/components/chat/OrchestrationFlowCard.tsx +23 -22
- package/src/components/chat/PlanTodoList.tsx +2 -1
- package/src/components/chat/ProjectTrustBanner.tsx +19 -5
- package/src/components/chat/SessionOrchestrationFlow.tsx +27 -26
- package/src/components/chat/SessionTodoList.tsx +8 -7
- package/src/components/chat/SessionTreeModal.tsx +10 -9
- package/src/components/chat/StreamingStatus.tsx +4 -3
- package/src/components/chat/SuggestedReplies.tsx +3 -2
- package/src/components/chat/TaskOutcomeCards.tsx +8 -5
- package/src/components/chat/TaskPanelModal.tsx +31 -21
- package/src/components/chat/TaskProcessPanel.tsx +14 -13
- package/src/components/chat/TurnFilesCard.tsx +14 -13
- package/src/components/chat/UserMessage.tsx +12 -11
- package/src/components/chat/executed-actions-model.ts +47 -35
- package/src/components/chat/orchestration-flow-model.ts +16 -5
- package/src/components/chat/process-status.ts +8 -7
- package/src/components/chat/todo-list-model.ts +5 -4
- package/src/components/chat/turn-files.ts +8 -8
- package/src/components/settings/AgentCapabilityFields.tsx +24 -21
- package/src/components/settings/AgentsSettingsPanel.tsx +30 -25
- package/src/components/settings/AppUpdateSettingsPanel.tsx +3 -2
- package/src/components/settings/AppearanceSettingsPanel.tsx +81 -37
- package/src/components/settings/DiagnoseSettingsPanel.tsx +11 -8
- package/src/components/settings/InsightsSettingsPanel.tsx +43 -28
- package/src/components/settings/LlmSettingsPanel.tsx +104 -83
- package/src/components/settings/McpSettingsPanel.tsx +193 -71
- package/src/components/settings/ModelIdCombobox.tsx +2 -1
- package/src/components/settings/OrchestrationSettingsPanel.tsx +8 -5
- package/src/components/settings/PortableSettingsPanel.tsx +83 -55
- package/src/components/settings/PythonRunnerSettingsPanel.tsx +22 -19
- package/src/components/settings/SecuritySettingsPanel.tsx +55 -31
- package/src/components/settings/SettingsNavMenu.tsx +3 -2
- package/src/components/settings/SettingsSaveButton.tsx +2 -1
- package/src/components/settings/SkillsSettingsPanel.tsx +36 -31
- package/src/components/settings/TelemetrySettingsPanel.tsx +15 -12
- package/src/components/settings/UsagePanel.tsx +19 -20
- package/src/components/settings/WebSearchSettingsPanel.tsx +23 -14
- package/src/components/settings/llm-vendors.ts +12 -7
- package/src/components/settings/model-capabilities.ts +16 -15
- package/src/components/settings/preset-choice-model.ts +6 -3
- package/src/fixtures/browser-dev-data.json +24 -24
- package/src/hooks/useProjects.ts +1 -1
- package/src/i18n/agent-label.ts +15 -0
- package/src/i18n/index.tsx +68 -0
- package/src/i18n/runtime.ts +117 -0
- package/src/i18n/translate.ts +65 -0
- package/src/layouts/AgentLayout.tsx +7 -6
- package/src/lib/app-release.ts +15 -10
- package/src/lib/attachments.ts +24 -7
- package/src/lib/browser-dev-electron-mock.ts +34 -34
- package/src/lib/chat-title.ts +3 -2
- package/src/lib/chat-transport.ts +6 -2
- package/src/lib/date-groups.ts +23 -15
- package/src/lib/electron-bridge.ts +19 -6
- package/src/lib/host-file-drop.ts +68 -0
- package/src/lib/host-paste.ts +38 -9
- package/src/lib/http-bridge.ts +4 -1
- package/src/lib/portable.ts +8 -7
- package/src/lib/show-thinking-content.ts +15 -3
- package/src/lib/tauri-bridge.ts +5 -0
- package/src/main.tsx +25 -2
- package/src/pages/AgentPage.tsx +17 -12
- package/src/pages/ChatPanelPreviewPage.tsx +46 -44
- package/src/pages/SettingsPage.tsx +48 -42
- package/src/vite-env.d.ts +3 -0
- package/vite.base.js +6 -2
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
updateProject,
|
|
18
18
|
type LocalProject,
|
|
19
19
|
} from '@/lib/local-api';
|
|
20
|
+
import { t } from '@/i18n';
|
|
20
21
|
|
|
21
22
|
/**
|
|
22
23
|
* 项目徽章 / 选择器(Codex 式 cwd 指示),渲染在输入框上方的 meta 行里,
|
|
@@ -113,8 +114,8 @@ function MenuSectionLabel({
|
|
|
113
114
|
type="button"
|
|
114
115
|
onClick={onAdd}
|
|
115
116
|
className="flex h-5 w-5 items-center justify-center rounded-full text-agent-muted-foreground transition-colors hover:bg-agent-foreground/10 hover:text-agent-foreground"
|
|
116
|
-
title=
|
|
117
|
-
aria-label=
|
|
117
|
+
title={t('New project')}
|
|
118
|
+
aria-label={t('New project')}
|
|
118
119
|
>
|
|
119
120
|
<LuPlus className="h-3 w-3" />
|
|
120
121
|
</button>
|
|
@@ -132,7 +133,7 @@ function CreateProjectMenuItem({ onClick }: { onClick: () => void }) {
|
|
|
132
133
|
className="flex w-full items-center gap-2 rounded px-2 py-1.5 text-left text-xs text-agent-foreground transition-colors hover:bg-agent-foreground/5"
|
|
133
134
|
>
|
|
134
135
|
<LuFolderPlus className="h-3.5 w-3.5 shrink-0 text-agent-muted-foreground" />
|
|
135
|
-
|
|
136
|
+
{t('New project')}
|
|
136
137
|
</button>
|
|
137
138
|
);
|
|
138
139
|
}
|
|
@@ -220,7 +221,7 @@ export function ChatProjectBadge({
|
|
|
220
221
|
run(async () => {
|
|
221
222
|
if (!isElectron() || !project) return;
|
|
222
223
|
const result = await getElectronBridge()!.local?.selectDirectory({
|
|
223
|
-
title:
|
|
224
|
+
title: t('Choose a new folder for "{name}"', { name: project.name }),
|
|
224
225
|
});
|
|
225
226
|
if (!result || result.canceled || result.filePaths.length === 0) return;
|
|
226
227
|
await updateProject(project.id, { folderPath: result.filePaths[0] });
|
|
@@ -230,7 +231,7 @@ export function ChatProjectBadge({
|
|
|
230
231
|
return (
|
|
231
232
|
<div className="relative">
|
|
232
233
|
<BadgeButton
|
|
233
|
-
label={project ? project.name : '
|
|
234
|
+
label={project ? project.name : t('Choose a project')}
|
|
234
235
|
active={Boolean(project)}
|
|
235
236
|
open={open}
|
|
236
237
|
busy={busy}
|
|
@@ -240,8 +241,10 @@ export function ChatProjectBadge({
|
|
|
240
241
|
}}
|
|
241
242
|
title={
|
|
242
243
|
project
|
|
243
|
-
? `${project.folderPath}\n
|
|
244
|
-
:
|
|
244
|
+
? `${project.folderPath}\n${t('Click to manage the project or change its folder')}`
|
|
245
|
+
: t(
|
|
246
|
+
'Link this chat to a project (file writes are limited to the project home folder, its source folders, and their subfolders)',
|
|
247
|
+
)
|
|
245
248
|
}
|
|
246
249
|
/>
|
|
247
250
|
|
|
@@ -249,7 +252,7 @@ export function ChatProjectBadge({
|
|
|
249
252
|
<MenuShell onClose={() => setOpen(false)}>
|
|
250
253
|
{project ? (
|
|
251
254
|
<>
|
|
252
|
-
<MenuSectionLabel
|
|
255
|
+
<MenuSectionLabel>{t('Current project')}</MenuSectionLabel>
|
|
253
256
|
<div
|
|
254
257
|
className="flex items-center gap-2 rounded px-2 py-1.5 text-xs text-agent-foreground"
|
|
255
258
|
title={project.folderPath}
|
|
@@ -268,7 +271,7 @@ export function ChatProjectBadge({
|
|
|
268
271
|
</div>
|
|
269
272
|
</>
|
|
270
273
|
) : (
|
|
271
|
-
<MenuSectionLabel onAdd={openCreate}
|
|
274
|
+
<MenuSectionLabel onAdd={openCreate}>{t('Link to a project')}</MenuSectionLabel>
|
|
272
275
|
)}
|
|
273
276
|
|
|
274
277
|
{otherProjects.length > 0 && (
|
|
@@ -276,7 +279,7 @@ export function ChatProjectBadge({
|
|
|
276
279
|
{project && (
|
|
277
280
|
<div className="mx-1 my-1 border-t border-agent-border/60" />
|
|
278
281
|
)}
|
|
279
|
-
{project && <MenuSectionLabel
|
|
282
|
+
{project && <MenuSectionLabel>{t('Move to')}</MenuSectionLabel>}
|
|
280
283
|
<div className="max-h-40 overflow-y-auto">
|
|
281
284
|
{otherProjects.map((p) => (
|
|
282
285
|
<ProjectMenuRow
|
|
@@ -291,7 +294,7 @@ export function ChatProjectBadge({
|
|
|
291
294
|
{!project && projects.length === 0 && (
|
|
292
295
|
<>
|
|
293
296
|
<div className="px-2 py-1.5 text-[11px] text-agent-muted-foreground/70">
|
|
294
|
-
|
|
297
|
+
{t('No projects yet')}
|
|
295
298
|
</div>
|
|
296
299
|
<CreateProjectMenuItem onClick={openCreate} />
|
|
297
300
|
</>
|
|
@@ -307,7 +310,7 @@ export function ChatProjectBadge({
|
|
|
307
310
|
className="flex w-full items-center gap-2 rounded px-2 py-1.5 text-left text-xs text-agent-foreground transition-colors hover:bg-agent-foreground/5"
|
|
308
311
|
>
|
|
309
312
|
<LuFolderPen className="h-3.5 w-3.5 shrink-0 text-agent-muted-foreground" />
|
|
310
|
-
|
|
313
|
+
{t('Change project folder...')}
|
|
311
314
|
</button>
|
|
312
315
|
<button
|
|
313
316
|
type="button"
|
|
@@ -316,7 +319,7 @@ export function ChatProjectBadge({
|
|
|
316
319
|
className="flex w-full items-center gap-2 rounded px-2 py-1.5 text-left text-xs text-agent-foreground transition-colors hover:bg-agent-foreground/5"
|
|
317
320
|
>
|
|
318
321
|
<LuFolderX className="h-3.5 w-3.5 shrink-0 text-agent-muted-foreground" />
|
|
319
|
-
|
|
322
|
+
{t('Remove from project (becomes a chat without a project)')}
|
|
320
323
|
</button>
|
|
321
324
|
</>
|
|
322
325
|
)}
|
|
@@ -375,21 +378,23 @@ export function ProjectPickerButton({
|
|
|
375
378
|
return (
|
|
376
379
|
<div className="relative">
|
|
377
380
|
<BadgeButton
|
|
378
|
-
label={selected ? selected.name : '
|
|
381
|
+
label={selected ? selected.name : t('Choose a project')}
|
|
379
382
|
active={Boolean(selected)}
|
|
380
383
|
open={open}
|
|
381
384
|
busy={false}
|
|
382
385
|
onClick={() => setOpen((v) => !v)}
|
|
383
386
|
title={
|
|
384
387
|
selected
|
|
385
|
-
? `${selected.folderPath}\n
|
|
386
|
-
:
|
|
388
|
+
? `${selected.folderPath}\n${t('New chats will be linked to this project')}`
|
|
389
|
+
: t(
|
|
390
|
+
'Choose a project for the new chat. Without one, a workspace for this chat is created under Documents/<app name>/conversations/',
|
|
391
|
+
)
|
|
387
392
|
}
|
|
388
393
|
/>
|
|
389
394
|
|
|
390
395
|
{open && (
|
|
391
396
|
<MenuShell onClose={() => setOpen(false)}>
|
|
392
|
-
<MenuSectionLabel onAdd={openCreate}
|
|
397
|
+
<MenuSectionLabel onAdd={openCreate}>{t('Project for new chat')}</MenuSectionLabel>
|
|
393
398
|
<button
|
|
394
399
|
type="button"
|
|
395
400
|
role="menuitem"
|
|
@@ -400,7 +405,7 @@ export function ProjectPickerButton({
|
|
|
400
405
|
className="flex w-full items-center gap-2 rounded px-2 py-1.5 text-left text-xs text-agent-foreground transition-colors hover:bg-agent-foreground/5"
|
|
401
406
|
>
|
|
402
407
|
<LuFolderX className="h-3.5 w-3.5 shrink-0 text-agent-muted-foreground" />
|
|
403
|
-
<span className="min-w-0 flex-1 truncate"
|
|
408
|
+
<span className="min-w-0 flex-1 truncate">{t('No project')}</span>
|
|
404
409
|
{!selected && (
|
|
405
410
|
<LuCheck className="h-3 w-3 shrink-0 text-agent-muted-foreground" />
|
|
406
411
|
)}
|
|
@@ -421,7 +426,7 @@ export function ProjectPickerButton({
|
|
|
421
426
|
{projects.length === 0 && (
|
|
422
427
|
<>
|
|
423
428
|
<div className="px-2 py-1.5 text-[11px] text-agent-muted-foreground/70">
|
|
424
|
-
|
|
429
|
+
{t('No projects yet')}
|
|
425
430
|
</div>
|
|
426
431
|
<CreateProjectMenuItem onClick={openCreate} />
|
|
427
432
|
</>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { BRAND_HOME_HINT, BRAND_NAME, getBrandLogoUrl } from '@/brand';
|
|
2
|
+
import { t } from '@/i18n';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* 新对话落地页与空会话首屏的品牌区:侧栏同一枚 logo,加一句
|
|
@@ -16,7 +17,7 @@ export function EmptyChatHero() {
|
|
|
16
17
|
draggable={false}
|
|
17
18
|
/>
|
|
18
19
|
</h1>
|
|
19
|
-
{hint ? <p className="mt-1.5 text-xs text-agent-muted-foreground">{hint}</p> : null}
|
|
20
|
+
{hint ? <p className="mt-1.5 text-xs text-agent-muted-foreground">{t(hint)}</p> : null}
|
|
20
21
|
</div>
|
|
21
22
|
);
|
|
22
23
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useEffect, useRef, useState } from 'react';
|
|
2
2
|
import { LuChevronDown, LuFolderLock, LuLockOpen } from 'react-icons/lu';
|
|
3
3
|
import type { ExecPolicy } from '@/lib/exec-policy';
|
|
4
|
+
import { t } from '@/i18n';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* ExecPolicyPicker — 输入框上的命令沙箱切换(类 Codex 底部权限档)。
|
|
@@ -17,14 +18,14 @@ const OPTIONS: Array<{
|
|
|
17
18
|
}> = [
|
|
18
19
|
{
|
|
19
20
|
id: 'workspace',
|
|
20
|
-
label: '
|
|
21
|
-
description:
|
|
22
|
-
'只能写入当前工作区,范围外的路径会被拒绝。',
|
|
21
|
+
label: 'Workspace',
|
|
22
|
+
description: 'Can only write to the current workspace. Paths outside it are blocked.',
|
|
23
23
|
},
|
|
24
24
|
{
|
|
25
25
|
id: 'full',
|
|
26
|
-
label: '
|
|
27
|
-
description:
|
|
26
|
+
label: 'Full access',
|
|
27
|
+
description:
|
|
28
|
+
'Turns off the command sandbox so any path on this computer can be written. Tool approvals still apply.',
|
|
28
29
|
},
|
|
29
30
|
];
|
|
30
31
|
|
|
@@ -60,9 +61,9 @@ export function ExecPolicyPicker({
|
|
|
60
61
|
disabled={disabled}
|
|
61
62
|
aria-haspopup="listbox"
|
|
62
63
|
aria-expanded={open}
|
|
63
|
-
aria-label=
|
|
64
|
+
aria-label={t('Command sandbox')}
|
|
64
65
|
data-testid="exec-policy-picker"
|
|
65
|
-
title={current.description}
|
|
66
|
+
title={t(current.description)}
|
|
66
67
|
onClick={() => setOpen((next) => !next)}
|
|
67
68
|
className={[
|
|
68
69
|
'inline-flex h-6 max-w-[140px] items-center gap-1 rounded-full border px-1.5 text-[12px] leading-[1.45] transition-colors disabled:cursor-not-allowed disabled:opacity-70',
|
|
@@ -76,13 +77,13 @@ export function ExecPolicyPicker({
|
|
|
76
77
|
) : (
|
|
77
78
|
<LuFolderLock className="h-3 w-3 shrink-0" />
|
|
78
79
|
)}
|
|
79
|
-
<span className="truncate">{current.label}</span>
|
|
80
|
+
<span className="truncate">{t(current.label)}</span>
|
|
80
81
|
<LuChevronDown className="h-3 w-3 shrink-0 text-agent-muted-foreground" />
|
|
81
82
|
</button>
|
|
82
83
|
{open && (
|
|
83
84
|
<div
|
|
84
85
|
role="listbox"
|
|
85
|
-
aria-label=
|
|
86
|
+
aria-label={t('Command sandbox')}
|
|
86
87
|
className="absolute bottom-full left-0 z-30 mb-1 w-64 overflow-hidden rounded-agent-md border border-agent-border bg-agent-canvas py-1 shadow-sm"
|
|
87
88
|
>
|
|
88
89
|
{OPTIONS.map((option) => {
|
|
@@ -106,10 +107,10 @@ export function ExecPolicyPicker({
|
|
|
106
107
|
].join(' ')}
|
|
107
108
|
>
|
|
108
109
|
<span className="text-[12px] font-medium leading-[1.45] text-agent-foreground">
|
|
109
|
-
{option.label}
|
|
110
|
+
{t(option.label)}
|
|
110
111
|
</span>
|
|
111
112
|
<span className="text-[11px] leading-snug text-agent-muted-foreground">
|
|
112
|
-
{option.description}
|
|
113
|
+
{t(option.description)}
|
|
113
114
|
</span>
|
|
114
115
|
</button>
|
|
115
116
|
);
|
|
@@ -3,6 +3,7 @@ import { LuCircleCheck, LuCircleX, LuListTodo } from 'react-icons/lu';
|
|
|
3
3
|
import { ToolExecutionCard } from '@steerable/agent-ui/cards';
|
|
4
4
|
import type { ToolExecutionPayload } from '@steerable/agent-protocol';
|
|
5
5
|
import type { LocalChatAgent } from '@/lib/local-api';
|
|
6
|
+
import { t } from '@/i18n';
|
|
6
7
|
import { UnifiedDiff } from './UnifiedDiff';
|
|
7
8
|
import {
|
|
8
9
|
summarizeRunCodeAction,
|
|
@@ -117,11 +118,11 @@ function sandboxBadge(sandbox: ExecutedAction['sandbox']): string | null {
|
|
|
117
118
|
if (!sandbox) return null;
|
|
118
119
|
switch (sandbox.enforcement) {
|
|
119
120
|
case 'full':
|
|
120
|
-
return '[
|
|
121
|
+
return t('[Sandboxed]');
|
|
121
122
|
case 'partial':
|
|
122
|
-
return '[
|
|
123
|
+
return t('[Sandboxed · partial]');
|
|
123
124
|
default:
|
|
124
|
-
return '[
|
|
125
|
+
return t('[Not sandboxed]');
|
|
125
126
|
}
|
|
126
127
|
}
|
|
127
128
|
|
|
@@ -252,7 +253,7 @@ function renderDelegateArgs(
|
|
|
252
253
|
{profile ? (
|
|
253
254
|
<section>
|
|
254
255
|
<div className="mb-0.5 text-[10px] uppercase tracking-wider text-agent-muted-foreground">
|
|
255
|
-
|
|
256
|
+
{t('Subagent')}
|
|
256
257
|
</div>
|
|
257
258
|
<div className="text-[11px]">{displayName}</div>
|
|
258
259
|
</section>
|
|
@@ -260,7 +261,7 @@ function renderDelegateArgs(
|
|
|
260
261
|
{task ? (
|
|
261
262
|
<section>
|
|
262
263
|
<div className="mb-0.5 text-[10px] uppercase tracking-wider text-agent-muted-foreground">
|
|
263
|
-
|
|
264
|
+
{t('Task')}
|
|
264
265
|
</div>
|
|
265
266
|
<pre className="whitespace-pre-wrap break-words rounded bg-agent-muted/40 px-2 py-1.5">
|
|
266
267
|
{task}
|
|
@@ -277,7 +278,7 @@ function renderDelegateOutput(output: unknown): ReactNode {
|
|
|
277
278
|
return (
|
|
278
279
|
<div className="space-y-1.5">
|
|
279
280
|
{kvBlock(
|
|
280
|
-
'
|
|
281
|
+
t('Report'),
|
|
281
282
|
<pre className="whitespace-pre-wrap break-words rounded bg-agent-muted/40 px-2 py-1.5">
|
|
282
283
|
{env.message}
|
|
283
284
|
</pre>,
|
|
@@ -286,7 +287,7 @@ function renderDelegateOutput(output: unknown): ReactNode {
|
|
|
286
287
|
);
|
|
287
288
|
}
|
|
288
289
|
if (env.error || env.success === false) {
|
|
289
|
-
return <div className="text-[11px] text-agent-muted-foreground"
|
|
290
|
+
return <div className="text-[11px] text-agent-muted-foreground">{t('No text report')}</div>;
|
|
290
291
|
}
|
|
291
292
|
return renderActionOutput(output);
|
|
292
293
|
}
|
|
@@ -303,11 +304,11 @@ function TaskLead() {
|
|
|
303
304
|
);
|
|
304
305
|
}
|
|
305
306
|
|
|
306
|
-
const
|
|
307
|
-
running: '
|
|
308
|
-
blocked: '
|
|
309
|
-
completed: '
|
|
310
|
-
failed: '
|
|
307
|
+
const TASK_STATUS_LABELS: Record<string, string> = {
|
|
308
|
+
running: 'Started',
|
|
309
|
+
blocked: 'Waiting on dependencies',
|
|
310
|
+
completed: 'Completed',
|
|
311
|
+
failed: 'Failed',
|
|
311
312
|
};
|
|
312
313
|
|
|
313
314
|
function kvBlock(label: string, value: ReactNode): ReactNode {
|
|
@@ -331,17 +332,22 @@ function renderTaskArgs(tool: string, args: unknown): ReactNode {
|
|
|
331
332
|
<div className="space-y-1.5">
|
|
332
333
|
{parsed.task
|
|
333
334
|
? kvBlock(
|
|
334
|
-
'
|
|
335
|
+
t('Task'),
|
|
335
336
|
<pre className="whitespace-pre-wrap break-words rounded bg-agent-muted/40 px-2 py-1.5">
|
|
336
337
|
{parsed.task}
|
|
337
338
|
</pre>,
|
|
338
339
|
)
|
|
339
340
|
: null}
|
|
340
341
|
{parsed.dependsOn.length > 0
|
|
341
|
-
? kvBlock('
|
|
342
|
+
? kvBlock(t('Dependencies'), parsed.dependsOn.map(shortTaskId).join(t(', ')))
|
|
342
343
|
: null}
|
|
343
344
|
{parsed.worktree
|
|
344
|
-
? kvBlock(
|
|
345
|
+
? kvBlock(
|
|
346
|
+
t('Workspace'),
|
|
347
|
+
parsed.worktreeName
|
|
348
|
+
? t('Isolated · {name}', { name: parsed.worktreeName })
|
|
349
|
+
: t('Isolated'),
|
|
350
|
+
)
|
|
345
351
|
: null}
|
|
346
352
|
</div>
|
|
347
353
|
);
|
|
@@ -352,10 +358,10 @@ function renderTaskArgs(tool: string, args: unknown): ReactNode {
|
|
|
352
358
|
if (!taskId && !message) return renderActionOutput(args);
|
|
353
359
|
return (
|
|
354
360
|
<div className="space-y-1.5">
|
|
355
|
-
{taskId ? kvBlock('
|
|
361
|
+
{taskId ? kvBlock(t('ID'), <span className="font-mono">{shortTaskId(taskId)}</span>) : null}
|
|
356
362
|
{message
|
|
357
363
|
? kvBlock(
|
|
358
|
-
'
|
|
364
|
+
t('Message'),
|
|
359
365
|
<pre className="whitespace-pre-wrap break-words rounded bg-agent-muted/40 px-2 py-1.5">
|
|
360
366
|
{message}
|
|
361
367
|
</pre>,
|
|
@@ -375,32 +381,35 @@ function renderTaskOutput(output: unknown): ReactNode {
|
|
|
375
381
|
{parsed.items.map((item, index) => (
|
|
376
382
|
<div key={item.taskId ?? `task-${index}`} className="space-y-1.5">
|
|
377
383
|
{item.taskId
|
|
378
|
-
? kvBlock('
|
|
384
|
+
? kvBlock(t('ID'), <span className="font-mono">{shortTaskId(item.taskId)}</span>)
|
|
379
385
|
: null}
|
|
380
|
-
{item.status ? kvBlock(
|
|
386
|
+
{item.status ? kvBlock(
|
|
387
|
+
t('Status'),
|
|
388
|
+
TASK_STATUS_LABELS[item.status] ? t(TASK_STATUS_LABELS[item.status]) : item.status,
|
|
389
|
+
) : null}
|
|
381
390
|
{item.task
|
|
382
391
|
? kvBlock(
|
|
383
|
-
'
|
|
392
|
+
t('Task'),
|
|
384
393
|
<pre className="whitespace-pre-wrap break-words rounded bg-agent-muted/40 px-2 py-1.5">
|
|
385
394
|
{item.task}
|
|
386
395
|
</pre>,
|
|
387
396
|
)
|
|
388
397
|
: null}
|
|
389
|
-
{item.worktreeLabel ? kvBlock('
|
|
390
|
-
{item.hint ? kvBlock('
|
|
398
|
+
{item.worktreeLabel ? kvBlock(t('Workspace'), item.worktreeLabel) : null}
|
|
399
|
+
{item.hint ? kvBlock(t('Note'), item.hint) : null}
|
|
391
400
|
{item.answer
|
|
392
401
|
? kvBlock(
|
|
393
|
-
'
|
|
402
|
+
t('Result'),
|
|
394
403
|
<pre className="whitespace-pre-wrap break-words rounded bg-agent-muted/40 px-2 py-1.5">
|
|
395
404
|
{item.answer}
|
|
396
405
|
</pre>,
|
|
397
406
|
)
|
|
398
407
|
: null}
|
|
399
|
-
{item.error ? kvBlock('
|
|
408
|
+
{item.error ? kvBlock(t('Reason'), item.error) : null}
|
|
400
409
|
</div>
|
|
401
410
|
))}
|
|
402
411
|
{parsed.hint && parsed.items.every((item) => item.hint !== parsed.hint)
|
|
403
|
-
? kvBlock('
|
|
412
|
+
? kvBlock(t('Note'), parsed.hint)
|
|
404
413
|
: null}
|
|
405
414
|
</div>
|
|
406
415
|
);
|
|
@@ -419,7 +428,7 @@ function toolCardExtras(
|
|
|
419
428
|
const { profile } = parseDelegateSubagent(action.arguments);
|
|
420
429
|
const displayName = delegateSubagentDisplayName(profile, agents);
|
|
421
430
|
return {
|
|
422
|
-
label:
|
|
431
|
+
label: t('Delegate · {name}', { name: displayName }),
|
|
423
432
|
lead: <DelegateLead name={displayName} color={delegateColor(profile, agents)} />,
|
|
424
433
|
renderArgs: (args) => renderDelegateArgs(args, agents),
|
|
425
434
|
renderOutput: renderDelegateOutput,
|
|
@@ -453,16 +462,16 @@ function inspectActivate(
|
|
|
453
462
|
chatId,
|
|
454
463
|
recordId: child.recordId,
|
|
455
464
|
live: child.status === 'running',
|
|
456
|
-
title: task ?? '
|
|
465
|
+
title: task ?? t('Subagent task'),
|
|
457
466
|
}),
|
|
458
|
-
activateTitle: '
|
|
467
|
+
activateTitle: t('View subagent run'),
|
|
459
468
|
};
|
|
460
469
|
}
|
|
461
470
|
const target = inspectableTaskFromAction(action.tool, action.arguments, action.result);
|
|
462
471
|
if (!target) return {};
|
|
463
472
|
return {
|
|
464
473
|
onActivate: () => onInspectTask({ id: target.id, chatId, title: target.title }),
|
|
465
|
-
activateTitle: '
|
|
474
|
+
activateTitle: t('View background run'),
|
|
466
475
|
};
|
|
467
476
|
}
|
|
468
477
|
|
|
@@ -558,15 +567,18 @@ export function ExecutedActionsCard({
|
|
|
558
567
|
<LuCircleX className="h-3.5 w-3.5 text-agent-destructive" />
|
|
559
568
|
)}
|
|
560
569
|
<span className="font-medium text-agent-foreground">
|
|
561
|
-
|
|
570
|
+
{t('Ran {count} actions automatically', { count: expanded.length })}
|
|
562
571
|
</span>
|
|
563
572
|
</div>
|
|
564
573
|
<span className="text-[11px] text-agent-muted-foreground">
|
|
565
574
|
{runningCount > 0
|
|
566
|
-
?
|
|
575
|
+
? t('{count} running', { count: runningCount })
|
|
567
576
|
: failureCount > 0
|
|
568
|
-
?
|
|
569
|
-
|
|
577
|
+
? t('{success} succeeded · {failed} failed', {
|
|
578
|
+
success: successCount,
|
|
579
|
+
failed: failureCount,
|
|
580
|
+
})
|
|
581
|
+
: t('All succeeded')}
|
|
570
582
|
</span>
|
|
571
583
|
</div>
|
|
572
584
|
<div className="space-y-px">
|
|
@@ -3,6 +3,7 @@ import { openLocalPath, type ResolvedLocalPath } from '@/lib/local-api';
|
|
|
3
3
|
import { hostToolChrome } from '@/lib/host-tools';
|
|
4
4
|
import { peekResolvedPath, subscribeResolvedPath } from './path-mentions';
|
|
5
5
|
import { splitTurnFilePath } from './turn-files';
|
|
6
|
+
import { t } from '@/i18n';
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* 行内代码里的文件路径:后端确认存在后变成可点击,点击用系统默认应用打开。
|
|
@@ -48,9 +49,9 @@ export function FilePathCode({ candidate, chatId, children, ...rest }: FilePathC
|
|
|
48
49
|
setOpenError(null);
|
|
49
50
|
try {
|
|
50
51
|
const result = await openLocalPath(resolved.path);
|
|
51
|
-
if (!result.success) setOpenError(result.error || '
|
|
52
|
+
if (!result.success) setOpenError(result.error || t('Failed to open'));
|
|
52
53
|
} catch (err) {
|
|
53
|
-
setOpenError(err instanceof Error ? err.message : '
|
|
54
|
+
setOpenError(err instanceof Error ? err.message : t('Failed to open'));
|
|
54
55
|
}
|
|
55
56
|
};
|
|
56
57
|
|
|
@@ -59,7 +60,11 @@ export function FilePathCode({ candidate, chatId, children, ...rest }: FilePathC
|
|
|
59
60
|
type="button"
|
|
60
61
|
data-file-path-chip=""
|
|
61
62
|
onClick={handleClick}
|
|
62
|
-
title={
|
|
63
|
+
title={
|
|
64
|
+
openError
|
|
65
|
+
? t('{path} ({error})', { path: resolved.path, error: openError })
|
|
66
|
+
: t('Click to open {path}', { path: resolved.path })
|
|
67
|
+
}
|
|
63
68
|
className={`${INLINE_CODE_CLASS} cursor-pointer underline decoration-dotted underline-offset-2 transition-colors hover:bg-agent-accent/15 hover:text-agent-accent ${
|
|
64
69
|
openError ? 'text-red-600 dark:text-red-400' : ''
|
|
65
70
|
}`}
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* 写下的终态,不会进入本卡片:前者是用户的明确意图,后者已有错误展示。
|
|
13
13
|
*/
|
|
14
14
|
import { LuTriangleAlert } from 'react-icons/lu';
|
|
15
|
+
import { t } from '@/i18n';
|
|
15
16
|
|
|
16
17
|
export interface InterruptedTurnCardProps {
|
|
17
18
|
/** 点击「继续上次回复」——触发 resume 流(不再追加用户消息)。 */
|
|
@@ -28,7 +29,7 @@ export function InterruptedTurnCard({ onContinue, onDismiss }: InterruptedTurnCa
|
|
|
28
29
|
>
|
|
29
30
|
<div className="flex items-center gap-2 text-amber-700 dark:text-amber-300">
|
|
30
31
|
<LuTriangleAlert className="h-4 w-4 shrink-0" />
|
|
31
|
-
<span
|
|
32
|
+
<span>{t('The last reply was interrupted because the app quit or crashed, so it did not finish.')}</span>
|
|
32
33
|
</div>
|
|
33
34
|
<div className="flex shrink-0 items-center gap-2">
|
|
34
35
|
<button
|
|
@@ -36,14 +37,14 @@ export function InterruptedTurnCard({ onContinue, onDismiss }: InterruptedTurnCa
|
|
|
36
37
|
onClick={onDismiss}
|
|
37
38
|
className="rounded px-1.5 py-0.5 text-amber-700/80 transition-colors hover:text-amber-900 dark:text-amber-300/80 dark:hover:text-amber-100"
|
|
38
39
|
>
|
|
39
|
-
|
|
40
|
+
{t('Dismiss')}
|
|
40
41
|
</button>
|
|
41
42
|
<button
|
|
42
43
|
type="button"
|
|
43
44
|
onClick={onContinue}
|
|
44
45
|
className="inline-flex items-center gap-1 rounded-full bg-agent-foreground px-3 py-1 font-medium text-agent-canvas transition hover:opacity-90"
|
|
45
46
|
>
|
|
46
|
-
|
|
47
|
+
{t('Continue last reply')}
|
|
47
48
|
</button>
|
|
48
49
|
</div>
|
|
49
50
|
</div>
|
|
@@ -20,6 +20,7 @@ import type { TurnBlock } from './turn-timeline';
|
|
|
20
20
|
import type { TurnFile } from './turn-files';
|
|
21
21
|
import type { LlmSpeedSnapshot } from './process-status';
|
|
22
22
|
import { ComposerStatusStack } from './composer-status-stack';
|
|
23
|
+
import { t } from '@/i18n';
|
|
23
24
|
import { SessionTodoList } from './SessionTodoList';
|
|
24
25
|
import { resolveLatestSessionTodos } from './todo-list-model';
|
|
25
26
|
import { SessionOrchestrationFlow } from './SessionOrchestrationFlow';
|
|
@@ -418,7 +419,7 @@ export function LocalChatPanel({
|
|
|
418
419
|
disabled={disabled}
|
|
419
420
|
placeholder={
|
|
420
421
|
mode === 'plan'
|
|
421
|
-
? '
|
|
422
|
+
? t('Describe your goal. The Agent will make a plan first...')
|
|
422
423
|
: inputPlaceholder
|
|
423
424
|
}
|
|
424
425
|
currentAgent={currentAgent}
|
|
@@ -11,6 +11,7 @@ import ReactMarkdown, { type Components } from 'react-markdown';
|
|
|
11
11
|
import remarkGfm from 'remark-gfm';
|
|
12
12
|
import rehypeRaw from 'rehype-raw';
|
|
13
13
|
import { LuBlocks, LuMessageSquare, LuPlug } from 'react-icons/lu';
|
|
14
|
+
import { t } from '@/i18n';
|
|
14
15
|
import type { LocalChat, LocalChatAgent } from '@/lib/local-api';
|
|
15
16
|
import {
|
|
16
17
|
resolveSlashTool,
|
|
@@ -314,7 +315,7 @@ function renderTextWithMentions(
|
|
|
314
315
|
);
|
|
315
316
|
}
|
|
316
317
|
|
|
317
|
-
const chat = chats.find((c) => (c.title || '
|
|
318
|
+
const chat = chats.find((c) => (c.title || t('Untitled chat')) === name);
|
|
318
319
|
if (chat) {
|
|
319
320
|
return (
|
|
320
321
|
<span
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
import { LuArrowDown } from 'react-icons/lu';
|
|
11
11
|
import type { ChatMessage } from '@steerable/agent-protocol';
|
|
12
12
|
import type { LocalChat, LocalChatAgent, LocalTask } from '@/lib/local-api';
|
|
13
|
+
import { t } from '@/i18n';
|
|
13
14
|
import { UserMessage } from './UserMessage';
|
|
14
15
|
import { AssistantMessage } from './AssistantMessage';
|
|
15
16
|
import { InterruptedTurnCard } from './InterruptedTurnCard';
|
|
@@ -467,8 +468,8 @@ export function MessageList({
|
|
|
467
468
|
scrollToBottom('smooth');
|
|
468
469
|
}}
|
|
469
470
|
className="absolute bottom-2 right-2 flex h-7 w-7 items-center justify-center rounded-full border border-agent-border bg-agent-canvas text-agent-foreground shadow-md transition-colors hover:bg-agent-foreground/5"
|
|
470
|
-
title=
|
|
471
|
-
aria-label=
|
|
471
|
+
title={t('Back to bottom')}
|
|
472
|
+
aria-label={t('Back to bottom')}
|
|
472
473
|
>
|
|
473
474
|
<LuArrowDown className="h-4 w-4" />
|
|
474
475
|
</button>
|