Approval Card
在助手继续工作前,引导用户完成一个或多个决策步骤。
适用场景
- 当界面需要以下行为时使用 Approval Card:在助手继续工作前,引导用户完成一个或多个决策步骤。
安装
选择安装方式,用法示例将同步更新导入路径。
$ bunx --bun shadcn-vue@2.8.2 add https://orvui.efu.me/r/approval-card.json
首次使用时,将安装的 src/components/orvui/styles.css 导入项目样式入口。
示例
Questions
questions.vue逐步完成决策问题,并保留每题答案以便复核。
How focused should the first release be?
1/3Question 1 of 3
packages/vue/src/agents/approval-card/examples/questions.vue
<script setup lang="ts">
import { onBeforeUnmount, ref } from "vue";
import { RotateCcw } from "lucide-vue-next";
import {
ApprovalCard,
type ApprovalCardQuestion,
type ApprovalCardStatus,
} from "@/components/orvui/agents/approval-card";
const QUESTIONS: ApprovalCardQuestion[] = [
{
id: "scope",
title: "How focused should the first release be?",
options: [
{
value: "focused",
label: "A focused starter set",
description: "Ship a few polished pieces first.",
},
{
value: "broad",
label: "A broader collection",
description: "Cover more ground with lighter depth.",
},
{
value: "flagship",
label: "One flagship experience",
description: "Pour everything into a single showcase.",
},
],
allowCustom: true,
customPlaceholder: "Describe another scope…",
},
{
id: "checks",
title: "Which checks should block publishing?",
description:
"Select every check the agent must pass before it can continue.",
multiple: true,
options: [
{ value: "types", label: "Type safety" },
{ value: "accessibility", label: "Accessibility" },
{ value: "registry", label: "Registry validation" },
],
},
{
id: "preserve",
title: "Anything the agent should preserve?",
allowCustom: true,
customPlaceholder: "Add a final constraint…",
},
];
const status = ref<ApprovalCardStatus>("pending"),
run = ref(0);
let epoch = 0;
const scheduledTasks = new Set<ReturnType<typeof setTimeout>>();
function cancel() {
epoch++;
scheduledTasks.forEach(clearTimeout);
scheduledTasks.clear();
}
function later(milliseconds: number, task: () => void) {
const version = epoch;
const handle = setTimeout(() => {
scheduledTasks.delete(handle);
if (version === epoch) task();
}, milliseconds);
scheduledTasks.add(handle);
}
onBeforeUnmount(cancel);
function submit() {
cancel();
status.value = "submitting";
later(750, () => {
status.value = "answered";
});
}
function replay() {
cancel();
run.value++;
status.value = "pending";
}
</script>
<template>
<div class="relative h-[470px] w-full max-w-lg">
<ApprovalCard
:key="run"
:questions="QUESTIONS"
:status="status"
@submit="submit"
result="Three responses sent to the agent."
/><button
type="button"
@click="replay"
class="absolute bottom-0 left-0 inline-flex items-center gap-1.5 rounded-full px-2 py-1 text-xs font-medium text-muted-foreground outline-none transition-colors hover:bg-muted hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring"
>
<RotateCcw class="size-3" />Replay
</button>
</div>
</template>
packages/vue/src/agents/approval-card/src/ApprovalCard.vue
Review and Approve
review.vue在确认待执行操作前,复核已经收集的答案。
Publish the component update?
Input requiredThe agent has prepared the release and is waiting for your decision.
- Release
- approval-card
- Checks
- 4 passed
- Visibility
- Public registry
packages/vue/src/agents/approval-card/examples/review.vue
<script setup lang="ts">
import { onBeforeUnmount, ref } from "vue";
import { RotateCcw } from "lucide-vue-next";
import { ApprovalCard, type ApprovalCardStatus } from "@/components/orvui/agents/approval-card";
const status = ref<ApprovalCardStatus>("pending"),
run = ref(0);
const details = [
{ label: "Release", text: "approval-card", mono: true },
{ label: "Checks", text: "4 passed", mono: false },
{ label: "Visibility", text: "Public registry", mono: false },
];
const results = {
approved: "Publishing was approved.",
"changes-requested": "The agent will wait for revision notes.",
rejected: "Publishing was declined.",
};
let epoch = 0;
const scheduledTasks = new Set<ReturnType<typeof setTimeout>>();
function cancel() {
epoch++;
scheduledTasks.forEach(clearTimeout);
scheduledTasks.clear();
}
function later(milliseconds: number, task: () => void) {
const version = epoch;
const handle = setTimeout(() => {
scheduledTasks.delete(handle);
if (version === epoch) task();
}, milliseconds);
scheduledTasks.add(handle);
}
onBeforeUnmount(cancel);
function finish(value: ApprovalCardStatus) {
cancel();
status.value = "submitting";
later(700, () => {
status.value = value;
});
}
function replay() {
cancel();
run.value++;
status.value = "pending";
}
</script>
<template>
<div class="relative h-[360px] w-full max-w-lg">
<ApprovalCard
:key="run"
title="Publish the component update?"
description="The agent has prepared the release and is waiting for your decision."
:status="status"
@approve="finish('approved')"
@request-changes="finish('changes-requested')"
@reject="finish('rejected')"
:result="results[status as keyof typeof results] ?? results.rejected"
><dl class="grid gap-1 text-xs">
<div
v-for="item in details"
:key="item.label"
class="flex items-center justify-between gap-4 py-1"
>
<dt class="text-muted-foreground">{{ item.label }}</dt>
<dd :class="[item.mono && 'font-mono', 'text-foreground/80']">
{{ item.text }}
</dd>
</div>
</dl></ApprovalCard
><button
type="button"
@click="replay"
class="absolute bottom-0 left-0 inline-flex items-center gap-1.5 rounded-full px-2 py-1 text-xs font-medium text-muted-foreground outline-none transition-colors hover:bg-muted hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring"
>
<RotateCcw class="size-3" />Replay
</button>
</div>
</template>
packages/vue/src/agents/approval-card/src/ApprovalCard.vue
组合结构
使用公开部件 ApprovalCard 组合 Approval Card,并从该组件的公开入口导入。
Message
└── MessageContent
└── ApprovalCard说明: Tool Approval 为待执行工具操作展示说明,并请求明确决策。 Message 使用内容、附件、来源与操作部件组合会话条目。 Todo List 展示带展开详情和明确任务状态的工作条目序列。
工作方式
在助手继续工作前,引导用户完成一个或多个决策步骤。
状态与事件
需要由表单、路由或父组件管理时,绑定下列状态并接收对应更新。v-model 是属性与 update 事件的简写;值的类型应与 API 一致。
| 组件 | Vue 绑定 | 更新事件 |
|---|---|---|
ApprovalCard | v-model:answersApprovalCardAnswers | update:answers |
ApprovalCard | v-model:stepnumber | update:step |
可选受控属性未传入时,按组件文档的默认行为工作;不要用固定的 false 或空值代替未传入。异步请求和持久化在应用中处理,再把结果传回组件。
API 参考
Questions
title?boolean | null | string | number | void | VNode<RendererNode, RendererElement, { [key: string]: any; }> | VNodeArrayChildren | ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> | FunctionalComponent<any, {}, any, {}> | ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions>"Approval required"description?boolean | null | string | number | void | VNode<RendererNode, RendererElement, { [key: string]: any; }> | VNodeArrayChildren | ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> | FunctionalComponent<any, {}, any, {}> | ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions>—questions?ApprovalCardQuestion[]() => []status?"pending" | "submitting" | "approved" | "rejected" | "changes-requested" | "answered""pending"answers?ApprovalCardAnswers—defaultAnswers?ApprovalCardAnswers() => ({})step?number—defaultStep?number0approveLabel?boolean | null | string | number | void | VNode<RendererNode, RendererElement, { [key: string]: any; }> | VNodeArrayChildren | ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> | FunctionalComponent<any, {}, any, {}> | ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions>"Approve"submitLabel?boolean | null | string | number | void | VNode<RendererNode, RendererElement, { [key: string]: any; }> | VNodeArrayChildren | ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> | FunctionalComponent<any, {}, any, {}> | ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions>"Submit response"result?boolean | null | string | number | void | VNode<RendererNode, RendererElement, { [key: string]: any; }> | VNodeArrayChildren | ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> | FunctionalComponent<any, {}, any, {}> | ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions>undefined事件
update:answers[answers: ApprovalCardAnswers]@update:answersanswersChange[answers: ApprovalCardAnswers]@answersChangeupdate:step[step: number]@update:stepstepChange[step: number]@stepChangesubmit[answers: ApprovalCardAnswers]@submitapprove[]@approvereject[]@rejectrequestChanges[]@requestChangesdismiss[]@dismiss插槽
default() => VNodeChild#defaultReview and Approve
title?boolean | null | string | number | void | VNode<RendererNode, RendererElement, { [key: string]: any; }> | VNodeArrayChildren | ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> | FunctionalComponent<any, {}, any, {}> | ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions>"Approval required"description?boolean | null | string | number | void | VNode<RendererNode, RendererElement, { [key: string]: any; }> | VNodeArrayChildren | ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> | FunctionalComponent<any, {}, any, {}> | ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions>—questions?ApprovalCardQuestion[]() => []status?"pending" | "submitting" | "approved" | "rejected" | "changes-requested" | "answered""pending"answers?ApprovalCardAnswers—defaultAnswers?ApprovalCardAnswers() => ({})step?number—defaultStep?number0approveLabel?boolean | null | string | number | void | VNode<RendererNode, RendererElement, { [key: string]: any; }> | VNodeArrayChildren | ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> | FunctionalComponent<any, {}, any, {}> | ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions>"Approve"submitLabel?boolean | null | string | number | void | VNode<RendererNode, RendererElement, { [key: string]: any; }> | VNodeArrayChildren | ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> | FunctionalComponent<any, {}, any, {}> | ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions>"Submit response"result?boolean | null | string | number | void | VNode<RendererNode, RendererElement, { [key: string]: any; }> | VNodeArrayChildren | ComponentOptions<any, any, any, ComputedOptions, MethodOptions, any, any, any, string, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions> | FunctionalComponent<any, {}, any, {}> | ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions>undefined事件
update:answers[answers: ApprovalCardAnswers]@update:answersanswersChange[answers: ApprovalCardAnswers]@answersChangeupdate:step[step: number]@update:stepstepChange[step: number]@stepChangesubmit[answers: ApprovalCardAnswers]@submitapprove[]@approvereject[]@rejectrequestChanges[]@requestChangesdismiss[]@dismiss插槽
default() => VNodeChild#default无障碍
为交互控件提供明确的无障碍名称。保留文档中的键盘路径、可见焦点与减少动态效果状态;自定义插槽应继续保留所替代部件的语义。
常见问题
- 如何配置 Approval Card?
- 通过 title、description、questions、status、answers、defaultAnswers、step、defaultStep、approveLabel、submitLabel、result 配置 Approval Card;API 表列出其类型与默认值。
- 如何连接到应用状态?
- 监听 update:answers、answersChange、update:step、stepChange、submit、approve、reject、requestChanges、dismiss,将组件连接到应用状态。
- 可以在服务端渲染吗?
- 可以在 Vue SSR 应用中正常渲染。服务端与客户端的初始 props 应保持一致;浏览器测量与交互资源在挂载后启动。
更新于
来源与许可 ↗MIT · 0.1.1