@dowel-ui/react 0.2.0 → 0.4.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.
Files changed (141) hide show
  1. package/README.md +14 -7
  2. package/dist/components/ai-agent-plan/ai-agent-plan.d.ts +50 -0
  3. package/dist/components/ai-agent-plan/ai-agent-plan.d.ts.map +1 -0
  4. package/dist/components/ai-agent-plan/ai-agent-plan.js +156 -0
  5. package/dist/components/ai-agent-plan/ai-agent-plan.js.map +1 -0
  6. package/dist/components/ai-agent-plan/index.d.ts +2 -0
  7. package/dist/components/ai-agent-plan/index.js +2 -0
  8. package/dist/components/ai-agent-plan/meta.js +17 -0
  9. package/dist/components/ai-agent-plan/meta.js.map +1 -0
  10. package/dist/components/ai-agent-status/ai-agent-status.d.ts +1 -1
  11. package/dist/components/ai-approval-request/ai-approval-request.d.ts +67 -0
  12. package/dist/components/ai-approval-request/ai-approval-request.d.ts.map +1 -0
  13. package/dist/components/ai-approval-request/ai-approval-request.js +218 -0
  14. package/dist/components/ai-approval-request/ai-approval-request.js.map +1 -0
  15. package/dist/components/ai-approval-request/index.d.ts +2 -0
  16. package/dist/components/ai-approval-request/index.js +2 -0
  17. package/dist/components/ai-approval-request/meta.js +17 -0
  18. package/dist/components/ai-approval-request/meta.js.map +1 -0
  19. package/dist/components/ai-disclosure/ai-disclosure.d.ts +92 -0
  20. package/dist/components/ai-disclosure/ai-disclosure.d.ts.map +1 -0
  21. package/dist/components/ai-disclosure/ai-disclosure.js +122 -0
  22. package/dist/components/ai-disclosure/ai-disclosure.js.map +1 -0
  23. package/dist/components/ai-disclosure/index.d.ts +2 -0
  24. package/dist/components/ai-disclosure/index.js +2 -0
  25. package/dist/components/ai-disclosure/meta.js +17 -0
  26. package/dist/components/ai-disclosure/meta.js.map +1 -0
  27. package/dist/components/ai-response/ai-response.d.ts.map +1 -1
  28. package/dist/components/ai-response/ai-response.js +1 -0
  29. package/dist/components/ai-response/ai-response.js.map +1 -1
  30. package/dist/components/ai-structured-output/ai-structured-output.d.ts.map +1 -1
  31. package/dist/components/ai-structured-output/ai-structured-output.js +13 -2
  32. package/dist/components/ai-structured-output/ai-structured-output.js.map +1 -1
  33. package/dist/components/ai-tool/ai-tool.d.ts +1 -1
  34. package/dist/components/alert/alert.d.ts +1 -1
  35. package/dist/components/avatar/avatar.d.ts +1 -1
  36. package/dist/components/badge/badge.d.ts +1 -1
  37. package/dist/components/button/button.d.ts +1 -1
  38. package/dist/components/date-picker/date-picker.js +1 -1
  39. package/dist/components/diff-viewer/diff-model.d.ts +69 -0
  40. package/dist/components/diff-viewer/diff-model.d.ts.map +1 -0
  41. package/dist/components/diff-viewer/diff-model.js +180 -0
  42. package/dist/components/diff-viewer/diff-model.js.map +1 -0
  43. package/dist/components/diff-viewer/diff-viewer.d.ts +42 -0
  44. package/dist/components/diff-viewer/diff-viewer.d.ts.map +1 -0
  45. package/dist/components/diff-viewer/diff-viewer.js +212 -0
  46. package/dist/components/diff-viewer/diff-viewer.js.map +1 -0
  47. package/dist/components/diff-viewer/index.d.ts +3 -0
  48. package/dist/components/diff-viewer/index.js +3 -0
  49. package/dist/components/diff-viewer/meta.js +17 -0
  50. package/dist/components/diff-viewer/meta.js.map +1 -0
  51. package/dist/components/file-upload/file-upload.d.ts +46 -0
  52. package/dist/components/file-upload/file-upload.d.ts.map +1 -0
  53. package/dist/components/file-upload/file-upload.js +204 -0
  54. package/dist/components/file-upload/file-upload.js.map +1 -0
  55. package/dist/components/file-upload/index.d.ts +3 -0
  56. package/dist/components/file-upload/index.js +3 -0
  57. package/dist/components/file-upload/meta.js +17 -0
  58. package/dist/components/file-upload/meta.js.map +1 -0
  59. package/dist/components/file-upload/upload-queue.d.ts +87 -0
  60. package/dist/components/file-upload/upload-queue.d.ts.map +1 -0
  61. package/dist/components/file-upload/upload-queue.js +249 -0
  62. package/dist/components/file-upload/upload-queue.js.map +1 -0
  63. package/dist/components/log-viewer/index.d.ts +3 -0
  64. package/dist/components/log-viewer/index.js +3 -0
  65. package/dist/components/log-viewer/log-stream.d.ts +68 -0
  66. package/dist/components/log-viewer/log-stream.d.ts.map +1 -0
  67. package/dist/components/log-viewer/log-stream.js +120 -0
  68. package/dist/components/log-viewer/log-stream.js.map +1 -0
  69. package/dist/components/log-viewer/log-viewer.d.ts +51 -0
  70. package/dist/components/log-viewer/log-viewer.d.ts.map +1 -0
  71. package/dist/components/log-viewer/log-viewer.js +284 -0
  72. package/dist/components/log-viewer/log-viewer.js.map +1 -0
  73. package/dist/components/log-viewer/meta.js +17 -0
  74. package/dist/components/log-viewer/meta.js.map +1 -0
  75. package/dist/components/progress/progress.d.ts +1 -1
  76. package/dist/components/progress/progress.js +1 -0
  77. package/dist/components/progress/progress.js.map +1 -1
  78. package/dist/components/spinner/spinner.d.ts +1 -1
  79. package/dist/components/spinner/spinner.js +1 -0
  80. package/dist/components/spinner/spinner.js.map +1 -1
  81. package/dist/components/tags-input/index.d.ts +2 -0
  82. package/dist/components/tags-input/index.js +2 -0
  83. package/dist/components/tags-input/meta.js +17 -0
  84. package/dist/components/tags-input/meta.js.map +1 -0
  85. package/dist/components/tags-input/tags-input.d.ts +47 -0
  86. package/dist/components/tags-input/tags-input.d.ts.map +1 -0
  87. package/dist/components/tags-input/tags-input.js +182 -0
  88. package/dist/components/tags-input/tags-input.js.map +1 -0
  89. package/dist/components/time-range-picker/index.d.ts +3 -0
  90. package/dist/components/time-range-picker/index.js +3 -0
  91. package/dist/components/time-range-picker/meta.js +22 -0
  92. package/dist/components/time-range-picker/meta.js.map +1 -0
  93. package/dist/components/time-range-picker/time-expression.d.ts +88 -0
  94. package/dist/components/time-range-picker/time-expression.d.ts.map +1 -0
  95. package/dist/components/time-range-picker/time-expression.js +335 -0
  96. package/dist/components/time-range-picker/time-expression.js.map +1 -0
  97. package/dist/components/time-range-picker/time-range-picker.d.ts +65 -0
  98. package/dist/components/time-range-picker/time-range-picker.d.ts.map +1 -0
  99. package/dist/components/time-range-picker/time-range-picker.js +377 -0
  100. package/dist/components/time-range-picker/time-range-picker.js.map +1 -0
  101. package/dist/components/toast/toast.d.ts +1 -1
  102. package/dist/index.d.ts +21 -1
  103. package/dist/index.js +14 -2
  104. package/dist/registry/components.d.ts.map +1 -1
  105. package/dist/registry/components.js +66 -50
  106. package/dist/registry/components.js.map +1 -1
  107. package/package.json +6 -4
  108. package/src/components/ai-agent-plan/ai-agent-plan.tsx +0 -0
  109. package/src/components/ai-agent-plan/index.ts +10 -0
  110. package/src/components/ai-agent-plan/meta.ts +19 -0
  111. package/src/components/ai-approval-request/ai-approval-request.tsx +368 -0
  112. package/src/components/ai-approval-request/index.ts +7 -0
  113. package/src/components/ai-approval-request/meta.ts +21 -0
  114. package/src/components/ai-disclosure/ai-disclosure.tsx +244 -0
  115. package/src/components/ai-disclosure/index.ts +9 -0
  116. package/src/components/ai-disclosure/meta.ts +23 -0
  117. package/src/components/ai-response/ai-response.tsx +1 -0
  118. package/src/components/ai-structured-output/ai-structured-output.tsx +25 -1
  119. package/src/components/diff-viewer/diff-model.ts +256 -0
  120. package/src/components/diff-viewer/diff-viewer.tsx +339 -0
  121. package/src/components/diff-viewer/index.ts +17 -0
  122. package/src/components/diff-viewer/meta.ts +20 -0
  123. package/src/components/file-upload/file-upload.tsx +322 -0
  124. package/src/components/file-upload/index.ts +20 -0
  125. package/src/components/file-upload/meta.ts +21 -0
  126. package/src/components/file-upload/upload-queue.ts +337 -0
  127. package/src/components/log-viewer/index.ts +21 -0
  128. package/src/components/log-viewer/log-stream.ts +176 -0
  129. package/src/components/log-viewer/log-viewer.tsx +424 -0
  130. package/src/components/log-viewer/meta.ts +21 -0
  131. package/src/components/progress/progress.tsx +3 -0
  132. package/src/components/spinner/spinner.tsx +5 -0
  133. package/src/components/tags-input/index.ts +1 -0
  134. package/src/components/tags-input/meta.ts +23 -0
  135. package/src/components/tags-input/tags-input.tsx +300 -0
  136. package/src/components/time-range-picker/index.ts +25 -0
  137. package/src/components/time-range-picker/meta.ts +22 -0
  138. package/src/components/time-range-picker/time-expression.ts +491 -0
  139. package/src/components/time-range-picker/time-range-picker.tsx +522 -0
  140. package/src/index.ts +8 -0
  141. package/src/registry/components.ts +16 -0
