From fd1999cd813f397d080cb83980b4ba71cb437201 Mon Sep 17 00:00:00 2001 From: CherryXiao Date: Thu, 26 Feb 2026 08:36:07 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20Agent=20=E6=A8=A1=E5=BC=8F=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E7=BD=AE=E9=A1=B6=E4=BC=9A=E8=AF=9D=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1、Agent 模式下添加置顶会话功能。 2、用户可以通过右键当前会话以及点击会话右上角的pin图标,将当前会话置顶。 3、置顶会话支持折叠,节省空间。 4、置顶会话采用隔离工作区的方式,不同工作区不共享置顶会话。 1. **类型定义**:在 AgentSessionMeta 中添加 `pinned?: boolean` 字段 2. **服务层**:在 agent-session-manager.ts 中支持置顶/取消置顶操作,排序时置顶项优先 3. **IPC 层**:添加 TOGGLE_PIN 通道,在 ipc.ts 和 preload/index.ts 中实现 4. **UI 层**: - 侧边栏添加置顶会话区域(可展开/收起) - 头部添加置顶按钮 - 右键菜单支持置顶/取消置顶 - 置顶会话按工作区隔离(从 filteredAgentSessions 而非 agentSessions 中过滤) - packages/shared/src/types/agent.ts - apps/electron/src/main/lib/agent-session-manager.ts - apps/electron/src/main/ipc.ts - apps/electron/src/preload/index.ts - apps/electron/src/renderer/components/app-shell/LeftSidebar.tsx - apps/electron/src/renderer/components/agent/AgentHeader.tsx - [x] 右键会话,点击"置顶会话",会话移至置顶区域 - [x] 点击头部 Pin 按钮,当前会话置顶/取消置顶 - [x] 置顶会话在日期分组列表中显示,前面有 Pin 图标 - [x] 点击"置顶会话"标题,区域可展开/收起 - [x] 切换工作区,置顶会话按工作区隔离显示 - [x] 重启应用,置顶状态持久化保存 - [x] 右键置顶会话,点击"取消置顶",会话恢复普通状态 --- apps/electron/src/main/ipc.ts | 11 +++ .../src/main/lib/agent-session-manager.ts | 12 ++- apps/electron/src/preload/index.ts | 7 ++ .../renderer/components/agent/AgentHeader.tsx | 26 +++++- .../components/app-shell/LeftSidebar.tsx | 79 +++++++++++++++++++ packages/shared/src/types/agent.ts | 4 + 6 files changed, 135 insertions(+), 4 deletions(-) diff --git a/apps/electron/src/main/ipc.ts b/apps/electron/src/main/ipc.ts index 1073e2f..c0a4cb2 100644 --- a/apps/electron/src/main/ipc.ts +++ b/apps/electron/src/main/ipc.ts @@ -567,6 +567,17 @@ export function registerIpcHandlers(): void { } ) + // 切换 Agent 会话置顶状态 + ipcMain.handle( + AGENT_IPC_CHANNELS.TOGGLE_PIN, + async (_, id: string): Promise => { + const sessions = listAgentSessions() + const current = sessions.find((s) => s.id === id) + if (!current) throw new Error(`Agent 会话不存在: ${id}`) + return updateAgentSessionMeta(id, { pinned: !current.pinned }) + } + ) + // ===== Agent 工作区管理相关 ===== // 确保默认工作区存在 diff --git a/apps/electron/src/main/lib/agent-session-manager.ts b/apps/electron/src/main/lib/agent-session-manager.ts index 563e374..5e05e2d 100644 --- a/apps/electron/src/main/lib/agent-session-manager.ts +++ b/apps/electron/src/main/lib/agent-session-manager.ts @@ -66,11 +66,17 @@ function writeIndex(index: AgentSessionsIndex): void { } /** - * 获取所有会话(按 updatedAt 降序) + * 获取所有会话(置顶项优先,然后按 updatedAt 降序) */ export function listAgentSessions(): AgentSessionMeta[] { const index = readIndex() - return index.sessions.sort((a, b) => b.updatedAt - a.updatedAt) + return index.sessions.sort((a, b) => { + // 置顶项优先 + if (a.pinned && !b.pinned) return -1 + if (!a.pinned && b.pinned) return 1 + // 同为置顶或同为非置顶,按更新时间降序 + return b.updatedAt - a.updatedAt + }) } /** @@ -159,7 +165,7 @@ export function appendAgentMessage(id: string, message: AgentMessage): void { */ export function updateAgentSessionMeta( id: string, - updates: Partial>, + updates: Partial>, ): AgentSessionMeta { const index = readIndex() const idx = index.sessions.findIndex((s) => s.id === id) diff --git a/apps/electron/src/preload/index.ts b/apps/electron/src/preload/index.ts index 20c563f..c05467d 100644 --- a/apps/electron/src/preload/index.ts +++ b/apps/electron/src/preload/index.ts @@ -254,6 +254,9 @@ export interface ElectronAPI { /** 删除 Agent 会话 */ deleteAgentSession: (id: string) => Promise + /** 切换 Agent 会话置顶状态 */ + togglePinAgentSession: (id: string) => Promise + /** 生成 Agent 会话标题 */ generateAgentTitle: (input: AgentGenerateTitleInput) => Promise @@ -660,6 +663,10 @@ const electronAPI: ElectronAPI = { return ipcRenderer.invoke(AGENT_IPC_CHANNELS.DELETE_SESSION, id) }, + togglePinAgentSession: (id: string) => { + return ipcRenderer.invoke(AGENT_IPC_CHANNELS.TOGGLE_PIN, id) + }, + generateAgentTitle: (input: AgentGenerateTitleInput) => { return ipcRenderer.invoke(AGENT_IPC_CHANNELS.GENERATE_TITLE, input) }, diff --git a/apps/electron/src/renderer/components/agent/AgentHeader.tsx b/apps/electron/src/renderer/components/agent/AgentHeader.tsx index 2fd4846..3b98e74 100644 --- a/apps/electron/src/renderer/components/agent/AgentHeader.tsx +++ b/apps/electron/src/renderer/components/agent/AgentHeader.tsx @@ -7,8 +7,11 @@ import * as React from 'react' import { useAtomValue, useSetAtom } from 'jotai' -import { Pencil, Check, X } from 'lucide-react' +import { Pencil, Check, X, Pin } from 'lucide-react' import { currentAgentSessionAtom, agentSessionsAtom } from '@/atoms/agent-atoms' +import { Button } from '@/components/ui/button' +import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' +import { cn } from '@/lib/utils' export function AgentHeader(): React.ReactElement | null { const session = useAtomValue(currentAgentSessionAtom) @@ -101,6 +104,27 @@ export function AgentHeader(): React.ReactElement | null { )} + + {/* 右侧按钮组 */} +
+ + + + +

