feat(agent): optimize agent panel header and functionality layout for improved space efficiency

This commit is contained in:
HouYunFei
2026-07-29 17:08:17 +08:00
parent 8babf7faa0
commit 7a48f441cf
5 changed files with 20 additions and 26 deletions
+1
View File
@@ -2,6 +2,7 @@
## Unreleased
+ [优化] Agent 面板标题、功能标签与新对话操作合并为单行顶部栏,减少面板空间占用。
+ [新增] 画布 Agent 支持请求批准、自动审查和完全访问三档 Codex 权限,并可在对话中审批文件、命令与网络访问。
+ [修复] Agent 回复中的本地绝对文件路径改为在系统文件管理器中定位,不再被浏览器错误拼接为 localhost 地址。
+ [优化] Agent Markdown 代码块与外链确认弹窗改为紧凑中文样式,改善右侧面板阅读体验。
@@ -5,6 +5,7 @@ description: 当前版本已实现但仍需人工验证的变更项
# 待测试
- Agent 顶部栏:标题只显示垂直居中的「Agent」,连接、对话、历史、日志、新对话和收起操作应位于同一行;面板较窄时标签仍可横向滚动且操作不应错位。
- Agent Markdown 样式:右侧 Agent 回复中的代码块应为横向占满消息区域、无语言标题和无双层边框的紧凑代码条,单行内容不应保留纵向大面积空白,复制操作仅在悬停时弱化显示;行内代码、链接在浅色和深色主题下应清晰;点击外部链接后应显示中文紧凑确认弹窗,长路径能够正常换行且复制、继续打开和关闭操作可用;点击 `/Users/`、`/home/` 等本地绝对文件路径时应改为提示在系统文件管理器中定位,支持复制路径,且浏览器地址不应跳转为 localhost 文件路径。
- Agent 工具确认模式:右侧面板标题栏不应再显示「工具确认」开关;对话输入框左下方应显示确认模式选择,默认选中「自动确认」,画布写入工具应直接执行;切换为「手动确认」后,画布写入工具应在对话中展示等待确认卡片,并支持批准或拒绝。
- Canvas Agent Codex 升级:启动 Canvas Agent 后应能正常连接 Codex、创建或恢复会话、发送消息并调用画布工具,实际运行的 Codex CLI 版本应为 0.145.0;运行中停止任务应只中断当前 turn,随后无需重启 Agent 即可继续发送新任务。
@@ -2,13 +2,14 @@ import type { ReactNode } from "react";
import { canvasThemes } from "@/lib/canvas-theme";
export function AgentPanelTabs<T extends string>({ value, items, theme, right, onChange }: { value: T; items: { value: T; label: string; icon?: ReactNode; count?: number }[]; theme: (typeof canvasThemes)[keyof typeof canvasThemes]; right?: ReactNode; onChange: (value: T) => void }) {
export function AgentPanelTabs<T extends string>({ value, items, theme, leading, right, onChange }: { value: T; items: { value: T; label: string; icon?: ReactNode; count?: number }[]; theme: (typeof canvasThemes)[keyof typeof canvasThemes]; leading?: ReactNode; right?: ReactNode; onChange: (value: T) => void }) {
return (
<div className="border-b px-3" style={{ borderColor: theme.node.stroke }}>
<div className="flex min-h-11 items-center justify-between gap-3">
<div className="flex h-12 items-center justify-between gap-3">
{leading ? <div className="flex shrink-0 items-center">{leading}</div> : null}
<nav className="thin-scrollbar flex min-w-0 flex-1 items-center gap-3 overflow-x-auto text-sm" role="tablist" aria-label="Agent 面板">
{items.map((item) => (
<button key={item.value} type="button" role="tab" aria-selected={value === item.value} className={`inline-flex h-11 shrink-0 items-center gap-1.5 border-b-2 px-0.5 transition ${value === item.value ? "font-medium" : "font-normal"}`} style={{ borderColor: value === item.value ? theme.node.text : "transparent", color: value === item.value ? theme.node.text : theme.node.muted }} onClick={() => onChange(item.value)}>
<button key={item.value} type="button" role="tab" aria-selected={value === item.value} className={`inline-flex h-12 shrink-0 items-center gap-1.5 border-b-2 px-0.5 transition ${value === item.value ? "font-medium" : "font-normal"}`} style={{ borderColor: value === item.value ? theme.node.text : "transparent", color: value === item.value ? theme.node.text : theme.node.muted }} onClick={() => onChange(item.value)}>
{item.icon}
{item.label}{item.count ? ` ${item.count}` : ""}
</button>
-21
View File
@@ -1,6 +1,4 @@
import { useState, type PointerEvent as ReactPointerEvent } from "react";
import { Bot, PanelRightClose } from "lucide-react";
import { Button, Tooltip } from "antd";
import { motion } from "motion/react";
import { LocalAgentPanel } from "./local-agent-panel";
@@ -18,9 +16,6 @@ export function AgentPanel() {
const panelOpen = useAgentStore((state) => state.panelOpen);
const panelClosing = useAgentStore((state) => state.panelClosing);
const setAgentState = useAgentStore((state) => state.setAgentState);
const closePanel = useAgentStore((state) => state.closePanel);
const startResize = (event: ReactPointerEvent<HTMLButtonElement>) => {
event.preventDefault();
const startX = event.clientX;
@@ -60,22 +55,6 @@ export function AgentPanel() {
style={{ width, background: theme.node.panel, borderColor: theme.node.stroke, color: theme.node.text }}
>
<button type="button" className="absolute inset-y-0 left-0 z-40 w-4 -translate-x-1/2 cursor-col-resize" onPointerDown={startResize} aria-label="调整右侧面板宽度" />
<header className="flex h-14 shrink-0 items-center justify-between border-b px-4" style={{ borderColor: theme.node.stroke }}>
<div className="flex min-w-0 items-center gap-2">
<span className="grid size-8 place-items-center rounded-lg">
<Bot className="size-4" />
</span>
<div className="min-w-0">
<div className="text-base font-semibold leading-5">Agent</div>
<div className="truncate text-xs" style={{ color: theme.node.muted }}></div>
</div>
</div>
<div className="flex shrink-0 items-center gap-2">
<Tooltip title="收起对话">
<Button type="text" shape="circle" className="!h-8 !w-8 !min-w-8" style={{ color: theme.node.muted }} icon={<PanelRightClose className="size-4" />} onClick={closePanel} />
</Tooltip>
</div>
</header>
<LocalAgentPanel embedded />
</motion.aside>
</motion.div>
+14 -2
View File
@@ -1,7 +1,7 @@
import { useCallback, useEffect, useMemo, useRef } from "react";
import { useNavigate, useSearchParams } from "react-router-dom";
import { App, Button } from "antd";
import { History, MessageSquare, PlugZap, Plus, Terminal } from "lucide-react";
import { App, Button, Tooltip } from "antd";
import { Bot, History, MessageSquare, PanelRightClose, PlugZap, Plus, Terminal } from "lucide-react";
import { canvasThemes } from "@/lib/canvas-theme";
import { imageMetadata } from "@/lib/canvas/canvas-node-factory";
@@ -105,6 +105,7 @@ export function LocalAgentPanel({ embedded, headless, autoConnect }: { embedded?
})),
);
const setAgentState = useAgentStore((state) => state.setAgentState);
const closePanel = useAgentStore((state) => state.closePanel);
const pushMessage = useAgentStore((state) => state.addMessage);
const pushEventLog = useAgentStore((state) => state.addEventLog);
const clearEventLogs = useAgentStore((state) => state.clearEventLogs);
@@ -783,6 +784,14 @@ export function LocalAgentPanel({ embedded, headless, autoConnect }: { embedded?
<AgentPanelTabs
value={activeTab}
theme={theme}
leading={
<div className="flex items-center gap-2 pr-1">
<span className="grid size-8 place-items-center">
<Bot className="size-4" />
</span>
<div className="text-base font-semibold leading-5">Agent</div>
</div>
}
items={[
{ value: "setup", label: "连接", icon: <PlugZap className="size-3.5" /> },
{ value: "chat", label: "对话", icon: <MessageSquare className="size-3.5" /> },
@@ -798,6 +807,9 @@ export function LocalAgentPanel({ embedded, headless, autoConnect }: { embedded?
<Button size="small" type="text" disabled={!connected || loadingThreads || sending || waiting} icon={<Plus className="size-3.5" />} onClick={startNewThread}>
</Button>
<Tooltip title="收起对话">
<Button type="text" shape="circle" className="!h-8 !w-8 !min-w-8" style={{ color: theme.node.muted }} icon={<PanelRightClose className="size-4" />} onClick={closePanel} />
</Tooltip>
</>
}
/>