Files
UHD_Media_Tool/apps/web/app/routes/console/agent.tsx
T
rootandClaude Opus 5.5 8352e5e881 feat(web): W4-F rebuild agent conversation and drawer with agentui components
- Port agentui (MIT, ashish200729/agentui@b2b752b) Message/MessageBubble/MessageScroller,
  StreamingResponse, ToolResult/ToolApproval, TodoList, ApprovalCard, CodeBlock/FileDiff,
  Citations, PromptInput into components/agentui; drop motion/shiki/Next, switch colors,
  type scale, radii, durations and easing to semantic tokens, honour reduced motion
- Conversation: streaming cursor, collapsible thinking, tool cards with kind/scope/risk/
  idempotency badges and audit idempotency key, prominent server-rejection state for both
  whitelist rejections and unauthorized errors, compaction markers, budget meters with
  over-limit state, approval cards (admin session only), two-column final report
  (model said vs system verified with task/record links), follow-scroll with jump-to-bottom
- Composer: Enter send / Shift+Enter newline, stop = abort (confirmed), append,
  execute/plan_only switch, "execute this plan" from finished plan_only runs
- Session list sidebar with status filter and usage; reconnect/error banners with retry
- Drawer: route-derived context (entity/site/task), minimize to a live mini dock
- Fix stream refetch race when the run switches from loading to finished

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-27 23:08:17 +08:00

66 lines
3.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { AgentRunStatus } from "@umt/contracts";
import { FlaskConical, ShieldCheck } from "lucide-react";
import { useState } from "react";
import { useNavigate } from "react-router";
import { toast } from "sonner";
import { agentMockEnabled, useCreateAgentRun } from "~/api/agent.ts";
import { GoalComposer } from "~/components/agent/agent-run-panel.tsx";
import { AgentSessionList } from "~/components/agent/agent-session-list.tsx";
import { PageHeader } from "~/components/console/page-header.tsx";
import { Button } from "~/components/ui/button.tsx";
import { errorMessage } from "~/lib/api.ts";
export default function AgentPage() {
const [status, setStatus] = useState<"" | AgentRunStatus>("");
const create = useCreateAgentRun();
const navigate = useNavigate();
const mock = agentMockEnabled();
return (
<>
<PageHeader
title="Agent 会话"
description="交代目标,Agent 在授权范围内边执行边汇报;只要方案时,它只调查不执行。"
/>
{mock ? (
<div className="mb-4 flex flex-wrap items-center gap-3 rounded-xl border border-warning/40 bg-warning/10 px-4 py-3 text-caption text-warning">
<FlaskConical className="h-4 w-4 shrink-0" />
<span className="min-w-0 flex-1">演示模式:会话是本地假数据,不连后端,不会产生任何动作。</span>
<Button variant="outline" size="sm" onClick={() => window.location.assign("/console/agent?mock=0")}>
退出演示
</Button>
</div>
) : null}
<div className="grid grid-cols-1 gap-4 lg:grid-cols-[minmax(0,20rem)_minmax(0,1fr)]">
<div className="pg-slab order-2 rounded-xl p-3 lg:order-1 lg:max-h-[calc(100dvh-12rem)] lg:overflow-hidden">
<AgentSessionList status={status} onStatusChange={setStatus} className="h-full" />
</div>
<div className="order-1 space-y-4 lg:order-2">
<div className="pg-slab rounded-xl p-4">
<h2 className="mb-3 text-body font-medium text-foreground">新会话</h2>
<GoalComposer
pending={create.isPending}
onSubmit={(goal, mode) =>
create.mutate(
{ goal, mode },
{
onSuccess: (run) => navigate(`/console/agent/${run.id}${mock ? "?mock=1" : ""}`),
onError: (e) => toast.error(errorMessage(e)),
},
)
}
/>
</div>
<div className="flex gap-3 rounded-xl border border-border/70 p-4 text-caption text-muted-foreground">
<ShieldCheck aria-hidden className="mt-0.5 size-4 shrink-0" />
<div className="space-y-1">
<p>每一次工具调用都经服务端授权校验:不在白名单或没有授权覆盖的调用会被拒绝,并在对话里醒目标出。</p>
<p>运行结束后,汇报分两栏:模型的自述只作参考,"系统核实的结果"取自下载计划、下载任务与整理记录。</p>
</div>
</div>
</div>
</div>
</>
);
}