chore(web): 删除没有引用的整理向导旧步骤与工具审批组件
step-files / step-options / agentui/tool-approval 全仓无引用。 Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei
This commit is contained in:
1 parent
592f40ccf8
commit
a4c554f841
3 files changed
-932
No files matched your search
@@ -1,184 +0,0 @@
|
||||
// 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<ToolApprovalStatus, string> = {
|
||||
pending: "等你批准",
|
||||
approving: "提交中",
|
||||
approved: "已批准",
|
||||
denied: "已拒绝",
|
||||
rejected: "服务端拒绝",
|
||||
running: "执行中",
|
||||
complete: "已完成",
|
||||
error: "失败",
|
||||
};
|
||||
|
||||
const BADGE: Record<ToolApprovalStatus, string> = {
|
||||
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 (
|
||||
<div
|
||||
data-state={status}
|
||||
aria-busy={busy}
|
||||
role={rejected ? "alert" : undefined}
|
||||
className={cn(
|
||||
"w-full min-w-0 overflow-hidden rounded-xl border text-sm",
|
||||
rejected ? "border-destructive/50 bg-destructive/10" : "border-border/70 bg-foreground/[0.02]",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<div className="flex items-start gap-3 p-3">
|
||||
<span
|
||||
aria-hidden
|
||||
className={cn(
|
||||
"mt-0.5 grid size-8 shrink-0 place-items-center rounded-lg border",
|
||||
rejected
|
||||
? "border-destructive/40 bg-destructive/15 text-destructive"
|
||||
: "border-border/60 text-muted-foreground",
|
||||
status === "error" && "text-destructive",
|
||||
)}
|
||||
>
|
||||
{busy ? (
|
||||
<LoaderCircle className="size-4 animate-spin motion-reduce:animate-none" />
|
||||
) : rejected ? (
|
||||
<ShieldAlert className="size-4" />
|
||||
) : status === "error" ? (
|
||||
<CircleAlert className="size-4" />
|
||||
) : status === "denied" ? (
|
||||
<X className="size-4" />
|
||||
) : status === "approved" || status === "complete" ? (
|
||||
<Check className="size-4" />
|
||||
) : (
|
||||
<ShieldCheck className="size-4" />
|
||||
)}
|
||||
</span>
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex min-w-0 flex-wrap items-start justify-between gap-x-3 gap-y-1">
|
||||
<div className="min-w-0">
|
||||
<div className={cn("font-medium", rejected ? "text-destructive" : "text-foreground")}>{title}</div>
|
||||
<div className="mt-0.5 font-mono text-meta break-all text-muted-foreground">{tool}</div>
|
||||
</div>
|
||||
<span className={cn("shrink-0 rounded-full border px-2 py-0.5 text-label font-medium", BADGE[status])}>
|
||||
{COPY[status]}
|
||||
</span>
|
||||
</div>
|
||||
{badges ? <div className="mt-1.5 flex flex-wrap items-center gap-1">{badges}</div> : null}
|
||||
{rejected && reason ? (
|
||||
<p className="mt-2 text-caption font-medium break-words text-destructive">原因:{reason}</p>
|
||||
) : null}
|
||||
{description ? <p className="mt-1.5 text-caption leading-5 text-muted-foreground">{description}</p> : null}
|
||||
{rejected && explanation ? (
|
||||
<p className="mt-1.5 text-meta leading-5 text-muted-foreground">{explanation}</p>
|
||||
) : null}
|
||||
{parameters.length ? (
|
||||
<button
|
||||
type="button"
|
||||
aria-expanded={open}
|
||||
aria-controls={id}
|
||||
onClick={() => setOpen(!open)}
|
||||
className="mt-2 inline-flex cursor-pointer items-center gap-1 rounded-md text-meta font-medium text-muted-foreground outline-none transition-colors duration-micro hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring"
|
||||
>
|
||||
{open ? "收起详情" : "查看详情"}
|
||||
<ChevronDown aria-hidden className={cn("size-3.5", CHEVRON_CLASS, open && "rotate-180")} />
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
{parameters.length ? (
|
||||
<Disclosure id={id} open={open}>
|
||||
<dl className="mx-3 mb-3 grid gap-1.5 rounded-lg border border-border/50 p-2.5">
|
||||
{parameters.map((p) => (
|
||||
<div key={p.id} className="grid grid-cols-[minmax(0,7rem)_minmax(0,1fr)] items-start gap-3 text-meta">
|
||||
<dt className="text-muted-foreground">{p.label}</dt>
|
||||
<dd className="min-w-0 font-mono break-all text-foreground/85">{p.value}</dd>
|
||||
</div>
|
||||
))}
|
||||
</dl>
|
||||
</Disclosure>
|
||||
) : null}
|
||||
{status === "pending" && (onApprove || onDeny) ? (
|
||||
<div className="flex flex-wrap items-center gap-2 border-t border-border/60 px-3 py-2.5">
|
||||
<Button size="sm" onClick={onApprove} disabled={!!actionsDisabledReason || !onApprove}>
|
||||
批准
|
||||
</Button>
|
||||
<Button size="sm" variant="ghost" onClick={onDeny} disabled={!!actionsDisabledReason || !onDeny}>
|
||||
拒绝
|
||||
</Button>
|
||||
{actionsDisabledReason ? (
|
||||
<span className="text-meta text-muted-foreground">{actionsDisabledReason}</span>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user