@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
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
LuTrash2,
|
|
7
7
|
} from 'react-icons/lu';
|
|
8
8
|
import { ConfirmDialog } from '@/components/ConfirmDialog';
|
|
9
|
+
import { t } from '@/i18n';
|
|
9
10
|
import { isElectron } from '@/lib/electron-bridge';
|
|
10
11
|
import {
|
|
11
12
|
archiveChatAgent,
|
|
@@ -61,15 +62,15 @@ function agentInitial(agent: LocalChatAgent): string {
|
|
|
61
62
|
function capabilitySummary(agent: LocalChatAgent): string[] {
|
|
62
63
|
const labels: string[] = [];
|
|
63
64
|
const skillCount = agent.skillIds?.length ?? 0;
|
|
64
|
-
if (skillCount > 0) labels.push(
|
|
65
|
-
if (agent.allowExternalSkills === false) labels.push('
|
|
66
|
-
if (agent.loadAllSkills) labels.push('
|
|
65
|
+
if (skillCount > 0) labels.push(t('Skills {count}', { count: skillCount }));
|
|
66
|
+
if (agent.allowExternalSkills === false) labels.push(t('Selected skills only'));
|
|
67
|
+
if (agent.loadAllSkills) labels.push(t('All skills'));
|
|
67
68
|
const policy = agent.toolPolicy;
|
|
68
69
|
if (policy && policy.mode !== 'all' && policy.tools.length > 0) {
|
|
69
70
|
labels.push(
|
|
70
71
|
policy.mode === 'allowlist'
|
|
71
|
-
?
|
|
72
|
-
:
|
|
72
|
+
? t('Only {count} tools', { count: policy.tools.length })
|
|
73
|
+
: t('{count} tools blocked', { count: policy.tools.length }),
|
|
73
74
|
);
|
|
74
75
|
}
|
|
75
76
|
return labels;
|
|
@@ -158,7 +159,7 @@ export function AgentsSettingsPanel({
|
|
|
158
159
|
name: trimmedName,
|
|
159
160
|
color,
|
|
160
161
|
description: description.trim() || null,
|
|
161
|
-
rolePrompt: rolePrompt.trim() ||
|
|
162
|
+
rolePrompt: rolePrompt.trim() || t('You are **{name}**.', { name: trimmedName }),
|
|
162
163
|
skillIds: capability.skillIds,
|
|
163
164
|
allowExternalSkills: capability.allowExternalSkills,
|
|
164
165
|
loadAllSkills: capability.loadAllSkills,
|
|
@@ -203,20 +204,21 @@ export function AgentsSettingsPanel({
|
|
|
203
204
|
return (
|
|
204
205
|
<div className="space-y-3">
|
|
205
206
|
<p className="text-[11px] text-agent-muted-foreground">
|
|
206
|
-
|
|
207
|
-
|
|
207
|
+
{t(
|
|
208
|
+
'Custom agents appear in the expert picker above the input box. The role prompt opens the conversation as its persona, and the checked skills and tool permissions apply on every turn. Built-in agents can have their text and capabilities edited, but cannot be deleted.',
|
|
209
|
+
)}
|
|
208
210
|
</p>
|
|
209
211
|
|
|
210
212
|
{formOpen ? (
|
|
211
213
|
<div className="space-y-2 rounded-agent-md border border-agent-border/60 bg-agent-muted/30 p-2.5">
|
|
212
214
|
<h4 className="text-xs font-semibold text-agent-foreground">
|
|
213
|
-
{editingId ? '
|
|
215
|
+
{editingId ? t('Edit agent') : t('New agent')}
|
|
214
216
|
</h4>
|
|
215
217
|
<input
|
|
216
218
|
type="text"
|
|
217
219
|
value={name}
|
|
218
220
|
onChange={(event) => setName(event.target.value)}
|
|
219
|
-
placeholder=
|
|
221
|
+
placeholder={t('Name, e.g. Geology advisor')}
|
|
220
222
|
maxLength={40}
|
|
221
223
|
className="h-8 w-full rounded-agent-md border border-agent-border bg-agent-canvas px-3 text-xs text-agent-foreground focus:outline-none focus:ring-2 focus:ring-agent-foreground/30"
|
|
222
224
|
data-testid="agent-form-name"
|
|
@@ -225,13 +227,13 @@ export function AgentsSettingsPanel({
|
|
|
225
227
|
type="text"
|
|
226
228
|
value={description}
|
|
227
229
|
onChange={(event) => setDescription(event.target.value)}
|
|
228
|
-
placeholder=
|
|
230
|
+
placeholder={t('One-line summary (optional)')}
|
|
229
231
|
maxLength={200}
|
|
230
232
|
className="h-8 w-full rounded-agent-md border border-agent-border bg-agent-canvas px-3 text-xs text-agent-foreground focus:outline-none focus:ring-2 focus:ring-agent-foreground/30"
|
|
231
233
|
data-testid="agent-form-description"
|
|
232
234
|
/>
|
|
233
235
|
<div>
|
|
234
|
-
<p className="mb-1.5 text-[10px] font-medium text-agent-muted-foreground"
|
|
236
|
+
<p className="mb-1.5 text-[10px] font-medium text-agent-muted-foreground">{t('Color')}</p>
|
|
235
237
|
<div className="flex flex-wrap gap-1.5" data-testid="agent-form-colors">
|
|
236
238
|
{AGENT_COLORS.map((item) => (
|
|
237
239
|
<button
|
|
@@ -245,7 +247,7 @@ export function AgentsSettingsPanel({
|
|
|
245
247
|
}`}
|
|
246
248
|
style={{ backgroundColor: item }}
|
|
247
249
|
title={item}
|
|
248
|
-
aria-label={
|
|
250
|
+
aria-label={t('Color {color}', { color: item })}
|
|
249
251
|
/>
|
|
250
252
|
))}
|
|
251
253
|
</div>
|
|
@@ -253,7 +255,7 @@ export function AgentsSettingsPanel({
|
|
|
253
255
|
<textarea
|
|
254
256
|
value={rolePrompt}
|
|
255
257
|
onChange={(event) => setRolePrompt(event.target.value)}
|
|
256
|
-
placeholder=
|
|
258
|
+
placeholder={t('Role prompt. For example: You are a geology advisor. Answer concisely, giving the conclusion first and then the evidence.')}
|
|
257
259
|
rows={6}
|
|
258
260
|
className="w-full rounded-agent-md border border-agent-border bg-agent-canvas px-3 py-2 text-xs text-agent-foreground focus:outline-none focus:ring-2 focus:ring-agent-foreground/30"
|
|
259
261
|
data-testid="agent-form-role"
|
|
@@ -268,7 +270,7 @@ export function AgentsSettingsPanel({
|
|
|
268
270
|
}}
|
|
269
271
|
className="h-8 rounded-full px-4 text-xs font-medium text-agent-muted-foreground transition-colors hover:bg-agent-muted hover:text-agent-foreground"
|
|
270
272
|
>
|
|
271
|
-
|
|
273
|
+
{t('Cancel')}
|
|
272
274
|
</button>
|
|
273
275
|
<button
|
|
274
276
|
type="button"
|
|
@@ -281,7 +283,7 @@ export function AgentsSettingsPanel({
|
|
|
281
283
|
: 'bg-agent-foreground text-agent-canvas hover:opacity-90'
|
|
282
284
|
}`}
|
|
283
285
|
>
|
|
284
|
-
{saving ? <LuLoaderCircle className="h-3 w-3 animate-spin" /> : '
|
|
286
|
+
{saving ? <LuLoaderCircle className="h-3 w-3 animate-spin" /> : t('Save')}
|
|
285
287
|
</button>
|
|
286
288
|
</div>
|
|
287
289
|
</div>
|
|
@@ -293,13 +295,13 @@ export function AgentsSettingsPanel({
|
|
|
293
295
|
data-testid="agent-add"
|
|
294
296
|
>
|
|
295
297
|
<LuPlus className="h-3.5 w-3.5" />
|
|
296
|
-
|
|
298
|
+
{t('Add agent')}
|
|
297
299
|
</button>
|
|
298
300
|
)}
|
|
299
301
|
|
|
300
302
|
<div className="space-y-2">
|
|
301
303
|
<h4 className="text-xs font-semibold uppercase tracking-wide text-agent-muted-foreground">
|
|
302
|
-
|
|
304
|
+
{t('Existing agents')}
|
|
303
305
|
{agents.length > 0 && (
|
|
304
306
|
<span className="ml-1.5 font-normal normal-case">· {agents.length}</span>
|
|
305
307
|
)}
|
|
@@ -307,11 +309,11 @@ export function AgentsSettingsPanel({
|
|
|
307
309
|
{loading ? (
|
|
308
310
|
<div className="flex items-center gap-2 py-4 text-xs text-agent-muted-foreground">
|
|
309
311
|
<LuLoaderCircle className="h-3.5 w-3.5 animate-spin" />
|
|
310
|
-
|
|
312
|
+
{t('Loading agents...')}
|
|
311
313
|
</div>
|
|
312
314
|
) : agents.length === 0 ? (
|
|
313
315
|
<div className="py-6 text-center text-xs text-agent-muted-foreground">
|
|
314
|
-
|
|
316
|
+
{t('No agents yet')}
|
|
315
317
|
</div>
|
|
316
318
|
) : (
|
|
317
319
|
<div className="divide-y divide-agent-border/40 pr-1">
|
|
@@ -334,7 +336,7 @@ export function AgentsSettingsPanel({
|
|
|
334
336
|
</span>
|
|
335
337
|
{agent.isBuiltin && (
|
|
336
338
|
<span className="flex-shrink-0 rounded bg-agent-muted px-1 py-0.5 text-[9px] font-medium text-agent-muted-foreground">
|
|
337
|
-
|
|
339
|
+
{t('Built-in')}
|
|
338
340
|
</span>
|
|
339
341
|
)}
|
|
340
342
|
</div>
|
|
@@ -364,7 +366,7 @@ export function AgentsSettingsPanel({
|
|
|
364
366
|
type="button"
|
|
365
367
|
onClick={() => openEdit(agent)}
|
|
366
368
|
className="rounded-full p-1 text-agent-muted-foreground transition-colors hover:bg-agent-muted hover:text-agent-foreground"
|
|
367
|
-
title=
|
|
369
|
+
title={t('Edit')}
|
|
368
370
|
data-testid={`agent-edit-${agent.id}`}
|
|
369
371
|
>
|
|
370
372
|
<LuPencil className="h-3.5 w-3.5" />
|
|
@@ -377,7 +379,7 @@ export function AgentsSettingsPanel({
|
|
|
377
379
|
}
|
|
378
380
|
disabled={archivingId === agent.id}
|
|
379
381
|
className="rounded-full p-1 text-agent-muted-foreground transition-colors hover:bg-agent-muted hover:text-agent-destructive"
|
|
380
|
-
title=
|
|
382
|
+
title={t('Delete')}
|
|
381
383
|
data-testid={`agent-archive-${agent.id}`}
|
|
382
384
|
>
|
|
383
385
|
{archivingId === agent.id ? (
|
|
@@ -404,10 +406,13 @@ export function AgentsSettingsPanel({
|
|
|
404
406
|
)}
|
|
405
407
|
<ConfirmDialog
|
|
406
408
|
open={pendingArchive !== null}
|
|
407
|
-
title=
|
|
409
|
+
title={t('Delete agent')}
|
|
408
410
|
description={
|
|
409
411
|
pendingArchive
|
|
410
|
-
?
|
|
412
|
+
? t(
|
|
413
|
+
'Delete "{name}"? It will be removed from the expert picker. Existing conversations are not affected.',
|
|
414
|
+
{ name: pendingArchive.name },
|
|
415
|
+
)
|
|
411
416
|
: ''
|
|
412
417
|
}
|
|
413
418
|
pending={pendingArchive !== null && archivingId === pendingArchive.id}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { LuInfo } from 'react-icons/lu';
|
|
2
2
|
import { useAppRelease } from '@/components/SidebarRelease';
|
|
3
|
+
import { t } from '@/i18n';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* 综合设置里的版本与更新。侧栏只显示版本号;检查、下载和安装在这里。
|
|
@@ -17,11 +18,11 @@ export function AppUpdateSettingsPanel() {
|
|
|
17
18
|
<section className="space-y-2" data-testid="settings-section-update">
|
|
18
19
|
<h2 className="flex items-center gap-1.5 text-xs font-semibold text-agent-foreground">
|
|
19
20
|
<LuInfo className="h-3.5 w-3.5 text-agent-muted-foreground" />
|
|
20
|
-
|
|
21
|
+
{t('About')}
|
|
21
22
|
</h2>
|
|
22
23
|
<div className="flex items-center gap-3 rounded-agent-md border border-agent-border bg-agent-card p-2.5">
|
|
23
24
|
<div className="min-w-0 flex-1">
|
|
24
|
-
<p className="text-xs font-medium text-agent-foreground"
|
|
25
|
+
<p className="text-xs font-medium text-agent-foreground">{t('Current version')}</p>
|
|
25
26
|
<p
|
|
26
27
|
className="mt-1 text-xs tabular-nums text-agent-muted-foreground"
|
|
27
28
|
data-testid="settings-app-version"
|
|
@@ -3,54 +3,98 @@ import {
|
|
|
3
3
|
persistThinkingDisplay,
|
|
4
4
|
useThinkingDisplay,
|
|
5
5
|
} from '@/lib/show-thinking-content';
|
|
6
|
+
import { localeEndonym, supportedLocales, useI18n } from '@/i18n';
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* 界面偏好:思考过程正文按「隐藏 / 显示5行 / 完整显示」三档。
|
|
9
|
-
*
|
|
10
|
+
* 产品声明了多于一种语言时,在这里切换。立即写入 localStorage。
|
|
10
11
|
*/
|
|
11
12
|
export function AppearanceSettingsPanel() {
|
|
13
|
+
const { t, locale, setLocale } = useI18n();
|
|
12
14
|
const mode = useThinkingDisplay();
|
|
13
15
|
const current = THINKING_DISPLAY_OPTIONS.find((item) => item.mode === mode);
|
|
16
|
+
const locales = supportedLocales();
|
|
14
17
|
|
|
15
18
|
return (
|
|
16
|
-
<div
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
19
|
+
<div className="space-y-2">
|
|
20
|
+
{locales.length > 1 ? (
|
|
21
|
+
<div
|
|
22
|
+
className="space-y-2 rounded-agent-md border border-agent-border bg-agent-card p-2.5"
|
|
23
|
+
data-testid="locale-settings"
|
|
24
|
+
>
|
|
25
|
+
<p className="text-xs font-medium text-agent-foreground">{t('Language')}</p>
|
|
26
|
+
<div
|
|
27
|
+
className="flex w-full rounded-full border border-agent-border bg-agent-canvas p-0.5"
|
|
28
|
+
role="radiogroup"
|
|
29
|
+
aria-label={t('Language')}
|
|
30
|
+
data-testid="locale-toggle"
|
|
31
|
+
>
|
|
32
|
+
{locales.map((item) => {
|
|
33
|
+
const selected = locale === item;
|
|
34
|
+
return (
|
|
35
|
+
<button
|
|
36
|
+
key={item}
|
|
37
|
+
type="button"
|
|
38
|
+
role="radio"
|
|
39
|
+
aria-checked={selected}
|
|
40
|
+
data-testid={`locale-option-${item}`}
|
|
41
|
+
onClick={() => setLocale(item)}
|
|
42
|
+
className={[
|
|
43
|
+
'h-7 flex-1 rounded-full px-2 text-xs transition-colors',
|
|
44
|
+
selected
|
|
45
|
+
? 'bg-agent-foreground/10 text-agent-foreground'
|
|
46
|
+
: 'text-agent-muted-foreground hover:text-agent-foreground',
|
|
47
|
+
].join(' ')}
|
|
48
|
+
>
|
|
49
|
+
{localeEndonym(item)}
|
|
50
|
+
</button>
|
|
51
|
+
);
|
|
52
|
+
})}
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
) : null}
|
|
26
56
|
<div
|
|
27
|
-
className="
|
|
28
|
-
|
|
29
|
-
aria-label="显示思考内容"
|
|
30
|
-
data-testid="thinking-display-toggle"
|
|
57
|
+
className="space-y-2 rounded-agent-md border border-agent-border bg-agent-card p-2.5"
|
|
58
|
+
data-testid="appearance-settings-panel"
|
|
31
59
|
>
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
60
|
+
<div>
|
|
61
|
+
<p className="text-xs font-medium text-agent-foreground">{t('Show thinking')}</p>
|
|
62
|
+
<p className="mt-1 text-xs leading-relaxed text-agent-muted-foreground">
|
|
63
|
+
{current ? t(current.hint) : null}{' '}
|
|
64
|
+
{t(
|
|
65
|
+
'These options only control the thinking text. Tool cards and work rows stay as they are. A work row shows "Working", or the count and elapsed time after it finishes.',
|
|
66
|
+
)}
|
|
67
|
+
</p>
|
|
68
|
+
</div>
|
|
69
|
+
<div
|
|
70
|
+
className="flex w-full rounded-full border border-agent-border bg-agent-canvas p-0.5"
|
|
71
|
+
role="radiogroup"
|
|
72
|
+
aria-label={t('Show thinking')}
|
|
73
|
+
data-testid="thinking-display-toggle"
|
|
74
|
+
>
|
|
75
|
+
{THINKING_DISPLAY_OPTIONS.map((item) => {
|
|
76
|
+
const selected = mode === item.mode;
|
|
77
|
+
return (
|
|
78
|
+
<button
|
|
79
|
+
key={item.mode}
|
|
80
|
+
type="button"
|
|
81
|
+
role="radio"
|
|
82
|
+
aria-checked={selected}
|
|
83
|
+
aria-label={t(item.label)}
|
|
84
|
+
data-testid={`thinking-display-${item.mode}`}
|
|
85
|
+
onClick={() => persistThinkingDisplay(item.mode)}
|
|
86
|
+
className={[
|
|
87
|
+
'h-7 flex-1 rounded-full px-2 text-xs transition-colors',
|
|
88
|
+
selected
|
|
89
|
+
? 'bg-agent-foreground/10 text-agent-foreground'
|
|
90
|
+
: 'text-agent-muted-foreground hover:text-agent-foreground',
|
|
91
|
+
].join(' ')}
|
|
92
|
+
>
|
|
93
|
+
{t(item.label)}
|
|
94
|
+
</button>
|
|
95
|
+
);
|
|
96
|
+
})}
|
|
97
|
+
</div>
|
|
54
98
|
</div>
|
|
55
99
|
</div>
|
|
56
100
|
);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useState } from 'react';
|
|
2
2
|
import { LuCircleCheck, LuCircleX, LuLoader, LuPlay } from 'react-icons/lu';
|
|
3
3
|
import { diagnoseLlmConnection, type DiagnoseResult } from '@/lib/local-api';
|
|
4
|
+
import { t } from '@/i18n';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* 设置页「链路诊断」面板:一键探测当前 LLM 配置的连通性
|
|
@@ -29,8 +30,10 @@ export function DiagnoseSettingsPanel() {
|
|
|
29
30
|
return (
|
|
30
31
|
<div className="space-y-2 rounded-agent-md border border-agent-border bg-agent-card p-2.5">
|
|
31
32
|
<p className="text-xs text-agent-muted-foreground">
|
|
32
|
-
|
|
33
|
-
|
|
33
|
+
{t(
|
|
34
|
+
'Diagnose network connectivity to the current model service, including DNS resolution, TCP connection, TLS handshake, the HTTP endpoint, and chat completion.',
|
|
35
|
+
)}{' '}
|
|
36
|
+
{t('The diagnosis runs in the host process and also reports system and environment proxy settings.')}
|
|
34
37
|
</p>
|
|
35
38
|
|
|
36
39
|
<button
|
|
@@ -42,19 +45,19 @@ export function DiagnoseSettingsPanel() {
|
|
|
42
45
|
{running ? (
|
|
43
46
|
<>
|
|
44
47
|
<LuLoader className="h-3.5 w-3.5 animate-spin" />
|
|
45
|
-
|
|
48
|
+
{t('Diagnosing…')}
|
|
46
49
|
</>
|
|
47
50
|
) : (
|
|
48
51
|
<>
|
|
49
52
|
<LuPlay className="h-3.5 w-3.5" />
|
|
50
|
-
|
|
53
|
+
{t('Start diagnosis')}
|
|
51
54
|
</>
|
|
52
55
|
)}
|
|
53
56
|
</button>
|
|
54
57
|
|
|
55
58
|
{error && (
|
|
56
59
|
<p className="rounded-agent-md border border-agent-destructive/20 bg-agent-destructive/10 p-2.5 text-xs text-agent-destructive">
|
|
57
|
-
|
|
60
|
+
{t('Diagnosis request failed: {error}', { error })}
|
|
58
61
|
</p>
|
|
59
62
|
)}
|
|
60
63
|
|
|
@@ -64,12 +67,12 @@ export function DiagnoseSettingsPanel() {
|
|
|
64
67
|
{result.ok ? (
|
|
65
68
|
<span className="inline-flex items-center gap-1 text-agent-success">
|
|
66
69
|
<LuCircleCheck className="h-4 w-4" />
|
|
67
|
-
|
|
70
|
+
{t('All checks passed')}
|
|
68
71
|
</span>
|
|
69
72
|
) : (
|
|
70
73
|
<span className="inline-flex items-center gap-1 text-agent-destructive">
|
|
71
74
|
<LuCircleX className="h-4 w-4" />
|
|
72
|
-
|
|
75
|
+
{t('Diagnosis failed')}
|
|
73
76
|
</span>
|
|
74
77
|
)}
|
|
75
78
|
</div>
|
|
@@ -98,7 +101,7 @@ export function DiagnoseSettingsPanel() {
|
|
|
98
101
|
|
|
99
102
|
{result.ambientProxies.length > 0 && (
|
|
100
103
|
<div className="rounded-agent-sm border border-agent-border bg-agent-muted/30 p-2.5 text-xs">
|
|
101
|
-
<p className="font-medium text-agent-foreground"
|
|
104
|
+
<p className="font-medium text-agent-foreground">{t('Detected proxy settings')}</p>
|
|
102
105
|
<ul className="mt-1 list-inside list-disc space-y-0.5 text-agent-muted-foreground">
|
|
103
106
|
{result.ambientProxies.map((proxy) => (
|
|
104
107
|
<li key={proxy}>{proxy}</li>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useCallback, useEffect, useState } from 'react';
|
|
2
2
|
import { LuChartBar } from 'react-icons/lu';
|
|
3
3
|
import { BRAND_NAME } from '@/brand';
|
|
4
|
+
import { t } from '@/i18n';
|
|
4
5
|
import { getElectronBridge, isElectron } from '@/lib/electron-bridge';
|
|
5
6
|
|
|
6
7
|
type InsightsWire = {
|
|
@@ -71,13 +72,13 @@ export function InsightsConsentBanner() {
|
|
|
71
72
|
className="flex-shrink-0 border-t border-agent-border bg-agent-muted px-2.5 py-2"
|
|
72
73
|
data-testid="insights-consent-banner"
|
|
73
74
|
role="region"
|
|
74
|
-
aria-label=
|
|
75
|
+
aria-label={t('Consent to help improve the product')}
|
|
75
76
|
>
|
|
76
77
|
<div className="flex items-center justify-between gap-3">
|
|
77
78
|
<div className="min-w-0">
|
|
78
|
-
<p className="text-xs font-medium text-agent-foreground"
|
|
79
|
+
<p className="text-xs font-medium text-agent-foreground">{t('Help improve {brand}', { brand: BRAND_NAME })}</p>
|
|
79
80
|
<p className="mt-1 text-xs leading-relaxed text-agent-muted-foreground">
|
|
80
|
-
|
|
81
|
+
{t('Agree to upload data to the server to help improve the product. You can change this later in Settings.')}
|
|
81
82
|
</p>
|
|
82
83
|
</div>
|
|
83
84
|
<div className="flex shrink-0 gap-2">
|
|
@@ -87,7 +88,7 @@ export function InsightsConsentBanner() {
|
|
|
87
88
|
onClick={() => void save(true)}
|
|
88
89
|
className="h-8 whitespace-nowrap rounded-full bg-agent-foreground px-3.5 text-xs text-agent-canvas disabled:opacity-50"
|
|
89
90
|
>
|
|
90
|
-
|
|
91
|
+
{t('Save')}
|
|
91
92
|
</button>
|
|
92
93
|
<button
|
|
93
94
|
type="button"
|
|
@@ -95,7 +96,7 @@ export function InsightsConsentBanner() {
|
|
|
95
96
|
onClick={() => void save(false)}
|
|
96
97
|
className="h-8 whitespace-nowrap rounded-full border border-agent-border bg-agent-canvas px-3.5 text-xs text-agent-foreground disabled:opacity-50"
|
|
97
98
|
>
|
|
98
|
-
|
|
99
|
+
{t('Cancel')}
|
|
99
100
|
</button>
|
|
100
101
|
</div>
|
|
101
102
|
</div>
|
|
@@ -152,7 +153,7 @@ export function InsightsSettingsPanel() {
|
|
|
152
153
|
},
|
|
153
154
|
});
|
|
154
155
|
setData({ ...empty, ...res, profile: { ...empty.profile, ...res.profile } });
|
|
155
|
-
setStatus('
|
|
156
|
+
setStatus(t('Saved. Unchecked categories stay on this device only.'));
|
|
156
157
|
} catch (err) {
|
|
157
158
|
setError(err instanceof Error ? err.message : String(err));
|
|
158
159
|
} finally {
|
|
@@ -170,11 +171,11 @@ export function InsightsSettingsPanel() {
|
|
|
170
171
|
});
|
|
171
172
|
const text = `${JSON.stringify(bundle, null, 2)}\n`;
|
|
172
173
|
const saved = await getElectronBridge()!.local?.saveTextFile?.({
|
|
173
|
-
title: '
|
|
174
|
+
title: t('Export local insight records'),
|
|
174
175
|
defaultPath: `deeppath-insights-${new Date().toISOString().slice(0, 10)}.json`,
|
|
175
176
|
content: text,
|
|
176
177
|
});
|
|
177
|
-
if (saved?.canceled === false) setStatus(
|
|
178
|
+
if (saved?.canceled === false) setStatus(t('Saved to {path}', { path: saved.filePath ?? '' }));
|
|
178
179
|
} catch (err) {
|
|
179
180
|
setError(err instanceof Error ? err.message : String(err));
|
|
180
181
|
}
|
|
@@ -188,7 +189,11 @@ export function InsightsSettingsPanel() {
|
|
|
188
189
|
method: 'POST',
|
|
189
190
|
path: '/api/v2/insights/upload-local',
|
|
190
191
|
});
|
|
191
|
-
setStatus(
|
|
192
|
+
setStatus(
|
|
193
|
+
res.ok
|
|
194
|
+
? t('Uploaded to the {brand} server', { brand: BRAND_NAME })
|
|
195
|
+
: t('Upload failed. The records are still on this device. You can export a file and send it instead.'),
|
|
196
|
+
);
|
|
192
197
|
await load();
|
|
193
198
|
} catch (err) {
|
|
194
199
|
setError(err instanceof Error ? err.message : String(err));
|
|
@@ -202,15 +207,17 @@ export function InsightsSettingsPanel() {
|
|
|
202
207
|
<div className="rounded-agent-md border border-agent-border/60 bg-agent-muted/30 p-2.5 space-y-2">
|
|
203
208
|
<h4 className="flex items-center gap-1.5 text-xs font-semibold text-agent-foreground">
|
|
204
209
|
<LuChartBar className="h-3.5 w-3.5 text-agent-muted-foreground" />
|
|
205
|
-
|
|
210
|
+
{t('Help improve the product (behavior / conversations / user info are confirmed separately)')}
|
|
206
211
|
</h4>
|
|
207
212
|
<p className="text-[11px] text-agent-muted-foreground">
|
|
208
|
-
|
|
213
|
+
{t(
|
|
214
|
+
'Even if the user does not agree to upload, records stay on this device. You can export JSON and send it to the product team, or click "Upload now" once.',
|
|
215
|
+
)}
|
|
209
216
|
</p>
|
|
210
217
|
{!isElectron() ? (
|
|
211
|
-
<p className="text-[11px] text-agent-muted-foreground"
|
|
218
|
+
<p className="text-[11px] text-agent-muted-foreground">{t('Open this in the desktop app')}</p>
|
|
212
219
|
) : loading ? (
|
|
213
|
-
<p className="text-[11px] text-agent-muted-foreground"
|
|
220
|
+
<p className="text-[11px] text-agent-muted-foreground">{t('Loading…')}</p>
|
|
214
221
|
) : (
|
|
215
222
|
<>
|
|
216
223
|
<label className="flex items-start gap-2 text-xs">
|
|
@@ -220,8 +227,8 @@ export function InsightsSettingsPanel() {
|
|
|
220
227
|
onChange={(e) => setData((d) => ({ ...d, shareBehavior: e.target.checked }))}
|
|
221
228
|
/>
|
|
222
229
|
<span>
|
|
223
|
-
|
|
224
|
-
<span className="block text-[10px] text-agent-muted-foreground"
|
|
230
|
+
{t('Auto-upload')} <strong>{t('behavior')}</strong>
|
|
231
|
+
<span className="block text-[10px] text-agent-muted-foreground">{t('Opens, sends, stuck settings, and so on. No question or answer text.')}</span>
|
|
225
232
|
</span>
|
|
226
233
|
</label>
|
|
227
234
|
<label className="flex items-start gap-2 text-xs">
|
|
@@ -231,8 +238,8 @@ export function InsightsSettingsPanel() {
|
|
|
231
238
|
onChange={(e) => setData((d) => ({ ...d, shareConversation: e.target.checked }))}
|
|
232
239
|
/>
|
|
233
240
|
<span>
|
|
234
|
-
|
|
235
|
-
<span className="block text-[10px] text-agent-muted-foreground"
|
|
241
|
+
{t('Auto-upload')} <strong>{t('conversations')}</strong>
|
|
242
|
+
<span className="block text-[10px] text-agent-muted-foreground">{t('Questions and answers (keys and user directories are redacted)')}</span>
|
|
236
243
|
</span>
|
|
237
244
|
</label>
|
|
238
245
|
<label className="flex items-start gap-2 text-xs">
|
|
@@ -242,21 +249,21 @@ export function InsightsSettingsPanel() {
|
|
|
242
249
|
onChange={(e) => setData((d) => ({ ...d, shareProfile: e.target.checked }))}
|
|
243
250
|
/>
|
|
244
251
|
<span>
|
|
245
|
-
|
|
246
|
-
<span className="block text-[10px] text-agent-muted-foreground"
|
|
252
|
+
{t('Auto-upload')} <strong>{t('user info')}</strong>
|
|
253
|
+
<span className="block text-[10px] text-agent-muted-foreground">{t('Separate from the two above. If unchecked, your name and email stay on this device only.')}</span>
|
|
247
254
|
</span>
|
|
248
255
|
</label>
|
|
249
256
|
<div className="grid grid-cols-2 gap-2">
|
|
250
257
|
{(
|
|
251
258
|
[
|
|
252
|
-
['displayName', '
|
|
253
|
-
['email', '
|
|
254
|
-
['company', '
|
|
255
|
-
['note', '
|
|
259
|
+
['displayName', 'Name'],
|
|
260
|
+
['email', 'Email'],
|
|
261
|
+
['company', 'Company/team'],
|
|
262
|
+
['note', 'Anything to tell us'],
|
|
256
263
|
] as const
|
|
257
264
|
).map(([key, label]) => (
|
|
258
265
|
<label key={key} className="space-y-1 text-[11px] text-agent-muted-foreground">
|
|
259
|
-
{label}
|
|
266
|
+
{t(label)}
|
|
260
267
|
<input
|
|
261
268
|
value={data.profile[key]}
|
|
262
269
|
onChange={(e) =>
|
|
@@ -268,7 +275,15 @@ export function InsightsSettingsPanel() {
|
|
|
268
275
|
))}
|
|
269
276
|
</div>
|
|
270
277
|
<p className="text-[10px] text-agent-muted-foreground">
|
|
271
|
-
|
|
278
|
+
{t(
|
|
279
|
+
'Recorded on this device: behavior {events} / conversations {turns} / profile {profile}, pending upload {pending}',
|
|
280
|
+
{
|
|
281
|
+
events: stats.events,
|
|
282
|
+
turns: stats.turns,
|
|
283
|
+
profile: stats.profile,
|
|
284
|
+
pending: stats.pending,
|
|
285
|
+
},
|
|
286
|
+
)}
|
|
272
287
|
{data.installId ? ` · ${data.installId.slice(0, 8)}` : ''}
|
|
273
288
|
</p>
|
|
274
289
|
<div className="flex flex-wrap gap-2">
|
|
@@ -278,21 +293,21 @@ export function InsightsSettingsPanel() {
|
|
|
278
293
|
onClick={() => void save()}
|
|
279
294
|
className="h-8 rounded-full bg-agent-foreground px-4 text-xs text-agent-canvas"
|
|
280
295
|
>
|
|
281
|
-
|
|
296
|
+
{t('Save')}
|
|
282
297
|
</button>
|
|
283
298
|
<button
|
|
284
299
|
type="button"
|
|
285
300
|
onClick={() => void exportFile()}
|
|
286
301
|
className="h-8 rounded-full border border-agent-border px-4 text-xs"
|
|
287
302
|
>
|
|
288
|
-
|
|
303
|
+
{t('Export a file for the developers')}
|
|
289
304
|
</button>
|
|
290
305
|
<button
|
|
291
306
|
type="button"
|
|
292
307
|
onClick={() => void uploadNow()}
|
|
293
308
|
className="h-8 rounded-full border border-agent-border px-4 text-xs"
|
|
294
309
|
>
|
|
295
|
-
|
|
310
|
+
{t('Upload local records now')}
|
|
296
311
|
</button>
|
|
297
312
|
</div>
|
|
298
313
|
</>
|