@steerable/agent-shell-web 0.6.53 → 0.6.55
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +4 -4
- package/src/brand.ts +1 -1
- package/src/components/AgentSidebar.tsx +60 -52
- package/src/components/ChatHeader.tsx +41 -25
- package/src/components/ConfirmDialog.tsx +4 -3
- package/src/components/CreateProjectModal.tsx +29 -19
- package/src/components/LocalLlmSettingsModal.tsx +4 -3
- package/src/components/SidebarRelease.tsx +2 -1
- package/src/components/TerminalPanel.tsx +3 -2
- package/src/components/TerminalView.tsx +12 -7
- package/src/components/chat/ApprovalModal.tsx +39 -25
- package/src/components/chat/ApprovalPromptProvider.tsx +1 -1
- package/src/components/chat/AskUserPromptProvider.tsx +1 -1
- package/src/components/chat/AskUserQuestionMenu.tsx +19 -14
- package/src/components/chat/AssistantMessage.tsx +18 -13
- package/src/components/chat/ChatInput.tsx +56 -40
- package/src/components/chat/ChatProjectBadge.tsx +24 -19
- package/src/components/chat/EmptyChatHero.tsx +2 -1
- package/src/components/chat/ExecPolicyPicker.tsx +12 -11
- package/src/components/chat/ExecutedActionsCard.tsx +45 -33
- package/src/components/chat/FilePathCode.tsx +8 -3
- package/src/components/chat/InterruptedTurnCard.tsx +4 -3
- package/src/components/chat/LocalChatPanel.tsx +2 -1
- package/src/components/chat/Markdown.tsx +2 -1
- package/src/components/chat/MessageList.tsx +3 -2
- package/src/components/chat/ModelPicker.tsx +25 -16
- package/src/components/chat/OrchestrationChildrenCard.tsx +2 -1
- package/src/components/chat/OrchestrationFlowCard.tsx +23 -22
- package/src/components/chat/PlanTodoList.tsx +2 -1
- package/src/components/chat/ProjectTrustBanner.tsx +19 -5
- package/src/components/chat/SessionOrchestrationFlow.tsx +27 -26
- package/src/components/chat/SessionTodoList.tsx +8 -7
- package/src/components/chat/SessionTreeModal.tsx +10 -9
- package/src/components/chat/StreamingStatus.tsx +4 -3
- package/src/components/chat/SuggestedReplies.tsx +3 -2
- package/src/components/chat/TaskOutcomeCards.tsx +8 -5
- package/src/components/chat/TaskPanelModal.tsx +31 -21
- package/src/components/chat/TaskProcessPanel.tsx +14 -13
- package/src/components/chat/TurnFilesCard.tsx +14 -13
- package/src/components/chat/UserMessage.tsx +12 -11
- package/src/components/chat/executed-actions-model.ts +47 -35
- package/src/components/chat/orchestration-flow-model.ts +16 -5
- package/src/components/chat/process-status.ts +8 -7
- package/src/components/chat/todo-list-model.ts +5 -4
- package/src/components/chat/turn-files.ts +8 -8
- package/src/components/settings/AgentCapabilityFields.tsx +24 -21
- package/src/components/settings/AgentsSettingsPanel.tsx +30 -25
- package/src/components/settings/AppUpdateSettingsPanel.tsx +3 -2
- package/src/components/settings/AppearanceSettingsPanel.tsx +81 -37
- package/src/components/settings/DiagnoseSettingsPanel.tsx +11 -8
- package/src/components/settings/InsightsSettingsPanel.tsx +43 -28
- package/src/components/settings/LlmSettingsPanel.tsx +104 -83
- package/src/components/settings/McpSettingsPanel.tsx +60 -34
- package/src/components/settings/ModelIdCombobox.tsx +2 -1
- package/src/components/settings/OrchestrationSettingsPanel.tsx +8 -5
- package/src/components/settings/PortableSettingsPanel.tsx +83 -55
- package/src/components/settings/PythonRunnerSettingsPanel.tsx +22 -19
- package/src/components/settings/SecuritySettingsPanel.tsx +55 -31
- package/src/components/settings/SettingsNavMenu.tsx +3 -2
- package/src/components/settings/SettingsSaveButton.tsx +2 -1
- package/src/components/settings/SkillsSettingsPanel.tsx +36 -31
- package/src/components/settings/TelemetrySettingsPanel.tsx +15 -12
- package/src/components/settings/UsagePanel.tsx +19 -20
- package/src/components/settings/WebSearchSettingsPanel.tsx +23 -14
- package/src/components/settings/llm-vendors.ts +12 -7
- package/src/components/settings/model-capabilities.ts +16 -15
- package/src/components/settings/preset-choice-model.ts +6 -3
- package/src/fixtures/browser-dev-data.json +24 -24
- package/src/hooks/useProjects.ts +1 -1
- package/src/i18n/agent-label.ts +15 -0
- package/src/i18n/index.tsx +68 -0
- package/src/i18n/runtime.ts +117 -0
- package/src/i18n/translate.ts +65 -0
- package/src/layouts/AgentLayout.tsx +7 -6
- package/src/lib/app-release.ts +15 -10
- package/src/lib/attachments.ts +16 -7
- package/src/lib/browser-dev-electron-mock.ts +34 -34
- package/src/lib/chat-title.ts +3 -2
- package/src/lib/chat-transport.ts +6 -2
- package/src/lib/date-groups.ts +23 -15
- package/src/lib/electron-bridge.ts +4 -5
- package/src/lib/http-bridge.ts +4 -1
- package/src/lib/portable.ts +8 -7
- package/src/lib/show-thinking-content.ts +15 -3
- package/src/main.tsx +25 -2
- package/src/pages/AgentPage.tsx +17 -12
- package/src/pages/ChatPanelPreviewPage.tsx +46 -44
- package/src/pages/SettingsPage.tsx +48 -42
- package/src/vite-env.d.ts +3 -0
- package/vite.base.js +6 -2
|
@@ -141,7 +141,7 @@ export function getDeliverableMeta(filePath: string): DeliverableFileMeta {
|
|
|
141
141
|
case '.tsv':
|
|
142
142
|
case '.numbers':
|
|
143
143
|
return {
|
|
144
|
-
label: '
|
|
144
|
+
label: 'Spreadsheet',
|
|
145
145
|
extBadge: extClean,
|
|
146
146
|
kind: 'spreadsheet',
|
|
147
147
|
themeColor: 'emerald',
|
|
@@ -150,7 +150,7 @@ export function getDeliverableMeta(filePath: string): DeliverableFileMeta {
|
|
|
150
150
|
case '.ppt': // shell-neutral:allow — Office 幻灯片扩展名,不是产品品牌
|
|
151
151
|
case '.key':
|
|
152
152
|
return {
|
|
153
|
-
label: '
|
|
153
|
+
label: 'Presentation',
|
|
154
154
|
extBadge: extClean,
|
|
155
155
|
kind: 'presentation',
|
|
156
156
|
themeColor: 'amber',
|
|
@@ -160,14 +160,14 @@ export function getDeliverableMeta(filePath: string): DeliverableFileMeta {
|
|
|
160
160
|
case '.pages':
|
|
161
161
|
case '.rtf':
|
|
162
162
|
return {
|
|
163
|
-
label: '
|
|
163
|
+
label: 'Document',
|
|
164
164
|
extBadge: extClean,
|
|
165
165
|
kind: 'document',
|
|
166
166
|
themeColor: 'blue',
|
|
167
167
|
};
|
|
168
168
|
case '.pdf':
|
|
169
169
|
return {
|
|
170
|
-
label: '
|
|
170
|
+
label: 'Document',
|
|
171
171
|
extBadge: 'PDF',
|
|
172
172
|
kind: 'pdf',
|
|
173
173
|
themeColor: 'rose',
|
|
@@ -179,7 +179,7 @@ export function getDeliverableMeta(filePath: string): DeliverableFileMeta {
|
|
|
179
179
|
case '.webp':
|
|
180
180
|
case '.svg':
|
|
181
181
|
return {
|
|
182
|
-
label: '
|
|
182
|
+
label: 'Image',
|
|
183
183
|
extBadge: extClean,
|
|
184
184
|
kind: 'image',
|
|
185
185
|
themeColor: 'purple',
|
|
@@ -189,7 +189,7 @@ export function getDeliverableMeta(filePath: string): DeliverableFileMeta {
|
|
|
189
189
|
case '.gz':
|
|
190
190
|
case '.7z':
|
|
191
191
|
return {
|
|
192
|
-
label: '
|
|
192
|
+
label: 'Archive',
|
|
193
193
|
extBadge: extClean,
|
|
194
194
|
kind: 'archive',
|
|
195
195
|
themeColor: 'amber',
|
|
@@ -197,14 +197,14 @@ export function getDeliverableMeta(filePath: string): DeliverableFileMeta {
|
|
|
197
197
|
case '.html':
|
|
198
198
|
case '.htm':
|
|
199
199
|
return {
|
|
200
|
-
label: '
|
|
200
|
+
label: 'Web page',
|
|
201
201
|
extBadge: 'HTML',
|
|
202
202
|
kind: 'document',
|
|
203
203
|
themeColor: 'blue',
|
|
204
204
|
};
|
|
205
205
|
default:
|
|
206
206
|
return {
|
|
207
|
-
label: '
|
|
207
|
+
label: 'File',
|
|
208
208
|
extBadge: extClean,
|
|
209
209
|
kind: 'file',
|
|
210
210
|
themeColor: 'slate',
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
2
2
|
import { LuLoaderCircle, LuSearch } from 'react-icons/lu';
|
|
3
|
+
import { t } from '@/i18n';
|
|
3
4
|
import { isElectron } from '@/lib/electron-bridge';
|
|
4
5
|
import {
|
|
5
6
|
listChatAgentSkills,
|
|
@@ -43,16 +44,16 @@ const TOOL_POLICY_MODES: Array<{
|
|
|
43
44
|
label: string;
|
|
44
45
|
hint: string;
|
|
45
46
|
}> = [
|
|
46
|
-
{ mode: 'all', label: '
|
|
47
|
+
{ mode: 'all', label: 'All tools', hint: 'No restriction. The agent can call every wired tool.' },
|
|
47
48
|
{
|
|
48
49
|
mode: 'allowlist',
|
|
49
|
-
label: '
|
|
50
|
-
hint: '
|
|
50
|
+
label: 'Only checked',
|
|
51
|
+
hint: 'Only checked tools enter the tool list. The agent cannot even see the names of the rest.',
|
|
51
52
|
},
|
|
52
53
|
{
|
|
53
54
|
mode: 'denylist',
|
|
54
|
-
label: '
|
|
55
|
-
hint: '
|
|
55
|
+
label: 'Block checked',
|
|
56
|
+
hint: 'Checked tools are refused. The rest stay available.',
|
|
56
57
|
},
|
|
57
58
|
];
|
|
58
59
|
|
|
@@ -203,24 +204,26 @@ export function AgentCapabilityFields({
|
|
|
203
204
|
<div className="space-y-3 rounded-agent-md border border-agent-border/50 bg-agent-canvas/60 p-3">
|
|
204
205
|
<section className="space-y-1.5">
|
|
205
206
|
<div className="flex items-center justify-between gap-2">
|
|
206
|
-
<h5 className="text-[11px] font-semibold text-agent-foreground"
|
|
207
|
+
<h5 className="text-[11px] font-semibold text-agent-foreground">{t('Skills')}</h5>
|
|
207
208
|
{value.skillIds.length > 0 && (
|
|
208
209
|
<span className="text-[10px] text-agent-muted-foreground">
|
|
209
|
-
|
|
210
|
+
{t('{count} selected', { count: value.skillIds.length })}
|
|
210
211
|
</span>
|
|
211
212
|
)}
|
|
212
213
|
</div>
|
|
213
214
|
<p className="text-[10px] text-agent-muted-foreground">
|
|
214
|
-
|
|
215
|
+
{t(
|
|
216
|
+
"Checked skills stay in this agent's system prompt without waiting for their triggers to match.",
|
|
217
|
+
)}
|
|
215
218
|
</p>
|
|
216
219
|
{loading ? (
|
|
217
220
|
<div className="flex items-center gap-2 py-3 text-[11px] text-agent-muted-foreground">
|
|
218
221
|
<LuLoaderCircle className="h-3 w-3 animate-spin" />
|
|
219
|
-
|
|
222
|
+
{t('Loading skill catalog...')}
|
|
220
223
|
</div>
|
|
221
224
|
) : skills.length === 0 ? (
|
|
222
225
|
<p className="py-2 text-[11px] text-agent-muted-foreground">
|
|
223
|
-
|
|
226
|
+
{t('No skills to check yet. Import them in "Skill settings".')}
|
|
224
227
|
</p>
|
|
225
228
|
) : (
|
|
226
229
|
<>
|
|
@@ -231,7 +234,7 @@ export function AgentCapabilityFields({
|
|
|
231
234
|
type="text"
|
|
232
235
|
value={skillFilter}
|
|
233
236
|
onChange={(event) => setSkillFilter(event.target.value)}
|
|
234
|
-
placeholder=
|
|
237
|
+
placeholder={t('Filter skills')}
|
|
235
238
|
className="h-7 w-full rounded-agent-md border border-agent-border bg-agent-canvas pl-7 pr-2 text-[11px] text-agent-foreground focus:outline-none focus:ring-2 focus:ring-agent-foreground/30"
|
|
236
239
|
data-testid="agent-form-skill-filter"
|
|
237
240
|
/>
|
|
@@ -247,7 +250,7 @@ export function AgentCapabilityFields({
|
|
|
247
250
|
}
|
|
248
251
|
testId={`agent-form-skill-${skill.id}`}
|
|
249
252
|
title={skill.displayName || skill.name}
|
|
250
|
-
badge={skill.layer === 'eager' ? '
|
|
253
|
+
badge={skill.layer === 'eager' ? t('Always on') : t('On demand')}
|
|
251
254
|
description={skill.description}
|
|
252
255
|
/>
|
|
253
256
|
))}
|
|
@@ -261,21 +264,21 @@ export function AgentCapabilityFields({
|
|
|
261
264
|
onChange({ ...value, allowExternalSkills: !value.allowExternalSkills })
|
|
262
265
|
}
|
|
263
266
|
testId="agent-form-allow-external-skills"
|
|
264
|
-
label=
|
|
265
|
-
hint=
|
|
267
|
+
label={t('Allow unchecked skills')}
|
|
268
|
+
hint={t('When off, this agent can only use the skills checked above. No other skill can load.')}
|
|
266
269
|
/>
|
|
267
270
|
<SwitchRow
|
|
268
271
|
checked={value.loadAllSkills}
|
|
269
272
|
onChange={() => onChange({ ...value, loadAllSkills: !value.loadAllSkills })}
|
|
270
273
|
testId="agent-form-load-all-skills"
|
|
271
|
-
label=
|
|
272
|
-
hint="
|
|
274
|
+
label={t('Ignore triggers and load all skills')}
|
|
275
|
+
hint={t('The built-in "Assistant" uses this mode. It makes every turn\'s prompt much longer.')}
|
|
273
276
|
/>
|
|
274
277
|
</div>
|
|
275
278
|
</section>
|
|
276
279
|
|
|
277
280
|
<section className="space-y-1.5 border-t border-agent-border/40 pt-3">
|
|
278
|
-
<h5 className="text-[11px] font-semibold text-agent-foreground"
|
|
281
|
+
<h5 className="text-[11px] font-semibold text-agent-foreground">{t('Tool permissions')}</h5>
|
|
279
282
|
<div className="flex flex-wrap gap-1" data-testid="agent-form-tool-modes">
|
|
280
283
|
{TOOL_POLICY_MODES.map((item) => (
|
|
281
284
|
<button
|
|
@@ -290,12 +293,12 @@ export function AgentCapabilityFields({
|
|
|
290
293
|
: 'bg-agent-muted text-agent-muted-foreground hover:text-agent-foreground'
|
|
291
294
|
}`}
|
|
292
295
|
>
|
|
293
|
-
{item.label}
|
|
296
|
+
{t(item.label)}
|
|
294
297
|
</button>
|
|
295
298
|
))}
|
|
296
299
|
</div>
|
|
297
300
|
{activeMode && (
|
|
298
|
-
<p className="text-[10px] text-agent-muted-foreground">{activeMode.hint}</p>
|
|
301
|
+
<p className="text-[10px] text-agent-muted-foreground">{t(activeMode.hint)}</p>
|
|
299
302
|
)}
|
|
300
303
|
{policy.mode !== 'all' && (
|
|
301
304
|
<>
|
|
@@ -304,7 +307,7 @@ export function AgentCapabilityFields({
|
|
|
304
307
|
className="text-[10px] text-agent-destructive"
|
|
305
308
|
data-testid="agent-form-tool-policy-empty"
|
|
306
309
|
>
|
|
307
|
-
|
|
310
|
+
{t('No tool is checked. Saving treats this as "All tools".')}
|
|
308
311
|
</p>
|
|
309
312
|
)}
|
|
310
313
|
<div className="relative">
|
|
@@ -313,7 +316,7 @@ export function AgentCapabilityFields({
|
|
|
313
316
|
type="text"
|
|
314
317
|
value={toolFilter}
|
|
315
318
|
onChange={(event) => setToolFilter(event.target.value)}
|
|
316
|
-
placeholder=
|
|
319
|
+
placeholder={t('Filter tools')}
|
|
317
320
|
className="h-7 w-full rounded-agent-md border border-agent-border bg-agent-canvas pl-7 pr-2 text-[11px] text-agent-foreground focus:outline-none focus:ring-2 focus:ring-agent-foreground/30"
|
|
318
321
|
data-testid="agent-form-tool-filter"
|
|
319
322
|
/>
|
|
@@ -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>
|