@steerable/agent-shell-web 0.6.27 → 0.6.29
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/components/AgentSidebar.tsx +27 -27
- package/src/components/BrandLockup.tsx +20 -4
- package/src/components/ChatHeader.tsx +5 -15
- package/src/components/LocalLlmSettingsModal.tsx +5 -5
- package/src/components/TerminalPanel.tsx +1 -1
- package/src/components/chat/ApprovalModal.tsx +5 -5
- package/src/components/chat/AskUserQuestionMenu.tsx +13 -13
- package/src/components/chat/AssistantMessage.tsx +122 -36
- package/src/components/chat/ChatInput.tsx +34 -26
- package/src/components/chat/ExecPolicyPicker.tsx +4 -4
- package/src/components/chat/ExecutedActionsCard.tsx +398 -29
- package/src/components/chat/FilePathCode.tsx +2 -1
- package/src/components/chat/LocalChatPanel.tsx +42 -11
- package/src/components/chat/Markdown.tsx +15 -14
- package/src/components/chat/MessageList.tsx +23 -5
- package/src/components/chat/ModelPicker.tsx +6 -6
- package/src/components/chat/OrchestrationChildrenCard.tsx +69 -5
- package/src/components/chat/SessionTodoList.tsx +234 -0
- package/src/components/chat/SessionTreeModal.tsx +3 -3
- package/src/components/chat/SuggestedReplies.tsx +14 -5
- package/src/components/chat/TaskPanelModal.tsx +3 -3
- package/src/components/chat/TaskProcessPanel.tsx +67 -21
- package/src/components/chat/TurnFilesCard.tsx +2 -2
- package/src/components/chat/TurnProcessGroup.tsx +252 -99
- package/src/components/chat/UserMessage.tsx +7 -7
- package/src/components/chat/executed-actions-model.ts +469 -0
- package/src/components/chat/next-steps-tags.ts +28 -0
- package/src/components/chat/orchestration-children-model.ts +120 -0
- package/src/components/chat/process-status.ts +125 -53
- package/src/components/chat/todo-list-model.ts +167 -0
- package/src/components/chat/turn-timeline.ts +123 -11
- package/src/components/settings/AgentsSettingsPanel.tsx +2 -2
- package/src/components/settings/AppearanceSettingsPanel.tsx +3 -3
- package/src/components/settings/DiagnoseSettingsPanel.tsx +2 -2
- package/src/components/settings/InsightsSettingsPanel.tsx +4 -4
- package/src/components/settings/LlmSettingsPanel.tsx +10 -10
- package/src/components/settings/McpSettingsPanel.tsx +3 -3
- package/src/components/settings/ModelIdCombobox.tsx +2 -2
- package/src/components/settings/SecuritySettingsPanel.tsx +1 -1
- package/src/components/settings/SettingsSaveButton.tsx +1 -1
- package/src/components/settings/SkillsSettingsPanel.tsx +2 -2
- package/src/components/settings/TelemetrySettingsPanel.tsx +2 -2
- package/src/components/settings/UsagePanel.tsx +14 -10
- package/src/components/settings/WebSearchSettingsPanel.tsx +2 -2
- package/src/layouts/AgentLayout.tsx +3 -3
- package/src/lib/chat-transport.ts +68 -12
- package/src/lib/local-api.ts +11 -0
- package/src/lib/show-thinking-content.ts +4 -4
- package/src/pages/AgentPage.tsx +82 -11
- package/src/pages/ChatPanelPreviewPage.tsx +26 -4
- package/src/pages/SettingsPage.tsx +39 -39
- package/src/styles/index.css +12 -2
package/src/pages/AgentPage.tsx
CHANGED
|
@@ -23,9 +23,17 @@ import {
|
|
|
23
23
|
type ExecPolicy,
|
|
24
24
|
} from "@/lib/exec-policy";
|
|
25
25
|
import type { ExecutedAction } from "@/components/chat/ExecutedActionsCard";
|
|
26
|
+
import { inspectTaskTitle, type InspectTaskInput } from "@/components/chat/executed-actions-model";
|
|
26
27
|
import type { ChildInfo } from "@/components/chat/OrchestrationChildrenCard";
|
|
27
|
-
import {
|
|
28
|
+
import {
|
|
29
|
+
extractPersistedOrchestrationChildren,
|
|
30
|
+
foldOrchestrationChildEvents,
|
|
31
|
+
} from "@/components/chat/orchestration-children-model";
|
|
28
32
|
import { parseTurnBlocks, type TurnBlock } from "@/components/chat/turn-timeline";
|
|
33
|
+
import {
|
|
34
|
+
parseLlmSpeedPayload,
|
|
35
|
+
type LlmSpeedSnapshot,
|
|
36
|
+
} from "@/components/chat/process-status";
|
|
29
37
|
import { parseTurnFiles, type TurnFile } from "@/components/chat/turn-files";
|
|
30
38
|
import { readPersistedDurationMs } from "@/components/chat/elapsed";
|
|
31
39
|
import {
|
|
@@ -46,7 +54,6 @@ import {
|
|
|
46
54
|
type ChatLiveStream,
|
|
47
55
|
type LocalChatMessage,
|
|
48
56
|
type LocalProject,
|
|
49
|
-
type LocalTask,
|
|
50
57
|
} from "@/lib/local-api";
|
|
51
58
|
import type { AgentOutletContext } from "@/layouts/AgentLayout";
|
|
52
59
|
import {
|
|
@@ -233,6 +240,8 @@ function AgentChatLoader({
|
|
|
233
240
|
const [initialTurnFiles, setInitialTurnFiles] = useState<
|
|
234
241
|
Record<string, TurnFile[]>
|
|
235
242
|
>({});
|
|
243
|
+
const [initialOrchestrationChildren, setInitialOrchestrationChildren] =
|
|
244
|
+
useState<Record<string, ChildInfo[]>>({});
|
|
236
245
|
const [initialSuggestedReplies, setInitialSuggestedReplies] =
|
|
237
246
|
useState<SuggestedRepliesState | null>(null);
|
|
238
247
|
// W7-1: 后端在 messages 响应里下发 interrupted(上一轮崩溃/强杀中断)。
|
|
@@ -270,6 +279,9 @@ function AgentChatLoader({
|
|
|
270
279
|
setInitialTimelines(extractPersistedTimelines(response.messages));
|
|
271
280
|
setInitialDurations(extractPersistedDurations(response.messages));
|
|
272
281
|
setInitialTurnFiles(extractPersistedTurnFiles(response.messages));
|
|
282
|
+
setInitialOrchestrationChildren(
|
|
283
|
+
extractPersistedOrchestrationChildren(response.messages),
|
|
284
|
+
);
|
|
273
285
|
setInitialSuggestedReplies(
|
|
274
286
|
extractLatestSuggestedReplies(
|
|
275
287
|
ordered as ChatMessageWithMetadata[],
|
|
@@ -285,6 +297,7 @@ function AgentChatLoader({
|
|
|
285
297
|
setInitialTimelines({});
|
|
286
298
|
setInitialDurations({});
|
|
287
299
|
setInitialTurnFiles({});
|
|
300
|
+
setInitialOrchestrationChildren({});
|
|
288
301
|
setInitialSuggestedReplies(null);
|
|
289
302
|
setInitialInterrupted(false);
|
|
290
303
|
setInitialLiveStream({ active: false });
|
|
@@ -297,7 +310,7 @@ function AgentChatLoader({
|
|
|
297
310
|
|
|
298
311
|
if (initialMessages === null) {
|
|
299
312
|
return (
|
|
300
|
-
<div className="flex h-full w-full items-center justify-center text-
|
|
313
|
+
<div className="flex h-full w-full items-center justify-center text-xs text-agent-muted-foreground">
|
|
301
314
|
加载对话历史…
|
|
302
315
|
</div>
|
|
303
316
|
);
|
|
@@ -311,6 +324,7 @@ function AgentChatLoader({
|
|
|
311
324
|
initialTimelines={initialTimelines}
|
|
312
325
|
initialDurations={initialDurations}
|
|
313
326
|
initialTurnFiles={initialTurnFiles}
|
|
327
|
+
initialOrchestrationChildren={initialOrchestrationChildren}
|
|
314
328
|
initialSuggestedReplies={initialSuggestedReplies}
|
|
315
329
|
initialInterrupted={initialInterrupted}
|
|
316
330
|
initialLiveStream={initialLiveStream}
|
|
@@ -327,6 +341,7 @@ interface AgentChatViewProps {
|
|
|
327
341
|
initialTimelines: Record<string, TurnBlock[]>;
|
|
328
342
|
initialDurations: Record<string, number>;
|
|
329
343
|
initialTurnFiles: Record<string, TurnFile[]>;
|
|
344
|
+
initialOrchestrationChildren: Record<string, ChildInfo[]>;
|
|
330
345
|
initialSuggestedReplies: SuggestedRepliesState | null;
|
|
331
346
|
/** W7-1: 打开会话时上一轮处于中断态(崩溃/强杀,无完成记录)。 */
|
|
332
347
|
initialInterrupted: boolean;
|
|
@@ -343,6 +358,7 @@ function AgentChatView({
|
|
|
343
358
|
initialTimelines,
|
|
344
359
|
initialDurations,
|
|
345
360
|
initialTurnFiles,
|
|
361
|
+
initialOrchestrationChildren,
|
|
346
362
|
initialSuggestedReplies,
|
|
347
363
|
initialInterrupted,
|
|
348
364
|
initialLiveStream,
|
|
@@ -386,8 +402,14 @@ function AgentChatView({
|
|
|
386
402
|
dismissFinished,
|
|
387
403
|
} = useChatTasks(chatId);
|
|
388
404
|
const handleInspectTask = useCallback(
|
|
389
|
-
(task:
|
|
390
|
-
ctx.inspectTask({
|
|
405
|
+
(task: InspectTaskInput) => {
|
|
406
|
+
ctx.inspectTask({
|
|
407
|
+
id: task.id,
|
|
408
|
+
chatId: task.chatId,
|
|
409
|
+
title: inspectTaskTitle(task),
|
|
410
|
+
...(task.recordId ? { recordId: task.recordId } : {}),
|
|
411
|
+
...(task.live ? { live: true } : {}),
|
|
412
|
+
});
|
|
391
413
|
},
|
|
392
414
|
[ctx],
|
|
393
415
|
);
|
|
@@ -449,11 +471,18 @@ function AgentChatView({
|
|
|
449
471
|
number | undefined
|
|
450
472
|
>(undefined);
|
|
451
473
|
const pendingDurationMessageIdRef = useRef<string | null>(null);
|
|
474
|
+
const [currentLlmSpeed, setCurrentLlmSpeed] = useState<LlmSpeedSnapshot | undefined>(
|
|
475
|
+
undefined,
|
|
476
|
+
);
|
|
477
|
+
const currentLlmSpeedRef = useRef<LlmSpeedSnapshot | null>(null);
|
|
478
|
+
const [llmSpeedByMessageId, setLlmSpeedByMessageId] = useState<
|
|
479
|
+
Record<string, LlmSpeedSnapshot>
|
|
480
|
+
>({});
|
|
452
481
|
// P3.1 编排:本轮子代理生命周期(orchestration_child SSE 事件累积),
|
|
453
482
|
// 与 currentTurnActions 同款 reconcile 模式(message_id 落库后按键归档)。
|
|
454
483
|
const [currentTurnChildren, setCurrentTurnChildren] = useState<ChildInfo[]>([]);
|
|
455
484
|
const [orchestrationChildrenByMessageId, setOrchestrationChildrenByMessageId] =
|
|
456
|
-
useState<Record<string, ChildInfo[]>>(
|
|
485
|
+
useState<Record<string, ChildInfo[]>>(initialOrchestrationChildren);
|
|
457
486
|
const pendingChildrenRef = useRef<ChildInfo[]>([]);
|
|
458
487
|
// Round counter for the in-flight turn. Bumped on every `round_end` event
|
|
459
488
|
// emitted by chat-transport (was previously suppressed). Driving this state
|
|
@@ -519,6 +548,13 @@ function AgentChatView({
|
|
|
519
548
|
}
|
|
520
549
|
return;
|
|
521
550
|
}
|
|
551
|
+
if (ev.event === "llm_speed") {
|
|
552
|
+
const snap = parseLlmSpeedPayload(ev.payload);
|
|
553
|
+
if (!snap) return;
|
|
554
|
+
currentLlmSpeedRef.current = snap;
|
|
555
|
+
setCurrentLlmSpeed(snap);
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
522
558
|
if (ev.event === "turn_files") {
|
|
523
559
|
// 回合收尾时后端发一次(在 message_id 之前);先挂 pending,
|
|
524
560
|
// message_id 到达时随其他队列一起归档。
|
|
@@ -554,6 +590,8 @@ function AgentChatView({
|
|
|
554
590
|
task: ev.payload?.task as string | undefined,
|
|
555
591
|
depth: ev.payload?.depth as number | undefined,
|
|
556
592
|
profile: ev.payload?.profile as string | undefined,
|
|
593
|
+
// 子代理自己的 durable record:委派行靠它跳转右侧过程栏。
|
|
594
|
+
recordId: ev.payload?.recordId as string | undefined,
|
|
557
595
|
status: "running",
|
|
558
596
|
},
|
|
559
597
|
];
|
|
@@ -583,6 +621,13 @@ function AgentChatView({
|
|
|
583
621
|
const messageId = ev.payload?.messageId as string | undefined;
|
|
584
622
|
if (!messageId) return;
|
|
585
623
|
pendingDurationMessageIdRef.current = messageId;
|
|
624
|
+
const queuedSpeed = currentLlmSpeedRef.current;
|
|
625
|
+
if (queuedSpeed) {
|
|
626
|
+
setLlmSpeedByMessageId((prev) => ({
|
|
627
|
+
...prev,
|
|
628
|
+
[messageId]: queuedSpeed,
|
|
629
|
+
}));
|
|
630
|
+
}
|
|
586
631
|
const queued = pendingActionsRef.current;
|
|
587
632
|
if (queued.length > 0) {
|
|
588
633
|
setExecutedActionsByMessageId((prev) => ({
|
|
@@ -727,6 +772,25 @@ function AgentChatView({
|
|
|
727
772
|
}
|
|
728
773
|
return next;
|
|
729
774
|
});
|
|
775
|
+
const snap = currentLlmSpeedRef.current;
|
|
776
|
+
if (snap) {
|
|
777
|
+
const frozen: LlmSpeedSnapshot = {
|
|
778
|
+
...snap,
|
|
779
|
+
live: false,
|
|
780
|
+
requestStartedAt: null,
|
|
781
|
+
elapsedMs: snap.elapsedMs,
|
|
782
|
+
};
|
|
783
|
+
currentLlmSpeedRef.current = frozen;
|
|
784
|
+
setCurrentLlmSpeed(frozen);
|
|
785
|
+
setLlmSpeedByMessageId((prev) => {
|
|
786
|
+
const next = { ...prev };
|
|
787
|
+
if (lastAssistant) next[lastAssistant.id] = frozen;
|
|
788
|
+
if (pendingDurationMessageIdRef.current) {
|
|
789
|
+
next[pendingDurationMessageIdRef.current] = frozen;
|
|
790
|
+
}
|
|
791
|
+
return next;
|
|
792
|
+
});
|
|
793
|
+
}
|
|
730
794
|
}
|
|
731
795
|
}
|
|
732
796
|
wasStreamingRef.current = isStreaming;
|
|
@@ -770,6 +834,8 @@ function AgentChatView({
|
|
|
770
834
|
turnStartedAtRef.current = started;
|
|
771
835
|
setCurrentTurnStartedAtMs(started);
|
|
772
836
|
pendingDurationMessageIdRef.current = null;
|
|
837
|
+
currentLlmSpeedRef.current = null;
|
|
838
|
+
setCurrentLlmSpeed(undefined);
|
|
773
839
|
// 新一轮开始:记录本轮是否为 plan 模式,并隐藏上一份计划的操作条。
|
|
774
840
|
planTurnRef.current = input.metadata?.mode === "plan";
|
|
775
841
|
setPlanReady(false);
|
|
@@ -842,6 +908,8 @@ function AgentChatView({
|
|
|
842
908
|
turnStartedAtRef.current = started;
|
|
843
909
|
setCurrentTurnStartedAtMs(started);
|
|
844
910
|
pendingDurationMessageIdRef.current = null;
|
|
911
|
+
currentLlmSpeedRef.current = null;
|
|
912
|
+
setCurrentLlmSpeed(undefined);
|
|
845
913
|
planTurnRef.current = mode === "plan";
|
|
846
914
|
setPlanReady(false);
|
|
847
915
|
const metadata = {
|
|
@@ -987,7 +1055,7 @@ function AgentChatView({
|
|
|
987
1055
|
return (
|
|
988
1056
|
<div className="flex h-full w-full flex-col">
|
|
989
1057
|
{hydrationError && (
|
|
990
|
-
<div className="border-b border-agent-border bg-agent-muted/60 px-
|
|
1058
|
+
<div className="border-b border-agent-border bg-agent-muted/60 px-2.5 py-1 text-xs text-agent-destructive">
|
|
991
1059
|
加载对话历史失败:{hydrationError}
|
|
992
1060
|
</div>
|
|
993
1061
|
)}
|
|
@@ -1001,6 +1069,7 @@ function AgentChatView({
|
|
|
1001
1069
|
pendingFollowUps={pendingFollowUps.map((m) => m.content)}
|
|
1002
1070
|
onRemoveFollowUp={removeFollowUp}
|
|
1003
1071
|
className="flex-1"
|
|
1072
|
+
chatId={chatId}
|
|
1004
1073
|
emptyHero={{
|
|
1005
1074
|
title: BRAND_NAME,
|
|
1006
1075
|
subtitle: '输入消息,直接开始一段新对话。',
|
|
@@ -1035,6 +1104,8 @@ function AgentChatView({
|
|
|
1035
1104
|
currentTurnTimeline={effectiveCurrentTurnTimeline}
|
|
1036
1105
|
currentTurnStartedAtMs={currentTurnStartedAtMs}
|
|
1037
1106
|
durationByMessageId={durationByMessageId}
|
|
1107
|
+
currentLlmSpeed={currentLlmSpeed}
|
|
1108
|
+
llmSpeedByMessageId={llmSpeedByMessageId}
|
|
1038
1109
|
turnFilesByMessageId={turnFilesByMessageId}
|
|
1039
1110
|
currentTurnFiles={currentTurnFiles}
|
|
1040
1111
|
currentTurnChildren={effectiveCurrentTurnChildren}
|
|
@@ -1083,7 +1154,7 @@ function AgentChatView({
|
|
|
1083
1154
|
onTrustChanged={fetchProjects}
|
|
1084
1155
|
/>
|
|
1085
1156
|
{planReady && !isStreaming ? (
|
|
1086
|
-
<div className="mx-
|
|
1157
|
+
<div className="mx-2.5 mb-1 flex items-center justify-between gap-2 rounded-agent-md border border-amber-400/50 bg-amber-400/10 px-2.5 py-1.5 text-xs">
|
|
1087
1158
|
<div className="flex items-center gap-2 text-amber-700 dark:text-amber-300">
|
|
1088
1159
|
<LuListChecks className="h-4 w-4 shrink-0" />
|
|
1089
1160
|
<span>计划已生成。确认无误后可切换到 Agent 模式开始执行。</span>
|
|
@@ -1264,15 +1335,15 @@ function EmptyChatGate() {
|
|
|
1264
1335
|
|
|
1265
1336
|
return (
|
|
1266
1337
|
<div
|
|
1267
|
-
className="flex h-full w-full items-center justify-center p-
|
|
1338
|
+
className="flex h-full w-full items-center justify-center p-3 sm:p-5"
|
|
1268
1339
|
data-testid="empty-chat-home"
|
|
1269
1340
|
>
|
|
1270
|
-
<div className="flex w-full max-w-
|
|
1341
|
+
<div className="flex w-full max-w-3xl flex-col items-center gap-4">
|
|
1271
1342
|
<div className="text-center">
|
|
1272
1343
|
<h1 className="text-xl font-semibold tracking-tight text-agent-foreground">
|
|
1273
1344
|
{BRAND_NAME}
|
|
1274
1345
|
</h1>
|
|
1275
|
-
<p className="mt-1.5 text-
|
|
1346
|
+
<p className="mt-1.5 text-xs text-agent-muted-foreground">
|
|
1276
1347
|
输入消息,直接开始一段新对话。
|
|
1277
1348
|
</p>
|
|
1278
1349
|
</div>
|
|
@@ -82,7 +82,28 @@ Tests: 1 failed, 2 passed, 3 total
|
|
|
82
82
|
},
|
|
83
83
|
];
|
|
84
84
|
|
|
85
|
+
const MOCK_TODOS = [
|
|
86
|
+
{ id: 'survey', content: '检查服务连通性', status: 'completed' as const },
|
|
87
|
+
{ id: 'test', content: '跑本目录测试套件', status: 'in_progress' as const },
|
|
88
|
+
{ id: 'fix', content: '修失败的断言颜色', status: 'pending' as const },
|
|
89
|
+
];
|
|
90
|
+
|
|
91
|
+
const MOCK_TODO_ACTION: ExecutedAction = {
|
|
92
|
+
tool: 'todo_write',
|
|
93
|
+
arguments: { todos: MOCK_TODOS },
|
|
94
|
+
result: {
|
|
95
|
+
success: true,
|
|
96
|
+
data: {
|
|
97
|
+
value: {
|
|
98
|
+
todos: MOCK_TODOS,
|
|
99
|
+
summary: { total: 3, pending: 1, inProgress: 1, completed: 1 },
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
|
|
85
105
|
const MOCK_ACTIONS: ExecutedAction[] = [
|
|
106
|
+
MOCK_TODO_ACTION,
|
|
86
107
|
{
|
|
87
108
|
tool: 'check_connection',
|
|
88
109
|
arguments: { host: '127.0.0.1', port: 8765 },
|
|
@@ -107,11 +128,12 @@ const MOCK_ACTIONS: ExecutedAction[] = [
|
|
|
107
128
|
|
|
108
129
|
const MOCK_TIMELINE: TurnBlock[] = [
|
|
109
130
|
{ type: 'reasoning', content: '先检查服务连通性,再跑测试套件。' },
|
|
110
|
-
{ type: 'tools', actions: [
|
|
111
|
-
{ type: 'text', content: '连接正常。接下来跑测试套件。' },
|
|
131
|
+
{ type: 'tools', actions: [MOCK_TODO_ACTION] },
|
|
112
132
|
{ type: 'tools', actions: [MOCK_ACTIONS[1]] },
|
|
113
|
-
{ type: '
|
|
133
|
+
{ type: 'text', content: '连接正常。接下来跑测试套件。' },
|
|
114
134
|
{ type: 'tools', actions: [MOCK_ACTIONS[2]] },
|
|
135
|
+
{ type: 'reasoning', content: '测试失败,读一下失败文件再给建议。' },
|
|
136
|
+
{ type: 'tools', actions: [MOCK_ACTIONS[3]] },
|
|
115
137
|
{ type: 'text', content: MOCK_MESSAGES[1].content ?? '' },
|
|
116
138
|
];
|
|
117
139
|
|
|
@@ -284,7 +306,7 @@ export function ChatPanelPreviewPage() {
|
|
|
284
306
|
|
|
285
307
|
return (
|
|
286
308
|
<div className="flex h-full w-full flex-col bg-agent-muted/30">
|
|
287
|
-
<div className="flex shrink-0 items-center gap-2 border-b border-agent-border bg-agent-canvas px-
|
|
309
|
+
<div className="flex shrink-0 items-center gap-2 border-b border-agent-border bg-agent-canvas px-2.5 py-1.5 text-xs">
|
|
288
310
|
<span className="text-agent-muted-foreground">
|
|
289
311
|
/preview/chat (dev harness)
|
|
290
312
|
</span>
|
|
@@ -59,8 +59,8 @@ export function SettingsPage() {
|
|
|
59
59
|
|
|
60
60
|
return (
|
|
61
61
|
<div className="flex h-full w-full flex-col overflow-hidden">
|
|
62
|
-
<header className="flex h-
|
|
63
|
-
<h1 className="text-
|
|
62
|
+
<header className="flex h-9 flex-shrink-0 items-center justify-between gap-2 border-b border-agent-border px-2.5">
|
|
63
|
+
<h1 className="text-xs font-semibold text-agent-foreground">{title}</h1>
|
|
64
64
|
{section === 'general' && (
|
|
65
65
|
<SettingsSaveButton
|
|
66
66
|
testId="settings-header-save"
|
|
@@ -73,7 +73,7 @@ export function SettingsPage() {
|
|
|
73
73
|
</header>
|
|
74
74
|
|
|
75
75
|
<div className="min-h-0 flex-1 overflow-y-auto">
|
|
76
|
-
<div className="mx-auto max-w-3xl space-y-
|
|
76
|
+
<div className="mx-auto max-w-3xl space-y-4 px-3 py-3">
|
|
77
77
|
{!isElectron() && (
|
|
78
78
|
<p className="rounded-agent-md border border-agent-destructive/20 bg-agent-destructive/10 p-2.5 text-xs text-agent-destructive">
|
|
79
79
|
浏览器预览模式 — 没有 Electron IPC 桥接,部分设置不可用。
|
|
@@ -81,9 +81,9 @@ export function SettingsPage() {
|
|
|
81
81
|
)}
|
|
82
82
|
|
|
83
83
|
{section === 'skills' && (
|
|
84
|
-
<section className="space-y-
|
|
85
|
-
<h2 className="flex items-center gap-1.5 text-
|
|
86
|
-
<LuBlocks className="h-
|
|
84
|
+
<section className="space-y-2" data-testid="settings-section-skills">
|
|
85
|
+
<h2 className="flex items-center gap-1.5 text-xs font-semibold text-agent-foreground">
|
|
86
|
+
<LuBlocks className="h-3.5 w-3.5 text-agent-muted-foreground" />
|
|
87
87
|
本地技能
|
|
88
88
|
</h2>
|
|
89
89
|
<SkillsSettingsPanel />
|
|
@@ -91,9 +91,9 @@ export function SettingsPage() {
|
|
|
91
91
|
)}
|
|
92
92
|
|
|
93
93
|
{section === 'mcp' && (
|
|
94
|
-
<section className="space-y-
|
|
95
|
-
<h2 className="flex items-center gap-1.5 text-
|
|
96
|
-
<LuPlug className="h-
|
|
94
|
+
<section className="space-y-2" data-testid="settings-section-mcp">
|
|
95
|
+
<h2 className="flex items-center gap-1.5 text-xs font-semibold text-agent-foreground">
|
|
96
|
+
<LuPlug className="h-3.5 w-3.5 text-agent-muted-foreground" />
|
|
97
97
|
MCP 服务
|
|
98
98
|
</h2>
|
|
99
99
|
<McpSettingsPanel />
|
|
@@ -101,9 +101,9 @@ export function SettingsPage() {
|
|
|
101
101
|
)}
|
|
102
102
|
|
|
103
103
|
{section === 'agents' && (
|
|
104
|
-
<section className="space-y-
|
|
105
|
-
<h2 className="flex items-center gap-1.5 text-
|
|
106
|
-
<LuBot className="h-
|
|
104
|
+
<section className="space-y-2" data-testid="settings-section-agents">
|
|
105
|
+
<h2 className="flex items-center gap-1.5 text-xs font-semibold text-agent-foreground">
|
|
106
|
+
<LuBot className="h-3.5 w-3.5 text-agent-muted-foreground" />
|
|
107
107
|
智能体
|
|
108
108
|
</h2>
|
|
109
109
|
<AgentsSettingsPanel onCatalogChange={catalog?.refreshAgents} />
|
|
@@ -112,17 +112,17 @@ export function SettingsPage() {
|
|
|
112
112
|
|
|
113
113
|
{section === 'general' && (
|
|
114
114
|
<>
|
|
115
|
-
<section className="space-y-
|
|
116
|
-
<h2 className="flex items-center gap-1.5 text-
|
|
117
|
-
<LuMonitor className="h-
|
|
115
|
+
<section className="space-y-2" data-testid="settings-section-appearance">
|
|
116
|
+
<h2 className="flex items-center gap-1.5 text-xs font-semibold text-agent-foreground">
|
|
117
|
+
<LuMonitor className="h-3.5 w-3.5 text-agent-muted-foreground" />
|
|
118
118
|
界面
|
|
119
119
|
</h2>
|
|
120
120
|
<AppearanceSettingsPanel />
|
|
121
121
|
</section>
|
|
122
122
|
|
|
123
|
-
<section className="space-y-
|
|
124
|
-
<h2 className="flex items-center gap-1.5 text-
|
|
125
|
-
<LuSettings className="h-
|
|
123
|
+
<section className="space-y-2" data-testid="settings-section-llm">
|
|
124
|
+
<h2 className="flex items-center gap-1.5 text-xs font-semibold text-agent-foreground">
|
|
125
|
+
<LuSettings className="h-3.5 w-3.5 text-agent-muted-foreground" />
|
|
126
126
|
本地模型设置
|
|
127
127
|
</h2>
|
|
128
128
|
<LlmSettingsPanel
|
|
@@ -132,49 +132,49 @@ export function SettingsPage() {
|
|
|
132
132
|
/>
|
|
133
133
|
</section>
|
|
134
134
|
|
|
135
|
-
<section className="space-y-
|
|
136
|
-
<h2 className="flex items-center gap-1.5 text-
|
|
137
|
-
<LuSearch className="h-
|
|
135
|
+
<section className="space-y-2" data-testid="settings-section-web-search">
|
|
136
|
+
<h2 className="flex items-center gap-1.5 text-xs font-semibold text-agent-foreground">
|
|
137
|
+
<LuSearch className="h-3.5 w-3.5 text-agent-muted-foreground" />
|
|
138
138
|
网络搜索
|
|
139
139
|
</h2>
|
|
140
140
|
<WebSearchSettingsPanel />
|
|
141
141
|
</section>
|
|
142
142
|
|
|
143
|
-
<section className="space-y-
|
|
144
|
-
<h2 className="flex items-center gap-1.5 text-
|
|
145
|
-
<LuChartBar className="h-
|
|
143
|
+
<section className="space-y-2" data-testid="settings-section-usage">
|
|
144
|
+
<h2 className="flex items-center gap-1.5 text-xs font-semibold text-agent-foreground">
|
|
145
|
+
<LuChartBar className="h-3.5 w-3.5 text-agent-muted-foreground" />
|
|
146
146
|
用量与成本
|
|
147
147
|
</h2>
|
|
148
148
|
<UsagePanel />
|
|
149
149
|
</section>
|
|
150
150
|
|
|
151
|
-
<section className="space-y-
|
|
152
|
-
<h2 className="flex items-center gap-1.5 text-
|
|
153
|
-
<LuNetwork className="h-
|
|
151
|
+
<section className="space-y-2" data-testid="settings-section-diagnose">
|
|
152
|
+
<h2 className="flex items-center gap-1.5 text-xs font-semibold text-agent-foreground">
|
|
153
|
+
<LuNetwork className="h-3.5 w-3.5 text-agent-muted-foreground" />
|
|
154
154
|
链路诊断
|
|
155
155
|
</h2>
|
|
156
156
|
<DiagnoseSettingsPanel />
|
|
157
157
|
</section>
|
|
158
158
|
|
|
159
|
-
<section className="space-y-
|
|
160
|
-
<h2 className="flex items-center gap-1.5 text-
|
|
161
|
-
<LuShieldCheck className="h-
|
|
159
|
+
<section className="space-y-2" data-testid="settings-section-security">
|
|
160
|
+
<h2 className="flex items-center gap-1.5 text-xs font-semibold text-agent-foreground">
|
|
161
|
+
<LuShieldCheck className="h-3.5 w-3.5 text-agent-muted-foreground" />
|
|
162
162
|
安全
|
|
163
163
|
</h2>
|
|
164
164
|
<SecuritySettingsPanel />
|
|
165
165
|
</section>
|
|
166
166
|
|
|
167
|
-
<section className="space-y-
|
|
168
|
-
<h2 className="flex items-center gap-1.5 text-
|
|
169
|
-
<LuChartBar className="h-
|
|
167
|
+
<section className="space-y-2" data-testid="settings-section-insights">
|
|
168
|
+
<h2 className="flex items-center gap-1.5 text-xs font-semibold text-agent-foreground">
|
|
169
|
+
<LuChartBar className="h-3.5 w-3.5 text-agent-muted-foreground" />
|
|
170
170
|
帮助改进产品
|
|
171
171
|
</h2>
|
|
172
172
|
<InsightsSettingsPanel />
|
|
173
173
|
</section>
|
|
174
174
|
|
|
175
|
-
<section className="space-y-
|
|
176
|
-
<h2 className="flex items-center gap-1.5 text-
|
|
177
|
-
<LuActivity className="h-
|
|
175
|
+
<section className="space-y-2" data-testid="settings-section-telemetry">
|
|
176
|
+
<h2 className="flex items-center gap-1.5 text-xs font-semibold text-agent-foreground">
|
|
177
|
+
<LuActivity className="h-3.5 w-3.5 text-agent-muted-foreground" />
|
|
178
178
|
遥测(OTLP)
|
|
179
179
|
</h2>
|
|
180
180
|
<TelemetrySettingsPanel />
|
|
@@ -185,12 +185,12 @@ export function SettingsPage() {
|
|
|
185
185
|
{getPackSettingsPanels().map((panel) => (
|
|
186
186
|
<section
|
|
187
187
|
key={panel.panelId}
|
|
188
|
-
className="space-y-
|
|
188
|
+
className="space-y-2"
|
|
189
189
|
data-testid={`settings-section-${panel.panelId}`}
|
|
190
190
|
>
|
|
191
|
-
<h2 className="flex items-center gap-1.5 text-
|
|
191
|
+
<h2 className="flex items-center gap-1.5 text-xs font-semibold text-agent-foreground">
|
|
192
192
|
{panel.Icon ? (
|
|
193
|
-
<panel.Icon className="h-
|
|
193
|
+
<panel.Icon className="h-3.5 w-3.5 text-agent-muted-foreground" />
|
|
194
194
|
) : null}
|
|
195
195
|
{panel.title}
|
|
196
196
|
</h2>
|
package/src/styles/index.css
CHANGED
|
@@ -58,8 +58,8 @@ body {
|
|
|
58
58
|
margin: 0;
|
|
59
59
|
font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto,
|
|
60
60
|
"Helvetica Neue", Arial, "PingFang SC", "Microsoft YaHei", sans-serif;
|
|
61
|
-
font-size:
|
|
62
|
-
line-height: 1.
|
|
61
|
+
font-size: 12px;
|
|
62
|
+
line-height: 1.45;
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
/*
|
|
@@ -87,6 +87,16 @@ body {
|
|
|
87
87
|
white-space: nowrap;
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
+
/* 12px 字号下收紧工具行:框架卡默认还是 px-3 py-2。 */
|
|
91
|
+
.steerable-tool-execution > button {
|
|
92
|
+
gap: 0.375rem;
|
|
93
|
+
padding: 0.25rem 0.625rem;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.steerable-tool-execution > button + div {
|
|
97
|
+
padding: 0.375rem 0.625rem;
|
|
98
|
+
}
|
|
99
|
+
|
|
90
100
|
/*
|
|
91
101
|
* ChatInput overlay: the contenteditable must keep a real text fill.
|
|
92
102
|
* macOS Pinyin inspects fill during the first letter's keydown — before
|