@steerable/agent-shell-web 0.6.53 → 0.6.56
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/README.md +3 -4
- package/package.json +4 -4
- package/src/AppShell.tsx +3 -3
- package/src/brand.ts +1 -1
- package/src/components/AgentSidebar.tsx +64 -56
- package/src/components/ChatHeader.tsx +41 -25
- package/src/components/ConfirmDialog.tsx +4 -3
- package/src/components/CreateProjectModal.tsx +31 -21
- package/src/components/LocalLlmSettingsModal.tsx +4 -3
- package/src/components/SidebarRelease.tsx +6 -5
- package/src/components/TerminalPanel.tsx +6 -5
- package/src/components/TerminalView.tsx +15 -10
- package/src/components/chat/ApprovalModal.tsx +40 -26
- package/src/components/chat/ApprovalPromptProvider.tsx +5 -5
- package/src/components/chat/AskUserPromptProvider.tsx +4 -4
- 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 +27 -22
- 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 +33 -23
- package/src/components/chat/TaskProcessPanel.tsx +16 -15
- 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/chat/useChatTasks.ts +3 -3
- package/src/components/settings/AgentCapabilityFields.tsx +26 -23
- package/src/components/settings/AgentsSettingsPanel.tsx +33 -28
- 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 +58 -43
- package/src/components/settings/LlmSettingsPanel.tsx +107 -86
- package/src/components/settings/McpSettingsPanel.tsx +72 -46
- 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 +26 -23
- package/src/components/settings/SecuritySettingsPanel.tsx +58 -34
- package/src/components/settings/SettingsNavMenu.tsx +3 -2
- package/src/components/settings/SettingsSaveButton.tsx +2 -1
- package/src/components/settings/SkillsSettingsPanel.tsx +43 -38
- package/src/components/settings/TelemetrySettingsPanel.tsx +20 -17
- package/src/components/settings/UsagePanel.tsx +22 -23
- package/src/components/settings/WebSearchSettingsPanel.tsx +28 -19
- 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/useChatsAndAgents.ts +9 -9
- package/src/hooks/useProjects.ts +3 -3
- 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 +13 -12
- package/src/lib/app-release.ts +16 -11
- package/src/lib/attachments.ts +18 -9
- package/src/lib/{browser-dev-electron-mock.ts → browser-dev-host-mock.ts} +42 -42
- package/src/lib/chat-title.ts +3 -2
- package/src/lib/chat-transport.ts +16 -12
- package/src/lib/date-groups.ts +23 -15
- package/src/lib/host-bridge.ts +371 -4
- package/src/lib/host-paste.ts +1 -1
- package/src/lib/http-bridge.ts +12 -9
- package/src/lib/insights.ts +2 -2
- package/src/lib/local-api.ts +5 -5
- package/src/lib/portable.ts +11 -10
- package/src/lib/show-thinking-content.ts +15 -3
- package/src/lib/slash-sources.ts +3 -3
- package/src/lib/tauri-bridge.ts +1 -1
- package/src/main.tsx +27 -4
- package/src/pages/AgentPage.tsx +39 -34
- package/src/pages/ChatPanelPreviewPage.tsx +46 -44
- package/src/pages/SettingsPage.tsx +51 -45
- package/src/vite-env.d.ts +3 -0
- package/vite.base.js +6 -2
- package/src/lib/electron-bridge.ts +0 -390
|
@@ -2,6 +2,7 @@ import { useState } from 'react';
|
|
|
2
2
|
import { LuDownload, LuUpload } from 'react-icons/lu';
|
|
3
3
|
import { hostToolChrome, settingsChrome } from '@/lib/host-tools';
|
|
4
4
|
import { BRAND_NAME } from '@/brand';
|
|
5
|
+
import { t } from '@/i18n';
|
|
5
6
|
import {
|
|
6
7
|
applyClientSections,
|
|
7
8
|
fetchChatDocument,
|
|
@@ -20,42 +21,61 @@ import {
|
|
|
20
21
|
} from '@/lib/portable';
|
|
21
22
|
|
|
22
23
|
const EXPORT_SECTIONS: Array<{ id: string; label: string; visible: () => boolean }> = [
|
|
23
|
-
{ id: 'appearance', label: '
|
|
24
|
-
{ id: 'execPolicy', label: '
|
|
25
|
-
{ id: 'llm', label: '
|
|
26
|
-
{ id: 'webSearch', label: '
|
|
27
|
-
{ id: 'insights', label: '
|
|
28
|
-
{ id: 'telemetry', label: '
|
|
29
|
-
{ id: 'mcp', label: 'MCP
|
|
30
|
-
{ id: 'agents', label: '
|
|
31
|
-
{ id: 'skills', label: '
|
|
24
|
+
{ id: 'appearance', label: 'Interface', visible: () => settingsChrome('appearance') },
|
|
25
|
+
{ id: 'execPolicy', label: 'Command permissions', visible: () => hostToolChrome('local-fs') },
|
|
26
|
+
{ id: 'llm', label: 'Local model', visible: () => settingsChrome('llm') },
|
|
27
|
+
{ id: 'webSearch', label: 'Web search', visible: () => settingsChrome('web-search') },
|
|
28
|
+
{ id: 'insights', label: 'Help improve the product', visible: () => settingsChrome('insights') },
|
|
29
|
+
{ id: 'telemetry', label: 'Telemetry', visible: () => settingsChrome('telemetry') },
|
|
30
|
+
{ id: 'mcp', label: 'MCP servers', visible: () => settingsChrome('mcp') },
|
|
31
|
+
{ id: 'agents', label: 'Agents', visible: () => settingsChrome('agents') },
|
|
32
|
+
{ id: 'skills', label: 'Skill references', visible: () => settingsChrome('skills') },
|
|
32
33
|
];
|
|
33
34
|
|
|
34
35
|
function projectClause(chat: { projectName?: string; projectCount?: number }): string | null {
|
|
35
36
|
const count = chat.projectCount ?? 0;
|
|
36
37
|
if (count <= 0) return null;
|
|
37
38
|
if (count === 1 && chat.projectName) {
|
|
38
|
-
return
|
|
39
|
+
return t(
|
|
40
|
+
'Project "{name}" (files in its directory are not included; a project with the same name reuses the one on this computer)',
|
|
41
|
+
{ name: chat.projectName },
|
|
42
|
+
);
|
|
39
43
|
}
|
|
40
|
-
return
|
|
44
|
+
return t(
|
|
45
|
+
'{count} projects (files in their directories are not included; projects with the same name reuse the ones on this computer)',
|
|
46
|
+
{ count },
|
|
47
|
+
);
|
|
41
48
|
}
|
|
42
49
|
|
|
43
50
|
function describeChat(preview: PortablePreview): string {
|
|
44
51
|
const chat = preview.chat;
|
|
45
|
-
if (!chat) return '
|
|
46
|
-
const bits = [
|
|
47
|
-
if (chat.attachmentCount > 0) bits.push(
|
|
48
|
-
if (chat.omittedAttachmentCount > 0)
|
|
49
|
-
|
|
52
|
+
if (!chat) return t('Will be imported as a new chat. Existing chats are not overwritten.');
|
|
53
|
+
const bits = [t('{count} messages', { count: chat.messageCount })];
|
|
54
|
+
if (chat.attachmentCount > 0) bits.push(t('{count} attachments', { count: chat.attachmentCount }));
|
|
55
|
+
if (chat.omittedAttachmentCount > 0) {
|
|
56
|
+
bits.push(t('{count} attachments left out because they are too large', { count: chat.omittedAttachmentCount }));
|
|
57
|
+
}
|
|
58
|
+
if (chat.truncated) bits.push(t('Messages reached the export limit'));
|
|
50
59
|
const project = projectClause(chat);
|
|
51
60
|
if (project) bits.push(project);
|
|
52
|
-
|
|
53
|
-
|
|
61
|
+
const details = bits.join(t(', '));
|
|
62
|
+
if ((chat.count ?? 1) > 1) {
|
|
63
|
+
return t('Will add {count} chats with {details} in total. Existing chats are not overwritten.', {
|
|
64
|
+
count: chat.count ?? 1,
|
|
65
|
+
details,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
return t('Will add chat "{title}": {details}. Existing chats are not overwritten.', {
|
|
69
|
+
title: chat.title,
|
|
70
|
+
details,
|
|
71
|
+
});
|
|
54
72
|
}
|
|
55
73
|
|
|
56
74
|
function describeImport(preview: PortablePreview): string {
|
|
57
|
-
if (preview.includeSecrets) return '
|
|
58
|
-
return
|
|
75
|
+
if (preview.includeSecrets) return t('This config includes API keys. Importing writes them to this computer.');
|
|
76
|
+
return t(
|
|
77
|
+
'Checked settings will be overwritten. Sections not in the file stay unchanged. Keys are not in the package by default, so you need to fill them in after importing.',
|
|
78
|
+
);
|
|
59
79
|
}
|
|
60
80
|
|
|
61
81
|
/**
|
|
@@ -113,7 +133,7 @@ export function PortableSettingsPanel() {
|
|
|
113
133
|
const confirmExportChats = async () => {
|
|
114
134
|
const chosen = chatRows.filter((row) => chatIds.has(row.id));
|
|
115
135
|
if (chosen.length === 0) {
|
|
116
|
-
setError('
|
|
136
|
+
setError(t('Select at least one chat'));
|
|
117
137
|
return;
|
|
118
138
|
}
|
|
119
139
|
setBusy(true);
|
|
@@ -121,13 +141,13 @@ export function PortableSettingsPanel() {
|
|
|
121
141
|
try {
|
|
122
142
|
const chats = [];
|
|
123
143
|
for (const row of chosen) chats.push(await fetchChatDocument(row.id));
|
|
124
|
-
const saved = await saveJsonFile(safeDownloadName(BRAND_NAME, '
|
|
144
|
+
const saved = await saveJsonFile(safeDownloadName(BRAND_NAME, t('Chats')), {
|
|
125
145
|
kind: 'steerable-chats',
|
|
126
146
|
schemaVersion: 1,
|
|
127
147
|
exportedAt: new Date().toISOString(),
|
|
128
148
|
chats,
|
|
129
149
|
});
|
|
130
|
-
setStatus(saved ?
|
|
150
|
+
setStatus(saved ? t('Saved to {path}', { path: saved }) : t('Canceled'));
|
|
131
151
|
if (saved) setMode('idle');
|
|
132
152
|
} catch (err) {
|
|
133
153
|
setError(portableErrorMessage(err));
|
|
@@ -151,11 +171,11 @@ export function PortableSettingsPanel() {
|
|
|
151
171
|
const fetched = await fetchConfigDocument(includeSecrets);
|
|
152
172
|
const finished = finishConfigDocument(fetched, selected);
|
|
153
173
|
if (Object.keys(finished.sections).length === 0) {
|
|
154
|
-
setError('
|
|
174
|
+
setError(t('Nothing to export'));
|
|
155
175
|
return;
|
|
156
176
|
}
|
|
157
|
-
const saved = await saveJsonFile(safeDownloadName(BRAND_NAME, '
|
|
158
|
-
setStatus(saved ?
|
|
177
|
+
const saved = await saveJsonFile(safeDownloadName(BRAND_NAME, t('Config')), finished);
|
|
178
|
+
setStatus(saved ? t('Saved to {path}', { path: saved }) : t('Canceled'));
|
|
159
179
|
if (saved) setMode('idle');
|
|
160
180
|
} catch (err) {
|
|
161
181
|
setError(portableErrorMessage(err));
|
|
@@ -182,14 +202,14 @@ export function PortableSettingsPanel() {
|
|
|
182
202
|
setPreview(null);
|
|
183
203
|
setDocument(null);
|
|
184
204
|
setMode('idle');
|
|
185
|
-
setError('
|
|
205
|
+
setError(t('This is a config package. Use "Import config".'));
|
|
186
206
|
return;
|
|
187
207
|
}
|
|
188
208
|
if (target === 'config' && isChat) {
|
|
189
209
|
setPreview(null);
|
|
190
210
|
setDocument(null);
|
|
191
211
|
setMode('idle');
|
|
192
|
-
setError('
|
|
212
|
+
setError(t('This is a chat package. Use "Import chats".'));
|
|
193
213
|
return;
|
|
194
214
|
}
|
|
195
215
|
setDocument(parsed.value);
|
|
@@ -222,13 +242,17 @@ export function PortableSettingsPanel() {
|
|
|
222
242
|
const appliedLabels = sections
|
|
223
243
|
.filter((section) => section.clientOnly || result.applied.includes(section.id))
|
|
224
244
|
.map((section) => section.label);
|
|
225
|
-
const parts = [
|
|
245
|
+
const parts = [
|
|
246
|
+
appliedLabels.length > 0
|
|
247
|
+
? t('Imported: {sections}', { sections: appliedLabels.join(t(', ')) })
|
|
248
|
+
: t('Imported'),
|
|
249
|
+
];
|
|
226
250
|
for (const item of result.skipped) parts.push(item.reason);
|
|
227
251
|
for (const note of result.notes) parts.push(note);
|
|
228
252
|
if (result.missingSkills.length > 0) {
|
|
229
|
-
parts.push(
|
|
253
|
+
parts.push(t('These skills are not on this computer: {skills}', { skills: result.missingSkills.join(t(', ')) }));
|
|
230
254
|
}
|
|
231
|
-
setStatus(parts.join('
|
|
255
|
+
setStatus(parts.join(t('. ')));
|
|
232
256
|
setMode('idle');
|
|
233
257
|
setPreview(null);
|
|
234
258
|
setDocument(null);
|
|
@@ -246,7 +270,7 @@ export function PortableSettingsPanel() {
|
|
|
246
270
|
try {
|
|
247
271
|
const result = await importChatDocument(document);
|
|
248
272
|
const count = preview.chat?.count ?? 1;
|
|
249
|
-
setStatus(count > 1 ?
|
|
273
|
+
setStatus(count > 1 ? t('Imported {count} chats', { count }) : t('Imported chat "{title}"', { title: result.title }));
|
|
250
274
|
setMode('idle');
|
|
251
275
|
setPreview(null);
|
|
252
276
|
setDocument(null);
|
|
@@ -269,7 +293,9 @@ export function PortableSettingsPanel() {
|
|
|
269
293
|
data-testid="portable-settings-panel"
|
|
270
294
|
>
|
|
271
295
|
<p className="text-xs leading-relaxed text-agent-muted-foreground">
|
|
272
|
-
|
|
296
|
+
{t(
|
|
297
|
+
'Export your config or chat history, take it to another computer, and import it there. Chats are exported with their projects, without the files in project directories. Chats are imported as new chats and do not overwrite existing history. API keys are not included by default.',
|
|
298
|
+
)}
|
|
273
299
|
</p>
|
|
274
300
|
<div className="flex flex-wrap gap-1.5">
|
|
275
301
|
<button
|
|
@@ -279,7 +305,7 @@ export function PortableSettingsPanel() {
|
|
|
279
305
|
data-testid="portable-export-config"
|
|
280
306
|
>
|
|
281
307
|
<LuDownload className="h-3.5 w-3.5" />
|
|
282
|
-
|
|
308
|
+
{t('Export config')}
|
|
283
309
|
</button>
|
|
284
310
|
<button
|
|
285
311
|
type="button"
|
|
@@ -289,7 +315,7 @@ export function PortableSettingsPanel() {
|
|
|
289
315
|
data-testid="portable-export-chats"
|
|
290
316
|
>
|
|
291
317
|
<LuDownload className="h-3.5 w-3.5" />
|
|
292
|
-
|
|
318
|
+
{t('Export chats')}
|
|
293
319
|
</button>
|
|
294
320
|
<button
|
|
295
321
|
type="button"
|
|
@@ -299,7 +325,7 @@ export function PortableSettingsPanel() {
|
|
|
299
325
|
data-testid="portable-import-config"
|
|
300
326
|
>
|
|
301
327
|
<LuUpload className="h-3.5 w-3.5" />
|
|
302
|
-
|
|
328
|
+
{t('Import config')}
|
|
303
329
|
</button>
|
|
304
330
|
<button
|
|
305
331
|
type="button"
|
|
@@ -309,13 +335,13 @@ export function PortableSettingsPanel() {
|
|
|
309
335
|
data-testid="portable-import-chats"
|
|
310
336
|
>
|
|
311
337
|
<LuUpload className="h-3.5 w-3.5" />
|
|
312
|
-
|
|
338
|
+
{t('Import chats')}
|
|
313
339
|
</button>
|
|
314
340
|
</div>
|
|
315
341
|
|
|
316
342
|
{mode === 'export' && (
|
|
317
343
|
<fieldset className="space-y-1.5" data-testid="portable-export-form">
|
|
318
|
-
<legend className="text-xs font-medium text-agent-foreground"
|
|
344
|
+
<legend className="text-xs font-medium text-agent-foreground">{t('Sections to export')}</legend>
|
|
319
345
|
{visible.map((item) => (
|
|
320
346
|
<label key={item.id} className="flex items-center gap-1.5 text-xs text-agent-foreground">
|
|
321
347
|
<input
|
|
@@ -324,7 +350,7 @@ export function PortableSettingsPanel() {
|
|
|
324
350
|
onChange={() => toggle(item.id)}
|
|
325
351
|
data-testid={`portable-section-${item.id}`}
|
|
326
352
|
/>
|
|
327
|
-
{item.label}
|
|
353
|
+
{t(item.label)}
|
|
328
354
|
</label>
|
|
329
355
|
))}
|
|
330
356
|
<label className="flex items-center gap-1.5 text-xs text-agent-foreground">
|
|
@@ -334,10 +360,10 @@ export function PortableSettingsPanel() {
|
|
|
334
360
|
onChange={(event) => setIncludeSecrets(event.target.checked)}
|
|
335
361
|
data-testid="portable-include-secrets"
|
|
336
362
|
/>
|
|
337
|
-
|
|
363
|
+
{t('Include API keys')}
|
|
338
364
|
</label>
|
|
339
365
|
<p className="text-[11px] leading-relaxed text-agent-muted-foreground">
|
|
340
|
-
|
|
366
|
+
{t('When checked, the file is as sensitive as a key. Do not send it to anyone or commit it to a repository.')}
|
|
341
367
|
</p>
|
|
342
368
|
<div className="flex gap-1.5">
|
|
343
369
|
<button
|
|
@@ -347,14 +373,14 @@ export function PortableSettingsPanel() {
|
|
|
347
373
|
className="h-7 rounded-full bg-agent-foreground px-2.5 text-xs text-agent-canvas disabled:opacity-60"
|
|
348
374
|
data-testid="portable-export-confirm"
|
|
349
375
|
>
|
|
350
|
-
{busy ? '
|
|
376
|
+
{busy ? t('Exporting') : t('Save file')}
|
|
351
377
|
</button>
|
|
352
378
|
<button
|
|
353
379
|
type="button"
|
|
354
380
|
onClick={cancel}
|
|
355
381
|
className="h-7 rounded-full px-2.5 text-xs text-agent-muted-foreground hover:bg-agent-foreground/5"
|
|
356
382
|
>
|
|
357
|
-
|
|
383
|
+
{t('Cancel')}
|
|
358
384
|
</button>
|
|
359
385
|
</div>
|
|
360
386
|
</fieldset>
|
|
@@ -362,14 +388,16 @@ export function PortableSettingsPanel() {
|
|
|
362
388
|
|
|
363
389
|
{mode === 'export-chats' && (
|
|
364
390
|
<fieldset className="space-y-1.5" data-testid="portable-export-chats-form">
|
|
365
|
-
<legend className="text-xs font-medium text-agent-foreground"
|
|
391
|
+
<legend className="text-xs font-medium text-agent-foreground">{t('Chats to export')}</legend>
|
|
366
392
|
<p className="text-[11px] leading-relaxed text-agent-muted-foreground">
|
|
367
|
-
|
|
393
|
+
{t(
|
|
394
|
+
'The projects these chats belong to are written into the file too, keeping only their names and directory locations. Files in the directories are not packaged.',
|
|
395
|
+
)}
|
|
368
396
|
</p>
|
|
369
397
|
{busy && chatRows.length === 0 ? (
|
|
370
|
-
<p className="text-xs text-agent-muted-foreground"
|
|
398
|
+
<p className="text-xs text-agent-muted-foreground">{t('Loading chats…')}</p>
|
|
371
399
|
) : chatRows.length === 0 ? (
|
|
372
|
-
<p className="text-xs text-agent-muted-foreground"
|
|
400
|
+
<p className="text-xs text-agent-muted-foreground">{t('No chats yet.')}</p>
|
|
373
401
|
) : (
|
|
374
402
|
<div className="max-h-48 space-y-1 overflow-y-auto">
|
|
375
403
|
<label className="flex items-center gap-1.5 text-xs text-agent-foreground">
|
|
@@ -381,7 +409,7 @@ export function PortableSettingsPanel() {
|
|
|
381
409
|
}}
|
|
382
410
|
data-testid="portable-chats-all"
|
|
383
411
|
/>
|
|
384
|
-
|
|
412
|
+
{t('All ({count})', { count: chatRows.length })}
|
|
385
413
|
</label>
|
|
386
414
|
{chatRows.map((row) => (
|
|
387
415
|
<label key={row.id} className="flex items-center gap-1.5 text-xs text-agent-foreground">
|
|
@@ -404,14 +432,14 @@ export function PortableSettingsPanel() {
|
|
|
404
432
|
className="h-7 rounded-full bg-agent-foreground px-2.5 text-xs text-agent-canvas disabled:opacity-60"
|
|
405
433
|
data-testid="portable-export-chats-confirm"
|
|
406
434
|
>
|
|
407
|
-
{busy ? '
|
|
435
|
+
{busy ? t('Exporting') : t('Save file')}
|
|
408
436
|
</button>
|
|
409
437
|
<button
|
|
410
438
|
type="button"
|
|
411
439
|
onClick={cancel}
|
|
412
440
|
className="h-7 rounded-full px-2.5 text-xs text-agent-muted-foreground hover:bg-agent-foreground/5"
|
|
413
441
|
>
|
|
414
|
-
|
|
442
|
+
{t('Cancel')}
|
|
415
443
|
</button>
|
|
416
444
|
</div>
|
|
417
445
|
</fieldset>
|
|
@@ -428,14 +456,14 @@ export function PortableSettingsPanel() {
|
|
|
428
456
|
className="h-7 rounded-full bg-agent-foreground px-2.5 text-xs text-agent-canvas disabled:opacity-60"
|
|
429
457
|
data-testid="portable-import-chat-confirm"
|
|
430
458
|
>
|
|
431
|
-
{busy ? '
|
|
459
|
+
{busy ? t('Importing') : t('Import chats')}
|
|
432
460
|
</button>
|
|
433
461
|
<button
|
|
434
462
|
type="button"
|
|
435
463
|
onClick={cancel}
|
|
436
464
|
className="h-7 rounded-full px-2.5 text-xs text-agent-muted-foreground hover:bg-agent-foreground/5"
|
|
437
465
|
>
|
|
438
|
-
|
|
466
|
+
{t('Cancel')}
|
|
439
467
|
</button>
|
|
440
468
|
</div>
|
|
441
469
|
</div>
|
|
@@ -443,7 +471,7 @@ export function PortableSettingsPanel() {
|
|
|
443
471
|
|
|
444
472
|
{mode === 'import' && preview && (
|
|
445
473
|
<fieldset className="space-y-1.5" data-testid="portable-import-form">
|
|
446
|
-
<legend className="text-xs font-medium text-agent-foreground"
|
|
474
|
+
<legend className="text-xs font-medium text-agent-foreground">{t('Sections to import')}</legend>
|
|
447
475
|
<p className="text-[11px] leading-relaxed text-agent-muted-foreground">{describeImport(preview)}</p>
|
|
448
476
|
{preview.sections.map((section) => (
|
|
449
477
|
<label key={section.id} className="flex items-center gap-1.5 text-xs text-agent-foreground">
|
|
@@ -465,14 +493,14 @@ export function PortableSettingsPanel() {
|
|
|
465
493
|
className="h-7 rounded-full bg-agent-foreground px-2.5 text-xs text-agent-canvas disabled:opacity-60"
|
|
466
494
|
data-testid="portable-import-confirm"
|
|
467
495
|
>
|
|
468
|
-
{busy ? '
|
|
496
|
+
{busy ? t('Importing') : t('Import config')}
|
|
469
497
|
</button>
|
|
470
498
|
<button
|
|
471
499
|
type="button"
|
|
472
500
|
onClick={cancel}
|
|
473
501
|
className="h-7 rounded-full px-2.5 text-xs text-agent-muted-foreground hover:bg-agent-foreground/5"
|
|
474
502
|
>
|
|
475
|
-
|
|
503
|
+
{t('Cancel')}
|
|
476
504
|
</button>
|
|
477
505
|
</div>
|
|
478
506
|
</fieldset>
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { useEffect, useState } from 'react';
|
|
2
2
|
import { LuCodeXml } from 'react-icons/lu';
|
|
3
|
+
import { t } from '@/i18n';
|
|
3
4
|
import {
|
|
4
|
-
|
|
5
|
+
getHostBridge,
|
|
5
6
|
type PythonRunnerSnapshot,
|
|
6
|
-
} from '@/lib/
|
|
7
|
+
} from '@/lib/host-bridge';
|
|
7
8
|
|
|
8
9
|
type Source = PythonRunnerSnapshot['source'];
|
|
9
10
|
|
|
@@ -19,7 +20,7 @@ export function PythonRunnerSettingsPanel() {
|
|
|
19
20
|
const [localPath, setLocalPath] = useState('');
|
|
20
21
|
|
|
21
22
|
useEffect(() => {
|
|
22
|
-
const runner =
|
|
23
|
+
const runner = getHostBridge()?.pythonRunner;
|
|
23
24
|
if (!runner) return;
|
|
24
25
|
let alive = true;
|
|
25
26
|
void runner.snapshot().then((next) => {
|
|
@@ -38,7 +39,7 @@ export function PythonRunnerSettingsPanel() {
|
|
|
38
39
|
}, []);
|
|
39
40
|
|
|
40
41
|
if (!snapshot?.supported) return null;
|
|
41
|
-
const runner =
|
|
42
|
+
const runner = getHostBridge()?.pythonRunner;
|
|
42
43
|
const busy =
|
|
43
44
|
snapshot.phase === 'downloading'
|
|
44
45
|
|| snapshot.phase === 'verifying'
|
|
@@ -72,33 +73,35 @@ export function PythonRunnerSettingsPanel() {
|
|
|
72
73
|
|
|
73
74
|
const actionLabel =
|
|
74
75
|
source === 'local'
|
|
75
|
-
? '
|
|
76
|
+
? t('Use this Python')
|
|
76
77
|
: source === 'url'
|
|
77
|
-
? '
|
|
78
|
-
: '
|
|
78
|
+
? t('Download from this URL')
|
|
79
|
+
: t('Download default runner');
|
|
79
80
|
const phaseLabel =
|
|
80
81
|
snapshot.phase === 'verifying'
|
|
81
|
-
? '
|
|
82
|
+
? t('Verifying')
|
|
82
83
|
: snapshot.phase === 'extracting'
|
|
83
|
-
? '
|
|
84
|
-
: '
|
|
84
|
+
? t('Extracting')
|
|
85
|
+
: t('Downloading');
|
|
85
86
|
|
|
86
87
|
return (
|
|
87
88
|
<section className="space-y-2" data-testid="settings-section-python-runner">
|
|
88
89
|
<h2 className="flex items-center gap-1.5 text-xs font-semibold text-agent-foreground">
|
|
89
90
|
<LuCodeXml className="h-3.5 w-3.5 text-agent-muted-foreground" />
|
|
90
|
-
Python
|
|
91
|
+
{t('Python code runner')}
|
|
91
92
|
</h2>
|
|
92
93
|
<div className="space-y-3 rounded-agent-md border border-agent-border bg-agent-card p-3 text-xs">
|
|
93
94
|
<p className="leading-relaxed text-agent-muted-foreground">
|
|
94
|
-
|
|
95
|
+
{t(
|
|
96
|
+
'run_code uses a separate Python interpreter. Downloading and configuring it does not block chat. Changes take effect after you restart the app.',
|
|
97
|
+
)}
|
|
95
98
|
</p>
|
|
96
99
|
|
|
97
100
|
<div className="flex flex-wrap gap-3">
|
|
98
101
|
{([
|
|
99
|
-
['default', '
|
|
100
|
-
['url', '
|
|
101
|
-
['local', '
|
|
102
|
+
['default', 'Default URL'],
|
|
103
|
+
['url', 'Custom URL'],
|
|
104
|
+
['local', 'Local Python'],
|
|
102
105
|
] as const).map(([value, label]) => (
|
|
103
106
|
<label key={value} className="flex items-center gap-1.5 text-agent-foreground">
|
|
104
107
|
<input
|
|
@@ -109,7 +112,7 @@ export function PythonRunnerSettingsPanel() {
|
|
|
109
112
|
disabled={busy}
|
|
110
113
|
onChange={() => setSource(value)}
|
|
111
114
|
/>
|
|
112
|
-
{label}
|
|
115
|
+
{t(label)}
|
|
113
116
|
</label>
|
|
114
117
|
))}
|
|
115
118
|
</div>
|
|
@@ -131,7 +134,7 @@ export function PythonRunnerSettingsPanel() {
|
|
|
131
134
|
onChange={(event) => setUrl(event.target.value)}
|
|
132
135
|
/>
|
|
133
136
|
<p className="text-agent-destructive">
|
|
134
|
-
|
|
137
|
+
{t('Custom URLs are not checked for file integrity. Use only trusted sources.')}
|
|
135
138
|
</p>
|
|
136
139
|
</div>
|
|
137
140
|
) : null}
|
|
@@ -142,7 +145,7 @@ export function PythonRunnerSettingsPanel() {
|
|
|
142
145
|
type="text"
|
|
143
146
|
value={localPath}
|
|
144
147
|
disabled={busy}
|
|
145
|
-
placeholder=
|
|
148
|
+
placeholder={t('Absolute path to the Python executable')}
|
|
146
149
|
className="h-8 min-w-0 flex-1 rounded-agent-sm border border-agent-border bg-agent-canvas px-2 text-agent-foreground outline-none focus:border-agent-foreground/40"
|
|
147
150
|
onChange={(event) => setLocalPath(event.target.value)}
|
|
148
151
|
/>
|
|
@@ -152,7 +155,7 @@ export function PythonRunnerSettingsPanel() {
|
|
|
152
155
|
className="h-8 shrink-0 rounded-agent-sm border border-agent-border px-3 text-agent-foreground hover:bg-agent-foreground/5 disabled:opacity-50"
|
|
153
156
|
onClick={chooseLocal}
|
|
154
157
|
>
|
|
155
|
-
|
|
158
|
+
{t('Choose…')}
|
|
156
159
|
</button>
|
|
157
160
|
</div>
|
|
158
161
|
) : null}
|
|
@@ -178,7 +181,7 @@ export function PythonRunnerSettingsPanel() {
|
|
|
178
181
|
className="text-agent-foreground hover:underline"
|
|
179
182
|
onClick={() => runner && update(runner.cancel())}
|
|
180
183
|
>
|
|
181
|
-
|
|
184
|
+
{t('Cancel')}
|
|
182
185
|
</button>
|
|
183
186
|
</div>
|
|
184
187
|
</div>
|
|
@@ -203,7 +206,7 @@ export function PythonRunnerSettingsPanel() {
|
|
|
203
206
|
className="h-8 rounded-agent-sm bg-agent-foreground px-3 font-medium text-agent-canvas hover:opacity-90"
|
|
204
207
|
onClick={() => void runner?.restart()}
|
|
205
208
|
>
|
|
206
|
-
|
|
209
|
+
{t('Restart app')}
|
|
207
210
|
</button>
|
|
208
211
|
) : null}
|
|
209
212
|
</div>
|
|
@@ -218,11 +221,11 @@ export function PythonRunnerSettingsPanel() {
|
|
|
218
221
|
</p>
|
|
219
222
|
) : null}
|
|
220
223
|
{snapshot.restartRequired ? (
|
|
221
|
-
<p className="font-medium text-agent-foreground"
|
|
224
|
+
<p className="font-medium text-agent-foreground">{t('Saved. Takes effect after you restart the app.')}</p>
|
|
222
225
|
) : null}
|
|
223
226
|
{snapshot.activeRunner ? (
|
|
224
227
|
<p className="break-all text-agent-muted-foreground">
|
|
225
|
-
|
|
228
|
+
{t('Current session: {runner}', { runner: snapshot.activeRunner })}
|
|
226
229
|
</p>
|
|
227
230
|
) : null}
|
|
228
231
|
</div>
|