import { ArrowLeft, Bot, ExternalLink, History, Maximize2, Minus, Plus, X } from "lucide-react"; import { useRef } from "react"; import { Link, useLocation } from "react-router"; import { toast } from "sonner"; import { agentMockEnabled, isRunActive, useAgentRun, useCreateAgentRun } from "~/api/agent.ts"; import { StreamingCursor } from "~/components/agentui/streaming-response.tsx"; import { Button } from "~/components/ui/button.tsx"; import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from "~/components/ui/sheet.tsx"; import { errorMessage } from "~/lib/api.ts"; import { cn } from "~/lib/utils.ts"; import { capabilityMeta, isDenied, RunStatusBadge } from "./agent-conversation.tsx"; import { AgentRunPanel, GoalComposer } from "./agent-run-panel.tsx"; import { AgentSessionList } from "./agent-session-list.tsx"; import { type AgentContext, type AgentDrawerTab, agentShortcutLabel, contextFromPath, contextPrefix, lastAgentDrawerTab, minimizeAgentDrawer, openAgentDrawer, openAgentHistoryRun, restoreAgentDrawer, setAgentDrawerOpen, setAgentDrawerRun, setAgentDrawerTab, showAgentRunInDrawer, splitContextGoal, useAgentDrawer, } from "./agent-store.ts"; import { AgentRunOpenContext } from "./agent-subagent-tree.tsx"; import { quickGoalsFor } from "./quick-goals.ts"; import { useAgentStream } from "./use-agent-stream.ts"; /** 悬浮按钮等"抽屉外但属于抽屉"的控件打这个属性:点它不算点外面(否则先被关掉又被重新打开) */ export const AGENT_LAUNCHER_ATTR = "data-agent-launcher"; const CONTEXT_KEYS: [keyof AgentContext, string][] = [ ["entityId", "作品"], ["releaseId", "资源"], ["downloadTaskId", "下载任务"], ["recordId", "整理记录"], ["siteId", "站点"], ["taskId", "任务"], ]; function ContextChips({ context }: { context: AgentContext }) { const chips = CONTEXT_KEYS.filter(([k]) => context[k]); if (!chips.length) return null; return ( ); } const TABS: { value: AgentDrawerTab; label: string }[] = [ { value: "current", label: "当前" }, { value: "history", label: "历史" }, ]; function DrawerTabs({ tab }: { tab: AgentDrawerTab }) { return (
{TABS.map((t) => ( ))}
); } /** 历史 Tab 里打开的一条会话:返回列表 + 目标 + 在页面打开(次要);正文复用会话组件,进行中可继续输入 */ function HistoryRunView({ runId }: { runId: string }) { const run = useAgentRun(runId); const mock = agentMockEnabled(); return (

{run.data ? splitContextGoal(run.data.run.goal).text : ""}

); } /** * Agent 侧栏抽屉:任意页面可打开,带当前页面上下文(entityId / siteId / taskId 等)作为 goal 前缀; * 顶部 Tab「当前 / 历史」:历史里点一条在抽屉内打开(不跳页),返回列表保留筛选与滚动位置;抽屉记住上次 Tab。 * 运行中可最小化到右下角小窗,流式输出继续。 * 挂载:在 console layout 里渲染一次 ;顶栏放 。 * 页面内打开:import { openAgentDrawer } from "~/components/agent/agent-store.ts"。 */ export function AgentDrawer() { const { open, minimized, context, runId, tab, historyRunId } = useAgentDrawer(); const create = useCreateAgentRun(); const prefix = contextPrefix(context); const { pathname } = useLocation(); const content = useRef(null); const history = tab === "history"; return ( <> { const ta = content.current?.querySelector("textarea:not([disabled])"); if (!ta) return; e.preventDefault(); ta.focus(); ta.setSelectionRange(ta.value.length, ta.value.length); }} onInteractOutside={(e) => { if ((e.target as Element | null)?.closest?.(`[${AGENT_LAUNCHER_ATTR}]`)) e.preventDefault(); }} > Agent {context?.label ? `当前对象:${context.label}` : "交代目标,Agent 在授权范围内执行并汇报。"} ({agentShortcutLabel()} 开关,Esc 关闭) {context && !history ? : null} {runId && !history ? ( ) : null}
{history ? ( historyRunId ? ( ) : ( ) ) : runId ? ( ) : ( create.mutate( { goal: `${prefix}${goal}`, ...opts }, { onSuccess: (run) => setAgentDrawerRun(run.id), onError: (e) => toast.error(errorMessage(e)), }, ) } /> )}
{history ? null : (
{runId ? ( <> ) : ( )}
)}
{minimized && runId ? : null} ); } /** 最小化后的小窗:保持事件流订阅,显示状态与最新一句输出 */ function AgentMiniDock({ runId }: { runId: string }) { const run = useAgentRun(runId); const active = isRunActive(run.data?.run.status); const live = useAgentStream(runId, active || !run.data); const last = live.items[live.items.length - 1]; const streaming = active && !live.finished; const line = last?.kind === "text" ? last.text.trim().split("\n").pop() : last?.kind === "tool" ? `${isDenied(last.call) ? "被拒绝" : last.call.status === "running" ? "调用" : "完成"} · ${capabilityMeta(last.call.capabilityId)?.title ?? last.call.capabilityId}` : last?.kind === "thinking" ? "思考中…" : last?.kind === "end" ? last.ok ? "已完成,展开查看核实结果" : last.message : "等待输出…"; const status = run.data ? live.finished && isRunActive(run.data.run.status) ? "running" : run.data.run.status : null; return (
{status ? : null} {run.data ? splitContextGoal(run.data.run.goal).text : ""}
); } /** 顶栏按钮:有最小化的运行时恢复它;否则按当前路由带上下文打开(页面内按钮用 openAgentDrawer 传更完整的上下文) */ export function AgentDrawerTrigger({ className }: { className?: string }) { const { minimized, runId } = useAgentDrawer(); const { pathname } = useLocation(); return ( ); }