package/README.md CHANGED
@@ -54,27 +54,29 @@ they want to change something.
54
54
 
55
55
  ## What is in it
56
56
 
57
- **52 components** and **8 blocks**, every one keyboard-operable and audited for
57
+ **60 components** and **8 blocks**, every one keyboard-operable and audited for
58
58
  contrast.
59
59
 
60
60
  **AI** — the reason this library exists
61
61
  Conversation · Message · Response · Prompt Input · Tool Call · Reasoning ·
62
- Sources · Model Selector · Token Usage · Agent Status · **Action Ledger** ·
63
- **Structured Output** · **Inline Completion**
62
+ Sources · Model Selector · Token Usage · Agent Status · Agent Plan ·
63
+ Approval Request · Action Ledger · Structured Output · Inline Completion ·
64
+ **Disclosure**
64
65
 
65
66
  **Forms**
66
67
  Input · Label · Checkbox · Radio Group · Switch · Slider · Select · Combobox ·
67
- Form · Calendar · Date Picker
68
+ Form · Calendar · Date Picker · Tags Input · File Upload ·
69
+ **Time Range Picker**
68
70
 
69
71
  **Overlays**
70
72
  Dialog · Sheet · Drawer · Popover · Tooltip · Dropdown Menu
71
73
 
72
74
  **Data**
