fix(agent): 抽屉与整页之间的切换

- 在智能体页面按 Ctrl+J / 点智能体入口聚焦页面输入框,不再在抽屉里叠一份同一会话
- 会话页统计行右侧加「在侧边打开」:回到进智能体页面之前的页面,会话在抽屉里接着看
- 窄屏顶栏按钮与快捷键、悬浮按钮共用同一套切换
- 抽屉关闭钮放进标题栏按钮行(原来绝对定位,比其它按钮低 5px)
- 关闭抽屉后焦点回到打开前的元素(快捷键打开没有触发元素,原来落到 body)

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-10-06 18:07:21 +08:00
1 parent 93a5341688
commit a87c2c19c4
6 files changed
+150 -25

No files matched your search

+26 -9
View File
@@ -1,5 +1,5 @@
import { Bot, ExternalLink, History, Maximize2, Minus, SquarePen, X } from "lucide-react";
import { useRef, useState } from "react";
import { useEffect, useRef, useState } from "react";
import { Link, useLocation } from "react-router";
import { toast } from "sonner";
import { agentMockEnabled, isRunActive, useAgentRun, useCreateAgentRun } from "~/api/agent.ts";
@@ -10,7 +10,7 @@ import { useSite } from "~/api/sites.ts";
import { StreamingCursor } from "~/components/agentui/streaming-response.tsx";
import { Button } from "~/components/ui/button.tsx";
import { Popover, PopoverContent, PopoverTrigger } from "~/components/ui/popover.tsx";
import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from "~/components/ui/sheet.tsx";
import { Sheet, SheetClose, SheetContent, SheetDescription, SheetHeader, SheetTitle } from "~/components/ui/sheet.tsx";
import { errorMessage } from "~/lib/api.ts";
import { capabilityMeta, isDenied, RunStatusBadge } from "./agent-conversation.tsx";
import { DRAFT_DRAWER_GOAL, writeDraft } from "./agent-nav.ts";
@@ -20,15 +20,16 @@ import {
type AgentContext,
agentShortcutLabel,
clearAgentContext,
contextFromPath,
contextPrefix,
minimizeAgentDrawer,
newAgentConversation,
openAgentDrawer,
noteAgentReturnPath,
restoreAgentDrawer,
setAgentDrawerOpen,
shortGoalTitle,
showAgentRunInDrawer,
takeReturnFocus,
toggleAgentDrawer,
useAgentDrawer,
} from "./agent-store.ts";
import { AgentRunOpenContext } from "./agent-subagent-tree.tsx";
@@ -99,8 +100,10 @@ function HistoryPopover({ activeId }: { activeId: string | null }) {
export function AgentDrawer() {
const { open, minimized, context, runId } = useAgentDrawer();
const create = useCreateAgentRun();
const { pathname } = useLocation();
const { pathname, search } = useLocation();
const content = useRef<HTMLDivElement>(null);
// 记下最近的非智能体页面:会话页「在侧边打开」回到这里
useEffect(() => noteAgentReturnPath(pathname, search), [pathname, search]);
const name = useContextName(context);
const mock = agentMockEnabled();
// 目标前缀带名称和 ID:名称给人看,ID 给模型用
@@ -112,6 +115,7 @@ export function AgentDrawer() {
<SheetContent
ref={content}
side="right"
showCloseButton={false}
className="w-full gap-0 p-0 sm:max-w-xl"
// 打开即可输入:焦点直接给输入框,没有输入框时退回抽屉本身
onOpenAutoFocus={(e) => {
@@ -121,11 +125,18 @@ export function AgentDrawer() {
ta.focus();
ta.setSelectionRange(ta.value.length, ta.value.length);
}}
// 关闭后焦点回到打开前的位置(快捷键打开没有触发元素,缺省会落到 body)
onCloseAutoFocus={(e) => {
const el = takeReturnFocus();
if (!el) return;
e.preventDefault();
el.focus();
}}
onInteractOutside={(e) => {
if ((e.target as Element | null)?.closest?.(`[${AGENT_LAUNCHER_ATTR}]`)) e.preventDefault();
}}
>
<SheetHeader className="flex-row items-center gap-1 border-b border-[var(--shell-border)] py-2.5 pr-12 pl-5">
<SheetHeader className="flex-row items-center gap-1 border-b border-[var(--shell-border)] py-2.5 pr-3 pl-5">
<SheetTitle className="mr-auto flex items-center gap-2 text-lead">
<Bot className="h-4 w-4" />
智能体
@@ -157,6 +168,12 @@ export function AgentDrawer() {
</Button>
</>
) : null}
{/* 关闭钮放进标题栏这一行,与其它按钮对齐(Sheet 自带的是绝对定位,偏下 5px) */}
<SheetClose asChild>
<Button variant="ghost" size="icon-sm" aria-label="关闭" title="关闭(Esc)">
<X />
</Button>
</SheetClose>
</SheetHeader>
<div className="flex min-h-0 flex-1 flex-col px-4 py-3">
{runId ? (
@@ -258,9 +275,8 @@ function AgentMiniDock({ runId }: { runId: string }) {
);
}
/** 顶栏按钮:有最小化的运行时恢复它;否则按当前路由带上下文打开(页面内按钮用 openAgentDrawer 传更完整的上下文) */
/** 顶栏按钮(窄屏):与快捷键、悬浮按钮同一套切换(页面内按钮用 openAgentDrawer 传更完整的上下文) */
export function AgentDrawerTrigger({ className }: { className?: string }) {
const { minimized, runId } = useAgentDrawer();
const { pathname } = useLocation();
return (
<Button
@@ -269,7 +285,8 @@ export function AgentDrawerTrigger({ className }: { className?: string }) {
aria-label="打开智能体"
title={`智能体(${agentShortcutLabel()})`}
className={className}
onClick={() => (minimized && runId ? restoreAgentDrawer() : openAgentDrawer(contextFromPath(pathname)))}
{...{ [AGENT_LAUNCHER_ATTR]: "" }}
onClick={() => toggleAgentDrawer(pathname)}
>
<Bot />
</Button>