@dowel-ui/react 0.4.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -5
- package/dist/blocks/admin-dashboard/meta.js +31 -0
- package/dist/blocks/admin-dashboard/meta.js.map +1 -0
- package/dist/blocks/agent-console/meta.js +27 -0
- package/dist/blocks/agent-console/meta.js.map +1 -0
- package/dist/blocks/ai-dashboard/meta.js +25 -0
- package/dist/blocks/ai-dashboard/meta.js.map +1 -0
- package/dist/blocks/ai-workspace/meta.js +33 -0
- package/dist/blocks/ai-workspace/meta.js.map +1 -0
- package/dist/blocks/analytics/meta.js +24 -0
- package/dist/blocks/analytics/meta.js.map +1 -0
- package/dist/blocks/billing/meta.js +25 -0
- package/dist/blocks/billing/meta.js.map +1 -0
- package/dist/blocks/command-center/meta.js +28 -0
- package/dist/blocks/command-center/meta.js.map +1 -0
- package/dist/blocks/crm/meta.js +32 -0
- package/dist/blocks/crm/meta.js.map +1 -0
- package/dist/blocks/onboarding/meta.js +23 -0
- package/dist/blocks/onboarding/meta.js.map +1 -0
- package/dist/components/accordion/accordion.js +1 -1
- package/dist/components/accordion/accordion.js.map +1 -1
- package/dist/components/activity-feed/activity-feed.js +1 -1
- package/dist/components/activity-feed/activity-feed.js.map +1 -1
- package/dist/components/ai-agent-plan/ai-agent-plan.js +1 -1
- package/dist/components/ai-agent-plan/ai-agent-plan.js.map +1 -1
- package/dist/components/ai-extraction-review/ai-extraction-review.d.ts +52 -0
- package/dist/components/ai-extraction-review/ai-extraction-review.d.ts.map +1 -0
- package/dist/components/ai-extraction-review/ai-extraction-review.js +249 -0
- package/dist/components/ai-extraction-review/ai-extraction-review.js.map +1 -0
- package/dist/components/ai-extraction-review/extraction-model.d.ts +94 -0
- package/dist/components/ai-extraction-review/extraction-model.d.ts.map +1 -0
- package/dist/components/ai-extraction-review/extraction-model.js +91 -0
- package/dist/components/ai-extraction-review/extraction-model.js.map +1 -0
- package/dist/components/ai-extraction-review/index.d.ts +3 -0
- package/dist/components/ai-extraction-review/index.js +3 -0
- package/dist/components/ai-extraction-review/meta.js +17 -0
- package/dist/components/ai-extraction-review/meta.js.map +1 -0
- package/dist/components/ai-prompt-input/ai-prompt-input.js +1 -1
- package/dist/components/ai-prompt-input/ai-prompt-input.js.map +1 -1
- package/dist/components/ai-reasoning/ai-reasoning.js +1 -1
- package/dist/components/ai-reasoning/ai-reasoning.js.map +1 -1
- package/dist/components/ai-response/ai-response.js +2 -2
- package/dist/components/ai-response/ai-response.js.map +1 -1
- package/dist/components/ai-suggested-value/ai-suggested-value.d.ts +72 -0
- package/dist/components/ai-suggested-value/ai-suggested-value.d.ts.map +1 -0
- package/dist/components/ai-suggested-value/ai-suggested-value.js +126 -0
- package/dist/components/ai-suggested-value/ai-suggested-value.js.map +1 -0
- package/dist/components/ai-suggested-value/index.d.ts +2 -0
- package/dist/components/ai-suggested-value/index.js +2 -0
- package/dist/components/ai-suggested-value/meta.js +17 -0
- package/dist/components/ai-suggested-value/meta.js.map +1 -0
- package/dist/components/ai-tool/ai-tool.js +2 -2
- package/dist/components/ai-tool/ai-tool.js.map +1 -1
- package/dist/components/breadcrumb/breadcrumb.d.ts +52 -0
- package/dist/components/breadcrumb/breadcrumb.d.ts.map +1 -0
- package/dist/components/breadcrumb/breadcrumb.js +141 -0
- package/dist/components/breadcrumb/breadcrumb.js.map +1 -0
- package/dist/components/breadcrumb/index.d.ts +2 -0
- package/dist/components/breadcrumb/index.js +2 -0
- package/dist/components/breadcrumb/meta.js +17 -0
- package/dist/components/breadcrumb/meta.js.map +1 -0
- package/dist/components/calendar/calendar.js +2 -2
- package/dist/components/calendar/calendar.js.map +1 -1
- package/dist/components/code-block/code-block.js +1 -1
- package/dist/components/code-block/code-block.js.map +1 -1
- package/dist/components/collapsible/collapsible.d.ts +30 -0
- package/dist/components/collapsible/collapsible.d.ts.map +1 -0
- package/dist/components/collapsible/collapsible.js +38 -0
- package/dist/components/collapsible/collapsible.js.map +1 -0
- package/dist/components/collapsible/index.d.ts +2 -0
- package/dist/components/collapsible/index.js +2 -0
- package/dist/components/collapsible/meta.js +17 -0
- package/dist/components/collapsible/meta.js.map +1 -0
- package/dist/components/combobox/combobox.js +2 -2
- package/dist/components/combobox/combobox.js.map +1 -1
- package/dist/components/command/command.js +1 -1
- package/dist/components/command/command.js.map +1 -1
- package/dist/components/confirm-typed/confirm-typed.d.ts +47 -0
- package/dist/components/confirm-typed/confirm-typed.d.ts.map +1 -0
- package/dist/components/confirm-typed/confirm-typed.js +106 -0
- package/dist/components/confirm-typed/confirm-typed.js.map +1 -0
- package/dist/components/confirm-typed/index.d.ts +2 -0
- package/dist/components/confirm-typed/index.js +2 -0
- package/dist/components/confirm-typed/meta.js +17 -0
- package/dist/components/confirm-typed/meta.js.map +1 -0
- package/dist/components/cron-editor/cron-editor.d.ts +36 -0
- package/dist/components/cron-editor/cron-editor.d.ts.map +1 -0
- package/dist/components/cron-editor/cron-editor.js +493 -0
- package/dist/components/cron-editor/cron-editor.js.map +1 -0
- package/dist/components/cron-editor/cron-expression.d.ts +82 -0
- package/dist/components/cron-editor/cron-expression.d.ts.map +1 -0
- package/dist/components/cron-editor/cron-expression.js +352 -0
- package/dist/components/cron-editor/cron-expression.js.map +1 -0
- package/dist/components/cron-editor/index.d.ts +3 -0
- package/dist/components/cron-editor/index.js +3 -0
- package/dist/components/cron-editor/meta.js +17 -0
- package/dist/components/cron-editor/meta.js.map +1 -0
- package/dist/components/data-table/data-table.d.ts.map +1 -1
- package/dist/components/data-table/data-table.js +4 -1
- package/dist/components/data-table/data-table.js.map +1 -1
- package/dist/components/dialog/dialog.js +2 -2
- package/dist/components/dialog/dialog.js.map +1 -1
- package/dist/components/diff-viewer/diff-viewer.js +4 -4
- package/dist/components/diff-viewer/diff-viewer.js.map +1 -1
- package/dist/components/direction/direction.d.ts +35 -0
- package/dist/components/direction/direction.d.ts.map +1 -0
- package/dist/components/direction/direction.js +14 -0
- package/dist/components/direction/direction.js.map +1 -0
- package/dist/components/direction/index.d.ts +2 -0
- package/dist/components/direction/index.js +2 -0
- package/dist/components/direction/meta.js +17 -0
- package/dist/components/direction/meta.js.map +1 -0
- package/dist/components/dns-record/dns-record.d.ts +61 -0
- package/dist/components/dns-record/dns-record.d.ts.map +1 -0
- package/dist/components/dns-record/dns-record.js +227 -0
- package/dist/components/dns-record/dns-record.js.map +1 -0
- package/dist/components/dns-record/index.d.ts +2 -0
- package/dist/components/dns-record/index.js +2 -0
- package/dist/components/dns-record/meta.js +17 -0
- package/dist/components/dns-record/meta.js.map +1 -0
- package/dist/components/drawer/drawer.js +1 -1
- package/dist/components/drawer/drawer.js.map +1 -1
- package/dist/components/dropdown-menu/dropdown-menu.d.ts.map +1 -1
- package/dist/components/dropdown-menu/dropdown-menu.js +9 -8
- package/dist/components/dropdown-menu/dropdown-menu.js.map +1 -1
- package/dist/components/input/input.js +1 -1
- package/dist/components/input/input.js.map +1 -1
- package/dist/components/log-viewer/log-viewer.js +2 -2
- package/dist/components/log-viewer/log-viewer.js.map +1 -1
- package/dist/components/meter/meter.js +1 -1
- package/dist/components/meter/meter.js.map +1 -1
- package/dist/components/pagination/pagination.d.ts.map +1 -1
- package/dist/components/pagination/pagination.js +3 -2
- package/dist/components/pagination/pagination.js.map +1 -1
- package/dist/components/permission-matrix/index.d.ts +3 -0
- package/dist/components/permission-matrix/index.js +3 -0
- package/dist/components/permission-matrix/meta.js +17 -0
- package/dist/components/permission-matrix/meta.js.map +1 -0
- package/dist/components/permission-matrix/permission-matrix.d.ts +46 -0
- package/dist/components/permission-matrix/permission-matrix.d.ts.map +1 -0
- package/dist/components/permission-matrix/permission-matrix.js +199 -0
- package/dist/components/permission-matrix/permission-matrix.js.map +1 -0
- package/dist/components/permission-matrix/permission-model.d.ts +53 -0
- package/dist/components/permission-matrix/permission-model.d.ts.map +1 -0
- package/dist/components/permission-matrix/permission-model.js +64 -0
- package/dist/components/permission-matrix/permission-model.js.map +1 -0
- package/dist/components/record-diff/record-diff.js +4 -4
- package/dist/components/record-diff/record-diff.js.map +1 -1
- package/dist/components/secret-field/index.d.ts +2 -0
- package/dist/components/secret-field/index.js +2 -0
- package/dist/components/secret-field/meta.js +17 -0
- package/dist/components/secret-field/meta.js.map +1 -0
- package/dist/components/secret-field/secret-field.d.ts +58 -0
- package/dist/components/secret-field/secret-field.d.ts.map +1 -0
- package/dist/components/secret-field/secret-field.js +168 -0
- package/dist/components/secret-field/secret-field.js.map +1 -0
- package/dist/components/select/select.js +2 -2
- package/dist/components/select/select.js.map +1 -1
- package/dist/components/session-expiry/index.d.ts +2 -0
- package/dist/components/session-expiry/index.js +2 -0
- package/dist/components/session-expiry/meta.js +17 -0
- package/dist/components/session-expiry/meta.js.map +1 -0
- package/dist/components/session-expiry/session-expiry.d.ts +64 -0
- package/dist/components/session-expiry/session-expiry.d.ts.map +1 -0
- package/dist/components/session-expiry/session-expiry.js +185 -0
- package/dist/components/session-expiry/session-expiry.js.map +1 -0
- package/dist/components/sheet/sheet.d.ts.map +1 -1
- package/dist/components/sheet/sheet.js +2 -2
- package/dist/components/sheet/sheet.js.map +1 -1
- package/dist/components/shortcut-recorder/index.d.ts +3 -0
- package/dist/components/shortcut-recorder/index.js +3 -0
- package/dist/components/shortcut-recorder/meta.js +17 -0
- package/dist/components/shortcut-recorder/meta.js.map +1 -0
- package/dist/components/shortcut-recorder/shortcut-model.d.ts +67 -0
- package/dist/components/shortcut-recorder/shortcut-model.d.ts.map +1 -0
- package/dist/components/shortcut-recorder/shortcut-model.js +168 -0
- package/dist/components/shortcut-recorder/shortcut-model.js.map +1 -0
- package/dist/components/shortcut-recorder/shortcut-recorder.d.ts +47 -0
- package/dist/components/shortcut-recorder/shortcut-recorder.d.ts.map +1 -0
- package/dist/components/shortcut-recorder/shortcut-recorder.js +212 -0
- package/dist/components/shortcut-recorder/shortcut-recorder.js.map +1 -0
- package/dist/components/sidebar/index.d.ts +2 -0
- package/dist/components/sidebar/index.js +2 -0
- package/dist/components/sidebar/meta.js +17 -0
- package/dist/components/sidebar/meta.js.map +1 -0
- package/dist/components/sidebar/sidebar.d.ts +69 -0
- package/dist/components/sidebar/sidebar.d.ts.map +1 -0
- package/dist/components/sidebar/sidebar.js +256 -0
- package/dist/components/sidebar/sidebar.js.map +1 -0
- package/dist/components/sync-status/index.d.ts +2 -0
- package/dist/components/sync-status/index.js +2 -0
- package/dist/components/sync-status/meta.js +17 -0
- package/dist/components/sync-status/meta.js.map +1 -0
- package/dist/components/sync-status/sync-status.d.ts +65 -0
- package/dist/components/sync-status/sync-status.d.ts.map +1 -0
- package/dist/components/sync-status/sync-status.js +103 -0
- package/dist/components/sync-status/sync-status.js.map +1 -0
- package/dist/components/table/table.js +2 -2
- package/dist/components/table/table.js.map +1 -1
- package/dist/components/tags-input/tags-input.js +1 -1
- package/dist/components/tags-input/tags-input.js.map +1 -1
- package/dist/components/textarea/index.d.ts +2 -0
- package/dist/components/textarea/index.js +2 -0
- package/dist/components/textarea/meta.js +17 -0
- package/dist/components/textarea/meta.js.map +1 -0
- package/dist/components/textarea/textarea.d.ts +43 -0
- package/dist/components/textarea/textarea.d.ts.map +1 -0
- package/dist/components/textarea/textarea.js +119 -0
- package/dist/components/textarea/textarea.js.map +1 -0
- package/dist/components/time-range-picker/time-range-picker.js +3 -3
- package/dist/components/time-range-picker/time-range-picker.js.map +1 -1
- package/dist/components/toast/toast.d.ts.map +1 -1
- package/dist/components/toast/toast.js +2 -2
- package/dist/components/toast/toast.js.map +1 -1
- package/dist/index.d.ts +36 -2
- package/dist/index.js +22 -3
- package/dist/lib/styles.d.ts +16 -1
- package/dist/lib/styles.d.ts.map +1 -1
- package/dist/lib/styles.js +16 -1
- package/dist/lib/styles.js.map +1 -1
- package/dist/registry/blocks.d.ts.map +1 -1
- package/dist/registry/blocks.js +27 -9
- package/dist/registry/blocks.js.map +1 -1
- package/dist/registry/components.d.ts.map +1 -1
- package/dist/registry/components.js +84 -54
- package/dist/registry/components.js.map +1 -1
- package/dist/registry/schema.d.ts +8 -0
- package/dist/registry/schema.d.ts.map +1 -1
- package/dist/registry/schema.js.map +1 -1
- package/package.json +5 -4
- package/src/components/accordion/accordion.tsx +1 -1
- package/src/components/activity-feed/activity-feed.tsx +1 -1
- package/src/components/ai-agent-plan/ai-agent-plan.tsx +0 -0
- package/src/components/ai-extraction-review/ai-extraction-review.tsx +492 -0
- package/src/components/ai-extraction-review/extraction-model.ts +159 -0
- package/src/components/ai-extraction-review/index.ts +13 -0
- package/src/components/ai-extraction-review/meta.ts +24 -0
- package/src/components/ai-prompt-input/ai-prompt-input.tsx +1 -1
- package/src/components/ai-reasoning/ai-reasoning.tsx +1 -1
- package/src/components/ai-response/ai-response.tsx +2 -2
- package/src/components/ai-suggested-value/ai-suggested-value.tsx +295 -0
- package/src/components/ai-suggested-value/index.ts +6 -0
- package/src/components/ai-suggested-value/meta.ts +24 -0
- package/src/components/ai-tool/ai-tool.tsx +2 -2
- package/src/components/breadcrumb/breadcrumb.tsx +157 -0
- package/src/components/breadcrumb/index.ts +10 -0
- package/src/components/breadcrumb/meta.ts +19 -0
- package/src/components/calendar/calendar.tsx +2 -2
- package/src/components/code-block/code-block.tsx +1 -1
- package/src/components/collapsible/collapsible.tsx +63 -0
- package/src/components/collapsible/index.ts +6 -0
- package/src/components/collapsible/meta.ts +16 -0
- package/src/components/combobox/combobox.tsx +2 -2
- package/src/components/command/command.tsx +1 -1
- package/src/components/confirm-typed/confirm-typed.tsx +191 -0
- package/src/components/confirm-typed/index.ts +1 -0
- package/src/components/confirm-typed/meta.ts +22 -0
- package/src/components/cron-editor/cron-editor.tsx +676 -0
- package/src/components/cron-editor/cron-expression.ts +552 -0
- package/src/components/cron-editor/index.ts +23 -0
- package/src/components/cron-editor/meta.ts +23 -0
- package/src/components/data-table/data-table.tsx +14 -3
- package/src/components/dialog/dialog.tsx +2 -2
- package/src/components/diff-viewer/diff-viewer.tsx +4 -4
- package/src/components/direction/direction.tsx +38 -0
- package/src/components/direction/index.ts +1 -0
- package/src/components/direction/meta.ts +19 -0
- package/src/components/dns-record/dns-record.tsx +315 -0
- package/src/components/dns-record/index.ts +7 -0
- package/src/components/dns-record/meta.ts +21 -0
- package/src/components/drawer/drawer.tsx +1 -1
- package/src/components/dropdown-menu/dropdown-menu.tsx +14 -8
- package/src/components/input/input.tsx +1 -1
- package/src/components/log-viewer/log-viewer.tsx +2 -2
- package/src/components/meter/meter.tsx +1 -1
- package/src/components/pagination/pagination.tsx +13 -2
- package/src/components/permission-matrix/index.ts +14 -0
- package/src/components/permission-matrix/meta.ts +23 -0
- package/src/components/permission-matrix/permission-matrix.tsx +368 -0
- package/src/components/permission-matrix/permission-model.ts +120 -0
- package/src/components/record-diff/record-diff.tsx +4 -4
- package/src/components/secret-field/index.ts +6 -0
- package/src/components/secret-field/meta.ts +21 -0
- package/src/components/secret-field/secret-field.tsx +284 -0
- package/src/components/select/select.tsx +2 -2
- package/src/components/session-expiry/index.ts +9 -0
- package/src/components/session-expiry/meta.ts +23 -0
- package/src/components/session-expiry/session-expiry.tsx +321 -0
- package/src/components/sheet/sheet.tsx +8 -4
- package/src/components/shortcut-recorder/index.ts +19 -0
- package/src/components/shortcut-recorder/meta.ts +22 -0
- package/src/components/shortcut-recorder/shortcut-model.ts +204 -0
- package/src/components/shortcut-recorder/shortcut-recorder.tsx +336 -0
- package/src/components/sidebar/index.ts +19 -0
- package/src/components/sidebar/meta.ts +25 -0
- package/src/components/sidebar/sidebar.tsx +374 -0
- package/src/components/sync-status/index.ts +9 -0
- package/src/components/sync-status/meta.ts +21 -0
- package/src/components/sync-status/sync-status.tsx +217 -0
- package/src/components/table/table.tsx +3 -3
- package/src/components/tags-input/tags-input.tsx +1 -1
- package/src/components/textarea/index.ts +1 -0
- package/src/components/textarea/meta.ts +21 -0
- package/src/components/textarea/textarea.tsx +210 -0
- package/src/components/time-range-picker/time-range-picker.tsx +3 -3
- package/src/components/toast/toast.tsx +14 -6
- package/src/index.ts +15 -0
- package/src/lib/styles.ts +16 -0
- package/src/registry/blocks.ts +18 -0
- package/src/registry/components.ts +30 -0
- package/src/registry/schema.ts +8 -0
- package/src/blocks/admin-users/admin-users.tsx +0 -251
- package/src/blocks/admin-users/index.ts +0 -1
- package/src/blocks/admin-users/meta.ts +0 -27
- package/src/blocks/ai-chat/ai-chat.tsx +0 -280
- package/src/blocks/ai-chat/index.ts +0 -7
- package/src/blocks/ai-chat/meta.ts +0 -32
- package/src/blocks/dashboard/dashboard.tsx +0 -192
- package/src/blocks/dashboard/index.ts +0 -6
- package/src/blocks/dashboard/meta.ts +0 -18
- package/src/blocks/forgot-password/forgot-password.tsx +0 -158
- package/src/blocks/forgot-password/index.ts +0 -1
- package/src/blocks/forgot-password/meta.ts +0 -18
- package/src/blocks/login/index.ts +0 -1
- package/src/blocks/login/login.tsx +0 -178
- package/src/blocks/login/meta.ts +0 -18
- package/src/blocks/pricing/index.ts +0 -1
- package/src/blocks/pricing/meta.ts +0 -18
- package/src/blocks/pricing/pricing.tsx +0 -176
- package/src/blocks/settings/index.ts +0 -6
- package/src/blocks/settings/meta.ts +0 -29
- package/src/blocks/settings/settings.tsx +0 -317
- package/src/blocks/signup/index.ts +0 -1
- package/src/blocks/signup/meta.ts +0 -18
- package/src/blocks/signup/signup.tsx +0 -240
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The model behind a review of extracted fields: where each value was read
|
|
3
|
+
* from, what the reviewer decided about it, and how far through the review
|
|
4
|
+
* they are.
|
|
5
|
+
*
|
|
6
|
+
* Pure, so overlap handling and the completion rule are tested without
|
|
7
|
+
* rendering anything — and so an application can answer "is this review
|
|
8
|
+
* finished" on the server from the same decisions the component reports,
|
|
9
|
+
* rather than trusting a flag the client sent.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export interface SourceSpan {
|
|
13
|
+
/** Offset into the source text in UTF-16 code units. Inclusive. */
|
|
14
|
+
start: number;
|
|
15
|
+
/** Exclusive. */
|
|
16
|
+
end: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface ExtractionField {
|
|
20
|
+
/** Key in the extracted object. */
|
|
21
|
+
name: string;
|
|
22
|
+
label: string;
|
|
23
|
+
/**
|
|
24
|
+
* What the model extracted. Absent while streaming means it has not arrived;
|
|
25
|
+
* absent once streaming has ended means the model found nothing.
|
|
26
|
+
*/
|
|
27
|
+
value?: string;
|
|
28
|
+
/**
|
|
29
|
+
* Where in the source the value was read from. Absent means the model
|
|
30
|
+
* supplied the value without evidence, which the review says outright.
|
|
31
|
+
*/
|
|
32
|
+
span?: SourceSpan;
|
|
33
|
+
/** 0 to 1. Shown as a number, never only as a colour. */
|
|
34
|
+
confidence?: number;
|
|
35
|
+
/** A textarea instead of a single line. For addresses and descriptions. */
|
|
36
|
+
multiline?: boolean;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export type FieldDecision =
|
|
40
|
+
/** The model's value, taken as it was. */
|
|
41
|
+
| { kind: "accepted"; value: string }
|
|
42
|
+
/** The reviewer's value, with the model's kept so a record can tell them apart. */
|
|
43
|
+
| { kind: "corrected"; value: string; proposed: string }
|
|
44
|
+
/** Wrong and not corrected — the field does not apply, or cannot be known. */
|
|
45
|
+
| { kind: "rejected" };
|
|
46
|
+
|
|
47
|
+
/** A run of source text and the fields whose evidence covers it. */
|
|
48
|
+
export interface SourceRun {
|
|
49
|
+
text: string;
|
|
50
|
+
/** Field names, in declaration order. Empty for text nobody cites. */
|
|
51
|
+
fields: string[];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* A span brought within the source, or null if nothing of it is.
|
|
56
|
+
*
|
|
57
|
+
* Offsets come from a model or an OCR layer and are wrong often enough that
|
|
58
|
+
* refusing to render on a bad one would leave the whole review blank. A span
|
|
59
|
+
* past the end is clamped; an inverted or empty one is treated as no evidence,
|
|
60
|
+
* which is what it is.
|
|
61
|
+
*/
|
|
62
|
+
export function clampSpan(span: SourceSpan, length: number): SourceSpan | null {
|
|
63
|
+
const start = Math.max(0, Math.min(span.start, length));
|
|
64
|
+
const end = Math.max(0, Math.min(span.end, length));
|
|
65
|
+
return end > start ? { start, end } : null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** The text a span points at, or an empty string if it points at nothing. */
|
|
69
|
+
export function evidenceOf(source: string, span: SourceSpan | undefined): string {
|
|
70
|
+
if (!span) return "";
|
|
71
|
+
const clamped = clampSpan(span, source.length);
|
|
72
|
+
return clamped ? source.slice(clamped.start, clamped.end) : "";
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Whether a field's value can be checked against the source at all. */
|
|
76
|
+
export function isSourced(source: string, field: ExtractionField): boolean {
|
|
77
|
+
return field.span !== undefined && clampSpan(field.span, source.length) !== null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Cuts the source into runs at every span boundary, so overlapping evidence —
|
|
82
|
+
* a total that sits inside the line that contains it — renders as nested
|
|
83
|
+
* coverage rather than as two marks fighting over the same characters.
|
|
84
|
+
*/
|
|
85
|
+
export function segmentSource(source: string, fields: ExtractionField[]): SourceRun[] {
|
|
86
|
+
const spans = fields.flatMap((field) => {
|
|
87
|
+
const clamped = field.span ? clampSpan(field.span, source.length) : null;
|
|
88
|
+
return clamped ? [{ name: field.name, ...clamped }] : [];
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
if (source.length === 0) return [];
|
|
92
|
+
if (spans.length === 0) return [{ text: source, fields: [] }];
|
|
93
|
+
|
|
94
|
+
const cuts = new Set<number>([0, source.length]);
|
|
95
|
+
for (const span of spans) {
|
|
96
|
+
cuts.add(span.start);
|
|
97
|
+
cuts.add(span.end);
|
|
98
|
+
}
|
|
99
|
+
const points = [...cuts].sort((a, b) => a - b);
|
|
100
|
+
|
|
101
|
+
const runs: SourceRun[] = [];
|
|
102
|
+
for (let index = 0; index < points.length - 1; index += 1) {
|
|
103
|
+
const start = points[index] ?? 0;
|
|
104
|
+
const end = points[index + 1] ?? start;
|
|
105
|
+
if (end <= start) continue;
|
|
106
|
+
runs.push({
|
|
107
|
+
text: source.slice(start, end),
|
|
108
|
+
fields: spans.filter((span) => span.start <= start && span.end >= end).map((s) => s.name),
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
return runs;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export interface ReviewSummary {
|
|
115
|
+
total: number;
|
|
116
|
+
reviewed: number;
|
|
117
|
+
accepted: number;
|
|
118
|
+
corrected: number;
|
|
119
|
+
rejected: number;
|
|
120
|
+
/** Fields with a value the model could not point to in the source. */
|
|
121
|
+
unsourced: string[];
|
|
122
|
+
/** Fields still awaiting a decision. */
|
|
123
|
+
remaining: string[];
|
|
124
|
+
/** Every field has a decision. The only completion signal worth trusting. */
|
|
125
|
+
complete: boolean;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function summarizeReview(
|
|
129
|
+
source: string,
|
|
130
|
+
fields: ExtractionField[],
|
|
131
|
+
decisions: Record<string, FieldDecision> = {},
|
|
132
|
+
): ReviewSummary {
|
|
133
|
+
const summary: ReviewSummary = {
|
|
134
|
+
total: fields.length,
|
|
135
|
+
reviewed: 0,
|
|
136
|
+
accepted: 0,
|
|
137
|
+
corrected: 0,
|
|
138
|
+
rejected: 0,
|
|
139
|
+
unsourced: [],
|
|
140
|
+
remaining: [],
|
|
141
|
+
complete: false,
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
for (const field of fields) {
|
|
145
|
+
if (field.value !== undefined && !isSourced(source, field)) {
|
|
146
|
+
summary.unsourced.push(field.name);
|
|
147
|
+
}
|
|
148
|
+
const decision = decisions[field.name];
|
|
149
|
+
if (!decision) {
|
|
150
|
+
summary.remaining.push(field.name);
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
summary.reviewed += 1;
|
|
154
|
+
summary[decision.kind] += 1;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
summary.complete = fields.length > 0 && summary.remaining.length === 0;
|
|
158
|
+
return summary;
|
|
159
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export { ExtractionReview, type ExtractionReviewProps } from "./ai-extraction-review";
|
|
2
|
+
export {
|
|
3
|
+
clampSpan,
|
|
4
|
+
evidenceOf,
|
|
5
|
+
isSourced,
|
|
6
|
+
segmentSource,
|
|
7
|
+
summarizeReview,
|
|
8
|
+
type ExtractionField,
|
|
9
|
+
type FieldDecision,
|
|
10
|
+
type ReviewSummary,
|
|
11
|
+
type SourceRun,
|
|
12
|
+
type SourceSpan,
|
|
13
|
+
} from "./extraction-model";
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { defineMeta } from "@/registry/schema";
|
|
2
|
+
|
|
3
|
+
export const meta = defineMeta({
|
|
4
|
+
name: "ai-extraction-review",
|
|
5
|
+
title: "AI Extraction Review",
|
|
6
|
+
description:
|
|
7
|
+
"Check extracted fields against the document they came from, and decide each one.",
|
|
8
|
+
category: "ai",
|
|
9
|
+
status: "stable",
|
|
10
|
+
dependencies: [],
|
|
11
|
+
registryDependencies: [],
|
|
12
|
+
files: ["extraction-model.ts", "ai-extraction-review.tsx"],
|
|
13
|
+
a11y:
|
|
14
|
+
"Every field quotes its evidence in text under the value, so a reviewer who cannot see the " +
|
|
15
|
+
"highlight in the source still has what the model read. A value with no evidence is said in " +
|
|
16
|
+
"words — never left to look like a good one. The source is a focusable named region because " +
|
|
17
|
+
"it scrolls, and its highlights are mark elements with nothing spliced into the text, since a " +
|
|
18
|
+
"document read aloud with field names inserted is no longer the document. Focus anywhere in a " +
|
|
19
|
+
"field brings its evidence into view without moving focus, so a keyboard user is shown the " +
|
|
20
|
+
"source rather than sent into it. Each control's name carries the field it belongs to, so " +
|
|
21
|
+
"ten Accept buttons are ten different buttons. Status is a word, outside the label so editing " +
|
|
22
|
+
"never renames the control, and the running count is a polite live region present from the " +
|
|
23
|
+
"start. Enter accepts, except while an IME is composing.",
|
|
24
|
+
});
|
|
@@ -222,7 +222,7 @@ export function PromptInputSubmit({
|
|
|
222
222
|
disabled={disabled}
|
|
223
223
|
onClick={busy ? onStop : undefined}
|
|
224
224
|
className={cn(
|
|
225
|
-
"
|
|
225
|
+
"ms-auto grid size-8 shrink-0 place-items-center rounded-lg",
|
|
226
226
|
"bg-primary text-primary-foreground transition-colors duration-[var(--duration-fast)]",
|
|
227
227
|
"hover:bg-primary-hover disabled:pointer-events-none disabled:opacity-55",
|
|
228
228
|
focusRing,
|
|
@@ -98,7 +98,7 @@ export function ReasoningContent({
|
|
|
98
98
|
)}
|
|
99
99
|
{...props}
|
|
100
100
|
>
|
|
101
|
-
<div className="mt-2 border-
|
|
101
|
+
<div className="mt-2 border-s-2 border-border ps-3 text-sm whitespace-pre-wrap text-muted-foreground">
|
|
102
102
|
{children}
|
|
103
103
|
</div>
|
|
104
104
|
</CollapsiblePrimitive.Content>
|
|
@@ -31,7 +31,7 @@ export function Response({ className, streaming, children, ...props }: ResponseP
|
|
|
31
31
|
// Minimal prose styling for consumers who pass rendered Markdown.
|
|
32
32
|
"[&_a]:text-primary [&_a]:underline [&_a]:underline-offset-4",
|
|
33
33
|
"[&_code]:rounded [&_code]:bg-muted [&_code]:px-1 [&_code]:py-0.5 [&_code]:font-mono [&_code]:text-[0.9em]",
|
|
34
|
-
"[&_ol]:my-2 [&_ol]:list-decimal [&_ol]:
|
|
34
|
+
"[&_ol]:my-2 [&_ol]:list-decimal [&_ol]:ps-5 [&_ul]:my-2 [&_ul]:list-disc [&_ul]:ps-5",
|
|
35
35
|
"[&_p+p]:mt-3",
|
|
36
36
|
className,
|
|
37
37
|
)}
|
|
@@ -52,7 +52,7 @@ export function ResponseCaret({ className, ...props }: ComponentPropsWithRef<"sp
|
|
|
52
52
|
aria-hidden="true"
|
|
53
53
|
data-motion="indicator"
|
|
54
54
|
className={cn(
|
|
55
|
-
"
|
|
55
|
+
"ms-0.5 inline-block h-[1em] w-[2px] translate-y-[0.15em] animate-caret bg-current align-baseline",
|
|
56
56
|
className,
|
|
57
57
|
)}
|
|
58
58
|
{...props}
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
cloneElement,
|
|
5
|
+
isValidElement,
|
|
6
|
+
useId,
|
|
7
|
+
useState,
|
|
8
|
+
type ComponentPropsWithRef,
|
|
9
|
+
type ReactElement,
|
|
10
|
+
type ReactNode,
|
|
11
|
+
} from "react";
|
|
12
|
+
|
|
13
|
+
import { disabledStyles, focusRing } from "@/lib/styles";
|
|
14
|
+
import { cn } from "@/lib/utils";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* An AI-proposed value for a form control, offered beside it rather than
|
|
18
|
+
* written into it.
|
|
19
|
+
*
|
|
20
|
+
* Autofill is the most common shape AI takes inside ordinary software —
|
|
21
|
+
* enrich this contact, fill this form from the upload, guess the category —
|
|
22
|
+
* and nearly every implementation writes the value straight into the field
|
|
23
|
+
* as if the person had typed it. Two things go wrong. A plausible, wrong
|
|
24
|
+
* value rides through on the person's own Submit, because nothing marked it
|
|
25
|
+
* as needing a look. And once submitted, the record cannot tell a value the
|
|
26
|
+
* model supplied from one a human typed, which is the one fact an audit later
|
|
27
|
+
* needs.
|
|
28
|
+
*
|
|
29
|
+
* So the suggestion stays pending until it is accepted, and acceptance is
|
|
30
|
+
* reported rather than performed: the component hands the value to `onAccept`
|
|
31
|
+
* and never touches the control. That is also what lets it wrap any control —
|
|
32
|
+
* a select, a date, a number, a combobox — where ghost text can only complete
|
|
33
|
+
* a string. Afterwards the field says it was filled by AI, says if it was
|
|
34
|
+
* edited since, and offers to put back what was there.
|
|
35
|
+
*
|
|
36
|
+
* Deliberately no "accept all". A button that takes every suggestion at once
|
|
37
|
+
* is the review deleting itself, and every product that ships one watches its
|
|
38
|
+
* users press it without reading.
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
export interface Suggestion<T = string> {
|
|
42
|
+
value: T;
|
|
43
|
+
/** How the value reads — a select's option text, a formatted date. Defaults to the value. */
|
|
44
|
+
label?: string;
|
|
45
|
+
/** Where it came from, in the reader's language: "from the invoice header". */
|
|
46
|
+
source?: string;
|
|
47
|
+
/** 0 to 1. Shown as a number, never only as a colour. */
|
|
48
|
+
confidence?: number;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export type SuggestionStatus = "pending" | "accepted" | "dismissed";
|
|
52
|
+
|
|
53
|
+
export interface SuggestedValueProps<T = string> extends Omit<
|
|
54
|
+
ComponentPropsWithRef<"div">,
|
|
55
|
+
"children" | "id" | "aria-describedby" | "aria-invalid"
|
|
56
|
+
> {
|
|
57
|
+
/** The field, for the buttons' names: "Accept suggestion — Company". */
|
|
58
|
+
label: string;
|
|
59
|
+
/** Null or undefined offers nothing. A different suggestion starts pending again. */
|
|
60
|
+
suggestion?: Suggestion<T> | null;
|
|
61
|
+
/**
|
|
62
|
+
* The control's current value. Lets the marker say the fill was edited
|
|
63
|
+
* since, and lets Undo restore what the field held before.
|
|
64
|
+
*/
|
|
65
|
+
value?: T;
|
|
66
|
+
/** Supply to own the status; otherwise it is kept here. */
|
|
67
|
+
status?: SuggestionStatus;
|
|
68
|
+
onAccept: (value: T, suggestion: Suggestion<T>) => void;
|
|
69
|
+
onDismiss?: (suggestion: Suggestion<T>) => void;
|
|
70
|
+
/** Called with the value the field held before the fill, so it can be put back. */
|
|
71
|
+
onRevert?: (previous: T | undefined) => void;
|
|
72
|
+
/** How to compare values. Object.is by default. */
|
|
73
|
+
equals?: (a: T, b: T) => boolean;
|
|
74
|
+
/** Confidence below this is called low, in words. */
|
|
75
|
+
lowConfidenceBelow?: number;
|
|
76
|
+
/**
|
|
77
|
+
* Announce a suggestion when it arrives. Off by default: a form that fills
|
|
78
|
+
* twenty fields at once would narrate all twenty, and the description on
|
|
79
|
+
* the control already tells a reader when they reach the field.
|
|
80
|
+
*/
|
|
81
|
+
announce?: boolean;
|
|
82
|
+
/** The control. Receives the description, and any id or ARIA a FormControl passes down. */
|
|
83
|
+
children: ReactElement<Record<string, unknown>>;
|
|
84
|
+
/* Passed through to the control, so this can sit inside a FormControl. */
|
|
85
|
+
id?: string;
|
|
86
|
+
"aria-describedby"?: string;
|
|
87
|
+
"aria-invalid"?: ComponentPropsWithRef<"input">["aria-invalid"];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function defaultLabel(value: unknown): string {
|
|
91
|
+
if (value === null || value === undefined) return "";
|
|
92
|
+
if (typeof value === "string") return value;
|
|
93
|
+
if (typeof value === "number" || typeof value === "bigint") return String(value);
|
|
94
|
+
if (typeof value === "boolean") return value ? "Yes" : "No";
|
|
95
|
+
if (value instanceof Date) return value.toLocaleDateString();
|
|
96
|
+
return JSON.stringify(value);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function sameSuggestion<T>(
|
|
100
|
+
a: Suggestion<T> | null | undefined,
|
|
101
|
+
b: Suggestion<T> | null | undefined,
|
|
102
|
+
equals: (x: T, y: T) => boolean,
|
|
103
|
+
): boolean {
|
|
104
|
+
if (!a || !b) return a === b;
|
|
105
|
+
return equals(a.value, b.value) && a.label === b.label && a.source === b.source;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function SuggestedValue<T = string>({
|
|
109
|
+
className,
|
|
110
|
+
label,
|
|
111
|
+
suggestion,
|
|
112
|
+
value,
|
|
113
|
+
status: controlledStatus,
|
|
114
|
+
onAccept,
|
|
115
|
+
onDismiss,
|
|
116
|
+
onRevert,
|
|
117
|
+
equals = Object.is,
|
|
118
|
+
lowConfidenceBelow = 0.7,
|
|
119
|
+
announce = false,
|
|
120
|
+
children,
|
|
121
|
+
id,
|
|
122
|
+
"aria-describedby": describedByProp,
|
|
123
|
+
"aria-invalid": ariaInvalid,
|
|
124
|
+
...props
|
|
125
|
+
}: SuggestedValueProps<T>) {
|
|
126
|
+
const descriptionId = useId();
|
|
127
|
+
|
|
128
|
+
const [internalStatus, setInternalStatus] = useState<SuggestionStatus>("pending");
|
|
129
|
+
const [previous, setPrevious] = useState<T | undefined>(undefined);
|
|
130
|
+
|
|
131
|
+
// A new suggestion starts pending, whatever happened to the last one.
|
|
132
|
+
// Compared by content rather than identity, so a parent that rebuilds the
|
|
133
|
+
// object on every keystroke does not reopen a suggestion already accepted.
|
|
134
|
+
const [seen, setSeen] = useState(suggestion);
|
|
135
|
+
if (!sameSuggestion(seen, suggestion, equals)) {
|
|
136
|
+
setSeen(suggestion);
|
|
137
|
+
setInternalStatus("pending");
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const status = controlledStatus ?? internalStatus;
|
|
141
|
+
const offered = suggestion ?? null;
|
|
142
|
+
const pending = offered !== null && status === "pending";
|
|
143
|
+
const accepted = offered !== null && status === "accepted";
|
|
144
|
+
const edited = accepted && value !== undefined && !equals(value, offered.value);
|
|
145
|
+
|
|
146
|
+
const low = offered?.confidence !== undefined && offered.confidence < lowConfidenceBelow;
|
|
147
|
+
const percent =
|
|
148
|
+
offered?.confidence === undefined
|
|
149
|
+
? null
|
|
150
|
+
: new Intl.NumberFormat(undefined, { style: "percent", maximumFractionDigits: 0 }).format(
|
|
151
|
+
offered.confidence,
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
const source = offered?.source ? ` ${offered.source}` : "";
|
|
155
|
+
|
|
156
|
+
// What the control's description says. This is how a reader who lands on
|
|
157
|
+
// the field learns there is a suggestion, without any announcement.
|
|
158
|
+
let description: ReactNode = null;
|
|
159
|
+
if (pending) {
|
|
160
|
+
description = (
|
|
161
|
+
<>
|
|
162
|
+
Suggested:{" "}
|
|
163
|
+
<strong className="font-medium text-foreground">
|
|
164
|
+
{offered.label ?? defaultLabel(offered.value)}
|
|
165
|
+
</strong>
|
|
166
|
+
{source ? `,${source}` : null}
|
|
167
|
+
{percent !== null ? (
|
|
168
|
+
<span className={cn(low && "text-warning")}>
|
|
169
|
+
{low ? `. Low confidence, ${percent} — worth checking` : `. ${percent} confidence`}
|
|
170
|
+
</span>
|
|
171
|
+
) : null}
|
|
172
|
+
</>
|
|
173
|
+
);
|
|
174
|
+
} else if (accepted) {
|
|
175
|
+
description = edited
|
|
176
|
+
? `Filled by AI${source ? `,${source}` : ""}, then edited`
|
|
177
|
+
: `Filled by AI${source ? `,${source}` : ""}`;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Merged with whatever already describes the control — a FormControl's
|
|
181
|
+
// description and message, or the child's own — rather than replacing it.
|
|
182
|
+
const childDescribedBy = isValidElement(children)
|
|
183
|
+
? (children.props["aria-describedby"] as string | undefined)
|
|
184
|
+
: undefined;
|
|
185
|
+
const describedBy =
|
|
186
|
+
[childDescribedBy, describedByProp, description !== null ? descriptionId : null]
|
|
187
|
+
.filter(Boolean)
|
|
188
|
+
.join(" ") || undefined;
|
|
189
|
+
|
|
190
|
+
const control = isValidElement(children)
|
|
191
|
+
? cloneElement(children, {
|
|
192
|
+
id: id ?? children.props.id,
|
|
193
|
+
"aria-describedby": describedBy,
|
|
194
|
+
"aria-invalid": ariaInvalid ?? children.props["aria-invalid"],
|
|
195
|
+
})
|
|
196
|
+
: children;
|
|
197
|
+
|
|
198
|
+
const accept = () => {
|
|
199
|
+
if (!offered) return;
|
|
200
|
+
setPrevious(value);
|
|
201
|
+
setInternalStatus("accepted");
|
|
202
|
+
onAccept(offered.value, offered);
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
return (
|
|
206
|
+
<div
|
|
207
|
+
data-slot="suggested-value"
|
|
208
|
+
data-status={offered ? status : undefined}
|
|
209
|
+
data-filled-by={accepted ? "ai" : undefined}
|
|
210
|
+
data-edited={edited || undefined}
|
|
211
|
+
className={cn("flex flex-col gap-1.5", className)}
|
|
212
|
+
{...props}
|
|
213
|
+
>
|
|
214
|
+
{control}
|
|
215
|
+
|
|
216
|
+
{/* Present from the start and never display:none, so that with `announce`
|
|
217
|
+
a suggestion arriving after mount is heard — a region that enters the
|
|
218
|
+
accessibility tree at the same moment as its content announces
|
|
219
|
+
nothing. Empty, it has no height. */}
|
|
220
|
+
<div
|
|
221
|
+
data-slot="suggested-value-row"
|
|
222
|
+
aria-live={announce ? "polite" : undefined}
|
|
223
|
+
className={cn(
|
|
224
|
+
"flex flex-wrap items-center gap-x-3 gap-y-1 text-xs",
|
|
225
|
+
pending && "rounded-md border border-primary/40 bg-primary/5 px-2.5 py-1.5",
|
|
226
|
+
)}
|
|
227
|
+
>
|
|
228
|
+
{description !== null ? (
|
|
229
|
+
<p
|
|
230
|
+
id={descriptionId}
|
|
231
|
+
data-slot="suggested-value-description"
|
|
232
|
+
className="m-0 flex-1 text-muted-foreground"
|
|
233
|
+
>
|
|
234
|
+
{description}
|
|
235
|
+
</p>
|
|
236
|
+
) : null}
|
|
237
|
+
|
|
238
|
+
{pending ? (
|
|
239
|
+
<span className="flex items-center gap-2">
|
|
240
|
+
<RowButton
|
|
241
|
+
variant="primary"
|
|
242
|
+
aria-label={`Accept suggestion — ${label}`}
|
|
243
|
+
onClick={accept}
|
|
244
|
+
>
|
|
245
|
+
Accept
|
|
246
|
+
</RowButton>
|
|
247
|
+
<RowButton
|
|
248
|
+
aria-label={`Dismiss suggestion — ${label}`}
|
|
249
|
+
onClick={() => {
|
|
250
|
+
setInternalStatus("dismissed");
|
|
251
|
+
onDismiss?.(offered);
|
|
252
|
+
}}
|
|
253
|
+
>
|
|
254
|
+
Dismiss
|
|
255
|
+
</RowButton>
|
|
256
|
+
</span>
|
|
257
|
+
) : null}
|
|
258
|
+
|
|
259
|
+
{accepted && onRevert ? (
|
|
260
|
+
<RowButton
|
|
261
|
+
aria-label={`Undo — ${label}`}
|
|
262
|
+
onClick={() => {
|
|
263
|
+
setInternalStatus("pending");
|
|
264
|
+
onRevert(previous);
|
|
265
|
+
}}
|
|
266
|
+
>
|
|
267
|
+
Undo
|
|
268
|
+
</RowButton>
|
|
269
|
+
) : null}
|
|
270
|
+
</div>
|
|
271
|
+
</div>
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function RowButton({
|
|
276
|
+
className,
|
|
277
|
+
variant = "default",
|
|
278
|
+
...props
|
|
279
|
+
}: ComponentPropsWithRef<"button"> & { variant?: "default" | "primary" }) {
|
|
280
|
+
return (
|
|
281
|
+
<button
|
|
282
|
+
type="button"
|
|
283
|
+
className={cn(
|
|
284
|
+
"rounded-md border px-2 py-0.5 text-xs font-medium transition-colors",
|
|
285
|
+
variant === "primary"
|
|
286
|
+
? "border-primary bg-primary text-primary-foreground hover:bg-primary-hover"
|
|
287
|
+
: "border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
|
288
|
+
focusRing,
|
|
289
|
+
disabledStyles,
|
|
290
|
+
className,
|
|
291
|
+
)}
|
|
292
|
+
{...props}
|
|
293
|
+
/>
|
|
294
|
+
);
|
|
295
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { defineMeta } from "@/registry/schema";
|
|
2
|
+
|
|
3
|
+
export const meta = defineMeta({
|
|
4
|
+
name: "ai-suggested-value",
|
|
5
|
+
title: "AI Suggested Value",
|
|
6
|
+
description:
|
|
7
|
+
"An AI-proposed value for any form control, pending until accepted and marked afterwards.",
|
|
8
|
+
category: "ai",
|
|
9
|
+
status: "stable",
|
|
10
|
+
dependencies: [],
|
|
11
|
+
registryDependencies: [],
|
|
12
|
+
files: ["ai-suggested-value.tsx"],
|
|
13
|
+
a11y:
|
|
14
|
+
"The suggestion is the control's description, so a reader who lands on the field hears it " +
|
|
15
|
+
"without any announcement — and announcing is opt-in, because a form filling twenty fields at " +
|
|
16
|
+
"once would narrate all twenty. The row that holds it is rendered from the start and never " +
|
|
17
|
+
"display:none, so when announcing is on, a suggestion arriving after mount is actually heard. " +
|
|
18
|
+
"Each button's name carries the field it belongs to, so a page of Accepts is a page of " +
|
|
19
|
+
"different buttons. Provenance and confidence are words with a number, never a colour. " +
|
|
20
|
+
"Acceptance is reported, not performed: the control is the consumer's, so its value, its " +
|
|
21
|
+
"label and its own keyboard behaviour are untouched — there is no shortcut hijacked from a " +
|
|
22
|
+
"select or a date field. The id and ARIA a FormControl passes down are forwarded to the " +
|
|
23
|
+
"control, and an existing aria-describedby is merged rather than replaced.",
|
|
24
|
+
});
|
|
@@ -82,7 +82,7 @@ export function ToolHeader({
|
|
|
82
82
|
<CollapsiblePrimitive.Trigger
|
|
83
83
|
data-slot="tool-header"
|
|
84
84
|
className={cn(
|
|
85
|
-
"flex w-full items-center gap-2 px-3 py-2 text-
|
|
85
|
+
"flex w-full items-center gap-2 px-3 py-2 text-start",
|
|
86
86
|
"transition-colors duration-[var(--duration-fast)] hover:bg-accent/50",
|
|
87
87
|
"[&[data-state=open]>svg:last-child]:rotate-180",
|
|
88
88
|
focusRing,
|
|
@@ -97,7 +97,7 @@ export function ToolHeader({
|
|
|
97
97
|
<span
|
|
98
98
|
data-slot="tool-status"
|
|
99
99
|
className={cn(
|
|
100
|
-
"
|
|
100
|
+
"ms-auto text-xs",
|
|
101
101
|
status === "error" ? "text-destructive" : "text-muted-foreground",
|
|
102
102
|
)}
|
|
103
103
|
>
|