@assistant-ui/react-generative-ui 0.0.21 → 0.0.22
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/LICENSE +1 -1
- package/dist/JSONGenerativeUI.client.d.ts.map +1 -1
- package/dist/JSONGenerativeUI.client.js +30 -13
- package/dist/JSONGenerativeUI.client.js.map +1 -1
- package/dist/RadioGroupScope.d.ts +6 -0
- package/dist/RadioGroupScope.d.ts.map +1 -0
- package/dist/RadioGroupScope.js +25 -0
- package/dist/RadioGroupScope.js.map +1 -0
- package/dist/a2ui/BindingContext.d.ts +11 -0
- package/dist/a2ui/BindingContext.d.ts.map +1 -0
- package/dist/a2ui/BindingContext.js +12 -0
- package/dist/a2ui/BindingContext.js.map +1 -0
- package/dist/a2ui/PresentRenderer.d.ts +11 -0
- package/dist/a2ui/PresentRenderer.d.ts.map +1 -0
- package/dist/a2ui/PresentRenderer.js +39 -0
- package/dist/a2ui/PresentRenderer.js.map +1 -0
- package/dist/a2ui/convert.d.ts +6 -0
- package/dist/a2ui/convert.d.ts.map +1 -1
- package/dist/a2ui/convert.js +344 -83
- package/dist/a2ui/convert.js.map +1 -1
- package/dist/a2ui/dataModel.d.ts +9 -0
- package/dist/a2ui/dataModel.d.ts.map +1 -0
- package/dist/a2ui/dataModel.js +44 -0
- package/dist/a2ui/dataModel.js.map +1 -0
- package/dist/a2ui/reducer.d.ts +5 -0
- package/dist/a2ui/reducer.d.ts.map +1 -1
- package/dist/a2ui/reducer.js +3 -3
- package/dist/a2ui/reducer.js.map +1 -1
- package/dist/a2ui/types.d.ts +8 -2
- package/dist/a2ui/types.d.ts.map +1 -1
- package/dist/a2ui/types.js.map +1 -1
- package/dist/a2ui/valueFunctions.d.ts +16 -0
- package/dist/a2ui/valueFunctions.d.ts.map +1 -0
- package/dist/a2ui/valueFunctions.js +332 -0
- package/dist/a2ui/valueFunctions.js.map +1 -0
- package/dist/actionRegistry.d.ts +1 -1
- package/dist/actionRegistry.js.map +1 -1
- package/dist/answeredValues.d.ts +16 -0
- package/dist/answeredValues.d.ts.map +1 -0
- package/dist/answeredValues.js +37 -0
- package/dist/answeredValues.js.map +1 -0
- package/dist/constants.d.ts +2 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +3 -1
- package/dist/constants.js.map +1 -1
- package/dist/fieldReferences.d.ts +9 -0
- package/dist/fieldReferences.d.ts.map +1 -0
- package/dist/fieldReferences.js +53 -0
- package/dist/fieldReferences.js.map +1 -0
- package/dist/generativeUIToJSX.js +3 -3
- package/dist/generativeUIToJSX.js.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.server.js +2 -2
- package/dist/index.shared.js +2 -2
- package/dist/ir.d.ts +1 -1
- package/dist/ir.js.map +1 -1
- package/dist/renderGenerativeUI.d.ts.map +1 -1
- package/dist/renderGenerativeUI.js +12 -1
- package/dist/renderGenerativeUI.js.map +1 -1
- package/dist/slack/constants.d.ts +2 -0
- package/dist/slack/constants.d.ts.map +1 -1
- package/dist/slack/constants.js +3 -1
- package/dist/slack/constants.js.map +1 -1
- package/dist/slack/decodeBlockAction.d.ts +3 -1
- package/dist/slack/decodeBlockAction.d.ts.map +1 -1
- package/dist/slack/decodeBlockAction.js +58 -7
- package/dist/slack/decodeBlockAction.js.map +1 -1
- package/dist/slack/fromSlackBlocks.d.ts.map +1 -1
- package/dist/slack/fromSlackBlocks.js +20 -3
- package/dist/slack/fromSlackBlocks.js.map +1 -1
- package/dist/slack/toSlackBlocks.d.ts.map +1 -1
- package/dist/slack/toSlackBlocks.js +187 -19
- package/dist/slack/toSlackBlocks.js.map +1 -1
- package/dist/slack/types.d.ts +4 -0
- package/dist/slack/types.d.ts.map +1 -1
- package/dist/teams/constants.d.ts +1 -1
- package/dist/teams/constants.d.ts.map +1 -1
- package/dist/teams/constants.js +1 -1
- package/dist/teams/constants.js.map +1 -1
- package/dist/teams/decodeSubmitData.d.ts +4 -0
- package/dist/teams/decodeSubmitData.d.ts.map +1 -1
- package/dist/teams/decodeSubmitData.js +6 -1
- package/dist/teams/decodeSubmitData.js.map +1 -1
- package/dist/teams/toAdaptiveCard.d.ts +5 -4
- package/dist/teams/toAdaptiveCard.d.ts.map +1 -1
- package/dist/teams/toAdaptiveCard.js +56 -17
- package/dist/teams/toAdaptiveCard.js.map +1 -1
- package/dist/teams/types.d.ts +4 -1
- package/dist/teams/types.d.ts.map +1 -1
- package/dist/vocabulary/alert.d.ts +9 -2
- package/dist/vocabulary/alert.d.ts.map +1 -1
- package/dist/vocabulary/alert.js +116 -19
- package/dist/vocabulary/alert.js.map +1 -1
- package/dist/vocabulary/collectFormValues.d.ts +2 -1
- package/dist/vocabulary/collectFormValues.d.ts.map +1 -1
- package/dist/vocabulary/collectFormValues.js +10 -3
- package/dist/vocabulary/collectFormValues.js.map +1 -1
- package/dist/vocabulary/data.d.ts +22 -5
- package/dist/vocabulary/data.d.ts.map +1 -1
- package/dist/vocabulary/data.js +278 -83
- package/dist/vocabulary/data.js.map +1 -1
- package/dist/vocabulary/dispatch.d.ts +3 -2
- package/dist/vocabulary/dispatch.d.ts.map +1 -1
- package/dist/vocabulary/dispatch.js +33 -4
- package/dist/vocabulary/dispatch.js.map +1 -1
- package/dist/vocabulary/fact.d.ts +8 -1
- package/dist/vocabulary/fact.d.ts.map +1 -1
- package/dist/vocabulary/fact.js +37 -11
- package/dist/vocabulary/fact.js.map +1 -1
- package/dist/vocabulary/form.d.ts.map +1 -1
- package/dist/vocabulary/form.js +3 -3
- package/dist/vocabulary/form.js.map +1 -1
- package/dist/vocabulary/formatValue.d.ts +7 -0
- package/dist/vocabulary/formatValue.d.ts.map +1 -0
- package/dist/vocabulary/formatValue.js +76 -0
- package/dist/vocabulary/formatValue.js.map +1 -0
- package/dist/vocabulary/icon.d.ts +1 -1
- package/dist/vocabulary/interactive.d.ts +137 -8
- package/dist/vocabulary/interactive.d.ts.map +1 -1
- package/dist/vocabulary/interactive.js +404 -103
- package/dist/vocabulary/interactive.js.map +1 -1
- package/dist/vocabulary/layout.d.ts.map +1 -1
- package/dist/vocabulary/layout.js +4 -4
- package/dist/vocabulary/layout.js.map +1 -1
- package/dist/vocabulary/list.js +2 -2
- package/dist/vocabulary/list.js.map +1 -1
- package/dist/vocabulary/text.d.ts +1 -1
- package/package.json +6 -3
- package/src/JSONGenerativeUI.client.tsx +84 -40
- package/src/JSONGenerativeUI.test.tsx +964 -68
- package/src/RadioGroupScope.server.test.tsx +24 -0
- package/src/RadioGroupScope.tsx +34 -0
- package/src/a2ui/BindingContext.ts +26 -0
- package/src/a2ui/PresentRenderer.tsx +83 -0
- package/src/a2ui/convert.test.ts +1795 -179
- package/src/a2ui/convert.ts +629 -132
- package/src/a2ui/dataModel.ts +69 -0
- package/src/a2ui/reducer.test.ts +35 -0
- package/src/a2ui/reducer.ts +5 -4
- package/src/a2ui/types.ts +9 -2
- package/src/a2ui/valueFunctions.test.ts +271 -0
- package/src/a2ui/valueFunctions.ts +523 -0
- package/src/actionRegistry.ts +1 -1
- package/src/answeredValues.test.ts +33 -0
- package/src/answeredValues.tsx +70 -0
- package/src/constants.ts +4 -0
- package/src/fieldReferences.test.ts +66 -0
- package/src/fieldReferences.ts +83 -0
- package/src/generativeUIToJSX.test.ts +18 -0
- package/src/generativeUIToJSX.ts +4 -6
- package/src/index.server.test.ts +106 -0
- package/src/ir.ts +1 -1
- package/src/renderGenerativeUI.test.tsx +14 -5
- package/src/renderGenerativeUI.tsx +30 -1
- package/src/slack/a2uiSlack.test.ts +198 -0
- package/src/slack/constants.ts +3 -0
- package/src/slack/decodeBlockAction.test.ts +98 -0
- package/src/slack/decodeBlockAction.ts +110 -17
- package/src/slack/fromSlackBlocks.test.ts +222 -3
- package/src/slack/fromSlackBlocks.ts +53 -2
- package/src/slack/toSlackBlocks.test.ts +682 -3
- package/src/slack/toSlackBlocks.ts +374 -23
- package/src/slack/types.ts +4 -0
- package/src/teams/constants.ts +1 -1
- package/src/teams/decodeSubmitData.test.ts +33 -0
- package/src/teams/decodeSubmitData.ts +9 -1
- package/src/teams/toAdaptiveCard.test.ts +219 -0
- package/src/teams/toAdaptiveCard.ts +93 -19
- package/src/teams/types.ts +4 -1
- package/src/vocabulary/alert.test.tsx +288 -7
- package/src/vocabulary/alert.tsx +168 -26
- package/src/vocabulary/collectFormValues.test.ts +28 -1
- package/src/vocabulary/collectFormValues.ts +25 -5
- package/src/vocabulary/data.test.tsx +248 -6
- package/src/vocabulary/data.tsx +398 -100
- package/src/vocabulary/dispatch.ts +63 -2
- package/src/vocabulary/fact.test.tsx +67 -0
- package/src/vocabulary/fact.tsx +40 -9
- package/src/vocabulary/form.tsx +7 -2
- package/src/vocabulary/formatValue.test.ts +72 -0
- package/src/vocabulary/formatValue.ts +127 -0
- package/src/vocabulary/index.test.ts +2 -0
- package/src/vocabulary/interactive.dispatch.test.tsx +167 -79
- package/src/vocabulary/interactive.dom.test.tsx +1699 -0
- package/src/vocabulary/interactive.test.tsx +60 -4
- package/src/vocabulary/interactive.tsx +770 -151
- package/src/vocabulary/layout.test.tsx +6 -4
- package/src/vocabulary/layout.tsx +13 -2
- package/src/vocabulary/list.tsx +2 -2
package/dist/a2ui/convert.js
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import { ICON_NAMES } from "../ir.js";
|
|
2
2
|
import { A2UI_SURFACE_ID } from "./types.js";
|
|
3
|
+
import "./reducer.js";
|
|
4
|
+
import { decodePointer, resolvePointer } from "./dataModel.js";
|
|
5
|
+
import { evaluateA2uiValueFunction } from "./valueFunctions.js";
|
|
3
6
|
//#region src/a2ui/convert.ts
|
|
4
7
|
const DEPTH_CAP = 32;
|
|
5
8
|
const TEMPLATE_ITEM_CAP = 100;
|
|
6
9
|
const NODE_BUDGET = 5e3;
|
|
10
|
+
const EVALUATION_BUDGET = 2e4;
|
|
7
11
|
const SUPPORTED_COMPONENTS = /* @__PURE__ */ new Set([
|
|
8
12
|
"Text",
|
|
9
13
|
"Image",
|
|
@@ -17,7 +21,13 @@ const SUPPORTED_COMPONENTS = /* @__PURE__ */ new Set([
|
|
|
17
21
|
"TextField",
|
|
18
22
|
"CheckBox",
|
|
19
23
|
"ChoicePicker",
|
|
20
|
-
"DateTimeInput"
|
|
24
|
+
"DateTimeInput",
|
|
25
|
+
"Slider"
|
|
26
|
+
]);
|
|
27
|
+
const CHILD_LIST_COMPONENTS = /* @__PURE__ */ new Set([
|
|
28
|
+
"Row",
|
|
29
|
+
"Column",
|
|
30
|
+
"List"
|
|
21
31
|
]);
|
|
22
32
|
const ICON_NAME_SET = new Set(ICON_NAMES);
|
|
23
33
|
const ICON_SIZE_SET = /* @__PURE__ */ new Set([
|
|
@@ -40,44 +50,85 @@ const setOwnProperty = (target, key, value) => {
|
|
|
40
50
|
});
|
|
41
51
|
else target[key] = value;
|
|
42
52
|
};
|
|
53
|
+
const FUNCTION_CALL_KEYS = /* @__PURE__ */ new Set([
|
|
54
|
+
"call",
|
|
55
|
+
"args",
|
|
56
|
+
"returnType",
|
|
57
|
+
"catalogId"
|
|
58
|
+
]);
|
|
59
|
+
const isFunctionCall = (value) => isPlainObject(value) && typeof value["call"] === "string" && (value["args"] === void 0 || isPlainObject(value["args"])) && Object.keys(value).every((key) => FUNCTION_CALL_KEYS.has(key));
|
|
43
60
|
const isBinding = (value) => isPlainObject(value) && Object.keys(value).length === 1 && typeof value["path"] === "string";
|
|
44
|
-
const
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
61
|
+
const childTemplateOf = (children) => {
|
|
62
|
+
const template = isPlainObject(children) && Object.keys(children).length === 1 && isPlainObject(children["template"]) ? children["template"] : children;
|
|
63
|
+
return isPlainObject(template) && Object.keys(template).length === 2 && typeof template["componentId"] === "string" && typeof template["path"] === "string" ? {
|
|
64
|
+
componentId: template["componentId"],
|
|
65
|
+
path: template["path"]
|
|
66
|
+
} : void 0;
|
|
49
67
|
};
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
68
|
+
const bindingPath = (value) => isBinding(value) ? value.path : void 0;
|
|
69
|
+
const pointerIn = (scope, path) => (path.startsWith("/") ? "" : scope.path) + decodePointer(path).map((segment) => `/${segment.replaceAll("~", "~0").replaceAll("/", "~1")}`).join("");
|
|
70
|
+
const setIn = (value, segments, leaf) => {
|
|
71
|
+
const [head, ...rest] = segments;
|
|
72
|
+
if (head === void 0) return leaf;
|
|
73
|
+
if (/^(0|[1-9]\d*)$/.test(head) && (Array.isArray(value) || value === void 0 || value === null)) {
|
|
74
|
+
const list = Array.isArray(value) ? value : [];
|
|
75
|
+
const index = Number(head);
|
|
76
|
+
if (index >= list.length && index > 1e4) return value;
|
|
77
|
+
const copy = [...list];
|
|
78
|
+
copy[index] = setIn(copy[index], rest, leaf);
|
|
79
|
+
return copy;
|
|
62
80
|
}
|
|
63
|
-
|
|
81
|
+
const copy = isRecord(value) ? { ...value } : {};
|
|
82
|
+
setOwnProperty(copy, head, setIn(Object.hasOwn(copy, head) ? copy[head] : void 0, rest, leaf));
|
|
83
|
+
return copy;
|
|
64
84
|
};
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
85
|
+
const withFieldReferences = (value, pointer, fields) => {
|
|
86
|
+
if (fields.has(pointer)) return fields.get(pointer);
|
|
87
|
+
let result = value;
|
|
88
|
+
for (const [name, field] of fields) if (name.startsWith(`${pointer}/`)) result = setIn(result, decodePointer(name.slice(pointer.length)), field);
|
|
89
|
+
return result;
|
|
69
90
|
};
|
|
70
|
-
const
|
|
71
|
-
if (isBinding(value)) return resolvePointer(source, value.path);
|
|
72
|
-
if (Array.isArray(value)) return value.map((entry) => materialize(entry, source)).filter((entry) => entry !== void 0);
|
|
73
|
-
if (!isPlainObject(value)) return value;
|
|
91
|
+
const materializeEntries = (value, source, context, evaluate, depth, positional = false) => {
|
|
74
92
|
const result = {};
|
|
75
93
|
for (const [key, entry] of Object.entries(value)) {
|
|
76
|
-
const resolved =
|
|
94
|
+
const resolved = key === "functionCall" && isFunctionCall(entry) ? {
|
|
95
|
+
...entry,
|
|
96
|
+
...entry.args !== void 0 ? { args: materializeEntries(entry.args, source, context, evaluate, depth, true) } : {}
|
|
97
|
+
} : materialize(entry, source, context, evaluate, depth, positional);
|
|
77
98
|
if (resolved !== void 0) setOwnProperty(result, key, resolved);
|
|
78
99
|
}
|
|
79
100
|
return result;
|
|
80
101
|
};
|
|
102
|
+
function materialize(value, source, context, evaluate = true, depth = 0, positional = false) {
|
|
103
|
+
if (isBinding(value)) {
|
|
104
|
+
const pointer = pointerIn({
|
|
105
|
+
data: source,
|
|
106
|
+
path: context.scopePath
|
|
107
|
+
}, value.path);
|
|
108
|
+
const resolved = resolvePointer(context.surface.dataModel, pointer);
|
|
109
|
+
context.dependencies?.set(pointer, resolved);
|
|
110
|
+
return resolved;
|
|
111
|
+
}
|
|
112
|
+
if (Array.isArray(value)) {
|
|
113
|
+
const entries = value.map((entry) => materialize(entry, source, context, evaluate, depth, positional));
|
|
114
|
+
return positional ? entries : entries.filter((entry) => entry !== void 0);
|
|
115
|
+
}
|
|
116
|
+
if (!isPlainObject(value)) return value;
|
|
117
|
+
if (!evaluate || !isFunctionCall(value)) return materializeEntries(value, source, context, evaluate, depth, positional);
|
|
118
|
+
if (!spendEvaluation(context)) return void 0;
|
|
119
|
+
if (depth >= DEPTH_CAP) {
|
|
120
|
+
if (!context.functionDepthWarned) {
|
|
121
|
+
context.warnings.push(`A2UI function nesting cap of ${DEPTH_CAP} was reached.`);
|
|
122
|
+
context.functionDepthWarned = true;
|
|
123
|
+
}
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
return evaluateA2uiValueFunction(value.call, materializeEntries(value.args ?? {}, source, context, true, depth + 1, true), {
|
|
127
|
+
resolve: (part) => spendEvaluation(context) ? materialize(part, source, context, true, depth + 1) : void 0,
|
|
128
|
+
warn: (message) => context.warnings.push(message),
|
|
129
|
+
templates: context.templates
|
|
130
|
+
});
|
|
131
|
+
}
|
|
81
132
|
const firstDefined = (props, keys) => {
|
|
82
133
|
for (const key of keys) if (props[key] !== void 0) return props[key];
|
|
83
134
|
};
|
|
@@ -86,6 +137,74 @@ const stringProp = (props, keys) => {
|
|
|
86
137
|
return typeof value === "string" ? value : void 0;
|
|
87
138
|
};
|
|
88
139
|
const sourceSurfaceId = (surface) => surface[A2UI_SURFACE_ID] ?? "";
|
|
140
|
+
const BOUND_KEYS = [
|
|
141
|
+
"text",
|
|
142
|
+
"value",
|
|
143
|
+
"binding"
|
|
144
|
+
];
|
|
145
|
+
const INPUT_COMPONENTS = /* @__PURE__ */ new Set([
|
|
146
|
+
"TextField",
|
|
147
|
+
"CheckBox",
|
|
148
|
+
"ChoicePicker",
|
|
149
|
+
"DateTimeInput",
|
|
150
|
+
"Slider"
|
|
151
|
+
]);
|
|
152
|
+
const DATE_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
|
|
153
|
+
const finiteNumber = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
154
|
+
const decimalPlaces = (value) => {
|
|
155
|
+
const [mantissa = "", exponent = "0"] = String(value).split("e");
|
|
156
|
+
return Math.max(0, (mantissa.split(".")[1]?.length ?? 0) - Number(exponent));
|
|
157
|
+
};
|
|
158
|
+
const sliderStep = (min, max, value, steps) => {
|
|
159
|
+
if (max <= min) return void 0;
|
|
160
|
+
if (typeof steps === "number" && Number.isInteger(steps) && steps >= 1) return (max - min) / steps;
|
|
161
|
+
const precision = Math.max(decimalPlaces(min), value === void 0 ? 0 : decimalPlaces(value));
|
|
162
|
+
return Math.min(10 ** Math.floor(Math.log10((max - min) / 100)), 10 ** -precision);
|
|
163
|
+
};
|
|
164
|
+
const recordBindings = (node, props, mapped, scope, context) => {
|
|
165
|
+
const component = node["component"];
|
|
166
|
+
const name = mapped["name"];
|
|
167
|
+
const boundKey = BOUND_KEYS.find((key) => node[key] !== void 0);
|
|
168
|
+
const value = boundKey === void 0 ? void 0 : props[boundKey];
|
|
169
|
+
if (INPUT_COMPONENTS.has(String(component)) && typeof name === "string") {
|
|
170
|
+
context.textFields.set(name, (!context.textFields.has(name) || context.bindings !== void 0) && (component === "TextField" || component === "DateTimeInput"));
|
|
171
|
+
const field = (fallback) => fallback === void 0 ? { $field: name } : {
|
|
172
|
+
$field: name,
|
|
173
|
+
fallback
|
|
174
|
+
};
|
|
175
|
+
const listValued = component === "ChoicePicker" && mapped.$type !== "CheckboxGroup" && typeof value !== "string";
|
|
176
|
+
context.inputFields.set(name, listValued ? [field(Array.isArray(value) ? value[0] : void 0)] : field(value));
|
|
177
|
+
}
|
|
178
|
+
const action = mapped.$action;
|
|
179
|
+
const raw = node["action"];
|
|
180
|
+
if (!action || !isRecord(raw)) return;
|
|
181
|
+
const functionCall = action.type === "a2ui:functionCall";
|
|
182
|
+
const event = isRecord(raw["event"]) ? raw["event"] : raw;
|
|
183
|
+
const userMessage = event["userMessage"];
|
|
184
|
+
if (!functionCall && isBinding(userMessage)) context.boundUserMessages.push({
|
|
185
|
+
action,
|
|
186
|
+
pointer: pointerIn(scope, userMessage.path)
|
|
187
|
+
});
|
|
188
|
+
const rawEntries = functionCall ? isRecord(raw["functionCall"]) ? raw["functionCall"]["args"] : void 0 : event["context"];
|
|
189
|
+
const target = functionCall ? action["args"] : action["context"];
|
|
190
|
+
if (!isRecord(rawEntries) || !isRecord(target)) return;
|
|
191
|
+
for (const [key, entry] of Object.entries(rawEntries)) if (isBinding(entry)) context.boundActionEntries.push({
|
|
192
|
+
target,
|
|
193
|
+
key,
|
|
194
|
+
pointer: pointerIn(scope, entry.path)
|
|
195
|
+
});
|
|
196
|
+
};
|
|
197
|
+
const spendEvaluation = (context) => {
|
|
198
|
+
if (context.evaluations < EVALUATION_BUDGET) {
|
|
199
|
+
context.evaluations++;
|
|
200
|
+
return true;
|
|
201
|
+
}
|
|
202
|
+
if (!context.evaluationBudgetWarned) {
|
|
203
|
+
context.warnings.push(`A2UI function evaluation budget of ${EVALUATION_BUDGET} was reached.`);
|
|
204
|
+
context.evaluationBudgetWarned = true;
|
|
205
|
+
}
|
|
206
|
+
return false;
|
|
207
|
+
};
|
|
89
208
|
const reserveNode = (context) => {
|
|
90
209
|
if (context.emittedNodes >= NODE_BUDGET) {
|
|
91
210
|
if (!context.budgetWarned) {
|
|
@@ -105,14 +224,14 @@ const childReferences = (node) => {
|
|
|
105
224
|
if (node["component"] === "Tabs" && Array.isArray(node["tabs"])) for (const tab of node["tabs"]) references.push(isRecord(tab) ? tab["child"] : void 0);
|
|
106
225
|
return references;
|
|
107
226
|
};
|
|
108
|
-
const childrenOf = (node,
|
|
227
|
+
const childrenOf = (node, scope, context, depth, visited) => {
|
|
109
228
|
const result = [];
|
|
110
229
|
for (const childId of childReferences(node)) {
|
|
111
230
|
if (typeof childId !== "string") {
|
|
112
231
|
context.warnings.push(`Component "${String(node["id"] ?? "")}" has a malformed child reference.`);
|
|
113
232
|
continue;
|
|
114
233
|
}
|
|
115
|
-
const child = convertComponent(childId,
|
|
234
|
+
const child = convertComponent(childId, scope, context, depth + 1, visited);
|
|
116
235
|
if (child) result.push(child);
|
|
117
236
|
}
|
|
118
237
|
return result;
|
|
@@ -127,17 +246,35 @@ const textLabel = (node, children, context) => {
|
|
|
127
246
|
};
|
|
128
247
|
const mappedAction = (node, props, context) => {
|
|
129
248
|
const action = props["action"];
|
|
130
|
-
|
|
131
|
-
const
|
|
132
|
-
if (!actionName) return void 0;
|
|
133
|
-
const actionContext = isRecord(event) ? event["context"] : void 0;
|
|
134
|
-
return {
|
|
135
|
-
type: "a2ui:action",
|
|
136
|
-
name: actionName,
|
|
249
|
+
if (action === void 0) return void 0;
|
|
250
|
+
const source = {
|
|
137
251
|
surfaceId: context.surfaceId,
|
|
138
|
-
sourceComponentId: typeof node["id"] === "string" ? node["id"] : ""
|
|
139
|
-
...actionContext !== void 0 ? { context: actionContext } : {}
|
|
252
|
+
sourceComponentId: typeof node["id"] === "string" ? node["id"] : ""
|
|
140
253
|
};
|
|
254
|
+
const event = isRecord(action) && isRecord(action["event"]) ? action["event"] : action;
|
|
255
|
+
const actionName = typeof event === "string" ? event : isRecord(event) && typeof event["name"] === "string" ? event["name"] : void 0;
|
|
256
|
+
if (actionName) {
|
|
257
|
+
const actionContext = isRecord(event) ? event["context"] : void 0;
|
|
258
|
+
const userMessage = isRecord(event) ? event["userMessage"] : void 0;
|
|
259
|
+
return {
|
|
260
|
+
type: "a2ui:action",
|
|
261
|
+
name: actionName,
|
|
262
|
+
...source,
|
|
263
|
+
...typeof userMessage === "string" ? { userMessage } : {},
|
|
264
|
+
...actionContext !== void 0 ? { context: actionContext } : {}
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
const functionCall = isRecord(action) ? action["functionCall"] : void 0;
|
|
268
|
+
if (isRecord(functionCall) && typeof functionCall["call"] === "string") {
|
|
269
|
+
const args = functionCall["args"];
|
|
270
|
+
return {
|
|
271
|
+
type: "a2ui:functionCall",
|
|
272
|
+
call: functionCall["call"],
|
|
273
|
+
...source,
|
|
274
|
+
...isRecord(args) ? { args } : {}
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
context.warnings.push(`Component "${String(node["id"] ?? "")}" has a malformed action.`);
|
|
141
278
|
};
|
|
142
279
|
const choiceOptions = (value) => {
|
|
143
280
|
if (!Array.isArray(value)) return [];
|
|
@@ -148,7 +285,7 @@ const choiceOptions = (value) => {
|
|
|
148
285
|
});
|
|
149
286
|
return options;
|
|
150
287
|
};
|
|
151
|
-
const mappedProps = (node, props, context) => {
|
|
288
|
+
const mappedProps = (node, props, context, scope) => {
|
|
152
289
|
const component = node["component"];
|
|
153
290
|
if (component === "Text") {
|
|
154
291
|
const text = stringProp(props, ["text", "value"]);
|
|
@@ -239,35 +376,37 @@ const mappedProps = (node, props, context) => {
|
|
|
239
376
|
};
|
|
240
377
|
if (component === "Button") {
|
|
241
378
|
const label = stringProp(props, ["label", "text"]);
|
|
242
|
-
const
|
|
379
|
+
const variantStyle = props["variant"] === "primary" ? "primary" : props["variant"] === "borderless" ? "ghost" : void 0;
|
|
380
|
+
const buttonStyle = typeof props["buttonStyle"] === "string" ? props["buttonStyle"] : variantStyle;
|
|
243
381
|
const block = props["block"];
|
|
244
382
|
const submit = props["submit"];
|
|
245
383
|
const action = mappedAction(node, props, context);
|
|
246
384
|
return {
|
|
247
385
|
$type: "Button",
|
|
248
386
|
...label !== void 0 ? { label } : {},
|
|
249
|
-
...
|
|
387
|
+
...buttonStyle !== void 0 ? { buttonStyle } : {},
|
|
250
388
|
...typeof block === "boolean" ? { block } : {},
|
|
251
389
|
...typeof submit === "boolean" ? { submit } : {},
|
|
252
390
|
...action ? { $action: action } : {}
|
|
253
391
|
};
|
|
254
392
|
}
|
|
255
|
-
const
|
|
256
|
-
|
|
257
|
-
"value",
|
|
258
|
-
"binding"
|
|
259
|
-
]);
|
|
260
|
-
const name = lastPointerSegment(bindingPath(binding));
|
|
393
|
+
const bound = bindingPath(firstDefined(node, BOUND_KEYS));
|
|
394
|
+
const name = bound === void 0 ? void 0 : pointerIn(scope, bound);
|
|
261
395
|
const label = stringProp(props, ["label"]);
|
|
262
396
|
if (component === "TextField") {
|
|
263
397
|
const placeholder = stringProp(props, ["placeholder"]);
|
|
264
|
-
const
|
|
398
|
+
const inputType = props["variant"] === "obscured" ? "password" : props["variant"] === "number" ? "number" : void 0;
|
|
399
|
+
const multiline = typeof props["multiline"] === "boolean" ? props["multiline"] : props["variant"] === "longText" || props["textFieldType"] === "longText" ? true : void 0;
|
|
400
|
+
const initial = firstDefined(props, ["value", "text"]);
|
|
401
|
+
const defaultValue = typeof initial === "string" ? initial : typeof initial === "number" && Number.isFinite(initial) ? String(initial) : void 0;
|
|
265
402
|
return {
|
|
266
403
|
$type: "Input",
|
|
404
|
+
...inputType !== void 0 ? { inputType } : {},
|
|
267
405
|
...placeholder !== void 0 ? { placeholder } : {},
|
|
268
406
|
...multiline !== void 0 ? { multiline } : {},
|
|
269
407
|
...label !== void 0 ? { label } : {},
|
|
270
|
-
...name !== void 0 ? { name } : {}
|
|
408
|
+
...name !== void 0 ? { name } : {},
|
|
409
|
+
...defaultValue !== void 0 ? { defaultValue } : {}
|
|
271
410
|
};
|
|
272
411
|
}
|
|
273
412
|
if (component === "CheckBox") {
|
|
@@ -279,28 +418,51 @@ const mappedProps = (node, props, context) => {
|
|
|
279
418
|
...typeof defaultChecked === "boolean" ? { defaultChecked } : {}
|
|
280
419
|
};
|
|
281
420
|
}
|
|
421
|
+
if (component === "Slider") {
|
|
422
|
+
const min = finiteNumber(props["min"]) ?? 0;
|
|
423
|
+
const max = finiteNumber(props["max"]);
|
|
424
|
+
if (max === void 0 || max < min) return void 0;
|
|
425
|
+
const value = finiteNumber(props["value"]);
|
|
426
|
+
const step = sliderStep(min, max, name === void 0 ? value : finiteNumber(resolvePointer(context.stepModel, name)), props["steps"]);
|
|
427
|
+
return {
|
|
428
|
+
$type: "Slider",
|
|
429
|
+
min,
|
|
430
|
+
max,
|
|
431
|
+
...step !== void 0 ? { step } : {},
|
|
432
|
+
...value !== void 0 ? { defaultValue: value } : {},
|
|
433
|
+
...label !== void 0 ? { label } : {},
|
|
434
|
+
...name !== void 0 ? { name } : {}
|
|
435
|
+
};
|
|
436
|
+
}
|
|
282
437
|
if (component === "ChoicePicker") {
|
|
283
438
|
const options = choiceOptions(props["options"]);
|
|
284
439
|
const value = props["value"];
|
|
285
|
-
const
|
|
286
|
-
if (props["variant"] === "
|
|
287
|
-
$type: "
|
|
440
|
+
const selected = typeof value === "string" ? [value] : Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
|
|
441
|
+
if (props["variant"] === "multipleSelection") return {
|
|
442
|
+
$type: "CheckboxGroup",
|
|
288
443
|
options,
|
|
289
444
|
...label !== void 0 ? { label } : {},
|
|
290
445
|
...name !== void 0 ? { name } : {},
|
|
291
|
-
...
|
|
446
|
+
...selected.length > 0 ? { defaultValue: selected } : {}
|
|
292
447
|
};
|
|
293
|
-
const
|
|
448
|
+
const [defaultValue] = selected;
|
|
294
449
|
return {
|
|
295
|
-
$type: "
|
|
450
|
+
$type: "RadioGroup",
|
|
296
451
|
options,
|
|
297
|
-
...placeholder !== void 0 ? { placeholder } : {},
|
|
298
452
|
...label !== void 0 ? { label } : {},
|
|
299
|
-
...name !== void 0 ? { name } : {}
|
|
453
|
+
...name !== void 0 ? { name } : {},
|
|
454
|
+
...defaultValue !== void 0 ? { defaultValue } : {}
|
|
300
455
|
};
|
|
301
456
|
}
|
|
302
457
|
if (component === "DateTimeInput") {
|
|
303
458
|
const value = stringProp(props, ["value"]);
|
|
459
|
+
const shapeValue = name === void 0 ? value : resolvePointer(context.stepModel, name);
|
|
460
|
+
if (props["enableTime"] === true || typeof shapeValue === "string" && shapeValue !== "" && !DATE_PATTERN.test(shapeValue)) return {
|
|
461
|
+
$type: "Input",
|
|
462
|
+
...value !== void 0 ? { defaultValue: value } : {},
|
|
463
|
+
...label !== void 0 ? { label } : {},
|
|
464
|
+
...name !== void 0 ? { name } : {}
|
|
465
|
+
};
|
|
304
466
|
const min = stringProp(props, ["min"]);
|
|
305
467
|
const max = stringProp(props, ["max"]);
|
|
306
468
|
return {
|
|
@@ -313,11 +475,12 @@ const mappedProps = (node, props, context) => {
|
|
|
313
475
|
};
|
|
314
476
|
}
|
|
315
477
|
};
|
|
316
|
-
const convertTemplate = (node,
|
|
478
|
+
const convertTemplate = (node, template, scope, context, depth, visited, converted) => {
|
|
317
479
|
if (!reserveNode(context)) return null;
|
|
318
|
-
const
|
|
319
|
-
const
|
|
320
|
-
const list = resolvePointer(
|
|
480
|
+
const container = converted ?? { $type: "ListView" };
|
|
481
|
+
const itemized = container.$type === "ListView";
|
|
482
|
+
const list = resolvePointer(context.surface.dataModel, pointerIn(scope, template.path));
|
|
483
|
+
const listPointer = pointerIn(scope, template.path);
|
|
321
484
|
if (!Array.isArray(list)) {
|
|
322
485
|
context.warnings.push(`Template on component "${String(node["id"] ?? "")}" did not resolve to a list.`);
|
|
323
486
|
return {
|
|
@@ -332,21 +495,23 @@ const convertTemplate = (node, templateChildren, dataSource, context, depth, vis
|
|
|
332
495
|
}
|
|
333
496
|
const children = [];
|
|
334
497
|
for (let index = 0; index < itemCount; index++) {
|
|
335
|
-
if (
|
|
336
|
-
const child = convertComponent(
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
}
|
|
498
|
+
if (itemized && !reserveNode(context)) break;
|
|
499
|
+
const child = convertComponent(template.componentId, {
|
|
500
|
+
data: list[index],
|
|
501
|
+
path: `${listPointer}/${index}`
|
|
502
|
+
}, context, depth + 1, visited);
|
|
503
|
+
if (itemized) children.push({
|
|
340
504
|
$type: "ListViewItem",
|
|
341
505
|
...child ? { children: child } : {}
|
|
342
506
|
});
|
|
507
|
+
else if (child) children.push(child);
|
|
343
508
|
}
|
|
344
509
|
return {
|
|
345
510
|
...container,
|
|
346
511
|
children
|
|
347
512
|
};
|
|
348
513
|
};
|
|
349
|
-
function convertComponent(componentId,
|
|
514
|
+
function convertComponent(componentId, scope, context, depth, visited) {
|
|
350
515
|
if (depth >= DEPTH_CAP) {
|
|
351
516
|
if (!context.depthWarned) {
|
|
352
517
|
context.warnings.push(`A2UI depth cap of ${DEPTH_CAP} was reached.`);
|
|
@@ -370,19 +535,61 @@ function convertComponent(componentId, dataSource, context, depth, visited) {
|
|
|
370
535
|
context.warnings.push(`Unknown A2UI component "${String(component ?? "")}" was skipped.`);
|
|
371
536
|
return null;
|
|
372
537
|
}
|
|
373
|
-
const
|
|
374
|
-
|
|
375
|
-
if (!SUPPORTED_COMPONENTS.has(component) && !context.keepUnknownComponents && !hasTemplate) {
|
|
538
|
+
const template = childTemplateOf(node["children"]);
|
|
539
|
+
if (!SUPPORTED_COMPONENTS.has(component) && !context.keepUnknownComponents && !template) {
|
|
376
540
|
context.warnings.push(`Unknown A2UI component "${component}" was skipped.`);
|
|
377
541
|
return null;
|
|
378
542
|
}
|
|
379
|
-
const
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
543
|
+
const cacheKey = context.cache ? JSON.stringify([
|
|
544
|
+
componentId,
|
|
545
|
+
scope.path,
|
|
546
|
+
depth
|
|
547
|
+
]) : "";
|
|
548
|
+
let cached = context.cache?.get(cacheKey);
|
|
549
|
+
if (cached && (context.evaluations + cached.evaluations > EVALUATION_BUDGET || [...cached.dependencies].some(([path, value]) => !Object.is(resolvePointer(context.surface.dataModel, path), value)))) cached = void 0;
|
|
550
|
+
let props;
|
|
551
|
+
let mapped;
|
|
552
|
+
if (cached) {
|
|
553
|
+
props = cached.props;
|
|
554
|
+
mapped = cached.mapped;
|
|
555
|
+
context.evaluations += cached.evaluations;
|
|
556
|
+
} else {
|
|
557
|
+
props = {};
|
|
558
|
+
const dependencies = /* @__PURE__ */ new Map();
|
|
559
|
+
const evaluations = context.evaluations;
|
|
560
|
+
context.dependencies = dependencies;
|
|
561
|
+
context.scopePath = scope.path;
|
|
562
|
+
for (const [key, value] of Object.entries(node)) {
|
|
563
|
+
if (key === "id" || key === "component" || key === "children" || key === "child") continue;
|
|
564
|
+
const resolved = materialize(value, scope.data, context, key !== "checks");
|
|
565
|
+
if (resolved !== void 0) setOwnProperty(props, key, resolved);
|
|
566
|
+
}
|
|
567
|
+
context.dependencies = void 0;
|
|
568
|
+
mapped = mappedProps(node, props, context, scope);
|
|
569
|
+
if (context.cache && context.warnings.length === 0 && !mapped?.$action) {
|
|
570
|
+
cached = {
|
|
571
|
+
props,
|
|
572
|
+
mapped,
|
|
573
|
+
dependencies,
|
|
574
|
+
evaluations: context.evaluations - evaluations
|
|
575
|
+
};
|
|
576
|
+
context.cache.set(cacheKey, cached);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
if (mapped && context.bindings && typeof mapped["name"] === "string" && INPUT_COMPONENTS.has(component)) {
|
|
580
|
+
const field = mapped.$type === "Checkbox" ? "checked" : "value";
|
|
581
|
+
const defaultField = mapped.$type === "Checkbox" ? "defaultChecked" : "defaultValue";
|
|
582
|
+
const fallback = mapped.$type === "Checkbox" ? false : mapped.$type === "CheckboxGroup" ? [] : mapped.$type === "Slider" ? mapped["min"] : "";
|
|
583
|
+
const { [defaultField]: initialValue, ...rest } = mapped;
|
|
584
|
+
mapped = {
|
|
585
|
+
...rest,
|
|
586
|
+
[field]: initialValue ?? mapped[field] ?? fallback
|
|
587
|
+
};
|
|
588
|
+
context.bindings.set(mapped["name"], {
|
|
589
|
+
value: mapped[field],
|
|
590
|
+
arrayValue: component === "ChoicePicker" && mapped.$type !== "CheckboxGroup" && typeof props["value"] !== "string"
|
|
591
|
+
});
|
|
384
592
|
}
|
|
385
|
-
const mapped = mappedProps(node, props, context);
|
|
386
593
|
if (!mapped && SUPPORTED_COMPONENTS.has(component)) {
|
|
387
594
|
context.warnings.push(`A2UI component "${component}" could not be mapped and was skipped.`);
|
|
388
595
|
return null;
|
|
@@ -391,11 +598,12 @@ function convertComponent(componentId, dataSource, context, depth, visited) {
|
|
|
391
598
|
$type: component,
|
|
392
599
|
...Object.fromEntries(Object.entries(props).filter(([key]) => !key.startsWith("$")))
|
|
393
600
|
} : void 0;
|
|
394
|
-
if (
|
|
601
|
+
if (template) return convertTemplate(node, template, scope, context, depth, visited, CHILD_LIST_COMPONENTS.has(component) ? mapped : retained);
|
|
395
602
|
if (!reserveNode(context)) return null;
|
|
396
603
|
const converted = mapped ?? retained;
|
|
397
604
|
if (!converted) return null;
|
|
398
|
-
|
|
605
|
+
recordBindings(node, props, converted, scope, context);
|
|
606
|
+
const children = childrenOf(node, scope, context, depth, visited);
|
|
399
607
|
if (mapped?.$type === "Button" && mapped["label"] === void 0) {
|
|
400
608
|
const label = textLabel(node, children, context);
|
|
401
609
|
if (label !== void 0) return {
|
|
@@ -407,15 +615,39 @@ function convertComponent(componentId, dataSource, context, depth, visited) {
|
|
|
407
615
|
$type: "ListViewItem",
|
|
408
616
|
children: child
|
|
409
617
|
})) : children;
|
|
410
|
-
return
|
|
618
|
+
if (cached?.result && cached.children?.length === listChildren.length && cached.children.every((child, index) => child === listChildren[index])) return cached.result;
|
|
619
|
+
const result = {
|
|
411
620
|
...converted,
|
|
412
621
|
...listChildren.length > 0 ? { children: listChildren } : {}
|
|
413
622
|
};
|
|
623
|
+
if (cached) {
|
|
624
|
+
cached.children = listChildren;
|
|
625
|
+
cached.result = result;
|
|
626
|
+
}
|
|
627
|
+
return result;
|
|
414
628
|
} finally {
|
|
415
629
|
visited.delete(componentId);
|
|
416
630
|
}
|
|
417
631
|
}
|
|
418
632
|
function convertSurfaceToUISpec(surface, options = {}) {
|
|
633
|
+
return convertSurface(surface, options.keepUnknownComponents === true);
|
|
634
|
+
}
|
|
635
|
+
const createLiveSurfaceConverter = (surface) => {
|
|
636
|
+
const cache = /* @__PURE__ */ new Map();
|
|
637
|
+
const templates = /* @__PURE__ */ new Map();
|
|
638
|
+
return (dataModel) => {
|
|
639
|
+
const bindings = /* @__PURE__ */ new Map();
|
|
640
|
+
return {
|
|
641
|
+
...convertSurface({
|
|
642
|
+
...surface,
|
|
643
|
+
dataModel,
|
|
644
|
+
[A2UI_SURFACE_ID]: sourceSurfaceId(surface)
|
|
645
|
+
}, false, bindings, cache, templates, surface.dataModel),
|
|
646
|
+
bindings
|
|
647
|
+
};
|
|
648
|
+
};
|
|
649
|
+
};
|
|
650
|
+
function convertSurface(surface, keepUnknownComponents, bindings, cache, templates = /* @__PURE__ */ new Map(), stepModel = surface.dataModel) {
|
|
419
651
|
const warnings = [];
|
|
420
652
|
if (!surface.components.has("root")) return {
|
|
421
653
|
spec: null,
|
|
@@ -423,17 +655,46 @@ function convertSurfaceToUISpec(surface, options = {}) {
|
|
|
423
655
|
};
|
|
424
656
|
const context = {
|
|
425
657
|
surface,
|
|
658
|
+
stepModel,
|
|
426
659
|
surfaceId: sourceSurfaceId(surface),
|
|
427
660
|
warnings,
|
|
428
661
|
emittedNodes: 0,
|
|
429
662
|
depthWarned: false,
|
|
430
663
|
budgetWarned: false,
|
|
431
664
|
templateCapWarned: false,
|
|
432
|
-
|
|
665
|
+
evaluations: 0,
|
|
666
|
+
evaluationBudgetWarned: false,
|
|
667
|
+
functionDepthWarned: false,
|
|
668
|
+
templates,
|
|
669
|
+
inputFields: /* @__PURE__ */ new Map(),
|
|
670
|
+
textFields: /* @__PURE__ */ new Map(),
|
|
671
|
+
boundActionEntries: [],
|
|
672
|
+
boundUserMessages: [],
|
|
673
|
+
keepUnknownComponents,
|
|
674
|
+
bindings,
|
|
675
|
+
cache,
|
|
676
|
+
dependencies: void 0,
|
|
677
|
+
scopePath: ""
|
|
433
678
|
};
|
|
434
679
|
try {
|
|
680
|
+
const spec = convertComponent("root", {
|
|
681
|
+
data: surface.dataModel,
|
|
682
|
+
path: ""
|
|
683
|
+
}, context, 0, /* @__PURE__ */ new Set());
|
|
684
|
+
for (const { target, key, pointer } of context.boundActionEntries) {
|
|
685
|
+
const value = withFieldReferences(target[key], pointer, context.inputFields);
|
|
686
|
+
if (value !== void 0) setOwnProperty(target, key, value);
|
|
687
|
+
}
|
|
688
|
+
for (const { action, pointer } of context.boundUserMessages) {
|
|
689
|
+
if (!context.textFields.get(pointer)) continue;
|
|
690
|
+
const fallback = action["userMessage"];
|
|
691
|
+
setOwnProperty(action, "userMessage", typeof fallback === "string" ? {
|
|
692
|
+
$field: pointer,
|
|
693
|
+
fallback
|
|
694
|
+
} : { $field: pointer });
|
|
695
|
+
}
|
|
435
696
|
return {
|
|
436
|
-
spec
|
|
697
|
+
spec,
|
|
437
698
|
warnings
|
|
438
699
|
};
|
|
439
700
|
} catch {
|
|
@@ -445,6 +706,6 @@ function convertSurfaceToUISpec(surface, options = {}) {
|
|
|
445
706
|
}
|
|
446
707
|
}
|
|
447
708
|
//#endregion
|
|
448
|
-
export { convertSurfaceToUISpec };
|
|
709
|
+
export { convertSurfaceToUISpec, createLiveSurfaceConverter };
|
|
449
710
|
|
|
450
711
|
//# sourceMappingURL=convert.js.map
|