73
- Table · Data Table · Accordion · Activity Feed · Code Block · **Metric Delta** ·
74
- **Record Diff**
75
+ Table · Data Table · Accordion · Activity Feed · Code Block · Metric Delta ·
76
+ Record Diff · **Log Viewer** · **Diff Viewer**
75
77
 
76
78
  **Feedback**
77
- Alert · Toast · Progress · **Meter** · Skeleton · Spinner · Empty State
79
+ Alert · Toast · Progress · Meter · Skeleton · Spinner · Empty State
78
80
 
79
81
  **Navigation & layout**
80
82
  Tabs · Command · Pagination · Card · Badge · Avatar · Button · Separator
@@ -87,6 +89,11 @@ Most "AI component libraries" ship a chat transcript and stop. Real AI features
87
89
  inside real software are extraction, enrichment, autofill and agents that
88
90
  _change things_ — and those need surfaces nobody else provides.
89
91
 
92
+ **`ai-agent-plan` + `ai-approval-request` + `ai-action-ledger`** — the whole
93
+ sequence, which no other library covers: what the agent intends, the gate where
94
+ you approve it (after correcting the arguments it got wrong), and the record of
95
+ what it actually did.
96
+
90
97
  **`ai-action-ledger`** — what the agent actually did, and what can be undone.
91
98
  Everyone ships approval _before_ a tool runs. Nothing ships the part after,
