@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.
- 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 +56 -40
- 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 +60 -34
- 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 +16 -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 +4 -5
- 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/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
|
@@ -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>
|
|
@@ -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">
|