@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
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { t } from '@/i18n';
|
|
1
2
|
import type { GatewayModelEntry } from '@/lib/local-api';
|
|
2
3
|
|
|
3
4
|
/**
|
|
@@ -18,17 +19,17 @@ export interface ModelCapabilityChip {
|
|
|
18
19
|
const MODALITY_ORDER = ['image', 'pdf', 'audio', 'video'] as const;
|
|
19
20
|
|
|
20
21
|
const MODALITY_LABELS: Record<(typeof MODALITY_ORDER)[number], string> = {
|
|
21
|
-
image: '
|
|
22
|
+
image: 'Image',
|
|
22
23
|
pdf: 'PDF',
|
|
23
|
-
audio: '
|
|
24
|
-
video: '
|
|
24
|
+
audio: 'Audio',
|
|
25
|
+
video: 'Video',
|
|
25
26
|
};
|
|
26
27
|
|
|
27
28
|
const MODALITY_TITLES: Record<(typeof MODALITY_ORDER)[number], string> = {
|
|
28
|
-
image: '
|
|
29
|
-
pdf: '
|
|
30
|
-
audio: '
|
|
31
|
-
video: '
|
|
29
|
+
image: 'Supports image input',
|
|
30
|
+
pdf: 'Supports PDF input',
|
|
31
|
+
audio: 'Supports audio input',
|
|
32
|
+
video: 'Supports video input',
|
|
32
33
|
};
|
|
33
34
|
|
|
34
35
|
/** 上下文窗口:1_000_000 → 1M,131_072 → 131K。 */
|
|
@@ -45,8 +46,8 @@ export function formatContextWindow(window: number | null | undefined): string |
|
|
|
45
46
|
function unknownChip(): ModelCapabilityChip {
|
|
46
47
|
return {
|
|
47
48
|
key: 'unknown',
|
|
48
|
-
label: '
|
|
49
|
-
title: '
|
|
49
|
+
label: t('Unrecognized'),
|
|
50
|
+
title: t('The gateway catalog has no match for this id. Thinking levels and multimodal input are unverified.'),
|
|
50
51
|
kind: 'unknown',
|
|
51
52
|
};
|
|
52
53
|
}
|
|
@@ -68,8 +69,8 @@ export function modelCapabilityChips(
|
|
|
68
69
|
const joined = levels.join(' / ');
|
|
69
70
|
chips.push({
|
|
70
71
|
key: 'reasoning',
|
|
71
|
-
label: options?.detail ?
|
|
72
|
-
title:
|
|
72
|
+
label: options?.detail ? t('Thinking ({levels})', { levels: joined }) : t('Thinking'),
|
|
73
|
+
title: t('Supported thinking levels: {levels}', { levels: joined }),
|
|
73
74
|
kind: 'reasoning',
|
|
74
75
|
});
|
|
75
76
|
}
|
|
@@ -80,8 +81,8 @@ export function modelCapabilityChips(
|
|
|
80
81
|
seen.add(modality);
|
|
81
82
|
chips.push({
|
|
82
83
|
key: modality,
|
|
83
|
-
label: MODALITY_LABELS[modality],
|
|
84
|
-
title: MODALITY_TITLES[modality],
|
|
84
|
+
label: t(MODALITY_LABELS[modality]),
|
|
85
|
+
title: t(MODALITY_TITLES[modality]),
|
|
85
86
|
kind: 'modality',
|
|
86
87
|
});
|
|
87
88
|
}
|
|
@@ -92,7 +93,7 @@ export function modelCapabilityChips(
|
|
|
92
93
|
chips.push({
|
|
93
94
|
key: key,
|
|
94
95
|
label: modality,
|
|
95
|
-
title:
|
|
96
|
+
title: t('Input modality: {modality}', { modality }),
|
|
96
97
|
kind: 'modality',
|
|
97
98
|
});
|
|
98
99
|
}
|
|
@@ -102,7 +103,7 @@ export function modelCapabilityChips(
|
|
|
102
103
|
chips.push({
|
|
103
104
|
key: 'window',
|
|
104
105
|
label: windowLabel,
|
|
105
|
-
title:
|
|
106
|
+
title: t('Context window {window} tokens', { window: entry.window ?? '' }),
|
|
106
107
|
kind: 'window',
|
|
107
108
|
});
|
|
108
109
|
}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* 载荷之间的转换,不硬编码任何厂商参数。
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import { t } from '@/i18n';
|
|
8
9
|
import type {
|
|
9
10
|
ProviderPresetDescriptor,
|
|
10
11
|
ProviderPresetOverride,
|
|
@@ -28,12 +29,12 @@ export function overrideFromDescriptor(d: ProviderPresetDescriptor): ProviderPre
|
|
|
28
29
|
/** 选择器选项标签:`host + 前缀*`,缺省键名退化为「未命名」。 */
|
|
29
30
|
export function descriptorLabel(d: ProviderPresetDescriptor): string {
|
|
30
31
|
const parts = [d.host, d.modelPrefix ? `${d.modelPrefix}*` : null].filter(Boolean);
|
|
31
|
-
return parts.join(' + ') || '
|
|
32
|
+
return parts.join(' + ') || t('(Unnamed preset)');
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
/** 预制参数的一行摘要,用于生效预览。 */
|
|
35
36
|
export function summarizePreset(p: ProviderPresetOverride | null): string {
|
|
36
|
-
if (!p) return '
|
|
37
|
+
if (!p) return t('No extra sampling parameters are sent');
|
|
37
38
|
const parts: string[] = [];
|
|
38
39
|
if (p.temperature != null) parts.push(`temperature ${p.temperature}`);
|
|
39
40
|
if (p.topP != null) parts.push(`top_p ${p.topP}`);
|
|
@@ -42,7 +43,9 @@ export function summarizePreset(p: ProviderPresetOverride | null): string {
|
|
|
42
43
|
if (p.extraBody) {
|
|
43
44
|
for (const [k, v] of Object.entries(p.extraBody)) parts.push(`${k} ${JSON.stringify(v)}`);
|
|
44
45
|
}
|
|
45
|
-
return parts.length > 0
|
|
46
|
+
return parts.length > 0
|
|
47
|
+
? parts.join(' · ')
|
|
48
|
+
: t('No extra sampling parameters are sent (the best request for this vendor omits sampling fields)');
|
|
46
49
|
}
|
|
47
50
|
|
|
48
51
|
export function overridesEqual(a: ProviderPresetOverride, b: ProviderPresetOverride): boolean {
|
|
@@ -6,11 +6,11 @@
|
|
|
6
6
|
{
|
|
7
7
|
"id": "local-assistant",
|
|
8
8
|
"slug": "local-assistant",
|
|
9
|
-
"name": "
|
|
9
|
+
"name": "Computer operator",
|
|
10
10
|
"icon": "Cpu",
|
|
11
11
|
"color": "#4f46e5",
|
|
12
|
-
"description": "
|
|
13
|
-
"rolePrompt": "
|
|
12
|
+
"description": "Default local assistant. It can use the shell, files, and MCP tools.",
|
|
13
|
+
"rolePrompt": "You are **Computer operator**, a local offline assistant. Answer clearly and in steps that can be carried out.",
|
|
14
14
|
"forbiddenPrompt": null,
|
|
15
15
|
"skillIds": [],
|
|
16
16
|
"toolPolicy": {
|
|
@@ -27,11 +27,11 @@
|
|
|
27
27
|
{
|
|
28
28
|
"id": "data-analyst",
|
|
29
29
|
"slug": "data-analyst",
|
|
30
|
-
"name": "
|
|
30
|
+
"name": "Data analyst",
|
|
31
31
|
"icon": "Activity",
|
|
32
32
|
"color": "#0ea5e9",
|
|
33
|
-
"description": "
|
|
34
|
-
"rolePrompt": "
|
|
33
|
+
"description": "Works on local CSV and log files: cleaning, stats, charts, and a summary.",
|
|
34
|
+
"rolePrompt": "You are **Data analyst**. You clean, summarize, and chart local data files.\nBe concise and lead with the conclusion.\n\nWorking rules:\n1) Inspect the file structure before changing it;\n2) Say what each step did;\n3) Present results as a table or bullets.",
|
|
35
35
|
"forbiddenPrompt": null,
|
|
36
36
|
"skillIds": [],
|
|
37
37
|
"toolPolicy": {
|
|
@@ -48,11 +48,11 @@
|
|
|
48
48
|
{
|
|
49
49
|
"id": "all-round-assistant",
|
|
50
50
|
"slug": "all-round-assistant",
|
|
51
|
-
"name": "
|
|
51
|
+
"name": "Assistant",
|
|
52
52
|
"icon": "🔮",
|
|
53
53
|
"color": "#a855f7",
|
|
54
|
-
"description": "
|
|
55
|
-
"rolePrompt": "
|
|
54
|
+
"description": "General agent. It loads every installed local skill and tool, with no extra trigger.",
|
|
55
|
+
"rolePrompt": "You are **Assistant**, with full access to every installed local skill.\nYou can run local commands (local_exec_shell), read and write files, and analyze data.\nEvery installed local skill and tool is already in your system prompt.\nCall the most direct local tool or script for the task.",
|
|
56
56
|
"forbiddenPrompt": null,
|
|
57
57
|
"skillIds": [],
|
|
58
58
|
"toolPolicy": {
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
"id": "8286e3c8-2d83-409e-930a-3e99b2004d41",
|
|
73
73
|
"projectId": null,
|
|
74
74
|
"userId": "local",
|
|
75
|
-
"title": "
|
|
75
|
+
"title": "Hello",
|
|
76
76
|
"agentId": "local-assistant",
|
|
77
77
|
"createdAt": "2026-07-01T10:05:58.604Z",
|
|
78
78
|
"updatedAt": "2026-07-01T10:06:06.736Z",
|
|
@@ -84,7 +84,7 @@
|
|
|
84
84
|
"id": "8b13a92e-4b3e-4bac-885f-879ddd9e4835",
|
|
85
85
|
"projectId": null,
|
|
86
86
|
"userId": "local",
|
|
87
|
-
"title": "
|
|
87
|
+
"title": "Enable the csv-data-tools skill",
|
|
88
88
|
"agentId": "all-round-assistant",
|
|
89
89
|
"createdAt": "2026-06-22T04:30:54.662Z",
|
|
90
90
|
"updatedAt": "2026-06-22T04:32:12.657Z",
|
|
@@ -96,7 +96,7 @@
|
|
|
96
96
|
"id": "d536659c-6492-424d-97e8-2f701a9ce5d8",
|
|
97
97
|
"projectId": null,
|
|
98
98
|
"userId": "local",
|
|
99
|
-
"title": "
|
|
99
|
+
"title": "New chat",
|
|
100
100
|
"agentId": "all-round-assistant",
|
|
101
101
|
"createdAt": "2026-06-22T04:19:52.607Z",
|
|
102
102
|
"updatedAt": "2026-06-22T04:20:00.151Z",
|
|
@@ -108,7 +108,7 @@
|
|
|
108
108
|
"id": "4845b527-c9c9-467c-9d72-5fc5f8763aa5",
|
|
109
109
|
"projectId": null,
|
|
110
110
|
"userId": "local",
|
|
111
|
-
"title": "
|
|
111
|
+
"title": "New chat",
|
|
112
112
|
"agentId": "all-round-assistant",
|
|
113
113
|
"createdAt": "2026-06-22T04:12:17.151Z",
|
|
114
114
|
"updatedAt": "2026-06-22T04:12:22.038Z",
|
|
@@ -120,7 +120,7 @@
|
|
|
120
120
|
"id": "405f5964-f01c-4ece-9c8f-02a73d539469",
|
|
121
121
|
"projectId": null,
|
|
122
122
|
"userId": "local",
|
|
123
|
-
"title": "
|
|
123
|
+
"title": "Enable the csv-data-tools skill",
|
|
124
124
|
"agentId": "all-round-assistant",
|
|
125
125
|
"createdAt": "2026-06-22T04:06:21.745Z",
|
|
126
126
|
"updatedAt": "2026-06-22T04:11:42.782Z",
|
|
@@ -132,7 +132,7 @@
|
|
|
132
132
|
"id": "19a1d8e7-7e9c-425b-8509-0fe9dd292971",
|
|
133
133
|
"projectId": null,
|
|
134
134
|
"userId": "local",
|
|
135
|
-
"title": "
|
|
135
|
+
"title": "Enable the csv-data-tools skill",
|
|
136
136
|
"agentId": "all-round-assistant",
|
|
137
137
|
"createdAt": "2026-06-22T04:01:21.158Z",
|
|
138
138
|
"updatedAt": "2026-06-22T04:05:08.801Z",
|
|
@@ -144,7 +144,7 @@
|
|
|
144
144
|
"id": "492c1a80-85b2-486a-b365-3d668c9ad7e6",
|
|
145
145
|
"projectId": null,
|
|
146
146
|
"userId": "local",
|
|
147
|
-
"title": "
|
|
147
|
+
"title": "New chat",
|
|
148
148
|
"agentId": "all-round-assistant",
|
|
149
149
|
"createdAt": "2026-06-22T03:44:47.381Z",
|
|
150
150
|
"updatedAt": "2026-06-22T03:44:54.479Z",
|
|
@@ -171,7 +171,7 @@
|
|
|
171
171
|
"id": "ea36a5de-e2e0-428b-b882-f5e67ca326b0",
|
|
172
172
|
"chatId": "8286e3c8-2d83-409e-930a-3e99b2004d41",
|
|
173
173
|
"role": "assistant",
|
|
174
|
-
"content": "
|
|
174
|
+
"content": "Hello. I am the **local assistant**, running on this computer.\n\nI can help with:\n\n**Local terminal**\n- Run shell commands and read or write files\n- Search a codebase and replace in bulk\n\n**Data**\n- Clean CSV and log files\n- Build a summary\n\nType what you want done.",
|
|
175
175
|
"createdAt": "2026-07-01T10:06:06.733Z",
|
|
176
176
|
"messageMetadata": "{\"traceId\":\"tr_269aa1b0f5244962b3279304d6f26323\",\"completionStatus\":\"completed\",\"trace\":{\"traceId\":\"tr_269aa1b0f5244962b3279304d6f26323\",\"sessionId\":\"8286e3c8-2d83-409e-930a-3e99b2004d41\",\"chatId\":\"8286e3c8-2d83-409e-930a-3e99b2004d41\",\"startedAtMs\":1782900364396,\"eventCount\":3,\"spanCount\":1,\"events\":[{\"name\":\"stream_start\",\"timestampMs\":1782900364396,\"data\":{\"path\":\"/api/v2/chats/8286e3c8-2d83-409e-930a-3e99b2004d41/run\",\"method\":\"POST\"}},{\"name\":\"completion_decision\",\"timestampMs\":1782900366733,\"data\":{\"status\":\"completed\",\"reason\":\"assistant produced final response with no pending tools\",\"confidence\":0.85,\"evidence\":[{\"textLength\":218,\"toolCalls\":[],\"toolErrorCount\":0}]}},{\"name\":\"stream_complete\",\"timestampMs\":1782900366733,\"data\":{\"usedTool\":false,\"completionStatus\":\"completed\",\"assistantLength\":218}}],\"spans\":[{\"name\":\"llm_call\",\"stepId\":\"step_1\",\"startMs\":1782900364396,\"endMs\":1782900366733,\"status\":\"ok\",\"data\":{\"turn\":0,\"model\":\"default\",\"tokensUsed\":{\"prompt\":8596,\"completion\":130,\"total\":8726},\"toolCalls\":0},\"durationMs\":2337}]},\"executedActions\":[]}"
|
|
177
177
|
},
|
|
@@ -179,7 +179,7 @@
|
|
|
179
179
|
"id": "ab1387a6-5c43-4d4b-986d-073d98b3f5e2",
|
|
180
180
|
"chatId": "8286e3c8-2d83-409e-930a-3e99b2004d41",
|
|
181
181
|
"role": "user",
|
|
182
|
-
"content": "
|
|
182
|
+
"content": "Hello",
|
|
183
183
|
"createdAt": "2026-07-01T10:06:04.392Z",
|
|
184
184
|
"messageMetadata": null
|
|
185
185
|
}
|
|
@@ -189,7 +189,7 @@
|
|
|
189
189
|
"id": "6599eb84-515c-4f58-bec8-0f37fcfbb234",
|
|
190
190
|
"chatId": "8b13a92e-4b3e-4bac-885f-879ddd9e4835",
|
|
191
191
|
"role": "assistant",
|
|
192
|
-
"content": "
|
|
192
|
+
"content": "I'll look at the modules in `csv-data-tools`.\n\n**`csv-data-tools`** is ready:\n\n---\n\n## csv-data-tools\n\n- `csv_stats` — column stats (rows, missing values, distribution)\n- `csv_filter` — filter and export\n- `csv_plot` — a quick chart\n\nSend a file path to start.",
|
|
193
193
|
"createdAt": "2026-06-22T04:32:11.576Z",
|
|
194
194
|
"messageMetadata": null
|
|
195
195
|
},
|
|
@@ -227,7 +227,7 @@
|
|
|
227
227
|
"id": "c8dc9e96-642b-4232-8c3c-c8abc32a615c",
|
|
228
228
|
"chatId": "405f5964-f01c-4ece-9c8f-02a73d539469",
|
|
229
229
|
"role": "assistant",
|
|
230
|
-
"content": "
|
|
230
|
+
"content": "I'll check the MCP tools for `csv-data-tools`.\n\nIt is not registered as its own MCP server. I'll scan local scripts and executables instead.\n\nIt is not installed. Import it from the skills settings.",
|
|
231
231
|
"createdAt": "2026-06-22T04:11:42.077Z",
|
|
232
232
|
"messageMetadata": null
|
|
233
233
|
},
|
|
@@ -245,7 +245,7 @@
|
|
|
245
245
|
"id": "cfb0f03c-7faa-4594-b29f-f4c11fb09105",
|
|
246
246
|
"chatId": "19a1d8e7-7e9c-425b-8509-0fe9dd292971",
|
|
247
247
|
"role": "assistant",
|
|
248
|
-
"content": "
|
|
248
|
+
"content": "The local model call failed: could not reach the OpenAI-compatible gateway (https://api.deepseek.com). Check baseUrl and the network. Original error: fetch failed",
|
|
249
249
|
"createdAt": "2026-06-22T04:05:06.751Z",
|
|
250
250
|
"messageMetadata": null
|
|
251
251
|
},
|
|
@@ -273,7 +273,7 @@
|
|
|
273
273
|
"id": "c5d6136b-eaa4-4cc7-878b-450099b7d226",
|
|
274
274
|
"chatId": "4e997d62-20ef-4937-a866-27778dee3a17",
|
|
275
275
|
"role": "assistant",
|
|
276
|
-
"content": "
|
|
276
|
+
"content": "I'll check the MCP tools for `csv-data-tools`.\n\nIt is not registered as its own MCP server. I'll scan local scripts and executables instead.\n\nIt is not installed. Import it from the skills settings.",
|
|
277
277
|
"createdAt": "2026-06-22T03:37:37.601Z",
|
|
278
278
|
"messageMetadata": null
|
|
279
279
|
},
|
|
@@ -289,7 +289,7 @@
|
|
|
289
289
|
"id": "fa89030e-5db1-4d04-9320-e796baeb89e2",
|
|
290
290
|
"chatId": "4e997d62-20ef-4937-a866-27778dee3a17",
|
|
291
291
|
"role": "assistant",
|
|
292
|
-
"content": "
|
|
292
|
+
"content": "Hello. I am the **local assistant**, running on this computer.\n\nI can help with:\n\n**Local terminal**\n- Run shell commands and read or write files\n- Search a codebase and replace in bulk\n\n**Data**\n- Clean CSV and log files\n- Build a summary\n\nType what you want done.",
|
|
293
293
|
"createdAt": "2026-06-22T03:34:48.778Z",
|
|
294
294
|
"messageMetadata": "{\"traceId\":\"tr_f53c2142bbd347b5888f9d50c5444478\",\"completionStatus\":\"completed\",\"trace\":{\"traceId\":\"tr_f53c2142bbd347b5888f9d50c5444478\",\"sessionId\":\"4e997d62-20ef-4937-a866-27778dee3a17\",\"chatId\":\"4e997d62-20ef-4937-a866-27778dee3a17\",\"startedAtMs\":1782099286405,\"eventCount\":3,\"spanCount\":1,\"events\":[{\"name\":\"stream_start\",\"timestampMs\":1782099286405,\"data\":{\"path\":\"/api/v2/chats/4e997d62-20ef-4937-a866-27778dee3a17/run\",\"method\":\"POST\"}},{\"name\":\"completion_decision\",\"timestampMs\":1782099288778,\"data\":{\"status\":\"completed\",\"reason\":\"assistant produced final response with no pending tools\",\"confidence\":0.85,\"evidence\":[{\"textLength\":224,\"toolCalls\":[],\"toolErrorCount\":0}]}},{\"name\":\"stream_complete\",\"timestampMs\":1782099288778,\"data\":{\"usedTool\":false,\"completionStatus\":\"completed\",\"assistantLength\":224}}],\"spans\":[{\"name\":\"llm_call\",\"stepId\":\"step_1\",\"startMs\":1782099286405,\"endMs\":1782099288777,\"status\":\"ok\",\"data\":{\"turn\":0,\"model\":\"default\",\"tokensUsed\":{\"prompt\":2234,\"completion\":130,\"total\":2364},\"toolCalls\":0},\"durationMs\":2372}]},\"executedActions\":[]}"
|
|
295
295
|
},
|
|
@@ -309,7 +309,7 @@
|
|
|
309
309
|
"baseUrl": "https://api.deepseek.com",
|
|
310
310
|
"apiKey": "",
|
|
311
311
|
"temperature": 0.3,
|
|
312
|
-
"systemPrompt": "
|
|
312
|
+
"systemPrompt": "You are a local assistant. Prefer advice that can be carried out, and call local tools when that helps."
|
|
313
313
|
},
|
|
314
314
|
"skills": []
|
|
315
315
|
}
|
package/src/hooks/useProjects.ts
CHANGED
|
@@ -51,7 +51,7 @@ export async function refreshProjects(): Promise<void> {
|
|
|
51
51
|
} catch (err) {
|
|
52
52
|
if (seq !== requestSeq) return;
|
|
53
53
|
const message = err instanceof Error ? err.message : String(err);
|
|
54
|
-
console.error('
|
|
54
|
+
console.error('Failed to load projects:', err);
|
|
55
55
|
snapshot = { projects: snapshot.projects, error: message };
|
|
56
56
|
emit();
|
|
57
57
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { t } from './runtime';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 内置智能体的界面名按 id 翻译。库里的 name 仍是种子原文,切换语言不改历史行。
|
|
5
|
+
* 用户自建智能体用库里的名字。
|
|
6
|
+
*/
|
|
7
|
+
const BUILTIN_AGENT_LABELS: Record<string, string> = {
|
|
8
|
+
'local-assistant': 'Computer operator',
|
|
9
|
+
'all-round-assistant': 'Assistant',
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export function agentLabel(agent: { id: string; name: string }): string {
|
|
13
|
+
const source = BUILTIN_AGENT_LABELS[agent.id];
|
|
14
|
+
return source ? t(source) : agent.name;
|
|
15
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { createContext, useContext, useEffect, useMemo, useState, type ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
configureI18n,
|
|
5
|
+
getLocale,
|
|
6
|
+
registerLocale,
|
|
7
|
+
setLocale,
|
|
8
|
+
subscribeI18n,
|
|
9
|
+
supportedLocales,
|
|
10
|
+
t,
|
|
11
|
+
} from './runtime';
|
|
12
|
+
|
|
13
|
+
export { configureI18n, getLocale, registerLocale, setLocale, supportedLocales, t };
|
|
14
|
+
export { LOCALE_STORAGE_KEY, SOURCE_LOCALE, normalizeLocale, resolveLocale } from './translate';
|
|
15
|
+
|
|
16
|
+
interface I18nContextValue {
|
|
17
|
+
locale: string;
|
|
18
|
+
locales: readonly string[];
|
|
19
|
+
t: typeof t;
|
|
20
|
+
setLocale: (locale: string) => void;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const I18nContext = createContext<I18nContextValue | null>(null);
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 包住应用树。语言变化时用 key 重挂子树,这样直接调用 `t()` 的组件也会换文案。
|
|
27
|
+
*/
|
|
28
|
+
export function I18nProvider({ children }: { children: ReactNode }) {
|
|
29
|
+
const [locale, setSnapshot] = useState(getLocale);
|
|
30
|
+
useEffect(() => subscribeI18n(() => setSnapshot(getLocale())), []);
|
|
31
|
+
const value = useMemo<I18nContextValue>(
|
|
32
|
+
() => ({
|
|
33
|
+
locale,
|
|
34
|
+
locales: supportedLocales(),
|
|
35
|
+
t,
|
|
36
|
+
setLocale: (next) => {
|
|
37
|
+
if (!setLocale(next)) return;
|
|
38
|
+
window.electron?.setLocale?.(next);
|
|
39
|
+
},
|
|
40
|
+
}),
|
|
41
|
+
[locale],
|
|
42
|
+
);
|
|
43
|
+
return (
|
|
44
|
+
<I18nContext.Provider value={value}>
|
|
45
|
+
<div key={locale} className="contents">
|
|
46
|
+
{children}
|
|
47
|
+
</div>
|
|
48
|
+
</I18nContext.Provider>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function useI18n(): I18nContextValue {
|
|
53
|
+
const value = useContext(I18nContext);
|
|
54
|
+
if (value) return value;
|
|
55
|
+
return {
|
|
56
|
+
locale: getLocale(),
|
|
57
|
+
locales: supportedLocales(),
|
|
58
|
+
t,
|
|
59
|
+
setLocale,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** 语言名用该语言自己的写法,不跟着界面语言翻。 */
|
|
64
|
+
export function localeEndonym(locale: string): string {
|
|
65
|
+
if (locale === 'zh') return '中文'; // i18n:allow 语言名用该语言自己的写法
|
|
66
|
+
if (locale === 'en') return 'English';
|
|
67
|
+
return locale;
|
|
68
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 渲染进程的当前语言。产品入口在 bootstrap 前 registerLocale,
|
|
3
|
+
* bootstrap 再 configureI18n。未配置时只有英语。
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
LOCALE_STORAGE_KEY,
|
|
8
|
+
SOURCE_LOCALE,
|
|
9
|
+
resolveLocale,
|
|
10
|
+
translate,
|
|
11
|
+
} from './translate';
|
|
12
|
+
|
|
13
|
+
export interface I18nStorage {
|
|
14
|
+
getItem(key: string): string | null;
|
|
15
|
+
setItem(key: string, value: string): void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface I18nState {
|
|
19
|
+
locales: string[];
|
|
20
|
+
defaultLocale: string;
|
|
21
|
+
locale: string;
|
|
22
|
+
catalogs: Record<string, Record<string, string>>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const state: I18nState = {
|
|
26
|
+
locales: [SOURCE_LOCALE],
|
|
27
|
+
defaultLocale: SOURCE_LOCALE,
|
|
28
|
+
locale: SOURCE_LOCALE,
|
|
29
|
+
catalogs: {},
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const listeners = new Set<() => void>();
|
|
33
|
+
|
|
34
|
+
function emit(): void {
|
|
35
|
+
for (const listener of listeners) listener();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function subscribeI18n(listener: () => void): () => void {
|
|
39
|
+
listeners.add(listener);
|
|
40
|
+
return () => {
|
|
41
|
+
listeners.delete(listener);
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function registerLocale(locale: string, messages: Record<string, string>): void {
|
|
46
|
+
const normalized = locale.trim();
|
|
47
|
+
if (!normalized || normalized === SOURCE_LOCALE) return;
|
|
48
|
+
state.catalogs[normalized] = { ...state.catalogs[normalized], ...messages };
|
|
49
|
+
emit();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function readStorage(): I18nStorage | null {
|
|
53
|
+
if (typeof localStorage === 'undefined') return null;
|
|
54
|
+
return localStorage;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function systemLocale(): string | null {
|
|
58
|
+
if (typeof navigator === 'undefined') return null;
|
|
59
|
+
return navigator.language || null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function configureI18n(options: {
|
|
63
|
+
locales: readonly string[];
|
|
64
|
+
defaultLocale: string;
|
|
65
|
+
systemLocale?: string | null;
|
|
66
|
+
storage?: I18nStorage | null;
|
|
67
|
+
}): void {
|
|
68
|
+
const locales = [...new Set(options.locales.filter((locale) => locale.length > 0))];
|
|
69
|
+
state.locales = locales.length > 0 ? locales : [SOURCE_LOCALE];
|
|
70
|
+
state.defaultLocale = options.defaultLocale || SOURCE_LOCALE;
|
|
71
|
+
const storage = options.storage === undefined ? readStorage() : options.storage;
|
|
72
|
+
state.locale = resolveLocale({
|
|
73
|
+
supported: state.locales,
|
|
74
|
+
defaultLocale: state.defaultLocale,
|
|
75
|
+
saved: storage?.getItem(LOCALE_STORAGE_KEY) ?? null,
|
|
76
|
+
system: options.systemLocale === undefined ? systemLocale() : options.systemLocale,
|
|
77
|
+
});
|
|
78
|
+
applyDocumentLang(state.locale);
|
|
79
|
+
emit();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function getLocale(): string {
|
|
83
|
+
return state.locale;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function supportedLocales(): readonly string[] {
|
|
87
|
+
return state.locales;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function setLocale(locale: string): boolean {
|
|
91
|
+
if (!state.locales.includes(locale)) return false;
|
|
92
|
+
if (state.locale === locale) return true;
|
|
93
|
+
state.locale = locale;
|
|
94
|
+
const storage = readStorage();
|
|
95
|
+
storage?.setItem(LOCALE_STORAGE_KEY, locale);
|
|
96
|
+
applyDocumentLang(locale);
|
|
97
|
+
emit();
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function t(message: string, params?: Record<string, string | number>): string {
|
|
102
|
+
return translate(message, state.locale, state.catalogs, params);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function applyDocumentLang(locale: string): void {
|
|
106
|
+
if (typeof document === 'undefined') return;
|
|
107
|
+
document.documentElement.lang = locale === 'zh' ? 'zh-CN' : locale;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** 测试隔离。生产代码不调用。 */
|
|
111
|
+
export function resetI18nForTests(): void {
|
|
112
|
+
state.locales = [SOURCE_LOCALE];
|
|
113
|
+
state.defaultLocale = SOURCE_LOCALE;
|
|
114
|
+
state.locale = SOURCE_LOCALE;
|
|
115
|
+
state.catalogs = {};
|
|
116
|
+
listeners.clear();
|
|
117
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 界面文案查找。源语言是英语:调用处写英文句子,应用层语言包把英文映射到译文。
|
|
3
|
+
* 缺译文时原样返回英文,漏翻在界面上看得见。
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export const SOURCE_LOCALE = 'en';
|
|
7
|
+
export const LOCALE_STORAGE_KEY = 'agent-shell.locale';
|
|
8
|
+
|
|
9
|
+
/** `zh-CN` / `en-US` 收成语言包用的主标签。不认识的标签保留主段。 */
|
|
10
|
+
export function normalizeLocale(raw: string | null | undefined): string | null {
|
|
11
|
+
if (!raw) return null;
|
|
12
|
+
const lower = raw.trim().toLowerCase().replace(/_/g, '-');
|
|
13
|
+
if (!lower) return null;
|
|
14
|
+
if (lower.startsWith('zh')) return 'zh';
|
|
15
|
+
if (lower.startsWith('en')) return 'en';
|
|
16
|
+
const primary = lower.split('-')[0];
|
|
17
|
+
return primary || null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function resolveLocale(options: {
|
|
21
|
+
supported: readonly string[];
|
|
22
|
+
defaultLocale: string;
|
|
23
|
+
saved: string | null;
|
|
24
|
+
system: string | null;
|
|
25
|
+
}): string {
|
|
26
|
+
const supported = options.supported.filter((locale) => locale.length > 0);
|
|
27
|
+
const list = supported.length > 0 ? supported : [SOURCE_LOCALE];
|
|
28
|
+
const pick = (value: string | null): string | null => {
|
|
29
|
+
if (!value) return null;
|
|
30
|
+
if (list.includes(value)) return value;
|
|
31
|
+
const normalized = normalizeLocale(value);
|
|
32
|
+
if (normalized && list.includes(normalized)) return normalized;
|
|
33
|
+
return null;
|
|
34
|
+
};
|
|
35
|
+
return (
|
|
36
|
+
pick(options.saved) ??
|
|
37
|
+
pick(options.system) ??
|
|
38
|
+
pick(options.defaultLocale) ??
|
|
39
|
+
list[0] ??
|
|
40
|
+
SOURCE_LOCALE
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function formatMessage(
|
|
45
|
+
template: string,
|
|
46
|
+
params?: Record<string, string | number>,
|
|
47
|
+
): string {
|
|
48
|
+
if (!params) return template;
|
|
49
|
+
return template.replace(/\{(\w+)\}/g, (whole, name: string) => {
|
|
50
|
+
const value = params[name];
|
|
51
|
+
return value === undefined ? whole : String(value);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** 语言包的键是英文源句。源语言不查表。 */
|
|
56
|
+
export function translate(
|
|
57
|
+
message: string,
|
|
58
|
+
locale: string,
|
|
59
|
+
catalogs: Readonly<Record<string, Readonly<Record<string, string>>>>,
|
|
60
|
+
params?: Record<string, string | number>,
|
|
61
|
+
): string {
|
|
62
|
+
const table = locale === SOURCE_LOCALE ? undefined : catalogs[locale];
|
|
63
|
+
const template = table?.[message] ?? message;
|
|
64
|
+
return formatMessage(template, params);
|
|
65
|
+
}
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
type UseChatsAndAgentsResult,
|
|
18
18
|
} from '@/hooks/useChatsAndAgents';
|
|
19
19
|
import { hostToolChrome, sanitizeRightPanelKind, settingsChrome } from '@/lib/host-tools';
|
|
20
|
+
import { t } from '@/i18n';
|
|
20
21
|
|
|
21
22
|
/**
|
|
22
23
|
* AgentLayout wraps `/agent`, `/agent/:chatId`, and `/` (default) with a
|
|
@@ -560,8 +561,8 @@ function AgentLayoutContent() {
|
|
|
560
561
|
type="button"
|
|
561
562
|
onClick={toggleSidebarCollapsed}
|
|
562
563
|
className="flex h-7 w-7 items-center justify-center rounded-full text-agent-muted-foreground transition-colors duration-200 hover:bg-agent-foreground/5 hover:text-agent-foreground"
|
|
563
|
-
title=
|
|
564
|
-
aria-label=
|
|
564
|
+
title={t('Expand sidebar')}
|
|
565
|
+
aria-label={t('Expand sidebar')}
|
|
565
566
|
>
|
|
566
567
|
<LuPanelLeftOpen className="h-3.5 w-3.5" />
|
|
567
568
|
</button>
|
|
@@ -581,11 +582,11 @@ function AgentLayoutContent() {
|
|
|
581
582
|
<div
|
|
582
583
|
role="separator"
|
|
583
584
|
aria-orientation="vertical"
|
|
584
|
-
aria-label=
|
|
585
|
+
aria-label={t('Resize sidebar')}
|
|
585
586
|
onMouseDown={() => setIsResizing(true)}
|
|
586
587
|
onDoubleClick={resetWidth}
|
|
587
588
|
className="w-1 flex-shrink-0 cursor-col-resize bg-transparent transition-colors hover:bg-agent-border/60"
|
|
588
|
-
title=
|
|
589
|
+
title={t('Drag to resize the sidebar (double-click to reset)')}
|
|
589
590
|
/>
|
|
590
591
|
</>
|
|
591
592
|
)}
|
|
@@ -612,11 +613,11 @@ function AgentLayoutContent() {
|
|
|
612
613
|
<div
|
|
613
614
|
role="separator"
|
|
614
615
|
aria-orientation="vertical"
|
|
615
|
-
aria-label=
|
|
616
|
+
aria-label={t('Resize right panel')}
|
|
616
617
|
onMouseDown={() => setIsTerminalResizing(true)}
|
|
617
618
|
onDoubleClick={resetTerminalWidth}
|
|
618
619
|
className="w-1 flex-shrink-0 cursor-col-resize bg-transparent transition-colors hover:bg-agent-border/60"
|
|
619
|
-
title=
|
|
620
|
+
title={t('Drag to resize the right panel (double-click to reset)')}
|
|
620
621
|
/>
|
|
621
622
|
<div
|
|
622
623
|
className={`h-full flex-shrink-0 overflow-hidden rounded-agent-lg shadow-sm ${
|