@steerable/agent-shell-web 0.6.27 → 0.6.28
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 +24 -24
- package/src/components/BrandLockup.tsx +1 -1
- 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 +41 -13
- package/src/components/chat/ChatInput.tsx +24 -24
- package/src/components/chat/ExecPolicyPicker.tsx +4 -4
- package/src/components/chat/ExecutedActionsCard.tsx +10 -3
- package/src/components/chat/FilePathCode.tsx +2 -1
- package/src/components/chat/LocalChatPanel.tsx +39 -4
- package/src/components/chat/Markdown.tsx +15 -14
- package/src/components/chat/MessageList.tsx +16 -3
- package/src/components/chat/ModelPicker.tsx +6 -6
- package/src/components/chat/SessionTodoList.tsx +137 -0
- package/src/components/chat/SessionTreeModal.tsx +3 -3
- package/src/components/chat/SuggestedReplies.tsx +5 -4
- package/src/components/chat/TaskPanelModal.tsx +3 -3
- package/src/components/chat/TaskProcessPanel.tsx +5 -4
- package/src/components/chat/TurnFilesCard.tsx +2 -2
- package/src/components/chat/TurnProcessGroup.tsx +226 -99
- package/src/components/chat/UserMessage.tsx +4 -4
- package/src/components/chat/next-steps-tags.ts +28 -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 +100 -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/show-thinking-content.ts +4 -4
- package/src/pages/AgentPage.tsx +56 -6
- package/src/pages/ChatPanelPreviewPage.tsx +26 -4
- package/src/pages/SettingsPage.tsx +39 -39
- package/src/styles/index.css +12 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@steerable/agent-shell-web",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.28",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Steerable framework — product-neutral renderer SPA source for @steerable/agent-shell (React + Vite). Consumed by product web entry points via the `@/` alias (anchored at this package's src/) and the createProductViteConfig factory. Published as a source package (src/), plus the compiled vite.base config factory (vite.base.js + .d.ts) for Node-side config loading.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -46,8 +46,8 @@
|
|
|
46
46
|
"react-router-dom": "^6.30.0",
|
|
47
47
|
"rehype-raw": "^7.0.0",
|
|
48
48
|
"remark-gfm": "^4.0.1",
|
|
49
|
-
"@steerable/agent-protocol": "0.6.
|
|
50
|
-
"@steerable/agent-ui": "0.6.
|
|
49
|
+
"@steerable/agent-protocol": "0.6.28",
|
|
50
|
+
"@steerable/agent-ui": "0.6.28"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"@tailwindcss/vite": "^4.3.0",
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
"typescript": "^5.8.3",
|
|
61
61
|
"vite": "^5.4.21",
|
|
62
62
|
"vitest": "^3.2.4",
|
|
63
|
-
"@steerable/pack-sdk": "0.6.
|
|
63
|
+
"@steerable/pack-sdk": "0.6.28"
|
|
64
64
|
},
|
|
65
65
|
"scripts": {
|
|
66
66
|
"gen": "echo agent-shell-web",
|
|
@@ -477,7 +477,7 @@ export function AgentSidebar({
|
|
|
477
477
|
navigate(`/agent/${chat.id}`);
|
|
478
478
|
}}
|
|
479
479
|
className={[
|
|
480
|
-
'flex h-
|
|
480
|
+
'flex h-7 w-full min-w-0 items-center gap-1.5 rounded-full px-2.5 text-xs transition-colors duration-200',
|
|
481
481
|
isCurrent
|
|
482
482
|
? 'bg-agent-foreground/10 font-medium text-agent-foreground'
|
|
483
483
|
: 'text-agent-muted-foreground hover:bg-agent-foreground/5 hover:text-agent-foreground',
|
|
@@ -620,7 +620,7 @@ export function AgentSidebar({
|
|
|
620
620
|
return (
|
|
621
621
|
<div className="flex h-full w-full flex-col border-r border-agent-border/60 bg-agent-muted/70 backdrop-blur-md">
|
|
622
622
|
{/* ───── Brand + actions ───── */}
|
|
623
|
-
<div className="flex h-
|
|
623
|
+
<div className="flex h-9 flex-shrink-0 items-center justify-between px-2.5">
|
|
624
624
|
<BrandLockup />
|
|
625
625
|
<div className="flex items-center gap-1">
|
|
626
626
|
<button
|
|
@@ -638,12 +638,12 @@ export function AgentSidebar({
|
|
|
638
638
|
{/* ───── 会话 ───── */}
|
|
639
639
|
<div className="flex min-h-0 flex-1 flex-col overflow-hidden">
|
|
640
640
|
{/* 新对话 + 智能体 / Skill / MCP 设置入口 — 横版行按钮,各自打开独立设置页 */}
|
|
641
|
-
<div className="flex-shrink-0 space-y-0.5 px-
|
|
641
|
+
<div className="flex-shrink-0 space-y-0.5 px-2.5 pb-0.5">
|
|
642
642
|
<button
|
|
643
643
|
type="button"
|
|
644
644
|
onClick={() => handleOpenNewChat()}
|
|
645
645
|
className={[
|
|
646
|
-
'flex h-
|
|
646
|
+
'flex h-7 w-full items-center gap-1.5 rounded-full px-2.5 text-xs transition-colors',
|
|
647
647
|
onNewChatHome
|
|
648
648
|
? 'bg-agent-foreground/10 font-medium text-agent-foreground'
|
|
649
649
|
: 'text-agent-muted-foreground hover:bg-agent-foreground/5 hover:text-agent-foreground',
|
|
@@ -651,14 +651,14 @@ export function AgentSidebar({
|
|
|
651
651
|
title="新建对话"
|
|
652
652
|
data-testid="sidebar-new-chat"
|
|
653
653
|
>
|
|
654
|
-
<LuPlus className="h-
|
|
654
|
+
<LuPlus className="h-3.5 w-3.5" />
|
|
655
655
|
<span>新对话</span>
|
|
656
656
|
</button>
|
|
657
657
|
<button
|
|
658
658
|
type="button"
|
|
659
659
|
onClick={() => navigate('/settings?section=agents')}
|
|
660
660
|
className={[
|
|
661
|
-
'flex h-
|
|
661
|
+
'flex h-7 w-full items-center gap-1.5 rounded-full px-2.5 text-xs transition-colors',
|
|
662
662
|
onSettingsPage && settingsSection === 'agents'
|
|
663
663
|
? 'bg-agent-foreground/10 font-medium text-agent-foreground'
|
|
664
664
|
: 'text-agent-muted-foreground hover:bg-agent-foreground/5 hover:text-agent-foreground',
|
|
@@ -666,14 +666,14 @@ export function AgentSidebar({
|
|
|
666
666
|
title="智能体管理"
|
|
667
667
|
data-testid="sidebar-agent-settings"
|
|
668
668
|
>
|
|
669
|
-
<LuBot className="h-
|
|
669
|
+
<LuBot className="h-3.5 w-3.5" />
|
|
670
670
|
<span>智能体管理</span>
|
|
671
671
|
</button>
|
|
672
672
|
<button
|
|
673
673
|
type="button"
|
|
674
674
|
onClick={() => navigate('/settings?section=skills')}
|
|
675
675
|
className={[
|
|
676
|
-
'flex h-
|
|
676
|
+
'flex h-7 w-full items-center gap-1.5 rounded-full px-2.5 text-xs transition-colors',
|
|
677
677
|
onSettingsPage && settingsSection === 'skills'
|
|
678
678
|
? 'bg-agent-foreground/10 font-medium text-agent-foreground'
|
|
679
679
|
: 'text-agent-muted-foreground hover:bg-agent-foreground/5 hover:text-agent-foreground',
|
|
@@ -681,14 +681,14 @@ export function AgentSidebar({
|
|
|
681
681
|
title="Skill 设置"
|
|
682
682
|
data-testid="sidebar-skill-settings"
|
|
683
683
|
>
|
|
684
|
-
<LuBlocks className="h-
|
|
684
|
+
<LuBlocks className="h-3.5 w-3.5" />
|
|
685
685
|
<span>Skill 设置</span>
|
|
686
686
|
</button>
|
|
687
687
|
<button
|
|
688
688
|
type="button"
|
|
689
689
|
onClick={() => navigate('/settings?section=mcp')}
|
|
690
690
|
className={[
|
|
691
|
-
'flex h-
|
|
691
|
+
'flex h-7 w-full items-center gap-1.5 rounded-full px-2.5 text-xs transition-colors',
|
|
692
692
|
onSettingsPage && settingsSection === 'mcp'
|
|
693
693
|
? 'bg-agent-foreground/10 font-medium text-agent-foreground'
|
|
694
694
|
: 'text-agent-muted-foreground hover:bg-agent-foreground/5 hover:text-agent-foreground',
|
|
@@ -696,11 +696,11 @@ export function AgentSidebar({
|
|
|
696
696
|
title="MCP 设置"
|
|
697
697
|
data-testid="sidebar-mcp-settings"
|
|
698
698
|
>
|
|
699
|
-
<LuPlug className="h-
|
|
699
|
+
<LuPlug className="h-3.5 w-3.5" />
|
|
700
700
|
<span>MCP 设置</span>
|
|
701
701
|
</button>
|
|
702
702
|
</div>
|
|
703
|
-
<div className="flex flex-shrink-0 items-center justify-between py-1
|
|
703
|
+
<div className="flex flex-shrink-0 items-center justify-between py-1 pl-2.5 pr-2.5">
|
|
704
704
|
<button
|
|
705
705
|
type="button"
|
|
706
706
|
onClick={() => setChatsExpanded((v) => !v)}
|
|
@@ -747,7 +747,7 @@ export function AgentSidebar({
|
|
|
747
747
|
加载中...
|
|
748
748
|
</div>
|
|
749
749
|
) : chatGroups.length === 0 && projectGroups.length === 0 ? (
|
|
750
|
-
<div className="flex flex-col items-center gap-1.5 py-
|
|
750
|
+
<div className="flex flex-col items-center gap-1.5 py-4 text-xs text-agent-muted-foreground">
|
|
751
751
|
<LuMessageSquare className="h-4 w-4 text-agent-muted-foreground/60" />
|
|
752
752
|
暂无会话
|
|
753
753
|
</div>
|
|
@@ -759,7 +759,7 @@ export function AgentSidebar({
|
|
|
759
759
|
<div className="group/proj relative">
|
|
760
760
|
{renamingProjectId === project.id ? (
|
|
761
761
|
<form
|
|
762
|
-
className="flex items-center px-
|
|
762
|
+
className="flex items-center px-2.5 pb-1 pt-1.5"
|
|
763
763
|
onSubmit={(event) => {
|
|
764
764
|
event.preventDefault();
|
|
765
765
|
void handleRenameProject(project.id);
|
|
@@ -781,7 +781,7 @@ export function AgentSidebar({
|
|
|
781
781
|
<button
|
|
782
782
|
type="button"
|
|
783
783
|
onClick={() => toggleProjectCollapsed(project.id)}
|
|
784
|
-
className="flex w-full min-w-0 items-center px-
|
|
784
|
+
className="flex w-full min-w-0 items-center px-2.5 pb-0.5 pt-1.5 text-[10px] font-medium uppercase tracking-wider text-agent-muted-foreground/80 transition-colors hover:text-agent-foreground"
|
|
785
785
|
title={`${project.name}\n${project.folderPath}`}
|
|
786
786
|
>
|
|
787
787
|
<span className="mr-1">
|
|
@@ -856,7 +856,7 @@ export function AgentSidebar({
|
|
|
856
856
|
{!collapsedProjectIds.has(project.id) && (
|
|
857
857
|
<div className="space-y-0.5">
|
|
858
858
|
{items.length === 0 ? (
|
|
859
|
-
<div className="px-
|
|
859
|
+
<div className="px-2.5 py-0.5 text-[11px] text-agent-muted-foreground/60">
|
|
860
860
|
暂无会话 — hover 项目名点 + 新建
|
|
861
861
|
</div>
|
|
862
862
|
) : (
|
|
@@ -869,7 +869,7 @@ export function AgentSidebar({
|
|
|
869
869
|
{/* 无项目对话排在项目分组之后:保持原有日期分组 */}
|
|
870
870
|
{chatGroups.map((group) => (
|
|
871
871
|
<div key={group.label} className="mb-1">
|
|
872
|
-
<div className="px-
|
|
872
|
+
<div className="px-2.5 pb-0.5 pt-1.5 text-[10px] font-medium uppercase tracking-wider text-agent-muted-foreground/80">
|
|
873
873
|
{group.label}
|
|
874
874
|
</div>
|
|
875
875
|
<div className="space-y-0.5">
|
|
@@ -899,7 +899,7 @@ export function AgentSidebar({
|
|
|
899
899
|
|
|
900
900
|
{(error || projectError) && (
|
|
901
901
|
<div
|
|
902
|
-
className="flex-shrink-0 border-t border-agent-destructive/40 bg-agent-destructive/10 px-
|
|
902
|
+
className="flex-shrink-0 border-t border-agent-destructive/40 bg-agent-destructive/10 px-2.5 py-1.5 text-[11px] text-agent-destructive"
|
|
903
903
|
role="alert"
|
|
904
904
|
>
|
|
905
905
|
{error ?? projectError}
|
|
@@ -907,13 +907,13 @@ export function AgentSidebar({
|
|
|
907
907
|
)}
|
|
908
908
|
|
|
909
909
|
{/* ───── Footer: 右侧面板切换(终端 | 包槽位)+ 设置 ───── */}
|
|
910
|
-
<div className="flex-shrink-0 border-t border-agent-border/40 px-
|
|
910
|
+
<div className="flex-shrink-0 border-t border-agent-border/40 px-2.5 py-1.5">
|
|
911
911
|
{chatSlots.length === 0 ? (
|
|
912
912
|
<button
|
|
913
913
|
type="button"
|
|
914
914
|
onClick={() => onToggleRightPanel('terminal')}
|
|
915
915
|
className={[
|
|
916
|
-
'flex h-
|
|
916
|
+
'flex h-7 w-full items-center gap-1.5 rounded-full px-2.5 text-xs transition-colors',
|
|
917
917
|
rightPanel === 'terminal'
|
|
918
918
|
? 'bg-agent-foreground/10 font-medium text-agent-foreground'
|
|
919
919
|
: 'text-agent-muted-foreground hover:bg-agent-foreground/5 hover:text-foreground',
|
|
@@ -921,7 +921,7 @@ export function AgentSidebar({
|
|
|
921
921
|
title={`${rightPanel === 'terminal' ? '关闭' : '打开'}终端面板 (${isMac ? '⌘T' : 'Ctrl+T'})`}
|
|
922
922
|
data-testid="sidebar-terminal"
|
|
923
923
|
>
|
|
924
|
-
<LuTerminal className="h-
|
|
924
|
+
<LuTerminal className="h-3.5 w-3.5" />
|
|
925
925
|
<span>终端</span>
|
|
926
926
|
<span className="ml-auto text-[10px] text-agent-muted-foreground/70">
|
|
927
927
|
{isMac ? '⌘T' : 'Ctrl+T'}
|
|
@@ -931,7 +931,7 @@ export function AgentSidebar({
|
|
|
931
931
|
<div
|
|
932
932
|
role="group"
|
|
933
933
|
aria-label="右侧面板切换"
|
|
934
|
-
className="flex h-
|
|
934
|
+
className="flex h-7 w-full items-center gap-0.5 rounded-full bg-agent-foreground/5 p-0.5"
|
|
935
935
|
>
|
|
936
936
|
<button
|
|
937
937
|
type="button"
|
|
@@ -972,7 +972,7 @@ export function AgentSidebar({
|
|
|
972
972
|
type="button"
|
|
973
973
|
onClick={() => navigate('/settings')}
|
|
974
974
|
className={[
|
|
975
|
-
'mt-0.5 flex h-
|
|
975
|
+
'mt-0.5 flex h-7 w-full items-center gap-1.5 rounded-full px-2.5 text-xs transition-colors',
|
|
976
976
|
onGeneralSettings
|
|
977
977
|
? 'bg-agent-foreground/10 font-medium text-agent-foreground'
|
|
978
978
|
: 'text-agent-muted-foreground hover:bg-agent-foreground/5 hover:text-agent-foreground',
|
|
@@ -980,7 +980,7 @@ export function AgentSidebar({
|
|
|
980
980
|
title="设置"
|
|
981
981
|
data-testid="sidebar-llm-settings"
|
|
982
982
|
>
|
|
983
|
-
<LuSettings className="h-
|
|
983
|
+
<LuSettings className="h-3.5 w-3.5" />
|
|
984
984
|
<span>设置</span>
|
|
985
985
|
</button>
|
|
986
986
|
</div>
|
|
@@ -15,7 +15,7 @@ export function BrandLockup() {
|
|
|
15
15
|
draggable={false}
|
|
16
16
|
/>
|
|
17
17
|
{title ? (
|
|
18
|
-
<span className="truncate text-
|
|
18
|
+
<span className="truncate text-xs font-semibold tracking-tight text-agent-foreground">
|
|
19
19
|
{title}
|
|
20
20
|
</span>
|
|
21
21
|
) : null}
|
|
@@ -127,7 +127,7 @@ export function ChatHeader({
|
|
|
127
127
|
data-branch-menu
|
|
128
128
|
>
|
|
129
129
|
{!branches || branchCount === 0 ? (
|
|
130
|
-
<div className="px-
|
|
130
|
+
<div className="px-2.5 py-1.5 text-xs text-agent-muted-foreground">
|
|
131
131
|
暂无分支 — 重新生成回复后,旧版本会保留在这里。
|
|
132
132
|
</div>
|
|
133
133
|
) : (
|
|
@@ -159,7 +159,7 @@ export function ChatHeader({
|
|
|
159
159
|
setBranchMenuOpen(false);
|
|
160
160
|
setTreeModalOpen(true);
|
|
161
161
|
}}
|
|
162
|
-
className="flex w-full items-center gap-2 rounded px-
|
|
162
|
+
className="flex w-full items-center gap-2 rounded px-2.5 py-1 text-left text-xs text-agent-muted-foreground transition-colors hover:bg-agent-foreground/5 hover:text-agent-foreground"
|
|
163
163
|
data-action="branch-tree"
|
|
164
164
|
>
|
|
165
165
|
<LuListTree className="h-3 w-3 shrink-0" />
|
|
@@ -174,19 +174,14 @@ export function ChatHeader({
|
|
|
174
174
|
: null;
|
|
175
175
|
|
|
176
176
|
return (
|
|
177
|
-
<header className="flex w-full min-w-0 items-center gap-
|
|
177
|
+
<header className="flex w-full min-w-0 items-center gap-2 bg-agent-canvas px-2.5 py-1.5">
|
|
178
178
|
<div className="flex min-w-0 flex-1 flex-wrap items-center gap-x-2 gap-y-0.5">
|
|
179
179
|
<span
|
|
180
|
-
className="break-words text-
|
|
180
|
+
className="break-words text-xs font-medium text-agent-foreground"
|
|
181
181
|
title={chat?.title}
|
|
182
182
|
>
|
|
183
183
|
{chat?.title ?? '未选择对话'}
|
|
184
184
|
</span>
|
|
185
|
-
{chat && (
|
|
186
|
-
<span className="hidden shrink-0 font-mono text-[10px] text-agent-muted-foreground/70 md:inline">
|
|
187
|
-
{shortenId(chat.id)}
|
|
188
|
-
</span>
|
|
189
|
-
)}
|
|
190
185
|
</div>
|
|
191
186
|
{chat && onBranchSwitched && (
|
|
192
187
|
<div className="shrink-0">
|
|
@@ -367,11 +362,6 @@ function taskButtonTitle(
|
|
|
367
362
|
: `${base} · 点击处理 worktree`;
|
|
368
363
|
}
|
|
369
364
|
|
|
370
|
-
function shortenId(id: string): string {
|
|
371
|
-
if (id.length <= 12) return id;
|
|
372
|
-
return `${id.slice(0, 6)}…${id.slice(-4)}`;
|
|
373
|
-
}
|
|
374
|
-
|
|
375
365
|
/** Pin the branch menu to the trigger, clamped inside the chat panel so
|
|
376
366
|
* `overflow-hidden` ancestors cannot clip the left side of a `w-72` sheet. */
|
|
377
367
|
function placeBranchMenu(button: HTMLElement): { top: number; left: number; width: number } {
|
|
@@ -403,7 +393,7 @@ function BranchRow({
|
|
|
403
393
|
onClick={onSelect}
|
|
404
394
|
aria-current={active ? 'true' : undefined}
|
|
405
395
|
aria-disabled={disabled || active || undefined}
|
|
406
|
-
className={`flex w-full items-center gap-2 rounded px-
|
|
396
|
+
className={`flex w-full items-center gap-2 rounded px-2.5 py-1 text-left text-xs transition-colors ${
|
|
407
397
|
active
|
|
408
398
|
? 'cursor-default bg-agent-foreground/5 font-medium text-agent-foreground'
|
|
409
399
|
: 'text-agent-muted-foreground hover:bg-agent-foreground/5 hover:text-agent-foreground'
|
|
@@ -42,8 +42,8 @@ export function LocalLlmSettingsModal({ open, onClose, onSaved }: LocalLlmSettin
|
|
|
42
42
|
<div
|
|
43
43
|
className="flex h-[560px] max-h-[90vh] w-[540px] max-w-[92vw] flex-col overflow-hidden rounded-agent-lg border border-agent-border bg-agent-canvas shadow-2xl animate-fade-in"
|
|
44
44
|
>
|
|
45
|
-
<div className="flex h-
|
|
46
|
-
<h2 className="text-
|
|
45
|
+
<div className="flex h-9 flex-shrink-0 items-center justify-between border-b border-agent-border px-3">
|
|
46
|
+
<h2 className="text-xs font-semibold text-agent-foreground">本地模型设置</h2>
|
|
47
47
|
<button
|
|
48
48
|
type="button"
|
|
49
49
|
onClick={onClose}
|
|
@@ -53,14 +53,14 @@ export function LocalLlmSettingsModal({ open, onClose, onSaved }: LocalLlmSettin
|
|
|
53
53
|
<LuX className="h-4 w-4" />
|
|
54
54
|
</button>
|
|
55
55
|
</div>
|
|
56
|
-
<div className="flex-1 overflow-y-auto p-
|
|
56
|
+
<div className="flex-1 overflow-y-auto p-3">
|
|
57
57
|
<LlmSettingsPanel onSaved={onSaved} />
|
|
58
58
|
</div>
|
|
59
|
-
<div className="flex h-
|
|
59
|
+
<div className="flex h-9 flex-shrink-0 items-center justify-end border-t border-agent-border px-3 bg-agent-muted/10">
|
|
60
60
|
<button
|
|
61
61
|
type="button"
|
|
62
62
|
onClick={onClose}
|
|
63
|
-
className="h-
|
|
63
|
+
className="h-7 rounded-full px-3 text-xs font-medium text-agent-muted-foreground transition-colors hover:bg-agent-muted hover:text-agent-foreground"
|
|
64
64
|
>
|
|
65
65
|
关闭
|
|
66
66
|
</button>
|
|
@@ -40,7 +40,7 @@ export function TerminalPanel({
|
|
|
40
40
|
}
|
|
41
41
|
if (!hasElectron) {
|
|
42
42
|
return (
|
|
43
|
-
<div className="flex h-full w-full items-center justify-center bg-black p-
|
|
43
|
+
<div className="flex h-full w-full items-center justify-center bg-black p-4 text-center font-mono text-xs text-white">
|
|
44
44
|
<div>
|
|
45
45
|
<p className="mb-2">⚠️ 此面板需要桌面客户端或 BS server 连接。</p>
|
|
46
46
|
<p className="text-white/60">
|
|
@@ -106,17 +106,17 @@ export function ApprovalPromptMenu({
|
|
|
106
106
|
|
|
107
107
|
return (
|
|
108
108
|
<div
|
|
109
|
-
className="overflow-hidden rounded-agent-lg border border-agent-border bg-agent-canvas text-
|
|
109
|
+
className="overflow-hidden rounded-agent-lg border border-agent-border bg-agent-canvas text-xs text-agent-foreground shadow-xl"
|
|
110
110
|
data-testid="approval-composer"
|
|
111
111
|
>
|
|
112
|
-
<div className="flex items-center gap-2 border-b border-agent-border px-
|
|
112
|
+
<div className="flex items-center gap-2 border-b border-agent-border px-3 py-2">
|
|
113
113
|
{destructive ? (
|
|
114
114
|
<LuTriangleAlert className="h-4 w-4 shrink-0 text-agent-destructive" />
|
|
115
115
|
) : (
|
|
116
116
|
<LuShieldAlert className="h-4 w-4 shrink-0 text-amber-500" />
|
|
117
117
|
)}
|
|
118
118
|
<div className="min-w-0 flex-1">
|
|
119
|
-
<div className="text-
|
|
119
|
+
<div className="text-xs font-medium text-agent-foreground">
|
|
120
120
|
{isEgress ? (
|
|
121
121
|
<>
|
|
122
122
|
Agent 请求访问外网
|
|
@@ -143,7 +143,7 @@ export function ApprovalPromptMenu({
|
|
|
143
143
|
</div>
|
|
144
144
|
</div>
|
|
145
145
|
|
|
146
|
-
<div className="max-h-48 overflow-auto px-
|
|
146
|
+
<div className="max-h-48 overflow-auto px-3 py-2">
|
|
147
147
|
<pre className="whitespace-pre-wrap break-all rounded-agent-md bg-agent-muted/50 px-3 py-2 font-mono text-xs text-agent-foreground">
|
|
148
148
|
{summarizeArguments(current.arguments)}
|
|
149
149
|
</pre>
|
|
@@ -169,7 +169,7 @@ export function ApprovalPromptMenu({
|
|
|
169
169
|
)}
|
|
170
170
|
</div>
|
|
171
171
|
|
|
172
|
-
<div className="space-y-2 border-t border-agent-border px-
|
|
172
|
+
<div className="space-y-2 border-t border-agent-border px-3 py-2">
|
|
173
173
|
<DecisionRow
|
|
174
174
|
buttons={
|
|
175
175
|
isEgress
|
|
@@ -120,7 +120,7 @@ function Footer({
|
|
|
120
120
|
children?: React.ReactNode;
|
|
121
121
|
}) {
|
|
122
122
|
return (
|
|
123
|
-
<div className="mt-
|
|
123
|
+
<div className="mt-3 flex items-center justify-between gap-2 border-t border-agent-border px-3 py-2">
|
|
124
124
|
<div className="flex min-w-0 items-center gap-2">
|
|
125
125
|
{canGoBack && onBack && (
|
|
126
126
|
<button
|
|
@@ -279,7 +279,7 @@ function SelectQuestionStep({
|
|
|
279
279
|
role={question.multiSelect ? 'checkbox' : 'radio'}
|
|
280
280
|
aria-checked={active}
|
|
281
281
|
className={[
|
|
282
|
-
'flex w-full items-center gap-2 rounded-agent-md border px-3 py-2 text-left text-
|
|
282
|
+
'flex w-full items-center gap-2 rounded-agent-md border px-3 py-2 text-left text-xs transition',
|
|
283
283
|
active
|
|
284
284
|
? 'border-agent-foreground bg-agent-muted text-agent-foreground'
|
|
285
285
|
: 'border-agent-border bg-agent-canvas text-agent-foreground hover:bg-agent-muted',
|
|
@@ -335,7 +335,7 @@ function SelectQuestionStep({
|
|
|
335
335
|
role={question.multiSelect ? 'checkbox' : 'radio'}
|
|
336
336
|
aria-checked={active}
|
|
337
337
|
className={[
|
|
338
|
-
'flex w-full items-center gap-2 rounded-agent-md border border-dashed px-3 py-2 text-left text-
|
|
338
|
+
'flex w-full items-center gap-2 rounded-agent-md border border-dashed px-3 py-2 text-left text-xs transition',
|
|
339
339
|
active
|
|
340
340
|
? 'border-agent-foreground bg-agent-muted text-agent-foreground'
|
|
341
341
|
: 'border-agent-border bg-agent-canvas text-agent-muted-foreground hover:bg-agent-muted hover:text-agent-foreground',
|
|
@@ -359,13 +359,13 @@ function SelectQuestionStep({
|
|
|
359
359
|
|
|
360
360
|
return (
|
|
361
361
|
<div>
|
|
362
|
-
<div className="space-y-2 px-
|
|
362
|
+
<div className="space-y-2 px-3 py-2">
|
|
363
363
|
{question.header && (
|
|
364
364
|
<p className="text-[11px] font-medium uppercase tracking-wide text-agent-muted-foreground">
|
|
365
365
|
{question.header}
|
|
366
366
|
</p>
|
|
367
367
|
)}
|
|
368
|
-
<p className="text-
|
|
368
|
+
<p className="text-xs font-medium text-agent-foreground">{question.text}</p>
|
|
369
369
|
{question.detail && (
|
|
370
370
|
<p className="whitespace-pre-line text-xs text-agent-muted-foreground">
|
|
371
371
|
{question.detail}
|
|
@@ -396,7 +396,7 @@ function SelectQuestionStep({
|
|
|
396
396
|
}
|
|
397
397
|
}}
|
|
398
398
|
placeholder={question.placeholder || '输入你的补充...'}
|
|
399
|
-
className="w-full rounded-agent-md border border-agent-border bg-agent-canvas px-3 py-2 text-
|
|
399
|
+
className="w-full rounded-agent-md border border-agent-border bg-agent-canvas px-3 py-2 text-xs text-agent-foreground outline-none placeholder:text-agent-muted-foreground focus:border-agent-foreground focus:ring-1 focus:ring-agent-foreground"
|
|
400
400
|
/>
|
|
401
401
|
)}
|
|
402
402
|
|
|
@@ -414,7 +414,7 @@ function SelectQuestionStep({
|
|
|
414
414
|
}
|
|
415
415
|
}}
|
|
416
416
|
placeholder={question.placeholder || '输入你的回答...'}
|
|
417
|
-
className="w-full rounded-agent-md border border-agent-border bg-agent-canvas px-3 py-2 text-
|
|
417
|
+
className="w-full rounded-agent-md border border-agent-border bg-agent-canvas px-3 py-2 text-xs text-agent-foreground outline-none placeholder:text-agent-muted-foreground focus:border-agent-foreground focus:ring-1 focus:ring-agent-foreground"
|
|
418
418
|
/>
|
|
419
419
|
<button
|
|
420
420
|
type="button"
|
|
@@ -488,13 +488,13 @@ function TextQuestionStep({
|
|
|
488
488
|
|
|
489
489
|
return (
|
|
490
490
|
<div>
|
|
491
|
-
<div className="space-y-2 px-
|
|
491
|
+
<div className="space-y-2 px-3 py-2">
|
|
492
492
|
{question.header && (
|
|
493
493
|
<p className="text-[11px] font-medium uppercase tracking-wide text-agent-muted-foreground">
|
|
494
494
|
{question.header}
|
|
495
495
|
</p>
|
|
496
496
|
)}
|
|
497
|
-
<p className="text-
|
|
497
|
+
<p className="text-xs font-medium text-agent-foreground">{question.text}</p>
|
|
498
498
|
{question.detail && (
|
|
499
499
|
<p className="whitespace-pre-line text-xs text-agent-muted-foreground">
|
|
500
500
|
{question.detail}
|
|
@@ -512,7 +512,7 @@ function TextQuestionStep({
|
|
|
512
512
|
}
|
|
513
513
|
}}
|
|
514
514
|
placeholder={question.placeholder || '输入你的回答...'}
|
|
515
|
-
className="w-full rounded-agent-md border border-agent-border bg-agent-canvas px-3 py-2 text-
|
|
515
|
+
className="w-full rounded-agent-md border border-agent-border bg-agent-canvas px-3 py-2 text-xs text-agent-foreground outline-none placeholder:text-agent-muted-foreground focus:border-agent-foreground focus:ring-1 focus:ring-agent-foreground"
|
|
516
516
|
/>
|
|
517
517
|
</div>
|
|
518
518
|
|
|
@@ -577,10 +577,10 @@ export function AskUserQuestionMenu({
|
|
|
577
577
|
if (!current) return null;
|
|
578
578
|
|
|
579
579
|
return (
|
|
580
|
-
<div className="overflow-hidden rounded-agent-lg border border-agent-border bg-agent-canvas text-
|
|
581
|
-
<div className="flex items-start justify-between gap-
|
|
580
|
+
<div className="overflow-hidden rounded-agent-lg border border-agent-border bg-agent-canvas text-xs text-agent-foreground shadow-xl">
|
|
581
|
+
<div className="flex items-start justify-between gap-2 border-b border-agent-border px-3 py-2">
|
|
582
582
|
<div className="min-w-0 flex-1">
|
|
583
|
-
<p className="text-
|
|
583
|
+
<p className="text-xs font-medium text-agent-foreground">
|
|
584
584
|
{intro || '需要你的输入'}
|
|
585
585
|
</p>
|
|
586
586
|
<p className="mt-0.5 text-[11px] text-agent-muted-foreground">
|
|
@@ -13,6 +13,12 @@ import type { TurnBlock } from './turn-timeline';
|
|
|
13
13
|
import { TurnProcessGroup } from './TurnProcessGroup';
|
|
14
14
|
import { TurnFilesCard } from './TurnFilesCard';
|
|
15
15
|
import type { TurnFile } from './turn-files';
|
|
16
|
+
import { stripNextStepsTags } from './next-steps-tags';
|
|
17
|
+
import {
|
|
18
|
+
formatTokenSpeed,
|
|
19
|
+
llmRequestElapsedMs,
|
|
20
|
+
type LlmSpeedSnapshot,
|
|
21
|
+
} from './process-status';
|
|
16
22
|
|
|
17
23
|
/**
|
|
18
24
|
* AssistantMessage — Tier-1 port of `deeppath`'s assistant-bubble.
|
|
@@ -98,6 +104,12 @@ interface AssistantMessageProps {
|
|
|
98
104
|
startedAtMs?: number;
|
|
99
105
|
/** Frozen wall-clock of a finished turn. */
|
|
100
106
|
durationMs?: number;
|
|
107
|
+
/**
|
|
108
|
+
* Model-request generation speed (reasoning + reply + any other LLM
|
|
109
|
+
* output). Elapsed excludes tool waits. Live snapshots keep ticking
|
|
110
|
+
* while `live` is true.
|
|
111
|
+
*/
|
|
112
|
+
llmSpeed?: LlmSpeedSnapshot;
|
|
101
113
|
/**
|
|
102
114
|
* 本回合产生/修改的文件列表(local-backend 回合收尾时收集,经
|
|
103
115
|
* `turn_files` SSE + messageMetadata.turnFiles 持久化)。渲染在回答
|
|
@@ -152,11 +164,7 @@ const META_ACTION =
|
|
|
152
164
|
'inline-flex items-center gap-0.5 rounded transition-all duration-200 hover:text-agent-foreground opacity-0 focus:opacity-100 group-hover/message:opacity-100';
|
|
153
165
|
|
|
154
166
|
function bubbleClass(isPlanMode: boolean): string {
|
|
155
|
-
return
|
|
156
|
-
isPlanMode
|
|
157
|
-
? 'border-amber-400/50 dark:border-amber-500/30 bg-amber-50/10 dark:bg-amber-950/5 shadow-amber-500/5'
|
|
158
|
-
: 'border-agent-border bg-agent-canvas'
|
|
159
|
-
}`;
|
|
167
|
+
return isPlanMode ? 'rounded-agent-lg bg-amber-50/10 dark:bg-amber-950/5' : '';
|
|
160
168
|
}
|
|
161
169
|
|
|
162
170
|
// 框架 useChatStream 收到 SSE error 事件时把最后一条助手消息的内容打成
|
|
@@ -194,7 +202,7 @@ function readTurnFailure(
|
|
|
194
202
|
|
|
195
203
|
function TurnErrorBubble({ reason }: { reason: string }) {
|
|
196
204
|
return (
|
|
197
|
-
<div className="rounded-agent-lg border border-agent-destructive/40 bg-agent-destructive/5
|
|
205
|
+
<div className="rounded-agent-lg border border-agent-destructive/40 bg-agent-destructive/5 px-2.5 py-2 text-xs leading-relaxed text-agent-destructive shadow-sm">
|
|
198
206
|
请求失败:{reason}
|
|
199
207
|
</div>
|
|
200
208
|
);
|
|
@@ -206,6 +214,17 @@ function StreamingCursor() {
|
|
|
206
214
|
);
|
|
207
215
|
}
|
|
208
216
|
|
|
217
|
+
function useLiveNow(enabled: boolean): number {
|
|
218
|
+
const [now, setNow] = useState(() => Date.now());
|
|
219
|
+
useEffect(() => {
|
|
220
|
+
if (!enabled) return;
|
|
221
|
+
setNow(Date.now());
|
|
222
|
+
const id = window.setInterval(() => setNow(Date.now()), 1000);
|
|
223
|
+
return () => window.clearInterval(id);
|
|
224
|
+
}, [enabled]);
|
|
225
|
+
return now;
|
|
226
|
+
}
|
|
227
|
+
|
|
209
228
|
export function AssistantMessage({
|
|
210
229
|
message,
|
|
211
230
|
isStreaming,
|
|
@@ -221,6 +240,7 @@ export function AssistantMessage({
|
|
|
221
240
|
onRegenerate,
|
|
222
241
|
startedAtMs,
|
|
223
242
|
durationMs,
|
|
243
|
+
llmSpeed,
|
|
224
244
|
turnFiles,
|
|
225
245
|
onShare,
|
|
226
246
|
}: AssistantMessageProps) {
|
|
@@ -229,7 +249,7 @@ export function AssistantMessage({
|
|
|
229
249
|
// live 错误文本("[stream error] ...")不当正文渲染;落库的失败回合可能
|
|
230
250
|
// 留有部分正文,此时正文与错误气泡都展示。
|
|
231
251
|
const displayContent = failure?.contentIsError ? '' : content;
|
|
232
|
-
const { copied, copy } = useCopy(content);
|
|
252
|
+
const { copied, copy } = useCopy(stripNextStepsTags(content));
|
|
233
253
|
const [regenerating, setRegenerating] = useState(false);
|
|
234
254
|
const [regenerateError, setRegenerateError] = useState<string | null>(null);
|
|
235
255
|
const persistedAgent = message.agentId
|
|
@@ -240,10 +260,15 @@ export function AssistantMessage({
|
|
|
240
260
|
const displayAgent = persistedAgent ?? (isStreaming ? currentAgent : null);
|
|
241
261
|
const useTimeline = timeline !== undefined;
|
|
242
262
|
const blocks = timeline ?? [];
|
|
263
|
+
const now = useLiveNow(Boolean(isStreaming && llmSpeed?.live));
|
|
264
|
+
const tokenSpeed = formatTokenSpeed(
|
|
265
|
+
llmSpeed?.tokens ?? 0,
|
|
266
|
+
llmRequestElapsedMs(llmSpeed, now),
|
|
267
|
+
);
|
|
243
268
|
|
|
244
269
|
return (
|
|
245
270
|
<motion.div
|
|
246
|
-
className="group/message mb-
|
|
271
|
+
className="group/message mb-1"
|
|
247
272
|
data-message-role="assistant"
|
|
248
273
|
data-message-id={message.id}
|
|
249
274
|
initial={{ opacity: 0, y: 8 }}
|
|
@@ -252,7 +277,7 @@ export function AssistantMessage({
|
|
|
252
277
|
>
|
|
253
278
|
<div className="mx-auto w-full max-w-[var(--chat-input-box-width)] px-1">
|
|
254
279
|
{(displayAgent || isPlanMode) && (
|
|
255
|
-
<div className="flex items-center justify-between
|
|
280
|
+
<div className="mb-1 flex min-h-4 items-center justify-between">
|
|
256
281
|
{displayAgent ? (
|
|
257
282
|
<AgentBadge agent={displayAgent} isStreaming={isStreaming} />
|
|
258
283
|
) : (
|
|
@@ -291,7 +316,7 @@ export function AssistantMessage({
|
|
|
291
316
|
/>
|
|
292
317
|
</div>
|
|
293
318
|
) : (
|
|
294
|
-
<div className={`${bubbleClass(isPlanMode)} text-
|
|
319
|
+
<div className={`${bubbleClass(isPlanMode)} text-xs italic text-agent-muted-foreground`}>
|
|
295
320
|
(空消息)
|
|
296
321
|
</div>
|
|
297
322
|
)
|
|
@@ -305,7 +330,7 @@ export function AssistantMessage({
|
|
|
305
330
|
}
|
|
306
331
|
renderAnswer={(block, isLast) => (
|
|
307
332
|
<div className={bubbleClass(isPlanMode)}>
|
|
308
|
-
<div className="markdown-content text-
|
|
333
|
+
<div className="markdown-content text-xs leading-relaxed text-agent-foreground">
|
|
309
334
|
<Markdown agents={agents} chats={chats} chatId={chatId}>{block.content}</Markdown>
|
|
310
335
|
</div>
|
|
311
336
|
{isStreaming && isLast && <StreamingCursor />}
|
|
@@ -327,7 +352,7 @@ export function AssistantMessage({
|
|
|
327
352
|
)}
|
|
328
353
|
{displayContent ? (
|
|
329
354
|
<div className={bubbleClass(isPlanMode)}>
|
|
330
|
-
<div className="markdown-content text-
|
|
355
|
+
<div className="markdown-content text-xs leading-relaxed text-agent-foreground">
|
|
331
356
|
<Markdown agents={agents} chats={chats} chatId={chatId}>{displayContent}</Markdown>
|
|
332
357
|
</div>
|
|
333
358
|
{isStreaming && <StreamingCursor />}
|
|
@@ -343,7 +368,7 @@ export function AssistantMessage({
|
|
|
343
368
|
/>
|
|
344
369
|
</div>
|
|
345
370
|
) : (
|
|
346
|
-
<div className={`${bubbleClass(isPlanMode)} text-
|
|
371
|
+
<div className={`${bubbleClass(isPlanMode)} text-xs italic text-agent-muted-foreground`}>
|
|
347
372
|
(空消息)
|
|
348
373
|
</div>
|
|
349
374
|
)}
|
|
@@ -367,6 +392,9 @@ export function AssistantMessage({
|
|
|
367
392
|
? getFriendlyDate(new Date(message.createdAt))
|
|
368
393
|
: ''}
|
|
369
394
|
</span>
|
|
395
|
+
{tokenSpeed ? (
|
|
396
|
+
<span data-testid="turn-token-speed">{tokenSpeed}</span>
|
|
397
|
+
) : null}
|
|
370
398
|
{content && !isStreaming && (
|
|
371
399
|
<button
|
|
372
400
|
type="button"
|