@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
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
type GatewayModelCatalog,
|
|
8
8
|
type GatewayModelEntry,
|
|
9
9
|
} from '@/lib/local-api';
|
|
10
|
+
import { t } from '@/i18n';
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* ModelPicker — 聊天输入框的模型/档位选择器。
|
|
@@ -97,11 +98,11 @@ export function ModelPicker({
|
|
|
97
98
|
const statusBadge =
|
|
98
99
|
status === 'offline' ? (
|
|
99
100
|
<span className="inline-flex items-center gap-1 rounded-full border border-amber-400/50 bg-amber-400/10 px-1.5 py-0.5 text-[10px] text-amber-700 dark:text-amber-300">
|
|
100
|
-
|
|
101
|
+
{t('Catalog unavailable')}
|
|
101
102
|
</span>
|
|
102
103
|
) : status === 'stale' ? (
|
|
103
104
|
<span className="inline-flex items-center gap-1 rounded-full border border-agent-border bg-agent-muted px-1.5 py-0.5 text-[10px] text-agent-muted-foreground">
|
|
104
|
-
|
|
105
|
+
{t('Cached')}
|
|
105
106
|
</span>
|
|
106
107
|
) : null;
|
|
107
108
|
|
|
@@ -124,16 +125,20 @@ export function ModelPicker({
|
|
|
124
125
|
className="inline-flex h-6 min-w-0 max-w-[160px] items-center gap-1 rounded-full border border-agent-border bg-agent-canvas px-1.5 text-[12px] leading-[1.45] text-agent-foreground transition-colors hover:bg-agent-foreground/5 disabled:cursor-not-allowed disabled:opacity-70 @sm:max-w-[220px]"
|
|
125
126
|
title={
|
|
126
127
|
status === 'offline'
|
|
127
|
-
?
|
|
128
|
-
|
|
128
|
+
? catalog?.error
|
|
129
|
+
? t('Model catalog unavailable: {error} (you can still enter a model manually)', {
|
|
130
|
+
error: catalog.error,
|
|
131
|
+
})
|
|
132
|
+
: t('Model catalog unavailable (you can still enter a model manually)')
|
|
133
|
+
: t('Current model: {model}', { model: effectiveModel || t('Not configured') })
|
|
129
134
|
}
|
|
130
135
|
aria-haspopup="menu"
|
|
131
136
|
aria-expanded={modelMenuOpen}
|
|
132
137
|
>
|
|
133
|
-
<span className="min-w-0 truncate">{effectiveModel || '
|
|
138
|
+
<span className="min-w-0 truncate">{effectiveModel || t('Choose a model')}</span>
|
|
134
139
|
{model != null && (
|
|
135
140
|
<span className="inline-flex shrink-0 items-center rounded bg-agent-foreground/10 px-1 py-0.5 text-[9px] font-medium text-agent-foreground">
|
|
136
|
-
|
|
141
|
+
{t('This chat')}
|
|
137
142
|
</span>
|
|
138
143
|
)}
|
|
139
144
|
<LuChevronDown className="h-3 w-3 shrink-0 text-agent-muted-foreground" />
|
|
@@ -146,7 +151,7 @@ export function ModelPicker({
|
|
|
146
151
|
>
|
|
147
152
|
<div className="flex items-center justify-between gap-2 px-2 py-1">
|
|
148
153
|
<span className="text-[10px] font-semibold uppercase tracking-wider text-agent-muted-foreground">
|
|
149
|
-
|
|
154
|
+
{t('Models (gateway catalog)')}
|
|
150
155
|
</span>
|
|
151
156
|
<span className="flex items-center gap-1">
|
|
152
157
|
{statusBadge}
|
|
@@ -154,8 +159,8 @@ export function ModelPicker({
|
|
|
154
159
|
type="button"
|
|
155
160
|
onClick={() => void refresh()}
|
|
156
161
|
className="flex h-4 w-4 items-center justify-center rounded text-agent-muted-foreground transition-colors hover:text-agent-foreground"
|
|
157
|
-
title=
|
|
158
|
-
aria-label=
|
|
162
|
+
title={t('Refresh catalog')}
|
|
163
|
+
aria-label={t('Refresh catalog')}
|
|
159
164
|
>
|
|
160
165
|
<LuRefreshCw className={`h-3 w-3 ${loading ? 'animate-spin' : ''}`} />
|
|
161
166
|
</button>
|
|
@@ -168,8 +173,8 @@ export function ModelPicker({
|
|
|
168
173
|
setEffortMenuOpen(false);
|
|
169
174
|
}}
|
|
170
175
|
className="flex h-4 w-4 items-center justify-center rounded text-agent-muted-foreground transition-colors hover:text-agent-foreground"
|
|
171
|
-
title=
|
|
172
|
-
aria-label=
|
|
176
|
+
title={t('LLM settings')}
|
|
177
|
+
aria-label={t('LLM settings')}
|
|
173
178
|
data-testid="chat-llm-settings"
|
|
174
179
|
>
|
|
175
180
|
<LuSettings className="h-3 w-3" />
|
|
@@ -192,7 +197,9 @@ export function ModelPicker({
|
|
|
192
197
|
].join(' ')}
|
|
193
198
|
>
|
|
194
199
|
<span className="min-w-0 flex-1 truncate">
|
|
195
|
-
|
|
200
|
+
{settingsModel
|
|
201
|
+
? t('Follow settings ({model})', { model: settingsModel })
|
|
202
|
+
: t('Follow settings')}
|
|
196
203
|
</span>
|
|
197
204
|
</button>
|
|
198
205
|
{catalog?.models.map((entry) => {
|
|
@@ -236,7 +243,9 @@ export function ModelPicker({
|
|
|
236
243
|
})}
|
|
237
244
|
{catalog != null && catalog.models.length === 0 && (
|
|
238
245
|
<div className="px-2 py-2 text-[11px] text-agent-muted-foreground">
|
|
239
|
-
|
|
246
|
+
{status === 'offline'
|
|
247
|
+
? t('The catalog is empty. The gateway is unreachable, so messages still use the model in Settings.')
|
|
248
|
+
: t('The catalog is empty. The gateway reported no models.')}
|
|
240
249
|
</div>
|
|
241
250
|
)}
|
|
242
251
|
</div>
|
|
@@ -253,11 +262,11 @@ export function ModelPicker({
|
|
|
253
262
|
}}
|
|
254
263
|
disabled={disabled}
|
|
255
264
|
className="inline-flex h-6 items-center gap-1 rounded-full border border-agent-border bg-agent-canvas px-1.5 text-[12px] leading-[1.45] text-agent-foreground transition-colors hover:bg-agent-foreground/5 disabled:cursor-not-allowed disabled:opacity-70"
|
|
256
|
-
title=
|
|
265
|
+
title={t('Reasoning level (sent with every request; an error appears if the model does not support it)')}
|
|
257
266
|
aria-haspopup="menu"
|
|
258
267
|
aria-expanded={effortMenuOpen}
|
|
259
268
|
>
|
|
260
|
-
<span className="truncate">{reasoningEffort ?? '
|
|
269
|
+
<span className="truncate">{reasoningEffort ?? t('Auto')}</span>
|
|
261
270
|
<LuChevronDown className="h-3 w-3 shrink-0 text-agent-muted-foreground" />
|
|
262
271
|
</button>
|
|
263
272
|
{effortMenuOpen && (
|
|
@@ -279,7 +288,7 @@ export function ModelPicker({
|
|
|
279
288
|
: 'text-agent-muted-foreground hover:bg-agent-foreground/5 hover:text-agent-foreground',
|
|
280
289
|
].join(' ')}
|
|
281
290
|
>
|
|
282
|
-
|
|
291
|
+
{t('Auto')}
|
|
283
292
|
</button>
|
|
284
293
|
{reasoningLevels.map((level) => (
|
|
285
294
|
<button
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
type ChildInfo,
|
|
12
12
|
} from './orchestration-children-model';
|
|
13
13
|
import type { LocalChatAgent } from '@/lib/local-api';
|
|
14
|
+
import { t } from '@/i18n';
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* OrchestrationChildrenCard — the desktop surface of the framework's P3.1
|
|
@@ -57,7 +58,7 @@ export function OrchestrationChildrenCard({
|
|
|
57
58
|
<OrchestrationPlanCard
|
|
58
59
|
payload={payload}
|
|
59
60
|
taskStatuses={taskStatuses}
|
|
60
|
-
headerLabel={delegateOnly ? '
|
|
61
|
+
headerLabel={delegateOnly ? t('Subagents') : undefined}
|
|
61
62
|
hideMode={delegateOnly}
|
|
62
63
|
renderTaskRow={(task, status) => {
|
|
63
64
|
// 没有匹配的智能体:编排六件套的子代理(只有 lineage id)或被删掉的
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
type OrchestrationNodeStatus,
|
|
20
20
|
} from './orchestration-flow-model';
|
|
21
21
|
import type { InspectTaskInput } from './executed-actions-model';
|
|
22
|
+
import { t } from '@/i18n';
|
|
22
23
|
|
|
23
24
|
function NodeStatusIcon({
|
|
24
25
|
status,
|
|
@@ -50,20 +51,20 @@ function NodeStatusIcon({
|
|
|
50
51
|
function statusBadgeText(status: OrchestrationNodeStatus): string {
|
|
51
52
|
switch (status) {
|
|
52
53
|
case 'completed':
|
|
53
|
-
return '
|
|
54
|
+
return t('Completed');
|
|
54
55
|
case 'running':
|
|
55
|
-
return '
|
|
56
|
+
return t('Running');
|
|
56
57
|
case 'interrupted':
|
|
57
|
-
return '
|
|
58
|
+
return t('Paused');
|
|
58
59
|
case 'cancelled':
|
|
59
|
-
return '
|
|
60
|
+
return t('Stopped');
|
|
60
61
|
case 'closed':
|
|
61
|
-
return '
|
|
62
|
+
return t('Closed');
|
|
62
63
|
case 'failed':
|
|
63
|
-
return '
|
|
64
|
+
return t('Failed');
|
|
64
65
|
case 'pending':
|
|
65
66
|
default:
|
|
66
|
-
return '
|
|
67
|
+
return t('Waiting');
|
|
67
68
|
}
|
|
68
69
|
}
|
|
69
70
|
|
|
@@ -302,9 +303,9 @@ export function OrchestrationFlowCard({
|
|
|
302
303
|
<LuGitFork className="h-3 w-3" />
|
|
303
304
|
</div>
|
|
304
305
|
<div className="flex min-w-0 items-center gap-1.5 text-xs font-semibold text-agent-foreground">
|
|
305
|
-
<span
|
|
306
|
+
<span>{t('Multi-agent orchestration')}</span>
|
|
306
307
|
<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">
|
|
307
|
-
Fork-Join
|
|
308
|
+
{t('Fork-Join flow')}
|
|
308
309
|
</span>
|
|
309
310
|
</div>
|
|
310
311
|
</div>
|
|
@@ -343,9 +344,9 @@ export function OrchestrationFlowCard({
|
|
|
343
344
|
{/* 1. 起点:目标分发 (Fork) 居中胶囊节点 */}
|
|
344
345
|
<div className="inline-flex items-center gap-1.5 rounded-full border border-violet-500/30 bg-violet-500/10 px-3 py-1 text-xs font-semibold text-violet-800 dark:text-violet-200 shadow-2xs">
|
|
345
346
|
<LuBot className="h-3.5 w-3.5 text-violet-600 dark:text-violet-400" />
|
|
346
|
-
<span
|
|
347
|
+
<span>{t('Main agent goal dispatch (Fork)')}</span>
|
|
347
348
|
<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">
|
|
348
|
-
|
|
349
|
+
{t('{count} parallel branches', { count: nodes.length })}
|
|
349
350
|
</span>
|
|
350
351
|
</div>
|
|
351
352
|
|
|
@@ -370,7 +371,7 @@ export function OrchestrationFlowCard({
|
|
|
370
371
|
<div className="flex items-center gap-1.5 min-w-0">
|
|
371
372
|
<NodeStatusIcon status={node.status} />
|
|
372
373
|
<span className="font-semibold text-agent-foreground text-[11px] truncate">
|
|
373
|
-
|
|
374
|
+
{t('Branch #{index}', { index: index + 1 })}
|
|
374
375
|
</span>
|
|
375
376
|
</div>
|
|
376
377
|
<div className="flex items-center gap-1">
|
|
@@ -390,14 +391,14 @@ export function OrchestrationFlowCard({
|
|
|
390
391
|
chatId,
|
|
391
392
|
recordId: node.recordId,
|
|
392
393
|
live: node.status === 'running',
|
|
393
|
-
title: node.task || '
|
|
394
|
+
title: node.task || t('Subagent run'),
|
|
394
395
|
})
|
|
395
396
|
}
|
|
396
397
|
className="inline-flex shrink-0 items-center gap-0.5 rounded px-1 text-[10px] text-agent-muted-foreground hover:bg-agent-muted hover:text-agent-foreground"
|
|
397
|
-
title=
|
|
398
|
+
title={t('View reasoning process')}
|
|
398
399
|
>
|
|
399
400
|
<LuExternalLink className="h-2.5 w-2.5" />
|
|
400
|
-
<span
|
|
401
|
+
<span>{t('Process')}</span>
|
|
401
402
|
</button>
|
|
402
403
|
) : null}
|
|
403
404
|
</div>
|
|
@@ -415,7 +416,7 @@ export function OrchestrationFlowCard({
|
|
|
415
416
|
}`}
|
|
416
417
|
title={node.task}
|
|
417
418
|
>
|
|
418
|
-
{node.task ||
|
|
419
|
+
{node.task || t('Subtask ({id})', { id: node.childId })}
|
|
419
420
|
</p>
|
|
420
421
|
</div>
|
|
421
422
|
|
|
@@ -437,7 +438,7 @@ export function OrchestrationFlowCard({
|
|
|
437
438
|
<div className="mt-2 rounded bg-emerald-500/10 border border-emerald-500/20 px-2 py-1 text-[11px]">
|
|
438
439
|
<div className="flex items-center gap-1 font-semibold text-emerald-800 dark:text-emerald-200 text-[10px]">
|
|
439
440
|
<LuCheck className="h-3 w-3" />
|
|
440
|
-
<span
|
|
441
|
+
<span>{t('Conclusion:')}</span>
|
|
441
442
|
<span className="font-mono text-emerald-950 dark:text-emerald-50 truncate">
|
|
442
443
|
{node.answer}
|
|
443
444
|
</span>
|
|
@@ -445,7 +446,7 @@ export function OrchestrationFlowCard({
|
|
|
445
446
|
</div>
|
|
446
447
|
) : node.error ? (
|
|
447
448
|
<div className="mt-2 rounded bg-agent-destructive/10 px-1.5 py-1 text-[10px] text-agent-destructive truncate">
|
|
448
|
-
|
|
449
|
+
{t('Failed: {error}', { error: node.error })}
|
|
449
450
|
</div>
|
|
450
451
|
) : null}
|
|
451
452
|
</div>
|
|
@@ -465,13 +466,13 @@ export function OrchestrationFlowCard({
|
|
|
465
466
|
}`}
|
|
466
467
|
>
|
|
467
468
|
<LuCircleCheck className="h-3.5 w-3.5 text-emerald-600 dark:text-emerald-400" />
|
|
468
|
-
<span
|
|
469
|
+
<span>{t('Result merge (Join)')}</span>
|
|
469
470
|
<span className="text-[10px] opacity-80">
|
|
470
471
|
{isAllCompleted
|
|
471
|
-
? '
|
|
472
|
+
? t('All branches merged and answered')
|
|
472
473
|
: hasActive
|
|
473
|
-
? '
|
|
474
|
-
: '
|
|
474
|
+
? t('Waiting for branches...')
|
|
475
|
+
: t('Stopped')}
|
|
475
476
|
</span>
|
|
476
477
|
</div>
|
|
477
478
|
</div>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { LuCircle, LuCircleCheck, LuListChecks } from 'react-icons/lu';
|
|
2
|
+
import { t } from '@/i18n';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* PlanTodoList — renders the ```plan fenced block that the plan-mode skill
|
|
@@ -75,7 +76,7 @@ export function PlanTodoList({ content }: { content: string }) {
|
|
|
75
76
|
<div className="flex items-center gap-2 border-b border-amber-400/30 bg-amber-400/10 px-3 py-2 dark:border-amber-500/20">
|
|
76
77
|
<LuListChecks className="h-4 w-4 shrink-0 text-amber-600 dark:text-amber-400" />
|
|
77
78
|
<span className="flex-1 truncate text-[13px] font-semibold text-amber-800 dark:text-amber-200">
|
|
78
|
-
{title || '
|
|
79
|
+
{title || t('Execution plan')}
|
|
79
80
|
</span>
|
|
80
81
|
{items.length > 0 && (
|
|
81
82
|
<span className="shrink-0 rounded-full bg-amber-400/20 px-1.5 py-0.5 text-[10px] font-medium text-amber-700 dark:text-amber-300">
|
|
@@ -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
|
-
|
|
60
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 ||
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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((
|
|
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 ? '
|
|
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
|
|
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
|
-
: '
|
|
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>
|