{session.pinned ? '取消置顶' : '置顶会话'}

+
+
) } diff --git a/apps/electron/src/renderer/components/app-shell/LeftSidebar.tsx b/apps/electron/src/renderer/components/app-shell/LeftSidebar.tsx index b9a5379..0153e9c 100644 --- a/apps/electron/src/renderer/components/app-shell/LeftSidebar.tsx +++ b/apps/electron/src/renderer/components/app-shell/LeftSidebar.tsx @@ -141,6 +141,8 @@ export function LeftSidebar({ width }: LeftSidebarProps): React.ReactElement { const [pendingDeleteId, setPendingDeleteId] = React.useState(null) /** 置顶区域展开/收起 */ const [pinnedExpanded, setPinnedExpanded] = React.useState(true) + /** Agent 置顶区域展开/收起 */ + const [pinnedAgentExpanded, setPinnedAgentExpanded] = React.useState(true) const setUserProfile = useSetAtom(userProfileAtom) const selectedModel = useAtomValue(selectedModelAtom) const streamingIds = useAtomValue(streamingConversationIdsAtom) @@ -289,6 +291,18 @@ export function LeftSidebar({ width }: LeftSidebarProps): React.ReactElement { } } + /** 切换 Agent 会话置顶状态 */ + const handleTogglePinAgent = async (id: string): Promise => { + try { + const updated = await window.electronAPI.togglePinAgentSession(id) + setAgentSessions((prev) => + prev.map((s) => (s.id === updated.id ? updated : s)) + ) + } catch (error) { + console.error('[侧边栏] 切换 Agent 会话置顶失败:', error) + } + } + /** 确认删除对话 */ const handleConfirmDelete = async (): Promise => { if (!pendingDeleteId) return @@ -364,6 +378,12 @@ export function LeftSidebar({ width }: LeftSidebarProps): React.ReactElement { [agentSessions, currentWorkspaceId] ) + /** 置顶 Agent 会话列表(按当前工作区过滤) */ + const pinnedAgentSessions = React.useMemo( + () => filteredAgentSessions.filter((s) => s.pinned), + [filteredAgentSessions] + ) + /** Agent 会话按日期分组 */ const agentSessionGroups = React.useMemo( () => groupByDate(filteredAgentSessions), @@ -417,6 +437,22 @@ export function LeftSidebar({ width }: LeftSidebarProps): React.ReactElement { )} + {/* Agent 模式:导航菜单(置顶区域) */} + {mode === 'agent' && pinnedAgentSessions.length > 0 && ( +
+ } + label="置顶会话" + suffix={ + pinnedAgentExpanded + ? + : + } + onClick={() => setPinnedAgentExpanded(!pinnedAgentExpanded)} + /> +
+ )} + {/* Chat 模式:置顶对话区域 */} {mode === 'chat' && pinnedExpanded && pinnedConversations.length > 0 && (
@@ -441,6 +477,30 @@ export function LeftSidebar({ width }: LeftSidebarProps): React.ReactElement {
)} + {/* Agent 模式:置顶会话区域 */} + {mode === 'agent' && pinnedAgentExpanded && pinnedAgentSessions.length > 0 && ( +
+
+ {pinnedAgentSessions.map((session) => ( + handleSelectAgentSession(session.id)} + onRequestDelete={() => handleRequestDelete(session.id)} + onRename={handleAgentRename} + onTogglePin={handleTogglePinAgent} + onMouseEnter={() => setHoveredId(session.id)} + onMouseLeave={() => setHoveredId(null)} + /> + ))} +
+
+ )} + {/* 列表区域:根据模式切换 */}
{mode === 'chat' ? ( @@ -485,9 +545,11 @@ export function LeftSidebar({ width }: LeftSidebarProps): React.ReactElement { active={session.id === currentAgentSessionId} hovered={session.id === hoveredId} running={agentRunningIds.has(session.id)} + showPinIcon={!!session.pinned} onSelect={() => handleSelectAgentSession(session.id)} onRequestDelete={() => handleRequestDelete(session.id)} onRename={handleAgentRename} + onTogglePin={handleTogglePinAgent} onMouseEnter={() => setHoveredId(session.id)} onMouseLeave={() => setHoveredId(null)} /> @@ -750,9 +812,11 @@ interface AgentSessionItemProps { active: boolean hovered: boolean running: boolean + showPinIcon?: boolean onSelect: () => void onRequestDelete: () => void onRename: (id: string, newTitle: string) => Promise + onTogglePin?: (id: string) => void onMouseEnter: () => void onMouseLeave: () => void } @@ -762,9 +826,11 @@ function AgentSessionItem({ active, hovered, running, + showPinIcon, onSelect, onRequestDelete, onRename, + onTogglePin, onMouseEnter, onMouseLeave, }: AgentSessionItemProps): React.ReactElement { @@ -773,6 +839,8 @@ function AgentSessionItem({ const inputRef = React.useRef(null) const justStartedEditing = React.useRef(false) + const isPinned = !!session.pinned + const startEdit = (): void => { setEditTitle(session.title) setEditing(true) @@ -847,6 +915,10 @@ function AgentSessionItem({ )} + {/* 置顶标记 */} + {showPinIcon && ( + + )} {session.title}
)} @@ -870,6 +942,13 @@ function AgentSessionItem({ + onTogglePin?.(session.id)} + > + {isPinned ? : } + {isPinned ? '取消置顶' : '置顶会话'} + Date: Thu, 26 Feb 2026 09:04:44 +0800 Subject: [PATCH 2/2] =?UTF-8?q?style:=20=E7=BD=AE=E9=A1=B6=E6=8C=89?= =?UTF-8?q?=E9=92=AE=E6=A0=B7=E5=BC=8F=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - 置顶会话按钮的 Pin 图标从16px调整到14px,与其他图标保持一致。 - 将"置顶会话"按钮与"新会话"按钮在视觉上进行对齐,统一界面风格。 ## 实现 1. Pin 图标大小从 16px 调整为 14px,与其他图标保持一致 2. "置顶对话"和"置顶会话"按钮从 SidebarItem 组件改为独立实现 3. 图标和文字间距统一为 `gap-2`(8px),与"新会话"按钮一致 4. 移除固定宽度图标容器 \`w-[18px]\`,使用与"新会话"按钮相同的结构 ## 变更文件 - apps/electron/src/renderer/components/app-shell/LeftSidebar.tsx ## Test Plan - [x] Pin 图标大小为 14px,与 Plus 图标大小一致 - [x] "置顶会话"按钮与"新会话"按钮视觉对齐 - [x] Chat 模式和 Agent 模式的置顶按钮样式一致 - [x] 按钮 hover 效果正常 --- .../components/app-shell/LeftSidebar.tsx | 48 ++++++++++--------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/apps/electron/src/renderer/components/app-shell/LeftSidebar.tsx b/apps/electron/src/renderer/components/app-shell/LeftSidebar.tsx index 0153e9c..a2ec32f 100644 --- a/apps/electron/src/renderer/components/app-shell/LeftSidebar.tsx +++ b/apps/electron/src/renderer/components/app-shell/LeftSidebar.tsx @@ -421,35 +421,39 @@ export function LeftSidebar({ width }: LeftSidebarProps): React.ReactElement { {/* Chat 模式:导航菜单(置顶区域) */} {mode === 'chat' && ( -
- } - label="置顶对话" - suffix={ - pinnedConversations.length > 0 ? ( - pinnedExpanded - ? - : - ) : undefined - } +
+
)} {/* Agent 模式:导航菜单(置顶区域) */} {mode === 'agent' && pinnedAgentSessions.length > 0 && ( -
- } - label="置顶会话" - suffix={ - pinnedAgentExpanded - ? - : - } +
+
)}