@superxd-studio/adptr 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 (221) hide show
  1. package/GENERATIVE.md +101 -0
  2. package/INTERACTIONS.md +51 -0
  3. package/LICENSE +21 -0
  4. package/README.md +57 -0
  5. package/REUSE.md +170 -0
  6. package/REVIEW.md +68 -0
  7. package/THIRD-PARTY-NOTICES.md +28 -0
  8. package/ai-sdk/index.js +96 -0
  9. package/assets/ibm-plex-mono-cyrillic-400-normal.woff2 +0 -0
  10. package/assets/ibm-plex-mono-cyrillic-500-normal.woff2 +0 -0
  11. package/assets/ibm-plex-mono-cyrillic-ext-400-normal.woff2 +0 -0
  12. package/assets/ibm-plex-mono-cyrillic-ext-500-normal.woff2 +0 -0
  13. package/assets/ibm-plex-mono-latin-400-normal.woff2 +0 -0
  14. package/assets/ibm-plex-mono-latin-500-normal.woff2 +0 -0
  15. package/assets/ibm-plex-mono-latin-ext-400-normal.woff2 +0 -0
  16. package/assets/ibm-plex-mono-latin-ext-500-normal.woff2 +0 -0
  17. package/assets/ibm-plex-mono-vietnamese-400-normal.woff2 +0 -0
  18. package/assets/ibm-plex-mono-vietnamese-500-normal.woff2 +0 -0
  19. package/assets/instrument-sans-latin-ext-wght-normal.woff2 +0 -0
  20. package/assets/instrument-sans-latin-wght-normal.woff2 +0 -0
  21. package/assistant-ui/index.js +139 -0
  22. package/chunks/callout-DKMHSnjc.js +342 -0
  23. package/chunks/canvas-BJ15-XxE.js +598 -0
  24. package/chunks/controller-Cfpg0C7v.js +180 -0
  25. package/chunks/core-BCqVRLUy.js +312 -0
  26. package/chunks/text-field-BLtoaZFn.js +41 -0
  27. package/chunks/types-BGF7r1my.js +115 -0
  28. package/chunks/ui-9DyNsYXY.js +967 -0
  29. package/core/index.js +4 -0
  30. package/custom/index.js +64 -0
  31. package/examples/ai-sdk-backend/package.json +11 -0
  32. package/examples/ai-sdk-backend/server.mjs +316 -0
  33. package/examples/custom-backend/copy.mjs +7 -0
  34. package/examples/custom-backend/server.mjs +118 -0
  35. package/examples/generative/specs.json +2094 -0
  36. package/examples/review-backend/copy.mjs +27 -0
  37. package/examples/review-backend/package.json +11 -0
  38. package/examples/review-backend/server.mjs +276 -0
  39. package/examples/web-ai-sdk/index.html +12 -0
  40. package/examples/web-ai-sdk/package.json +21 -0
  41. package/examples/web-ai-sdk/src/copy.ts +1 -0
  42. package/examples/web-ai-sdk/src/main.tsx +64 -0
  43. package/examples/web-ai-sdk/tsconfig.json +13 -0
  44. package/examples/web-generative/index.html +12 -0
  45. package/examples/web-generative/package.json +23 -0
  46. package/examples/web-generative/src/copy.ts +42 -0
  47. package/examples/web-generative/src/main.tsx +74 -0
  48. package/examples/web-generative/tsconfig.json +14 -0
  49. package/examples/web-independent/index.html +12 -0
  50. package/examples/web-independent/package.json +20 -0
  51. package/examples/web-independent/src/main.tsx +32 -0
  52. package/examples/web-independent/tsconfig.json +14 -0
  53. package/examples/web-interactions/index.html +12 -0
  54. package/examples/web-interactions/package.json +21 -0
  55. package/examples/web-interactions/src/copy.ts +215 -0
  56. package/examples/web-interactions/src/examples.tsx +628 -0
  57. package/examples/web-interactions/src/main.tsx +116 -0
  58. package/examples/web-interactions/tsconfig.json +14 -0
  59. package/examples/web-review/index.html +12 -0
  60. package/examples/web-review/package.json +20 -0
  61. package/examples/web-review/src/main.tsx +59 -0
  62. package/examples/web-review/tsconfig.json +14 -0
  63. package/fonts.css +117 -0
  64. package/generative/index.js +1560 -0
  65. package/interactions/index.js +1591 -0
  66. package/langgraph-review/index.js +36 -0
  67. package/licenses/ai-sdk.txt +13 -0
  68. package/licenses/assistant-ui-generative.txt +21 -0
  69. package/licenses/assistant-ui.txt +23 -0
  70. package/licenses/ibm-plex-mono.txt +93 -0
  71. package/licenses/instrument-sans.txt +93 -0
  72. package/licenses/internationalized-date.txt +201 -0
  73. package/licenses/langgraph.txt +21 -0
  74. package/licenses/lucide.txt +43 -0
  75. package/licenses/react-markdown.txt +21 -0
  76. package/licenses/remark-gfm.txt +22 -0
  77. package/licenses/zod.txt +21 -0
  78. package/package.json +138 -0
  79. package/review/index.js +31 -0
  80. package/source/components/avatar.tsx +50 -0
  81. package/source/components/button.tsx +155 -0
  82. package/source/components/callout.tsx +65 -0
  83. package/source/components/canvas/connector.tsx +14 -0
  84. package/source/components/canvas/copy.ts +34 -0
  85. package/source/components/canvas/index.tsx +257 -0
  86. package/source/components/canvas/recorder.ts +36 -0
  87. package/source/components/date-time-field.tsx +77 -0
  88. package/source/components/flow-motion.tsx +109 -0
  89. package/source/components/icons.tsx +275 -0
  90. package/source/components/responsive-overlay.tsx +91 -0
  91. package/source/components/scenario-picker/copy.ts +5 -0
  92. package/source/components/scenario-picker/index.tsx +142 -0
  93. package/source/components/scroll-area.tsx +59 -0
  94. package/source/components/search-picker.tsx +127 -0
  95. package/source/components/segmented-control.tsx +123 -0
  96. package/source/components/skeleton.tsx +11 -0
  97. package/source/components/spinner.tsx +19 -0
  98. package/source/components/superxd-arrow.tsx +22 -0
  99. package/source/components/switch.tsx +45 -0
  100. package/source/components/text-field.tsx +81 -0
  101. package/source/components/thinking.tsx +16 -0
  102. package/source/components/ui/copy.ts +12 -0
  103. package/source/components/ui/display.tsx +276 -0
  104. package/source/components/ui/navigation.tsx +346 -0
  105. package/source/components/ui/portal-theme.tsx +28 -0
  106. package/source/components/ui/selection.tsx +336 -0
  107. package/source/lib/cn.ts +23 -0
  108. package/source/reusable/ai-sdk/index.ts +114 -0
  109. package/source/reusable/assistant-ui/index.tsx +266 -0
  110. package/source/reusable/core/attachments.ts +163 -0
  111. package/source/reusable/core/index.ts +417 -0
  112. package/source/reusable/custom/index.ts +90 -0
  113. package/source/reusable/generative/actions.ts +188 -0
  114. package/source/reusable/generative/copy.ts +47 -0
  115. package/source/reusable/generative/data.tsx +126 -0
  116. package/source/reusable/generative/index.tsx +517 -0
  117. package/source/reusable/generative/schema.ts +327 -0
  118. package/source/reusable/generative/timeline.tsx +214 -0
  119. package/source/reusable/generative/vocabulary.tsx +847 -0
  120. package/source/reusable/interactions/agent.ts +259 -0
  121. package/source/reusable/interactions/artifact.ts +236 -0
  122. package/source/reusable/interactions/context.tsx +38 -0
  123. package/source/reusable/interactions/copy.ts +117 -0
  124. package/source/reusable/interactions/feedback.ts +73 -0
  125. package/source/reusable/interactions/index.ts +11 -0
  126. package/source/reusable/interactions/presentation.tsx +483 -0
  127. package/source/reusable/interactions/store.ts +74 -0
  128. package/source/reusable/interactions/threads.ts +126 -0
  129. package/source/reusable/interactions/web.tsx +574 -0
  130. package/source/reusable/langgraph-review/index.ts +73 -0
  131. package/source/reusable/review/controller.ts +263 -0
  132. package/source/reusable/review/index.ts +47 -0
  133. package/source/reusable/review/types.ts +189 -0
  134. package/source/reusable/ui/index.ts +25 -0
  135. package/source/reusable/uploads/index.ts +48 -0
  136. package/source/reusable/web/attachments/copy.ts +23 -0
  137. package/source/reusable/web/attachments/index.tsx +233 -0
  138. package/source/reusable/web/copy.ts +22 -0
  139. package/source/reusable/web/index.tsx +470 -0
  140. package/source/reusable/web/message/copy.ts +26 -0
  141. package/source/reusable/web/message/index.tsx +388 -0
  142. package/source/reusable/web/message/streaming.ts +64 -0
  143. package/source/reusable/web/message/styles.css +21 -0
  144. package/source/reusable/web/review/copy.ts +78 -0
  145. package/source/reusable/web/review/flow.ts +24 -0
  146. package/source/reusable/web/review/index.tsx +506 -0
  147. package/source/reusable/web/styles.ts +2 -0
  148. package/source/styles/library.css +612 -0
  149. package/source/styles/theme.css +228 -0
  150. package/styles.css +3 -0
  151. package/styles.d.ts +1 -0
  152. package/types/components/avatar.d.ts +14 -0
  153. package/types/components/button.d.ts +36 -0
  154. package/types/components/callout.d.ts +19 -0
  155. package/types/components/canvas/connector.d.ts +2 -0
  156. package/types/components/canvas/copy.d.ts +34 -0
  157. package/types/components/canvas/index.d.ts +33 -0
  158. package/types/components/canvas/recorder.d.ts +10 -0
  159. package/types/components/date-time-field.d.ts +11 -0
  160. package/types/components/flow-motion.d.ts +38 -0
  161. package/types/components/icons.d.ts +138 -0
  162. package/types/components/responsive-overlay.d.ts +15 -0
  163. package/types/components/scenario-picker/copy.d.ts +5 -0
  164. package/types/components/scenario-picker/index.d.ts +14 -0
  165. package/types/components/scroll-area.d.ts +14 -0
  166. package/types/components/search-picker.d.ts +27 -0
  167. package/types/components/segmented-control.d.ts +23 -0
  168. package/types/components/skeleton.d.ts +4 -0
  169. package/types/components/spinner.d.ts +4 -0
  170. package/types/components/superxd-arrow.d.ts +5 -0
  171. package/types/components/switch.d.ts +8 -0
  172. package/types/components/text-field.d.ts +19 -0
  173. package/types/components/thinking.d.ts +8 -0
  174. package/types/components/ui/copy.d.ts +12 -0
  175. package/types/components/ui/display.d.ts +50 -0
  176. package/types/components/ui/navigation.d.ts +63 -0
  177. package/types/components/ui/portal-theme.d.ts +8 -0
  178. package/types/components/ui/selection.d.ts +37 -0
  179. package/types/lib/cn.d.ts +3 -0
  180. package/types/reusable/ai-sdk/index.d.ts +15 -0
  181. package/types/reusable/assistant-ui/index.d.ts +12 -0
  182. package/types/reusable/core/attachments.d.ts +54 -0
  183. package/types/reusable/core/index.d.ts +107 -0
  184. package/types/reusable/custom/index.d.ts +14 -0
  185. package/types/reusable/generative/actions.d.ts +45 -0
  186. package/types/reusable/generative/copy.d.ts +48 -0
  187. package/types/reusable/generative/data.d.ts +4 -0
  188. package/types/reusable/generative/index.d.ts +30 -0
  189. package/types/reusable/generative/schema.d.ts +303 -0
  190. package/types/reusable/generative/timeline.d.ts +21 -0
  191. package/types/reusable/generative/vocabulary.d.ts +34 -0
  192. package/types/reusable/interactions/agent.d.ts +98 -0
  193. package/types/reusable/interactions/artifact.d.ts +118 -0
  194. package/types/reusable/interactions/context.d.ts +130 -0
  195. package/types/reusable/interactions/copy.d.ts +115 -0
  196. package/types/reusable/interactions/feedback.d.ts +34 -0
  197. package/types/reusable/interactions/index.d.ts +10 -0
  198. package/types/reusable/interactions/presentation.d.ts +97 -0
  199. package/types/reusable/interactions/store.d.ts +29 -0
  200. package/types/reusable/interactions/threads.d.ts +38 -0
  201. package/types/reusable/interactions/web.d.ts +22 -0
  202. package/types/reusable/langgraph-review/index.d.ts +5 -0
  203. package/types/reusable/review/controller.d.ts +52 -0
  204. package/types/reusable/review/index.d.ts +12 -0
  205. package/types/reusable/review/types.d.ts +61 -0
  206. package/types/reusable/ui/index.d.ts +19 -0
  207. package/types/reusable/uploads/index.d.ts +11 -0
  208. package/types/reusable/web/attachments/copy.d.ts +25 -0
  209. package/types/reusable/web/attachments/index.d.ts +24 -0
  210. package/types/reusable/web/copy.d.ts +24 -0
  211. package/types/reusable/web/index.d.ts +41 -0
  212. package/types/reusable/web/message/copy.d.ts +27 -0
  213. package/types/reusable/web/message/index.d.ts +56 -0
  214. package/types/reusable/web/message/streaming.d.ts +27 -0
  215. package/types/reusable/web/review/copy.d.ts +72 -0
  216. package/types/reusable/web/review/flow.d.ts +3 -0
  217. package/types/reusable/web/review/index.d.ts +38 -0
  218. package/types/reusable/web/styles.d.ts +2 -0
  219. package/ui/index.js +4 -0
  220. package/uploads/index.js +34 -0
  221. package/web/index.js +931 -0
