// Adapted from agentui (https://github.com/ashish200729/agentui), components/agents/tool-approval.tsx // Copyright (c) 2026 Saurabh Chauhan. MIT License, see ./LICENSE-agentui.md // 改造:状态徽章 amber/blue/emerald/rose → warning/中性/success/destructive;去掉 "Always allow"(本项目的持续授权 // 只在设置页按策略版本发放,不在对话里一键放行);新增 `rejected` 醒目态:服务端拒绝了一次工具调用时, // 整卡换 destructive 材质、role=alert、给出拒绝原因与授权说明;新增 `badges` 槽(scope/风险级别);参数区用 Disclosure。 import { Check, ChevronDown, CircleAlert, LoaderCircle, ShieldAlert, ShieldCheck, X } from "lucide-react"; import { type ReactNode, useId, useState } from "react"; import { Button } from "~/components/ui/button.tsx"; import { cn } from "~/lib/utils.ts"; import { CHEVRON_CLASS, Disclosure } from "./disclosure.tsx"; export type ToolApprovalStatus = | "pending" | "approving" | "approved" | "denied" | "rejected" | "running" | "complete" | "error"; export interface ToolApprovalParameter { id: string; label: ReactNode; value: ReactNode; } const COPY: Record = { pending: "待授权", approving: "提交中", approved: "已批准", denied: "已拒绝", rejected: "服务端拒绝", running: "执行中", complete: "已完成", error: "失败", }; const BADGE: Record = { pending: "border-warning/30 bg-warning/10 text-warning", approving: "border-border bg-foreground/[0.04] text-muted-foreground", running: "border-border bg-foreground/[0.04] text-muted-foreground", approved: "border-success/30 bg-success/10 text-success", complete: "border-success/30 bg-success/10 text-success", denied: "border-destructive/30 bg-destructive/10 text-destructive", rejected: "border-destructive/40 bg-destructive/15 text-destructive", error: "border-destructive/30 bg-destructive/10 text-destructive", }; export interface ToolApprovalProps { tool: ReactNode; title?: ReactNode; description?: ReactNode; parameters?: ToolApprovalParameter[]; status?: ToolApprovalStatus; badges?: ReactNode; /** rejected 态:拒绝原因(服务端给出) */ reason?: ReactNode; /** rejected 态:为什么会被拒绝、怎样才能放行 */ explanation?: ReactNode; defaultOpen?: boolean; onApprove?: () => void; onDeny?: () => void; /** 为什么按钮不可用(如非管理员会话) */ actionsDisabledReason?: string; className?: string; } export function ToolApproval({ tool, title = "允许执行这个工具?", description, parameters = [], status = "pending", badges, reason, explanation, defaultOpen = false, onApprove, onDeny, actionsDisabledReason, className, }: ToolApprovalProps) { const id = useId(); const [open, setOpen] = useState(defaultOpen); const busy = status === "approving" || status === "running"; const rejected = status === "rejected"; return (
{busy ? ( ) : rejected ? ( ) : status === "error" ? ( ) : status === "denied" ? ( ) : status === "approved" || status === "complete" ? ( ) : ( )}
{title}
{tool}
{COPY[status]}
{badges ?
{badges}
: null} {rejected && reason ? (

原因:{reason}

) : null} {description ?

{description}

: null} {rejected && explanation ? (

{explanation}

) : null} {parameters.length ? ( ) : null}
{parameters.length ? (
{parameters.map((p) => (
{p.label}
{p.value}
))}
) : null} {status === "pending" && (onApprove || onDeny) ? (
{actionsDisabledReason ? ( {actionsDisabledReason} ) : null}
) : null}
); }