@dowel-ui/react 0.3.0 → 0.5.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 (206) hide show
  1. package/README.md +13 -8
  2. package/dist/components/ai-disclosure/ai-disclosure.d.ts +92 -0
  3. package/dist/components/ai-disclosure/ai-disclosure.d.ts.map +1 -0
  4. package/dist/components/ai-disclosure/ai-disclosure.js +122 -0
  5. package/dist/components/ai-disclosure/ai-disclosure.js.map +1 -0
  6. package/dist/components/ai-disclosure/index.d.ts +2 -0
  7. package/dist/components/ai-disclosure/index.js +2 -0
  8. package/dist/components/ai-disclosure/meta.js +17 -0
  9. package/dist/components/ai-disclosure/meta.js.map +1 -0
  10. package/dist/components/ai-extraction-review/ai-extraction-review.d.ts +52 -0
  11. package/dist/components/ai-extraction-review/ai-extraction-review.d.ts.map +1 -0
  12. package/dist/components/ai-extraction-review/ai-extraction-review.js +249 -0
  13. package/dist/components/ai-extraction-review/ai-extraction-review.js.map +1 -0
  14. package/dist/components/ai-extraction-review/extraction-model.d.ts +94 -0
  15. package/dist/components/ai-extraction-review/extraction-model.d.ts.map +1 -0
  16. package/dist/components/ai-extraction-review/extraction-model.js +91 -0
  17. package/dist/components/ai-extraction-review/extraction-model.js.map +1 -0
  18. package/dist/components/ai-extraction-review/index.d.ts +3 -0
  19. package/dist/components/ai-extraction-review/index.js +3 -0
  20. package/dist/components/ai-extraction-review/meta.js +17 -0
  21. package/dist/components/ai-extraction-review/meta.js.map +1 -0
  22. package/dist/components/ai-suggested-value/ai-suggested-value.d.ts +72 -0
  23. package/dist/components/ai-suggested-value/ai-suggested-value.d.ts.map +1 -0
  24. package/dist/components/ai-suggested-value/ai-suggested-value.js +126 -0
  25. package/dist/components/ai-suggested-value/ai-suggested-value.js.map +1 -0
  26. package/dist/components/ai-suggested-value/index.d.ts +2 -0
  27. package/dist/components/ai-suggested-value/index.js +2 -0
  28. package/dist/components/ai-suggested-value/meta.js +17 -0
  29. package/dist/components/ai-suggested-value/meta.js.map +1 -0
  30. package/dist/components/alert/alert.d.ts +1 -1
  31. package/dist/components/avatar/avatar.d.ts +1 -1
  32. package/dist/components/badge/badge.d.ts +1 -1
  33. package/dist/components/button/button.d.ts +1 -1
  34. package/dist/components/confirm-typed/confirm-typed.d.ts +47 -0
  35. package/dist/components/confirm-typed/confirm-typed.d.ts.map +1 -0
  36. package/dist/components/confirm-typed/confirm-typed.js +106 -0
  37. package/dist/components/confirm-typed/confirm-typed.js.map +1 -0
  38. package/dist/components/confirm-typed/index.d.ts +2 -0
  39. package/dist/components/confirm-typed/index.js +2 -0
  40. package/dist/components/confirm-typed/meta.js +17 -0
  41. package/dist/components/confirm-typed/meta.js.map +1 -0
  42. package/dist/components/cron-editor/cron-editor.d.ts +36 -0
  43. package/dist/components/cron-editor/cron-editor.d.ts.map +1 -0
  44. package/dist/components/cron-editor/cron-editor.js +493 -0
  45. package/dist/components/cron-editor/cron-editor.js.map +1 -0
  46. package/dist/components/cron-editor/cron-expression.d.ts +82 -0
  47. package/dist/components/cron-editor/cron-expression.d.ts.map +1 -0
  48. package/dist/components/cron-editor/cron-expression.js +352 -0
  49. package/dist/components/cron-editor/cron-expression.js.map +1 -0
  50. package/dist/components/cron-editor/index.d.ts +3 -0
  51. package/dist/components/cron-editor/index.js +3 -0
  52. package/dist/components/cron-editor/meta.js +17 -0
  53. package/dist/components/cron-editor/meta.js.map +1 -0
  54. package/dist/components/date-picker/date-picker.js +1 -1
  55. package/dist/components/diff-viewer/diff-model.d.ts +69 -0
  56. package/dist/components/diff-viewer/diff-model.d.ts.map +1 -0
  57. package/dist/components/diff-viewer/diff-model.js +180 -0
  58. package/dist/components/diff-viewer/diff-model.js.map +1 -0
  59. package/dist/components/diff-viewer/diff-viewer.d.ts +42 -0
  60. package/dist/components/diff-viewer/diff-viewer.d.ts.map +1 -0
  61. package/dist/components/diff-viewer/diff-viewer.js +212 -0
  62. package/dist/components/diff-viewer/diff-viewer.js.map +1 -0
  63. package/dist/components/diff-viewer/index.d.ts +3 -0
  64. package/dist/components/diff-viewer/index.js +3 -0
  65. package/dist/components/diff-viewer/meta.js +17 -0
  66. package/dist/components/diff-viewer/meta.js.map +1 -0
  67. package/dist/components/dns-record/dns-record.d.ts +61 -0
  68. package/dist/components/dns-record/dns-record.d.ts.map +1 -0
  69. package/dist/components/dns-record/dns-record.js +227 -0
  70. package/dist/components/dns-record/dns-record.js.map +1 -0
  71. package/dist/components/dns-record/index.d.ts +2 -0
  72. package/dist/components/dns-record/index.js +2 -0
  73. package/dist/components/dns-record/meta.js +17 -0
  74. package/dist/components/dns-record/meta.js.map +1 -0
  75. package/dist/components/log-viewer/index.d.ts +3 -0
  76. package/dist/components/log-viewer/index.js +3 -0
  77. package/dist/components/log-viewer/log-stream.d.ts +68 -0
  78. package/dist/components/log-viewer/log-stream.d.ts.map +1 -0
  79. package/dist/components/log-viewer/log-stream.js +120 -0
  80. package/dist/components/log-viewer/log-stream.js.map +1 -0
  81. package/dist/components/log-viewer/log-viewer.d.ts +51 -0
  82. package/dist/components/log-viewer/log-viewer.d.ts.map +1 -0
  83. package/dist/components/log-viewer/log-viewer.js +284 -0
  84. package/dist/components/log-viewer/log-viewer.js.map +1 -0
  85. package/dist/components/log-viewer/meta.js +17 -0
  86. package/dist/components/log-viewer/meta.js.map +1 -0
  87. package/dist/components/permission-matrix/index.d.ts +3 -0
  88. package/dist/components/permission-matrix/index.js +3 -0
  89. package/dist/components/permission-matrix/meta.js +17 -0
  90. package/dist/components/permission-matrix/meta.js.map +1 -0
  91. package/dist/components/permission-matrix/permission-matrix.d.ts +46 -0
  92. package/dist/components/permission-matrix/permission-matrix.d.ts.map +1 -0
  93. package/dist/components/permission-matrix/permission-matrix.js +199 -0
  94. package/dist/components/permission-matrix/permission-matrix.js.map +1 -0
  95. package/dist/components/permission-matrix/permission-model.d.ts +53 -0
  96. package/dist/components/permission-matrix/permission-model.d.ts.map +1 -0
  97. package/dist/components/permission-matrix/permission-model.js +64 -0
  98. package/dist/components/permission-matrix/permission-model.js.map +1 -0
  99. package/dist/components/progress/progress.d.ts +1 -1
  100. package/dist/components/secret-field/index.d.ts +2 -0
  101. package/dist/components/secret-field/index.js +2 -0
  102. package/dist/components/secret-field/meta.js +17 -0
  103. package/dist/components/secret-field/meta.js.map +1 -0
  104. package/dist/components/secret-field/secret-field.d.ts +58 -0
  105. package/dist/components/secret-field/secret-field.d.ts.map +1 -0
  106. package/dist/components/secret-field/secret-field.js +168 -0
  107. package/dist/components/secret-field/secret-field.js.map +1 -0
  108. package/dist/components/session-expiry/index.d.ts +2 -0
  109. package/dist/components/session-expiry/index.js +2 -0
  110. package/dist/components/session-expiry/meta.js +17 -0
  111. package/dist/components/session-expiry/meta.js.map +1 -0
  112. package/dist/components/session-expiry/session-expiry.d.ts +64 -0
  113. package/dist/components/session-expiry/session-expiry.d.ts.map +1 -0
  114. package/dist/components/session-expiry/session-expiry.js +185 -0
  115. package/dist/components/session-expiry/session-expiry.js.map +1 -0
  116. package/dist/components/shortcut-recorder/index.d.ts +3 -0
  117. package/dist/components/shortcut-recorder/index.js +3 -0
  118. package/dist/components/shortcut-recorder/meta.js +17 -0
  119. package/dist/components/shortcut-recorder/meta.js.map +1 -0
  120. package/dist/components/shortcut-recorder/shortcut-model.d.ts +67 -0
  121. package/dist/components/shortcut-recorder/shortcut-model.d.ts.map +1 -0
  122. package/dist/components/shortcut-recorder/shortcut-model.js +168 -0
  123. package/dist/components/shortcut-recorder/shortcut-model.js.map +1 -0
  124. package/dist/components/shortcut-recorder/shortcut-recorder.d.ts +47 -0
  125. package/dist/components/shortcut-recorder/shortcut-recorder.d.ts.map +1 -0
  126. package/dist/components/shortcut-recorder/shortcut-recorder.js +212 -0
  127. package/dist/components/shortcut-recorder/shortcut-recorder.js.map +1 -0
  128. package/dist/components/spinner/spinner.d.ts +1 -1
  129. package/dist/components/sync-status/index.d.ts +2 -0
  130. package/dist/components/sync-status/index.js +2 -0
  131. package/dist/components/sync-status/meta.js +17 -0
  132. package/dist/components/sync-status/meta.js.map +1 -0
  133. package/dist/components/sync-status/sync-status.d.ts +65 -0
  134. package/dist/components/sync-status/sync-status.d.ts.map +1 -0
  135. package/dist/components/sync-status/sync-status.js +103 -0
  136. package/dist/components/sync-status/sync-status.js.map +1 -0
  137. package/dist/components/time-range-picker/index.d.ts +3 -0
  138. package/dist/components/time-range-picker/index.js +3 -0
  139. package/dist/components/time-range-picker/meta.js +22 -0
  140. package/dist/components/time-range-picker/meta.js.map +1 -0
  141. package/dist/components/time-range-picker/time-expression.d.ts +88 -0
  142. package/dist/components/time-range-picker/time-expression.d.ts.map +1 -0
  143. package/dist/components/time-range-picker/time-expression.js +335 -0
  144. package/dist/components/time-range-picker/time-expression.js.map +1 -0
  145. package/dist/components/time-range-picker/time-range-picker.d.ts +65 -0
  146. package/dist/components/time-range-picker/time-range-picker.d.ts.map +1 -0
  147. package/dist/components/time-range-picker/time-range-picker.js +377 -0
  148. package/dist/components/time-range-picker/time-range-picker.js.map +1 -0
  149. package/dist/components/toast/toast.d.ts +1 -1
  150. package/dist/index.d.ts +36 -1
  151. package/dist/index.js +24 -3
  152. package/dist/registry/components.d.ts.map +1 -1
  153. package/dist/registry/components.js +78 -50
  154. package/dist/registry/components.js.map +1 -1
  155. package/package.json +6 -4
  156. package/src/components/ai-disclosure/ai-disclosure.tsx +244 -0
  157. package/src/components/ai-disclosure/index.ts +9 -0
  158. package/src/components/ai-disclosure/meta.ts +23 -0
  159. package/src/components/ai-extraction-review/ai-extraction-review.tsx +492 -0
  160. package/src/components/ai-extraction-review/extraction-model.ts +159 -0
  161. package/src/components/ai-extraction-review/index.ts +13 -0
  162. package/src/components/ai-extraction-review/meta.ts +24 -0
  163. package/src/components/ai-suggested-value/ai-suggested-value.tsx +295 -0
  164. package/src/components/ai-suggested-value/index.ts +6 -0
  165. package/src/components/ai-suggested-value/meta.ts +24 -0
  166. package/src/components/confirm-typed/confirm-typed.tsx +191 -0
  167. package/src/components/confirm-typed/index.ts +1 -0
  168. package/src/components/confirm-typed/meta.ts +22 -0
  169. package/src/components/cron-editor/cron-editor.tsx +676 -0
  170. package/src/components/cron-editor/cron-expression.ts +552 -0
  171. package/src/components/cron-editor/index.ts +23 -0
  172. package/src/components/cron-editor/meta.ts +23 -0
  173. package/src/components/diff-viewer/diff-model.ts +256 -0
  174. package/src/components/diff-viewer/diff-viewer.tsx +339 -0
  175. package/src/components/diff-viewer/index.ts +17 -0
  176. package/src/components/diff-viewer/meta.ts +20 -0
  177. package/src/components/dns-record/dns-record.tsx +315 -0
  178. package/src/components/dns-record/index.ts +7 -0
  179. package/src/components/dns-record/meta.ts +21 -0
  180. package/src/components/log-viewer/index.ts +21 -0
  181. package/src/components/log-viewer/log-stream.ts +176 -0
  182. package/src/components/log-viewer/log-viewer.tsx +424 -0
  183. package/src/components/log-viewer/meta.ts +21 -0
  184. package/src/components/permission-matrix/index.ts +14 -0
  185. package/src/components/permission-matrix/meta.ts +23 -0
  186. package/src/components/permission-matrix/permission-matrix.tsx +368 -0
  187. package/src/components/permission-matrix/permission-model.ts +120 -0
  188. package/src/components/secret-field/index.ts +6 -0
  189. package/src/components/secret-field/meta.ts +21 -0
  190. package/src/components/secret-field/secret-field.tsx +284 -0
  191. package/src/components/session-expiry/index.ts +9 -0
  192. package/src/components/session-expiry/meta.ts +23 -0
  193. package/src/components/session-expiry/session-expiry.tsx +321 -0
  194. package/src/components/shortcut-recorder/index.ts +19 -0
  195. package/src/components/shortcut-recorder/meta.ts +22 -0
  196. package/src/components/shortcut-recorder/shortcut-model.ts +204 -0
  197. package/src/components/shortcut-recorder/shortcut-recorder.tsx +336 -0
  198. package/src/components/sync-status/index.ts +9 -0
  199. package/src/components/sync-status/meta.ts +21 -0
  200. package/src/components/sync-status/sync-status.tsx +217 -0
  201. package/src/components/time-range-picker/index.ts +25 -0
  202. package/src/components/time-range-picker/meta.ts +22 -0
  203. package/src/components/time-range-picker/time-expression.ts +491 -0
  204. package/src/components/time-range-picker/time-range-picker.tsx +522 -0
  205. package/src/index.ts +14 -0
  206. package/src/registry/components.ts +28 -0