@@ -0,0 +1,47 @@
1
+ export const generativeCopy = {
2
+ stepReady: "Ready",
3
+ stepLoading: "Loading",
4
+ stepReview: "Review",
5
+ stepFailed: "Not applied",
6
+ stepUncertain: "Unconfirmed",
7
+ stepResult: "Result",
8
+ stepPast: "Previous state",
9
+ loading: "Preparing your response",
10
+ streaming: "The response is arriving. Actions will be available when it’s complete.",
11
+ empty: "There’s nothing to show yet.",
12
+ invalid: "We couldn’t display this response.",
13
+ invalidNote: "Your work is still here. Try requesting the response again.",
14
+ retry: "Try again",
15
+ back: "Back",
16
+ confirm: "Confirm action",
17
+ working: "Applying",
18
+ checking: "Checking",
19
+ review: "Review before continuing",
20
+ input: "Your details",
21
+ noHandler: "This action isn’t available here.",
22
+ failed: "The action wasn’t applied.",
23
+ failedNote: "Your details are saved here. You can try again.",
24
+ uncertain: "We haven’t received confirmation.",
25
+ uncertainNote: "Check the result before trying again. Your details are kept here.",
26
+ check: "Check status",
27
+ noReceipt: "There’s no confirmation yet. Check again or retry this same request.",
28
+ complete: "Action complete",
29
+ dismissed: "Dismissed",
30
+ fieldRequired: "Enter a value to continue.",
31
+ email: "Enter an email address like name@company.com.",
32
+ date: "Enter a valid date.",
33
+ imageMissing: "Image unavailable",
34
+ data: "View data",
35
+ point: "Point",
36
+ series: "Value",
37
+ mediaSelected: "Track selected",
38
+ quoted: "Quoted amount",
39
+ validation: "Check the highlighted fields.",
40
+ updated: "A new response is available. Finish or dismiss this action before opening it.",
41
+ next: "Open new response",
42
+ quoteUnavailable: "We couldn’t confirm the amount. Try again before continuing.",
43
+ actionInvalid: "We couldn’t prepare this action.",
44
+ actionInvalidNote:
45
+ "Your details are still here. Check the entries, shorten long text and try again.",
46
+ } as const;
47
+ export type GenerativeCopy = { [K in keyof typeof generativeCopy]: string };
@@ -0,0 +1,126 @@
1
+ import { useState } from "react";
2
+ import { defaultGenerativeUILibrary } from "@assistant-ui/react-generative-ui";
3
+ import { Button } from "../../components/button.js";
4
+ import { generativeCopy as copy } from "./copy.js";
5
+ import type { GenerativeChartProps, GenerativeTableProps } from "./schema.js";
6
+
7
+ export function GenerativeTable({ label, columns, rows, sortable = false }: GenerativeTableProps) {
8
+ const [sort, setSort] = useState<{ index: number; direction: 1 | -1 }>();
9
+ const ordered = sort
10
+ ? [...rows].sort((a, b) => {
11
+ const left = a[sort.index],
12
+ right = b[sort.index];
13
+ return (
14
+ sort.direction *
15
+ (typeof left === "number" && typeof right === "number"
16
+ ? left - right
17
+ : String(left).localeCompare(String(right), "en", { numeric: true }))
18
+ );
19
+ })
20
+ : rows;
21
+ return (
22
+ <div
23
+ className="min-w-0 overflow-x-auto rounded-control border border-line-subtle"
24
+ tabIndex={0}
25
+ role="region"
26
+ aria-label={label}
27
+ >
28
+ <table className="w-full type-body text-fg numeric">
29
+ <caption className="sr-only">{label}</caption>
30
+ <thead className="bg-surface-muted">
31
+ <tr>
32
+ {columns.map((column, index) => (
33
+ <th
34
+ key={index}
35
+ scope="col"
36
+ aria-sort={
37
+ sort?.index === index
38
+ ? sort.direction === 1
39
+ ? "ascending"
40
+ : "descending"
41
+ : undefined
42
+ }
43
+ className="px-4 py-2 text-left type-label"
44
+ style={{ textAlign: column.align === "end" ? "right" : "left" }}
45
+ >
46
+ {sortable ? (
47
+ <Button
48
+ size="sm"
49
+ variant="ghost"
50
+ className="-mx-2"
51
+ onPress={() =>
52
+ setSort({
53
+ index,
54
+ direction: sort?.index === index && sort.direction === 1 ? -1 : 1,
55
+ })
56
+ }
57
+ >
58
+ {column.label}
59
+ {sort?.index === index ? (
60
+ <span aria-hidden>{sort.direction === 1 ? "↑" : "↓"}</span>
61
+ ) : null}
62
+ </Button>
63
+ ) : (
64
+ column.label
65
+ )}
66
+ </th>
67
+ ))}
68
+ </tr>
69
+ </thead>
70
+ <tbody>
71
+ {ordered.map((row, index) => (
72
+ <tr key={index} className="border-t border-line-subtle">
73
+ {row.map((cell, c) => (
74
+ <td
75
+ key={c}
76
+ className="px-4 py-3"
77
+ style={{ textAlign: columns[c]?.align === "end" ? "right" : "left" }}
78
+ >
79
+ {String(cell)}
80
+ </td>
81
+ ))}
82
+ </tr>
83
+ ))}
84
+ </tbody>
85
+ </table>
86
+ </div>
87
+ );
88
+ }
89
+ /** Upstream chart geometry, host tokens, and the same values in a keyboard-accessible table. */
90
+ export function GenerativeChart(props: GenerativeChartProps) {
91
+ const [showData, setShowData] = useState(false);
92
+ const series = props.series?.length
93
+ ? props.series
94
+ : [{ label: copy.series, data: props.data ?? [] }];
95
+ return (
96
+ <figure className="generative-chart flex min-w-0 flex-col gap-4" aria-label={props.label}>
97
+ {series.every((s) => s.data.length === 0) ? (
98
+ <p className="type-body text-fg-subtle">{copy.empty}</p>
99
+ ) : (
100
+ defaultGenerativeUILibrary.Chart?.render({ ...props, $status: "done" })
101
+ )}
102
+ <Button
103
+ size="sm"
104
+ variant="ghost"
105
+ className="self-start"
106
+ aria-expanded={showData}
107
+ onPress={() => setShowData((v) => !v)}
108
+ >
109
+ {copy.data}
110
+ </Button>
111
+ {showData ? (
112
+ <GenerativeTable
113
+ label={props.label}
114
+ columns={[
115
+ { label: copy.point },
116
+ ...series.map((s) => ({ label: s.label, align: "end" as const })),
117
+ ]}
118
+ rows={(series[0]?.data ?? []).map((p, i) => [
119
+ p.label,
120
+ ...series.map((s) => s.data[i]?.value ?? 0),
121
+ ])}
122
+ />
123
+ ) : null}
124
+ </figure>
125
+ );
126
+ }
@@ -0,0 +1,517 @@
1
+ import { CanvasSequence, useCanvasObserver } from "../../components/canvas/index.js";
2
+ import {
3
+ useEffect,
4
+ useLayoutEffect,
5
+ useCallback,
6
+ useId,
7
+ useMemo,
8
+ useRef,
9
+ useState,
10
+ useSyncExternalStore,
11
+ type ReactNode,
12
+ } from "react";
13
+ import { renderGenerativeUI } from "@assistant-ui/react-generative-ui";
14
+ import { Button } from "../../components/button.js";
15
+ import { Callout } from "../../components/callout.js";
16
+ import { Spinner } from "../../components/spinner.js";
17
+ import { generativeCopy as defaultCopy, type GenerativeCopy } from "./copy.js";
18
+ import { parseStructuredSpec, type StructuredSpec } from "./schema.js";
19
+ import {
20
+ createSuperxdGenerativeLibrary,
21
+ GenerativeRuntimeContext,
22
+ type GenerativeLibraryOptions,
23
+ } from "./vocabulary.js";
24
+ import {
25
+ createGenerativeActionController,
26
+ type GenerativeActions,
27
+ type GenerativeActionController,
28
+ type GenerativeRequest,
29
+ type GenerativeActionSnapshot,
30
+ } from "./actions.js";
31
+ import { ResponseTimeline } from "./timeline.js";
32
+ export { createSuperxdGenerativeLibrary, GenerativeForm } from "./vocabulary.js";
33
+ export type {
34
+ GenerativeLibraryOptions,
35
+ GenerativeFormProps,
36
+ GenerativeFactResolver,
37
+ } from "./vocabulary.js";
38
+ export { GenerativeChart, GenerativeTable } from "./data.js";
39
+ export { parseStructuredSpec, chartSchema, tableSchema, nodeSchemas } from "./schema.js";
40
+ export type {
41
+ StructuredSpec,
42
+ StructuredAction,
43
+ GenerativeChartProps,
44
+ GenerativeTableProps,
45
+ } from "./schema.js";
46
+ export { createGenerativeActionController } from "./actions.js";
47
+ export type {
48
+ GenerativeActionController,
49
+ GenerativeActions,
50
+ GenerativeActionHandler,
51
+ GenerativeActionSnapshot,
52
+ GenerativeStage,
53
+ GenerativeRequest,
54
+ GenerativeReceipt,
55
+ } from "./actions.js";
56
+ export { generativeCopy } from "./copy.js";
57
+ export type { GenerativeCopy } from "./copy.js";
58
+
59
+ export interface StructuredResponseProps extends GenerativeLibraryOptions {
60
+ /** Stable identity of this response. A different response owns a different action session. */
61
+ responseId: string;
62
+ spec: unknown;
63
+ status?: "loading" | "streaming" | "done" | "error";
64
+ /** Host-authored allowlist; an omitted handler renders its action unavailable. Keep stable for this response. */
65
+ actions?: GenerativeActions;
66
+ controller?: GenerativeActionController;
67
+ onRetry?: () => void;
68
+ /** Host-owned destination shown only after a matching successful receipt. Return null for the default receipt. */
69
+ renderResult?: (request: GenerativeRequest) => ReactNode;
70
+ /** Keep action steps on the canvas; earlier steps become read-only previews. Off by default. */
71
+ retainSteps?: boolean;
72
+ copy?: GenerativeCopy;
73
+ className?: string;
74
+ }
75
+ const noActions: GenerativeActions = {};
76
+ export function StructuredResponse(props: StructuredResponseProps) {
77
+ return (
78
+ <CanvasSequence key={props.responseId}>
79
+ <ResponseSession key={props.responseId} {...props} />
80
+ </CanvasSequence>
81
+ );
82
+ }
83
+ function ResponseSession(props: StructuredResponseProps) {
84
+ const [owned] = useState(() => createGenerativeActionController(props.actions ?? noActions));
85
+ const controller = props.controller ?? owned;
86
+ const snapshot = useSyncExternalStore(
87
+ controller.subscribe,
88
+ controller.getSnapshot,
89
+ controller.getSnapshot,
90
+ );
91
+ const capture = useResponseObservation(props, controller);
92
+ const lease = useRef(0);
93
+ useEffect(() => {
94
+ const state = lease;
95
+ const current = ++state.current;
96
+ return () => {
97
+ queueMicrotask(() => {
98
+ if (state.current === current && !props.controller) owned.dispose();
99
+ });
100
+ };
101
+ }, [props.controller, owned]);
102
+ return props.retainSteps ? (
103
+ <ResponseTimeline
104
+ controller={controller}
105
+ spec={props.spec}
106
+ status={props.status ?? "done"}
107
+ actions={props.actions ?? noActions}
108
+ copy={props.copy ?? defaultCopy}
109
+ >
110
+ {(step, onRequest) => (
111
+ <ResponseView
112
+ {...props}
113
+ spec={step.spec}
114
+ status={step.status}
115
+ controller={controller}
116
+ snapshot={step.snapshot}
117
+ actionOnly={step.snapshot.stage !== "ready"}
118
+ onRequest={onRequest}
119
+ inputSnapshot={step.input}
120
+ />
121
+ )}
122
+ </ResponseTimeline>
123
+ ) : (
124
+ <ResponseView {...props} controller={controller} snapshot={snapshot} onRequest={capture} />
125
+ );
126
+ }
127
+ /** Record from the same controller used by the demo, including fast loading transitions. */
128
+ function useResponseObservation(
129
+ props: StructuredResponseProps,
130
+ controller: GenerativeActionController,
131
+ ) {
132
+ const observe = useCanvasObserver();
133
+ const source = useId();
134
+ const latest = useRef(props);
135
+ const input = useRef<Readonly<Record<string, string | boolean>>>(undefined);
136
+ const seen = useRef<GenerativeActionSnapshot | null>(null);
137
+ useLayoutEffect(() => {
138
+ latest.current = props;
139
+ }, [props]);
140
+ const record = useCallback(
141
+ (snapshot: GenerativeActionSnapshot, force = false) => {
142
+ if (!observe || (!force && seen.current === snapshot)) return;
143
+ if (seen.current?.stage === "complete" || seen.current?.stage === "dismissed") return;
144
+ if (snapshot.stage === "ready" && snapshot.request) return;
145
+ seen.current = snapshot;
146
+ const config = latest.current;
147
+ const copy = config.copy ?? defaultCopy;
148
+ const status = config.status ?? "done";
149
+ let contentState: string = status;
150
+ if (status === "done") {
151
+ if (config.spec == null) contentState = "empty";
152
+ else {
153
+ try {
154
+ parseStructuredSpec(config.spec);
155
+ } catch {
156
+ contentState = "invalid";
157
+ }
158
+ }
159
+ }
160
+ const inspecting =
161
+ snapshot.request && config.actions?.[snapshot.request.action.type]?.kind === "inspect";
162
+ const labels = {
163
+ ready:
164
+ contentState === "empty"
165
+ ? copy.empty
166
+ : contentState === "invalid" || status === "error"
167
+ ? copy.invalid
168
+ : status === "done"
169
+ ? copy.stepReady
170
+ : copy.stepLoading,
171
+ confirm: copy.stepReview,
172
+ submitting: inspecting ? copy.stepLoading : copy.working,
173
+ checking: copy.checking,
174
+ failed: copy.stepFailed,
175
+ uncertain: copy.stepUncertain,
176
+ complete: copy.stepResult,
177
+ dismissed: copy.dismissed,
178
+ };
179
+ const result =
180
+ snapshot.stage === "complete" && snapshot.request
181
+ ? config.renderResult?.(snapshot.request)
182
+ : null;
183
+ observe(
184
+ {
185
+ phase: `generative-${snapshot.stage}-${contentState}`,
186
+ label: labels[snapshot.stage],
187
+ className: "max-w-2xl",
188
+ yieldToChild: Boolean(result),
189
+ terminal: snapshot.stage === "dismissed" || (snapshot.stage === "complete" && !result),
190
+ node: (
191
+ <ResponseView
192
+ key={JSON.stringify(input.current)}
193
+ {...config}
194
+ controller={controller}
195
+ snapshot={snapshot}
196
+ retainSteps
197
+ actionOnly={snapshot.stage !== "ready"}
198
+ inputSnapshot={input.current}
199
+ />
200
+ ),
201
+ },
202
+ source,
203
+ );
204
+ },
205
+ [observe, source, controller],
206
+ );
207
+ useEffect(() => {
208
+ if (!observe) return;
209
+ record(controller.getSnapshot(), true);
210
+ return controller.subscribe(() => record(controller.getSnapshot()));
211
+ }, [observe, record, controller, props.spec, props.status]);
212
+ return (action: unknown) => {
213
+ if (!observe || !action || typeof action !== "object") return;
214
+ const values = (action as { $input?: unknown }).$input;
215
+ if (values && typeof values === "object" && !Array.isArray(values)) {
216
+ input.current = Object.fromEntries(
217
+ Object.entries(values).filter(
218
+ (entry): entry is [string, string | boolean] =>
219
+ typeof entry[1] === "string" || typeof entry[1] === "boolean",
220
+ ),
221
+ );
222
+ record(controller.getSnapshot(), true);
223
+ }
224
+ };
225
+ }
226
+
227
+ function ResponseView({
228
+ spec,
229
+ status = "done",
230
+ actions = noActions,
231
+ controller,
232
+ snapshot,
233
+ actionOnly = false,
234
+ retainSteps = false,
235
+ inputSnapshot,
236
+ onRequest,
237
+ onRetry,
238
+ renderResult,
239
+ resolveImage,
240
+ resolveFact,
241
+ copy = defaultCopy,
242
+ className,
243
+ }: StructuredResponseProps & {
244
+ controller: GenerativeActionController;
245
+ snapshot: GenerativeActionSnapshot;
246
+ actionOnly?: boolean;
247
+ inputSnapshot?: Readonly<Record<string, string | boolean>>;
248
+ onRequest?: (input: unknown) => void;
249
+ }) {
250
+ const library = useMemo(
251
+ () => createSuperxdGenerativeLibrary({ resolveImage, resolveFact }),
252
+ [resolveImage, resolveFact],
253
+ );
254
+ let parsed: StructuredSpec | undefined;
255
+ try {
256
+ if (spec != null) parsed = parseStructuredSpec(spec);
257
+ } catch {
258
+ /* Never display raw exceptions or partially validated actions. */
259
+ }
260
+ const [frozen, setFrozen] = useState<StructuredSpec>();
261
+ const busy = ["confirm", "submitting", "failed", "uncertain", "checking"].includes(
262
+ snapshot.stage,
263
+ );
264
+ const display = busy ? (frozen ?? parsed) : parsed;
265
+ const heading = useId();
266
+ const panel = useRef<HTMLDivElement>(null);
267
+ const [origin, setOrigin] = useState<HTMLElement | null>(null);
268
+ const [requestError, setRequestError] = useState(false);
269
+ const result =
270
+ snapshot.stage === "complete" && snapshot.request ? renderResult?.(snapshot.request) : null;
271
+ const hasResult = result != null && result !== false;
272
+ const inspecting = snapshot.request && actions[snapshot.request.action.type]?.kind === "inspect";
273
+ const previous = useRef(snapshot.stage);
274
+ useEffect(() => {
275
+ if (
276
+ previous.current !== snapshot.stage &&
277
+ previous.current === "ready" &&
278
+ snapshot.stage !== "ready" &&
279
+ origin
280
+ )
281
+ panel.current?.focus({ preventScroll: true });
282
+ if (previous.current !== snapshot.stage && hasResult && origin) panel.current?.focus();
283
+ if (previous.current !== snapshot.stage && snapshot.stage === "ready")
284
+ origin?.focus({ preventScroll: true });
285
+ previous.current = snapshot.stage;
286
+ }, [snapshot.stage, origin, hasResult]);
287
+ const request = (input: unknown) => {
288
+ onRequest?.(input);
289
+ setOrigin(document.activeElement as HTMLElement);
290
+ setFrozen(parsed);
291
+ setRequestError(!controller.request(input));
292
+ };
293
+ const updated = busy && frozen !== undefined && JSON.stringify(parsed) !== JSON.stringify(frozen);
294
+ const action = snapshot.request?.action;
295
+ const details = action
296
+ ? Object.entries(action).flatMap(([name, value]) =>
297
+ name === "type"
298
+ ? []
299
+ : ["$input", "$bindings"].includes(name) &&
300
+ value &&
301
+ typeof value === "object" &&
302
+ !Array.isArray(value)
303
+ ? Object.entries(value)
304
+ : [[name, value]],
305
+ )
306
+ : [];
307
+ return (
308
+ <section
309
+ aria-labelledby={heading}
310
+ className={`wrap-anywhere ${className ?? ""}`}
311
+ data-structured-response
312
+ >
313
+ <h2 id={heading} className="sr-only">
314
+ {copy.input}
315
+ </h2>
316
+ <div className="mx-auto flex w-full max-w-2xl min-w-0 flex-col gap-4">
317
+ {updated ? <Callout>{copy.updated}</Callout> : null}
318
+ {requestError ? (
319
+ <Callout tone="error" live="assertive" title={copy.actionInvalid}>
320
+ {copy.actionInvalidNote}
321
+ </Callout>
322
+ ) : null}
323
+ {actionOnly ? null : busy || status === "done" ? (
324
+ display ? (
325
+ <GenerativeRuntimeContext.Provider
326
+ value={{
327
+ blocked: snapshot.stage !== "ready",
328
+ available: new Set(Object.keys(actions)),
329
+ captureInput: onRequest,
330
+ initialValues: inputSnapshot,
331
+ }}
332
+ >
333
+ <div
334
+ className={
335
+ display.$type === "Form"
336
+ ? "min-w-0 rounded-surface border border-line-subtle bg-surface p-5 shadow-control sm:p-6"
337
+ : "min-w-0"
338
+ }
339
+ hidden={snapshot.stage !== "ready"}
340
+ data-generative-content
341
+ >
342
+ {renderGenerativeUI(display, library, { status: "done", dispatch: request })}
343
+ </div>
344
+ </GenerativeRuntimeContext.Provider>
345
+ ) : (
346
+ <Callout
347
+ tone={spec == null ? "neutral" : "error"}
348
+ title={spec == null ? copy.empty : copy.invalid}
349
+ actions={onRetry ? <Button onPress={onRetry}>{copy.retry}</Button> : undefined}
350
+ >
351
+ {spec == null ? undefined : copy.invalidNote}
352
+ </Callout>
353
+ )
354
+ ) : status === "error" ? (
355
+ <Callout
356
+ tone="error"
357
+ title={copy.invalid}
358
+ actions={onRetry ? <Button onPress={onRetry}>{copy.retry}</Button> : undefined}
359
+ >
360
+ {copy.invalidNote}
361
+ </Callout>
362
+ ) : (
363
+ <div
364
+ role="status"
365
+ className="flex flex-col gap-4 rounded-surface border border-line-subtle bg-surface p-6"
366
+ >
367
+ <div className="flex items-center gap-3 type-body text-fg">
368
+ <Spinner />
369
+ {copy.loading}
370
+ </div>
371
+ {status === "streaming" ? (
372
+ <p className="type-body text-fg-subtle">{copy.streaming}</p>
373
+ ) : null}
374
+ <div aria-hidden className="flex flex-col gap-3">
375
+ <span className="h-control-md w-full rounded-control bg-surface-subtle" />
376
+ <span className="h-4 w-2/3 rounded-cell bg-surface-subtle" />
377
+ <span className="h-4 w-1/2 rounded-cell bg-surface-subtle" />
378
+ </div>
379
+ </div>
380
+ )}
381
+ {snapshot.stage !== "ready" ? (
382
+ <div
383
+ ref={panel}
384
+ tabIndex={-1}
385
+ className="rounded-surface border border-line-subtle bg-surface p-5 outline-none focus-visible:focus-ring sm:p-6"
386
+ data-generative-action
387
+ >
388
+ {hasResult ? (
389
+ <div className="flex flex-col gap-5" data-generative-result>
390
+ {!retainSteps ? (
391
+ <Button
392
+ size="sm"
393
+ variant="ghost"
394
+ className="self-start"
395
+ onPress={() => controller.reset()}
396
+ >
397
+ {copy.back}
398
+ </Button>
399
+ ) : null}
400
+ {result}
401
+ </div>
402
+ ) : inspecting && ["submitting", "checking"].includes(snapshot.stage) ? (
403
+ <div role="status" className="flex items-center gap-3 type-body text-fg">
404
+ <Spinner />
405
+ {snapshot.stage === "checking" ? copy.checking : copy.loading}
406
+ </div>
407
+ ) : ["confirm", "submitting", "checking"].includes(snapshot.stage) ? (
408
+ <div className="flex flex-col gap-4">
409
+ <p className="font-mono type-caption text-fg-subtle">{copy.review}</p>
410
+ <h3 className="type-heading text-fg-emphasis">{snapshot.label}</h3>
411
+ <p className="type-body text-fg">{snapshot.description}</p>
412
+ {details.length ? (
413
+ <dl className="flex flex-col gap-3 border-y border-line-subtle py-4">
414
+ {details.map(([name, value], index) => (
415
+ <div key={index} className="flex flex-col gap-1">
416
+ <dt className="font-mono type-caption text-fg-subtle">
417
+ {String(name)
418
+ .replace(/^\$/, "")
419
+ .replace(/([A-Z])/g, " $1")
420
+ .replace(/^./, (letter) => letter.toUpperCase())}
421
+ </dt>
422
+ <dd className="type-body wrap-anywhere whitespace-pre-wrap text-fg-emphasis">
423
+ {value && typeof value === "object"
424
+ ? JSON.stringify(value, null, 2)
425
+ : String(value)}
426
+ </dd>
427
+ </div>
428
+ ))}
429
+ </dl>
430
+ ) : null}
431
+ <div className="flex flex-wrap gap-3">
432
+ <Button
433
+ variant="primary"
434
+ isDisabled={snapshot.stage !== "confirm"}
435
+ aria-busy={snapshot.stage !== "confirm"}
436
+ aria-label={
437
+ snapshot.stage === "checking"
438
+ ? copy.checking
439
+ : snapshot.stage === "submitting"
440
+ ? copy.working
441
+ : copy.confirm
442
+ }
443
+ onPress={() => controller.confirm()}
444
+ >
445
+ {snapshot.stage !== "confirm" ? <Spinner /> : null}
446
+ {snapshot.stage === "checking"
447
+ ? copy.checking
448
+ : snapshot.stage === "submitting"
449
+ ? copy.working
450
+ : copy.confirm}
451
+ </Button>
452
+ <Button
453
+ variant="ghost"
454
+ isDisabled={snapshot.stage !== "confirm"}
455
+ onPress={() => controller.back()}
456
+ >
457
+ {copy.back}
458
+ </Button>
459
+ </div>
460
+ {snapshot.stage !== "confirm" ? (
461
+ <p role="status" className="sr-only">
462
+ {snapshot.stage === "checking" ? copy.checking : copy.working}
463
+ </p>
464
+ ) : null}
465
+ </div>
466
+ ) : snapshot.stage === "failed" ? (
467
+ <Callout
468
+ tone="error"
469
+ title={copy.failed}
470
+ live="polite"
471
+ actions={
472
+ <>
473
+ <Button onPress={() => controller.retry()}>{copy.retry}</Button>
474
+ <Button variant="ghost" onPress={() => controller.back()}>
475
+ {copy.back}
476
+ </Button>
477
+ </>
478
+ }
479
+ >
480
+ {snapshot.message ?? copy.failedNote}
481
+ </Callout>
482
+ ) : snapshot.stage === "uncertain" ? (
483
+ <Callout
484
+ tone="attention"
485
+ title={copy.uncertain}
486
+ live="polite"
487
+ actions={
488
+ <>
489
+ {snapshot.request && actions[snapshot.request.action.type]?.lookup ? (
490
+ <Button onPress={() => controller.check()}>{copy.check}</Button>
491
+ ) : null}
492
+ <Button onPress={() => controller.retry()}>{copy.retry}</Button>
493
+ </>
494
+ }
495
+ >
496
+ {snapshot.noReceipt ? copy.noReceipt : copy.uncertainNote}
497
+ </Callout>
498
+ ) : (
499
+ <Callout
500
+ tone={snapshot.stage === "complete" ? "success" : "neutral"}
501
+ title={snapshot.stage === "complete" ? copy.complete : copy.dismissed}
502
+ live="polite"
503
+ actions={
504
+ retainSteps ? undefined : (
505
+ <Button onPress={() => controller.reset()}>{copy.back}</Button>
506
+ )
507
+ }
508
+ >
509
+ {snapshot.message}
510
+ </Callout>
511
+ )}
512
+ </div>
513
+ ) : null}
514
+ </div>
515
+ </section>
516
+ );
517
+ }