92
99
  where a deletion can be reverted, a refund can only be offset by another
@@ -0,0 +1,50 @@
1
+ import { ComponentPropsWithRef, ReactNode } from "react";
2
+ //#region src/components/ai-agent-plan/ai-agent-plan.d.ts
3
+ /**
4
+ * What an agent intends to do, and how far through it is.
5
+ *
6
+ * Not a stepper. A wizard's steps are decided up front and never change; an
7
+ * agent's plan is a hypothesis it revises as it learns — it adds a step when a
8
+ * lookup fails, drops one that turned out to be unnecessary, and reorders when
9
+ * a dependency appears. Every stepper treats its step list as fixed, so a plan
10
+ * rendered in one either silently mutates under the reader or re-renders from
11
+ * scratch and loses their place.
12
+ *
13
+ * So revision is the feature here, not an edge case. When the plan changes, the
14
+ * change is announced — "the model added a step" is information, and watching a
15
+ * list quietly grow is not the same as being told.
16
+ *
17
+ * Approval is not here. Deciding whether a plan may run is a different
18
+ * component with a different shape; this one displays and reports.
19
+ */
20
+ type StepStatus = "pending" | "running" | "done" | "failed" | "skipped";
21
+ interface PlanStep {
22
+ id: string;
23
+ title: string;
24
+ status: StepStatus;
25
+ /** What the step will do, or what it found. Shown under the title. */
26
+ detail?: string;
27
+ /** Why it failed. Shown only when the status is "failed". */
28
+ error?: string;
29
+ /** One level of sub-steps. Deeper nesting is a tree, which this is not. */
30
+ steps?: PlanStep[];
31
+ }
32
+ interface AgentPlanProps extends Omit<ComponentPropsWithRef<"div">, "children"> {
33
+ steps: PlanStep[];
34
+ /** Names the plan, so the list is not an unlabelled sequence. */
35
+ label: string;
36
+ children?: ReactNode;
37
+ }
38
+ declare function AgentPlan({ className, steps, label, children, ...props }: AgentPlanProps): import("react").JSX.Element;
39
+ /** "Step 3 of 6" plus the overall state, in words. */
40
+ declare function AgentPlanSummary({ className, ...props }: ComponentPropsWithRef<"p">): import("react").JSX.Element;
41
+ declare function AgentPlanSteps({ className, ...props }: ComponentPropsWithRef<"ol">): import("react").JSX.Element;
42
+ interface AgentPlanStepProps extends Omit<ComponentPropsWithRef<"li">, "children"> {
43
+ step: PlanStep;
44
+ /** Set on sub-steps. Not part of the public API surface. */
45
+ nested?: boolean;
46
+ }
47
+ declare function AgentPlanStep({ className, step, nested, ...props }: AgentPlanStepProps): import("react").JSX.Element;
48
+ //#endregion
49
+ export { AgentPlan, AgentPlanProps, AgentPlanStep, AgentPlanStepProps, AgentPlanSteps, AgentPlanSummary, PlanStep, StepStatus };
50
+ //# sourceMappingURL=ai-agent-plan.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-agent-plan.d.ts","names":[],"sources":["../../../src/components/ai-agent-plan/ai-agent-plan.tsx"],"mappings":";;;;;;;;;;;;;;;;;;;KA+BY;UAEK;EACf;EACA;EACA,QAAQ;;EAER;;EAEA;;EAEA,QAAQ;;UAyCO,uBAAuB,KAAK;EAC3C,OAAO;;EAEP;EACA,WAAW;;iBAGG,YAAY,WAAW,OAAO,OAAO,aAAa,SAAS,iCAAc,IAAA;;iBAqDzE,mBAAmB,cAAc,SAAS,6CAA0B,IAAA;iBA2BpE,iBAAiB,cAAc,SAAS,8CAA2B,IAAA;UAkBlE,2BAA2B,KAAK;EAC/C,MAAM;;EAEN;;iBAGc,gBACd,WACA,MACA,WACG,SACF,qCAAkB,IAAA"}
@@ -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,2 @@
1
+ import { AgentPlan, AgentPlanStep, AgentPlanSteps, AgentPlanSummary } from "./ai-agent-plan.js";
2
+ export { AgentPlan, AgentPlanStep, AgentPlanSteps, AgentPlanSummary };
@@ -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" | "idle" | "thinking" | "working" | "waiting" | "error" | null | undefined;
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