@assistant-ui/react-generative-ui 0.0.17 → 0.0.18
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/dist/a2ui/convert.d.ts.map +1 -1
- package/dist/a2ui/convert.js +11 -2
- package/dist/a2ui/convert.js.map +1 -1
- package/dist/a2ui/reducer.d.ts.map +1 -1
- package/dist/a2ui/reducer.js +63 -10
- package/dist/a2ui/reducer.js.map +1 -1
- package/dist/buildPresentParameters.js +4 -3
- package/dist/buildPresentParameters.js.map +1 -1
- package/dist/renderGenerativeUI.js +1 -1
- package/dist/renderGenerativeUI.js.map +1 -1
- package/dist/vocabulary/alert.d.ts +1 -1
- package/dist/vocabulary/alert.d.ts.map +1 -1
- package/dist/vocabulary/alert.js +21 -16
- package/dist/vocabulary/alert.js.map +1 -1
- package/dist/vocabulary/data.d.ts.map +1 -1
- package/dist/vocabulary/data.js +20 -12
- package/dist/vocabulary/data.js.map +1 -1
- package/dist/vocabulary/fact.d.ts.map +1 -1
- package/dist/vocabulary/fact.js +3 -2
- package/dist/vocabulary/fact.js.map +1 -1
- package/dist/vocabulary/interactive.d.ts +6 -5
- package/dist/vocabulary/interactive.d.ts.map +1 -1
- package/dist/vocabulary/interactive.js +28 -23
- package/dist/vocabulary/interactive.js.map +1 -1
- package/dist/vocabulary/layout.d.ts.map +1 -1
- package/dist/vocabulary/layout.js +7 -5
- package/dist/vocabulary/layout.js.map +1 -1
- package/dist/vocabulary/text.d.ts.map +1 -1
- package/dist/vocabulary/text.js +4 -3
- package/dist/vocabulary/text.js.map +1 -1
- package/dist/vocabulary/toTextContent.d.ts +5 -0
- package/dist/vocabulary/toTextContent.d.ts.map +1 -0
- package/dist/vocabulary/toTextContent.js +6 -0
- package/dist/vocabulary/toTextContent.js.map +1 -0
- package/package.json +4 -4
- package/src/a2ui/convert.test.ts +36 -0
- package/src/a2ui/convert.ts +21 -2
- package/src/a2ui/reducer.test.ts +223 -0
- package/src/a2ui/reducer.ts +76 -12
- package/src/buildPresentParameters.ts +7 -4
- package/src/renderGenerativeUI.test.tsx +57 -0
- package/src/renderGenerativeUI.tsx +3 -1
- package/src/vocabulary/alert.test.tsx +10 -0
- package/src/vocabulary/alert.tsx +16 -7
- package/src/vocabulary/data.test.tsx +40 -0
- package/src/vocabulary/data.tsx +47 -26
- package/src/vocabulary/fact.test.tsx +12 -0
- package/src/vocabulary/fact.tsx +3 -2
- package/src/vocabulary/interactive.test.tsx +41 -0
- package/src/vocabulary/interactive.tsx +42 -28
- package/src/vocabulary/layout.test.tsx +16 -0
- package/src/vocabulary/layout.tsx +8 -4
- package/src/vocabulary/text.test.tsx +29 -0
- package/src/vocabulary/text.tsx +4 -3
- package/src/vocabulary/toTextContent.ts +2 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"convert.d.ts","names":[],"sources":["../../src/a2ui/convert.ts"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"convert.d.ts","names":[],"sources":["../../src/a2ui/convert.ts"],"mappings":";;;iBAscgB,uBAAuB,SAAS;EAC9C,MAAM;EACN"}
|
package/dist/a2ui/convert.js
CHANGED
|
@@ -20,6 +20,15 @@ const isPlainObject = (value) => {
|
|
|
20
20
|
const prototype = Object.getPrototypeOf(value);
|
|
21
21
|
return prototype === Object.prototype || prototype === null;
|
|
22
22
|
};
|
|
23
|
+
const setOwnProperty = (target, key, value) => {
|
|
24
|
+
if (key === "__proto__") Object.defineProperty(target, key, {
|
|
25
|
+
value,
|
|
26
|
+
enumerable: true,
|
|
27
|
+
configurable: true,
|
|
28
|
+
writable: true
|
|
29
|
+
});
|
|
30
|
+
else target[key] = value;
|
|
31
|
+
};
|
|
23
32
|
const isBinding = (value) => isPlainObject(value) && Object.keys(value).length === 1 && typeof value["path"] === "string";
|
|
24
33
|
const isTemplateChildren = (value) => isPlainObject(value) && Object.keys(value).length === 1 && isPlainObject(value["template"]) && Object.keys(value["template"]).length === 2 && typeof value["template"]["componentId"] === "string" && typeof value["template"]["path"] === "string";
|
|
25
34
|
const decodePointer = (path) => {
|
|
@@ -54,7 +63,7 @@ const materialize = (value, source) => {
|
|
|
54
63
|
const result = {};
|
|
55
64
|
for (const [key, entry] of Object.entries(value)) {
|
|
56
65
|
const resolved = materialize(entry, source);
|
|
57
|
-
if (resolved !== void 0) result
|
|
66
|
+
if (resolved !== void 0) setOwnProperty(result, key, resolved);
|
|
58
67
|
}
|
|
59
68
|
return result;
|
|
60
69
|
};
|
|
@@ -274,7 +283,7 @@ function convertComponent(componentId, dataSource, context, depth, visited) {
|
|
|
274
283
|
for (const [key, value] of Object.entries(node)) {
|
|
275
284
|
if (key === "id" || key === "component" || key === "children") continue;
|
|
276
285
|
const resolved = materialize(value, dataSource);
|
|
277
|
-
if (resolved !== void 0) props
|
|
286
|
+
if (resolved !== void 0) setOwnProperty(props, key, resolved);
|
|
278
287
|
}
|
|
279
288
|
const mapped = mappedProps(node, props, dataSource, context);
|
|
280
289
|
if (!mapped) return null;
|
package/dist/a2ui/convert.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"convert.js","names":[],"sources":["../../src/a2ui/convert.ts"],"sourcesContent":["import type { UIElement } from \"../ir\";\nimport {\n A2UI_SURFACE_ID,\n type A2uiSurfaceState,\n type A2uiTemplateChildren,\n} from \"./types\";\n\nconst DEPTH_CAP = 32;\nconst TEMPLATE_ITEM_CAP = 100;\nconst NODE_BUDGET = 5000;\n\nconst SUPPORTED_COMPONENTS = new Set([\n \"Text\",\n \"Image\",\n \"Row\",\n \"Column\",\n \"Card\",\n \"Divider\",\n \"Button\",\n \"TextField\",\n \"CheckBox\",\n]);\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null && !Array.isArray(value);\n\nconst isPlainObject = (value: unknown): value is Record<string, unknown> => {\n if (!isRecord(value)) return false;\n const prototype = Object.getPrototypeOf(value);\n return prototype === Object.prototype || prototype === null;\n};\n\nconst isBinding = (value: unknown): value is { readonly path: string } =>\n isPlainObject(value) &&\n Object.keys(value).length === 1 &&\n typeof value[\"path\"] === \"string\";\n\nconst isTemplateChildren = (value: unknown): value is A2uiTemplateChildren =>\n isPlainObject(value) &&\n Object.keys(value).length === 1 &&\n isPlainObject(value[\"template\"]) &&\n Object.keys(value[\"template\"]).length === 2 &&\n typeof value[\"template\"][\"componentId\"] === \"string\" &&\n typeof value[\"template\"][\"path\"] === \"string\";\n\nconst decodePointer = (path: string): string[] | undefined => {\n if (path === \"\" || path === \"/\") return [];\n if (!path.startsWith(\"/\")) return undefined;\n return path\n .slice(1)\n .split(\"/\")\n .map((segment) => segment.replaceAll(\"~1\", \"/\").replaceAll(\"~0\", \"~\"));\n};\n\nconst resolvePointer = (source: unknown, path: string): unknown => {\n const segments = decodePointer(path);\n if (!segments) return undefined;\n let current = source;\n for (const segment of segments) {\n if (Array.isArray(current)) {\n if (!/^(0|[1-9]\\d*)$/.test(segment)) return undefined;\n current = current[Number(segment)];\n continue;\n }\n if (!isRecord(current) || !Object.hasOwn(current, segment)) {\n return undefined;\n }\n current = current[segment];\n }\n return current;\n};\n\nconst bindingPath = (value: unknown): string | undefined =>\n isBinding(value) ? value.path : undefined;\n\nconst lastPointerSegment = (path: string | undefined): string | undefined => {\n if (!path) return undefined;\n const segments = decodePointer(path);\n return segments?.at(-1);\n};\n\nconst materialize = (value: unknown, source: unknown): unknown => {\n if (isBinding(value)) return resolvePointer(source, value.path);\n if (Array.isArray(value)) {\n return value\n .map((entry) => materialize(entry, source))\n .filter((entry) => entry !== undefined);\n }\n if (!isPlainObject(value)) return value;\n const result: Record<string, unknown> = {};\n for (const [key, entry] of Object.entries(value)) {\n const resolved = materialize(entry, source);\n if (resolved !== undefined) result[key] = resolved;\n }\n return result;\n};\n\nconst firstDefined = (\n props: Record<string, unknown>,\n keys: readonly string[],\n): unknown => {\n for (const key of keys) {\n if (props[key] !== undefined) return props[key];\n }\n return undefined;\n};\n\nconst stringProp = (\n props: Record<string, unknown>,\n keys: readonly string[],\n): string | undefined => {\n const value = firstDefined(props, keys);\n return typeof value === \"string\" ? value : undefined;\n};\n\nconst sourceSurfaceId = (surface: A2uiSurfaceState): string =>\n (surface as A2uiSurfaceState & { [A2UI_SURFACE_ID]?: string })[\n A2UI_SURFACE_ID\n ] ?? \"\";\n\ntype ConversionContext = {\n readonly surface: A2uiSurfaceState;\n readonly surfaceId: string;\n readonly warnings: string[];\n emittedNodes: number;\n depthWarned: boolean;\n budgetWarned: boolean;\n templateCapWarned: boolean;\n};\n\nconst reserveNode = (context: ConversionContext): boolean => {\n if (context.emittedNodes >= NODE_BUDGET) {\n if (!context.budgetWarned) {\n context.warnings.push(`A2UI node budget of ${NODE_BUDGET} was reached.`);\n context.budgetWarned = true;\n }\n return false;\n }\n context.emittedNodes++;\n return true;\n};\n\nconst childrenOf = (\n node: Record<string, unknown>,\n dataSource: unknown,\n context: ConversionContext,\n depth: number,\n visited: Set<string>,\n): UIElement[] => {\n const children = node[\"children\"];\n if (!Array.isArray(children)) return [];\n const result: UIElement[] = [];\n for (const childId of children) {\n if (typeof childId !== \"string\") {\n context.warnings.push(\n `Component \"${String(node[\"id\"] ?? \"\")}\" has a malformed child reference.`,\n );\n continue;\n }\n const child = convertComponent(\n childId,\n dataSource,\n context,\n depth + 1,\n visited,\n );\n if (child) result.push(child);\n }\n return result;\n};\n\nconst mappedAction = (\n node: Record<string, unknown>,\n props: Record<string, unknown>,\n dataSource: unknown,\n context: ConversionContext,\n): UIElement[\"$action\"] | undefined => {\n const action = props[\"action\"];\n const actionName =\n typeof action === \"string\"\n ? action\n : isRecord(action) && typeof action[\"name\"] === \"string\"\n ? action[\"name\"]\n : undefined;\n if (!actionName) return undefined;\n const rawContext = isRecord(action) ? action[\"context\"] : undefined;\n const resolvedContext =\n rawContext === undefined ? undefined : materialize(rawContext, dataSource);\n return {\n type: \"a2ui:action\",\n name: actionName,\n surfaceId: context.surfaceId,\n sourceComponentId: typeof node[\"id\"] === \"string\" ? node[\"id\"] : \"\",\n ...(resolvedContext !== undefined ? { context: resolvedContext } : {}),\n };\n};\n\nconst mappedProps = (\n node: Record<string, unknown>,\n props: Record<string, unknown>,\n dataSource: unknown,\n context: ConversionContext,\n): UIElement | undefined => {\n const component = node[\"component\"];\n\n if (component === \"Text\") {\n const text = stringProp(props, [\"text\", \"value\"]);\n const variant = props[\"variant\"];\n if (typeof variant === \"string\" && /^h[1-6]$/.test(variant)) {\n return {\n $type: \"Header\",\n ...(text !== undefined ? { text } : {}),\n };\n }\n if (variant === \"caption\") {\n return {\n $type: \"Caption\",\n ...(text !== undefined ? { value: text } : {}),\n };\n }\n return {\n $type: \"Markdown\",\n ...(text !== undefined ? { value: text } : {}),\n };\n }\n\n if (component === \"Image\") {\n const src = stringProp(props, [\"src\", \"url\"]);\n const alt = stringProp(props, [\"alt\", \"altText\"]);\n const size = props[\"size\"];\n const round = props[\"round\"];\n return {\n $type: \"Image\",\n ...(src !== undefined ? { src } : {}),\n ...(alt !== undefined ? { alt } : {}),\n ...(typeof size === \"string\" || typeof size === \"number\" ? { size } : {}),\n ...(typeof round === \"boolean\" ? { round } : {}),\n };\n }\n\n if (component === \"Row\") {\n const gap = props[\"gap\"];\n const align = firstDefined(props, [\"align\", \"alignment\"]);\n const justify = firstDefined(props, [\"justify\", \"distribution\"]);\n return {\n $type: \"Row\",\n ...(typeof gap === \"number\" ? { gap } : {}),\n ...(typeof align === \"string\" ? { align } : {}),\n ...(typeof justify === \"string\" ? { justify } : {}),\n };\n }\n\n if (component === \"Column\") {\n const gap = props[\"gap\"];\n const align = firstDefined(props, [\"align\", \"alignment\"]);\n return {\n $type: \"Col\",\n ...(typeof gap === \"number\" ? { gap } : {}),\n ...(typeof align === \"string\" ? { align } : {}),\n };\n }\n\n if (component === \"Card\") {\n const title = props[\"title\"];\n const padding = props[\"padding\"];\n const background = props[\"background\"];\n return {\n $type: \"Card\",\n ...(typeof title === \"string\" ? { title } : {}),\n ...(typeof padding === \"number\" ? { padding } : {}),\n ...(typeof background === \"string\" ? { background } : {}),\n };\n }\n\n if (component === \"Divider\") {\n return {\n $type: \"Divider\",\n ...(typeof props[\"flush\"] === \"boolean\" ? { flush: props[\"flush\"] } : {}),\n };\n }\n\n if (component === \"Button\") {\n const label = stringProp(props, [\"label\", \"text\"]);\n const buttonStyle = props[\"buttonStyle\"];\n const block = props[\"block\"];\n const submit = props[\"submit\"];\n const action = mappedAction(node, props, dataSource, context);\n return {\n $type: \"Button\",\n ...(label !== undefined ? { label } : {}),\n ...(typeof buttonStyle === \"string\" ? { buttonStyle } : {}),\n ...(typeof block === \"boolean\" ? { block } : {}),\n ...(typeof submit === \"boolean\" ? { submit } : {}),\n ...(action ? { $action: action } : {}),\n };\n }\n\n const binding = firstDefined(node, [\"text\", \"value\", \"binding\"]);\n const name = lastPointerSegment(bindingPath(binding));\n const label = stringProp(props, [\"label\"]);\n\n if (component === \"TextField\") {\n const placeholder = stringProp(props, [\"placeholder\"]);\n const multiline =\n typeof props[\"multiline\"] === \"boolean\"\n ? props[\"multiline\"]\n : props[\"textFieldType\"] === \"longText\"\n ? true\n : undefined;\n return {\n $type: \"Input\",\n ...(placeholder !== undefined ? { placeholder } : {}),\n ...(multiline !== undefined ? { multiline } : {}),\n ...(label !== undefined ? { label } : {}),\n ...(name !== undefined ? { name } : {}),\n };\n }\n\n if (component === \"CheckBox\") {\n const defaultChecked = firstDefined(props, [\"defaultChecked\", \"value\"]);\n return {\n $type: \"Checkbox\",\n ...(label !== undefined ? { label } : {}),\n ...(name !== undefined ? { name } : {}),\n ...(typeof defaultChecked === \"boolean\" ? { defaultChecked } : {}),\n };\n }\n\n return undefined;\n};\n\nconst convertTemplate = (\n node: Record<string, unknown>,\n templateChildren: A2uiTemplateChildren,\n dataSource: unknown,\n context: ConversionContext,\n depth: number,\n visited: Set<string>,\n): UIElement | null => {\n if (!reserveNode(context)) return null;\n const list = resolvePointer(dataSource, templateChildren.template.path);\n if (!Array.isArray(list)) {\n context.warnings.push(\n `Template on component \"${String(node[\"id\"] ?? \"\")}\" did not resolve to a list.`,\n );\n return { $type: \"ListView\", children: [] };\n }\n const itemCount = Math.min(list.length, TEMPLATE_ITEM_CAP);\n if (list.length > TEMPLATE_ITEM_CAP && !context.templateCapWarned) {\n context.warnings.push(\n `A2UI template expansion was capped at ${TEMPLATE_ITEM_CAP} items.`,\n );\n context.templateCapWarned = true;\n }\n const children: UIElement[] = [];\n for (let index = 0; index < itemCount; index++) {\n if (!reserveNode(context)) break;\n const child = convertComponent(\n templateChildren.template.componentId,\n list[index],\n context,\n depth + 1,\n visited,\n );\n children.push({\n $type: \"ListViewItem\",\n ...(child ? { children: child } : {}),\n });\n }\n return { $type: \"ListView\", children };\n};\n\nfunction convertComponent(\n componentId: string,\n dataSource: unknown,\n context: ConversionContext,\n depth: number,\n visited: Set<string>,\n): UIElement | null {\n if (depth >= DEPTH_CAP) {\n if (!context.depthWarned) {\n context.warnings.push(`A2UI depth cap of ${DEPTH_CAP} was reached.`);\n context.depthWarned = true;\n }\n return null;\n }\n if (visited.has(componentId)) {\n context.warnings.push(`A2UI component cycle detected at \"${componentId}\".`);\n return null;\n }\n const node = context.surface.components.get(componentId);\n if (!node) {\n context.warnings.push(`A2UI component \"${componentId}\" was not found.`);\n return null;\n }\n const component = node[\"component\"];\n\n visited.add(componentId);\n try {\n const templateChildren = node[\"children\"];\n if (isTemplateChildren(templateChildren)) {\n return convertTemplate(\n node,\n templateChildren,\n dataSource,\n context,\n depth,\n visited,\n );\n }\n if (typeof component !== \"string\" || !SUPPORTED_COMPONENTS.has(component)) {\n context.warnings.push(\n `Unknown A2UI component \"${String(component ?? \"\")}\" was skipped.`,\n );\n return null;\n }\n if (!reserveNode(context)) return null;\n const props: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(node)) {\n if (key === \"id\" || key === \"component\" || key === \"children\") continue;\n const resolved = materialize(value, dataSource);\n if (resolved !== undefined) props[key] = resolved;\n }\n const mapped = mappedProps(node, props, dataSource, context);\n if (!mapped) return null;\n const children = childrenOf(node, dataSource, context, depth, visited);\n return {\n ...mapped,\n ...(children.length > 0 ? { children } : {}),\n };\n } finally {\n visited.delete(componentId);\n }\n}\n\nexport function convertSurfaceToUISpec(surface: A2uiSurfaceState): {\n spec: UIElement | null;\n warnings: string[];\n} {\n const warnings: string[] = [];\n if (!surface.components.has(\"root\")) {\n return {\n spec: null,\n warnings: ['A2UI root component \"root\" was not found.'],\n };\n }\n const context: ConversionContext = {\n surface,\n surfaceId: sourceSurfaceId(surface),\n warnings,\n emittedNodes: 0,\n depthWarned: false,\n budgetWarned: false,\n templateCapWarned: false,\n };\n try {\n const spec = convertComponent(\n \"root\",\n surface.dataModel,\n context,\n 0,\n new Set(),\n );\n return { spec, warnings };\n } catch {\n warnings.push(\"A2UI surface conversion encountered malformed input.\");\n return { spec: null, warnings };\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"convert.js","names":[],"sources":["../../src/a2ui/convert.ts"],"sourcesContent":["import type { UIElement } from \"../ir\";\nimport {\n A2UI_SURFACE_ID,\n type A2uiSurfaceState,\n type A2uiTemplateChildren,\n} from \"./types\";\n\nconst DEPTH_CAP = 32;\nconst TEMPLATE_ITEM_CAP = 100;\nconst NODE_BUDGET = 5000;\n\nconst SUPPORTED_COMPONENTS = new Set([\n \"Text\",\n \"Image\",\n \"Row\",\n \"Column\",\n \"Card\",\n \"Divider\",\n \"Button\",\n \"TextField\",\n \"CheckBox\",\n]);\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null && !Array.isArray(value);\n\nconst isPlainObject = (value: unknown): value is Record<string, unknown> => {\n if (!isRecord(value)) return false;\n const prototype = Object.getPrototypeOf(value);\n return prototype === Object.prototype || prototype === null;\n};\n\nconst setOwnProperty = (\n target: Record<string, unknown>,\n key: string,\n value: unknown,\n) => {\n if (key === \"__proto__\") {\n Object.defineProperty(target, key, {\n value,\n enumerable: true,\n configurable: true,\n writable: true,\n });\n } else {\n target[key] = value;\n }\n};\n\nconst isBinding = (value: unknown): value is { readonly path: string } =>\n isPlainObject(value) &&\n Object.keys(value).length === 1 &&\n typeof value[\"path\"] === \"string\";\n\nconst isTemplateChildren = (value: unknown): value is A2uiTemplateChildren =>\n isPlainObject(value) &&\n Object.keys(value).length === 1 &&\n isPlainObject(value[\"template\"]) &&\n Object.keys(value[\"template\"]).length === 2 &&\n typeof value[\"template\"][\"componentId\"] === \"string\" &&\n typeof value[\"template\"][\"path\"] === \"string\";\n\nconst decodePointer = (path: string): string[] | undefined => {\n if (path === \"\" || path === \"/\") return [];\n if (!path.startsWith(\"/\")) return undefined;\n return path\n .slice(1)\n .split(\"/\")\n .map((segment) => segment.replaceAll(\"~1\", \"/\").replaceAll(\"~0\", \"~\"));\n};\n\nconst resolvePointer = (source: unknown, path: string): unknown => {\n const segments = decodePointer(path);\n if (!segments) return undefined;\n let current = source;\n for (const segment of segments) {\n if (Array.isArray(current)) {\n if (!/^(0|[1-9]\\d*)$/.test(segment)) return undefined;\n current = current[Number(segment)];\n continue;\n }\n if (!isRecord(current) || !Object.hasOwn(current, segment)) {\n return undefined;\n }\n current = current[segment];\n }\n return current;\n};\n\nconst bindingPath = (value: unknown): string | undefined =>\n isBinding(value) ? value.path : undefined;\n\nconst lastPointerSegment = (path: string | undefined): string | undefined => {\n if (!path) return undefined;\n const segments = decodePointer(path);\n return segments?.at(-1);\n};\n\nconst materialize = (value: unknown, source: unknown): unknown => {\n if (isBinding(value)) return resolvePointer(source, value.path);\n if (Array.isArray(value)) {\n return value\n .map((entry) => materialize(entry, source))\n .filter((entry) => entry !== undefined);\n }\n if (!isPlainObject(value)) return value;\n const result: Record<string, unknown> = {};\n for (const [key, entry] of Object.entries(value)) {\n const resolved = materialize(entry, source);\n if (resolved !== undefined) {\n setOwnProperty(result, key, resolved);\n }\n }\n return result;\n};\n\nconst firstDefined = (\n props: Record<string, unknown>,\n keys: readonly string[],\n): unknown => {\n for (const key of keys) {\n if (props[key] !== undefined) return props[key];\n }\n return undefined;\n};\n\nconst stringProp = (\n props: Record<string, unknown>,\n keys: readonly string[],\n): string | undefined => {\n const value = firstDefined(props, keys);\n return typeof value === \"string\" ? value : undefined;\n};\n\nconst sourceSurfaceId = (surface: A2uiSurfaceState): string =>\n (surface as A2uiSurfaceState & { [A2UI_SURFACE_ID]?: string })[\n A2UI_SURFACE_ID\n ] ?? \"\";\n\ntype ConversionContext = {\n readonly surface: A2uiSurfaceState;\n readonly surfaceId: string;\n readonly warnings: string[];\n emittedNodes: number;\n depthWarned: boolean;\n budgetWarned: boolean;\n templateCapWarned: boolean;\n};\n\nconst reserveNode = (context: ConversionContext): boolean => {\n if (context.emittedNodes >= NODE_BUDGET) {\n if (!context.budgetWarned) {\n context.warnings.push(`A2UI node budget of ${NODE_BUDGET} was reached.`);\n context.budgetWarned = true;\n }\n return false;\n }\n context.emittedNodes++;\n return true;\n};\n\nconst childrenOf = (\n node: Record<string, unknown>,\n dataSource: unknown,\n context: ConversionContext,\n depth: number,\n visited: Set<string>,\n): UIElement[] => {\n const children = node[\"children\"];\n if (!Array.isArray(children)) return [];\n const result: UIElement[] = [];\n for (const childId of children) {\n if (typeof childId !== \"string\") {\n context.warnings.push(\n `Component \"${String(node[\"id\"] ?? \"\")}\" has a malformed child reference.`,\n );\n continue;\n }\n const child = convertComponent(\n childId,\n dataSource,\n context,\n depth + 1,\n visited,\n );\n if (child) result.push(child);\n }\n return result;\n};\n\nconst mappedAction = (\n node: Record<string, unknown>,\n props: Record<string, unknown>,\n dataSource: unknown,\n context: ConversionContext,\n): UIElement[\"$action\"] | undefined => {\n const action = props[\"action\"];\n const actionName =\n typeof action === \"string\"\n ? action\n : isRecord(action) && typeof action[\"name\"] === \"string\"\n ? action[\"name\"]\n : undefined;\n if (!actionName) return undefined;\n const rawContext = isRecord(action) ? action[\"context\"] : undefined;\n const resolvedContext =\n rawContext === undefined ? undefined : materialize(rawContext, dataSource);\n return {\n type: \"a2ui:action\",\n name: actionName,\n surfaceId: context.surfaceId,\n sourceComponentId: typeof node[\"id\"] === \"string\" ? node[\"id\"] : \"\",\n ...(resolvedContext !== undefined ? { context: resolvedContext } : {}),\n };\n};\n\nconst mappedProps = (\n node: Record<string, unknown>,\n props: Record<string, unknown>,\n dataSource: unknown,\n context: ConversionContext,\n): UIElement | undefined => {\n const component = node[\"component\"];\n\n if (component === \"Text\") {\n const text = stringProp(props, [\"text\", \"value\"]);\n const variant = props[\"variant\"];\n if (typeof variant === \"string\" && /^h[1-6]$/.test(variant)) {\n return {\n $type: \"Header\",\n ...(text !== undefined ? { text } : {}),\n };\n }\n if (variant === \"caption\") {\n return {\n $type: \"Caption\",\n ...(text !== undefined ? { value: text } : {}),\n };\n }\n return {\n $type: \"Markdown\",\n ...(text !== undefined ? { value: text } : {}),\n };\n }\n\n if (component === \"Image\") {\n const src = stringProp(props, [\"src\", \"url\"]);\n const alt = stringProp(props, [\"alt\", \"altText\"]);\n const size = props[\"size\"];\n const round = props[\"round\"];\n return {\n $type: \"Image\",\n ...(src !== undefined ? { src } : {}),\n ...(alt !== undefined ? { alt } : {}),\n ...(typeof size === \"string\" || typeof size === \"number\" ? { size } : {}),\n ...(typeof round === \"boolean\" ? { round } : {}),\n };\n }\n\n if (component === \"Row\") {\n const gap = props[\"gap\"];\n const align = firstDefined(props, [\"align\", \"alignment\"]);\n const justify = firstDefined(props, [\"justify\", \"distribution\"]);\n return {\n $type: \"Row\",\n ...(typeof gap === \"number\" ? { gap } : {}),\n ...(typeof align === \"string\" ? { align } : {}),\n ...(typeof justify === \"string\" ? { justify } : {}),\n };\n }\n\n if (component === \"Column\") {\n const gap = props[\"gap\"];\n const align = firstDefined(props, [\"align\", \"alignment\"]);\n return {\n $type: \"Col\",\n ...(typeof gap === \"number\" ? { gap } : {}),\n ...(typeof align === \"string\" ? { align } : {}),\n };\n }\n\n if (component === \"Card\") {\n const title = props[\"title\"];\n const padding = props[\"padding\"];\n const background = props[\"background\"];\n return {\n $type: \"Card\",\n ...(typeof title === \"string\" ? { title } : {}),\n ...(typeof padding === \"number\" ? { padding } : {}),\n ...(typeof background === \"string\" ? { background } : {}),\n };\n }\n\n if (component === \"Divider\") {\n return {\n $type: \"Divider\",\n ...(typeof props[\"flush\"] === \"boolean\" ? { flush: props[\"flush\"] } : {}),\n };\n }\n\n if (component === \"Button\") {\n const label = stringProp(props, [\"label\", \"text\"]);\n const buttonStyle = props[\"buttonStyle\"];\n const block = props[\"block\"];\n const submit = props[\"submit\"];\n const action = mappedAction(node, props, dataSource, context);\n return {\n $type: \"Button\",\n ...(label !== undefined ? { label } : {}),\n ...(typeof buttonStyle === \"string\" ? { buttonStyle } : {}),\n ...(typeof block === \"boolean\" ? { block } : {}),\n ...(typeof submit === \"boolean\" ? { submit } : {}),\n ...(action ? { $action: action } : {}),\n };\n }\n\n const binding = firstDefined(node, [\"text\", \"value\", \"binding\"]);\n const name = lastPointerSegment(bindingPath(binding));\n const label = stringProp(props, [\"label\"]);\n\n if (component === \"TextField\") {\n const placeholder = stringProp(props, [\"placeholder\"]);\n const multiline =\n typeof props[\"multiline\"] === \"boolean\"\n ? props[\"multiline\"]\n : props[\"textFieldType\"] === \"longText\"\n ? true\n : undefined;\n return {\n $type: \"Input\",\n ...(placeholder !== undefined ? { placeholder } : {}),\n ...(multiline !== undefined ? { multiline } : {}),\n ...(label !== undefined ? { label } : {}),\n ...(name !== undefined ? { name } : {}),\n };\n }\n\n if (component === \"CheckBox\") {\n const defaultChecked = firstDefined(props, [\"defaultChecked\", \"value\"]);\n return {\n $type: \"Checkbox\",\n ...(label !== undefined ? { label } : {}),\n ...(name !== undefined ? { name } : {}),\n ...(typeof defaultChecked === \"boolean\" ? { defaultChecked } : {}),\n };\n }\n\n return undefined;\n};\n\nconst convertTemplate = (\n node: Record<string, unknown>,\n templateChildren: A2uiTemplateChildren,\n dataSource: unknown,\n context: ConversionContext,\n depth: number,\n visited: Set<string>,\n): UIElement | null => {\n if (!reserveNode(context)) return null;\n const list = resolvePointer(dataSource, templateChildren.template.path);\n if (!Array.isArray(list)) {\n context.warnings.push(\n `Template on component \"${String(node[\"id\"] ?? \"\")}\" did not resolve to a list.`,\n );\n return { $type: \"ListView\", children: [] };\n }\n const itemCount = Math.min(list.length, TEMPLATE_ITEM_CAP);\n if (list.length > TEMPLATE_ITEM_CAP && !context.templateCapWarned) {\n context.warnings.push(\n `A2UI template expansion was capped at ${TEMPLATE_ITEM_CAP} items.`,\n );\n context.templateCapWarned = true;\n }\n const children: UIElement[] = [];\n for (let index = 0; index < itemCount; index++) {\n if (!reserveNode(context)) break;\n const child = convertComponent(\n templateChildren.template.componentId,\n list[index],\n context,\n depth + 1,\n visited,\n );\n children.push({\n $type: \"ListViewItem\",\n ...(child ? { children: child } : {}),\n });\n }\n return { $type: \"ListView\", children };\n};\n\nfunction convertComponent(\n componentId: string,\n dataSource: unknown,\n context: ConversionContext,\n depth: number,\n visited: Set<string>,\n): UIElement | null {\n if (depth >= DEPTH_CAP) {\n if (!context.depthWarned) {\n context.warnings.push(`A2UI depth cap of ${DEPTH_CAP} was reached.`);\n context.depthWarned = true;\n }\n return null;\n }\n if (visited.has(componentId)) {\n context.warnings.push(`A2UI component cycle detected at \"${componentId}\".`);\n return null;\n }\n const node = context.surface.components.get(componentId);\n if (!node) {\n context.warnings.push(`A2UI component \"${componentId}\" was not found.`);\n return null;\n }\n const component = node[\"component\"];\n\n visited.add(componentId);\n try {\n const templateChildren = node[\"children\"];\n if (isTemplateChildren(templateChildren)) {\n return convertTemplate(\n node,\n templateChildren,\n dataSource,\n context,\n depth,\n visited,\n );\n }\n if (typeof component !== \"string\" || !SUPPORTED_COMPONENTS.has(component)) {\n context.warnings.push(\n `Unknown A2UI component \"${String(component ?? \"\")}\" was skipped.`,\n );\n return null;\n }\n if (!reserveNode(context)) return null;\n const props: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(node)) {\n if (key === \"id\" || key === \"component\" || key === \"children\") continue;\n const resolved = materialize(value, dataSource);\n if (resolved !== undefined) setOwnProperty(props, key, resolved);\n }\n const mapped = mappedProps(node, props, dataSource, context);\n if (!mapped) return null;\n const children = childrenOf(node, dataSource, context, depth, visited);\n return {\n ...mapped,\n ...(children.length > 0 ? { children } : {}),\n };\n } finally {\n visited.delete(componentId);\n }\n}\n\nexport function convertSurfaceToUISpec(surface: A2uiSurfaceState): {\n spec: UIElement | null;\n warnings: string[];\n} {\n const warnings: string[] = [];\n if (!surface.components.has(\"root\")) {\n return {\n spec: null,\n warnings: ['A2UI root component \"root\" was not found.'],\n };\n }\n const context: ConversionContext = {\n surface,\n surfaceId: sourceSurfaceId(surface),\n warnings,\n emittedNodes: 0,\n depthWarned: false,\n budgetWarned: false,\n templateCapWarned: false,\n };\n try {\n const spec = convertComponent(\n \"root\",\n surface.dataModel,\n context,\n 0,\n new Set(),\n );\n return { spec, warnings };\n } catch {\n warnings.push(\"A2UI surface conversion encountered malformed input.\");\n return { spec: null, warnings };\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reducer.d.ts","names":[],"sources":["../../src/a2ui/reducer.ts"],"mappings":";;
|
|
1
|
+
{"version":3,"file":"reducer.d.ts","names":[],"sources":["../../src/a2ui/reducer.ts"],"mappings":";;iBAuNgB,oBACd,OAAO,WACP,sBACC"}
|
package/dist/a2ui/reducer.js
CHANGED
|
@@ -6,6 +6,8 @@ const OPERATION_KEYS = /* @__PURE__ */ new Set([
|
|
|
6
6
|
"updateDataModel",
|
|
7
7
|
"deleteSurface"
|
|
8
8
|
]);
|
|
9
|
+
const MAX_AUTO_VIVIFY_ARRAY_INDEX = 1e4;
|
|
10
|
+
const INVALID_POINTER = Symbol("invalidPointer");
|
|
9
11
|
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
10
12
|
const isVersion = (value) => value === "v0.9" || value === "v1.0";
|
|
11
13
|
const surfaceIdOf = (payload) => {
|
|
@@ -44,7 +46,7 @@ const decodePointer = (path) => {
|
|
|
44
46
|
return path.slice(1).split("/").map((segment) => segment.replaceAll("~1", "/").replaceAll("~0", "~"));
|
|
45
47
|
};
|
|
46
48
|
const isArrayIndex = (segment) => segment === "0" || /^[1-9]\d*$/.test(segment);
|
|
47
|
-
const setAtPointer = (model, path, value) => {
|
|
49
|
+
const setAtPointer = (model, path, value, nullDeletes) => {
|
|
48
50
|
const segments = decodePointer(path);
|
|
49
51
|
if (!segments) return {
|
|
50
52
|
ok: false,
|
|
@@ -54,24 +56,75 @@ const setAtPointer = (model, path, value) => {
|
|
|
54
56
|
ok: true,
|
|
55
57
|
value
|
|
56
58
|
};
|
|
59
|
+
const remove = (current, index) => {
|
|
60
|
+
const segment = segments[index];
|
|
61
|
+
const isLast = index === segments.length - 1;
|
|
62
|
+
if (Array.isArray(current)) {
|
|
63
|
+
if (segment !== "-" && !isArrayIndex(segment)) return current;
|
|
64
|
+
const targetIndex = segment === "-" ? current.length : Number(segment);
|
|
65
|
+
if (isLast) {
|
|
66
|
+
if (segment === "-" || !Object.prototype.hasOwnProperty.call(current, targetIndex)) return current;
|
|
67
|
+
const clone = current.slice();
|
|
68
|
+
delete clone[targetIndex];
|
|
69
|
+
return clone;
|
|
70
|
+
}
|
|
71
|
+
const child = current[targetIndex];
|
|
72
|
+
if (!Array.isArray(child) && !isRecord(child)) return current;
|
|
73
|
+
const next = remove(child, index + 1);
|
|
74
|
+
if (next === child) return current;
|
|
75
|
+
const clone = current.slice();
|
|
76
|
+
clone[targetIndex] = next;
|
|
77
|
+
return clone;
|
|
78
|
+
}
|
|
79
|
+
if (!isRecord(current)) return current;
|
|
80
|
+
if (!Object.prototype.hasOwnProperty.call(current, segment)) return current;
|
|
81
|
+
if (isLast) {
|
|
82
|
+
const clone = { ...current };
|
|
83
|
+
delete clone[segment];
|
|
84
|
+
return clone;
|
|
85
|
+
}
|
|
86
|
+
const child = current[segment];
|
|
87
|
+
if (!Array.isArray(child) && !isRecord(child)) return current;
|
|
88
|
+
const next = remove(child, index + 1);
|
|
89
|
+
if (next === child) return current;
|
|
90
|
+
return {
|
|
91
|
+
...current,
|
|
92
|
+
[segment]: next
|
|
93
|
+
};
|
|
94
|
+
};
|
|
95
|
+
if (nullDeletes && value === null) return {
|
|
96
|
+
ok: true,
|
|
97
|
+
value: remove(model, 0)
|
|
98
|
+
};
|
|
57
99
|
const update = (current, index) => {
|
|
58
100
|
const segment = segments[index];
|
|
59
101
|
const isLast = index === segments.length - 1;
|
|
60
102
|
if (Array.isArray(current)) {
|
|
61
103
|
if (segment !== "-" && !isArrayIndex(segment)) return current;
|
|
104
|
+
const targetIndex = segment === "-" ? current.length : Number(segment);
|
|
105
|
+
if (segment !== "-" && targetIndex >= current.length && targetIndex > MAX_AUTO_VIVIFY_ARRAY_INDEX) return INVALID_POINTER;
|
|
106
|
+
const next = isLast ? value : update(current[targetIndex], index + 1);
|
|
107
|
+
if (next === INVALID_POINTER) return INVALID_POINTER;
|
|
62
108
|
const clone = current.slice();
|
|
63
|
-
|
|
64
|
-
clone[targetIndex] = isLast ? value : update(clone[targetIndex], index + 1);
|
|
109
|
+
clone[targetIndex] = next;
|
|
65
110
|
return clone;
|
|
66
111
|
}
|
|
67
|
-
const
|
|
68
|
-
const child =
|
|
69
|
-
|
|
70
|
-
return
|
|
112
|
+
const record = isRecord(current) ? current : {};
|
|
113
|
+
const child = Object.hasOwn(record, segment) ? record[segment] : void 0;
|
|
114
|
+
const next = isLast ? value : update(child ?? (isArrayIndex(segments[index + 1] ?? "") || segments[index + 1] === "-" ? [] : {}), index + 1);
|
|
115
|
+
if (next === INVALID_POINTER) return INVALID_POINTER;
|
|
116
|
+
return {
|
|
117
|
+
...record,
|
|
118
|
+
[segment]: next
|
|
119
|
+
};
|
|
71
120
|
};
|
|
72
|
-
|
|
121
|
+
const result = update(model, 0);
|
|
122
|
+
return result === INVALID_POINTER ? {
|
|
123
|
+
ok: false,
|
|
124
|
+
value: model
|
|
125
|
+
} : {
|
|
73
126
|
ok: true,
|
|
74
|
-
value:
|
|
127
|
+
value: result
|
|
75
128
|
};
|
|
76
129
|
};
|
|
77
130
|
const dataModelValue = (payload) => {
|
|
@@ -160,7 +213,7 @@ function applyA2uiOperations(state, operations) {
|
|
|
160
213
|
warnings.push(`Operation at index ${index} has a malformed data model path.`);
|
|
161
214
|
continue;
|
|
162
215
|
}
|
|
163
|
-
const result = setAtPointer(surface.dataModel, path, update.value);
|
|
216
|
+
const result = setAtPointer(surface.dataModel, path, update.value, version === "v1.0");
|
|
164
217
|
if (!result.ok) {
|
|
165
218
|
warnings.push(`Operation at index ${index} has an invalid JSON Pointer path.`);
|
|
166
219
|
continue;
|
package/dist/a2ui/reducer.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reducer.js","names":[],"sources":["../../src/a2ui/reducer.ts"],"sourcesContent":["import {\n A2UI_SURFACE_ID,\n type A2uiOperationResult,\n type A2uiState,\n type A2uiSurfaceState,\n} from \"./types\";\n\nconst OPERATION_KEYS = new Set([\n \"createSurface\",\n \"updateComponents\",\n \"updateDataModel\",\n \"deleteSurface\",\n]);\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null && !Array.isArray(value);\n\nconst isVersion = (value: unknown): value is \"v0.9\" | \"v1.0\" =>\n value === \"v0.9\" || value === \"v1.0\";\n\nconst surfaceIdOf = (payload: Record<string, unknown>): string | undefined => {\n const surfaceId = payload[\"surfaceId\"];\n return typeof surfaceId === \"string\" && surfaceId.length > 0\n ? surfaceId\n : undefined;\n};\n\nconst withSurfaceId = (\n surface: A2uiSurfaceState,\n surfaceId: string,\n): A2uiSurfaceState => {\n Object.defineProperty(surface, A2UI_SURFACE_ID, {\n value: surfaceId,\n enumerable: false,\n });\n return surface;\n};\n\nconst getSurfaceId = (surface: A2uiSurfaceState): string | undefined =>\n (surface as A2uiSurfaceState & { [A2UI_SURFACE_ID]?: string })[\n A2UI_SURFACE_ID\n ];\n\nconst cloneSurface = (\n surface: A2uiSurfaceState,\n surfaceId: string,\n): A2uiSurfaceState =>\n withSurfaceId(\n {\n components: new Map(surface.components),\n dataModel: surface.dataModel,\n },\n getSurfaceId(surface) ?? surfaceId,\n );\n\nconst upsertComponents = (\n surface: A2uiSurfaceState,\n components: unknown,\n warnings: string[],\n operationIndex: number,\n): void => {\n if (!Array.isArray(components)) {\n warnings.push(\n `Operation at index ${operationIndex} has malformed components.`,\n );\n return;\n }\n\n for (\n let componentIndex = 0;\n componentIndex < components.length;\n componentIndex++\n ) {\n const component = components[componentIndex];\n if (\n !isRecord(component) ||\n typeof component[\"id\"] !== \"string\" ||\n component[\"id\"].length === 0 ||\n typeof component[\"component\"] !== \"string\" ||\n component[\"component\"].length === 0\n ) {\n warnings.push(\n `Component at index ${componentIndex} in operation ${operationIndex} is malformed.`,\n );\n continue;\n }\n surface.components.set(component[\"id\"], { ...component });\n }\n};\n\nconst decodePointer = (path: string): string[] | undefined => {\n if (path === \"\" || path === \"/\") return [];\n if (!path.startsWith(\"/\")) return undefined;\n return path\n .slice(1)\n .split(\"/\")\n .map((segment) => segment.replaceAll(\"~1\", \"/\").replaceAll(\"~0\", \"~\"));\n};\n\nconst isArrayIndex = (segment: string): boolean =>\n segment === \"0\" || /^[1-9]\\d*$/.test(segment);\n\nconst setAtPointer = (\n model: unknown,\n path: string,\n value: unknown,\n): { readonly ok: boolean; readonly value: unknown } => {\n const segments = decodePointer(path);\n if (!segments) return { ok: false, value: model };\n if (segments.length === 0) return { ok: true, value };\n\n const update = (current: unknown, index: number): unknown => {\n const segment = segments[index]!;\n const isLast = index === segments.length - 1;\n\n if (Array.isArray(current)) {\n if (segment !== \"-\" && !isArrayIndex(segment)) return current;\n const clone = current.slice();\n const targetIndex = segment === \"-\" ? clone.length : Number(segment);\n clone[targetIndex] = isLast\n ? value\n : update(clone[targetIndex], index + 1);\n return clone;\n }\n\n const clone: Record<string, unknown> = isRecord(current)\n ? { ...current }\n : {};\n const child = clone[segment];\n clone[segment] = isLast\n ? value\n : update(\n child ??\n (isArrayIndex(segments[index + 1] ?? \"\") ||\n segments[index + 1] === \"-\"\n ? []\n : {}),\n index + 1,\n );\n return clone;\n };\n\n return { ok: true, value: update(model, 0) };\n};\n\nconst dataModelValue = (\n payload: Record<string, unknown>,\n): { readonly found: boolean; readonly value: unknown } => {\n for (const key of [\"contents\", \"value\", \"data\"] as const) {\n if (payload[key] !== undefined) {\n return { found: true, value: payload[key] };\n }\n }\n return { found: false, value: undefined };\n};\n\nexport function applyA2uiOperations(\n state: A2uiState,\n operations: unknown,\n): A2uiOperationResult {\n const nextState = new Map(state);\n const warnings: string[] = [];\n\n if (!Array.isArray(operations)) {\n return {\n state: nextState,\n warnings: [\"A2UI operations must be an array.\"],\n };\n }\n\n for (let index = 0; index < operations.length; index++) {\n try {\n const entry = operations[index];\n if (!isRecord(entry)) {\n warnings.push(`Operation at index ${index} is malformed.`);\n continue;\n }\n\n const version = entry[\"version\"];\n if (!isVersion(version)) {\n warnings.push(\n `Operation at index ${index} has an unsupported version.`,\n );\n continue;\n }\n\n const operationKeys = Object.keys(entry).filter(\n (key) => key !== \"version\",\n );\n if (operationKeys.length !== 1) {\n warnings.push(\n `Operation at index ${index} must contain exactly one operation key.`,\n );\n continue;\n }\n\n const operationKey = operationKeys[0]!;\n if (!OPERATION_KEYS.has(operationKey)) {\n warnings.push(\n `Unknown A2UI operation \"${operationKey}\" at index ${index}.`,\n );\n continue;\n }\n\n const payload = entry[operationKey];\n if (!isRecord(payload)) {\n warnings.push(`Operation at index ${index} has a malformed payload.`);\n continue;\n }\n\n const surfaceId = surfaceIdOf(payload);\n if (!surfaceId) {\n warnings.push(`Operation at index ${index} has no valid surfaceId.`);\n continue;\n }\n\n if (operationKey === \"createSurface\") {\n const surface = withSurfaceId(\n {\n components: new Map(),\n dataModel:\n version === \"v1.0\" && payload[\"dataModel\"] !== undefined\n ? payload[\"dataModel\"]\n : undefined,\n },\n surfaceId,\n );\n if (version === \"v1.0\" && payload[\"components\"] !== undefined) {\n upsertComponents(surface, payload[\"components\"], warnings, index);\n }\n nextState.set(surfaceId, surface);\n continue;\n }\n\n if (operationKey === \"deleteSurface\") {\n nextState.delete(surfaceId);\n continue;\n }\n\n const currentSurface = nextState.get(surfaceId);\n if (!currentSurface) {\n warnings.push(\n `Operation at index ${index} references missing surface \"${surfaceId}\".`,\n );\n continue;\n }\n const surface = cloneSurface(currentSurface, surfaceId);\n\n if (operationKey === \"updateComponents\") {\n upsertComponents(surface, payload[\"components\"], warnings, index);\n nextState.set(surfaceId, surface);\n continue;\n }\n\n const update = dataModelValue(payload);\n if (!update.found) {\n warnings.push(`Operation at index ${index} has no data model value.`);\n continue;\n }\n const path = payload[\"path\"] ?? \"/\";\n if (typeof path !== \"string\") {\n warnings.push(\n `Operation at index ${index} has a malformed data model path.`,\n );\n continue;\n }\n const result = setAtPointer(surface.dataModel, path, update.value);\n if (!result.ok) {\n warnings.push(\n `Operation at index ${index} has an invalid JSON Pointer path.`,\n );\n continue;\n }\n surface.dataModel = result.value;\n nextState.set(surfaceId, surface);\n } catch {\n warnings.push(`Operation at index ${index} is malformed.`);\n }\n }\n\n return { state: nextState, warnings };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"reducer.js","names":[],"sources":["../../src/a2ui/reducer.ts"],"sourcesContent":["import {\n A2UI_SURFACE_ID,\n type A2uiOperationResult,\n type A2uiState,\n type A2uiSurfaceState,\n} from \"./types\";\n\nconst OPERATION_KEYS = new Set([\n \"createSurface\",\n \"updateComponents\",\n \"updateDataModel\",\n \"deleteSurface\",\n]);\n// This defensive ceiling is well above the renderer's displayed-item limit.\nconst MAX_AUTO_VIVIFY_ARRAY_INDEX = 10_000;\nconst INVALID_POINTER = Symbol(\"invalidPointer\");\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null && !Array.isArray(value);\n\nconst isVersion = (value: unknown): value is \"v0.9\" | \"v1.0\" =>\n value === \"v0.9\" || value === \"v1.0\";\n\nconst surfaceIdOf = (payload: Record<string, unknown>): string | undefined => {\n const surfaceId = payload[\"surfaceId\"];\n return typeof surfaceId === \"string\" && surfaceId.length > 0\n ? surfaceId\n : undefined;\n};\n\nconst withSurfaceId = (\n surface: A2uiSurfaceState,\n surfaceId: string,\n): A2uiSurfaceState => {\n Object.defineProperty(surface, A2UI_SURFACE_ID, {\n value: surfaceId,\n enumerable: false,\n });\n return surface;\n};\n\nconst getSurfaceId = (surface: A2uiSurfaceState): string | undefined =>\n (surface as A2uiSurfaceState & { [A2UI_SURFACE_ID]?: string })[\n A2UI_SURFACE_ID\n ];\n\nconst cloneSurface = (\n surface: A2uiSurfaceState,\n surfaceId: string,\n): A2uiSurfaceState =>\n withSurfaceId(\n {\n components: new Map(surface.components),\n dataModel: surface.dataModel,\n },\n getSurfaceId(surface) ?? surfaceId,\n );\n\nconst upsertComponents = (\n surface: A2uiSurfaceState,\n components: unknown,\n warnings: string[],\n operationIndex: number,\n): void => {\n if (!Array.isArray(components)) {\n warnings.push(\n `Operation at index ${operationIndex} has malformed components.`,\n );\n return;\n }\n\n for (\n let componentIndex = 0;\n componentIndex < components.length;\n componentIndex++\n ) {\n const component = components[componentIndex];\n if (\n !isRecord(component) ||\n typeof component[\"id\"] !== \"string\" ||\n component[\"id\"].length === 0 ||\n typeof component[\"component\"] !== \"string\" ||\n component[\"component\"].length === 0\n ) {\n warnings.push(\n `Component at index ${componentIndex} in operation ${operationIndex} is malformed.`,\n );\n continue;\n }\n surface.components.set(component[\"id\"], { ...component });\n }\n};\n\nconst decodePointer = (path: string): string[] | undefined => {\n if (path === \"\" || path === \"/\") return [];\n if (!path.startsWith(\"/\")) return undefined;\n return path\n .slice(1)\n .split(\"/\")\n .map((segment) => segment.replaceAll(\"~1\", \"/\").replaceAll(\"~0\", \"~\"));\n};\n\nconst isArrayIndex = (segment: string): boolean =>\n segment === \"0\" || /^[1-9]\\d*$/.test(segment);\n\nconst setAtPointer = (\n model: unknown,\n path: string,\n value: unknown,\n nullDeletes: boolean,\n): { readonly ok: boolean; readonly value: unknown } => {\n const segments = decodePointer(path);\n if (!segments) return { ok: false, value: model };\n if (segments.length === 0) return { ok: true, value };\n\n const remove = (current: unknown, index: number): unknown => {\n const segment = segments[index]!;\n const isLast = index === segments.length - 1;\n\n if (Array.isArray(current)) {\n if (segment !== \"-\" && !isArrayIndex(segment)) return current;\n const targetIndex = segment === \"-\" ? current.length : Number(segment);\n if (isLast) {\n if (\n segment === \"-\" ||\n !Object.prototype.hasOwnProperty.call(current, targetIndex)\n ) {\n return current;\n }\n const clone = current.slice();\n // Preserve indices referenced by other JSON Pointers.\n delete clone[targetIndex];\n return clone;\n }\n const child = current[targetIndex];\n if (!Array.isArray(child) && !isRecord(child)) return current;\n const next = remove(child, index + 1);\n if (next === child) return current;\n const clone = current.slice();\n clone[targetIndex] = next;\n return clone;\n }\n\n if (!isRecord(current)) return current;\n if (!Object.prototype.hasOwnProperty.call(current, segment)) return current;\n if (isLast) {\n const clone = { ...current };\n delete clone[segment];\n return clone;\n }\n const child = current[segment];\n if (!Array.isArray(child) && !isRecord(child)) return current;\n const next = remove(child, index + 1);\n if (next === child) return current;\n return { ...current, [segment]: next };\n };\n\n if (nullDeletes && value === null) {\n return { ok: true, value: remove(model, 0) };\n }\n\n const update = (current: unknown, index: number): unknown => {\n const segment = segments[index]!;\n const isLast = index === segments.length - 1;\n\n if (Array.isArray(current)) {\n if (segment !== \"-\" && !isArrayIndex(segment)) return current;\n const targetIndex = segment === \"-\" ? current.length : Number(segment);\n if (\n segment !== \"-\" &&\n targetIndex >= current.length &&\n targetIndex > MAX_AUTO_VIVIFY_ARRAY_INDEX\n ) {\n return INVALID_POINTER;\n }\n const next = isLast ? value : update(current[targetIndex], index + 1);\n if (next === INVALID_POINTER) return INVALID_POINTER;\n const clone = current.slice();\n clone[targetIndex] = next;\n return clone;\n }\n\n const record: Record<string, unknown> = isRecord(current) ? current : {};\n const child = Object.hasOwn(record, segment) ? record[segment] : undefined;\n const next = isLast\n ? value\n : update(\n child ??\n (isArrayIndex(segments[index + 1] ?? \"\") ||\n segments[index + 1] === \"-\"\n ? []\n : {}),\n index + 1,\n );\n if (next === INVALID_POINTER) return INVALID_POINTER;\n return { ...record, [segment]: next };\n };\n\n const result = update(model, 0);\n return result === INVALID_POINTER\n ? { ok: false, value: model }\n : { ok: true, value: result };\n};\n\nconst dataModelValue = (\n payload: Record<string, unknown>,\n): { readonly found: boolean; readonly value: unknown } => {\n for (const key of [\"contents\", \"value\", \"data\"] as const) {\n if (payload[key] !== undefined) {\n return { found: true, value: payload[key] };\n }\n }\n return { found: false, value: undefined };\n};\n\nexport function applyA2uiOperations(\n state: A2uiState,\n operations: unknown,\n): A2uiOperationResult {\n const nextState = new Map(state);\n const warnings: string[] = [];\n\n if (!Array.isArray(operations)) {\n return {\n state: nextState,\n warnings: [\"A2UI operations must be an array.\"],\n };\n }\n\n for (let index = 0; index < operations.length; index++) {\n try {\n const entry = operations[index];\n if (!isRecord(entry)) {\n warnings.push(`Operation at index ${index} is malformed.`);\n continue;\n }\n\n const version = entry[\"version\"];\n if (!isVersion(version)) {\n warnings.push(\n `Operation at index ${index} has an unsupported version.`,\n );\n continue;\n }\n\n const operationKeys = Object.keys(entry).filter(\n (key) => key !== \"version\",\n );\n if (operationKeys.length !== 1) {\n warnings.push(\n `Operation at index ${index} must contain exactly one operation key.`,\n );\n continue;\n }\n\n const operationKey = operationKeys[0]!;\n if (!OPERATION_KEYS.has(operationKey)) {\n warnings.push(\n `Unknown A2UI operation \"${operationKey}\" at index ${index}.`,\n );\n continue;\n }\n\n const payload = entry[operationKey];\n if (!isRecord(payload)) {\n warnings.push(`Operation at index ${index} has a malformed payload.`);\n continue;\n }\n\n const surfaceId = surfaceIdOf(payload);\n if (!surfaceId) {\n warnings.push(`Operation at index ${index} has no valid surfaceId.`);\n continue;\n }\n\n if (operationKey === \"createSurface\") {\n const surface = withSurfaceId(\n {\n components: new Map(),\n dataModel:\n version === \"v1.0\" && payload[\"dataModel\"] !== undefined\n ? payload[\"dataModel\"]\n : undefined,\n },\n surfaceId,\n );\n if (version === \"v1.0\" && payload[\"components\"] !== undefined) {\n upsertComponents(surface, payload[\"components\"], warnings, index);\n }\n nextState.set(surfaceId, surface);\n continue;\n }\n\n if (operationKey === \"deleteSurface\") {\n nextState.delete(surfaceId);\n continue;\n }\n\n const currentSurface = nextState.get(surfaceId);\n if (!currentSurface) {\n warnings.push(\n `Operation at index ${index} references missing surface \"${surfaceId}\".`,\n );\n continue;\n }\n const surface = cloneSurface(currentSurface, surfaceId);\n\n if (operationKey === \"updateComponents\") {\n upsertComponents(surface, payload[\"components\"], warnings, index);\n nextState.set(surfaceId, surface);\n continue;\n }\n\n const update = dataModelValue(payload);\n if (!update.found) {\n warnings.push(`Operation at index ${index} has no data model value.`);\n continue;\n }\n const path = payload[\"path\"] ?? \"/\";\n if (typeof path !== \"string\") {\n warnings.push(\n `Operation at index ${index} has a malformed data model path.`,\n );\n continue;\n }\n const result = setAtPointer(\n surface.dataModel,\n path,\n update.value,\n version === \"v1.0\",\n );\n if (!result.ok) {\n warnings.push(\n `Operation at index ${index} has an invalid JSON Pointer path.`,\n );\n continue;\n }\n surface.dataModel = result.value;\n nextState.set(surfaceId, surface);\n } catch {\n warnings.push(`Operation at index ${index} is malformed.`);\n }\n }\n\n return { state: nextState, warnings };\n}\n"],"mappings":"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"}
|
|
@@ -20,14 +20,15 @@ import { toJSONSchema } from "assistant-stream";
|
|
|
20
20
|
*/
|
|
21
21
|
function buildPresentParameters(library) {
|
|
22
22
|
const names = Object.keys(library);
|
|
23
|
-
const props =
|
|
23
|
+
const props = /* @__PURE__ */ new Map();
|
|
24
24
|
const propOwners = /* @__PURE__ */ new Map();
|
|
25
25
|
for (const name of names) {
|
|
26
26
|
const propsSchema = toJSONSchema(library[name].properties);
|
|
27
27
|
if (propsSchema.type !== "object") throw new Error(`[@assistant-ui/react-generative-ui] Component "${name}": \`properties\` must be an object schema (e.g. \`z.object({ ... })\`).`);
|
|
28
28
|
for (const [key, schema] of Object.entries(propsSchema.properties ?? {})) {
|
|
29
29
|
if (key.startsWith("$") || key === "children") continue;
|
|
30
|
-
if (
|
|
30
|
+
if (key === "__proto__") continue;
|
|
31
|
+
if (!props.has(key)) props.set(key, schema);
|
|
31
32
|
propOwners.set(key, [...propOwners.get(key) ?? [], name]);
|
|
32
33
|
}
|
|
33
34
|
}
|
|
@@ -48,7 +49,7 @@ function buildPresentParameters(library) {
|
|
|
48
49
|
description: "Stable identity for this UI node. Use it for list items that may reorder.",
|
|
49
50
|
anyOf: [{ type: "string" }, { type: "number" }]
|
|
50
51
|
},
|
|
51
|
-
...props,
|
|
52
|
+
...Object.fromEntries(props),
|
|
52
53
|
children: { $ref: "#/$defs/children" }
|
|
53
54
|
},
|
|
54
55
|
required: [TYPE_KEY]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"buildPresentParameters.js","names":[],"sources":["../src/buildPresentParameters.ts"],"sourcesContent":["import { toJSONSchema } from \"assistant-stream\";\nimport type { JSONSchema7, JSONSchema7Definition } from \"json-schema\";\nimport { TYPE_KEY } from \"./constants\";\nimport type { GenerativeUILibrary } from \"./types\";\n\n/**\n * Builds the JSON schema for the `present` tool from a {@link GenerativeUILibrary}.\n *\n * The model produces a node `{ $type, $key?, ...props }` where `$type` selects\n * a component, the optional `$key` pins a stable identity for list items that\n * may reorder, and the rest are its props. The schema is a flat object: `$type`\n * is an enum of the component names, every component's props are merged into\n * one optional bag, and `children` recurses via `$defs` so the tree can nest.\n *\n * It is intentionally flat rather than a per-`$type` discriminated union. Tool /\n * function-call schemas (OpenAI and others) require the top-level parameters to\n * be a plain object and reject a top-level `oneOf`/`anyOf`/`enum`. So props can't\n * be refined per `$type` at the root; the model is guided instead by `$type`'s\n * description (which lists each component) and each prop's own description. The\n * renderer validates nothing here — an unknown `$type` or stray prop is handled\n * at render time — so a looser schema only costs the model a hint, not safety.\n */\nexport function buildPresentParameters(\n library: GenerativeUILibrary,\n): JSONSchema7 {\n const names = Object.keys(library);\n\n // Merge every component's props into one optional bag. `$`-prefixed keys and\n // `children` are framework-reserved (see ir.ts), so drop any author-declared\n // copies. On a name clash the first component's schema wins — props are an\n // advisory hint here, not a strict per-component contract.\n const props
|
|
1
|
+
{"version":3,"file":"buildPresentParameters.js","names":[],"sources":["../src/buildPresentParameters.ts"],"sourcesContent":["import { toJSONSchema } from \"assistant-stream\";\nimport type { JSONSchema7, JSONSchema7Definition } from \"json-schema\";\nimport { TYPE_KEY } from \"./constants\";\nimport type { GenerativeUILibrary } from \"./types\";\n\n/**\n * Builds the JSON schema for the `present` tool from a {@link GenerativeUILibrary}.\n *\n * The model produces a node `{ $type, $key?, ...props }` where `$type` selects\n * a component, the optional `$key` pins a stable identity for list items that\n * may reorder, and the rest are its props. The schema is a flat object: `$type`\n * is an enum of the component names, every component's props are merged into\n * one optional bag, and `children` recurses via `$defs` so the tree can nest.\n *\n * It is intentionally flat rather than a per-`$type` discriminated union. Tool /\n * function-call schemas (OpenAI and others) require the top-level parameters to\n * be a plain object and reject a top-level `oneOf`/`anyOf`/`enum`. So props can't\n * be refined per `$type` at the root; the model is guided instead by `$type`'s\n * description (which lists each component) and each prop's own description. The\n * renderer validates nothing here — an unknown `$type` or stray prop is handled\n * at render time — so a looser schema only costs the model a hint, not safety.\n */\nexport function buildPresentParameters(\n library: GenerativeUILibrary,\n): JSONSchema7 {\n const names = Object.keys(library);\n\n // Merge every component's props into one optional bag. `$`-prefixed keys and\n // `children` are framework-reserved (see ir.ts), so drop any author-declared\n // copies. On a name clash the first component's schema wins — props are an\n // advisory hint here, not a strict per-component contract.\n const props = new Map<string, JSONSchema7Definition>();\n const propOwners = new Map<string, string[]>();\n for (const name of names) {\n const propsSchema = toJSONSchema(library[name]!.properties);\n if (propsSchema.type !== \"object\") {\n throw new Error(\n `[@assistant-ui/react-generative-ui] Component \"${name}\": ` +\n \"`properties` must be an object schema (e.g. `z.object({ ... })`).\",\n );\n }\n for (const [key, schema] of Object.entries(propsSchema.properties ?? {})) {\n if (key.startsWith(\"$\") || key === \"children\") continue;\n // secure-json-parse rejects the whole tool-argument payload on this key,\n // so advertising it would cost the model the node rather than one prop.\n if (key === \"__proto__\") continue;\n if (!props.has(key)) {\n props.set(key, schema);\n }\n propOwners.set(key, [...(propOwners.get(key) ?? []), name]);\n }\n }\n\n if (process.env[\"NODE_ENV\"] !== \"production\") {\n for (const [key, owners] of propOwners) {\n if (owners.length < 2) continue;\n // eslint-disable-next-line no-console\n console.warn(\n `[@assistant-ui/react-generative-ui] Prop \"${key}\" is declared by ` +\n `${formatComponentList(owners)}; keeping \"${owners[0]}\"'s schema. ` +\n \"Rename or align the prop type to avoid an ambiguous schema.\",\n );\n }\n }\n\n // Carry each component's description on the `$type` enum, since there are no\n // per-branch schemas to hang them on anymore.\n const typeDescription =\n names.length > 0\n ? `The component to render. ${names\n .map((name) => `\"${name}\": ${library[name]!.description}`)\n .join(\"; \")}`\n : \"The component to render.\";\n\n const node: JSONSchema7 = {\n type: \"object\",\n properties: {\n [TYPE_KEY]: { type: \"string\", enum: names, description: typeDescription },\n $key: {\n description:\n \"Stable identity for this UI node. Use it for list items that may reorder.\",\n anyOf: [{ type: \"string\" }, { type: \"number\" }],\n },\n ...Object.fromEntries(props),\n children: { $ref: \"#/$defs/children\" },\n },\n required: [TYPE_KEY],\n };\n\n const children: JSONSchema7 = {\n description: \"Nested generative UI rendered inside this component.\",\n anyOf: [\n { type: \"string\" },\n { $ref: \"#/$defs/node\" },\n {\n type: \"array\",\n items: { anyOf: [{ type: \"string\" }, { $ref: \"#/$defs/node\" }] },\n },\n ],\n };\n\n return {\n ...node,\n $defs: { node, children },\n };\n}\n\nfunction formatComponentList(names: string[]) {\n if (names.length <= 2) return names.map((name) => `\"${name}\"`).join(\" and \");\n\n return `${names\n .slice(0, -1)\n .map((name) => `\"${name}\"`)\n .join(\", \")}, and \"${names[names.length - 1]}\"`;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAsBA,SAAgB,uBACd,SACa;CACb,MAAM,QAAQ,OAAO,KAAK,OAAO;CAMjC,MAAM,wBAAQ,IAAI,IAAmC;CACrD,MAAM,6BAAa,IAAI,IAAsB;CAC7C,KAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,cAAc,aAAa,QAAQ,KAAK,CAAE,UAAU;EAC1D,IAAI,YAAY,SAAS,UACvB,MAAM,IAAI,MACR,kDAAkD,KAAK,yEAEzD;EAEF,KAAK,MAAM,CAAC,KAAK,WAAW,OAAO,QAAQ,YAAY,cAAc,CAAC,CAAC,GAAG;GACxE,IAAI,IAAI,WAAW,GAAG,KAAK,QAAQ,YAAY;GAG/C,IAAI,QAAQ,aAAa;GACzB,IAAI,CAAC,MAAM,IAAI,GAAG,GAChB,MAAM,IAAI,KAAK,MAAM;GAEvB,WAAW,IAAI,KAAK,CAAC,GAAI,WAAW,IAAI,GAAG,KAAK,CAAC,GAAI,IAAI,CAAC;EAC5D;CACF;CAEA,IAAI,QAAQ,IAAI,gBAAgB,cAC9B,KAAK,MAAM,CAAC,KAAK,WAAW,YAAY;EACtC,IAAI,OAAO,SAAS,GAAG;EAEvB,QAAQ,KACN,6CAA6C,IAAI,mBAC5C,oBAAoB,MAAM,EAAE,aAAa,OAAO,GAAG,wEAE1D;CACF;CAKF,MAAM,kBACJ,MAAM,SAAS,IACX,4BAA4B,MACzB,KAAK,SAAS,IAAI,KAAK,KAAK,QAAQ,KAAK,CAAE,aAAa,CAAC,CACzD,KAAK,IAAI,MACZ;CAEN,MAAM,OAAoB;EACxB,MAAM;EACN,YAAY;IACT,WAAW;IAAE,MAAM;IAAU,MAAM;IAAO,aAAa;GAAgB;GACxE,MAAM;IACJ,aACE;IACF,OAAO,CAAC,EAAE,MAAM,SAAS,GAAG,EAAE,MAAM,SAAS,CAAC;GAChD;GACA,GAAG,OAAO,YAAY,KAAK;GAC3B,UAAU,EAAE,MAAM,mBAAmB;EACvC;EACA,UAAU,CAAC,QAAQ;CACrB;CAEA,MAAM,WAAwB;EAC5B,aAAa;EACb,OAAO;GACL,EAAE,MAAM,SAAS;GACjB,EAAE,MAAM,eAAe;GACvB;IACE,MAAM;IACN,OAAO,EAAE,OAAO,CAAC,EAAE,MAAM,SAAS,GAAG,EAAE,MAAM,eAAe,CAAC,EAAE;GACjE;EACF;CACF;CAEA,OAAO;EACL,GAAG;EACH,OAAO;GAAE;GAAM;EAAS;CAC1B;AACF;AAEA,SAAS,oBAAoB,OAAiB;CAC5C,IAAI,MAAM,UAAU,GAAG,OAAO,MAAM,KAAK,SAAS,IAAI,KAAK,EAAE,CAAC,CAAC,KAAK,OAAO;CAE3E,OAAO,GAAG,MACP,MAAM,GAAG,EAAE,CAAC,CACZ,KAAK,SAAS,IAAI,KAAK,EAAE,CAAC,CAC1B,KAAK,IAAI,EAAE,SAAS,MAAM,MAAM,SAAS,GAAG;AACjD"}
|
|
@@ -27,7 +27,7 @@ function renderNode(node, library, context) {
|
|
|
27
27
|
return isElement(node) ? renderElement(node, library, context) : null;
|
|
28
28
|
}
|
|
29
29
|
function renderElement(element, library, context) {
|
|
30
|
-
const entry = library[element.type];
|
|
30
|
+
const entry = Object.hasOwn(library, element.type) ? library[element.type] : void 0;
|
|
31
31
|
if (!entry) {
|
|
32
32
|
reportUnknownComponent(element.type, Object.keys(library));
|
|
33
33
|
return null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"renderGenerativeUI.js","names":[],"sources":["../src/renderGenerativeUI.tsx"],"sourcesContent":["import { getPartialJsonObjectMeta } from \"assistant-stream/utils\";\nimport { Fragment, type ReactNode } from \"react\";\nimport {\n normalizeUINode,\n type NormalizedUIElement,\n type NormalizedUINode,\n} from \"./ir\";\nimport type { GenerativeUILibrary, GenerativeUIRenderContext } from \"./types\";\n\nconst DEFAULT_CONTEXT: GenerativeUIRenderContext = { status: \"done\" };\n\nconst isElement = (node: NormalizedUINode): node is NormalizedUIElement =>\n typeof node === \"object\" && node !== null && !Array.isArray(node);\n\n/**\n * Renders a generative-ui tree against a {@link GenerativeUILibrary}.\n *\n * The model emits each node as a flat object `{ $type, ...props }`. We first\n * normalize that wire form into the canonical {@link NormalizedUINode} (with\n * `children` lifted to a reserved top-level key), then render: each `type` is\n * looked up in the library and its `props` are passed to the component's\n * `render(props, context)`, with `children` rendered recursively so components\n * can nest.\n */\nexport function renderGenerativeUI(\n node: unknown,\n library: GenerativeUILibrary,\n context: GenerativeUIRenderContext = DEFAULT_CONTEXT,\n): ReactNode {\n // Tool args are parsed incrementally, and the parse meta records which path\n // is still mid-arrival, so normalization can hold back a node whose `$type`\n // string has not finished streaming.\n const meta = getPartialJsonObjectMeta(node as Record<symbol, unknown>);\n const partialPath = meta?.state === \"partial\" ? meta.partialPath : undefined;\n return renderNode(normalizeUINode(node, partialPath), library, context);\n}\n\nfunction renderNode(\n node: NormalizedUINode,\n library: GenerativeUILibrary,\n context: GenerativeUIRenderContext,\n): ReactNode {\n if (node == null || typeof node === \"boolean\") return null;\n if (typeof node === \"string\" || typeof node === \"number\") return node;\n if (Array.isArray(node)) {\n // Use a model-provided stable key when present. Otherwise, keep the\n // positional fallback: pairing the index with the node's kind means that\n // when the kind at an index changes, React remounts instead of handing a\n // streaming node's hook state to a different component.\n return node.map((child, index) => (\n <Fragment key={nodeKey(child, index)}>\n {renderNode(child, library, context)}\n </Fragment>\n ));\n }\n return isElement(node) ? renderElement(node, library, context) : null;\n}\n\nfunction renderElement(\n element: NormalizedUIElement,\n library: GenerativeUILibrary,\n context: GenerativeUIRenderContext,\n): ReactNode {\n const entry = library[element.type];\n if (!entry) {\n reportUnknownComponent(element.type, Object.keys(library));\n return null;\n }\n\n // Components that opt out of prop streaming wait until their props are\n // complete rather than rendering from a partial parse.\n if (!entry.streamProperties && context.status === \"streaming\") return null;\n\n // `children` is a reserved top-level key on the normalized element, not a\n // prop, so it is not in `props`. Inject the framework props last so the model\n // can never override them. `$action` is likewise reserved and stripped from\n // the prop bag during normalization, so it is re-injected here for components\n // that carry behavior (e.g. `Button`).\n const props: Record<string, unknown> = {\n ...element.props,\n $status: context.status,\n };\n if (context.dispatch !== undefined) {\n props[\"$dispatch\"] = context.dispatch;\n }\n if (element.action !== undefined) {\n props[\"$action\"] = element.action;\n }\n if (element.children !== undefined) {\n props[\"children\"] = renderNode(element.children, library, context);\n }\n\n return <GenerativeUIComponentRenderer render={entry.render} props={props} />;\n}\n\n/**\n * Mounts a single node's `render` on its own fiber so the function may use\n * hooks and hold state independently of its siblings and parent.\n */\nfunction GenerativeUIComponentRenderer({\n render,\n props,\n}: {\n render: (props: any) => ReactNode;\n props: Record<string, unknown>;\n}): ReactNode {\n return render(props);\n}\n\n/** A coarse kind tag for a child, used in its list key so a node changing kind\n * at a given index forces a remount rather than a wrong-fiber reuse. */\nfunction nodeKind(node: NormalizedUINode): string {\n if (node == null || typeof node === \"boolean\") return \"\";\n if (typeof node === \"string\" || typeof node === \"number\") return \"#text\";\n if (Array.isArray(node)) return \"#array\";\n return isElement(node) ? node.type : \"\";\n}\n\nfunction nodeKey(node: NormalizedUINode, index: number): string {\n return isElement(node) &&\n (typeof node.key === \"string\" || typeof node.key === \"number\")\n ? `model:${node.key}`\n : `${index}:${nodeKind(node)}`;\n}\n\nfunction reportUnknownComponent(type: string, available: string[]): void {\n if (process.env[\"NODE_ENV\"] !== \"production\") {\n // eslint-disable-next-line no-console\n console.error(\n `[@assistant-ui/react-generative-ui] Unknown component \"${type}\". ` +\n `Available components: ${available.join(\", \") || \"(none)\"}.`,\n );\n }\n}\n"],"mappings":";;;;;AASA,MAAM,kBAA6C,EAAE,QAAQ,OAAO;AAEpE,MAAM,aAAa,SACjB,OAAO,SAAS,YAAY,SAAS,QAAQ,CAAC,MAAM,QAAQ,IAAI;;;;;;;;;;;AAYlE,SAAgB,mBACd,MACA,SACA,UAAqC,iBAC1B;CAIX,MAAM,OAAO,yBAAyB,IAA+B;CACrE,MAAM,cAAc,MAAM,UAAU,YAAY,KAAK,cAAc,KAAA;CACnE,OAAO,WAAW,gBAAgB,MAAM,WAAW,GAAG,SAAS,OAAO;AACxE;AAEA,SAAS,WACP,MACA,SACA,SACW;CACX,IAAI,QAAQ,QAAQ,OAAO,SAAS,WAAW,OAAO;CACtD,IAAI,OAAO,SAAS,YAAY,OAAO,SAAS,UAAU,OAAO;CACjE,IAAI,MAAM,QAAQ,IAAI,GAKpB,OAAO,KAAK,KAAK,OAAO,UACtB,oBAAC,UAAD,EAAA,UACG,WAAW,OAAO,SAAS,OAAO,EAC3B,GAFK,QAAQ,OAAO,KAAK,CAEzB,CACX;CAEH,OAAO,UAAU,IAAI,IAAI,cAAc,MAAM,SAAS,OAAO,IAAI;AACnE;AAEA,SAAS,cACP,SACA,SACA,SACW;CACX,MAAM,QAAQ,QAAQ,QAAQ;
|
|
1
|
+
{"version":3,"file":"renderGenerativeUI.js","names":[],"sources":["../src/renderGenerativeUI.tsx"],"sourcesContent":["import { getPartialJsonObjectMeta } from \"assistant-stream/utils\";\nimport { Fragment, type ReactNode } from \"react\";\nimport {\n normalizeUINode,\n type NormalizedUIElement,\n type NormalizedUINode,\n} from \"./ir\";\nimport type { GenerativeUILibrary, GenerativeUIRenderContext } from \"./types\";\n\nconst DEFAULT_CONTEXT: GenerativeUIRenderContext = { status: \"done\" };\n\nconst isElement = (node: NormalizedUINode): node is NormalizedUIElement =>\n typeof node === \"object\" && node !== null && !Array.isArray(node);\n\n/**\n * Renders a generative-ui tree against a {@link GenerativeUILibrary}.\n *\n * The model emits each node as a flat object `{ $type, ...props }`. We first\n * normalize that wire form into the canonical {@link NormalizedUINode} (with\n * `children` lifted to a reserved top-level key), then render: each `type` is\n * looked up in the library and its `props` are passed to the component's\n * `render(props, context)`, with `children` rendered recursively so components\n * can nest.\n */\nexport function renderGenerativeUI(\n node: unknown,\n library: GenerativeUILibrary,\n context: GenerativeUIRenderContext = DEFAULT_CONTEXT,\n): ReactNode {\n // Tool args are parsed incrementally, and the parse meta records which path\n // is still mid-arrival, so normalization can hold back a node whose `$type`\n // string has not finished streaming.\n const meta = getPartialJsonObjectMeta(node as Record<symbol, unknown>);\n const partialPath = meta?.state === \"partial\" ? meta.partialPath : undefined;\n return renderNode(normalizeUINode(node, partialPath), library, context);\n}\n\nfunction renderNode(\n node: NormalizedUINode,\n library: GenerativeUILibrary,\n context: GenerativeUIRenderContext,\n): ReactNode {\n if (node == null || typeof node === \"boolean\") return null;\n if (typeof node === \"string\" || typeof node === \"number\") return node;\n if (Array.isArray(node)) {\n // Use a model-provided stable key when present. Otherwise, keep the\n // positional fallback: pairing the index with the node's kind means that\n // when the kind at an index changes, React remounts instead of handing a\n // streaming node's hook state to a different component.\n return node.map((child, index) => (\n <Fragment key={nodeKey(child, index)}>\n {renderNode(child, library, context)}\n </Fragment>\n ));\n }\n return isElement(node) ? renderElement(node, library, context) : null;\n}\n\nfunction renderElement(\n element: NormalizedUIElement,\n library: GenerativeUILibrary,\n context: GenerativeUIRenderContext,\n): ReactNode {\n const entry = Object.hasOwn(library, element.type)\n ? library[element.type]\n : undefined;\n if (!entry) {\n reportUnknownComponent(element.type, Object.keys(library));\n return null;\n }\n\n // Components that opt out of prop streaming wait until their props are\n // complete rather than rendering from a partial parse.\n if (!entry.streamProperties && context.status === \"streaming\") return null;\n\n // `children` is a reserved top-level key on the normalized element, not a\n // prop, so it is not in `props`. Inject the framework props last so the model\n // can never override them. `$action` is likewise reserved and stripped from\n // the prop bag during normalization, so it is re-injected here for components\n // that carry behavior (e.g. `Button`).\n const props: Record<string, unknown> = {\n ...element.props,\n $status: context.status,\n };\n if (context.dispatch !== undefined) {\n props[\"$dispatch\"] = context.dispatch;\n }\n if (element.action !== undefined) {\n props[\"$action\"] = element.action;\n }\n if (element.children !== undefined) {\n props[\"children\"] = renderNode(element.children, library, context);\n }\n\n return <GenerativeUIComponentRenderer render={entry.render} props={props} />;\n}\n\n/**\n * Mounts a single node's `render` on its own fiber so the function may use\n * hooks and hold state independently of its siblings and parent.\n */\nfunction GenerativeUIComponentRenderer({\n render,\n props,\n}: {\n render: (props: any) => ReactNode;\n props: Record<string, unknown>;\n}): ReactNode {\n return render(props);\n}\n\n/** A coarse kind tag for a child, used in its list key so a node changing kind\n * at a given index forces a remount rather than a wrong-fiber reuse. */\nfunction nodeKind(node: NormalizedUINode): string {\n if (node == null || typeof node === \"boolean\") return \"\";\n if (typeof node === \"string\" || typeof node === \"number\") return \"#text\";\n if (Array.isArray(node)) return \"#array\";\n return isElement(node) ? node.type : \"\";\n}\n\nfunction nodeKey(node: NormalizedUINode, index: number): string {\n return isElement(node) &&\n (typeof node.key === \"string\" || typeof node.key === \"number\")\n ? `model:${node.key}`\n : `${index}:${nodeKind(node)}`;\n}\n\nfunction reportUnknownComponent(type: string, available: string[]): void {\n if (process.env[\"NODE_ENV\"] !== \"production\") {\n // eslint-disable-next-line no-console\n console.error(\n `[@assistant-ui/react-generative-ui] Unknown component \"${type}\". ` +\n `Available components: ${available.join(\", \") || \"(none)\"}.`,\n );\n }\n}\n"],"mappings":";;;;;AASA,MAAM,kBAA6C,EAAE,QAAQ,OAAO;AAEpE,MAAM,aAAa,SACjB,OAAO,SAAS,YAAY,SAAS,QAAQ,CAAC,MAAM,QAAQ,IAAI;;;;;;;;;;;AAYlE,SAAgB,mBACd,MACA,SACA,UAAqC,iBAC1B;CAIX,MAAM,OAAO,yBAAyB,IAA+B;CACrE,MAAM,cAAc,MAAM,UAAU,YAAY,KAAK,cAAc,KAAA;CACnE,OAAO,WAAW,gBAAgB,MAAM,WAAW,GAAG,SAAS,OAAO;AACxE;AAEA,SAAS,WACP,MACA,SACA,SACW;CACX,IAAI,QAAQ,QAAQ,OAAO,SAAS,WAAW,OAAO;CACtD,IAAI,OAAO,SAAS,YAAY,OAAO,SAAS,UAAU,OAAO;CACjE,IAAI,MAAM,QAAQ,IAAI,GAKpB,OAAO,KAAK,KAAK,OAAO,UACtB,oBAAC,UAAD,EAAA,UACG,WAAW,OAAO,SAAS,OAAO,EAC3B,GAFK,QAAQ,OAAO,KAAK,CAEzB,CACX;CAEH,OAAO,UAAU,IAAI,IAAI,cAAc,MAAM,SAAS,OAAO,IAAI;AACnE;AAEA,SAAS,cACP,SACA,SACA,SACW;CACX,MAAM,QAAQ,OAAO,OAAO,SAAS,QAAQ,IAAI,IAC7C,QAAQ,QAAQ,QAChB,KAAA;CACJ,IAAI,CAAC,OAAO;EACV,uBAAuB,QAAQ,MAAM,OAAO,KAAK,OAAO,CAAC;EACzD,OAAO;CACT;CAIA,IAAI,CAAC,MAAM,oBAAoB,QAAQ,WAAW,aAAa,OAAO;CAOtE,MAAM,QAAiC;EACrC,GAAG,QAAQ;EACX,SAAS,QAAQ;CACnB;CACA,IAAI,QAAQ,aAAa,KAAA,GACvB,MAAM,eAAe,QAAQ;CAE/B,IAAI,QAAQ,WAAW,KAAA,GACrB,MAAM,aAAa,QAAQ;CAE7B,IAAI,QAAQ,aAAa,KAAA,GACvB,MAAM,cAAc,WAAW,QAAQ,UAAU,SAAS,OAAO;CAGnE,OAAO,oBAAC,+BAAD;EAA+B,QAAQ,MAAM;EAAe;CAAQ,CAAA;AAC7E;;;;;AAMA,SAAS,8BAA8B,EACrC,QACA,SAIY;CACZ,OAAO,OAAO,KAAK;AACrB;;;AAIA,SAAS,SAAS,MAAgC;CAChD,IAAI,QAAQ,QAAQ,OAAO,SAAS,WAAW,OAAO;CACtD,IAAI,OAAO,SAAS,YAAY,OAAO,SAAS,UAAU,OAAO;CACjE,IAAI,MAAM,QAAQ,IAAI,GAAG,OAAO;CAChC,OAAO,UAAU,IAAI,IAAI,KAAK,OAAO;AACvC;AAEA,SAAS,QAAQ,MAAwB,OAAuB;CAC9D,OAAO,UAAU,IAAI,MAClB,OAAO,KAAK,QAAQ,YAAY,OAAO,KAAK,QAAQ,YACnD,SAAS,KAAK,QACd,GAAG,MAAM,GAAG,SAAS,IAAI;AAC/B;AAEA,SAAS,uBAAuB,MAAc,WAA2B;CACvE,IAAI,QAAQ,IAAI,gBAAgB,cAE9B,QAAQ,MACN,0DAA0D,KAAK,2BACpC,UAAU,KAAK,IAAI,KAAK,SAAS,EAC9D;AAEJ"}
|
|
@@ -8,9 +8,9 @@ declare const alertVocabulary: {
|
|
|
8
8
|
description: z.ZodOptional<z.ZodString>;
|
|
9
9
|
tone: z.ZodOptional<z.ZodEnum<{
|
|
10
10
|
success: "success";
|
|
11
|
-
danger: "danger";
|
|
12
11
|
info: "info";
|
|
13
12
|
warning: "warning";
|
|
13
|
+
danger: "danger";
|
|
14
14
|
}>>;
|
|
15
15
|
}, z.core.$strip>;
|
|
16
16
|
render: ({ title, description, tone, children }: any) => import("react").JSX.Element;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"alert.d.ts","names":[],"sources":["../../src/vocabulary/alert.tsx"],"mappings":";;
|
|
1
|
+
{"version":3,"file":"alert.d.ts","names":[],"sources":["../../src/vocabulary/alert.tsx"],"mappings":";;cAQa"}
|
package/dist/vocabulary/alert.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ALERT_TONES } from "../ir.js";
|
|
2
|
+
import { toTextContent } from "./toTextContent.js";
|
|
2
3
|
import { Children } from "react";
|
|
3
4
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
5
|
import { z } from "zod";
|
|
@@ -12,22 +13,26 @@ const alertVocabulary = {
|
|
|
12
13
|
description: z.string().optional().describe("Supporting description text."),
|
|
13
14
|
tone: z.enum(ALERT_TONES).optional().describe("Severity tone; defaults to `info`.")
|
|
14
15
|
}),
|
|
15
|
-
render: ({ title, description, tone, children }) =>
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
16
|
+
render: ({ title, description, tone, children }) => {
|
|
17
|
+
const alertTitle = toTextContent(title);
|
|
18
|
+
const alertDescription = toTextContent(description);
|
|
19
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
20
|
+
"data-aui": "alert",
|
|
21
|
+
"data-aui-tone": tone ?? "info",
|
|
22
|
+
role: "alert",
|
|
23
|
+
children: [
|
|
24
|
+
alertTitle ? /* @__PURE__ */ jsx("header", {
|
|
25
|
+
"data-aui": "alert-title",
|
|
26
|
+
children: alertTitle
|
|
27
|
+
}) : null,
|
|
28
|
+
alertDescription ? /* @__PURE__ */ jsx("p", {
|
|
29
|
+
"data-aui": "alert-desc",
|
|
30
|
+
children: alertDescription
|
|
31
|
+
}) : null,
|
|
32
|
+
children
|
|
33
|
+
]
|
|
34
|
+
});
|
|
35
|
+
}
|
|
31
36
|
},
|
|
32
37
|
Carousel: {
|
|
33
38
|
description: "A horizontally scrollable group of `Card` children (max 10). Set `label` for an accessible name.",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"alert.js","names":[],"sources":["../../src/vocabulary/alert.tsx"],"sourcesContent":["import { Children } from \"react\";\nimport { z } from \"zod\";\nimport type { GenerativeUILibrary } from \"../types\";\nimport { ALERT_TONES } from \"../ir\";\n\nconst MAX_CARDS = 10;\n\nexport const alertVocabulary = {\n Alert: {\n description:\n \"A highlighted message conveying urgency. `tone` drives the severity.\",\n properties: z.object({\n title: z.string().optional().describe(\"Alert title.\"),\n description: z\n .string()\n .optional()\n .describe(\"Supporting description text.\"),\n tone: z\n .enum(ALERT_TONES)\n .optional()\n .describe(\"Severity tone; defaults to `info`.\"),\n }),\n render: ({ title, description, tone, children }) => (\n
|
|
1
|
+
{"version":3,"file":"alert.js","names":[],"sources":["../../src/vocabulary/alert.tsx"],"sourcesContent":["import { Children } from \"react\";\nimport { z } from \"zod\";\nimport type { GenerativeUILibrary } from \"../types\";\nimport { ALERT_TONES } from \"../ir\";\nimport { toTextContent } from \"./toTextContent\";\n\nconst MAX_CARDS = 10;\n\nexport const alertVocabulary = {\n Alert: {\n description:\n \"A highlighted message conveying urgency. `tone` drives the severity.\",\n properties: z.object({\n title: z.string().optional().describe(\"Alert title.\"),\n description: z\n .string()\n .optional()\n .describe(\"Supporting description text.\"),\n tone: z\n .enum(ALERT_TONES)\n .optional()\n .describe(\"Severity tone; defaults to `info`.\"),\n }),\n render: ({ title, description, tone, children }) => {\n const alertTitle = toTextContent(title);\n const alertDescription = toTextContent(description);\n return (\n <div data-aui=\"alert\" data-aui-tone={tone ?? \"info\"} role=\"alert\">\n {alertTitle ? (\n <header data-aui=\"alert-title\">{alertTitle}</header>\n ) : null}\n {alertDescription ? (\n <p data-aui=\"alert-desc\">{alertDescription}</p>\n ) : null}\n {children}\n </div>\n );\n },\n },\n Carousel: {\n description:\n \"A horizontally scrollable group of `Card` children (max 10). Set `label` for an accessible name.\",\n properties: z.object({\n label: z\n .string()\n .optional()\n .describe(\"Accessible label for the carousel.\"),\n }),\n render: ({ label, children }) => {\n const cards = Children.toArray(children).slice(0, MAX_CARDS);\n const n = cards.length;\n return (\n <div\n data-aui=\"carousel\"\n role={label ? \"region\" : \"group\"}\n aria-roledescription=\"carousel\"\n aria-label={label}\n tabIndex={0}\n >\n {cards.map((card, i) => (\n <div\n key={i}\n data-aui=\"carousel-slide\"\n role=\"group\"\n aria-roledescription=\"slide\"\n aria-label={`${i + 1} of ${n}`}\n >\n {card}\n </div>\n ))}\n </div>\n );\n },\n },\n} satisfies GenerativeUILibrary;\n"],"mappings":";;;;;;AAMA,MAAM,YAAY;AAElB,MAAa,kBAAkB;CAC7B,OAAO;EACL,aACE;EACF,YAAY,EAAE,OAAO;GACnB,OAAO,EAAE,OAAO,CAAC,CAAC,SAAS,CAAC,CAAC,SAAS,cAAc;GACpD,aAAa,EACV,OAAO,CAAC,CACR,SAAS,CAAC,CACV,SAAS,8BAA8B;GAC1C,MAAM,EACH,KAAK,WAAW,CAAC,CACjB,SAAS,CAAC,CACV,SAAS,oCAAoC;EAClD,CAAC;EACD,SAAS,EAAE,OAAO,aAAa,MAAM,eAAe;GAClD,MAAM,aAAa,cAAc,KAAK;GACtC,MAAM,mBAAmB,cAAc,WAAW;GAClD,OACE,qBAAC,OAAD;IAAK,YAAS;IAAQ,iBAAe,QAAQ;IAAQ,MAAK;IAA1D,UAAA;KACG,aACC,oBAAC,UAAD;MAAQ,YAAS;MAAe,UAAA;KAAmB,CAAA,IACjD;KACH,mBACC,oBAAC,KAAD;MAAG,YAAS;MAAc,UAAA;KAAoB,CAAA,IAC5C;KACH;IACE;;EAET;CACF;CACA,UAAU;EACR,aACE;EACF,YAAY,EAAE,OAAO,EACnB,OAAO,EACJ,OAAO,CAAC,CACR,SAAS,CAAC,CACV,SAAS,oCAAoC,EAClD,CAAC;EACD,SAAS,EAAE,OAAO,eAAe;GAC/B,MAAM,QAAQ,SAAS,QAAQ,QAAQ,CAAC,CAAC,MAAM,GAAG,SAAS;GAC3D,MAAM,IAAI,MAAM;GAChB,OACE,oBAAC,OAAD;IACE,YAAS;IACT,MAAM,QAAQ,WAAW;IACzB,wBAAqB;IACrB,cAAY;IACZ,UAAU;IAET,UAAA,MAAM,KAAK,MAAM,MAChB,oBAAC,OAAD;KAEE,YAAS;KACT,MAAK;KACL,wBAAqB;KACrB,cAAY,GAAG,IAAI,EAAE,MAAM;KAE1B,UAAA;IACE,GAPE,CAOF,CACN;GACE,CAAA;EAET;CACF;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"data.d.ts","names":[],"sources":["../../src/vocabulary/data.tsx"],"mappings":";;
|
|
1
|
+
{"version":3,"file":"data.d.ts","names":[],"sources":["../../src/vocabulary/data.tsx"],"mappings":";;cAmSa"}
|
package/dist/vocabulary/data.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { toTextContent } from "./toTextContent.js";
|
|
1
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
3
|
import { z } from "zod";
|
|
3
4
|
//#region src/vocabulary/data.tsx
|
|
@@ -7,6 +8,8 @@ const cellSchema = z.union([
|
|
|
7
8
|
z.number(),
|
|
8
9
|
z.boolean()
|
|
9
10
|
]).describe("A cell value.");
|
|
11
|
+
const isTableColumn = (column) => column !== null && typeof column === "object" && "label" in column && typeof column.label === "string";
|
|
12
|
+
const isTableCell = (cell) => typeof cell === "string" || typeof cell === "number" || typeof cell === "boolean";
|
|
10
13
|
const CHART_HEIGHT = 40;
|
|
11
14
|
const CHART_WIDTH = 100;
|
|
12
15
|
const clampValue = (value) => typeof value === "number" && Number.isFinite(value) ? Math.max(0, value) : 0;
|
|
@@ -178,17 +181,22 @@ const dataVocabulary = {
|
|
|
178
181
|
columns: z.array(columnSchema).optional().describe("Column definitions."),
|
|
179
182
|
rows: z.array(z.array(cellSchema)).optional().describe("Rows of cells.")
|
|
180
183
|
}),
|
|
181
|
-
render: ({ columns, rows, children }) =>
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
184
|
+
render: ({ columns, rows, children }) => {
|
|
185
|
+
const safeColumns = Array.isArray(columns) ? columns : [];
|
|
186
|
+
const hasColumns = safeColumns.some(isTableColumn);
|
|
187
|
+
const safeRows = Array.isArray(rows) ? rows.filter(Array.isArray) : [];
|
|
188
|
+
return /* @__PURE__ */ jsxs("table", {
|
|
189
|
+
"data-aui": "table",
|
|
190
|
+
children: [
|
|
191
|
+
hasColumns ? /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsx("tr", { children: safeColumns.map((column, i) => /* @__PURE__ */ jsx("th", {
|
|
192
|
+
"data-aui": "table-col",
|
|
193
|
+
children: isTableColumn(column) ? column.label : ""
|
|
194
|
+
}, i)) }) }) : null,
|
|
195
|
+
safeRows.length ? /* @__PURE__ */ jsx("tbody", { children: safeRows.map((row, r) => /* @__PURE__ */ jsx("tr", { children: row.map((cell, c) => /* @__PURE__ */ jsx("td", { children: isTableCell(cell) ? String(cell) : "" }, c)) }, r)) }) : null,
|
|
196
|
+
children
|
|
197
|
+
]
|
|
198
|
+
});
|
|
199
|
+
}
|
|
192
200
|
},
|
|
193
201
|
Markdown: {
|
|
194
202
|
description: "A markdown string. Rendered as-is by default; override this component for a full markdown renderer.",
|
|
@@ -196,7 +204,7 @@ const dataVocabulary = {
|
|
|
196
204
|
streamProperties: true,
|
|
197
205
|
render: ({ value, children }) => /* @__PURE__ */ jsxs("div", {
|
|
198
206
|
"data-aui": "markdown",
|
|
199
|
-
children: [value, children]
|
|
207
|
+
children: [toTextContent(value), children]
|
|
200
208
|
})
|
|
201
209
|
},
|
|
202
210
|
Chart: {
|