package/README.md CHANGED
@@ -54,28 +54,33 @@ they want to change something.
54
54
 
55
55
  ## What is in it
56
56
 
57
- **56 components** and **8 blocks**, every one keyboard-operable and audited for
57
+ **70 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 · **Agent Plan** ·
63
- **Approval Request** · **Action Ledger** · **Structured Output** ·
64
- **Inline Completion**
62
+ Sources · Model Selector · Token Usage · Agent Status · Agent Plan ·
63
+ Approval Request · Action Ledger · Structured Output · Inline Completion ·
64
+ Disclosure · **Extraction Review** ·
65
+ **Suggested Value**
65
66
 
66
67
  **Forms**
67
68
  Input · Label · Checkbox · Radio Group · Switch · Slider · Select · Combobox ·
68
- Form · Calendar · Date Picker · **Tags Input** · **File Upload**
69
+ Form · Calendar · Date Picker · Tags Input · File Upload ·
70
+ **Time Range Picker** · **Cron Editor** · **Secret Field** ·
71
+ **Confirm Typed** · **Shortcut Recorder**
69
72
 
70
73
  **Overlays**
71
74
  Dialog · Sheet · Drawer · Popover · Tooltip · Dropdown Menu
72
75
 
73
76
  **Data**
74
- Table · Data Table · Accordion · Activity Feed · Code Block · **Metric Delta** ·
75
- **Record Diff**
77
+ Table · Data Table · Accordion · Activity Feed · Code Block · Metric Delta ·
78
+ Record Diff · **Log Viewer** · **Diff Viewer** · **Permission Matrix** ·
79
+ **DNS Record**
76
80
 
