@dowel-ui/react 0.1.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +193 -0
- package/dist/components/ai-action-ledger/ai-action-ledger.d.ts +77 -0
- package/dist/components/ai-action-ledger/ai-action-ledger.d.ts.map +1 -0
- package/dist/components/ai-action-ledger/ai-action-ledger.js +224 -0
- package/dist/components/ai-action-ledger/ai-action-ledger.js.map +1 -0
- package/dist/components/ai-action-ledger/index.d.ts +2 -0
- package/dist/components/ai-action-ledger/index.js +2 -0
- package/dist/components/ai-action-ledger/meta.js +17 -0
- package/dist/components/ai-action-ledger/meta.js.map +1 -0
- package/dist/components/ai-agent-plan/ai-agent-plan.d.ts +50 -0
- package/dist/components/ai-agent-plan/ai-agent-plan.d.ts.map +1 -0
- package/dist/components/ai-agent-plan/ai-agent-plan.js +156 -0
- package/dist/components/ai-agent-plan/ai-agent-plan.js.map +1 -0
- package/dist/components/ai-agent-plan/index.d.ts +2 -0
- package/dist/components/ai-agent-plan/index.js +2 -0
- package/dist/components/ai-agent-plan/meta.js +17 -0
- package/dist/components/ai-agent-plan/meta.js.map +1 -0
- package/dist/components/ai-agent-status/ai-agent-status.d.ts +1 -1
- package/dist/components/ai-approval-request/ai-approval-request.d.ts +67 -0
- package/dist/components/ai-approval-request/ai-approval-request.d.ts.map +1 -0
- package/dist/components/ai-approval-request/ai-approval-request.js +218 -0
- package/dist/components/ai-approval-request/ai-approval-request.js.map +1 -0
- package/dist/components/ai-approval-request/index.d.ts +2 -0
- package/dist/components/ai-approval-request/index.js +2 -0
- package/dist/components/ai-approval-request/meta.js +17 -0
- package/dist/components/ai-approval-request/meta.js.map +1 -0
- package/dist/components/ai-inline-completion/ai-inline-completion.d.ts +45 -0
- package/dist/components/ai-inline-completion/ai-inline-completion.d.ts.map +1 -0
- package/dist/components/ai-inline-completion/ai-inline-completion.js +110 -0
- package/dist/components/ai-inline-completion/ai-inline-completion.js.map +1 -0
- package/dist/components/ai-inline-completion/index.d.ts +2 -0
- package/dist/components/ai-inline-completion/index.js +2 -0
- package/dist/components/ai-inline-completion/meta.js +17 -0
- package/dist/components/ai-inline-completion/meta.js.map +1 -0
- package/dist/components/ai-response/ai-response.d.ts.map +1 -1
- package/dist/components/ai-response/ai-response.js +1 -0
- package/dist/components/ai-response/ai-response.js.map +1 -1
- package/dist/components/ai-structured-output/ai-structured-output.d.ts +75 -0
- package/dist/components/ai-structured-output/ai-structured-output.d.ts.map +1 -0
- package/dist/components/ai-structured-output/ai-structured-output.js +137 -0
- package/dist/components/ai-structured-output/ai-structured-output.js.map +1 -0
- package/dist/components/ai-structured-output/index.d.ts +2 -0
- package/dist/components/ai-structured-output/index.js +2 -0
- package/dist/components/ai-structured-output/meta.js +17 -0
- package/dist/components/ai-structured-output/meta.js.map +1 -0
- package/dist/components/ai-tool/ai-tool.d.ts +1 -1
- package/dist/components/alert/alert.d.ts +1 -1
- package/dist/components/badge/badge.d.ts +1 -1
- package/dist/components/button/button.d.ts +1 -1
- package/dist/components/file-upload/file-upload.d.ts +46 -0
- package/dist/components/file-upload/file-upload.d.ts.map +1 -0
- package/dist/components/file-upload/file-upload.js +204 -0
- package/dist/components/file-upload/file-upload.js.map +1 -0
- package/dist/components/file-upload/index.d.ts +3 -0
- package/dist/components/file-upload/index.js +3 -0
- package/dist/components/file-upload/meta.js +17 -0
- package/dist/components/file-upload/meta.js.map +1 -0
- package/dist/components/file-upload/upload-queue.d.ts +87 -0
- package/dist/components/file-upload/upload-queue.d.ts.map +1 -0
- package/dist/components/file-upload/upload-queue.js +249 -0
- package/dist/components/file-upload/upload-queue.js.map +1 -0
- package/dist/components/meter/index.d.ts +2 -0
- package/dist/components/meter/index.js +2 -0
- package/dist/components/meter/meta.js +17 -0
- package/dist/components/meter/meta.js.map +1 -0
- package/dist/components/meter/meter.d.ts +69 -0
- package/dist/components/meter/meter.d.ts.map +1 -0
- package/dist/components/meter/meter.js +144 -0
- package/dist/components/meter/meter.js.map +1 -0
- package/dist/components/metric-delta/index.d.ts +2 -0
- package/dist/components/metric-delta/index.js +2 -0
- package/dist/components/metric-delta/meta.js +17 -0
- package/dist/components/metric-delta/meta.js.map +1 -0
- package/dist/components/metric-delta/metric-delta.d.ts +59 -0
- package/dist/components/metric-delta/metric-delta.d.ts.map +1 -0
- package/dist/components/metric-delta/metric-delta.js +82 -0
- package/dist/components/metric-delta/metric-delta.js.map +1 -0
- package/dist/components/progress/progress.d.ts +1 -1
- package/dist/components/progress/progress.js +1 -0
- package/dist/components/progress/progress.js.map +1 -1
- package/dist/components/record-diff/index.d.ts +2 -0
- package/dist/components/record-diff/index.js +2 -0
- package/dist/components/record-diff/meta.js +17 -0
- package/dist/components/record-diff/meta.js.map +1 -0
- package/dist/components/record-diff/record-diff.d.ts +56 -0
- package/dist/components/record-diff/record-diff.d.ts.map +1 -0
- package/dist/components/record-diff/record-diff.js +199 -0
- package/dist/components/record-diff/record-diff.js.map +1 -0
- package/dist/components/spinner/spinner.js +1 -0
- package/dist/components/spinner/spinner.js.map +1 -1
- package/dist/components/tags-input/index.d.ts +2 -0
- package/dist/components/tags-input/index.js +2 -0
- package/dist/components/tags-input/meta.js +17 -0
- package/dist/components/tags-input/meta.js.map +1 -0
- package/dist/components/tags-input/tags-input.d.ts +47 -0
- package/dist/components/tags-input/tags-input.d.ts.map +1 -0
- package/dist/components/tags-input/tags-input.js +182 -0
- package/dist/components/tags-input/tags-input.js.map +1 -0
- package/dist/components/toast/toast.d.ts +1 -1
- package/dist/index.d.ts +22 -1
- package/dist/index.js +12 -1
- package/dist/registry/components.d.ts.map +1 -1
- package/dist/registry/components.js +65 -45
- package/dist/registry/components.js.map +1 -1
- package/package.json +36 -4
- package/src/components/ai-action-ledger/ai-action-ledger.tsx +381 -0
- package/src/components/ai-action-ledger/index.ts +14 -0
- package/src/components/ai-action-ledger/meta.ts +19 -0
- package/src/components/ai-agent-plan/ai-agent-plan.tsx +0 -0
- package/src/components/ai-agent-plan/index.ts +10 -0
- package/src/components/ai-agent-plan/meta.ts +19 -0
- package/src/components/ai-approval-request/ai-approval-request.tsx +368 -0
- package/src/components/ai-approval-request/index.ts +7 -0
- package/src/components/ai-approval-request/meta.ts +21 -0
- package/src/components/ai-inline-completion/ai-inline-completion.tsx +214 -0
- package/src/components/ai-inline-completion/index.ts +1 -0
- package/src/components/ai-inline-completion/meta.ts +20 -0
- package/src/components/ai-response/ai-response.tsx +1 -0
- package/src/components/ai-structured-output/ai-structured-output.tsx +268 -0
- package/src/components/ai-structured-output/index.ts +10 -0
- package/src/components/ai-structured-output/meta.ts +18 -0
- package/src/components/file-upload/file-upload.tsx +322 -0
- package/src/components/file-upload/index.ts +20 -0
- package/src/components/file-upload/meta.ts +21 -0
- package/src/components/file-upload/upload-queue.ts +337 -0
- package/src/components/meter/index.ts +8 -0
- package/src/components/meter/meta.ts +20 -0
- package/src/components/meter/meter.tsx +219 -0
- package/src/components/metric-delta/index.ts +6 -0
- package/src/components/metric-delta/meta.ts +18 -0
- package/src/components/metric-delta/metric-delta.tsx +180 -0
- package/src/components/progress/progress.tsx +3 -0
- package/src/components/record-diff/index.ts +10 -0
- package/src/components/record-diff/meta.ts +19 -0
- package/src/components/record-diff/record-diff.tsx +341 -0
- package/src/components/spinner/spinner.tsx +5 -0
- package/src/components/tags-input/index.ts +1 -0
- package/src/components/tags-input/meta.ts +23 -0
- package/src/components/tags-input/tags-input.tsx +300 -0
- package/src/index.ts +10 -0
- package/src/registry/components.ts +20 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { cn } from "../../lib/utils.js";
|
|
3
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
import { createContext, useContext, useMemo, useState } from "react";
|
|
5
|
+
//#region src/components/ai-agent-plan/ai-agent-plan.tsx
|
|
6
|
+
const STATUS_LABEL = {
|
|
7
|
+
pending: "Not started",
|
|
8
|
+
running: "In progress",
|
|
9
|
+
done: "Done",
|
|
10
|
+
failed: "Failed",
|
|
11
|
+
skipped: "Skipped"
|
|
12
|
+
};
|
|
13
|
+
/** Flattens one level, so counting and "step N of M" include sub-steps. */
|
|
14
|
+
function flatten(steps) {
|
|
15
|
+
return steps.flatMap((step) => [step, ...step.steps ?? []]);
|
|
16
|
+
}
|
|
17
|
+
function describeRevision(added, removed) {
|
|
18
|
+
const parts = [];
|
|
19
|
+
if (added.length === 1 && added[0]) parts.push(`Plan updated: added "${added[0].title}"`);
|
|
20
|
+
else if (added.length > 1) parts.push(`Plan updated: ${String(added.length)} steps added`);
|
|
21
|
+
if (removed === 1) parts.push("1 step removed");
|
|
22
|
+
else if (removed > 1) parts.push(`${String(removed)} steps removed`);
|
|
23
|
+
return parts.join(", ");
|
|
24
|
+
}
|
|
25
|
+
const PlanContext = createContext(null);
|
|
26
|
+
function usePlanContext(component) {
|
|
27
|
+
const context = useContext(PlanContext);
|
|
28
|
+
if (!context) throw new Error(`${component} must be rendered inside <AgentPlan>.`);
|
|
29
|
+
return context;
|
|
30
|
+
}
|
|
31
|
+
function AgentPlan({ className, steps, label, children, ...props }) {
|
|
32
|
+
const flat = useMemo(() => flatten(steps), [steps]);
|
|
33
|
+
const ids = flat.map((step) => step.id);
|
|
34
|
+
const [seen, setSeen] = useState(ids);
|
|
35
|
+
const [revision, setRevision] = useState("");
|
|
36
|
+
if (ids.join("\0") !== seen.join("\0")) {
|
|
37
|
+
const added = flat.filter((step) => !seen.includes(step.id));
|
|
38
|
+
const removed = seen.filter((id) => !ids.includes(id)).length;
|
|
39
|
+
setSeen(ids);
|
|
40
|
+
setRevision(added.length > 0 || removed > 0 ? describeRevision(added, removed) : "");
|
|
41
|
+
}
|
|
42
|
+
const currentIndex = flat.findIndex((step) => step.status === "running");
|
|
43
|
+
const context = useMemo(() => ({
|
|
44
|
+
steps,
|
|
45
|
+
flat,
|
|
46
|
+
currentIndex
|
|
47
|
+
}), [
|
|
48
|
+
steps,
|
|
49
|
+
flat,
|
|
50
|
+
currentIndex
|
|
51
|
+
]);
|
|
52
|
+
return /* @__PURE__ */ jsx(PlanContext.Provider, {
|
|
53
|
+
value: context,
|
|
54
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
55
|
+
"data-slot": "agent-plan",
|
|
56
|
+
"aria-label": label,
|
|
57
|
+
role: "group",
|
|
58
|
+
className: cn("flex flex-col gap-2", className),
|
|
59
|
+
...props,
|
|
60
|
+
children: [children ?? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(AgentPlanSummary, {}), /* @__PURE__ */ jsx(AgentPlanSteps, {})] }), /* @__PURE__ */ jsx("span", {
|
|
61
|
+
"aria-live": "polite",
|
|
62
|
+
"data-slot": "agent-plan-revision",
|
|
63
|
+
className: "sr-only",
|
|
64
|
+
children: revision
|
|
65
|
+
})]
|
|
66
|
+
})
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
/** "Step 3 of 6" plus the overall state, in words. */
|
|
70
|
+
function AgentPlanSummary({ className, ...props }) {
|
|
71
|
+
const { flat, currentIndex } = usePlanContext("AgentPlanSummary");
|
|
72
|
+
const done = flat.filter((step) => step.status === "done").length;
|
|
73
|
+
const failed = flat.filter((step) => step.status === "failed").length;
|
|
74
|
+
const running = currentIndex >= 0;
|
|
75
|
+
const message = !flat.some((step) => step.status !== "pending") ? `Proposed plan, ${String(flat.length)} steps` : running ? `Step ${String(currentIndex + 1)} of ${String(flat.length)}` : failed > 0 ? `${String(done)} of ${String(flat.length)} done, ${String(failed)} failed` : `${String(done)} of ${String(flat.length)} done`;
|
|
76
|
+
return /* @__PURE__ */ jsx("p", {
|
|
77
|
+
"data-slot": "agent-plan-summary",
|
|
78
|
+
className: cn("text-xs text-muted-foreground tabular-nums", className),
|
|
79
|
+
...props,
|
|
80
|
+
children: message
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
function AgentPlanSteps({ className, ...props }) {
|
|
84
|
+
const { steps } = usePlanContext("AgentPlanSteps");
|
|
85
|
+
return /* @__PURE__ */ jsx("ol", {
|
|
86
|
+
"data-slot": "agent-plan-steps",
|
|
87
|
+
className: cn("flex list-none flex-col gap-1", className),
|
|
88
|
+
...props,
|
|
89
|
+
children: steps.map((step) => /* @__PURE__ */ jsx(AgentPlanStep, { step }, step.id))
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
function AgentPlanStep({ className, step, nested = false, ...props }) {
|
|
93
|
+
const { status, title, detail, error, steps: children } = step;
|
|
94
|
+
return /* @__PURE__ */ jsxs("li", {
|
|
95
|
+
"data-slot": "agent-plan-step",
|
|
96
|
+
"data-status": status,
|
|
97
|
+
"aria-current": status === "running" ? "step" : void 0,
|
|
98
|
+
className: cn("flex flex-col gap-1", nested && "ml-5", className),
|
|
99
|
+
...props,
|
|
100
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
101
|
+
className: "flex items-start gap-2",
|
|
102
|
+
children: [/* @__PURE__ */ jsx(StepMarker, { status }), /* @__PURE__ */ jsxs("div", {
|
|
103
|
+
className: "flex min-w-0 flex-1 flex-col",
|
|
104
|
+
children: [
|
|
105
|
+
/* @__PURE__ */ jsxs("span", {
|
|
106
|
+
className: cn("text-sm", status === "pending" && "text-muted-foreground", status === "skipped" && "text-muted-foreground line-through", status === "failed" && "text-destructive", status === "running" && "font-medium"),
|
|
107
|
+
children: [title, /* @__PURE__ */ jsxs("span", {
|
|
108
|
+
className: "sr-only",
|
|
109
|
+
children: [", ", STATUS_LABEL[status]]
|
|
110
|
+
})]
|
|
111
|
+
}),
|
|
112
|
+
detail ? /* @__PURE__ */ jsx("span", {
|
|
113
|
+
className: "text-xs text-muted-foreground",
|
|
114
|
+
children: detail
|
|
115
|
+
}) : null,
|
|
116
|
+
status === "failed" && error ? /* @__PURE__ */ jsx("span", {
|
|
117
|
+
"data-slot": "agent-plan-error",
|
|
118
|
+
className: "text-xs text-destructive",
|
|
119
|
+
children: error
|
|
120
|
+
}) : null
|
|
121
|
+
]
|
|
122
|
+
})]
|
|
123
|
+
}), children && children.length > 0 ? /* @__PURE__ */ jsx("ol", {
|
|
124
|
+
className: "flex list-none flex-col gap-1",
|
|
125
|
+
children: children.map((child) => /* @__PURE__ */ jsx(AgentPlanStep, {
|
|
126
|
+
step: child,
|
|
127
|
+
nested: true
|
|
128
|
+
}, child.id))
|
|
129
|
+
}) : null]
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* The status shape.
|
|
134
|
+
*
|
|
135
|
+
* aria-hidden throughout: every marker repeats what the step already states in
|
|
136
|
+
* text, and announcing both means hearing the status twice per step.
|
|
137
|
+
*/
|
|
138
|
+
function StepMarker({ status }) {
|
|
139
|
+
const base = "mt-0.5 grid size-4 shrink-0 place-items-center rounded-full border text-[9px]";
|
|
140
|
+
if (status === "running") return /* @__PURE__ */ jsx("span", {
|
|
141
|
+
"aria-hidden": "true",
|
|
142
|
+
"data-slot": "agent-plan-marker",
|
|
143
|
+
className: cn(base, "animate-pulse-soft border-primary bg-primary/15 text-primary"),
|
|
144
|
+
children: /* @__PURE__ */ jsx("span", { className: "size-1.5 rounded-full bg-primary" })
|
|
145
|
+
});
|
|
146
|
+
return /* @__PURE__ */ jsx("span", {
|
|
147
|
+
"aria-hidden": "true",
|
|
148
|
+
"data-slot": "agent-plan-marker",
|
|
149
|
+
className: cn(base, status === "done" && "border-success bg-success text-success-foreground", status === "failed" && "border-destructive bg-destructive text-destructive-foreground", status === "skipped" && "border-border-strong text-muted-foreground", status === "pending" && "border-border-strong"),
|
|
150
|
+
children: status === "done" ? "✓" : status === "failed" ? "✕" : status === "skipped" ? "–" : null
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
//#endregion
|
|
154
|
+
export { AgentPlan, AgentPlanStep, AgentPlanSteps, AgentPlanSummary };
|
|
155
|
+
|
|
156
|
+
//# sourceMappingURL=ai-agent-plan.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai-agent-plan.js","names":[],"sources":["../../../src/components/ai-agent-plan/ai-agent-plan.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n createContext,\n useContext,\n useMemo,\n useState,\n type ComponentPropsWithRef,\n type ReactNode,\n} from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\n/**\n * What an agent intends to do, and how far through it is.\n *\n * Not a stepper. A wizard's steps are decided up front and never change; an\n * agent's plan is a hypothesis it revises as it learns — it adds a step when a\n * lookup fails, drops one that turned out to be unnecessary, and reorders when\n * a dependency appears. Every stepper treats its step list as fixed, so a plan\n * rendered in one either silently mutates under the reader or re-renders from\n * scratch and loses their place.\n *\n * So revision is the feature here, not an edge case. When the plan changes, the\n * change is announced — \"the model added a step\" is information, and watching a\n * list quietly grow is not the same as being told.\n *\n * Approval is not here. Deciding whether a plan may run is a different\n * component with a different shape; this one displays and reports.\n */\n\nexport type StepStatus = \"pending\" | \"running\" | \"done\" | \"failed\" | \"skipped\";\n\nexport interface PlanStep {\n id: string;\n title: string;\n status: StepStatus;\n /** What the step will do, or what it found. Shown under the title. */\n detail?: string;\n /** Why it failed. Shown only when the status is \"failed\". */\n error?: string;\n /** One level of sub-steps. Deeper nesting is a tree, which this is not. */\n steps?: PlanStep[];\n}\n\nconst STATUS_LABEL: Record<StepStatus, string> = {\n pending: \"Not started\",\n running: \"In progress\",\n done: \"Done\",\n failed: \"Failed\",\n skipped: \"Skipped\",\n};\n\n/** Flattens one level, so counting and \"step N of M\" include sub-steps. */\nfunction flatten(steps: PlanStep[]): PlanStep[] {\n return steps.flatMap((step) => [step, ...(step.steps ?? [])]);\n}\n\nfunction describeRevision(added: PlanStep[], removed: number): string {\n const parts: string[] = [];\n if (added.length === 1 && added[0]) parts.push(`Plan updated: added \"${added[0].title}\"`);\n else if (added.length > 1) parts.push(`Plan updated: ${String(added.length)} steps added`);\n if (removed === 1) parts.push(\"1 step removed\");\n else if (removed > 1) parts.push(`${String(removed)} steps removed`);\n return parts.join(\", \");\n}\n\ninterface PlanContextValue {\n steps: PlanStep[];\n flat: PlanStep[];\n currentIndex: number;\n}\n\nconst PlanContext = createContext<PlanContextValue | null>(null);\n\nfunction usePlanContext(component: string): PlanContextValue {\n const context = useContext(PlanContext);\n if (!context) {\n throw new Error(`${component} must be rendered inside <AgentPlan>.`);\n }\n return context;\n}\n\nexport interface AgentPlanProps extends Omit<ComponentPropsWithRef<\"div\">, \"children\"> {\n steps: PlanStep[];\n /** Names the plan, so the list is not an unlabelled sequence. */\n label: string;\n children?: ReactNode;\n}\n\nexport function AgentPlan({ className, steps, label, children, ...props }: AgentPlanProps) {\n const flat = useMemo(() => flatten(steps), [steps]);\n const ids = flat.map((step) => step.id);\n\n // Revision detection, adjusted during render rather than in an effect: an\n // effect would announce a frame after the change is already on screen, and a\n // ref read during render is unsafe under concurrent rendering.\n const [seen, setSeen] = useState<string[]>(ids);\n const [revision, setRevision] = useState(\"\");\n\n if (ids.join(\"\u0000\") !== seen.join(\"\u0000\")) {\n const added = flat.filter((step) => !seen.includes(step.id));\n const removed = seen.filter((id) => !ids.includes(id)).length;\n setSeen(ids);\n // A plan whose steps only changed status has not been revised. Only\n // structural change is worth interrupting for.\n setRevision(added.length > 0 || removed > 0 ? describeRevision(added, removed) : \"\");\n }\n\n const currentIndex = flat.findIndex((step) => step.status === \"running\");\n\n const context = useMemo<PlanContextValue>(\n () => ({ steps, flat, currentIndex }),\n [steps, flat, currentIndex],\n );\n\n return (\n <PlanContext.Provider value={context}>\n <div\n data-slot=\"agent-plan\"\n aria-label={label}\n role=\"group\"\n className={cn(\"flex flex-col gap-2\", className)}\n {...props}\n >\n {children ?? (\n <>\n <AgentPlanSummary />\n <AgentPlanSteps />\n </>\n )}\n\n {/* Structural changes only. Announcing every status transition would\n talk over the reader continuously on a plan of any length. */}\n <span aria-live=\"polite\" data-slot=\"agent-plan-revision\" className=\"sr-only\">\n {revision}\n </span>\n </div>\n </PlanContext.Provider>\n );\n}\n\n/** \"Step 3 of 6\" plus the overall state, in words. */\nexport function AgentPlanSummary({ className, ...props }: ComponentPropsWithRef<\"p\">) {\n const { flat, currentIndex } = usePlanContext(\"AgentPlanSummary\");\n\n const done = flat.filter((step) => step.status === \"done\").length;\n const failed = flat.filter((step) => step.status === \"failed\").length;\n const running = currentIndex >= 0;\n const started = flat.some((step) => step.status !== \"pending\");\n\n const message = !started\n ? `Proposed plan, ${String(flat.length)} steps`\n : running\n ? `Step ${String(currentIndex + 1)} of ${String(flat.length)}`\n : failed > 0\n ? `${String(done)} of ${String(flat.length)} done, ${String(failed)} failed`\n : `${String(done)} of ${String(flat.length)} done`;\n\n return (\n <p\n data-slot=\"agent-plan-summary\"\n className={cn(\"text-xs text-muted-foreground tabular-nums\", className)}\n {...props}\n >\n {message}\n </p>\n );\n}\n\nexport function AgentPlanSteps({ className, ...props }: ComponentPropsWithRef<\"ol\">) {\n const { steps } = usePlanContext(\"AgentPlanSteps\");\n\n return (\n // An ordered list: a plan is a sequence, and the order carries meaning that\n // an unordered list would throw away.\n <ol\n data-slot=\"agent-plan-steps\"\n className={cn(\"flex list-none flex-col gap-1\", className)}\n {...props}\n >\n {steps.map((step) => (\n <AgentPlanStep key={step.id} step={step} />\n ))}\n </ol>\n );\n}\n\nexport interface AgentPlanStepProps extends Omit<ComponentPropsWithRef<\"li\">, \"children\"> {\n step: PlanStep;\n /** Set on sub-steps. Not part of the public API surface. */\n nested?: boolean;\n}\n\nexport function AgentPlanStep({\n className,\n step,\n nested = false,\n ...props\n}: AgentPlanStepProps) {\n const { status, title, detail, error, steps: children } = step;\n\n return (\n <li\n data-slot=\"agent-plan-step\"\n data-status={status}\n // Marks where the reader is in the sequence without moving focus, which\n // would fight anyone reading ahead.\n aria-current={status === \"running\" ? \"step\" : undefined}\n className={cn(\"flex flex-col gap-1\", nested && \"ml-5\", className)}\n {...props}\n >\n <div className=\"flex items-start gap-2\">\n <StepMarker status={status} />\n\n <div className=\"flex min-w-0 flex-1 flex-col\">\n <span\n className={cn(\n \"text-sm\",\n status === \"pending\" && \"text-muted-foreground\",\n status === \"skipped\" && \"text-muted-foreground line-through\",\n status === \"failed\" && \"text-destructive\",\n status === \"running\" && \"font-medium\",\n )}\n >\n {title}\n {/* Status in text, always. The marker is a shape and a colour, and\n neither is information on its own. */}\n <span className=\"sr-only\">, {STATUS_LABEL[status]}</span>\n </span>\n\n {detail ? <span className=\"text-xs text-muted-foreground\">{detail}</span> : null}\n {status === \"failed\" && error ? (\n <span data-slot=\"agent-plan-error\" className=\"text-xs text-destructive\">\n {error}\n </span>\n ) : null}\n </div>\n </div>\n\n {children && children.length > 0 ? (\n <ol className=\"flex list-none flex-col gap-1\">\n {children.map((child) => (\n <AgentPlanStep key={child.id} step={child} nested />\n ))}\n </ol>\n ) : null}\n </li>\n );\n}\n\n/**\n * The status shape.\n *\n * aria-hidden throughout: every marker repeats what the step already states in\n * text, and announcing both means hearing the status twice per step.\n */\nfunction StepMarker({ status }: { status: StepStatus }) {\n const base = \"mt-0.5 grid size-4 shrink-0 place-items-center rounded-full border text-[9px]\";\n\n if (status === \"running\") {\n return (\n // Deliberately not data-motion=\"indicator\". The bar for that exemption is\n // that stopping the animation would say something false, and it would\n // not: the step already states \"In progress\" in text and carries\n // aria-current. This pulse is reinforcement, so under reduced motion it\n // stops like any other decoration.\n <span\n aria-hidden=\"true\"\n data-slot=\"agent-plan-marker\"\n className={cn(base, \"animate-pulse-soft border-primary bg-primary/15 text-primary\")}\n >\n <span className=\"size-1.5 rounded-full bg-primary\" />\n </span>\n );\n }\n\n return (\n <span\n aria-hidden=\"true\"\n data-slot=\"agent-plan-marker\"\n className={cn(\n base,\n status === \"done\" && \"border-success bg-success text-success-foreground\",\n status === \"failed\" && \"border-destructive bg-destructive text-destructive-foreground\",\n status === \"skipped\" && \"border-border-strong text-muted-foreground\",\n status === \"pending\" && \"border-border-strong\",\n )}\n >\n {status === \"done\" ? \"✓\" : status === \"failed\" ? \"✕\" : status === \"skipped\" ? \"–\" : null}\n </span>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { AgentPlan, AgentPlanProps, AgentPlanStep, AgentPlanStepProps, AgentPlanSteps, AgentPlanSummary, PlanStep, StepStatus } from "./ai-agent-plan.js";
|
|
2
|
+
export { AgentPlan, type AgentPlanProps, AgentPlanStep, type AgentPlanStepProps, AgentPlanSteps, AgentPlanSummary, type PlanStep, type StepStatus };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { defineMeta } from "../../registry/schema.js";
|
|
2
|
+
//#region src/components/ai-agent-plan/meta.ts
|
|
3
|
+
const meta = defineMeta({
|
|
4
|
+
name: "ai-agent-plan",
|
|
5
|
+
title: "AI Agent Plan",
|
|
6
|
+
description: "What an agent intends to do — including when it changes its mind mid-run.",
|
|
7
|
+
category: "ai",
|
|
8
|
+
status: "stable",
|
|
9
|
+
dependencies: [],
|
|
10
|
+
registryDependencies: [],
|
|
11
|
+
files: ["ai-agent-plan.tsx"],
|
|
12
|
+
a11y: "An ordered list, because a plan is a sequence and the order carries meaning an unordered list would discard. The running step carries aria-current=\"step\", which marks the reader's place without moving focus and fighting anyone reading ahead. Every status is stated in text as well as drawn, and the markers are aria-hidden so the status is not heard twice per step. The live region reports structural revisions only — announcing every status transition would talk over the reader continuously on a plan of any length."
|
|
13
|
+
});
|
|
14
|
+
//#endregion
|
|
15
|
+
export { meta };
|
|
16
|
+
|
|
17
|
+
//# sourceMappingURL=meta.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"meta.js","names":[],"sources":["../../../src/components/ai-agent-plan/meta.ts"],"sourcesContent":["import { defineMeta } from \"@/registry/schema\";\n\nexport const meta = defineMeta({\n name: \"ai-agent-plan\",\n title: \"AI Agent Plan\",\n description: \"What an agent intends to do — including when it changes its mind mid-run.\",\n category: \"ai\",\n status: \"stable\",\n dependencies: [],\n registryDependencies: [],\n files: [\"ai-agent-plan.tsx\"],\n a11y:\n \"An ordered list, because a plan is a sequence and the order carries meaning an unordered \" +\n 'list would discard. The running step carries aria-current=\"step\", which marks the reader\\'s ' +\n \"place without moving focus and fighting anyone reading ahead. Every status is stated in text \" +\n \"as well as drawn, and the markers are aria-hidden so the status is not heard twice per step. \" +\n \"The live region reports structural revisions only — announcing every status transition would \" +\n \"talk over the reader continuously on a plan of any length.\",\n});\n"],"mappings":";;AAEA,MAAa,OAAO,WAAW;CAC7B,MAAM;CACN,OAAO;CACP,aAAa;CACb,UAAU;CACV,QAAQ;CACR,cAAc,CAAC;CACf,sBAAsB,CAAC;CACvB,OAAO,CAAC,mBAAmB;CAC3B,MACE;AAMJ,CAAC"}
|
|
@@ -10,7 +10,7 @@ import { ComponentPropsWithRef } from "react";
|
|
|
10
10
|
*/
|
|
11
11
|
type AgentState = "idle" | "thinking" | "working" | "waiting" | "done" | "error";
|
|
12
12
|
declare const agentStatusVariants: (props?: ({
|
|
13
|
-
state?: "done" | "
|
|
13
|
+
state?: "done" | "error" | "idle" | "thinking" | "working" | "waiting" | null | undefined;
|
|
14
14
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
15
15
|
interface AgentStatusProps extends ComponentPropsWithRef<"span">, VariantProps<typeof agentStatusVariants> {
|
|
16
16
|
state?: AgentState;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { ComponentPropsWithRef, ReactNode } from "react";
|
|
2
|
+
//#region src/components/ai-approval-request/ai-approval-request.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* The gate between an agent deciding to act and it acting.
|
|
5
|
+
*
|
|
6
|
+
* Two things separate this from the confirmations that already exist.
|
|
7
|
+
*
|
|
8
|
+
* The first is that the proposed arguments are editable. Every implementation
|
|
9
|
+
* surveyed returns a boolean over a read-only payload, which forces a false
|
|
10
|
+
* choice: approve a call whose recipient is wrong, or deny it and make the
|
|
11
|
+
* model try again. "Approve this, but fix the address first" is the answer
|
|
12
|
+
* people actually want, and it is only possible if the arguments are fields.
|
|
13
|
+
* Edited values come back in the decision, and are marked as changed so an
|
|
14
|
+
* audit trail can tell the model's proposal from the human's correction.
|
|
15
|
+
*
|
|
16
|
+
* The second is that it renders while the arguments are still arriving. The
|
|
17
|
+
* common implementation returns null until the tool input is complete, which
|
|
18
|
+
* means nothing is on screen at the moment approval becomes relevant and the
|
|
19
|
+
* request appears abruptly, fully formed. Here the request is visible as it
|
|
20
|
+
* forms, with the decision held until there is something whole to decide on.
|
|
21
|
+
*/
|
|
22
|
+
type ApprovalScope = "once" | "always";
|
|
23
|
+
type ApprovalDecision = {
|
|
24
|
+
approved: true;
|
|
25
|
+
scope: ApprovalScope;
|
|
26
|
+
arguments: Record<string, string>;
|
|
27
|
+
edited: string[];
|
|
28
|
+
} | {
|
|
29
|
+
approved: false;
|
|
30
|
+
reason?: string;
|
|
31
|
+
};
|
|
32
|
+
interface ApprovalField {
|
|
33
|
+
name: string;
|
|
34
|
+
label: string;
|
|
35
|
+
/** A textarea instead of a single line. For message bodies and prompts. */
|
|
36
|
+
multiline?: boolean;
|
|
37
|
+
/** Locked arguments are shown but not editable — an id, a resolved amount. */
|
|
38
|
+
readOnly?: boolean;
|
|
39
|
+
}
|
|
40
|
+
interface ApprovalRequestProps extends Omit<ComponentPropsWithRef<"section">, "onSubmit" | "children"> {
|
|
41
|
+
/** The tool the model wants to call, as it named it. */
|
|
42
|
+
tool: string;
|
|
43
|
+
/** What the call will do, in the reader's language rather than the API's. */
|
|
44
|
+
summary: string;
|
|
45
|
+
/** Proposed arguments. Missing keys are treated as still arriving. */
|
|
46
|
+
arguments: Record<string, string>;
|
|
47
|
+
/** Which arguments to show, in order, and which may be changed. */
|
|
48
|
+
fields: ApprovalField[];
|
|
49
|
+
onDecision: (decision: ApprovalDecision) => void;
|
|
50
|
+
/**
|
|
51
|
+
* True while the model is still producing the arguments. The request renders
|
|
52
|
+
* anyway; only the decision waits.
|
|
53
|
+
*/
|
|
54
|
+
streaming?: boolean;
|
|
55
|
+
/**
|
|
56
|
+
* Said plainly above the controls when the call cannot be taken back. Not a
|
|
57
|
+
* severity colour — the sentence is the warning.
|
|
58
|
+
*/
|
|
59
|
+
irreversible?: string;
|
|
60
|
+
/** Set once decided, to render the outcome instead of the controls. */
|
|
61
|
+
decision?: ApprovalDecision;
|
|
62
|
+
children?: ReactNode;
|
|
63
|
+
}
|
|
64
|
+
declare function ApprovalRequest({ className, tool, summary, arguments: proposed, fields, onDecision, streaming, irreversible, decision, children, ...props }: ApprovalRequestProps): import("react").JSX.Element;
|
|
65
|
+
//#endregion
|
|
66
|
+
export { ApprovalDecision, ApprovalField, ApprovalRequest, ApprovalRequestProps, ApprovalScope };
|
|
67
|
+
//# sourceMappingURL=ai-approval-request.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai-approval-request.d.ts","names":[],"sources":["../../../src/components/ai-approval-request/ai-approval-request.tsx"],"mappings":";;;;;;;;;;;;;;;;;;;;;KA2BY;KAEA;EAEN;EACA,OAAO;EACP,WAAW;EACX;;EAEA;EAAiB;;UAEN;EACf;EACA;;EAEA;;EAEA;;UAGe,6BAA6B,KAC5C;;EAIA;;EAEA;;EAEA,WAAW;;EAEX,QAAQ;EACR,aAAa,UAAU;;;;;EAKvB;;;;;EAKA;;EAEA,WAAW;EACX,WAAW;;iBAGG,kBACd,WACA,MACA,SACA,WAAW,UACX,QACA,YACA,WACA,cACA,UACA,aACG,SACF,uCAAoB,IAAA"}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { disabledStyles, focusRing } from "../../lib/styles.js";
|
|
3
|
+
import { cn } from "../../lib/utils.js";
|
|
4
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
+
import { useId, useMemo, useState } from "react";
|
|
6
|
+
//#region src/components/ai-approval-request/ai-approval-request.tsx
|
|
7
|
+
function ApprovalRequest({ className, tool, summary, arguments: proposed, fields, onDecision, streaming = false, irreversible, decision, children, ...props }) {
|
|
8
|
+
const headingId = useId();
|
|
9
|
+
const [edits, setEdits] = useState({});
|
|
10
|
+
const [denying, setDenying] = useState(false);
|
|
11
|
+
const [reason, setReason] = useState("");
|
|
12
|
+
const values = useMemo(() => ({
|
|
13
|
+
...proposed,
|
|
14
|
+
...edits
|
|
15
|
+
}), [proposed, edits]);
|
|
16
|
+
const edited = useMemo(() => Object.keys(edits).filter((name) => edits[name] !== proposed[name]), [edits, proposed]);
|
|
17
|
+
const missing = fields.filter((field) => proposed[field.name] === void 0);
|
|
18
|
+
const settled = !streaming && missing.length === 0;
|
|
19
|
+
if (decision) return /* @__PURE__ */ jsxs("section", {
|
|
20
|
+
"data-slot": "approval-request",
|
|
21
|
+
"data-state": decision.approved ? "approved" : "denied",
|
|
22
|
+
"aria-labelledby": headingId,
|
|
23
|
+
className: cn("rounded-lg border p-4 text-sm", decision.approved ? "border-border bg-muted/40" : "border-border bg-muted/40", className),
|
|
24
|
+
...props,
|
|
25
|
+
children: [/* @__PURE__ */ jsx("h3", {
|
|
26
|
+
id: headingId,
|
|
27
|
+
className: "text-sm font-medium",
|
|
28
|
+
children: summary
|
|
29
|
+
}), /* @__PURE__ */ jsx("p", {
|
|
30
|
+
"data-slot": "approval-outcome",
|
|
31
|
+
className: "mt-1 text-xs text-muted-foreground",
|
|
32
|
+
children: decision.approved ? `Approved ${decision.scope === "always" ? "for every call to this tool" : "once"}` + (decision.edited.length > 0 ? `, with ${String(decision.edited.length)} argument${decision.edited.length === 1 ? "" : "s"} corrected` : "") : `Denied${decision.reason ? `: ${decision.reason}` : ""}`
|
|
33
|
+
})]
|
|
34
|
+
});
|
|
35
|
+
return /* @__PURE__ */ jsxs("section", {
|
|
36
|
+
"data-slot": "approval-request",
|
|
37
|
+
"data-state": settled ? "pending" : "forming",
|
|
38
|
+
"aria-labelledby": headingId,
|
|
39
|
+
"aria-busy": !settled,
|
|
40
|
+
className: cn("rounded-lg border border-border bg-card p-4", className),
|
|
41
|
+
...props,
|
|
42
|
+
children: [
|
|
43
|
+
/* @__PURE__ */ jsxs("div", {
|
|
44
|
+
className: "flex flex-wrap items-baseline justify-between gap-2",
|
|
45
|
+
children: [/* @__PURE__ */ jsx("h3", {
|
|
46
|
+
id: headingId,
|
|
47
|
+
className: "text-sm font-medium",
|
|
48
|
+
children: summary
|
|
49
|
+
}), /* @__PURE__ */ jsx("code", {
|
|
50
|
+
className: "rounded bg-muted px-1.5 py-0.5 font-mono text-2xs text-muted-foreground",
|
|
51
|
+
children: tool
|
|
52
|
+
})]
|
|
53
|
+
}),
|
|
54
|
+
/* @__PURE__ */ jsx("dl", {
|
|
55
|
+
className: "mt-3 flex flex-col gap-2.5",
|
|
56
|
+
children: fields.map((field) => {
|
|
57
|
+
const arrived = proposed[field.name] !== void 0;
|
|
58
|
+
const changed = edited.includes(field.name);
|
|
59
|
+
return /* @__PURE__ */ jsx(ApprovalArgument, {
|
|
60
|
+
field,
|
|
61
|
+
value: values[field.name] ?? "",
|
|
62
|
+
arrived,
|
|
63
|
+
changed,
|
|
64
|
+
onChange: (next) => {
|
|
65
|
+
setEdits((current) => ({
|
|
66
|
+
...current,
|
|
67
|
+
[field.name]: next
|
|
68
|
+
}));
|
|
69
|
+
}
|
|
70
|
+
}, field.name);
|
|
71
|
+
})
|
|
72
|
+
}),
|
|
73
|
+
children,
|
|
74
|
+
irreversible ? /* @__PURE__ */ jsx("p", {
|
|
75
|
+
"data-slot": "approval-warning",
|
|
76
|
+
className: "mt-3 text-xs text-destructive",
|
|
77
|
+
children: irreversible
|
|
78
|
+
}) : null,
|
|
79
|
+
denying ? /* @__PURE__ */ jsxs("div", {
|
|
80
|
+
className: "mt-3 flex flex-col gap-2",
|
|
81
|
+
children: [
|
|
82
|
+
/* @__PURE__ */ jsx("label", {
|
|
83
|
+
htmlFor: `${headingId}-reason`,
|
|
84
|
+
className: "text-xs text-muted-foreground",
|
|
85
|
+
children: "Why not? The model sees this."
|
|
86
|
+
}),
|
|
87
|
+
/* @__PURE__ */ jsx("textarea", {
|
|
88
|
+
id: `${headingId}-reason`,
|
|
89
|
+
rows: 2,
|
|
90
|
+
value: reason,
|
|
91
|
+
onChange: (event) => {
|
|
92
|
+
setReason(event.target.value);
|
|
93
|
+
},
|
|
94
|
+
className: cn("w-full resize-none rounded-md border border-input bg-background px-2 py-1.5 text-sm", focusRing)
|
|
95
|
+
}),
|
|
96
|
+
/* @__PURE__ */ jsxs("div", {
|
|
97
|
+
className: "flex flex-wrap gap-2",
|
|
98
|
+
children: [/* @__PURE__ */ jsx(Action, {
|
|
99
|
+
variant: "destructive",
|
|
100
|
+
onClick: () => {
|
|
101
|
+
onDecision({
|
|
102
|
+
approved: false,
|
|
103
|
+
reason: reason.trim() || void 0
|
|
104
|
+
});
|
|
105
|
+
},
|
|
106
|
+
children: "Deny"
|
|
107
|
+
}), /* @__PURE__ */ jsx(Action, {
|
|
108
|
+
onClick: () => {
|
|
109
|
+
setDenying(false);
|
|
110
|
+
},
|
|
111
|
+
children: "Back"
|
|
112
|
+
})]
|
|
113
|
+
})
|
|
114
|
+
]
|
|
115
|
+
}) : /* @__PURE__ */ jsxs("div", {
|
|
116
|
+
className: "mt-4 flex flex-wrap items-center gap-2",
|
|
117
|
+
children: [
|
|
118
|
+
/* @__PURE__ */ jsx(Action, {
|
|
119
|
+
variant: "primary",
|
|
120
|
+
disabled: !settled,
|
|
121
|
+
onClick: () => {
|
|
122
|
+
onDecision({
|
|
123
|
+
approved: true,
|
|
124
|
+
scope: "once",
|
|
125
|
+
arguments: values,
|
|
126
|
+
edited
|
|
127
|
+
});
|
|
128
|
+
},
|
|
129
|
+
children: edited.length > 0 ? "Approve with changes" : "Approve once"
|
|
130
|
+
}),
|
|
131
|
+
/* @__PURE__ */ jsxs(Action, {
|
|
132
|
+
disabled: !settled,
|
|
133
|
+
onClick: () => {
|
|
134
|
+
onDecision({
|
|
135
|
+
approved: true,
|
|
136
|
+
scope: "always",
|
|
137
|
+
arguments: values,
|
|
138
|
+
edited
|
|
139
|
+
});
|
|
140
|
+
},
|
|
141
|
+
children: ["Always allow ", tool]
|
|
142
|
+
}),
|
|
143
|
+
/* @__PURE__ */ jsx(Action, {
|
|
144
|
+
onClick: () => {
|
|
145
|
+
setDenying(true);
|
|
146
|
+
},
|
|
147
|
+
children: "Deny…"
|
|
148
|
+
}),
|
|
149
|
+
!settled ? /* @__PURE__ */ jsx("span", {
|
|
150
|
+
"aria-live": "polite",
|
|
151
|
+
className: "text-xs text-muted-foreground",
|
|
152
|
+
children: "Waiting for the model to finish the request"
|
|
153
|
+
}) : null
|
|
154
|
+
]
|
|
155
|
+
})
|
|
156
|
+
]
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
function ApprovalArgument({ field, value, arrived, changed, onChange }) {
|
|
160
|
+
const id = useId();
|
|
161
|
+
const changedId = useId();
|
|
162
|
+
const editable = !field.readOnly && arrived;
|
|
163
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
164
|
+
"data-slot": "approval-argument",
|
|
165
|
+
"data-changed": changed || void 0,
|
|
166
|
+
className: "flex flex-col gap-1",
|
|
167
|
+
children: [/* @__PURE__ */ jsxs("dt", {
|
|
168
|
+
className: "flex items-baseline gap-1",
|
|
169
|
+
children: [/* @__PURE__ */ jsx("label", {
|
|
170
|
+
htmlFor: editable ? id : void 0,
|
|
171
|
+
className: "text-xs text-muted-foreground",
|
|
172
|
+
children: field.label
|
|
173
|
+
}), changed ? /* @__PURE__ */ jsx("span", {
|
|
174
|
+
id: changedId,
|
|
175
|
+
className: "text-xs text-warning",
|
|
176
|
+
children: "· changed from the model’s proposal"
|
|
177
|
+
}) : null]
|
|
178
|
+
}), /* @__PURE__ */ jsx("dd", {
|
|
179
|
+
className: "m-0",
|
|
180
|
+
children: !arrived ? /* @__PURE__ */ jsx("span", {
|
|
181
|
+
"aria-hidden": "true",
|
|
182
|
+
className: "block h-8 w-full animate-pulse-soft rounded-md bg-muted"
|
|
183
|
+
}) : editable ? field.multiline ? /* @__PURE__ */ jsx("textarea", {
|
|
184
|
+
id,
|
|
185
|
+
"aria-describedby": changed ? changedId : void 0,
|
|
186
|
+
rows: 3,
|
|
187
|
+
value,
|
|
188
|
+
onChange: (event) => {
|
|
189
|
+
onChange(event.target.value);
|
|
190
|
+
},
|
|
191
|
+
className: cn("w-full resize-none rounded-md border border-input bg-background px-2 py-1.5 font-mono text-xs", changed && "border-warning", focusRing)
|
|
192
|
+
}) : /* @__PURE__ */ jsx("input", {
|
|
193
|
+
id,
|
|
194
|
+
type: "text",
|
|
195
|
+
"aria-describedby": changed ? changedId : void 0,
|
|
196
|
+
value,
|
|
197
|
+
onChange: (event) => {
|
|
198
|
+
onChange(event.target.value);
|
|
199
|
+
},
|
|
200
|
+
className: cn("w-full rounded-md border border-input bg-background px-2 py-1.5 font-mono text-xs", changed && "border-warning", focusRing)
|
|
201
|
+
}) : /* @__PURE__ */ jsx("p", {
|
|
202
|
+
className: "rounded-md bg-muted px-2 py-1.5 font-mono text-xs break-words",
|
|
203
|
+
children: value
|
|
204
|
+
})
|
|
205
|
+
})]
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
function Action({ className, variant = "default", ...props }) {
|
|
209
|
+
return /* @__PURE__ */ jsx("button", {
|
|
210
|
+
type: "button",
|
|
211
|
+
className: cn("rounded-md border px-2.5 py-1 text-xs font-medium transition-colors", variant === "primary" && "border-primary bg-primary text-primary-foreground hover:bg-primary-hover", variant === "destructive" && "border-destructive bg-destructive text-destructive-foreground hover:bg-destructive/90", variant === "default" && "border-input bg-background hover:bg-accent hover:text-accent-foreground", focusRing, disabledStyles, className),
|
|
212
|
+
...props
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
//#endregion
|
|
216
|
+
export { ApprovalRequest };
|
|
217
|
+
|
|
218
|
+
//# sourceMappingURL=ai-approval-request.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai-approval-request.js","names":[],"sources":["../../../src/components/ai-approval-request/ai-approval-request.tsx"],"sourcesContent":["\"use client\";\n\nimport { useId, useMemo, useState, type ComponentPropsWithRef, type ReactNode } from \"react\";\n\nimport { disabledStyles, focusRing } from \"@/lib/styles\";\nimport { cn } from \"@/lib/utils\";\n\n/**\n * The gate between an agent deciding to act and it acting.\n *\n * Two things separate this from the confirmations that already exist.\n *\n * The first is that the proposed arguments are editable. Every implementation\n * surveyed returns a boolean over a read-only payload, which forces a false\n * choice: approve a call whose recipient is wrong, or deny it and make the\n * model try again. \"Approve this, but fix the address first\" is the answer\n * people actually want, and it is only possible if the arguments are fields.\n * Edited values come back in the decision, and are marked as changed so an\n * audit trail can tell the model's proposal from the human's correction.\n *\n * The second is that it renders while the arguments are still arriving. The\n * common implementation returns null until the tool input is complete, which\n * means nothing is on screen at the moment approval becomes relevant and the\n * request appears abruptly, fully formed. Here the request is visible as it\n * forms, with the decision held until there is something whole to decide on.\n */\n\nexport type ApprovalScope = \"once\" | \"always\";\n\nexport type ApprovalDecision =\n | {\n approved: true;\n scope: ApprovalScope;\n arguments: Record<string, string>;\n edited: string[];\n }\n | { approved: false; reason?: string };\n\nexport interface ApprovalField {\n name: string;\n label: string;\n /** A textarea instead of a single line. For message bodies and prompts. */\n multiline?: boolean;\n /** Locked arguments are shown but not editable — an id, a resolved amount. */\n readOnly?: boolean;\n}\n\nexport interface ApprovalRequestProps extends Omit<\n ComponentPropsWithRef<\"section\">,\n \"onSubmit\" | \"children\"\n> {\n /** The tool the model wants to call, as it named it. */\n tool: string;\n /** What the call will do, in the reader's language rather than the API's. */\n summary: string;\n /** Proposed arguments. Missing keys are treated as still arriving. */\n arguments: Record<string, string>;\n /** Which arguments to show, in order, and which may be changed. */\n fields: ApprovalField[];\n onDecision: (decision: ApprovalDecision) => void;\n /**\n * True while the model is still producing the arguments. The request renders\n * anyway; only the decision waits.\n */\n streaming?: boolean;\n /**\n * Said plainly above the controls when the call cannot be taken back. Not a\n * severity colour — the sentence is the warning.\n */\n irreversible?: string;\n /** Set once decided, to render the outcome instead of the controls. */\n decision?: ApprovalDecision;\n children?: ReactNode;\n}\n\nexport function ApprovalRequest({\n className,\n tool,\n summary,\n arguments: proposed,\n fields,\n onDecision,\n streaming = false,\n irreversible,\n decision,\n children,\n ...props\n}: ApprovalRequestProps) {\n const headingId = useId();\n const [edits, setEdits] = useState<Record<string, string>>({});\n const [denying, setDenying] = useState(false);\n const [reason, setReason] = useState(\"\");\n\n // The model's proposal with the human's corrections applied on top. Keeping\n // them apart is what lets the component say which is which.\n const values = useMemo(() => ({ ...proposed, ...edits }), [proposed, edits]);\n\n const edited = useMemo(\n () => Object.keys(edits).filter((name) => edits[name] !== proposed[name]),\n [edits, proposed],\n );\n\n const missing = fields.filter((field) => proposed[field.name] === undefined);\n const settled = !streaming && missing.length === 0;\n\n if (decision) {\n return (\n <section\n data-slot=\"approval-request\"\n data-state={decision.approved ? \"approved\" : \"denied\"}\n aria-labelledby={headingId}\n className={cn(\n \"rounded-lg border p-4 text-sm\",\n decision.approved ? \"border-border bg-muted/40\" : \"border-border bg-muted/40\",\n className,\n )}\n {...props}\n >\n <h3 id={headingId} className=\"text-sm font-medium\">\n {summary}\n </h3>\n <p data-slot=\"approval-outcome\" className=\"mt-1 text-xs text-muted-foreground\">\n {decision.approved\n ? `Approved ${decision.scope === \"always\" ? \"for every call to this tool\" : \"once\"}` +\n (decision.edited.length > 0\n ? `, with ${String(decision.edited.length)} argument${decision.edited.length === 1 ? \"\" : \"s\"} corrected`\n : \"\")\n : `Denied${decision.reason ? `: ${decision.reason}` : \"\"}`}\n </p>\n </section>\n );\n }\n\n return (\n <section\n data-slot=\"approval-request\"\n data-state={settled ? \"pending\" : \"forming\"}\n aria-labelledby={headingId}\n // Busy while the arguments are still arriving, so a reader is told the\n // request is not yet whole rather than acting on half of it.\n aria-busy={!settled}\n className={cn(\"rounded-lg border border-border bg-card p-4\", className)}\n {...props}\n >\n <div className=\"flex flex-wrap items-baseline justify-between gap-2\">\n <h3 id={headingId} className=\"text-sm font-medium\">\n {summary}\n </h3>\n <code className=\"rounded bg-muted px-1.5 py-0.5 font-mono text-2xs text-muted-foreground\">\n {tool}\n </code>\n </div>\n\n <dl className=\"mt-3 flex flex-col gap-2.5\">\n {fields.map((field) => {\n const arrived = proposed[field.name] !== undefined;\n const changed = edited.includes(field.name);\n\n return (\n <ApprovalArgument\n key={field.name}\n field={field}\n value={values[field.name] ?? \"\"}\n arrived={arrived}\n changed={changed}\n onChange={(next) => {\n setEdits((current) => ({ ...current, [field.name]: next }));\n }}\n />\n );\n })}\n </dl>\n\n {children}\n\n {irreversible ? (\n <p data-slot=\"approval-warning\" className=\"mt-3 text-xs text-destructive\">\n {irreversible}\n </p>\n ) : null}\n\n {denying ? (\n <div className=\"mt-3 flex flex-col gap-2\">\n <label htmlFor={`${headingId}-reason`} className=\"text-xs text-muted-foreground\">\n Why not? The model sees this.\n </label>\n <textarea\n id={`${headingId}-reason`}\n rows={2}\n value={reason}\n onChange={(event) => {\n setReason(event.target.value);\n }}\n className={cn(\n \"w-full resize-none rounded-md border border-input bg-background px-2 py-1.5 text-sm\",\n focusRing,\n )}\n />\n <div className=\"flex flex-wrap gap-2\">\n <Action\n variant=\"destructive\"\n onClick={() => {\n onDecision({ approved: false, reason: reason.trim() || undefined });\n }}\n >\n Deny\n </Action>\n <Action\n onClick={() => {\n setDenying(false);\n }}\n >\n Back\n </Action>\n </div>\n </div>\n ) : (\n <div className=\"mt-4 flex flex-wrap items-center gap-2\">\n <Action\n variant=\"primary\"\n disabled={!settled}\n onClick={() => {\n onDecision({ approved: true, scope: \"once\", arguments: values, edited });\n }}\n >\n {edited.length > 0 ? \"Approve with changes\" : \"Approve once\"}\n </Action>\n\n {/* Scope is a separate decision from approval, and blanket consent\n should never be the easiest button to reach. */}\n <Action\n disabled={!settled}\n onClick={() => {\n onDecision({ approved: true, scope: \"always\", arguments: values, edited });\n }}\n >\n Always allow {tool}\n </Action>\n\n <Action\n onClick={() => {\n setDenying(true);\n }}\n >\n Deny…\n </Action>\n\n {!settled ? (\n <span aria-live=\"polite\" className=\"text-xs text-muted-foreground\">\n Waiting for the model to finish the request\n </span>\n ) : null}\n </div>\n )}\n </section>\n );\n}\n\nfunction ApprovalArgument({\n field,\n value,\n arrived,\n changed,\n onChange,\n}: {\n field: ApprovalField;\n value: string;\n arrived: boolean;\n changed: boolean;\n onChange: (value: string) => void;\n}) {\n const id = useId();\n const changedId = useId();\n const editable = !field.readOnly && arrived;\n\n return (\n <div\n data-slot=\"approval-argument\"\n data-changed={changed || undefined}\n className=\"flex flex-col gap-1\"\n >\n <dt className=\"flex items-baseline gap-1\">\n <label htmlFor={editable ? id : undefined} className=\"text-xs text-muted-foreground\">\n {field.label}\n </label>\n {/* Outside the label, and attached as a description instead. Inside it,\n editing a field would rename the field — a screen reader user would\n hear its accessible name change under them mid-edit. Marked in text\n rather than by border colour alone, because an audit trail has to\n tell the model's proposal from the human's correction. */}\n {changed ? (\n <span id={changedId} className=\"text-xs text-warning\">\n · changed from the model’s proposal\n </span>\n ) : null}\n </dt>\n <dd className=\"m-0\">\n {!arrived ? (\n <span\n aria-hidden=\"true\"\n className=\"block h-8 w-full animate-pulse-soft rounded-md bg-muted\"\n />\n ) : editable ? (\n field.multiline ? (\n <textarea\n id={id}\n aria-describedby={changed ? changedId : undefined}\n rows={3}\n value={value}\n onChange={(event) => {\n onChange(event.target.value);\n }}\n className={cn(\n \"w-full resize-none rounded-md border border-input bg-background px-2 py-1.5 font-mono text-xs\",\n changed && \"border-warning\",\n focusRing,\n )}\n />\n ) : (\n <input\n id={id}\n type=\"text\"\n aria-describedby={changed ? changedId : undefined}\n value={value}\n onChange={(event) => {\n onChange(event.target.value);\n }}\n className={cn(\n \"w-full rounded-md border border-input bg-background px-2 py-1.5 font-mono text-xs\",\n changed && \"border-warning\",\n focusRing,\n )}\n />\n )\n ) : (\n <p className=\"rounded-md bg-muted px-2 py-1.5 font-mono text-xs break-words\">\n {value}\n </p>\n )}\n </dd>\n </div>\n );\n}\n\nfunction Action({\n className,\n variant = \"default\",\n ...props\n}: ComponentPropsWithRef<\"button\"> & { variant?: \"default\" | \"primary\" | \"destructive\" }) {\n return (\n <button\n type=\"button\"\n className={cn(\n \"rounded-md border px-2.5 py-1 text-xs font-medium transition-colors\",\n variant === \"primary\" &&\n \"border-primary bg-primary text-primary-foreground hover:bg-primary-hover\",\n variant === \"destructive\" &&\n \"border-destructive bg-destructive text-destructive-foreground hover:bg-destructive/90\",\n variant === \"default\" &&\n \"border-input bg-background hover:bg-accent hover:text-accent-foreground\",\n focusRing,\n disabledStyles,\n className,\n )}\n {...props}\n />\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { defineMeta } from "../../registry/schema.js";
|
|
2
|
+
//#region src/components/ai-approval-request/meta.ts
|
|
3
|
+
const meta = defineMeta({
|
|
4
|
+
name: "ai-approval-request",
|
|
5
|
+
title: "AI Approval Request",
|
|
6
|
+
description: "Approve a tool call — after correcting the arguments the model got wrong.",
|
|
7
|
+
category: "ai",
|
|
8
|
+
status: "stable",
|
|
9
|
+
dependencies: [],
|
|
10
|
+
registryDependencies: [],
|
|
11
|
+
files: ["ai-approval-request.tsx"],
|
|
12
|
+
a11y: "The proposed arguments are a description list of real labelled form controls, so each value is associated with its name and editable ones are reachable by keyboard. A corrected argument is marked in text rather than by border colour alone, because an audit trail has to distinguish the model's proposal from the human's edit. The request is aria-busy while arguments are still arriving and the decision controls stay disabled until it is whole — rendering nothing until then, as the common implementation does, means nothing is on screen at the moment approval becomes relevant. Irreversibility is stated as a sentence, never as a severity colour."
|
|
13
|
+
});
|
|
14
|
+
//#endregion
|
|
15
|
+
export { meta };
|
|
16
|
+
|
|
17
|
+
//# sourceMappingURL=meta.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"meta.js","names":[],"sources":["../../../src/components/ai-approval-request/meta.ts"],"sourcesContent":["import { defineMeta } from \"@/registry/schema\";\n\nexport const meta = defineMeta({\n name: \"ai-approval-request\",\n title: \"AI Approval Request\",\n description: \"Approve a tool call — after correcting the arguments the model got wrong.\",\n category: \"ai\",\n status: \"stable\",\n dependencies: [],\n registryDependencies: [],\n files: [\"ai-approval-request.tsx\"],\n a11y:\n \"The proposed arguments are a description list of real labelled form controls, so each value \" +\n \"is associated with its name and editable ones are reachable by keyboard. A corrected \" +\n \"argument is marked in text rather than by border colour alone, because an audit trail has \" +\n \"to distinguish the model's proposal from the human's edit. The request is aria-busy while \" +\n \"arguments are still arriving and the decision controls stay disabled until it is whole — \" +\n \"rendering nothing until then, as the common implementation does, means nothing is on screen \" +\n \"at the moment approval becomes relevant. Irreversibility is stated as a sentence, never as a \" +\n \"severity colour.\",\n});\n"],"mappings":";;AAEA,MAAa,OAAO,WAAW;CAC7B,MAAM;CACN,OAAO;CACP,aAAa;CACb,UAAU;CACV,QAAQ;CACR,cAAc,CAAC;CACf,sBAAsB,CAAC;CACvB,OAAO,CAAC,yBAAyB;CACjC,MACE;AAQJ,CAAC"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { ComponentPropsWithRef } from "react";
|
|
2
|
+
//#region src/components/ai-inline-completion/ai-inline-completion.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* Ghost-text suggestion inside a real textarea or input.
|
|
5
|
+
*
|
|
6
|
+
* Outside chat this is the most-copied AI affordance in software — Smart
|
|
7
|
+
* Compose, Copilot's grey continuation, Notion and Linear's inline suggestions —
|
|
8
|
+
* and every team rebuilds the same overlay-mirror trick from the same handful of
|
|
9
|
+
* blog posts.
|
|
10
|
+
*
|
|
11
|
+
* Two deliberate limits, both stated rather than discovered:
|
|
12
|
+
*
|
|
13
|
+
* 1. It works on `textarea` and `input`, not on contenteditable. Ghost text in
|
|
14
|
+
* contenteditable means owning selection, undo and inline formatting, which
|
|
15
|
+
* is an editor, not a component. Anyone in that territory already has
|
|
16
|
+
* ProseMirror or Lexical and should extend it there.
|
|
17
|
+
* 2. The suggestion completes the END of the value. Mid-caret insertion needs
|
|
18
|
+
* per-character measurement of wrapped text; the ghost hides whenever the
|
|
19
|
+
* caret is not at the end rather than rendering in the wrong place.
|
|
20
|
+
*
|
|
21
|
+
* There is no WAI-ARIA pattern for generative ghost text. Combobox is the
|
|
22
|
+
* nearest and does not fit: the suggestion is not one of a known set of options,
|
|
23
|
+
* so a listbox would be a lie. What the standard does require is that the
|
|
24
|
+
* suggestion be perceivable and that the keyboard is never trapped — so the text
|
|
25
|
+
* is announced through a live description, and Escape always restores plain Tab.
|
|
26
|
+
*/
|
|
27
|
+
interface InlineCompletionProps extends Omit<ComponentPropsWithRef<"textarea">, "value" | "onChange" | "children"> {
|
|
28
|
+
value: string;
|
|
29
|
+
onValueChange: (value: string) => void;
|
|
30
|
+
/**
|
|
31
|
+
* The continuation to show after the value. Empty or undefined shows nothing.
|
|
32
|
+
* The caller supplies this; the component never requests it.
|
|
33
|
+
*/
|
|
34
|
+
suggestion?: string;
|
|
35
|
+
/** Called with the full text after the suggestion is taken. */
|
|
36
|
+
onAccept?: (value: string) => void;
|
|
37
|
+
/** Called when the reader rejects the suggestion outright. */
|
|
38
|
+
onDismiss?: () => void;
|
|
39
|
+
/** Renders a single-line input instead of a textarea. */
|
|
40
|
+
singleLine?: boolean;
|
|
41
|
+
}
|
|
42
|
+
declare function InlineCompletion({ className, value, onValueChange, suggestion, onAccept, onDismiss, singleLine, onKeyDown, onScroll, ...props }: InlineCompletionProps): import("react").JSX.Element;
|
|
43
|
+
//#endregion
|
|
44
|
+
export { InlineCompletion, InlineCompletionProps };
|
|
45
|
+
//# sourceMappingURL=ai-inline-completion.d.ts.map
|