import { Bot, ExternalLink, Maximize2, Minus, Plus, X } from "lucide-react"; import { Link, useLocation } from "react-router"; import { toast } from "sonner"; import { 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 { capabilityMeta, isDenied, RunStatusBadge } from "./agent-conversation.tsx"; import { AgentRunPanel, GoalComposer } from "./agent-run-panel.tsx"; import { type AgentContext, contextFromPath, contextPrefix, minimizeAgentDrawer, openAgentDrawer, restoreAgentDrawer, setAgentDrawerOpen, setAgentDrawerRun, splitContextGoal, useAgentDrawer, } from "./agent-store.ts"; import { useAgentStream } from "./use-agent-stream.ts"; 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 ( ); } /** * Agent 侧栏抽屉:任意页面可打开,带当前页面上下文(entityId / siteId / taskId 等)作为 goal 前缀; * 运行中可最小化到右下角小窗,流式输出继续。 * 挂载:在 console layout 里渲染一次 ;顶栏放 。 * 页面内打开:import { openAgentDrawer } from "~/components/agent/agent-store.ts"。 */ export function AgentDrawer() { const { open, minimized, context, runId } = useAgentDrawer(); const create = useCreateAgentRun(); const prefix = contextPrefix(context); return ( <> Agent {context?.label ? `当前对象:${context.label}` : "交代目标,Agent 在授权范围内边执行边汇报。"} {context ? : null} {runId ? ( ) : null}
{runId ? ( ) : ( create.mutate( { goal: `${prefix}${goal}`, mode }, { onSuccess: (run) => setAgentDrawerRun(run.id), onError: (e) => toast.error(errorMessage(e)), }, ) } /> )}
{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() { const { minimized, runId } = useAgentDrawer(); const { pathname } = useLocation(); return ( ); }