- 后端
- 前端
- CRM
- 人工智能
- AI Agent
【免费下载链接】crm
Comp AI CRM is an open source, CRM designed for AI agents. Agentic-first CRM.
本篇文章以 Comp AI CRM 仓库中锁定的 ai-elements 技能集(见 skills-lock.json)为背景,深入讲解Confirmation组件——一个基于 alert 语义、专门用于管理 AI 工具执行审批工作流的 React 组件。你将掌握如何安装该组件、如何与 AI SDK 的useChat/streamText集成实现"危险工具执行前需人工确认"的完整闭环,理解approval-requested、approval-responded、output-denied、output-available四类状态的渲染规则,以及全部子组件的 Props 契约,并学会基于仓库内的真实示例脚本(confirmation.tsx 等)快速落地生产级审批 UI。
一、Confirmation 组件是什么
Confirmation是 AI Elements 组件库中的一个 alert 型组件,专门用于承载"工具执行审批工作流"的三种关键状态:请求(request)、接受(accept)、拒绝(reject)。它解决的场景非常具体:当 AI Agent 即将执行一个具有破坏性或高风险的工具调用(例如删除文件、操作生产数据库、发送邮件)时,需要先向用户展示审批请求;用户做出决定后,组件再展示对应的执行结果。
组件背景:AI Elements 是基于 shadcn/ui 中有完整说明。该技能在本仓库中以
ai-elements条目被锁定(来源为vercel/ai-elements,见 skills-lock.json),意味着 Comp AI CRM 的 Agent 开发工作流可以直接参考这套组件规范来构建聊天与工具审批界面。
该组件的核心设计目标:
- 在工具执行前展示审批请求及"批准 / 拒绝"操作按钮;
- 在用户响应后展示审批结果(批准或拒绝)与工具输出;
- 通过 props 驱动的状态管理,而非组件内部维护状态,便于与 AI SDK 的消息流(
ToolUIPart)天然对齐。
二、安装与前置条件
安装Confirmation组件只需一条命令:
npx ai-elements@latest add confirmation如果你的项目使用 pnpm 或 bun 作为包管理器,请替换为对应的 runner(原文档与 SKILL.md 均强调这一点):
pnpm dlx ai-elements@latest add confirmation bunx --bun ai-elements@latest add confirmation命令执行后,组件源码(而非编译产物)会被下载并放入你项目的 components 目录——默认是@/components/ai-elements/(或你在 shadcn/ui 的components.json中配置的目录)。这正是 AI Elements 的"代码即配置"哲学:安装完成后,你可以直接打开组件文件阅读实现或按需修改。
前置环境要求(来自 SKILL.md):
- Node.js 18 或更高版本;
- Next.js 项目且已安装 AI SDK(组件示例依赖
@ai-sdk/react的useChat与ai的streamText、DefaultChatTransport); - shadcn/ui 已安装(未安装时,执行安装命令会自动帮你装好);
- 若组件样式不生效,请确认
globals.css已按 Tailwind 4 规范引入 shadcn/ui 基础样式。
三、端到端集成示例:让危险工具执行前必须获得用户确认
原文档给出了一套完整的前后端集成示例(删除文件场景),这也是理解Confirmation组件如何嵌入真实 Agent 工作流的最佳入口。下面将完整呈现并逐段拆解。
3.1 前端:构建带审批的聊天 UI
将以下组件加入你的前端页面(原文档路径为app/page.tsx):
"use client"; import { useChat } from "@ai-sdk/react"; import { DefaultChatTransport, type ToolUIPart } from "ai"; import { useState } from "react"; import { CheckIcon, XIcon } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Confirmation, ConfirmationTitle, ConfirmationRequest, ConfirmationAccepted, ConfirmationRejected, ConfirmationActions, ConfirmationAction, } from "@/components/ai-elements/confirmation"; import { MessageResponse } from "@/components/ai-elements/message"; type DeleteFileInput = { filePath: string; confirm: boolean; }; type DeleteFileToolUIPart = ToolUIPart<{ delete_file: { input: DeleteFileInput; output: { success: boolean; message: string }; }; }>; const Example = () => { const { messages, sendMessage, status, addToolApprovalResponse } = useChat({ transport: new DefaultChatTransport({ api: "/api/chat", }), }); const handleDeleteFile = () => { sendMessage({ text: "Delete the file at /tmp/example.txt" }); }; const latestMessage = messages[messages.length - 1]; const deleteTool = latestMessage?.parts?.find( (part) => part.type === "tool-delete_file" ) as DeleteFileToolUIPart | undefined; return ( <div className="max-w-4xl mx-auto p-6 relative size-full rounded-lg border h-[600px]"> <div className="flex flex-col h-full space-y-4"> <Button onClick={handleDeleteFile} disabled={status !== "ready"}> Delete Example File </Button> {deleteTool?.approval && ( <Confirmation approval={deleteTool.approval} state={deleteTool.state}> <ConfirmationRequest> This tool wants to delete:{" "} <code>{deleteTool.input?.filePath}</code> <br /> Do you approve this action? </ConfirmationRequest> <ConfirmationAccepted> <CheckIcon className="size-4" /> <span>You approved this tool execution</span> </ConfirmationAccepted> <ConfirmationRejected> <XIcon className="size-4" /> <span>You rejected this tool execution</span> </ConfirmationRejected> <ConfirmationActions> <ConfirmationAction variant="outline" onClick={() => addToolApprovalResponse({ id: deleteTool.approval!.id, approved: false, }) } > Reject </ConfirmationAction> <ConfirmationAction variant="default" onClick={() => addToolApprovalResponse({ id: deleteTool.approval!.id, approved: true, }) } > Approve </ConfirmationAction> </ConfirmationActions> </Confirmation> )} {deleteTool?.output && ( <MessageResponse> {deleteTool.output.success ? deleteTool.output.message : `Error: ${deleteTool.output.message}`} </MessageResponse> )} </div> </div> ); }; export default Example;关键机制拆解:
- 消息流中定位工具片段:
latestMessage.parts.find((part) => part.type === "tool-delete_file")从最新消息中取出delete_file工具对应的ToolUIPart。AI SDK 会以tool-<toolName>的形式为每个工具调用生成 UI 片段。 - 条件渲染:
deleteTool?.approval &&保证只有服务端真的发起了审批请求(approval对象存在)时才渲染Confirmation;deleteTool?.output存在时才渲染执行结果。 - 用户决策回传:
addToolApprovalResponse({ id, approved })将用户的批准/拒绝结果按审批 ID 回传给聊天流。参考仓库中的示例脚本 confirmation.tsx,生产环境中对应的回调语义是respondToConfirmationRequest(以approved: true/false作为参数)——无论哪种命名,核心都是"把用户的决策绑定到具体的审批 ID 上"。 - 状态驱动 UI:
Confirmation接收approval(审批对象)与state(工具当前状态)两个 prop,内部依据它们决定渲染ConfirmationRequest(请求中)、ConfirmationAccepted(已批准)还是ConfirmationRejected(已拒绝)的内容块,以及是否显示ConfirmationActions操作按钮组。
3.2 后端:用 requireApproval 开启审批工作流
将以下路由加入后端(原文档路径为app/api/chat/route.tsx):
import { streamText, UIMessage, convertToModelMessages } from "ai"; import { z } from "zod"; // Allow streaming responses up to 30 seconds export const maxDuration = 30; export async function POST(req: Request) { const { messages }: { messages: UIMessage[] } = await req.json(); const result = streamText({ model: "openai/gpt-4o", messages: await convertToModelMessages(messages), tools: { delete_file: { description: "Delete a file from the file system", parameters: z.object({ filePath: z.string().describe("The path to the file to delete"), confirm: z .boolean() .default(false) .describe("Confirmation that the user wants to delete the file"), }), requireApproval: true, // Enable approval workflow execute: async ({ filePath, confirm }) => { if (!confirm) { return { success: false, message: "Deletion not confirmed", }; } // Simulate file deletion await new Promise((resolve) => setTimeout(resolve, 500)); return { success: true, message: `Successfully deleted ${filePath}`, }; }, }, }, }); return result.toUIMessageStreamResponse(); }后端要点:
requireApproval: true是审批工作流的总开关。开启后,AI SDK 会在模型请求调用delete_file工具时暂停执行,生成一个带有approval.id的审批请求并推送到前端;只有前端通过addToolApprovalResponse回传approved: true,工具的execute才会真正运行。- 双保险的
confirm参数:工具参数中的confirm: z.boolean().default(false)与审批流程形成纵深防御——即使审批环节被绕过或出现竞态,execute内还会二次校验confirm是否为true,否则直接返回success: false, message: "Deletion not confirmed"。这种"UI 审批 + 参数校验"的双层设计,是生产级 Agent 应用中防止危险操作误执行的常见模式。 convertToModelMessages将前端传来的UIMessage[](包含工具片段与审批结果)转换为模型可读的消息格式,保证审批决策能正确参与下一轮对话上下文。toUIMessageStreamResponse()将streamText结果以 UI 消息流的形式流式返回,前端DefaultChatTransport借此实时消费工具状态与输出。
四、状态机:组件的渲染决策规则
Confirmation组件通过stateprop 感知工具片段的当前状态。AI SDK 的ToolUIPart可能处于以下六种状态之一:
| 状态 | 含义 | Confirmation 是否渲染 |
|---|---|---|
input-streaming | 工具输入参数正在流式生成 | 否 |
input-available | 工具输入已就绪,尚未请求执行 | 否 |
approval-requested | 已请求用户审批 | 是(渲染请求内容与操作按钮) |
approval-responded | 用户已作出审批决定 | 是(渲染批准/拒绝结果) |
output-denied | 审批被拒绝,输出被拦截 | 是(渲染拒绝结果) |
output-available | 审批通过,工具输出可用 | 是(渲染批准结果) |
从原文档的 Props 表与示例脚本可以确认如下渲染逻辑(仓库示例见 confirmation.tsx、confirmation-accepted.tsx、confirmation-rejected.tsx):
ConfirmationRequest:仅在approval-requested状态渲染,展示工具想要执行的操作说明;ConfirmationAccepted:仅在approval.approved === true且状态为approval-responded或output-available时渲染,展示"已批准"的确认信息(通常搭配CheckIcon);ConfirmationRejected:仅在approval.approved === false且状态为output-denied时渲染,展示"已拒绝"的提示(通常搭配XIcon,示例中使用text-destructive语义色);ConfirmationActions:仅在approval-requested状态渲染,承载 "Approve / Reject" 两个操作按钮。
这种"内容块全部声明、由状态决定显隐"的组合式设计,使得组件可以无缝复用在审批流程的各个阶段,而无需在业务代码里手写大量if/else分支。
五、三种典型状态的可运行示例
原文档在 Examples 一节给出了三种典型状态,仓库的 scripts 目录中提供了对应的可运行 TSX 示例,均使用nanoid生成审批 ID,方便直接对照:
5.1 审批请求状态(approval-requested)
示例文件:confirmation-request.tsx
<Confirmation approval={{ id: nanoid() }} state="approval-requested"> {/* ...ConfirmationTitle / Request / Accepted / Rejected / Actions... */} </Confirmation>当state === "approval-requested"时,组件展示审批请求与操作按钮。仓库示例还演示了一个贴近生产数据库场景的变体——请求中嵌入待执行的 SQL 语句块:
<ConfirmationRequest> This tool wants to execute a query on the production database: <code className="mt-2 block rounded bg-muted p-2 text-sm"> SELECT * FROM users WHERE role = 'admin' </code> </ConfirmationRequest>这说明ConfirmationRequest的children是任意 ReactNode,可以自由放入code块、表格、描述列表等内容,非常适合展示"将要执行的危险操作详情"。
5.2 已批准状态(approval-responded / output-available)
示例文件:confirmation-accepted.tsx
<Confirmation approval={{ approved: true, id: nanoid() }} state="approval-responded" > <ConfirmationTitle> {/* ...Request / Accepted / Rejected... */} </ConfirmationTitle> </Confirmation>用户点击 Approve 后,approval.approved变为true,组件不再渲染操作按钮,仅展示ConfirmationAccepted中的"已批准"状态(CheckIcon+ 文案)。示例中图标使用了text-green-600 dark:text-green-400,随主题明暗自适应。
5.3 已拒绝状态(output-denied)
示例文件:confirmation-rejected.tsx
<Confirmation approval={{ approved: false, id: nanoid() }} state="output-denied" > {/* ...同样声明全部内容块,由状态驱动显隐... */} </Confirmation>用户点击 Reject 后进入output-denied,ConfirmationRejected生效,展示拒绝提示(XIcon+text-destructive语义色)。此时服务端将跳过工具执行、拦截输出,前端也不会出现deleteTool.output结果块(对应 3.1 中deleteTool?.output &&的条件分支不成立)。
六、Props 完整参考
以下为原文档给出的全部 Props 契约,完整继承并附上补充说明。
<Confirmation />(根组件,基于 shadcn/ui Alert)
| Prop | 类型 | 默认值 | 说明 |
|---|---|---|---|
approval | ToolUIPart["approval"] | - | 审批对象,包含审批 ID 与审批状态(approved)。未提供或为undefined时组件不渲染。 |
state | ToolUIPart["state"] | - | 工具当前状态:input-streaming、input-available、approval-requested、approval-responded、output-denied、output-available。input-streaming与input-available状态下不渲染。 |
className | string | - | 应用到 Alert 容器的额外 CSS 类。 |
...props | React.ComponentProps<typeof Alert> | - | 其余 props 全部透传给 Alert 组件(Alert 本身继承HTMLAttributes<HTMLDivElement>,因此id、aria-*、data-*等原生属性均可使用)。 |
<ConfirmationTitle />(基于 AlertDescription)
用于在审批 alert 内展示标题或标签的样式化描述元素。
| Prop | 类型 | 默认值 | 说明 |
|---|---|---|---|
...props | React.ComponentProps<typeof AlertDescription> | - | 其余 props 全部透传给 AlertDescription 组件。 |
<ConfirmationRequest />(请求内容块)
| Prop | 类型 | 默认值 | 说明 |
|---|---|---|---|
children | React.ReactNode | - | 审批请求阶段展示的内容。仅在状态为approval-requested时渲染。 |
<ConfirmationAccepted />(已批准内容块)
| Prop | 类型 | 默认值 | 说明 |
|---|---|---|---|
children | React.ReactNode | - | 审批通过后展示的内容。仅在approval.approved === true且状态为approval-responded或output-available时渲染。 |
<ConfirmationRejected />(已拒绝内容块)
| Prop | 类型 | 默认值 | 说明 |
|---|---|---|---|
children | React.ReactNode | - | 审批被拒后展示的内容。仅在approval.approved === false且状态为output-denied时渲染。 |
<ConfirmationActions />(操作按钮容器)
| Prop | 类型 | 默认值 | 说明 |
|---|---|---|---|
className | string | - | 应用到操作容器 div 的额外 CSS 类。 |
...props | React.ComponentProps<"div"> | - | 其余 props 透传给 div 元素。仅在状态为approval-requested时渲染。 |
<ConfirmationAction />(单个操作按钮,基于 shadcn/ui Button)
| Prop | 类型 | 默认值 | 说明 |
|---|---|---|---|
...props | React.ComponentProps<typeof Button> | - | 其余 props 全部透传给 Button 组件。默认带有h-8 px-3 text-sm尺寸样式,示例中通过variant="outline"(Reject)与variant="default"(Approve)区分主次。 |
七、特性总览与无障碍
原文档列出的特性清单,对应到实际能力:
- 基于 Context 的状态管理:审批工作流的状态由
approval/stateprops 驱动,组件内部通过上下文协调各内容块的显隐; - 按审批状态条件渲染:请求、批准、拒绝三种内容块可同时声明,由状态自动决定渲染哪个;
- 覆盖四种关键状态:
approval-requested、approval-responded、output-denied、output-available; - 基于 shadcn/ui 的 Alert 与 Button 构建:视觉与交互语义与 shadcn/ui 生态一致;
- TypeScript 全面类型支持:
ToolUIPart泛型约束可精确描述工具输入输出类型(如示例中的DeleteFileToolUIPart); - Tailwind CSS 可定制样式:组件代码就在项目内,可直接调整类名;
- 键盘导航与无障碍支持:继承自 shadcn/ui Alert / Button 的可访问性实现;
- 主题感知、自动暗色模式:示例中
text-green-600 dark:text-green-400、text-destructive等语义色会自动适配明暗主题。
八、在本仓库中的定位与扩展建议
- 技能来源锁定:本仓库通过 skills-lock.json 锁定了
ai-elements技能(来源vercel/ai-elements),因此.agents/skills/ai-elements/目录下的 SKILL.md、references/confirmation.md与scripts/示例共同构成了 Comp AI CRM 中构建 Agent 聊天与工具审批界面的标准参考。 - 代码即配置的扩展方式:安装后组件源码位于
@/components/ai-elements/confirmation.tsx(对应项目内路径)。由于代码直接落入你的项目而非封装在库里,你可以像 SKILL.md 中演示的那样直接编辑组件源码——例如移除圆角、替换图标、调整按钮尺寸等,所有修改即时生效。 - 与 CRM Agent 场景的结合:作为 Agentic-first 的 CRM,Comp AI CRM 的 Agent(如删除记录、批量修改字段、发送消息等工具调用)完全可以在前端复用这套审批模式:把
delete_file换成delete_contact/archive_deal,把filePath换成 CRM 记录 ID,即可为危险操作加上"人工确认"的安全闸门。审批请求内容块中甚至可以渲染记录的摘要信息,让用户在做决定前看到完整上下文。
实用排查提示(源自 SKILL.md):若组件样式缺失,检查globals.css是否正确引入 Tailwind 4 + shadcn/ui 基础样式;若 CLI 未添加文件,确认当前目录是项目根目录、components.json配置正确;若@/导入报 "module not found",检查tsconfig.json是否配置了"@/*": ["./*"]路径别名;若主题切换不生效,确保应用使用data-theme属性切换机制。
九、小结
Confirmation组件把"AI 工具执行前的人工审批"这个高频需求抽象为声明式、状态驱动的 UI 原语:前端通过useChat的addToolApprovalResponse回传用户决策,后端通过streamText的requireApproval: true挂起危险工具的执行,两者配合即可在几分钟内为任何 Agent 应用补齐安全审批闭环。结合仓库内的 SKILL.md、references/confirmation.md 与 scripts 下的四个示例文件,你可以直接对照实现,并根据项目需要自由定制组件样式与内容块。
- 后端
- 前端
- CRM
- 人工智能
- AI Agent
【免费下载链接】crm
Comp AI CRM is an open source, CRM designed for AI agents. Agentic-first CRM.
相关推荐
Comp AI CRM 中的 AI 推理展示:ai-elements Reasoning 组件集成实战指南
Comp AI CRM 中的 AI 推理展示:ai elements Reasoning 组件集成实战指南 本文是一份以 ai elements 技能文档 ht
后端前端CRM人工智能AI AgentComp AI CRM 集成指南:用 ai-elements Queue 组件构建 AI 工作流队列与待办列表界面
Comp AI CRM 集成指南:用 ai elements Queue 组件构建 AI 工作流队列与待办列表界面 Queue 是 AI Elements 组件
后端前端CRM人工智能AI AgentAgentOps-AI 实战:基于 Google ADK 实现人工审批工作流
AgentOps AI 实战:基于 Google ADK 实现人工审批工作流 概述 在现代企业自动化流程中,人工审批环节往往是不可或缺的关键节点。本文将介绍如何
人工智能大模型LLMOps可观测性AI 评测Agent Traces
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考