@steerable/agent-shell-web 0.6.48 → 0.6.49
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 +411 -298
- package/src/components/ChatHeader.tsx +74 -4
- package/src/components/chat/ApprovalModal.tsx +8 -0
- package/src/components/chat/ApprovalPromptProvider.tsx +16 -1
- package/src/components/chat/AskUserPromptProvider.tsx +14 -0
- package/src/components/chat/AssistantMessage.tsx +1 -1
- package/src/components/chat/ChatInput.tsx +82 -31
- package/src/components/chat/SuggestedReplies.tsx +5 -2
- package/src/components/chat/TurnFilesCard.tsx +417 -69
- package/src/components/chat/turn-files.ts +273 -0
- package/src/components/settings/PortableSettingsPanel.tsx +493 -0
- package/src/components/settings/PythonRunnerSettingsPanel.tsx +231 -0
- package/src/layouts/AgentLayout.tsx +23 -0
- package/src/lib/electron-bridge.ts +44 -0
- package/src/lib/host-paste.ts +85 -0
- package/src/lib/local-api.ts +18 -0
- package/src/lib/portable.ts +229 -0
- package/src/lib/tauri-bridge.ts +40 -1
- package/src/main.tsx +2 -0
- package/src/pages/AgentPage.tsx +17 -0
- package/src/pages/SettingsPage.tsx +15 -1
- package/src/styles/index.css +29 -0
- package/src/vite-env.d.ts +2 -0
- package/vite.base.js +1 -0
|
@@ -59,12 +59,12 @@
|
|
|
59
59
|
* │ ● 哲学家 │
|
|
60
60
|
* │ + 用「教练」新建对话 │ ← inline CTA when sel ≠ current
|
|
61
61
|
* ├──── divider ────────────────────┤
|
|
62
|
-
* │
|
|
62
|
+
* │ ✎ 新对话 │ ← 只打开落地页,有内容才落库
|
|
63
63
|
* │ ⬡ 智能体管理 │ ← /settings?section=agents(独立页)
|
|
64
64
|
* │ ⬡ Skill 设置 │ ← /settings?section=skills(独立页)
|
|
65
65
|
* │ 🔌 MCP 设置 │ ← /settings?section=mcp(独立页)
|
|
66
66
|
* │ 会话 v 📁+ │ ← 📁+ 打开新建项目弹窗
|
|
67
|
-
* │ v 📁 项目A
|
|
67
|
+
* │ v 📁 项目A (hover: ✎··)│ ← ✎ 新建对话;·· 菜单:重命名/换目录/访达/删
|
|
68
68
|
* │ ...(项目内对话) │
|
|
69
69
|
* │ 今天 │
|
|
70
70
|
* │ ...(无项目对话,按日期分组) │ ← 无项目排在项目分组之后
|
|
@@ -82,14 +82,13 @@ import { createPortal } from "react-dom";
|
|
|
82
82
|
import { useLocation, useNavigate, useParams } from "react-router-dom";
|
|
83
83
|
import {
|
|
84
84
|
LuChevronDown,
|
|
85
|
-
|
|
85
|
+
LuChevronRight,
|
|
86
86
|
LuMessageSquare,
|
|
87
87
|
LuCloudCog,
|
|
88
88
|
LuTrash2,
|
|
89
89
|
LuLoaderCircle,
|
|
90
|
-
LuTerminal,
|
|
91
90
|
LuSettings,
|
|
92
|
-
|
|
91
|
+
LuSquarePen,
|
|
93
92
|
LuPanelLeftClose,
|
|
94
93
|
LuBlocks,
|
|
95
94
|
LuBot,
|
|
@@ -99,7 +98,9 @@ import {
|
|
|
99
98
|
LuPencil,
|
|
100
99
|
LuPlug,
|
|
101
100
|
LuEllipsis,
|
|
101
|
+
LuCircleHelp,
|
|
102
102
|
} from "react-icons/lu";
|
|
103
|
+
import { RiPushpin2Fill, RiPushpin2Line } from "react-icons/ri";
|
|
103
104
|
import { parseChatTitle } from "@/lib/chat-title";
|
|
104
105
|
import { getDateGroupLabel, getDateGroupPriority } from "@/lib/date-groups";
|
|
105
106
|
import { getElectronBridge, isElectron } from "@/lib/electron-bridge";
|
|
@@ -109,11 +110,14 @@ import {
|
|
|
109
110
|
deleteProject,
|
|
110
111
|
listProjects,
|
|
111
112
|
openLocalPath,
|
|
113
|
+
setChatPinned,
|
|
112
114
|
updateProject,
|
|
113
115
|
type LocalChat,
|
|
114
116
|
type LocalChatAgent,
|
|
115
117
|
type LocalProject,
|
|
116
118
|
} from "@/lib/local-api";
|
|
119
|
+
import { usePendingAskUserChatIds } from "@/components/chat/AskUserPromptProvider";
|
|
120
|
+
import { usePendingApprovalChatIds } from "@/components/chat/ApprovalPromptProvider";
|
|
117
121
|
import type { UseChatsAndAgentsResult } from "@/hooks/useChatsAndAgents";
|
|
118
122
|
import type { RightPanelState } from "@/layouts/AgentLayout";
|
|
119
123
|
import { BrandLockup } from "@/components/BrandLockup";
|
|
@@ -123,31 +127,6 @@ import {
|
|
|
123
127
|
} from "@/components/SidebarRelease";
|
|
124
128
|
import { CreateProjectModal } from "@/components/CreateProjectModal";
|
|
125
129
|
|
|
126
|
-
const DEFAULT_DOT_COLOR = "#7c3aed";
|
|
127
|
-
|
|
128
|
-
function agentInitial(agent: LocalChatAgent | null): string {
|
|
129
|
-
const name = (agent?.name || "").trim();
|
|
130
|
-
return name ? name[0].toUpperCase() : "A";
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
function AgentDot({
|
|
134
|
-
agent,
|
|
135
|
-
size = 18,
|
|
136
|
-
}: {
|
|
137
|
-
agent: LocalChatAgent | null;
|
|
138
|
-
size?: number;
|
|
139
|
-
}) {
|
|
140
|
-
const color = agent?.color || DEFAULT_DOT_COLOR;
|
|
141
|
-
return (
|
|
142
|
-
<span
|
|
143
|
-
className="inline-flex flex-shrink-0 items-center justify-center rounded-full text-[10px] font-semibold text-white shadow-sm"
|
|
144
|
-
style={{ width: size, height: size, backgroundColor: color }}
|
|
145
|
-
>
|
|
146
|
-
{agentInitial(agent)}
|
|
147
|
-
</span>
|
|
148
|
-
);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
130
|
function placeProjectMenu(anchor: HTMLElement): { top: number; left: number } {
|
|
152
131
|
const box = anchor.getBoundingClientRect();
|
|
153
132
|
const width = 224;
|
|
@@ -272,17 +251,17 @@ function ProjectOverflowMenu({
|
|
|
272
251
|
interface AgentSidebarProps {
|
|
273
252
|
data: UseChatsAndAgentsResult;
|
|
274
253
|
/** 右侧栏当前打开的面板:null / 'terminal' / 包槽位 id(互斥)。 */
|
|
275
|
-
rightPanel
|
|
254
|
+
rightPanel?: RightPanelState;
|
|
276
255
|
/** 切换右侧栏面板显隐——面板不是路由也不是独立窗口,只是布局里的一栏。 */
|
|
277
|
-
onToggleRightPanel
|
|
256
|
+
onToggleRightPanel?: (kind: string) => void;
|
|
278
257
|
/** 收起侧边栏(AgentLayout 换成窄 rail,展开按钮在 rail 上)。 */
|
|
279
258
|
onCollapse: () => void;
|
|
280
259
|
}
|
|
281
260
|
|
|
282
261
|
export function AgentSidebar({
|
|
283
262
|
data,
|
|
284
|
-
rightPanel,
|
|
285
|
-
onToggleRightPanel,
|
|
263
|
+
rightPanel: _rightPanel,
|
|
264
|
+
onToggleRightPanel: _onToggleRightPanel,
|
|
286
265
|
onCollapse,
|
|
287
266
|
}: AgentSidebarProps) {
|
|
288
267
|
const navigate = useNavigate();
|
|
@@ -305,9 +284,9 @@ export function AgentSidebar({
|
|
|
305
284
|
|
|
306
285
|
const {
|
|
307
286
|
chats,
|
|
308
|
-
agents,
|
|
309
287
|
isLoading: isChatLoading,
|
|
310
288
|
error,
|
|
289
|
+
refreshChats,
|
|
311
290
|
setSelectedAgentId,
|
|
312
291
|
deleteChat,
|
|
313
292
|
isLoadingMoreChats,
|
|
@@ -326,7 +305,43 @@ export function AgentSidebar({
|
|
|
326
305
|
null,
|
|
327
306
|
);
|
|
328
307
|
const [deletingChatId, setDeletingChatId] = useState<string | null>(null);
|
|
329
|
-
const [
|
|
308
|
+
const [pinningChatId, setPinningChatId] = useState<string | null>(null);
|
|
309
|
+
const [streamingChatIds, setStreamingChatIds] = useState<Set<string>>(
|
|
310
|
+
() => new Set(),
|
|
311
|
+
);
|
|
312
|
+
const pendingAskUserChatIds = usePendingAskUserChatIds();
|
|
313
|
+
const pendingApprovalChatIds = usePendingApprovalChatIds();
|
|
314
|
+
|
|
315
|
+
useEffect(() => {
|
|
316
|
+
const handleStreamingChange = (event: Event) => {
|
|
317
|
+
const customEvent = event as CustomEvent<{
|
|
318
|
+
chatId: string;
|
|
319
|
+
isStreaming: boolean;
|
|
320
|
+
}>;
|
|
321
|
+
const { chatId, isStreaming } = customEvent.detail || {};
|
|
322
|
+
if (!chatId) return;
|
|
323
|
+
setStreamingChatIds((prev) => {
|
|
324
|
+
const next = new Set(prev);
|
|
325
|
+
if (isStreaming) {
|
|
326
|
+
next.add(chatId);
|
|
327
|
+
} else {
|
|
328
|
+
next.delete(chatId);
|
|
329
|
+
}
|
|
330
|
+
return next;
|
|
331
|
+
});
|
|
332
|
+
};
|
|
333
|
+
window.addEventListener("chat:streaming-change", handleStreamingChange);
|
|
334
|
+
return () => {
|
|
335
|
+
window.removeEventListener(
|
|
336
|
+
"chat:streaming-change",
|
|
337
|
+
handleStreamingChange,
|
|
338
|
+
);
|
|
339
|
+
};
|
|
340
|
+
}, []);
|
|
341
|
+
|
|
342
|
+
const [pinnedExpanded, setPinnedExpanded] = useState<boolean | null>(null);
|
|
343
|
+
const [projectsExpanded, setProjectsExpanded] = useState<boolean | null>(null);
|
|
344
|
+
const [recentsExpanded, setRecentsExpanded] = useState<boolean | null>(null);
|
|
330
345
|
const chatScrollRef = useRef<HTMLDivElement>(null);
|
|
331
346
|
|
|
332
347
|
// ───── 项目模式 ─────
|
|
@@ -542,6 +557,8 @@ export function AgentSidebar({
|
|
|
542
557
|
sortDate,
|
|
543
558
|
agentId: chat.agentId ?? null,
|
|
544
559
|
projectId: chat.projectId ?? null,
|
|
560
|
+
isStreaming: chat.isStreaming,
|
|
561
|
+
needsUserInput: chat.needsUserInput,
|
|
545
562
|
};
|
|
546
563
|
})
|
|
547
564
|
.sort((a, b) => {
|
|
@@ -579,7 +596,12 @@ export function AgentSidebar({
|
|
|
579
596
|
[projects, normalizedChats, showProjectsChrome],
|
|
580
597
|
);
|
|
581
598
|
|
|
582
|
-
const
|
|
599
|
+
const pinnedChats = useMemo(
|
|
600
|
+
() => normalizedChats.filter((c) => c.isPinned),
|
|
601
|
+
[normalizedChats],
|
|
602
|
+
);
|
|
603
|
+
|
|
604
|
+
const recentChatGroups = useMemo(() => {
|
|
583
605
|
const map = new Map<
|
|
584
606
|
string,
|
|
585
607
|
{
|
|
@@ -588,23 +610,26 @@ export function AgentSidebar({
|
|
|
588
610
|
items: typeof normalizedChats;
|
|
589
611
|
}
|
|
590
612
|
>();
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
});
|
|
613
|
+
noProjectChats
|
|
614
|
+
.filter((chat) => !chat.isPinned)
|
|
615
|
+
.forEach((chat) => {
|
|
616
|
+
const label = getDateGroupLabel(chat.sortDate);
|
|
617
|
+
if (!map.has(label)) {
|
|
618
|
+
map.set(label, {
|
|
619
|
+
label,
|
|
620
|
+
priority: getDateGroupPriority(label),
|
|
621
|
+
items: [],
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
map.get(label)!.items.push(chat);
|
|
625
|
+
});
|
|
605
626
|
return Array.from(map.values()).sort((a, b) => a.priority - b.priority);
|
|
606
627
|
}, [noProjectChats]);
|
|
607
628
|
|
|
629
|
+
const isPinnedExpanded = pinnedExpanded ?? pinnedChats.length > 0;
|
|
630
|
+
const isProjectsExpanded = projectsExpanded ?? true;
|
|
631
|
+
const isRecentsExpanded = recentsExpanded ?? true;
|
|
632
|
+
|
|
608
633
|
const handleDeleteChat = useCallback(
|
|
609
634
|
async (id: string) => {
|
|
610
635
|
if (deletingChatId === id) return;
|
|
@@ -629,12 +654,37 @@ export function AgentSidebar({
|
|
|
629
654
|
[confirmDeleteChatId, deleteChat, deletingChatId, currentChatId, navigate],
|
|
630
655
|
);
|
|
631
656
|
|
|
657
|
+
const handleTogglePinChat = useCallback(
|
|
658
|
+
async (id: string, nextPinned: boolean) => {
|
|
659
|
+
if (pinningChatId === id) return;
|
|
660
|
+
setPinningChatId(id);
|
|
661
|
+
try {
|
|
662
|
+
await setChatPinned(id, nextPinned);
|
|
663
|
+
await refreshChats();
|
|
664
|
+
} catch (err) {
|
|
665
|
+
console.error("切换会话置顶失败:", err);
|
|
666
|
+
} finally {
|
|
667
|
+
setPinningChatId(null);
|
|
668
|
+
}
|
|
669
|
+
},
|
|
670
|
+
[pinningChatId, refreshChats],
|
|
671
|
+
);
|
|
672
|
+
|
|
632
673
|
// 单条会话行——无项目日期分组和项目分组共用同一个渲染,避免两份 JSX。
|
|
633
674
|
const renderChatRow = (chat: (typeof normalizedChats)[number]) => {
|
|
634
675
|
const isCurrent = currentChatId === chat.id;
|
|
635
676
|
const isConfirmingDelete = confirmDeleteChatId === chat.id;
|
|
636
677
|
const isDeleting = deletingChatId === chat.id;
|
|
637
|
-
const
|
|
678
|
+
const isPinning = pinningChatId === chat.id;
|
|
679
|
+
const isStreaming = Boolean(
|
|
680
|
+
chat.isStreaming || streamingChatIds.has(chat.id),
|
|
681
|
+
);
|
|
682
|
+
const needsUserInput = Boolean(
|
|
683
|
+
chat.needsUserInput ||
|
|
684
|
+
pendingAskUserChatIds.has(chat.id) ||
|
|
685
|
+
pendingApprovalChatIds.has(chat.id),
|
|
686
|
+
);
|
|
687
|
+
|
|
638
688
|
return (
|
|
639
689
|
<div
|
|
640
690
|
key={chat.id}
|
|
@@ -658,61 +708,87 @@ export function AgentSidebar({
|
|
|
658
708
|
chat.isAutomation ? `[由自动化触发] ${chat.title}` : chat.title
|
|
659
709
|
}
|
|
660
710
|
>
|
|
661
|
-
{
|
|
662
|
-
<span
|
|
663
|
-
|
|
711
|
+
{needsUserInput ? (
|
|
712
|
+
<span
|
|
713
|
+
className="flex shrink-0 items-center justify-center text-amber-500 dark:text-amber-400"
|
|
714
|
+
title="有问题需要输入"
|
|
715
|
+
aria-label="等待用户输入"
|
|
716
|
+
>
|
|
717
|
+
<LuCircleHelp className="h-3.5 w-3.5 animate-pulse" />
|
|
664
718
|
</span>
|
|
665
|
-
)
|
|
719
|
+
) : isStreaming ? (
|
|
720
|
+
<span
|
|
721
|
+
className="flex shrink-0 items-center justify-center text-agent-foreground/70"
|
|
722
|
+
title="正在对话中"
|
|
723
|
+
aria-label="正在生成"
|
|
724
|
+
>
|
|
725
|
+
<LuLoaderCircle className="h-3.5 w-3.5 animate-spin" />
|
|
726
|
+
</span>
|
|
727
|
+
) : null}
|
|
666
728
|
{chat.isAutomation && (
|
|
667
729
|
<LuCloudCog
|
|
668
730
|
className="h-3 w-3 shrink-0 text-agent-muted-foreground"
|
|
669
731
|
aria-label="由自动化触发"
|
|
670
732
|
/>
|
|
671
733
|
)}
|
|
672
|
-
<AgentDot agent={chatAgent} size={16} />
|
|
673
734
|
<span className="min-w-0 truncate leading-none">{chat.title}</span>
|
|
674
735
|
</button>
|
|
675
|
-
{/*
|
|
676
|
-
|
|
677
|
-
own background (canvas when selected, muted when
|
|
678
|
-
hovered). */}
|
|
736
|
+
{/* Semi-transparent gradient container for action buttons:
|
|
737
|
+
fades long text smoothly to the left so buttons don't collide with text. */}
|
|
679
738
|
<div
|
|
680
|
-
aria-hidden="true"
|
|
681
739
|
className={[
|
|
682
|
-
"
|
|
740
|
+
"absolute inset-y-0 right-0 flex items-center justify-end gap-0.5 pr-1 pl-8 rounded-r-full transition-all duration-200",
|
|
741
|
+
"bg-gradient-to-l from-agent-muted/95 via-agent-muted/80 to-transparent",
|
|
683
742
|
isConfirmingDelete
|
|
684
|
-
?
|
|
685
|
-
|
|
686
|
-
: "bg-gradient-to-l from-agent-muted via-agent-muted/95 to-transparent opacity-100"
|
|
687
|
-
: isCurrent
|
|
688
|
-
? "bg-gradient-to-l from-agent-canvas via-agent-canvas/95 to-transparent opacity-0 group-hover/item:opacity-100"
|
|
689
|
-
: "bg-gradient-to-l from-agent-muted via-agent-muted/95 to-transparent opacity-0 group-hover/item:opacity-100",
|
|
743
|
+
? "opacity-100 pointer-events-auto"
|
|
744
|
+
: "opacity-0 group-hover/item:opacity-100 focus-within:opacity-100 pointer-events-none group-hover/item:pointer-events-auto focus-within:pointer-events-auto",
|
|
690
745
|
].join(" ")}
|
|
691
|
-
/>
|
|
692
|
-
<button
|
|
693
|
-
type="button"
|
|
694
|
-
onClick={(event) => {
|
|
695
|
-
event.stopPropagation();
|
|
696
|
-
void handleDeleteChat(chat.id);
|
|
697
|
-
}}
|
|
698
|
-
disabled={isDeleting}
|
|
699
|
-
className={[
|
|
700
|
-
"absolute right-1 top-1/2 flex h-7 w-7 -translate-y-1/2 items-center justify-center rounded-full transition-all duration-200",
|
|
701
|
-
isConfirmingDelete
|
|
702
|
-
? "bg-agent-destructive/10 text-agent-destructive opacity-100 hover:bg-agent-destructive/20"
|
|
703
|
-
: "text-agent-muted-foreground opacity-0 hover:bg-agent-foreground/5 hover:text-agent-destructive group-hover/item:opacity-100 focus:opacity-100",
|
|
704
|
-
"disabled:cursor-not-allowed disabled:opacity-100",
|
|
705
|
-
].join(" ")}
|
|
706
|
-
title={isConfirmingDelete ? "再次点击确认删除" : "删除会话"}
|
|
707
|
-
aria-label={isConfirmingDelete ? "再次点击确认删除" : "删除会话"}
|
|
708
|
-
data-testid="sidebar-chat-delete"
|
|
709
746
|
>
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
"
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
747
|
+
{!isConfirmingDelete && (
|
|
748
|
+
<button
|
|
749
|
+
type="button"
|
|
750
|
+
onClick={(event) => {
|
|
751
|
+
event.stopPropagation();
|
|
752
|
+
void handleTogglePinChat(chat.id, !chat.isPinned);
|
|
753
|
+
}}
|
|
754
|
+
disabled={isPinning}
|
|
755
|
+
className="flex h-6 w-6 items-center justify-center rounded-full text-agent-foreground/70 transition-all duration-200 hover:bg-agent-foreground/10 hover:text-agent-foreground disabled:cursor-not-allowed"
|
|
756
|
+
title={chat.isPinned ? "取消置顶" : "置顶会话"}
|
|
757
|
+
aria-label={chat.isPinned ? "取消置顶" : "置顶会话"}
|
|
758
|
+
data-testid="sidebar-chat-pin"
|
|
759
|
+
>
|
|
760
|
+
{chat.isPinned ? (
|
|
761
|
+
<RiPushpin2Fill className="h-3.5 w-3.5 text-agent-foreground" />
|
|
762
|
+
) : (
|
|
763
|
+
<RiPushpin2Line className="h-3.5 w-3.5 text-agent-foreground/75 hover:text-agent-foreground" />
|
|
764
|
+
)}
|
|
765
|
+
</button>
|
|
766
|
+
)}
|
|
767
|
+
<button
|
|
768
|
+
type="button"
|
|
769
|
+
onClick={(event) => {
|
|
770
|
+
event.stopPropagation();
|
|
771
|
+
void handleDeleteChat(chat.id);
|
|
772
|
+
}}
|
|
773
|
+
disabled={isDeleting}
|
|
774
|
+
className={[
|
|
775
|
+
"flex h-6 w-6 items-center justify-center rounded-full transition-all duration-200",
|
|
776
|
+
isConfirmingDelete
|
|
777
|
+
? "bg-agent-destructive/10 text-agent-destructive hover:bg-agent-destructive/20"
|
|
778
|
+
: "text-agent-foreground/70 hover:bg-agent-foreground/10 hover:text-agent-destructive",
|
|
779
|
+
"disabled:cursor-not-allowed disabled:opacity-100",
|
|
780
|
+
].join(" ")}
|
|
781
|
+
title={isConfirmingDelete ? "再次点击确认删除" : "删除会话"}
|
|
782
|
+
aria-label={isConfirmingDelete ? "再次点击确认删除" : "删除会话"}
|
|
783
|
+
data-testid="sidebar-chat-delete"
|
|
784
|
+
>
|
|
785
|
+
<LuTrash2
|
|
786
|
+
className={["h-3.5 w-3.5", isDeleting ? "animate-pulse" : ""].join(
|
|
787
|
+
" ",
|
|
788
|
+
)}
|
|
789
|
+
/>
|
|
790
|
+
</button>
|
|
791
|
+
</div>
|
|
716
792
|
</div>
|
|
717
793
|
);
|
|
718
794
|
};
|
|
@@ -734,7 +810,7 @@ export function AgentSidebar({
|
|
|
734
810
|
// Throttled by `isLoadingMoreChats` inside the hook.
|
|
735
811
|
useEffect(() => {
|
|
736
812
|
const container = chatScrollRef.current;
|
|
737
|
-
if (!container
|
|
813
|
+
if (!container) return;
|
|
738
814
|
|
|
739
815
|
const maybeLoadMore = () => {
|
|
740
816
|
if (!hasMoreChats || isLoadingMoreChats) return;
|
|
@@ -753,26 +829,10 @@ export function AgentSidebar({
|
|
|
753
829
|
hasMoreChats,
|
|
754
830
|
isLoadingMoreChats,
|
|
755
831
|
loadMoreChats,
|
|
756
|
-
|
|
832
|
+
isRecentsExpanded,
|
|
757
833
|
normalizedChats.length,
|
|
758
834
|
]);
|
|
759
835
|
|
|
760
|
-
// Cmd+T from the app menu — wired in src/main.ts:createMenu (sends
|
|
761
|
-
// `menu:open-terminal`). Same pattern as `menu:new-chat` above. The
|
|
762
|
-
// terminal is a toggleable panel beside the chat, so this just flips
|
|
763
|
-
// the layout state owned by AgentLayout.
|
|
764
|
-
const showTerminalChrome = hostToolChrome("terminal");
|
|
765
|
-
|
|
766
|
-
useEffect(() => {
|
|
767
|
-
if (!showTerminalChrome || !bridge?.onMenuOpenTerminal) return;
|
|
768
|
-
bridge.onMenuOpenTerminal(() => {
|
|
769
|
-
onToggleRightPanel("terminal");
|
|
770
|
-
});
|
|
771
|
-
return () => {
|
|
772
|
-
bridge.offMenuOpenTerminal?.();
|
|
773
|
-
};
|
|
774
|
-
}, [bridge, onToggleRightPanel, showTerminalChrome]);
|
|
775
|
-
|
|
776
836
|
const hasElectron = isElectron();
|
|
777
837
|
|
|
778
838
|
return (
|
|
@@ -809,7 +869,7 @@ export function AgentSidebar({
|
|
|
809
869
|
title="新建对话"
|
|
810
870
|
data-testid="sidebar-new-chat"
|
|
811
871
|
>
|
|
812
|
-
<
|
|
872
|
+
<LuSquarePen className="h-3.5 w-3.5" />
|
|
813
873
|
<span>新对话</span>
|
|
814
874
|
</button>
|
|
815
875
|
{settingsChrome("agents") && (
|
|
@@ -864,184 +924,257 @@ export function AgentSidebar({
|
|
|
864
924
|
</button>
|
|
865
925
|
)}
|
|
866
926
|
</div>
|
|
867
|
-
<div className="flex flex-shrink-0 items-center justify-between py-1 pl-2.5 pr-2.5">
|
|
868
|
-
<button
|
|
869
|
-
type="button"
|
|
870
|
-
onClick={() => setChatsExpanded((v) => !v)}
|
|
871
|
-
className="flex h-6 items-center rounded-full px-2.5 text-xs font-semibold tracking-wider text-agent-muted-foreground transition-colors hover:bg-agent-foreground/5 hover:text-agent-foreground"
|
|
872
|
-
>
|
|
873
|
-
会话
|
|
874
|
-
<span className="ml-1">
|
|
875
|
-
{chatsExpanded ? (
|
|
876
|
-
<LuChevronUp className="h-3 w-3" />
|
|
877
|
-
) : (
|
|
878
|
-
<LuChevronDown className="h-3 w-3" />
|
|
879
|
-
)}
|
|
880
|
-
</span>
|
|
881
|
-
</button>
|
|
882
|
-
<div className="flex items-center gap-1">
|
|
883
|
-
{hasElectron && showProjectsChrome && (
|
|
884
|
-
<button
|
|
885
|
-
type="button"
|
|
886
|
-
onClick={() => setCreateProjectOpen(true)}
|
|
887
|
-
className="flex h-6 w-6 items-center justify-center rounded-full text-agent-muted-foreground transition-colors duration-200 hover:bg-agent-foreground/5 hover:text-agent-foreground"
|
|
888
|
-
title="新建项目"
|
|
889
|
-
aria-label="新建项目"
|
|
890
|
-
>
|
|
891
|
-
<LuFolderPlus className="h-3.5 w-3.5" />
|
|
892
|
-
</button>
|
|
893
|
-
)}
|
|
894
|
-
</div>
|
|
895
|
-
</div>
|
|
896
927
|
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
<
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
928
|
+
<div
|
|
929
|
+
ref={chatScrollRef}
|
|
930
|
+
className="flex-1 overflow-y-auto px-2.5 pb-1"
|
|
931
|
+
>
|
|
932
|
+
{isChatLoading && chats.length === 0 && projects.length === 0 ? (
|
|
933
|
+
<div className="flex items-center justify-center py-4 text-xs text-agent-muted-foreground">
|
|
934
|
+
<LuLoaderCircle className="mr-1.5 h-3 w-3 animate-spin" />
|
|
935
|
+
加载中...
|
|
936
|
+
</div>
|
|
937
|
+
) : (
|
|
938
|
+
<>
|
|
939
|
+
{/* 1. 置顶 (Pinned):没有置顶会话时整组不出现 */}
|
|
940
|
+
{pinnedChats.length > 0 && (
|
|
941
|
+
<div className="mb-1.5">
|
|
942
|
+
<div className="group/section flex h-7 items-center justify-between rounded-agent-md px-1.5 text-xs text-agent-muted-foreground transition-colors hover:bg-agent-foreground/5 hover:text-agent-foreground">
|
|
943
|
+
<button
|
|
944
|
+
type="button"
|
|
945
|
+
onClick={() => setPinnedExpanded(!isPinnedExpanded)}
|
|
946
|
+
className="flex flex-1 min-w-0 items-center gap-1 text-left font-medium text-agent-muted-foreground hover:text-agent-foreground"
|
|
947
|
+
>
|
|
948
|
+
<span>置顶</span>
|
|
949
|
+
{isPinnedExpanded ? (
|
|
950
|
+
<LuChevronDown className="h-3 w-3 shrink-0 opacity-70" />
|
|
951
|
+
) : (
|
|
952
|
+
<LuChevronRight className="h-3 w-3 shrink-0 opacity-70" />
|
|
953
|
+
)}
|
|
954
|
+
</button>
|
|
955
|
+
</div>
|
|
956
|
+
{isPinnedExpanded && (
|
|
957
|
+
<div className="space-y-0.5">
|
|
958
|
+
{pinnedChats.map((chat) => renderChatRow(chat))}
|
|
959
|
+
</div>
|
|
960
|
+
)}
|
|
911
961
|
</div>
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
<div
|
|
918
|
-
<
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
962
|
+
)}
|
|
963
|
+
|
|
964
|
+
{/* 2. 项目 (Projects) */}
|
|
965
|
+
{showProjectsChrome && (
|
|
966
|
+
<div className="mb-1.5">
|
|
967
|
+
<div className="group/section flex h-7 items-center justify-between rounded-agent-md px-1.5 text-xs text-agent-muted-foreground transition-colors hover:bg-agent-foreground/5 hover:text-agent-foreground">
|
|
968
|
+
<button
|
|
969
|
+
type="button"
|
|
970
|
+
onClick={() => setProjectsExpanded(!isProjectsExpanded)}
|
|
971
|
+
className="flex flex-1 min-w-0 items-center gap-1 text-left font-medium text-agent-muted-foreground hover:text-agent-foreground"
|
|
972
|
+
>
|
|
973
|
+
<span>项目</span>
|
|
974
|
+
{isProjectsExpanded ? (
|
|
975
|
+
<LuChevronDown className="h-3 w-3 shrink-0 opacity-70" />
|
|
976
|
+
) : (
|
|
977
|
+
<LuChevronRight className="h-3 w-3 shrink-0 opacity-70" />
|
|
978
|
+
)}
|
|
979
|
+
</button>
|
|
980
|
+
{hasElectron && (
|
|
981
|
+
<button
|
|
982
|
+
type="button"
|
|
983
|
+
onClick={(e) => {
|
|
984
|
+
e.stopPropagation();
|
|
985
|
+
setCreateProjectOpen(true);
|
|
986
|
+
}}
|
|
987
|
+
className="flex h-5 w-5 items-center justify-center rounded-full text-agent-muted-foreground opacity-0 transition-opacity duration-200 hover:bg-agent-foreground/10 hover:text-agent-foreground group-hover/section:opacity-100 focus:opacity-100"
|
|
988
|
+
title="新建项目"
|
|
989
|
+
aria-label="新建项目"
|
|
990
|
+
>
|
|
991
|
+
<LuFolderPlus className="h-3.5 w-3.5" />
|
|
992
|
+
</button>
|
|
993
|
+
)}
|
|
994
|
+
</div>
|
|
995
|
+
{isProjectsExpanded && (
|
|
996
|
+
<div className="space-y-0.5">
|
|
997
|
+
{projectGroups.length === 0 ? (
|
|
998
|
+
<div className="px-2.5 py-1 text-[11px] text-agent-muted-foreground/60">
|
|
999
|
+
暂无项目
|
|
1000
|
+
</div>
|
|
941
1001
|
) : (
|
|
942
|
-
|
|
943
|
-
<
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
1002
|
+
projectGroups.map(({ project, items }) => (
|
|
1003
|
+
<div key={project.id} className="mb-1">
|
|
1004
|
+
<div className="group/proj relative">
|
|
1005
|
+
{renamingProjectId === project.id ? (
|
|
1006
|
+
<form
|
|
1007
|
+
className="flex items-center px-2.5 pb-1 pt-1.5"
|
|
1008
|
+
onSubmit={(event) => {
|
|
1009
|
+
event.preventDefault();
|
|
1010
|
+
void handleRenameProject(project.id);
|
|
1011
|
+
}}
|
|
1012
|
+
>
|
|
1013
|
+
<input
|
|
1014
|
+
autoFocus
|
|
1015
|
+
value={renamingValue}
|
|
1016
|
+
onChange={(event) =>
|
|
1017
|
+
setRenamingValue(event.target.value)
|
|
1018
|
+
}
|
|
1019
|
+
onBlur={() => void handleRenameProject(project.id)}
|
|
1020
|
+
onKeyDown={(event) => {
|
|
1021
|
+
if (event.key === "Escape")
|
|
1022
|
+
setRenamingProjectId(null);
|
|
1023
|
+
}}
|
|
1024
|
+
className="h-6 w-full rounded-agent-md border border-agent-border bg-agent-canvas px-2 text-xs text-agent-foreground focus:outline-none focus:ring-2 focus:ring-agent-foreground/30"
|
|
1025
|
+
/>
|
|
1026
|
+
</form>
|
|
956
1027
|
) : (
|
|
957
|
-
|
|
1028
|
+
<>
|
|
1029
|
+
<button
|
|
1030
|
+
type="button"
|
|
1031
|
+
onClick={() => toggleProjectCollapsed(project.id)}
|
|
1032
|
+
className="flex w-full min-w-0 items-center px-2 pb-0.5 pt-1 text-[11px] font-medium text-agent-muted-foreground/90 transition-colors hover:text-agent-foreground"
|
|
1033
|
+
title={`${project.name}\n${project.folderPath}`}
|
|
1034
|
+
>
|
|
1035
|
+
<LuFolder className="mr-1 h-3.5 w-3.5 shrink-0" />
|
|
1036
|
+
<span className="min-w-0 truncate normal-case">
|
|
1037
|
+
{project.name}
|
|
1038
|
+
</span>
|
|
1039
|
+
<span className="ml-1 shrink-0">
|
|
1040
|
+
{collapsedProjectIds.has(project.id) ? (
|
|
1041
|
+
<LuChevronRight className="h-3 w-3" />
|
|
1042
|
+
) : (
|
|
1043
|
+
<LuChevronDown className="h-3 w-3" />
|
|
1044
|
+
)}
|
|
1045
|
+
</span>
|
|
1046
|
+
</button>
|
|
1047
|
+
<div
|
|
1048
|
+
className={`absolute right-1 top-1/2 flex -translate-y-1/2 items-center gap-0.5 transition-opacity ${
|
|
1049
|
+
projectMenu?.id === project.id
|
|
1050
|
+
? "opacity-100"
|
|
1051
|
+
: "opacity-0 group-hover/proj:opacity-100"
|
|
1052
|
+
}`}
|
|
1053
|
+
>
|
|
1054
|
+
<button
|
|
1055
|
+
type="button"
|
|
1056
|
+
onClick={() => handleOpenNewChat(project.id)}
|
|
1057
|
+
className="flex h-5 w-5 items-center justify-center rounded-full text-agent-muted-foreground hover:bg-agent-foreground/10 hover:text-agent-foreground"
|
|
1058
|
+
title="在此项目下新建对话"
|
|
1059
|
+
>
|
|
1060
|
+
<LuSquarePen className="h-3 w-3" />
|
|
1061
|
+
</button>
|
|
1062
|
+
<button
|
|
1063
|
+
type="button"
|
|
1064
|
+
aria-label="项目菜单"
|
|
1065
|
+
aria-expanded={projectMenu?.id === project.id}
|
|
1066
|
+
onClick={(event) => {
|
|
1067
|
+
const button = event.currentTarget;
|
|
1068
|
+
setConfirmDeleteProjectId(null);
|
|
1069
|
+
setProjectMenu((current) =>
|
|
1070
|
+
current?.id === project.id
|
|
1071
|
+
? null
|
|
1072
|
+
: { id: project.id, anchor: button },
|
|
1073
|
+
);
|
|
1074
|
+
}}
|
|
1075
|
+
className="flex h-5 w-5 items-center justify-center rounded-full text-agent-muted-foreground hover:bg-agent-foreground/10 hover:text-agent-foreground"
|
|
1076
|
+
title="项目菜单"
|
|
1077
|
+
>
|
|
1078
|
+
<LuEllipsis className="h-3 w-3" />
|
|
1079
|
+
</button>
|
|
1080
|
+
</div>
|
|
1081
|
+
</>
|
|
958
1082
|
)}
|
|
959
|
-
</
|
|
960
|
-
{
|
|
961
|
-
<
|
|
962
|
-
|
|
963
|
-
|
|
1083
|
+
</div>
|
|
1084
|
+
{!collapsedProjectIds.has(project.id) && (
|
|
1085
|
+
<div className="space-y-0.5 pl-2">
|
|
1086
|
+
{items.length === 0 ? (
|
|
1087
|
+
<div className="px-2.5 py-0.5 text-[11px] text-agent-muted-foreground/60">
|
|
1088
|
+
暂无会话 — hover 项目名点 + 新建
|
|
1089
|
+
</div>
|
|
1090
|
+
) : (
|
|
1091
|
+
items.map((chat) => renderChatRow(chat))
|
|
1092
|
+
)}
|
|
1093
|
+
</div>
|
|
964
1094
|
)}
|
|
965
|
-
</button>
|
|
966
|
-
<div
|
|
967
|
-
className={`absolute right-1 top-1/2 flex -translate-y-1/2 items-center gap-0.5 transition-opacity ${
|
|
968
|
-
projectMenu?.id === project.id
|
|
969
|
-
? "opacity-100"
|
|
970
|
-
: "opacity-0 group-hover/proj:opacity-100"
|
|
971
|
-
}`}
|
|
972
|
-
>
|
|
973
|
-
<button
|
|
974
|
-
type="button"
|
|
975
|
-
onClick={() => handleOpenNewChat(project.id)}
|
|
976
|
-
className="flex h-5 w-5 items-center justify-center rounded-full text-agent-muted-foreground hover:bg-agent-foreground/10 hover:text-agent-foreground"
|
|
977
|
-
title="在此项目下新建对话"
|
|
978
|
-
>
|
|
979
|
-
<LuPlus className="h-3 w-3" />
|
|
980
|
-
</button>
|
|
981
|
-
<button
|
|
982
|
-
type="button"
|
|
983
|
-
aria-label="项目菜单"
|
|
984
|
-
aria-expanded={projectMenu?.id === project.id}
|
|
985
|
-
onClick={(event) => {
|
|
986
|
-
const button = event.currentTarget;
|
|
987
|
-
setConfirmDeleteProjectId(null);
|
|
988
|
-
setProjectMenu((current) =>
|
|
989
|
-
current?.id === project.id
|
|
990
|
-
? null
|
|
991
|
-
: { id: project.id, anchor: button },
|
|
992
|
-
);
|
|
993
|
-
}}
|
|
994
|
-
className="flex h-5 w-5 items-center justify-center rounded-full text-agent-muted-foreground hover:bg-agent-foreground/10 hover:text-agent-foreground"
|
|
995
|
-
title="项目菜单"
|
|
996
|
-
>
|
|
997
|
-
<LuEllipsis className="h-3 w-3" />
|
|
998
|
-
</button>
|
|
999
1095
|
</div>
|
|
1000
|
-
|
|
1096
|
+
))
|
|
1001
1097
|
)}
|
|
1002
1098
|
</div>
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1099
|
+
)}
|
|
1100
|
+
</div>
|
|
1101
|
+
)}
|
|
1102
|
+
|
|
1103
|
+
{/* 3. 最近 (Recents) */}
|
|
1104
|
+
<div className="mb-1.5">
|
|
1105
|
+
<div className="group/section flex h-7 items-center justify-between rounded-agent-md px-1.5 text-xs text-agent-muted-foreground transition-colors hover:bg-agent-foreground/5 hover:text-agent-foreground">
|
|
1106
|
+
<button
|
|
1107
|
+
type="button"
|
|
1108
|
+
onClick={() => setRecentsExpanded(!isRecentsExpanded)}
|
|
1109
|
+
className="flex flex-1 min-w-0 items-center gap-1 text-left font-medium text-agent-muted-foreground hover:text-agent-foreground"
|
|
1110
|
+
>
|
|
1111
|
+
<span>最近</span>
|
|
1112
|
+
{isRecentsExpanded ? (
|
|
1113
|
+
<LuChevronDown className="h-3 w-3 shrink-0 opacity-70" />
|
|
1114
|
+
) : (
|
|
1115
|
+
<LuChevronRight className="h-3 w-3 shrink-0 opacity-70" />
|
|
1013
1116
|
)}
|
|
1117
|
+
</button>
|
|
1118
|
+
<div className="flex items-center gap-0.5 opacity-0 transition-opacity duration-200 group-hover/section:opacity-100 focus-within:opacity-100">
|
|
1119
|
+
<button
|
|
1120
|
+
type="button"
|
|
1121
|
+
onClick={(e) => {
|
|
1122
|
+
e.stopPropagation();
|
|
1123
|
+
handleOpenNewChat();
|
|
1124
|
+
}}
|
|
1125
|
+
className="flex h-5 w-5 items-center justify-center rounded-full text-agent-muted-foreground hover:bg-agent-foreground/10 hover:text-agent-foreground"
|
|
1126
|
+
title="新对话"
|
|
1127
|
+
aria-label="新对话"
|
|
1128
|
+
>
|
|
1129
|
+
<LuSquarePen className="h-3 w-3" />
|
|
1130
|
+
</button>
|
|
1014
1131
|
</div>
|
|
1015
|
-
|
|
1016
|
-
{
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1132
|
+
</div>
|
|
1133
|
+
{isRecentsExpanded && (
|
|
1134
|
+
<div className="space-y-0.5">
|
|
1135
|
+
{recentChatGroups.length === 0 ? (
|
|
1136
|
+
chats.length === 0 && projects.length === 0 ? (
|
|
1137
|
+
<div className="flex flex-col items-center gap-1.5 py-4 text-xs text-agent-muted-foreground">
|
|
1138
|
+
<LuMessageSquare className="h-4 w-4 text-agent-muted-foreground/60" />
|
|
1139
|
+
暂无会话
|
|
1140
|
+
</div>
|
|
1141
|
+
) : (
|
|
1142
|
+
<div className="px-2.5 py-1 text-[11px] text-agent-muted-foreground/60">
|
|
1143
|
+
暂无会话
|
|
1144
|
+
</div>
|
|
1145
|
+
)
|
|
1146
|
+
) : (
|
|
1147
|
+
recentChatGroups.map((group) => (
|
|
1148
|
+
<div key={group.label} className="mb-1">
|
|
1149
|
+
<div className="px-2 pb-0.5 pt-1.5 text-[10px] font-medium uppercase tracking-wider text-agent-muted-foreground/80">
|
|
1150
|
+
{group.label}
|
|
1151
|
+
</div>
|
|
1152
|
+
<div className="space-y-0.5">
|
|
1153
|
+
{group.items.map((chat) => renderChatRow(chat))}
|
|
1154
|
+
</div>
|
|
1155
|
+
</div>
|
|
1156
|
+
))
|
|
1157
|
+
)}
|
|
1025
1158
|
</div>
|
|
1026
|
-
))}
|
|
1027
|
-
</>
|
|
1028
|
-
)}
|
|
1029
|
-
{(chatGroups.length > 0 || projectGroups.length > 0) && (
|
|
1030
|
-
<div className="px-2 py-2 text-center text-[11px] text-agent-muted-foreground">
|
|
1031
|
-
{isLoadingMoreChats ? (
|
|
1032
|
-
<span className="inline-flex items-center gap-1">
|
|
1033
|
-
<LuLoaderCircle className="h-3 w-3 animate-spin" />
|
|
1034
|
-
加载更多...
|
|
1035
|
-
</span>
|
|
1036
|
-
) : hasMoreChats ? (
|
|
1037
|
-
"继续下滑加载更多"
|
|
1038
|
-
) : (
|
|
1039
|
-
`共 ${normalizedChats.length} 个会话`
|
|
1040
1159
|
)}
|
|
1041
1160
|
</div>
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1161
|
+
</>
|
|
1162
|
+
)}
|
|
1163
|
+
{normalizedChats.length > 0 && (
|
|
1164
|
+
<div className="px-2 py-2 text-center text-[11px] text-agent-muted-foreground">
|
|
1165
|
+
{isLoadingMoreChats ? (
|
|
1166
|
+
<span className="inline-flex items-center gap-1">
|
|
1167
|
+
<LuLoaderCircle className="h-3 w-3 animate-spin" />
|
|
1168
|
+
加载更多...
|
|
1169
|
+
</span>
|
|
1170
|
+
) : hasMoreChats ? (
|
|
1171
|
+
"继续下滑加载更多"
|
|
1172
|
+
) : (
|
|
1173
|
+
`共 ${normalizedChats.length} 个会话`
|
|
1174
|
+
)}
|
|
1175
|
+
</div>
|
|
1176
|
+
)}
|
|
1177
|
+
</div>
|
|
1045
1178
|
</div>
|
|
1046
1179
|
|
|
1047
1180
|
{(error || projectError) && (
|
|
@@ -1053,34 +1186,14 @@ export function AgentSidebar({
|
|
|
1053
1186
|
</div>
|
|
1054
1187
|
)}
|
|
1055
1188
|
|
|
1056
|
-
{/* ───── Footer:
|
|
1189
|
+
{/* ───── Footer: 设置 ───── */}
|
|
1057
1190
|
<div className="flex-shrink-0 border-t border-agent-border/40 px-2.5 py-1.5">
|
|
1058
|
-
{showTerminalChrome ? (
|
|
1059
|
-
<button
|
|
1060
|
-
type="button"
|
|
1061
|
-
onClick={() => onToggleRightPanel("terminal")}
|
|
1062
|
-
className={[
|
|
1063
|
-
"flex h-7 w-full items-center gap-1.5 rounded-full px-2.5 text-xs transition-colors",
|
|
1064
|
-
rightPanel === "terminal"
|
|
1065
|
-
? "bg-agent-foreground/10 font-medium text-agent-foreground"
|
|
1066
|
-
: "text-agent-muted-foreground hover:bg-agent-foreground/5 hover:text-foreground",
|
|
1067
|
-
].join(" ")}
|
|
1068
|
-
title={`${rightPanel === "terminal" ? "关闭" : "打开"}终端面板 (${isMac ? "⌘T" : "Ctrl+T"})`}
|
|
1069
|
-
data-testid="sidebar-terminal"
|
|
1070
|
-
>
|
|
1071
|
-
<LuTerminal className="h-3.5 w-3.5" />
|
|
1072
|
-
<span>终端</span>
|
|
1073
|
-
<span className="ml-auto text-[10px] text-agent-muted-foreground/70">
|
|
1074
|
-
{isMac ? "⌘T" : "Ctrl+T"}
|
|
1075
|
-
</span>
|
|
1076
|
-
</button>
|
|
1077
|
-
) : null}
|
|
1078
1191
|
{hasGeneralSettingsChrome() && (
|
|
1079
1192
|
<button
|
|
1080
1193
|
type="button"
|
|
1081
1194
|
onClick={() => navigate("/settings")}
|
|
1082
1195
|
className={[
|
|
1083
|
-
"
|
|
1196
|
+
"flex h-7 w-full items-center gap-1.5 rounded-full px-2.5 text-xs transition-colors",
|
|
1084
1197
|
onGeneralSettings
|
|
1085
1198
|
? "bg-agent-foreground/10 font-medium text-agent-foreground"
|
|
1086
1199
|
: "text-agent-muted-foreground hover:bg-agent-foreground/5 hover:text-agent-foreground",
|