77
81
  **Feedback**
78
- Alert · Toast · Progress · **Meter** · Skeleton · Spinner · Empty State
82
+ Alert · Toast · Progress · Meter · Skeleton · Spinner · Empty State ·
83
+ **Sync Status** · **Session Expiry**
79
84
 
80
85
  **Navigation & layout**
81
86
  Tabs · Command · Pagination · Card · Badge · Avatar · Button · Separator
@@ -0,0 +1,92 @@
1
+ import { VariantProps } from "class-variance-authority";
2
+ import { ComponentPropsWithRef, ReactNode } from "react";
3
+ //#region src/components/ai-disclosure/ai-disclosure.d.ts
4
+ /**
5
+ * Telling someone they are looking at AI.
6
+ *
7
+ * Not one registry ships this — not AI Elements, assistant-ui, prompt-kit,
8
+ * CopilotKit or shadcn — while every one of them ships the chat surface that
9
+ * needs it. The gap is the reason this exists.
10
+ *
11
+ * The four `kind`s are not invented. They are the four human-visible situations
12
+ * the EU AI Act's Article 50 actually creates, which has applied since 2 August
13
+ * 2026: you are talking to a machine (50(1)); this media was generated or
14
+ * manipulated and resembles something real (50(4), "deep fakes"); this text was
15
+ * published to inform the public (50(4)); and the assisted case, which the same
16
+ * paragraph exempts where there is human review or editorial control — worth
17
+ * saying out loud rather than leaving to inference.
18
+ *
19
+ * What this cannot do, and what no React component can: Article 50(2) requires
20
+ * synthetic output to be marked *in a machine-readable format*, in the artifact
21
+ * itself, by whoever generated it. Rendering a badge does not do that, and a
22
+ * component that let you believe otherwise would be worse than none. Read the
23
+ * accessibility note and the docs page: this is a disclosure control, not a
24
+ * compliance product, and nothing here is legal advice.
25
+ */
26
+ type DisclosureKind = "interaction" | "generated" | "manipulated" | "assisted";
27
+ declare const disclosureVariants: (props?: ({
28
+ variant?: "banner" | "inline" | "overlay" | null | undefined;
29
+ } & import("class-variance-authority/types").ClassProp) | undefined) => string;
30
+ interface AIDisclosureProps extends Omit<ComponentPropsWithRef<"div">, "children">, VariantProps<typeof disclosureVariants> {
31
+ kind: DisclosureKind;
32
+ /** Replaces the default wording. The default is a starting point, not a rule. */
33
+ label?: ReactNode;
34
+ /**
35
+ * Replaces the mark. The Commission publishes three icons for this and lets
36
+ * anyone use them without attribution — they are deliberately not bundled
37
+ * here, because an official mark shipped inside a component library ends up
38
+ * on content nobody checked. Bring your own, and the Commission's own words
39
+ * apply: using them "does not establish legal compliance by itself".
40
+ */
41
+ icon?: ReactNode;
42
+ /** A provenance panel, or anything else that belongs after the label. */
43
+ children?: ReactNode;
44
+ }
45
+ /**
46
+ * A disclosure, in words.
47
+ *
48
+ * The icon is decorative and the text is the disclosure. A sparkle alone is not
49
+ * a notice — it is a decoration that a sighted reader may or may not decode and
50
+ * that a screen reader passes over in silence, which is the failure mode the
51
+ * whole component exists to avoid.
52
+ */
53
+ declare function AIDisclosure({ className, kind, variant, label, icon, children, ...props }: AIDisclosureProps): import("react").JSX.Element;
54
+ interface ProvenanceClaim {
55
+ label: string;
56
+ value: ReactNode;
57
+ }
58
+ interface AIDisclosureProvenanceProps extends Omit<ComponentPropsWithRef<"button">, "children"> {
59
+ /**
60
+ * What is being claimed about this content, and by whom.
61
+ *
62
+ * Claims, not findings. They arrive as data — from a C2PA manifest your
63
+ * server read, from your own pipeline, from a vendor — and this renders them.
64
+ */
65
+ claims: ProvenanceClaim[];
66
+ /** Who asserts the claims. Named, because an unattributed claim is a rumour. */
67
+ source?: string;
68
+ /**
69
+ * Whether something you trust checked the claims, and said so.
70
+ *
71
+ * Supplied, never computed. Verifying a C2PA manifest means parsing signed
72
+ * COSE and walking a certificate chain; doing that in the browser would mean
73
+ * a wasm blob you cannot read, and "you own the source" would stop being
74
+ * true. It also cannot be trusted client-side even when it succeeds, because
75
+ * the page doing the checking is the page making the claim. Verify on a
76
+ * server and pass the answer down. Left undefined, this says so.
77
+ */
78
+ verified?: boolean;
79
+ triggerLabel?: string;
80
+ }
81
+ /**
82
+ * The metadata behind a disclosure, presented as claims rather than proof.
83
+ *
84
+ * The distinction is the entire point. "Made with Model X on 3 September" looks
85
+ * like a fact and is a string somebody put in a file. Rendering it beside a
86
+ * checkmark would launder an assertion into evidence, so the panel names who is
87
+ * asserting and states plainly whether anyone checked.
88
+ */
89
+ declare function AIDisclosureProvenance({ className, claims, source, verified, triggerLabel, ...props }: AIDisclosureProvenanceProps): import("react").JSX.Element;
90
+ //#endregion
91
+ export { AIDisclosure, AIDisclosureProps, AIDisclosureProvenance, AIDisclosureProvenanceProps, DisclosureKind, ProvenanceClaim, disclosureVariants };
92
+ //# sourceMappingURL=ai-disclosure.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-disclosure.d.ts","names":[],"sources":["../../../src/components/ai-disclosure/ai-disclosure.tsx"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;KAgCY;cAgBN,qBAAkB;;6CAyBvB;UAegB,0BAEb,KAAK,2CACL,oBAAoB;EACtB,MAAM;;EAEN,QAAQ;;;;;;;;EAQR,OAAO;;EAEP,WAAW;;;;;;;;;;iBAWG,eACd,WACA,MACA,SACA,OACA,MACA,aACG,SACF,oCAAiB,IAAA;UAmBH;EACf;EACA,OAAO;;UAGQ,oCAAoC,KACnD;;;;;;;EASA,QAAQ;;EAER;;;;;;;;;;;EAWA;EACA;;;;;;;;;;iBAoBc,yBACd,WACA,QACA,QACA,UACA,iBACG,SACF,8CAA2B,IAAA"}
@@ -0,0 +1,122 @@
1
+ "use client";
2
+ import { focusRing, iconSlot } from "../../lib/styles.js";
3
+ import { cn } from "../../lib/utils.js";
4
+ import { Popover, PopoverContent, PopoverTrigger } from "../popover/popover.js";
5
+ import { jsx, jsxs } from "react/jsx-runtime";
6
+ import { cva } from "class-variance-authority";
7
+ //#region src/components/ai-disclosure/ai-disclosure.tsx
8
+ /**
9
+ * What each kind says by default.
10
+ *
11
+ * Phrased for a reader rather than for a lawyer, and every one of them is
12
+ * overridable — the Act says the disclosure must be clear, not that it must use
13
+ * these words, and the right words depend on the product.
14
+ */
15
+ const DEFAULT_LABEL = {
16
+ interaction: "You are chatting with an AI assistant",
17
+ generated: "Generated by AI",
18
+ manipulated: "This media was altered by AI",
19
+ assisted: "AI-assisted, reviewed by a person"
20
+ };
21
+ const disclosureVariants = cva(cn("inline-flex w-fit items-center gap-1.5 font-medium", "[&_svg:not([class*='size-'])]:size-3.5", iconSlot), {
22
+ variants: { variant: {
23
+ /** Sits beside the thing it describes. */
24
+ inline: "rounded-full border border-border-strong px-2 py-0.5 text-xs",
25
+ /** Above a conversation, where Article 50(1) usually belongs. */
26
+ banner: "w-full rounded-lg border border-border bg-muted/40 px-3 py-2 text-xs text-muted-foreground",
27
+ /**
28
+ * Over the corner of an image or video. `--overlay` is a dark scrim in
29
+ * every shipped theme, so the text is white on purpose rather than
30
+ * following the theme — a label that inverts over a photograph is
31
+ * unreadable on half of them.
32
+ */
33
+ overlay: "rounded-md bg-overlay px-2 py-1 text-2xs text-white backdrop-blur-sm"
34
+ } },
35
+ defaultVariants: { variant: "inline" }
36
+ });
37
+ function SparkIcon() {
38
+ return /* @__PURE__ */ jsx("svg", {
39
+ viewBox: "0 0 24 24",
40
+ fill: "none",
41
+ "aria-hidden": "true",
42
+ children: /* @__PURE__ */ jsx("path", {
43
+ d: "M12 3l1.9 5.1L19 10l-5.1 1.9L12 17l-1.9-5.1L5 10l5.1-1.9L12 3Z",
44
+ stroke: "currentColor",
45
+ strokeWidth: "2",
46
+ strokeLinejoin: "round"
47
+ })
48
+ });
49
+ }
50
+ /**
51
+ * A disclosure, in words.
52
+ *
53
+ * The icon is decorative and the text is the disclosure. A sparkle alone is not
54
+ * a notice — it is a decoration that a sighted reader may or may not decode and
55
+ * that a screen reader passes over in silence, which is the failure mode the
56
+ * whole component exists to avoid.
57
+ */
58
+ function AIDisclosure({ className, kind, variant, label, icon, children, ...props }) {
59
+ return /* @__PURE__ */ jsxs("div", {
60
+ "data-slot": "ai-disclosure",
61
+ "data-kind": kind,
62
+ className: cn(disclosureVariants({ variant }), className),
63
+ ...props,
64
+ children: [
65
+ icon ?? /* @__PURE__ */ jsx(SparkIcon, {}),
66
+ /* @__PURE__ */ jsx("span", { children: label ?? DEFAULT_LABEL[kind] }),
67
+ children
68
+ ]
69
+ });
70
+ }
71
+ function statusText(status, source) {
72
+ const by = source ? ` by ${source}` : "";
73
+ if (status === "verified") return `Checked${by} and the claims matched.`;
74
+ if (status === "failed") return `Checked${by} and the claims did not match.`;
75
+ return "Not checked. These are claims made about the content, not findings.";
76
+ }
77
+ /**
78
+ * The metadata behind a disclosure, presented as claims rather than proof.
79
+ *
80
+ * The distinction is the entire point. "Made with Model X on 3 September" looks
81
+ * like a fact and is a string somebody put in a file. Rendering it beside a
82
+ * checkmark would launder an assertion into evidence, so the panel names who is
83
+ * asserting and states plainly whether anyone checked.
84
+ */
85
+ function AIDisclosureProvenance({ className, claims, source, verified, triggerLabel = "Content details", ...props }) {
86
+ const status = verified === void 0 ? "unchecked" : verified ? "verified" : "failed";
87
+ return /* @__PURE__ */ jsxs(Popover, { children: [/* @__PURE__ */ jsx(PopoverTrigger, {
88
+ asChild: true,
89
+ children: /* @__PURE__ */ jsx("button", {
90
+ type: "button",
91
+ "data-slot": "ai-disclosure-provenance-trigger",
92
+ className: cn("rounded-sm underline decoration-dotted underline-offset-2 hover:decoration-solid", focusRing, className),
93
+ ...props,
94
+ children: triggerLabel
95
+ })
96
+ }), /* @__PURE__ */ jsxs(PopoverContent, {
97
+ "aria-label": triggerLabel,
98
+ className: "w-80 text-xs",
99
+ children: [/* @__PURE__ */ jsx("dl", {
100
+ className: "grid grid-cols-[auto_1fr] gap-x-3 gap-y-1.5",
101
+ children: claims.map((claim) => /* @__PURE__ */ jsxs("div", {
102
+ className: "contents",
103
+ children: [/* @__PURE__ */ jsx("dt", {
104
+ className: "text-muted-foreground",
105
+ children: claim.label
106
+ }), /* @__PURE__ */ jsx("dd", {
107
+ className: "break-words",
108
+ children: claim.value
109
+ })]
110
+ }, claim.label))
111
+ }), /* @__PURE__ */ jsx("p", {
112
+ "data-slot": "ai-disclosure-provenance-status",
113
+ "data-status": status,
114
+ className: cn("mt-3 border-t border-border pt-2", status === "failed" ? "text-destructive" : "text-muted-foreground"),
115
+ children: statusText(status, source)
116
+ })]
117
+ })] });
118
+ }
119
+ //#endregion
120
+ export { AIDisclosure, AIDisclosureProvenance, disclosureVariants };
121
+
122
+ //# sourceMappingURL=ai-disclosure.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-disclosure.js","names":[],"sources":["../../../src/components/ai-disclosure/ai-disclosure.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport type { ComponentPropsWithRef, ReactNode } from \"react\";\n\nimport { Popover, PopoverContent, PopoverTrigger } from \"@/components/popover\";\nimport { focusRing, iconSlot } from \"@/lib/styles\";\nimport { cn } from \"@/lib/utils\";\n\n/**\n * Telling someone they are looking at AI.\n *\n * Not one registry ships this — not AI Elements, assistant-ui, prompt-kit,\n * CopilotKit or shadcn — while every one of them ships the chat surface that\n * needs it. The gap is the reason this exists.\n *\n * The four `kind`s are not invented. They are the four human-visible situations\n * the EU AI Act's Article 50 actually creates, which has applied since 2 August\n * 2026: you are talking to a machine (50(1)); this media was generated or\n * manipulated and resembles something real (50(4), \"deep fakes\"); this text was\n * published to inform the public (50(4)); and the assisted case, which the same\n * paragraph exempts where there is human review or editorial control — worth\n * saying out loud rather than leaving to inference.\n *\n * What this cannot do, and what no React component can: Article 50(2) requires\n * synthetic output to be marked *in a machine-readable format*, in the artifact\n * itself, by whoever generated it. Rendering a badge does not do that, and a\n * component that let you believe otherwise would be worse than none. Read the\n * accessibility note and the docs page: this is a disclosure control, not a\n * compliance product, and nothing here is legal advice.\n */\n\nexport type DisclosureKind = \"interaction\" | \"generated\" | \"manipulated\" | \"assisted\";\n\n/**\n * What each kind says by default.\n *\n * Phrased for a reader rather than for a lawyer, and every one of them is\n * overridable — the Act says the disclosure must be clear, not that it must use\n * these words, and the right words depend on the product.\n */\nconst DEFAULT_LABEL: Record<DisclosureKind, string> = {\n interaction: \"You are chatting with an AI assistant\",\n generated: \"Generated by AI\",\n manipulated: \"This media was altered by AI\",\n assisted: \"AI-assisted, reviewed by a person\",\n};\n\nconst disclosureVariants = cva(\n cn(\n \"inline-flex w-fit items-center gap-1.5 font-medium\",\n \"[&_svg:not([class*='size-'])]:size-3.5\",\n iconSlot,\n ),\n {\n variants: {\n variant: {\n /** Sits beside the thing it describes. */\n inline: \"rounded-full border border-border-strong px-2 py-0.5 text-xs\",\n /** Above a conversation, where Article 50(1) usually belongs. */\n banner:\n \"w-full rounded-lg border border-border bg-muted/40 px-3 py-2 text-xs text-muted-foreground\",\n /**\n * Over the corner of an image or video. `--overlay` is a dark scrim in\n * every shipped theme, so the text is white on purpose rather than\n * following the theme — a label that inverts over a photograph is\n * unreadable on half of them.\n */\n overlay: \"rounded-md bg-overlay px-2 py-1 text-2xs text-white backdrop-blur-sm\",\n },\n },\n defaultVariants: { variant: \"inline\" },\n },\n);\n\nfunction SparkIcon() {\n return (\n <svg viewBox=\"0 0 24 24\" fill=\"none\" aria-hidden=\"true\">\n <path\n d=\"M12 3l1.9 5.1L19 10l-5.1 1.9L12 17l-1.9-5.1L5 10l5.1-1.9L12 3Z\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinejoin=\"round\"\n />\n </svg>\n );\n}\n\nexport interface AIDisclosureProps\n extends\n Omit<ComponentPropsWithRef<\"div\">, \"children\">,\n VariantProps<typeof disclosureVariants> {\n kind: DisclosureKind;\n /** Replaces the default wording. The default is a starting point, not a rule. */\n label?: ReactNode;\n /**\n * Replaces the mark. The Commission publishes three icons for this and lets\n * anyone use them without attribution — they are deliberately not bundled\n * here, because an official mark shipped inside a component library ends up\n * on content nobody checked. Bring your own, and the Commission's own words\n * apply: using them \"does not establish legal compliance by itself\".\n */\n icon?: ReactNode;\n /** A provenance panel, or anything else that belongs after the label. */\n children?: ReactNode;\n}\n\n/**\n * A disclosure, in words.\n *\n * The icon is decorative and the text is the disclosure. A sparkle alone is not\n * a notice — it is a decoration that a sighted reader may or may not decode and\n * that a screen reader passes over in silence, which is the failure mode the\n * whole component exists to avoid.\n */\nexport function AIDisclosure({\n className,\n kind,\n variant,\n label,\n icon,\n children,\n ...props\n}: AIDisclosureProps) {\n return (\n <div\n data-slot=\"ai-disclosure\"\n data-kind={kind}\n className={cn(disclosureVariants({ variant }), className)}\n {...props}\n >\n {icon ?? <SparkIcon />}\n <span>{label ?? DEFAULT_LABEL[kind]}</span>\n {children}\n </div>\n );\n}\n\n/* ------------------------------------------------------------------ */\n/* Provenance */\n/* ------------------------------------------------------------------ */\n\nexport interface ProvenanceClaim {\n label: string;\n value: ReactNode;\n}\n\nexport interface AIDisclosureProvenanceProps extends Omit<\n ComponentPropsWithRef<\"button\">,\n \"children\"\n> {\n /**\n * What is being claimed about this content, and by whom.\n *\n * Claims, not findings. They arrive as data — from a C2PA manifest your\n * server read, from your own pipeline, from a vendor — and this renders them.\n */\n claims: ProvenanceClaim[];\n /** Who asserts the claims. Named, because an unattributed claim is a rumour. */\n source?: string;\n /**\n * Whether something you trust checked the claims, and said so.\n *\n * Supplied, never computed. Verifying a C2PA manifest means parsing signed\n * COSE and walking a certificate chain; doing that in the browser would mean\n * a wasm blob you cannot read, and \"you own the source\" would stop being\n * true. It also cannot be trusted client-side even when it succeeds, because\n * the page doing the checking is the page making the claim. Verify on a\n * server and pass the answer down. Left undefined, this says so.\n */\n verified?: boolean;\n triggerLabel?: string;\n}\n\ntype CheckStatus = \"verified\" | \"failed\" | \"unchecked\";\n\nfunction statusText(status: CheckStatus, source?: string): string {\n const by = source ? ` by ${source}` : \"\";\n if (status === \"verified\") return `Checked${by} and the claims matched.`;\n if (status === \"failed\") return `Checked${by} and the claims did not match.`;\n return \"Not checked. These are claims made about the content, not findings.\";\n}\n\n/**\n * The metadata behind a disclosure, presented as claims rather than proof.\n *\n * The distinction is the entire point. \"Made with Model X on 3 September\" looks\n * like a fact and is a string somebody put in a file. Rendering it beside a\n * checkmark would launder an assertion into evidence, so the panel names who is\n * asserting and states plainly whether anyone checked.\n */\nexport function AIDisclosureProvenance({\n className,\n claims,\n source,\n verified,\n triggerLabel = \"Content details\",\n ...props\n}: AIDisclosureProvenanceProps) {\n const status = verified === undefined ? \"unchecked\" : verified ? \"verified\" : \"failed\";\n\n return (\n <Popover>\n <PopoverTrigger asChild>\n <button\n type=\"button\"\n data-slot=\"ai-disclosure-provenance-trigger\"\n className={cn(\n \"rounded-sm underline decoration-dotted underline-offset-2 hover:decoration-solid\",\n focusRing,\n className,\n )}\n {...props}\n >\n {triggerLabel}\n </button>\n </PopoverTrigger>\n <PopoverContent aria-label={triggerLabel} className=\"w-80 text-xs\">\n <dl className=\"grid grid-cols-[auto_1fr] gap-x-3 gap-y-1.5\">\n {claims.map((claim) => (\n <div key={claim.label} className=\"contents\">\n <dt className=\"text-muted-foreground\">{claim.label}</dt>\n <dd className=\"break-words\">{claim.value}</dd>\n </div>\n ))}\n </dl>\n {/* Said in text, and last, so it qualifies everything above it rather\n than being a tick a reader has to interpret. */}\n <p\n data-slot=\"ai-disclosure-provenance-status\"\n data-status={status}\n className={cn(\n \"mt-3 border-t border-border pt-2\",\n status === \"failed\" ? \"text-destructive\" : \"text-muted-foreground\",\n )}\n >\n {statusText(status, source)}\n </p>\n </PopoverContent>\n </Popover>\n );\n}\n\nexport { disclosureVariants };\n"],"mappings":";;;;;;;;;;;;;;AAyCA,MAAM,gBAAgD;CACpD,aAAa;CACb,WAAW;CACX,aAAa;CACb,UAAU;AACZ;AAEA,MAAM,qBAAqB,IACzB,GACE,sDACA,0CACA,QACF,GACA;CACE,UAAU,EACR,SAAS;;EAEP,QAAQ;;EAER,QACE;;;;;;;EAOF,SAAS;CACX,EACF;CACA,iBAAiB,EAAE,SAAS,SAAS;AACvC,CACF;AAEA,SAAS,YAAY;CACnB,OACE,oBAAC,OAAD;EAAK,SAAQ;EAAY,MAAK;EAAO,eAAY;EAC/C,UAAA,oBAAC,QAAD;GACE,GAAE;GACF,QAAO;GACP,aAAY;GACZ,gBAAe;EAChB,CAAA;CACE,CAAA;AAET;;;;;;;;;AA6BA,SAAgB,aAAa,EAC3B,WACA,MACA,SACA,OACA,MACA,UACA,GAAG,SACiB;CACpB,OACE,qBAAC,OAAD;EACE,aAAU;EACV,aAAW;EACX,WAAW,GAAG,mBAAmB,EAAE,QAAQ,CAAC,GAAG,SAAS;EACxD,GAAI;EAJN,UAAA;GAMG,QAAQ,oBAAC,WAAD,CAAY,CAAA;GACrB,oBAAC,QAAD,EAAA,UAAO,SAAS,cAAc,MAAY,CAAA;GACzC;EACE;;AAET;AAwCA,SAAS,WAAW,QAAqB,QAAyB;CAChE,MAAM,KAAK,SAAS,OAAO,WAAW;CACtC,IAAI,WAAW,YAAY,OAAO,UAAU,GAAG;CAC/C,IAAI,WAAW,UAAU,OAAO,UAAU,GAAG;CAC7C,OAAO;AACT;;;;;;;;;AAUA,SAAgB,uBAAuB,EACrC,WACA,QACA,QACA,UACA,eAAe,mBACf,GAAG,SAC2B;CAC9B,MAAM,SAAS,aAAa,KAAA,IAAY,cAAc,WAAW,aAAa;CAE9E,OACE,qBAAC,SAAD,EAAA,UAAA,CACE,oBAAC,gBAAD;EAAgB,SAAA;EACd,UAAA,oBAAC,UAAD;GACE,MAAK;GACL,aAAU;GACV,WAAW,GACT,oFACA,WACA,SACF;GACA,GAAI;GAEH,UAAA;EACK,CAAA;CACM,CAAA,GAChB,qBAAC,gBAAD;EAAgB,cAAY;EAAc,WAAU;EAApD,UAAA,CACE,oBAAC,MAAD;GAAI,WAAU;GACX,UAAA,OAAO,KAAK,UACX,qBAAC,OAAD;IAAuB,WAAU;IAAjC,UAAA,CACE,oBAAC,MAAD;KAAI,WAAU;KAAyB,UAAA,MAAM;IAAU,CAAA,GACvD,oBAAC,MAAD;KAAI,WAAU;KAAe,UAAA,MAAM;IAAU,CAAA,CAC1C;GAHK,GAAA,MAAM,KAGX,CACN;EACC,CAAA,GAGJ,oBAAC,KAAD;GACE,aAAU;GACV,eAAa;GACb,WAAW,GACT,oCACA,WAAW,WAAW,qBAAqB,uBAC7C;GAEC,UAAA,WAAW,QAAQ,MAAM;EACzB,CAAA,CACW;CACT,CAAA,CAAA,EAAA,CAAA;AAEb"}
@@ -0,0 +1,2 @@
1
+ import { AIDisclosure, AIDisclosureProps, AIDisclosureProvenance, AIDisclosureProvenanceProps, DisclosureKind, ProvenanceClaim, disclosureVariants } from "./ai-disclosure.js";
2
+ export { AIDisclosure, type AIDisclosureProps, AIDisclosureProvenance, type AIDisclosureProvenanceProps, type DisclosureKind, type ProvenanceClaim, disclosureVariants };
@@ -0,0 +1,2 @@
1
+ import { AIDisclosure, AIDisclosureProvenance, disclosureVariants } from "./ai-disclosure.js";
2
+ export { AIDisclosure, AIDisclosureProvenance, disclosureVariants };
@@ -0,0 +1,17 @@
1
+ import { defineMeta } from "../../registry/schema.js";
2
+ //#region src/components/ai-disclosure/meta.ts
3
+ const meta = defineMeta({
4
+ name: "ai-disclosure",
5
+ title: "AI Disclosure",
6
+ description: "Tells a reader they are looking at AI, and what is actually known about it.",
7
+ category: "ai",
8
+ status: "stable",
9
+ dependencies: ["class-variance-authority"],
10
+ registryDependencies: ["popover"],
11
+ files: ["ai-disclosure.tsx"],
12
+ a11y: "The text is the disclosure and the icon is decoration, marked aria-hidden — a sparkle alone is a mark a sighted reader may not decode and a screen reader passes over in silence, which is the exact failure this component exists to prevent. Each of the four kinds carries wording of its own, so the distinction between generated, manipulated and human-reviewed survives for anyone who cannot see the styling. The provenance panel states in words whether anyone checked the claims and who, rather than implying it with a tick, and its three states are told apart by their sentences rather than by colour. Scope: this renders the human-visible disclosures of EU AI Act Article 50(1) and 50(4). It cannot satisfy 50(2), which requires machine-readable marking inside the artifact by whoever generated it, and no React component can. Not legal advice."
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-disclosure/meta.ts"],"sourcesContent":["import { defineMeta } from \"@/registry/schema\";\n\nexport const meta = defineMeta({\n name: \"ai-disclosure\",\n title: \"AI Disclosure\",\n description: \"Tells a reader they are looking at AI, and what is actually known about it.\",\n category: \"ai\",\n status: \"stable\",\n dependencies: [\"class-variance-authority\"],\n registryDependencies: [\"popover\"],\n files: [\"ai-disclosure.tsx\"],\n a11y:\n \"The text is the disclosure and the icon is decoration, marked aria-hidden — a sparkle alone \" +\n \"is a mark a sighted reader may not decode and a screen reader passes over in silence, which \" +\n \"is the exact failure this component exists to prevent. Each of the four kinds carries \" +\n \"wording of its own, so the distinction between generated, manipulated and human-reviewed \" +\n \"survives for anyone who cannot see the styling. The provenance panel states in words \" +\n \"whether anyone checked the claims and who, rather than implying it with a tick, and its \" +\n \"three states are told apart by their sentences rather than by colour. \" +\n \"Scope: this renders the human-visible disclosures of EU AI Act Article 50(1) and 50(4). \" +\n \"It cannot satisfy 50(2), which requires machine-readable marking inside the artifact by \" +\n \"whoever generated it, and no React component can. Not legal advice.\",\n});\n"],"mappings":";;AAEA,MAAa,OAAO,WAAW;CAC7B,MAAM;CACN,OAAO;CACP,aAAa;CACb,UAAU;CACV,QAAQ;CACR,cAAc,CAAC,0BAA0B;CACzC,sBAAsB,CAAC,SAAS;CAChC,OAAO,CAAC,mBAAmB;CAC3B,MACE;AAUJ,CAAC"}
@@ -0,0 +1,52 @@
1
+ import { ExtractionField, FieldDecision } from "./extraction-model.js";
2
+ import { ComponentPropsWithRef, ReactNode } from "react";
3
+ //#region src/components/ai-extraction-review/ai-extraction-review.d.ts
4
+ /**
5
+ * The check after extraction: the document on one side, what the model read
6
+ * out of it on the other, and a decision about every field.
7
+ *
8
+ * Every extraction demo shows the filled object and stops. The products that
9
+ * run extraction for a living — invoice capture, KYC onboarding, claims intake —
10
+ * all have this screen, each built from scratch, because a value that cannot
11
+ * be checked can only be trusted, and nobody who is paying an invoice trusts a
12
+ * model that far. No component library ships it.
13
+ *
14
+ * The link is the component. Each field carries where in the source it was
15
+ * read from, quoted in text under the value as well as highlighted in the
16
+ * document, so a reviewer who cannot see the highlight still has the evidence.
17
+ * And a value with no evidence is said outright — "the model supplied this
18
+ * without evidence" — because that is the case a review exists to catch, and
19
+ * the one every filled-object view renders identically to a good value.
20
+ *
21
+ * Evidence is a text offset, not a bounding box. A language model reads text,
22
+ * and a text layer with offsets is what every OCR pipeline already yields.
23
+ * Boxes over a rendered page need page rendering, zoom and geometry, and are a
24
+ * different component; this one does not pretend at them.
25
+ *
26
+ * Decisions are controlled and the component writes nothing. It reports that
27
+ * a field was accepted as proposed, corrected from what was proposed, or
28
+ * rejected — a richer record than a form's submitted values, and the one an
29
+ * audit later needs — and leaves what to do with it to the application.
30
+ */
31
+ interface ExtractionReviewProps extends Omit<ComponentPropsWithRef<"section">, "children"> {
32
+ /** What the model read. Every span indexes into it. */
33
+ source: string;
34
+ /** Names the source — "Invoice 4471.pdf, page 1". */
35
+ sourceLabel?: string;
36
+ /** What is being reviewed, as the heading. */
37
+ heading: string;
38
+ fields: ExtractionField[];
39
+ /** Per-field decisions, keyed by field name. Controlled. */
40
+ decisions?: Record<string, FieldDecision>;
41
+ /** Omit to render the review as a record rather than something to operate. */
42
+ onDecision?: (name: string, decision: FieldDecision) => void;
43
+ /** True while fields are still arriving. Missing values wait instead of reading as not found. */
44
+ streaming?: boolean;
45
+ /** Confidence below this is called low, in words. */
46
+ lowConfidenceBelow?: number;
47
+ children?: ReactNode;
48
+ }
49
+ declare function ExtractionReview({ className, source, sourceLabel, heading, fields, decisions, onDecision, streaming, lowConfidenceBelow, children, ...props }: ExtractionReviewProps): import("react").JSX.Element;
50
+ //#endregion
51
+ export { ExtractionReview, ExtractionReviewProps };
52
+ //# sourceMappingURL=ai-extraction-review.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-extraction-review.d.ts","names":[],"sources":["../../../src/components/ai-extraction-review/ai-extraction-review.tsx"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAsDiB,8BAA8B,KAC7C;;EAIA;;EAEA;;EAEA;EACA,QAAQ;;EAER,YAAY,eAAe;;EAE3B,cAAc,cAAc,UAAU;;EAEtC;;EAEA;EACA,WAAW;;iBAGG,mBACd,WACA,QACA,aACA,SACA,QACA,WACA,YACA,WACA,oBACA,aACG,SACF,wCAAqB,IAAA"}
@@ -0,0 +1,249 @@
1
+ "use client";
2
+ import { disabledStyles, focusRing } from "../../lib/styles.js";
3
+ import { cn } from "../../lib/utils.js";
4
+ import { evidenceOf, isSourced, segmentSource, summarizeReview } from "./extraction-model.js";
5
+ import { jsx, jsxs } from "react/jsx-runtime";
6
+ import { useEffect, useId, useMemo, useRef, useState } from "react";
7
+ //#region src/components/ai-extraction-review/ai-extraction-review.tsx
8
+ function ExtractionReview({ className, source, sourceLabel = "Source", heading, fields, decisions = {}, onDecision, streaming = false, lowConfidenceBelow = .7, children, ...props }) {
9
+ const headingId = useId();
10
+ const [activeField, setActiveField] = useState(null);
11
+ const runs = useMemo(() => segmentSource(source, fields), [source, fields]);
12
+ const summary = useMemo(() => summarizeReview(source, fields, decisions), [
13
+ source,
14
+ fields,
15
+ decisions
16
+ ]);
17
+ return /* @__PURE__ */ jsxs("section", {
18
+ "data-slot": "extraction-review",
19
+ "data-complete": summary.complete || void 0,
20
+ "aria-labelledby": headingId,
21
+ "aria-busy": streaming || void 0,
22
+ className: cn("flex flex-col gap-3", className),
23
+ ...props,
24
+ children: [
25
+ /* @__PURE__ */ jsxs("div", {
26
+ className: "flex flex-wrap items-baseline justify-between gap-2",
27
+ children: [/* @__PURE__ */ jsx("h3", {
28
+ id: headingId,
29
+ className: "text-sm font-medium",
30
+ children: heading
31
+ }), /* @__PURE__ */ jsxs("p", {
32
+ "data-slot": "extraction-review-summary",
33
+ "aria-live": "polite",
34
+ className: "text-xs text-muted-foreground",
35
+ children: [streaming ? "Fields are still arriving" : `${String(summary.reviewed)} of ${String(summary.total)} reviewed`, summary.unsourced.length > 0 ? ` · ${String(summary.unsourced.length)} without evidence` : null]
36
+ })]
37
+ }),
38
+ children,
39
+ /* @__PURE__ */ jsxs("div", {
40
+ className: "grid gap-4 md:grid-cols-2",
41
+ children: [/* @__PURE__ */ jsx(SourcePanel, {
42
+ label: sourceLabel,
43
+ runs,
44
+ active: activeField,
45
+ decisions
46
+ }), /* @__PURE__ */ jsx("ol", {
47
+ "data-slot": "extraction-fields",
48
+ className: "m-0 flex list-none flex-col gap-3 p-0",
49
+ children: fields.map((field) => /* @__PURE__ */ jsx(ReviewField, {
50
+ field,
51
+ source,
52
+ decision: decisions[field.name],
53
+ streaming,
54
+ lowConfidenceBelow,
55
+ active: activeField === field.name,
56
+ onActivate: () => {
57
+ setActiveField(field.name);
58
+ },
59
+ onDecision
60
+ }, field.name))
61
+ })]
62
+ })
63
+ ]
64
+ });
65
+ }
66
+ function SourcePanel({ label, runs, active, decisions }) {
67
+ const ref = useRef(null);
68
+ useEffect(() => {
69
+ if (!active || !ref.current) return;
70
+ ref.current.querySelector(`mark[data-active]`)?.scrollIntoView({ block: "nearest" });
71
+ }, [active]);
72
+ return /* @__PURE__ */ jsx("div", {
73
+ ref,
74
+ "data-slot": "extraction-source",
75
+ role: "region",
76
+ "aria-label": label,
77
+ tabIndex: 0,
78
+ className: cn("max-h-96 overflow-auto rounded-lg border border-border bg-muted/30 p-3 md:sticky md:top-0 md:self-start", focusRing),
79
+ children: runs.length === 0 ? /* @__PURE__ */ jsx("p", {
80
+ className: "text-xs text-muted-foreground",
81
+ children: "No source text."
82
+ }) : /* @__PURE__ */ jsx("pre", {
83
+ className: "m-0 font-sans text-sm leading-relaxed break-words whitespace-pre-wrap",
84
+ children: runs.map((run, index) => run.fields.length === 0 ? /* @__PURE__ */ jsx("span", { children: run.text }, index) : /* @__PURE__ */ jsx("mark", {
85
+ "data-fields": run.fields.join(" "),
86
+ "data-active": active !== null && run.fields.includes(active) ? "" : void 0,
87
+ className: cn("rounded-[2px] text-inherit", run.fields.every((name) => decisions[name]?.kind === "rejected") ? "bg-muted line-through decoration-muted-foreground" : "bg-primary/15", active !== null && run.fields.includes(active) && "bg-primary/30 ring-1 ring-primary ring-inset"),
88
+ children: run.text
89
+ }, index))
90
+ })
91
+ });
92
+ }
93
+ /** Whitespace collapsed and cut short, for quoting evidence under a field. */
94
+ function quote(text, limit = 80) {
95
+ const collapsed = text.replace(/\s+/g, " ").trim();
96
+ return collapsed.length > limit ? `${collapsed.slice(0, limit - 1)}…` : collapsed;
97
+ }
98
+ function ReviewField({ field, source, decision, streaming, lowConfidenceBelow, active, onActivate, onDecision }) {
99
+ const inputId = useId();
100
+ const evidenceId = useId();
101
+ const statusId = useId();
102
+ const confidenceId = useId();
103
+ const [draft, setDraft] = useState(null);
104
+ const proposed = field.value ?? "";
105
+ const decided = decision && decision.kind !== "rejected" ? decision.value : void 0;
106
+ const current = draft ?? decided ?? proposed;
107
+ const edited = current !== proposed;
108
+ const changedSince = decided !== void 0 && current !== decided;
109
+ const arriving = streaming && field.value === void 0;
110
+ const sourced = isSourced(source, field);
111
+ const evidence = sourced ? evidenceOf(source, field.span) : "";
112
+ const unsourced = !arriving && field.value !== void 0 && !sourced;
113
+ const accept = () => {
114
+ onDecision?.(field.name, edited ? {
115
+ kind: "corrected",
116
+ value: current,
117
+ proposed
118
+ } : {
119
+ kind: "accepted",
120
+ value: current
121
+ });
122
+ };
123
+ const onKeyDown = (event) => {
124
+ if (event.key !== "Enter") return;
125
+ if (event.nativeEvent.isComposing) return;
126
+ if (field.multiline && !(event.ctrlKey || event.metaKey)) return;
127
+ event.preventDefault();
128
+ accept();
129
+ };
130
+ const status = arriving ? "Arriving" : !decision ? "Not reviewed" : decision.kind === "rejected" ? "Rejected" : changedSince ? `Changed since it was ${decision.kind}` : decision.kind === "accepted" ? "Accepted" : decision.proposed ? `Corrected from “${quote(decision.proposed, 40)}”` : "Supplied by the reviewer";
131
+ const low = field.confidence !== void 0 && field.confidence < lowConfidenceBelow;
132
+ const percent = field.confidence === void 0 ? null : new Intl.NumberFormat(void 0, {
133
+ style: "percent",
134
+ maximumFractionDigits: 0
135
+ }).format(field.confidence);
136
+ const describedBy = [
137
+ evidenceId,
138
+ statusId,
139
+ percent !== null ? confidenceId : null
140
+ ].filter(Boolean).join(" ");
141
+ const controlClass = cn("w-full rounded-md border border-input bg-background px-2 py-1.5 text-sm", edited && "border-warning", focusRing);
142
+ return /* @__PURE__ */ jsxs("li", {
143
+ "data-slot": "extraction-field",
144
+ "data-field": field.name,
145
+ "data-state": arriving ? "arriving" : decision?.kind ?? "undecided",
146
+ "data-active": active || void 0,
147
+ "data-unsourced": unsourced || void 0,
148
+ onFocus: onActivate,
149
+ className: cn("flex flex-col gap-1 rounded-lg border p-3", active ? "border-primary" : "border-border", decision?.kind === "rejected" && "bg-muted/40"),
150
+ children: [
151
+ /* @__PURE__ */ jsxs("div", {
152
+ className: "flex flex-wrap items-baseline justify-between gap-2",
153
+ children: [onDecision && !arriving ? /* @__PURE__ */ jsx("label", {
154
+ htmlFor: inputId,
155
+ className: "text-xs font-medium text-muted-foreground",
156
+ children: field.label
157
+ }) : /* @__PURE__ */ jsx("span", {
158
+ className: "text-xs font-medium text-muted-foreground",
159
+ children: field.label
160
+ }), /* @__PURE__ */ jsx("span", {
161
+ id: statusId,
162
+ "data-slot": "extraction-field-status",
163
+ className: cn("text-xs", decision?.kind === "rejected" ? "text-destructive" : "text-muted-foreground", changedSince && "text-warning"),
164
+ children: status
165
+ })]
166
+ }),
167
+ arriving ? /* @__PURE__ */ jsx("span", {
168
+ "aria-hidden": "true",
169
+ className: "block h-8 w-full animate-pulse-soft rounded-md bg-muted"
170
+ }) : !onDecision ? /* @__PURE__ */ jsx("p", {
171
+ className: "rounded-md bg-muted px-2 py-1.5 text-sm break-words",
172
+ children: current || /* @__PURE__ */ jsx("span", {
173
+ className: "text-muted-foreground",
174
+ children: "Empty"
175
+ })
176
+ }) : field.multiline ? /* @__PURE__ */ jsx("textarea", {
177
+ id: inputId,
178
+ rows: 3,
179
+ value: current,
180
+ "aria-describedby": describedBy,
181
+ onChange: (event) => {
182
+ setDraft(event.target.value);
183
+ },
184
+ onKeyDown,
185
+ className: cn(controlClass, "resize-none")
186
+ }) : /* @__PURE__ */ jsx("input", {
187
+ id: inputId,
188
+ type: "text",
189
+ value: current,
190
+ "aria-describedby": describedBy,
191
+ onChange: (event) => {
192
+ setDraft(event.target.value);
193
+ },
194
+ onKeyDown,
195
+ className: controlClass
196
+ }),
197
+ /* @__PURE__ */ jsx("p", {
198
+ id: evidenceId,
199
+ "data-slot": "extraction-field-evidence",
200
+ className: cn("text-xs", unsourced ? "text-warning" : "text-muted-foreground"),
201
+ children: arriving ? "Waiting for the model" : sourced ? `In the source: “${quote(evidence)}”` : field.value === void 0 ? "The model found nothing for this field." : "Not in the source — the model supplied this without evidence."
202
+ }),
203
+ percent !== null ? /* @__PURE__ */ jsx("p", {
204
+ id: confidenceId,
205
+ "data-slot": "extraction-field-confidence",
206
+ "data-low": low || void 0,
207
+ className: cn("text-xs", low ? "text-warning" : "text-muted-foreground"),
208
+ children: low ? `Low confidence, ${percent} — worth checking` : `${percent} confidence`
209
+ }) : null,
210
+ onDecision && !arriving ? /* @__PURE__ */ jsxs("div", {
211
+ className: "mt-1 flex flex-wrap items-center gap-2",
212
+ children: [
213
+ /* @__PURE__ */ jsx(FieldButton, {
214
+ variant: "primary",
215
+ "aria-label": `${edited ? "Accept correction" : "Accept"} — ${field.label}`,
216
+ "aria-pressed": decision !== void 0 && decision.kind !== "rejected" && !changedSince,
217
+ onClick: accept,
218
+ children: edited ? "Accept correction" : "Accept"
219
+ }),
220
+ /* @__PURE__ */ jsx(FieldButton, {
221
+ "aria-label": `Reject — ${field.label}`,
222
+ "aria-pressed": decision?.kind === "rejected",
223
+ onClick: () => {
224
+ onDecision(field.name, { kind: "rejected" });
225
+ },
226
+ children: "Reject"
227
+ }),
228
+ sourced ? /* @__PURE__ */ jsx(FieldButton, {
229
+ "aria-label": `Show in source — ${field.label}`,
230
+ className: "ml-auto",
231
+ onClick: onActivate,
232
+ children: "Show in source"
233
+ }) : null
234
+ ]
235
+ }) : null
236
+ ]
237
+ });
238
+ }
239
+ function FieldButton({ className, variant = "default", ...props }) {
240
+ return /* @__PURE__ */ jsx("button", {
241
+ type: "button",
242
+ 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 aria-pressed:bg-primary-active", variant === "default" && "border-input bg-background hover:bg-accent hover:text-accent-foreground aria-pressed:bg-secondary aria-pressed:text-secondary-foreground", focusRing, disabledStyles, className),
243
+ ...props
244
+ });
245
+ }
246
+ //#endregion
247
+ export { ExtractionReview };
248
+
249
+ //# sourceMappingURL=ai-extraction-review.js.map