autoui-react 0.1.0 → 0.1.1-alpha
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +52 -6
- package/dist/index.css +10 -0
- package/dist/index.css.map +1 -1
- package/dist/index.d.mts +453 -121
- package/dist/index.d.ts +453 -121
- package/dist/index.js +3163 -794
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +3123 -786
- package/dist/index.mjs.map +1 -1
- package/package.json +49 -9
package/dist/index.mjs
CHANGED
|
@@ -1,24 +1,112 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { clsx } from 'clsx';
|
|
2
|
+
import { twMerge } from 'tailwind-merge';
|
|
3
|
+
import { Slot } from '@radix-ui/react-slot';
|
|
4
|
+
import { cva } from 'class-variance-authority';
|
|
5
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
6
|
+
import { createAnthropic } from '@ai-sdk/anthropic';
|
|
7
|
+
import { generateObject } from 'ai';
|
|
2
8
|
import { z } from 'zod';
|
|
3
|
-
import {
|
|
9
|
+
import React, { useState, useRef, useEffect, useCallback, useReducer } from 'react';
|
|
10
|
+
import * as DialogPrimitive from '@radix-ui/react-dialog';
|
|
11
|
+
import { XIcon, ChevronDownIcon, CheckIcon, ChevronUpIcon, CircleIcon } from 'lucide-react';
|
|
12
|
+
import * as SelectPrimitive from '@radix-ui/react-select';
|
|
13
|
+
import * as CheckboxPrimitive from '@radix-ui/react-checkbox';
|
|
14
|
+
import * as RadioGroupPrimitive from '@radix-ui/react-radio-group';
|
|
15
|
+
import * as TabsPrimitive from '@radix-ui/react-tabs';
|
|
16
|
+
import * as LabelPrimitive from '@radix-ui/react-label';
|
|
17
|
+
|
|
18
|
+
var __defProp = Object.defineProperty;
|
|
19
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
20
|
+
var __esm = (fn, res) => function __init() {
|
|
21
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
22
|
+
};
|
|
23
|
+
var __export = (target, all) => {
|
|
24
|
+
for (var name in all)
|
|
25
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
26
|
+
};
|
|
27
|
+
function cn(...inputs) {
|
|
28
|
+
return twMerge(clsx(inputs));
|
|
29
|
+
}
|
|
30
|
+
var init_utils = __esm({
|
|
31
|
+
"src/lib/utils.ts"() {
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// components/ui/button.tsx
|
|
36
|
+
var button_exports = {};
|
|
37
|
+
__export(button_exports, {
|
|
38
|
+
Button: () => Button2,
|
|
39
|
+
buttonVariants: () => buttonVariants
|
|
40
|
+
});
|
|
41
|
+
function Button2({
|
|
42
|
+
className,
|
|
43
|
+
variant,
|
|
44
|
+
size,
|
|
45
|
+
asChild = false,
|
|
46
|
+
...props
|
|
47
|
+
}) {
|
|
48
|
+
const Comp = asChild ? Slot : "button";
|
|
49
|
+
return /* @__PURE__ */ jsx(
|
|
50
|
+
Comp,
|
|
51
|
+
{
|
|
52
|
+
"data-slot": "button",
|
|
53
|
+
className: cn(buttonVariants({ variant, size, className })),
|
|
54
|
+
...props
|
|
55
|
+
}
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
var buttonVariants;
|
|
59
|
+
var init_button = __esm({
|
|
60
|
+
"components/ui/button.tsx"() {
|
|
61
|
+
init_utils();
|
|
62
|
+
buttonVariants = cva(
|
|
63
|
+
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
|
64
|
+
{
|
|
65
|
+
variants: {
|
|
66
|
+
variant: {
|
|
67
|
+
default: "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
|
|
68
|
+
destructive: "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
|
|
69
|
+
outline: "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
|
|
70
|
+
secondary: "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
|
|
71
|
+
ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
|
|
72
|
+
link: "text-primary underline-offset-4 hover:underline"
|
|
73
|
+
},
|
|
74
|
+
size: {
|
|
75
|
+
default: "h-9 px-4 py-2 has-[>svg]:px-3",
|
|
76
|
+
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
|
|
77
|
+
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
|
|
78
|
+
icon: "size-9"
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
defaultVariants: {
|
|
82
|
+
variant: "default",
|
|
83
|
+
size: "default"
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
4
89
|
|
|
5
90
|
// src/core/reducer.ts
|
|
6
91
|
function cloneNode(node) {
|
|
7
92
|
return {
|
|
8
93
|
...node,
|
|
9
|
-
props: node.props ? { ...node.props } :
|
|
10
|
-
bindings: node.bindings ? { ...node.bindings } :
|
|
11
|
-
events: node.events ? { ...node.events } :
|
|
12
|
-
children: node.children
|
|
94
|
+
props: node.props ? { ...node.props } : null,
|
|
95
|
+
bindings: node.bindings ? { ...node.bindings } : null,
|
|
96
|
+
events: node.events ? { ...node.events } : null,
|
|
97
|
+
children: node.children ? node.children.map((child) => cloneNode(child)) : null
|
|
13
98
|
};
|
|
14
99
|
}
|
|
15
100
|
function findNodeById(tree, nodeId) {
|
|
16
|
-
if (!tree)
|
|
17
|
-
|
|
101
|
+
if (!tree)
|
|
102
|
+
return void 0;
|
|
103
|
+
if (tree.id === nodeId)
|
|
104
|
+
return tree;
|
|
18
105
|
if (tree.children) {
|
|
19
106
|
for (const child of tree.children) {
|
|
20
107
|
const found = findNodeById(child, nodeId);
|
|
21
|
-
if (found)
|
|
108
|
+
if (found)
|
|
109
|
+
return found;
|
|
22
110
|
}
|
|
23
111
|
}
|
|
24
112
|
return void 0;
|
|
@@ -33,13 +121,15 @@ function updateNodeById(tree, nodeId, updater) {
|
|
|
33
121
|
if (node.children) {
|
|
34
122
|
for (const child of node.children) {
|
|
35
123
|
const path2 = findPath(child, id, newPath);
|
|
36
|
-
if (path2)
|
|
124
|
+
if (path2)
|
|
125
|
+
return path2;
|
|
37
126
|
}
|
|
38
127
|
}
|
|
39
128
|
return null;
|
|
40
129
|
}
|
|
41
130
|
const path = findPath(result, nodeId);
|
|
42
|
-
if (!path)
|
|
131
|
+
if (!path)
|
|
132
|
+
return result;
|
|
43
133
|
const nodeToUpdate = path[path.length - 1];
|
|
44
134
|
const updatedNode = updater(nodeToUpdate);
|
|
45
135
|
if (path.length === 1) {
|
|
@@ -48,18 +138,14 @@ function updateNodeById(tree, nodeId, updater) {
|
|
|
48
138
|
const parent = path[path.length - 2];
|
|
49
139
|
const updatedParent = {
|
|
50
140
|
...parent,
|
|
51
|
-
children: parent.children
|
|
141
|
+
children: parent.children ? parent.children.map(
|
|
52
142
|
(child) => child.id === nodeId ? updatedNode : child
|
|
53
|
-
)
|
|
143
|
+
) : null
|
|
54
144
|
};
|
|
55
145
|
if (path.length === 2) {
|
|
56
146
|
return updatedParent;
|
|
57
147
|
}
|
|
58
|
-
return updateNodeById(
|
|
59
|
-
result,
|
|
60
|
-
parent.id,
|
|
61
|
-
() => updatedParent
|
|
62
|
-
);
|
|
148
|
+
return updateNodeById(result, parent.id, () => updatedParent);
|
|
63
149
|
}
|
|
64
150
|
function replaceNodeById(tree, nodeId, newNode) {
|
|
65
151
|
return updateNodeById(tree, nodeId, () => newNode);
|
|
@@ -86,7 +172,8 @@ function removeNodeById(tree, nodeId) {
|
|
|
86
172
|
}
|
|
87
173
|
for (const child of node.children) {
|
|
88
174
|
const parent2 = findParent(child, id);
|
|
89
|
-
if (parent2)
|
|
175
|
+
if (parent2)
|
|
176
|
+
return parent2;
|
|
90
177
|
}
|
|
91
178
|
}
|
|
92
179
|
return null;
|
|
@@ -96,15 +183,20 @@ function removeNodeById(tree, nodeId) {
|
|
|
96
183
|
throw new Error("Cannot remove root node");
|
|
97
184
|
}
|
|
98
185
|
const parent = findParent(result, nodeId);
|
|
99
|
-
if (!parent)
|
|
186
|
+
if (!parent)
|
|
187
|
+
return result;
|
|
100
188
|
return updateNodeById(result, parent.id, (node) => ({
|
|
101
189
|
...node,
|
|
102
|
-
children: node.children
|
|
190
|
+
children: node.children ? node.children.filter((child) => child.id !== nodeId) : null
|
|
103
191
|
}));
|
|
104
192
|
}
|
|
105
193
|
function uiReducer(state, action) {
|
|
106
194
|
switch (action.type) {
|
|
107
195
|
case "UI_EVENT": {
|
|
196
|
+
if (action.event.type === "INIT" && state.history.some((e) => e.type === "INIT")) {
|
|
197
|
+
console.log("[AutoUI uiReducer] Ignoring duplicate INIT event");
|
|
198
|
+
return state;
|
|
199
|
+
}
|
|
108
200
|
return {
|
|
109
201
|
...state,
|
|
110
202
|
loading: true,
|
|
@@ -116,7 +208,7 @@ function uiReducer(state, action) {
|
|
|
116
208
|
...state,
|
|
117
209
|
layout: action.node,
|
|
118
210
|
loading: false,
|
|
119
|
-
error:
|
|
211
|
+
error: null
|
|
120
212
|
};
|
|
121
213
|
}
|
|
122
214
|
case "PARTIAL_UPDATE": {
|
|
@@ -125,7 +217,7 @@ function uiReducer(state, action) {
|
|
|
125
217
|
...state,
|
|
126
218
|
layout: action.node,
|
|
127
219
|
loading: false,
|
|
128
|
-
error:
|
|
220
|
+
error: null
|
|
129
221
|
};
|
|
130
222
|
}
|
|
131
223
|
if (action.nodeId === "root" || action.nodeId === state.layout.id) {
|
|
@@ -133,19 +225,23 @@ function uiReducer(state, action) {
|
|
|
133
225
|
...state,
|
|
134
226
|
layout: action.node,
|
|
135
227
|
loading: false,
|
|
136
|
-
error:
|
|
228
|
+
error: null
|
|
137
229
|
};
|
|
138
230
|
}
|
|
139
231
|
return {
|
|
140
232
|
...state,
|
|
141
233
|
layout: replaceNodeById(state.layout, action.nodeId, action.node),
|
|
142
234
|
loading: false,
|
|
143
|
-
error:
|
|
235
|
+
error: null
|
|
144
236
|
};
|
|
145
237
|
}
|
|
146
238
|
case "ADD_NODE": {
|
|
147
239
|
if (!state.layout) {
|
|
148
|
-
return
|
|
240
|
+
return {
|
|
241
|
+
...state,
|
|
242
|
+
error: "Cannot add node: Layout is empty.",
|
|
243
|
+
loading: false
|
|
244
|
+
};
|
|
149
245
|
}
|
|
150
246
|
return {
|
|
151
247
|
...state,
|
|
@@ -153,21 +249,41 @@ function uiReducer(state, action) {
|
|
|
153
249
|
state.layout,
|
|
154
250
|
action.parentId,
|
|
155
251
|
action.node,
|
|
156
|
-
action.index
|
|
252
|
+
action.index === null ? void 0 : action.index
|
|
157
253
|
),
|
|
158
254
|
loading: false,
|
|
159
|
-
error:
|
|
255
|
+
error: null
|
|
160
256
|
};
|
|
161
257
|
}
|
|
162
258
|
case "REMOVE_NODE": {
|
|
163
259
|
if (!state.layout) {
|
|
164
|
-
return
|
|
260
|
+
return {
|
|
261
|
+
...state,
|
|
262
|
+
error: "Cannot remove node: Layout is empty.",
|
|
263
|
+
loading: false
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
try {
|
|
267
|
+
return {
|
|
268
|
+
...state,
|
|
269
|
+
layout: removeNodeById(state.layout, action.nodeId),
|
|
270
|
+
loading: false,
|
|
271
|
+
error: null
|
|
272
|
+
};
|
|
273
|
+
} catch (e) {
|
|
274
|
+
const errorMessage = e instanceof Error ? e.message : "Failed to remove node.";
|
|
275
|
+
return {
|
|
276
|
+
...state,
|
|
277
|
+
error: errorMessage,
|
|
278
|
+
loading: false
|
|
279
|
+
};
|
|
165
280
|
}
|
|
281
|
+
}
|
|
282
|
+
case "ERROR": {
|
|
166
283
|
return {
|
|
167
284
|
...state,
|
|
168
|
-
|
|
169
|
-
loading: false
|
|
170
|
-
error: void 0
|
|
285
|
+
error: action.message,
|
|
286
|
+
loading: false
|
|
171
287
|
};
|
|
172
288
|
}
|
|
173
289
|
case "LOADING": {
|
|
@@ -176,11 +292,10 @@ function uiReducer(state, action) {
|
|
|
176
292
|
loading: action.isLoading
|
|
177
293
|
};
|
|
178
294
|
}
|
|
179
|
-
case "
|
|
295
|
+
case "SET_DATA_CONTEXT": {
|
|
180
296
|
return {
|
|
181
297
|
...state,
|
|
182
|
-
|
|
183
|
-
loading: false
|
|
298
|
+
dataContext: action.payload
|
|
184
299
|
};
|
|
185
300
|
}
|
|
186
301
|
default:
|
|
@@ -188,246 +303,33 @@ function uiReducer(state, action) {
|
|
|
188
303
|
}
|
|
189
304
|
}
|
|
190
305
|
var initialState = {
|
|
306
|
+
layout: null,
|
|
191
307
|
loading: true,
|
|
192
|
-
|
|
308
|
+
error: null,
|
|
309
|
+
history: [],
|
|
310
|
+
dataContext: {}
|
|
193
311
|
};
|
|
194
312
|
|
|
195
|
-
// src/
|
|
313
|
+
// src/schema/action-types.ts
|
|
196
314
|
var ActionType = /* @__PURE__ */ ((ActionType2) => {
|
|
197
315
|
ActionType2["FULL_REFRESH"] = "FULL_REFRESH";
|
|
198
316
|
ActionType2["UPDATE_NODE"] = "UPDATE_NODE";
|
|
317
|
+
ActionType2["UPDATE_DATA"] = "UPDATE_DATA";
|
|
318
|
+
ActionType2["ADD_ITEM"] = "ADD_ITEM";
|
|
319
|
+
ActionType2["DELETE_ITEM"] = "DELETE_ITEM";
|
|
199
320
|
ActionType2["ADD_DROPDOWN"] = "ADD_DROPDOWN";
|
|
200
321
|
ActionType2["SHOW_DETAIL"] = "SHOW_DETAIL";
|
|
201
322
|
ActionType2["HIDE_DETAIL"] = "HIDE_DETAIL";
|
|
323
|
+
ActionType2["HIDE_DIALOG"] = "HIDE_DIALOG";
|
|
324
|
+
ActionType2["SAVE_TASK_CHANGES"] = "SAVE_TASK_CHANGES";
|
|
202
325
|
ActionType2["TOGGLE_STATE"] = "TOGGLE_STATE";
|
|
203
326
|
ActionType2["UPDATE_FORM"] = "UPDATE_FORM";
|
|
204
327
|
ActionType2["NAVIGATE"] = "NAVIGATE";
|
|
328
|
+
ActionType2["OPEN_DIALOG"] = "OPEN_DIALOG";
|
|
329
|
+
ActionType2["CLOSE_DIALOG"] = "CLOSE_DIALOG";
|
|
330
|
+
ActionType2["UPDATE_CONTEXT"] = "UPDATE_CONTEXT";
|
|
205
331
|
return ActionType2;
|
|
206
332
|
})(ActionType || {});
|
|
207
|
-
var ActionRouter = class {
|
|
208
|
-
constructor() {
|
|
209
|
-
this.routes = {};
|
|
210
|
-
}
|
|
211
|
-
/**
|
|
212
|
-
* Register a new action route
|
|
213
|
-
* @param eventType - UI event type to route
|
|
214
|
-
* @param config - Route configuration
|
|
215
|
-
*/
|
|
216
|
-
registerRoute(eventType, config) {
|
|
217
|
-
if (!this.routes[eventType]) {
|
|
218
|
-
this.routes[eventType] = [];
|
|
219
|
-
}
|
|
220
|
-
this.routes[eventType].push(config);
|
|
221
|
-
}
|
|
222
|
-
/**
|
|
223
|
-
* Find the appropriate route for an event
|
|
224
|
-
* @param event - UI event
|
|
225
|
-
* @param layout - Current UI layout
|
|
226
|
-
* @param dataContext - Current data context
|
|
227
|
-
* @returns Route resolution or null if no match
|
|
228
|
-
*/
|
|
229
|
-
resolveRoute(event, schema, layout, dataContext, goal, userContext) {
|
|
230
|
-
const routes = this.routes[event.type] || [];
|
|
231
|
-
if (routes.length === 0) {
|
|
232
|
-
return {
|
|
233
|
-
actionType: "FULL_REFRESH" /* FULL_REFRESH */,
|
|
234
|
-
targetNodeId: layout?.id || "root",
|
|
235
|
-
plannerInput: {
|
|
236
|
-
schema,
|
|
237
|
-
goal,
|
|
238
|
-
history: [event],
|
|
239
|
-
userContext
|
|
240
|
-
},
|
|
241
|
-
prompt: `Generate a new UI for the goal: "${goal}". The user just triggered: ${event.type} on node ${event.nodeId}`
|
|
242
|
-
};
|
|
243
|
-
}
|
|
244
|
-
const sourceNode = layout ? findNodeById(layout, event.nodeId) : void 0;
|
|
245
|
-
const nodeConfig = sourceNode?.events?.[event.type];
|
|
246
|
-
let matchingRoute;
|
|
247
|
-
if (nodeConfig) {
|
|
248
|
-
matchingRoute = routes.find(
|
|
249
|
-
(route) => route.actionType.toString() === nodeConfig.action
|
|
250
|
-
);
|
|
251
|
-
}
|
|
252
|
-
if (!matchingRoute) {
|
|
253
|
-
matchingRoute = routes[0];
|
|
254
|
-
}
|
|
255
|
-
const targetNodeId = nodeConfig?.target || matchingRoute.targetNodeId || event.nodeId;
|
|
256
|
-
const additionalContext = {};
|
|
257
|
-
if (matchingRoute.contextKeys) {
|
|
258
|
-
matchingRoute.contextKeys.forEach((key) => {
|
|
259
|
-
additionalContext[key] = dataContext[key];
|
|
260
|
-
});
|
|
261
|
-
}
|
|
262
|
-
if (sourceNode) {
|
|
263
|
-
additionalContext.sourceNode = sourceNode;
|
|
264
|
-
}
|
|
265
|
-
if (layout) {
|
|
266
|
-
const targetNode = findNodeById(layout, targetNodeId);
|
|
267
|
-
if (targetNode) {
|
|
268
|
-
additionalContext.targetNode = targetNode;
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
if (event.payload) {
|
|
272
|
-
additionalContext.eventPayload = event.payload;
|
|
273
|
-
}
|
|
274
|
-
if (nodeConfig?.payload) {
|
|
275
|
-
Object.entries(nodeConfig.payload).forEach(([key, value]) => {
|
|
276
|
-
additionalContext[key] = value;
|
|
277
|
-
});
|
|
278
|
-
}
|
|
279
|
-
const plannerInput2 = {
|
|
280
|
-
schema,
|
|
281
|
-
goal,
|
|
282
|
-
history: [event],
|
|
283
|
-
userContext: {
|
|
284
|
-
...userContext,
|
|
285
|
-
...additionalContext
|
|
286
|
-
}
|
|
287
|
-
};
|
|
288
|
-
const prompt = this.processTemplate(
|
|
289
|
-
matchingRoute.promptTemplate,
|
|
290
|
-
{
|
|
291
|
-
goal,
|
|
292
|
-
eventType: event.type,
|
|
293
|
-
nodeId: event.nodeId,
|
|
294
|
-
targetNodeId,
|
|
295
|
-
actionType: matchingRoute.actionType,
|
|
296
|
-
...additionalContext
|
|
297
|
-
}
|
|
298
|
-
);
|
|
299
|
-
return {
|
|
300
|
-
actionType: matchingRoute.actionType,
|
|
301
|
-
targetNodeId,
|
|
302
|
-
plannerInput: plannerInput2,
|
|
303
|
-
prompt
|
|
304
|
-
};
|
|
305
|
-
}
|
|
306
|
-
/**
|
|
307
|
-
* Process a prompt template with variables
|
|
308
|
-
* @param template - Template string with ${var} placeholders
|
|
309
|
-
* @param values - Values to substitute
|
|
310
|
-
* @returns Processed string
|
|
311
|
-
*/
|
|
312
|
-
processTemplate(template, values) {
|
|
313
|
-
return template.replace(/\${(\w+)}/g, (_, key) => {
|
|
314
|
-
return values[key] !== void 0 ? String(values[key]) : `\${${key}}`;
|
|
315
|
-
});
|
|
316
|
-
}
|
|
317
|
-
};
|
|
318
|
-
function createDefaultRouter() {
|
|
319
|
-
const router = new ActionRouter();
|
|
320
|
-
router.registerRoute("CLICK", {
|
|
321
|
-
actionType: "FULL_REFRESH" /* FULL_REFRESH */,
|
|
322
|
-
targetNodeId: "root",
|
|
323
|
-
promptTemplate: 'Generate a new UI for the goal: "${goal}". The user just clicked on node ${nodeId}'
|
|
324
|
-
});
|
|
325
|
-
router.registerRoute("CLICK", {
|
|
326
|
-
actionType: "SHOW_DETAIL" /* SHOW_DETAIL */,
|
|
327
|
-
targetNodeId: "${targetNodeId}",
|
|
328
|
-
promptTemplate: "Update the UI to show details for the selected item. Current node: ${nodeId}, Target node: ${targetNodeId}",
|
|
329
|
-
contextKeys: ["selected"]
|
|
330
|
-
});
|
|
331
|
-
router.registerRoute("CLICK", {
|
|
332
|
-
actionType: "NAVIGATE" /* NAVIGATE */,
|
|
333
|
-
targetNodeId: "root",
|
|
334
|
-
promptTemplate: "Navigate to a new view based on the user clicking ${nodeId}. Current goal: ${goal}"
|
|
335
|
-
});
|
|
336
|
-
router.registerRoute("CLICK", {
|
|
337
|
-
actionType: "ADD_DROPDOWN" /* ADD_DROPDOWN */,
|
|
338
|
-
targetNodeId: "${targetNodeId}",
|
|
339
|
-
promptTemplate: "Add a dropdown menu to node ${targetNodeId} with options relevant to the clicked element ${nodeId}"
|
|
340
|
-
});
|
|
341
|
-
router.registerRoute("CLICK", {
|
|
342
|
-
actionType: "TOGGLE_STATE" /* TOGGLE_STATE */,
|
|
343
|
-
targetNodeId: "${nodeId}",
|
|
344
|
-
promptTemplate: "Toggle the state of node ${nodeId} (e.g., expanded/collapsed, selected/unselected)"
|
|
345
|
-
});
|
|
346
|
-
router.registerRoute("CHANGE", {
|
|
347
|
-
actionType: "UPDATE_FORM" /* UPDATE_FORM */,
|
|
348
|
-
targetNodeId: "${targetNodeId}",
|
|
349
|
-
promptTemplate: "Update the form based on the user changing the value of ${nodeId}"
|
|
350
|
-
});
|
|
351
|
-
return router;
|
|
352
|
-
}
|
|
353
|
-
var uiEventType = z.enum([
|
|
354
|
-
"CLICK",
|
|
355
|
-
"CHANGE",
|
|
356
|
-
"SUBMIT",
|
|
357
|
-
"MOUSEOVER",
|
|
358
|
-
"MOUSEOUT",
|
|
359
|
-
"FOCUS",
|
|
360
|
-
"BLUR"
|
|
361
|
-
]);
|
|
362
|
-
var uiEvent = z.object({
|
|
363
|
-
type: uiEventType,
|
|
364
|
-
nodeId: z.string(),
|
|
365
|
-
timestamp: z.number().optional(),
|
|
366
|
-
payload: z.record(z.any()).optional()
|
|
367
|
-
});
|
|
368
|
-
z.enum([
|
|
369
|
-
"AI_RESPONSE",
|
|
370
|
-
"ERROR"
|
|
371
|
-
]);
|
|
372
|
-
var uiSpecNode = z.lazy(() => z.object({
|
|
373
|
-
id: z.string(),
|
|
374
|
-
type: z.string(),
|
|
375
|
-
// e.g., "ListView", "Button", "TextField"
|
|
376
|
-
props: z.record(z.any()).optional(),
|
|
377
|
-
bindings: z.record(z.any()).optional(),
|
|
378
|
-
// Data bindings
|
|
379
|
-
events: z.record(z.string(), z.object({
|
|
380
|
-
action: z.string(),
|
|
381
|
-
target: z.string().optional(),
|
|
382
|
-
payload: z.record(z.any()).optional()
|
|
383
|
-
})).optional(),
|
|
384
|
-
children: z.array(uiSpecNode).optional()
|
|
385
|
-
}));
|
|
386
|
-
z.discriminatedUnion("type", [
|
|
387
|
-
z.object({
|
|
388
|
-
type: z.literal("UI_EVENT"),
|
|
389
|
-
event: uiEvent
|
|
390
|
-
}),
|
|
391
|
-
z.object({
|
|
392
|
-
type: z.literal("AI_RESPONSE"),
|
|
393
|
-
node: uiSpecNode
|
|
394
|
-
}),
|
|
395
|
-
z.object({
|
|
396
|
-
type: z.literal("PARTIAL_UPDATE"),
|
|
397
|
-
nodeId: z.string(),
|
|
398
|
-
node: uiSpecNode
|
|
399
|
-
}),
|
|
400
|
-
z.object({
|
|
401
|
-
type: z.literal("ADD_NODE"),
|
|
402
|
-
parentId: z.string(),
|
|
403
|
-
node: uiSpecNode,
|
|
404
|
-
index: z.number().optional()
|
|
405
|
-
}),
|
|
406
|
-
z.object({
|
|
407
|
-
type: z.literal("REMOVE_NODE"),
|
|
408
|
-
nodeId: z.string()
|
|
409
|
-
}),
|
|
410
|
-
z.object({
|
|
411
|
-
type: z.literal("ERROR"),
|
|
412
|
-
message: z.string()
|
|
413
|
-
}),
|
|
414
|
-
z.object({
|
|
415
|
-
type: z.literal("LOADING"),
|
|
416
|
-
isLoading: z.boolean()
|
|
417
|
-
})
|
|
418
|
-
]);
|
|
419
|
-
z.object({
|
|
420
|
-
layout: uiSpecNode.optional(),
|
|
421
|
-
loading: z.boolean(),
|
|
422
|
-
history: z.array(uiEvent),
|
|
423
|
-
error: z.string().optional()
|
|
424
|
-
});
|
|
425
|
-
z.object({
|
|
426
|
-
schema: z.record(z.unknown()),
|
|
427
|
-
goal: z.string(),
|
|
428
|
-
history: z.array(uiEvent).optional(),
|
|
429
|
-
userContext: z.record(z.unknown()).optional()
|
|
430
|
-
});
|
|
431
333
|
|
|
432
334
|
// src/core/system-events.ts
|
|
433
335
|
var SystemEventType = /* @__PURE__ */ ((SystemEventType2) => {
|
|
@@ -452,7 +354,7 @@ var SystemEventManager = class {
|
|
|
452
354
|
}
|
|
453
355
|
/**
|
|
454
356
|
* Register a listener for a specific system event type
|
|
455
|
-
*
|
|
357
|
+
*
|
|
456
358
|
* @param eventType - The system event type to listen for
|
|
457
359
|
* @param listener - The listener function
|
|
458
360
|
* @returns Function to unregister the listener
|
|
@@ -472,7 +374,7 @@ var SystemEventManager = class {
|
|
|
472
374
|
}
|
|
473
375
|
/**
|
|
474
376
|
* Emit a system event to all registered listeners
|
|
475
|
-
*
|
|
377
|
+
*
|
|
476
378
|
* @param event - The system event to emit
|
|
477
379
|
*/
|
|
478
380
|
async emit(event) {
|
|
@@ -490,259 +392,1711 @@ function createSystemEvent(type, data) {
|
|
|
490
392
|
...data
|
|
491
393
|
};
|
|
492
394
|
}
|
|
395
|
+
var componentType = z.enum([
|
|
396
|
+
// Layout components
|
|
397
|
+
"Container",
|
|
398
|
+
"Card",
|
|
399
|
+
"Header",
|
|
400
|
+
// Input components
|
|
401
|
+
"Button",
|
|
402
|
+
"Input",
|
|
403
|
+
"Select",
|
|
404
|
+
"Textarea",
|
|
405
|
+
"Checkbox",
|
|
406
|
+
"RadioGroup",
|
|
407
|
+
// Data display components
|
|
408
|
+
"ListView",
|
|
409
|
+
"Detail",
|
|
410
|
+
"Tabs",
|
|
411
|
+
"Dialog",
|
|
412
|
+
"Badge",
|
|
413
|
+
// Typography
|
|
414
|
+
"Heading",
|
|
415
|
+
"Text"
|
|
416
|
+
]);
|
|
493
417
|
|
|
494
|
-
// src/
|
|
495
|
-
(
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
418
|
+
// src/schema/openai-ui-spec.ts
|
|
419
|
+
var actionTypeEnum = z.enum([
|
|
420
|
+
"FULL_REFRESH",
|
|
421
|
+
"UPDATE_NODE",
|
|
422
|
+
"UPDATE_DATA",
|
|
423
|
+
"ADD_DROPDOWN",
|
|
424
|
+
"SHOW_DETAIL",
|
|
425
|
+
"HIDE_DETAIL",
|
|
426
|
+
"HIDE_DIALOG",
|
|
427
|
+
"SAVE_TASK_CHANGES",
|
|
428
|
+
"TOGGLE_STATE",
|
|
429
|
+
"UPDATE_FORM",
|
|
430
|
+
"NAVIGATE",
|
|
431
|
+
"OPEN_DIALOG",
|
|
432
|
+
"CLOSE_DIALOG",
|
|
433
|
+
"UPDATE_CONTEXT"
|
|
434
|
+
]);
|
|
435
|
+
var openAISimplifiedValue = z.string().nullable();
|
|
436
|
+
var openAIRecordSimplifiedNullable = z.record(openAISimplifiedValue).nullable();
|
|
437
|
+
var openAIEventPayloadSimplifiedNullable = z.record(openAISimplifiedValue).nullable();
|
|
438
|
+
var openAIBaseNode = z.object({
|
|
439
|
+
id: z.string().describe("Unique identifier for the UI node."),
|
|
440
|
+
node_type: componentType.describe(
|
|
441
|
+
"The type of UI component (e.g., Container, Text, Button, ListView)."
|
|
442
|
+
),
|
|
443
|
+
props: openAIRecordSimplifiedNullable.describe(
|
|
444
|
+
'Component-specific properties (attributes). Values should be strings or null. E.g., for Header use { "title": "My Title" }; for Text use { "text": "My Text" }; for Button use { "label": "My Button Label" }.'
|
|
445
|
+
),
|
|
446
|
+
bindings: openAIRecordSimplifiedNullable.describe(
|
|
447
|
+
'Data bindings map context paths to component props. Values are paths (e.g., "user.name") or templates (e.g., "{{item.title}}")...'
|
|
448
|
+
),
|
|
449
|
+
events: z.record(
|
|
450
|
+
z.string(),
|
|
451
|
+
z.object({
|
|
452
|
+
action: actionTypeEnum.describe(
|
|
453
|
+
'Action identifier (e.g., "UPDATE_DATA", "ADD_ITEM", "DELETE_ITEM", "VIEW_DETAIL", "HIDE_DETAIL"). Defines what operation to perform when the event occurs.'
|
|
454
|
+
),
|
|
455
|
+
target: z.string().describe("Target identifier."),
|
|
456
|
+
payload: openAIEventPayloadSimplifiedNullable.describe(
|
|
457
|
+
"Static payload to merge with the event's runtime payload."
|
|
458
|
+
)
|
|
459
|
+
})
|
|
460
|
+
).nullable().describe(
|
|
461
|
+
'Defines event handlers mapped from UIEventType (e.g., "CLICK", "CHANGE") to an action configuration.'
|
|
462
|
+
),
|
|
463
|
+
children: z.null()
|
|
464
|
+
});
|
|
465
|
+
var openAINodeL4 = openAIBaseNode;
|
|
466
|
+
var openAINodeL3 = openAIBaseNode.extend({
|
|
467
|
+
children: z.array(openAINodeL4).nullable()
|
|
468
|
+
});
|
|
469
|
+
var openAINodeL2 = openAIBaseNode.extend({
|
|
470
|
+
children: z.array(openAINodeL3).nullable()
|
|
471
|
+
});
|
|
472
|
+
var openAIUISpec = openAIBaseNode.extend({
|
|
473
|
+
children: z.array(openAINodeL2).nullable()
|
|
499
474
|
});
|
|
500
475
|
|
|
501
476
|
// src/core/planner.ts
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
Schema: ${JSON.stringify(tableSchema)}`;
|
|
507
|
-
}).join("\n\n");
|
|
508
|
-
const recentEvents = history?.slice(-5).map(
|
|
509
|
-
(event) => `Event: ${event.type} on node ${event.nodeId}${event.payload ? ` with payload ${JSON.stringify(event.payload)}` : ""}`
|
|
510
|
-
).join("\n") || "No recent events";
|
|
511
|
-
const userContextSection = userContext ? `
|
|
512
|
-
|
|
513
|
-
User Context:
|
|
514
|
-
${JSON.stringify(userContext)}` : "";
|
|
515
|
-
return `
|
|
516
|
-
You are an expert UI generator.
|
|
517
|
-
Create a user interface that achieves the following goal: "${goal}"
|
|
518
|
-
|
|
519
|
-
Available data schema:
|
|
520
|
-
${schemaInfo}
|
|
521
|
-
|
|
522
|
-
Recent user interactions:
|
|
523
|
-
${recentEvents}${userContextSection}
|
|
524
|
-
|
|
525
|
-
Generate a complete UI specification in JSON format that matches the following TypeScript type:
|
|
526
|
-
type UISpecNode = {
|
|
527
|
-
id: string;
|
|
528
|
-
type: string;
|
|
529
|
-
props?: Record<string, any>;
|
|
530
|
-
bindings?: Record<string, any>;
|
|
531
|
-
events?: Record<string, { action: string; target?: string; payload?: Record<string, any>; }>;
|
|
532
|
-
children?: UISpecNode[];
|
|
477
|
+
var getAnthropicClient = (apiKey) => {
|
|
478
|
+
return createAnthropic({
|
|
479
|
+
apiKey
|
|
480
|
+
});
|
|
533
481
|
};
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
6. Provide event handlers for user interactions
|
|
542
|
-
|
|
543
|
-
Respond ONLY with the JSON UI specification and no other text.
|
|
544
|
-
`;
|
|
545
|
-
}
|
|
546
|
-
function mockPlanner(input, targetNodeId, customPrompt) {
|
|
547
|
-
const mockNode = {
|
|
548
|
-
id: targetNodeId || "root",
|
|
549
|
-
type: "Container",
|
|
550
|
-
props: { title: "Mock UI" },
|
|
482
|
+
function mockPlanner(_input) {
|
|
483
|
+
return {
|
|
484
|
+
id: "task-dashboard",
|
|
485
|
+
node_type: "Container",
|
|
486
|
+
props: { className: "p-4 space-y-4" },
|
|
487
|
+
bindings: null,
|
|
488
|
+
events: null,
|
|
551
489
|
children: [
|
|
552
490
|
{
|
|
553
|
-
id: "
|
|
554
|
-
|
|
555
|
-
props: {
|
|
491
|
+
id: "header",
|
|
492
|
+
node_type: "Container",
|
|
493
|
+
props: { className: "flex justify-between items-center mb-4" },
|
|
494
|
+
bindings: null,
|
|
495
|
+
events: null,
|
|
496
|
+
children: [
|
|
497
|
+
{
|
|
498
|
+
id: "title",
|
|
499
|
+
node_type: "Text",
|
|
500
|
+
props: { text: "Task Dashboard", className: "text-2xl font-bold" },
|
|
501
|
+
bindings: null,
|
|
502
|
+
events: null,
|
|
503
|
+
children: null
|
|
504
|
+
},
|
|
505
|
+
{
|
|
506
|
+
id: "add-task-button",
|
|
507
|
+
node_type: "Button",
|
|
508
|
+
props: { label: "Add Task", variant: "default" },
|
|
509
|
+
bindings: null,
|
|
510
|
+
events: {
|
|
511
|
+
CLICK: { action: "SHOW_DETAIL", target: "new-task-form" }
|
|
512
|
+
},
|
|
513
|
+
children: null
|
|
514
|
+
}
|
|
515
|
+
]
|
|
516
|
+
},
|
|
517
|
+
{
|
|
518
|
+
id: "main-content",
|
|
519
|
+
node_type: "Container",
|
|
520
|
+
props: { className: "flex gap-4" },
|
|
521
|
+
bindings: null,
|
|
522
|
+
events: null,
|
|
523
|
+
children: [
|
|
524
|
+
{
|
|
525
|
+
id: "tasks-container",
|
|
526
|
+
node_type: "Container",
|
|
527
|
+
props: { className: "flex-1" },
|
|
528
|
+
bindings: null,
|
|
529
|
+
events: null,
|
|
530
|
+
children: [
|
|
531
|
+
{
|
|
532
|
+
id: "task-list",
|
|
533
|
+
node_type: "ListView",
|
|
534
|
+
props: { className: "space-y-2" },
|
|
535
|
+
bindings: { data: "tasks.data" },
|
|
536
|
+
events: null,
|
|
537
|
+
children: [
|
|
538
|
+
{
|
|
539
|
+
id: "task-item-{{index}}",
|
|
540
|
+
node_type: "Card",
|
|
541
|
+
props: { className: "p-3 border rounded" },
|
|
542
|
+
bindings: null,
|
|
543
|
+
events: null,
|
|
544
|
+
children: [
|
|
545
|
+
{
|
|
546
|
+
id: "task-title-{{index}}",
|
|
547
|
+
node_type: "Text",
|
|
548
|
+
props: { className: "font-medium" },
|
|
549
|
+
bindings: { text: "item.title" },
|
|
550
|
+
events: null,
|
|
551
|
+
children: null
|
|
552
|
+
},
|
|
553
|
+
{
|
|
554
|
+
id: "task-status-{{index}}",
|
|
555
|
+
node_type: "Badge",
|
|
556
|
+
props: {},
|
|
557
|
+
bindings: { text: "item.status" },
|
|
558
|
+
events: null,
|
|
559
|
+
children: null
|
|
560
|
+
},
|
|
561
|
+
{
|
|
562
|
+
id: "view-details-button-{{index}}",
|
|
563
|
+
node_type: "Button",
|
|
564
|
+
props: { label: "View Details", variant: "outline", size: "sm" },
|
|
565
|
+
bindings: null,
|
|
566
|
+
events: {
|
|
567
|
+
CLICK: { action: "SHOW_DETAIL", target: "task-detail" }
|
|
568
|
+
},
|
|
569
|
+
children: null
|
|
570
|
+
}
|
|
571
|
+
]
|
|
572
|
+
}
|
|
573
|
+
]
|
|
574
|
+
}
|
|
575
|
+
]
|
|
576
|
+
},
|
|
577
|
+
{
|
|
578
|
+
id: "task-detail",
|
|
579
|
+
node_type: "Container",
|
|
580
|
+
props: { className: "w-1/3 border-l pl-4", visible: false },
|
|
581
|
+
bindings: null,
|
|
582
|
+
events: null,
|
|
583
|
+
children: [
|
|
584
|
+
{
|
|
585
|
+
id: "detail-title",
|
|
586
|
+
node_type: "Text",
|
|
587
|
+
props: { text: "Task Details", className: "text-lg font-bold mb-2" },
|
|
588
|
+
bindings: null,
|
|
589
|
+
events: null,
|
|
590
|
+
children: null
|
|
591
|
+
},
|
|
592
|
+
{
|
|
593
|
+
id: "detail-content",
|
|
594
|
+
node_type: "Text",
|
|
595
|
+
props: {},
|
|
596
|
+
bindings: { text: "tasks.selected.description" },
|
|
597
|
+
events: null,
|
|
598
|
+
children: null
|
|
599
|
+
},
|
|
600
|
+
{
|
|
601
|
+
id: "close-detail-button",
|
|
602
|
+
node_type: "Button",
|
|
603
|
+
props: { label: "Close", variant: "ghost" },
|
|
604
|
+
bindings: null,
|
|
605
|
+
events: {
|
|
606
|
+
CLICK: { action: "HIDE_DETAIL", target: "task-detail" }
|
|
607
|
+
},
|
|
608
|
+
children: null
|
|
609
|
+
}
|
|
610
|
+
]
|
|
611
|
+
}
|
|
612
|
+
]
|
|
556
613
|
}
|
|
557
614
|
]
|
|
558
615
|
};
|
|
559
|
-
return mockNode;
|
|
560
616
|
}
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
617
|
+
async function callPlannerLLM(input, apiKey, useMock) {
|
|
618
|
+
await systemEvents.emit(
|
|
619
|
+
createSystemEvent("PLAN_START" /* PLAN_START */, { plannerInput: input })
|
|
620
|
+
);
|
|
621
|
+
if (useMock || typeof window !== "undefined" && window.__USE_MOCK_PLANNER) {
|
|
622
|
+
console.log("[Mock Planner] Using mock planner for testing");
|
|
623
|
+
const mockLayout = mockPlanner();
|
|
624
|
+
await systemEvents.emit(
|
|
625
|
+
createSystemEvent("PLAN_COMPLETE" /* PLAN_COMPLETE */, {
|
|
626
|
+
layout: mockLayout,
|
|
627
|
+
executionTimeMs: 0
|
|
628
|
+
})
|
|
629
|
+
);
|
|
630
|
+
return mockLayout;
|
|
570
631
|
}
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
);
|
|
595
|
-
if (route) {
|
|
596
|
-
console.log("Resolved route:", route);
|
|
597
|
-
systemEvents.emit(
|
|
598
|
-
createSystemEvent("PLAN_START" /* PLAN_START */, {
|
|
599
|
-
plannerInput: route.plannerInput
|
|
600
|
-
})
|
|
601
|
-
);
|
|
602
|
-
if (mockMode) {
|
|
603
|
-
const node = mockPlanner(route.plannerInput, route.targetNodeId, route.prompt);
|
|
604
|
-
switch (route.actionType) {
|
|
605
|
-
case "FULL_REFRESH" /* FULL_REFRESH */:
|
|
606
|
-
dispatch({ type: "AI_RESPONSE", node });
|
|
607
|
-
break;
|
|
608
|
-
case "UPDATE_NODE" /* UPDATE_NODE */:
|
|
609
|
-
case "SHOW_DETAIL" /* SHOW_DETAIL */:
|
|
610
|
-
case "HIDE_DETAIL" /* HIDE_DETAIL */:
|
|
611
|
-
case "TOGGLE_STATE" /* TOGGLE_STATE */:
|
|
612
|
-
case "ADD_DROPDOWN" /* ADD_DROPDOWN */:
|
|
613
|
-
case "UPDATE_FORM" /* UPDATE_FORM */:
|
|
614
|
-
case "NAVIGATE" /* NAVIGATE */:
|
|
615
|
-
dispatch({
|
|
616
|
-
type: "PARTIAL_UPDATE",
|
|
617
|
-
nodeId: route.targetNodeId,
|
|
618
|
-
node
|
|
619
|
-
});
|
|
620
|
-
break;
|
|
621
|
-
}
|
|
622
|
-
} else {
|
|
623
|
-
const prompt = route.prompt;
|
|
624
|
-
systemEvents.emit(
|
|
625
|
-
createSystemEvent("PLAN_PROMPT_CREATED" /* PLAN_PROMPT_CREATED */, { prompt })
|
|
626
|
-
);
|
|
627
|
-
append({
|
|
628
|
-
content: prompt,
|
|
629
|
-
role: "user"
|
|
630
|
-
});
|
|
631
|
-
sessionStorage.setItem("currentRoute", JSON.stringify({
|
|
632
|
-
actionType: route.actionType,
|
|
633
|
-
targetNodeId: route.targetNodeId
|
|
634
|
-
}));
|
|
632
|
+
if (!apiKey) {
|
|
633
|
+
console.warn(
|
|
634
|
+
`Anthropic API key was not provided to callPlannerLLM. Returning a placeholder UI.`
|
|
635
|
+
);
|
|
636
|
+
return {
|
|
637
|
+
id: "root-no-api-key",
|
|
638
|
+
node_type: "Container",
|
|
639
|
+
props: {
|
|
640
|
+
className: "p-4 flex flex-col items-center justify-center h-full"
|
|
641
|
+
},
|
|
642
|
+
bindings: null,
|
|
643
|
+
events: null,
|
|
644
|
+
children: [
|
|
645
|
+
{
|
|
646
|
+
id: "no-api-key-message",
|
|
647
|
+
node_type: "Text",
|
|
648
|
+
props: {
|
|
649
|
+
text: "Anthropic API Key is required to generate the UI. Please provide one in your environment configuration.",
|
|
650
|
+
className: "text-red-500 text-center"
|
|
651
|
+
},
|
|
652
|
+
bindings: null,
|
|
653
|
+
events: null,
|
|
654
|
+
children: null
|
|
635
655
|
}
|
|
636
|
-
|
|
656
|
+
]
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
const startTime = Date.now();
|
|
660
|
+
const templateValuesForPrompt = input.userContext ? { ...input.userContext } : void 0;
|
|
661
|
+
const promptTemplateFromInput = typeof input.userContext?.promptTemplate === "string" ? input.userContext.promptTemplate : void 0;
|
|
662
|
+
const prompt = buildPrompt(
|
|
663
|
+
input,
|
|
664
|
+
promptTemplateFromInput,
|
|
665
|
+
// Use template from input.userContext
|
|
666
|
+
templateValuesForPrompt
|
|
667
|
+
// Use values from input.userContext
|
|
668
|
+
);
|
|
669
|
+
await systemEvents.emit(
|
|
670
|
+
createSystemEvent("PLAN_PROMPT_CREATED" /* PLAN_PROMPT_CREATED */, { prompt })
|
|
671
|
+
);
|
|
672
|
+
try {
|
|
673
|
+
let uiSpec;
|
|
674
|
+
if (typeof window !== "undefined") {
|
|
675
|
+
const response = await fetch("/api/generate-ui", {
|
|
676
|
+
method: "POST",
|
|
677
|
+
headers: {
|
|
678
|
+
"Content-Type": "application/json"
|
|
679
|
+
},
|
|
680
|
+
body: JSON.stringify({ prompt, apiKey })
|
|
681
|
+
});
|
|
682
|
+
if (!response.ok) {
|
|
683
|
+
const errorData = await response.json();
|
|
684
|
+
throw new Error(errorData.error || `HTTP error! status: ${response.status}`);
|
|
637
685
|
}
|
|
686
|
+
const data = await response.json();
|
|
687
|
+
uiSpec = data.uiSpec;
|
|
688
|
+
} else {
|
|
689
|
+
const { object } = await generateObject({
|
|
690
|
+
model: getAnthropicClient(apiKey)("claude-haiku-4-5"),
|
|
691
|
+
schema: openAIUISpec,
|
|
692
|
+
mode: "tool",
|
|
693
|
+
messages: [{ role: "user", content: prompt }],
|
|
694
|
+
temperature: 0.2,
|
|
695
|
+
maxTokens: 4e3
|
|
696
|
+
});
|
|
697
|
+
uiSpec = object;
|
|
638
698
|
}
|
|
639
|
-
|
|
699
|
+
await systemEvents.emit(
|
|
700
|
+
createSystemEvent("PLAN_COMPLETE" /* PLAN_COMPLETE */, {
|
|
701
|
+
layout: uiSpec,
|
|
702
|
+
executionTimeMs: Date.now() - startTime
|
|
703
|
+
})
|
|
704
|
+
);
|
|
705
|
+
return uiSpec;
|
|
706
|
+
} catch (error) {
|
|
707
|
+
console.error("Error calling LLM planner:", error);
|
|
708
|
+
await systemEvents.emit(
|
|
709
|
+
createSystemEvent("PLAN_ERROR" /* PLAN_ERROR */, {
|
|
710
|
+
error: error instanceof Error ? error : new Error(String(error))
|
|
711
|
+
})
|
|
712
|
+
);
|
|
713
|
+
throw error;
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
// src/core/bindings.ts
|
|
718
|
+
function getValueByPath(context, path) {
|
|
719
|
+
const parts = path.split(".");
|
|
720
|
+
let current = context;
|
|
721
|
+
for (const part of parts) {
|
|
722
|
+
if (current === null || current === void 0) {
|
|
723
|
+
return void 0;
|
|
724
|
+
}
|
|
725
|
+
if (typeof current !== "object") {
|
|
726
|
+
return void 0;
|
|
727
|
+
}
|
|
728
|
+
current = current[part];
|
|
729
|
+
}
|
|
730
|
+
return current;
|
|
731
|
+
}
|
|
732
|
+
function setValueByPath(context, path, value) {
|
|
733
|
+
if (!path) {
|
|
734
|
+
return context;
|
|
735
|
+
}
|
|
736
|
+
const result = { ...context };
|
|
737
|
+
const parts = path.split(".");
|
|
738
|
+
let current = result;
|
|
739
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
740
|
+
const part = parts[i];
|
|
741
|
+
if (typeof current !== "object" || current === null) {
|
|
742
|
+
console.warn(
|
|
743
|
+
`setValueByPath: Cannot traverse path "${path}". Parent segment "${parts[i - 1] || "(root)"}" is not an object.`
|
|
744
|
+
);
|
|
745
|
+
return context;
|
|
746
|
+
}
|
|
747
|
+
const currentAsObject = current;
|
|
748
|
+
const nextPartValue = currentAsObject[part];
|
|
749
|
+
if (nextPartValue === void 0 || nextPartValue === null) {
|
|
750
|
+
currentAsObject[part] = {};
|
|
751
|
+
} else if (typeof nextPartValue !== "object") {
|
|
752
|
+
console.warn(
|
|
753
|
+
`setValueByPath: Cannot create nested path "${path}". Segment "${part}" is not an object.`
|
|
754
|
+
);
|
|
755
|
+
return context;
|
|
756
|
+
} else {
|
|
757
|
+
currentAsObject[part] = { ...nextPartValue };
|
|
758
|
+
}
|
|
759
|
+
current = currentAsObject[part];
|
|
760
|
+
}
|
|
761
|
+
const lastPart = parts[parts.length - 1];
|
|
762
|
+
if (typeof current === "object" && current !== null) {
|
|
763
|
+
current[lastPart] = value;
|
|
764
|
+
} else {
|
|
765
|
+
console.warn(
|
|
766
|
+
`setValueByPath: Could not set value for path "${path}". Final segment parent is not an object.`
|
|
767
|
+
);
|
|
768
|
+
return context;
|
|
769
|
+
}
|
|
770
|
+
return result;
|
|
771
|
+
}
|
|
772
|
+
function processBinding(binding, context, itemData) {
|
|
773
|
+
if (typeof binding === "string") {
|
|
774
|
+
const exactMatchArr = binding.match(/^{{(.*)}}$/);
|
|
775
|
+
const pathInsideExact = exactMatchArr ? exactMatchArr[1].trim() : null;
|
|
776
|
+
if (pathInsideExact !== null && !pathInsideExact.includes("{{") && !pathInsideExact.includes("}}")) {
|
|
777
|
+
const pathToResolve = pathInsideExact;
|
|
778
|
+
let resolvedValue = void 0;
|
|
779
|
+
if ((pathToResolve.startsWith("item.") || pathToResolve.startsWith("row.")) && itemData) {
|
|
780
|
+
if (pathToResolve.startsWith("item.")) {
|
|
781
|
+
resolvedValue = getValueByPath(itemData, pathToResolve.substring(5));
|
|
782
|
+
} else {
|
|
783
|
+
resolvedValue = getValueByPath(itemData, pathToResolve.substring(4));
|
|
784
|
+
}
|
|
785
|
+
} else if (itemData && pathToResolve in itemData) {
|
|
786
|
+
resolvedValue = getValueByPath(itemData, pathToResolve);
|
|
787
|
+
}
|
|
788
|
+
if (resolvedValue === void 0) {
|
|
789
|
+
resolvedValue = getValueByPath(context, pathToResolve);
|
|
790
|
+
}
|
|
791
|
+
return resolvedValue;
|
|
792
|
+
} else if (binding.includes("{{") && binding.includes("}}")) {
|
|
793
|
+
const resolvedString = binding.replaceAll(
|
|
794
|
+
/{{(.*?)}}/g,
|
|
795
|
+
// Non-greedy match inside braces
|
|
796
|
+
(match, path) => {
|
|
797
|
+
const trimmedPath = path.trim();
|
|
798
|
+
let resolvedValue = void 0;
|
|
799
|
+
if ((trimmedPath.startsWith("item.") || trimmedPath.startsWith("row.")) && itemData) {
|
|
800
|
+
if (trimmedPath.startsWith("item.")) {
|
|
801
|
+
resolvedValue = getValueByPath(
|
|
802
|
+
itemData,
|
|
803
|
+
trimmedPath.substring(5)
|
|
804
|
+
);
|
|
805
|
+
} else {
|
|
806
|
+
resolvedValue = getValueByPath(
|
|
807
|
+
itemData,
|
|
808
|
+
trimmedPath.substring(4)
|
|
809
|
+
);
|
|
810
|
+
}
|
|
811
|
+
} else if (itemData && trimmedPath in itemData) {
|
|
812
|
+
resolvedValue = getValueByPath(itemData, trimmedPath);
|
|
813
|
+
}
|
|
814
|
+
if (resolvedValue === void 0) {
|
|
815
|
+
resolvedValue = getValueByPath(context, trimmedPath);
|
|
816
|
+
}
|
|
817
|
+
return resolvedValue === null || resolvedValue === void 0 ? "" : String(resolvedValue);
|
|
818
|
+
}
|
|
819
|
+
);
|
|
820
|
+
return resolvedString;
|
|
821
|
+
} else {
|
|
822
|
+
const pathToResolve = binding;
|
|
823
|
+
let resolvedValue = void 0;
|
|
824
|
+
if ((pathToResolve.startsWith("item.") || pathToResolve.startsWith("row.")) && itemData) {
|
|
825
|
+
if (pathToResolve.startsWith("item.")) {
|
|
826
|
+
resolvedValue = getValueByPath(itemData, pathToResolve.substring(5));
|
|
827
|
+
} else {
|
|
828
|
+
resolvedValue = getValueByPath(itemData, pathToResolve.substring(4));
|
|
829
|
+
}
|
|
830
|
+
if (resolvedValue !== void 0) {
|
|
831
|
+
return resolvedValue;
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
if (itemData && !pathToResolve.includes(".") && pathToResolve in itemData) {
|
|
835
|
+
resolvedValue = getValueByPath(itemData, pathToResolve);
|
|
836
|
+
}
|
|
837
|
+
if (resolvedValue === void 0) {
|
|
838
|
+
resolvedValue = getValueByPath(context, pathToResolve);
|
|
839
|
+
}
|
|
840
|
+
return resolvedValue;
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
if (Array.isArray(binding)) {
|
|
844
|
+
return binding.map((item) => processBinding(item, context, itemData));
|
|
845
|
+
}
|
|
846
|
+
if (binding !== null && typeof binding === "object") {
|
|
847
|
+
const result = {};
|
|
848
|
+
for (const [key, value] of Object.entries(binding)) {
|
|
849
|
+
result[key] = processBinding(value, context, itemData);
|
|
850
|
+
}
|
|
851
|
+
return result;
|
|
852
|
+
}
|
|
853
|
+
return binding;
|
|
854
|
+
}
|
|
855
|
+
async function resolveBindings(node, context, itemData) {
|
|
856
|
+
if (node.id === "task-detail") {
|
|
857
|
+
console.log(
|
|
858
|
+
`[resolveBindings task-detail SPECIFIC CHECK] Context for task-detail:`,
|
|
859
|
+
JSON.stringify(context)
|
|
860
|
+
);
|
|
861
|
+
console.log(
|
|
862
|
+
`[resolveBindings task-detail SPECIFIC CHECK] context.isTaskDetailDialogVisible = ${context.isTaskDetailDialogVisible}`
|
|
863
|
+
);
|
|
864
|
+
console.log(
|
|
865
|
+
`[resolveBindings task-detail SPECIFIC CHECK] context.selectedTask = ${JSON.stringify(
|
|
866
|
+
context.selectedTask
|
|
867
|
+
)}`
|
|
868
|
+
);
|
|
869
|
+
}
|
|
870
|
+
console.log(
|
|
871
|
+
`[resolveBindings ENTRY] Node ID: ${node.id}, Type: ${node.node_type}, Has itemData: ${!!itemData}, Context keys: ${Object.keys(
|
|
872
|
+
context || {}
|
|
873
|
+
// Ensure context is not null before Object.keys
|
|
874
|
+
).join(", ")}`
|
|
875
|
+
);
|
|
876
|
+
const result = {
|
|
877
|
+
...node,
|
|
878
|
+
props: node.props ? JSON.parse(JSON.stringify(node.props)) : null,
|
|
879
|
+
events: node.events ? JSON.parse(JSON.stringify(node.events)) : null,
|
|
880
|
+
bindings: node.bindings ? JSON.parse(JSON.stringify(node.bindings)) : null,
|
|
881
|
+
children: null
|
|
882
|
+
// Initialize children to null
|
|
883
|
+
};
|
|
884
|
+
let mergedProps = node.props ? { ...JSON.parse(JSON.stringify(node.props)) } : null;
|
|
885
|
+
const PROP_KEYS_TO_RESOLVE = /* @__PURE__ */ new Set([
|
|
886
|
+
"text",
|
|
887
|
+
"label",
|
|
888
|
+
"title",
|
|
889
|
+
"placeholder",
|
|
890
|
+
"value"
|
|
891
|
+
]);
|
|
892
|
+
if (node.props) {
|
|
893
|
+
for (const [key, value] of Object.entries(node.props)) {
|
|
894
|
+
if (!mergedProps)
|
|
895
|
+
mergedProps = {};
|
|
896
|
+
if (PROP_KEYS_TO_RESOLVE.has(key)) {
|
|
897
|
+
const resolvedPropValue = processBinding(
|
|
898
|
+
value,
|
|
899
|
+
context,
|
|
900
|
+
itemData ?? void 0
|
|
901
|
+
);
|
|
902
|
+
if (resolvedPropValue !== void 0) {
|
|
903
|
+
mergedProps[key] = resolvedPropValue;
|
|
904
|
+
} else {
|
|
905
|
+
if (typeof value === "string" && (value.includes("{{") || value.includes("."))) {
|
|
906
|
+
mergedProps[key] = "";
|
|
907
|
+
} else {
|
|
908
|
+
mergedProps[key] = value;
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
} else if (value !== void 0) {
|
|
912
|
+
mergedProps[key] = value;
|
|
913
|
+
} else {
|
|
914
|
+
delete mergedProps[key];
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
result.props = mergedProps;
|
|
919
|
+
if (node.bindings) {
|
|
920
|
+
for (const [key, bindingValue] of Object.entries(node.bindings)) {
|
|
921
|
+
const resolvedValue = processBinding(
|
|
922
|
+
bindingValue,
|
|
923
|
+
context,
|
|
924
|
+
itemData ?? void 0
|
|
925
|
+
);
|
|
926
|
+
if (node.id === "task-detail") {
|
|
927
|
+
console.log(
|
|
928
|
+
`[resolveBindings - ${node.id}] Binding for '${key}': '${String(
|
|
929
|
+
bindingValue
|
|
930
|
+
)}' -> Resolved:`,
|
|
931
|
+
resolvedValue
|
|
932
|
+
);
|
|
933
|
+
}
|
|
934
|
+
if (resolvedValue !== void 0) {
|
|
935
|
+
if (!result.props)
|
|
936
|
+
result.props = {};
|
|
937
|
+
result.props[key] = resolvedValue;
|
|
938
|
+
if (node.id === "task-detail") {
|
|
939
|
+
console.log(
|
|
940
|
+
`[resolveBindings - ${node.id}] Set result.props.${key} =`,
|
|
941
|
+
resolvedValue
|
|
942
|
+
);
|
|
943
|
+
}
|
|
944
|
+
} else {
|
|
945
|
+
if (node.id === "task-detail") {
|
|
946
|
+
console.log(
|
|
947
|
+
`[resolveBindings - ${node.id}] Binding for '${key}' ('${String(
|
|
948
|
+
bindingValue
|
|
949
|
+
)}') resolved to undefined. Not setting prop.`
|
|
950
|
+
);
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
if (node.events) {
|
|
956
|
+
const processedEvents = {};
|
|
957
|
+
for (const eventType in node.events) {
|
|
958
|
+
const eventConfig = node.events[eventType];
|
|
959
|
+
processedEvents[eventType] = {
|
|
960
|
+
...eventConfig,
|
|
961
|
+
payload: eventConfig.payload ? processBinding(
|
|
962
|
+
eventConfig.payload,
|
|
963
|
+
context,
|
|
964
|
+
itemData ?? void 0
|
|
965
|
+
) : null
|
|
966
|
+
};
|
|
967
|
+
}
|
|
968
|
+
result.events = processedEvents;
|
|
969
|
+
} else {
|
|
970
|
+
result.events = null;
|
|
971
|
+
}
|
|
972
|
+
const dataBindingValue = result.props?.data ?? result.props?.items;
|
|
973
|
+
if ((node.node_type === "ListView" || node.node_type === "Table") && Array.isArray(dataBindingValue) && node.children && node.children.length > 0) {
|
|
974
|
+
const templateChild = node.children[0];
|
|
975
|
+
const looksLikeTemplate = node.children.length === 1 && (templateChild.id.includes("{{") || templateChild.id.includes("-template"));
|
|
976
|
+
const isAlreadyExpanded = !looksLikeTemplate && node.children.length > 1;
|
|
977
|
+
if (isAlreadyExpanded) {
|
|
978
|
+
result.children = await Promise.all(
|
|
979
|
+
node.children.map(
|
|
980
|
+
(existingChild) => resolveBindings(existingChild, context, itemData)
|
|
981
|
+
)
|
|
982
|
+
// itemData is tricky here, should it be from parent or is it irrelevant?
|
|
983
|
+
// For now, assume itemData is not applicable for re-resolving top-level list items this way.
|
|
984
|
+
);
|
|
985
|
+
} else {
|
|
986
|
+
const mappedChildren = await Promise.all(
|
|
987
|
+
dataBindingValue.map(async (currentItemData, index) => {
|
|
988
|
+
try {
|
|
989
|
+
if (typeof currentItemData !== "object" || currentItemData === null) {
|
|
990
|
+
console.warn(
|
|
991
|
+
`List item at index ${index} for node ${node.id} is not an object:`,
|
|
992
|
+
currentItemData
|
|
993
|
+
);
|
|
994
|
+
return null;
|
|
995
|
+
}
|
|
996
|
+
const currentItemAsRecord = currentItemData;
|
|
997
|
+
const itemId = currentItemAsRecord.id;
|
|
998
|
+
const instanceId = `${templateChild.id}-${itemId || index}`;
|
|
999
|
+
const childNodeInstance = JSON.parse(
|
|
1000
|
+
JSON.stringify(templateChild)
|
|
1001
|
+
);
|
|
1002
|
+
childNodeInstance.id = instanceId;
|
|
1003
|
+
makeChildIdsUniqueInInstance(
|
|
1004
|
+
childNodeInstance,
|
|
1005
|
+
instanceId,
|
|
1006
|
+
templateChild.id
|
|
1007
|
+
);
|
|
1008
|
+
const resolvedChild = await resolveBindings(
|
|
1009
|
+
childNodeInstance,
|
|
1010
|
+
context,
|
|
1011
|
+
currentItemAsRecord
|
|
1012
|
+
);
|
|
1013
|
+
if (resolvedChild && !resolvedChild.props)
|
|
1014
|
+
resolvedChild.props = {};
|
|
1015
|
+
if (resolvedChild && resolvedChild.props) {
|
|
1016
|
+
resolvedChild.props.key = itemId || `${node.id}-item-${index}`;
|
|
1017
|
+
}
|
|
1018
|
+
return resolvedChild;
|
|
1019
|
+
} catch (error) {
|
|
1020
|
+
console.error(
|
|
1021
|
+
`[resolveBindings Error] Error processing item at index ${index} for node ${node.id}:`,
|
|
1022
|
+
error,
|
|
1023
|
+
"Item Data:",
|
|
1024
|
+
currentItemData
|
|
1025
|
+
);
|
|
1026
|
+
return null;
|
|
1027
|
+
}
|
|
1028
|
+
})
|
|
1029
|
+
);
|
|
1030
|
+
result.children = mappedChildren.filter(
|
|
1031
|
+
(child) => child !== null
|
|
1032
|
+
);
|
|
1033
|
+
}
|
|
1034
|
+
} else if (node.children && node.children.length > 0) {
|
|
1035
|
+
result.children = await Promise.all(
|
|
1036
|
+
node.children.map((child) => resolveBindings(child, context, itemData))
|
|
1037
|
+
);
|
|
1038
|
+
} else {
|
|
1039
|
+
result.children = [];
|
|
1040
|
+
}
|
|
1041
|
+
return result;
|
|
1042
|
+
}
|
|
1043
|
+
function executeAction(action, target, payload, context = {}) {
|
|
1044
|
+
let newContext = { ...context };
|
|
1045
|
+
switch (action) {
|
|
1046
|
+
case "SHOW_DETAIL" /* SHOW_DETAIL */: {
|
|
1047
|
+
const taskId = payload?.taskId;
|
|
1048
|
+
const dialogNodeId = target;
|
|
1049
|
+
if (taskId && dialogNodeId) {
|
|
1050
|
+
const tasksData = getValueByPath(context, "tasks.data");
|
|
1051
|
+
if (Array.isArray(tasksData)) {
|
|
1052
|
+
const foundTask = tasksData.find(
|
|
1053
|
+
(t) => t.id === taskId
|
|
1054
|
+
);
|
|
1055
|
+
if (foundTask) {
|
|
1056
|
+
newContext = setValueByPath(newContext, "selectedTask", foundTask);
|
|
1057
|
+
} else {
|
|
1058
|
+
console.warn(
|
|
1059
|
+
`[executeAction] ${"SHOW_DETAIL" /* SHOW_DETAIL */}: Task with id "${taskId}" not found in tasks.data.`
|
|
1060
|
+
);
|
|
1061
|
+
newContext = setValueByPath(newContext, "selectedTask", null);
|
|
1062
|
+
}
|
|
1063
|
+
} else {
|
|
1064
|
+
console.warn(
|
|
1065
|
+
`[executeAction] ${"SHOW_DETAIL" /* SHOW_DETAIL */}: context.tasks.data is not an array or not found.`
|
|
1066
|
+
);
|
|
1067
|
+
newContext = setValueByPath(newContext, "selectedTask", null);
|
|
1068
|
+
}
|
|
1069
|
+
} else {
|
|
1070
|
+
console.warn(
|
|
1071
|
+
`[executeAction] ${"SHOW_DETAIL" /* SHOW_DETAIL */}: payload.taskId or target (dialogNodeId) was missing. Dialog will be shown without a selected task.`
|
|
1072
|
+
);
|
|
1073
|
+
newContext = setValueByPath(newContext, "selectedTask", null);
|
|
1074
|
+
}
|
|
1075
|
+
newContext = setValueByPath(
|
|
1076
|
+
newContext,
|
|
1077
|
+
// Use the potentially modified newContext (with selectedTask set or cleared)
|
|
1078
|
+
"isTaskDetailDialogVisible",
|
|
1079
|
+
true
|
|
1080
|
+
);
|
|
1081
|
+
break;
|
|
1082
|
+
}
|
|
1083
|
+
case "HIDE_DIALOG" /* HIDE_DIALOG */: {
|
|
1084
|
+
newContext = setValueByPath(newContext, "selectedTask", null);
|
|
1085
|
+
newContext = setValueByPath(
|
|
1086
|
+
newContext,
|
|
1087
|
+
"isTaskDetailDialogVisible",
|
|
1088
|
+
false
|
|
1089
|
+
);
|
|
1090
|
+
break;
|
|
1091
|
+
}
|
|
1092
|
+
case "HIDE_DETAIL" /* HIDE_DETAIL */: {
|
|
1093
|
+
newContext = setValueByPath(newContext, "selectedItemForDetail", null);
|
|
1094
|
+
newContext = setValueByPath(newContext, "isDetailViewOpen", false);
|
|
1095
|
+
break;
|
|
1096
|
+
}
|
|
1097
|
+
case "OPEN_DIALOG" /* OPEN_DIALOG */: {
|
|
1098
|
+
const dialogId = target || payload?.dialogId;
|
|
1099
|
+
if (dialogId === "taskDetailDialogNodeId" || !dialogId) {
|
|
1100
|
+
newContext = setValueByPath(
|
|
1101
|
+
newContext,
|
|
1102
|
+
"isTaskDetailDialogVisible",
|
|
1103
|
+
true
|
|
1104
|
+
);
|
|
1105
|
+
} else {
|
|
1106
|
+
console.warn(
|
|
1107
|
+
`[executeAction] ${"OPEN_DIALOG" /* OPEN_DIALOG */}: Unhandled dialogId: ${dialogId}.`
|
|
1108
|
+
);
|
|
1109
|
+
}
|
|
1110
|
+
break;
|
|
1111
|
+
}
|
|
1112
|
+
case "CLOSE_DIALOG" /* CLOSE_DIALOG */: {
|
|
1113
|
+
const dialogId = target || payload?.dialogId;
|
|
1114
|
+
if (dialogId === "taskDetailDialogNodeId" || !dialogId) {
|
|
1115
|
+
newContext = setValueByPath(
|
|
1116
|
+
newContext,
|
|
1117
|
+
"isTaskDetailDialogVisible",
|
|
1118
|
+
false
|
|
1119
|
+
);
|
|
1120
|
+
newContext = setValueByPath(newContext, "selectedTask", null);
|
|
1121
|
+
} else {
|
|
1122
|
+
console.warn(
|
|
1123
|
+
`[executeAction] ${"CLOSE_DIALOG" /* CLOSE_DIALOG */}: Unhandled dialogId: ${dialogId}.`
|
|
1124
|
+
);
|
|
1125
|
+
}
|
|
1126
|
+
break;
|
|
1127
|
+
}
|
|
1128
|
+
case "UPDATE_DATA" /* UPDATE_DATA */: {
|
|
1129
|
+
let updatedContext = context;
|
|
1130
|
+
if (target && payload && "value" in payload) {
|
|
1131
|
+
updatedContext = setValueByPath(context, target, payload.value);
|
|
1132
|
+
} else {
|
|
1133
|
+
console.warn(
|
|
1134
|
+
`[executeAction] ${"UPDATE_DATA" /* UPDATE_DATA */} requires targetPath (data path) and payload with 'value' property.`
|
|
1135
|
+
);
|
|
1136
|
+
}
|
|
1137
|
+
newContext = updatedContext;
|
|
1138
|
+
break;
|
|
1139
|
+
}
|
|
1140
|
+
case "ADD_ITEM" /* ADD_ITEM */: {
|
|
1141
|
+
if (!target) {
|
|
1142
|
+
console.warn(`[executeAction] ADD_ITEM requires target path.`);
|
|
1143
|
+
break;
|
|
1144
|
+
}
|
|
1145
|
+
if (!payload?.item) {
|
|
1146
|
+
console.warn(
|
|
1147
|
+
`[executeAction] ADD_ITEM requires payload with item property.`
|
|
1148
|
+
);
|
|
1149
|
+
break;
|
|
1150
|
+
}
|
|
1151
|
+
const list = getValueByPath(newContext, target);
|
|
1152
|
+
if (!Array.isArray(list)) {
|
|
1153
|
+
console.warn(
|
|
1154
|
+
`[executeAction] ADD_ITEM failed: target path "${target}" does not resolve to an array.`
|
|
1155
|
+
);
|
|
1156
|
+
break;
|
|
1157
|
+
}
|
|
1158
|
+
const newItem = payload.item;
|
|
1159
|
+
const position = payload.position;
|
|
1160
|
+
let newList;
|
|
1161
|
+
if (position === "start") {
|
|
1162
|
+
newList = [newItem, ...list];
|
|
1163
|
+
} else {
|
|
1164
|
+
newList = [...list, newItem];
|
|
1165
|
+
}
|
|
1166
|
+
newContext = setValueByPath(newContext, target, newList);
|
|
1167
|
+
break;
|
|
1168
|
+
}
|
|
1169
|
+
case "DELETE_ITEM" /* DELETE_ITEM */: {
|
|
1170
|
+
if (!target) {
|
|
1171
|
+
console.warn(`[executeAction] DELETE_ITEM requires target path.`);
|
|
1172
|
+
break;
|
|
1173
|
+
}
|
|
1174
|
+
const itemId = payload?.id;
|
|
1175
|
+
if (itemId === void 0 || itemId === null) {
|
|
1176
|
+
console.warn(
|
|
1177
|
+
`[executeAction] DELETE_ITEM requires payload with id property.`
|
|
1178
|
+
);
|
|
1179
|
+
break;
|
|
1180
|
+
}
|
|
1181
|
+
const list = getValueByPath(newContext, target);
|
|
1182
|
+
if (!Array.isArray(list)) {
|
|
1183
|
+
console.warn(
|
|
1184
|
+
`[executeAction] DELETE_ITEM failed: target path "${target}" does not resolve to an array.`
|
|
1185
|
+
);
|
|
1186
|
+
break;
|
|
1187
|
+
}
|
|
1188
|
+
const newList = list.filter(
|
|
1189
|
+
(item) => item?.id !== itemId
|
|
1190
|
+
);
|
|
1191
|
+
if (newList.length !== list.length) {
|
|
1192
|
+
newContext = setValueByPath(newContext, target, newList);
|
|
1193
|
+
} else {
|
|
1194
|
+
console.warn(
|
|
1195
|
+
`[executeAction] DELETE_ITEM: Item with id "${itemId}" not found in list at path "${target}".`
|
|
1196
|
+
);
|
|
1197
|
+
}
|
|
1198
|
+
break;
|
|
1199
|
+
}
|
|
1200
|
+
case "SAVE_TASK_CHANGES" /* SAVE_TASK_CHANGES */: {
|
|
1201
|
+
if (!target) {
|
|
1202
|
+
console.warn(
|
|
1203
|
+
"[executeAction] SAVE_TASK_CHANGES requires target (task ID)."
|
|
1204
|
+
);
|
|
1205
|
+
break;
|
|
1206
|
+
}
|
|
1207
|
+
const taskIdToSave = target;
|
|
1208
|
+
const currentTasks = getValueByPath(newContext, "tasks.data");
|
|
1209
|
+
const selectedTaskData = getValueByPath(newContext, "selectedTask");
|
|
1210
|
+
if (currentTasks && selectedTaskData && selectedTaskData.id === taskIdToSave) {
|
|
1211
|
+
const updatedTasks = currentTasks.map(
|
|
1212
|
+
(task) => task.id === taskIdToSave ? { ...task, ...selectedTaskData, ...payload } : task
|
|
1213
|
+
// Merge selectedTaskData and any direct payload changes
|
|
1214
|
+
);
|
|
1215
|
+
newContext = setValueByPath(newContext, "tasks.data", updatedTasks);
|
|
1216
|
+
newContext = setValueByPath(newContext, "selectedTask", null);
|
|
1217
|
+
newContext = setValueByPath(
|
|
1218
|
+
newContext,
|
|
1219
|
+
"isTaskDetailDialogVisible",
|
|
1220
|
+
false
|
|
1221
|
+
);
|
|
1222
|
+
} else {
|
|
1223
|
+
console.warn(
|
|
1224
|
+
"[executeAction] SAVE_TASK_CHANGES: Could not save. Task list, selected task, or ID mismatch.",
|
|
1225
|
+
{
|
|
1226
|
+
taskIdToSave,
|
|
1227
|
+
selectedTaskDataId: selectedTaskData?.id,
|
|
1228
|
+
currentTasksExists: !!currentTasks
|
|
1229
|
+
}
|
|
1230
|
+
);
|
|
1231
|
+
}
|
|
1232
|
+
break;
|
|
1233
|
+
}
|
|
1234
|
+
default:
|
|
1235
|
+
console.warn(`[executeAction] Unhandled action type: ${action}`);
|
|
1236
|
+
}
|
|
1237
|
+
return newContext;
|
|
1238
|
+
}
|
|
1239
|
+
function makeChildIdsUniqueInInstance(parentNode, baseInstanceId, originalTemplateRootId) {
|
|
1240
|
+
if (parentNode.children) {
|
|
1241
|
+
parentNode.children = parentNode.children.map((child) => {
|
|
1242
|
+
let originalChildId = child.id;
|
|
1243
|
+
if (child.id.startsWith(originalTemplateRootId + "-")) {
|
|
1244
|
+
const parts = child.id.split("-");
|
|
1245
|
+
if (parts.length > 1) {
|
|
1246
|
+
originalChildId = parts[parts.length - 1];
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
const newChildId = `${baseInstanceId}-${originalChildId}`;
|
|
1250
|
+
const newChild = {
|
|
1251
|
+
...JSON.parse(JSON.stringify(child)),
|
|
1252
|
+
// Deep clone child
|
|
1253
|
+
id: newChildId
|
|
1254
|
+
};
|
|
1255
|
+
makeChildIdsUniqueInInstance(
|
|
1256
|
+
newChild,
|
|
1257
|
+
baseInstanceId,
|
|
1258
|
+
originalTemplateRootId
|
|
1259
|
+
);
|
|
1260
|
+
return newChild;
|
|
1261
|
+
});
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
// src/core/action-router.ts
|
|
1266
|
+
var UI_GUIDANCE_BASE = `
|
|
1267
|
+
UI Guidance:
|
|
1268
|
+
1. Create a focused interface that directly addresses the goal
|
|
1269
|
+
2. Use appropriate UI patterns (lists, forms, details, etc.)
|
|
1270
|
+
3. Include navigation between related views when needed
|
|
1271
|
+
4. Keep the interface simple and intuitive
|
|
1272
|
+
5. Bind to schema data where appropriate
|
|
1273
|
+
6. **CRITICAL for Buttons:** All \`Button\` nodes **MUST** include a \`label\` property in their \`props\` (e.g., \`{ "props": { "label": "Click Me" } }\`).
|
|
1274
|
+
7. Provide event handlers for user interactions - make sure to always include both action and target properties`;
|
|
1275
|
+
var LIST_BINDING_GUIDANCE = `8. **CRITICAL:** For \`ListView\` or \`Table\` nodes, the \`data\` binding key **MUST** point to the *exact path* of the data *array* within the context.`;
|
|
1276
|
+
var LIST_BINDING_EXAMPLE = `Example: If the context has \`{ tasks: { data: [...] } }\`, the binding **MUST** be \`{ "bindings": { "data": "tasks.data" } }\`. If the context has \`{ userList: [...] }\`, the binding **MUST** be \`{ "bindings": { "data": "userList" } }\`. **NEVER** bind to the parent object containing the array (e.g., DO NOT USE \`{ "bindings": { "data": "tasks" } }\`).`;
|
|
1277
|
+
var COMMON_UI_GUIDANCE = UI_GUIDANCE_BASE + "\n" + // Add a newline separator
|
|
1278
|
+
LIST_BINDING_GUIDANCE + // Add the specific list binding rule
|
|
1279
|
+
"\n" + // Add a newline separator
|
|
1280
|
+
LIST_BINDING_EXAMPLE;
|
|
1281
|
+
function processTemplate(template, values) {
|
|
1282
|
+
return template.replace(/\${(.*?)}/g, (match, key) => {
|
|
1283
|
+
const trimmedKey = key.trim();
|
|
1284
|
+
return trimmedKey in values ? String(values[trimmedKey]) : match;
|
|
1285
|
+
});
|
|
1286
|
+
}
|
|
1287
|
+
function buildPrompt(input, promptTemplate, templateValues) {
|
|
1288
|
+
const { schema, goal, history, userContext } = input;
|
|
1289
|
+
const schemaInfo = Object.entries(schema).map(([tableName, tableSchema]) => {
|
|
1290
|
+
const schemaString = typeof tableSchema === "object" && tableSchema !== null ? JSON.stringify(tableSchema) : String(tableSchema);
|
|
1291
|
+
return `Table: ${tableName}
|
|
1292
|
+
Schema: ${schemaString}`;
|
|
1293
|
+
}).join("\n\n");
|
|
1294
|
+
const recentEvents = history && history.length > 0 ? history.slice(-5).map(
|
|
1295
|
+
(event) => `Event: ${event.type} on node ${event.nodeId}${event.payload ? ` with payload ${JSON.stringify(event.payload)}` : ""}`
|
|
1296
|
+
).join("\n") : "No recent events";
|
|
1297
|
+
const userContextSection = userContext ? `
|
|
1298
|
+
|
|
1299
|
+
User Context:
|
|
1300
|
+
${JSON.stringify(userContext)}` : "";
|
|
1301
|
+
if (promptTemplate && templateValues) {
|
|
1302
|
+
const fullTemplateValues = {
|
|
1303
|
+
...templateValues,
|
|
1304
|
+
schemaInfo,
|
|
1305
|
+
recentEvents,
|
|
1306
|
+
userContextString: userContextSection.trim(),
|
|
1307
|
+
// Use trimmed version
|
|
1308
|
+
commonUIGuidance: COMMON_UI_GUIDANCE,
|
|
1309
|
+
goal
|
|
1310
|
+
// Ensure goal is always available to templates
|
|
1311
|
+
};
|
|
1312
|
+
return processTemplate(promptTemplate, fullTemplateValues);
|
|
1313
|
+
}
|
|
1314
|
+
const interactionDescription = history && history.length > 0 ? `The user's last action was: ${history[history.length - 1].type} on node ${history[history.length - 1].nodeId}` : "The user initiated the session for the goal";
|
|
1315
|
+
return `
|
|
1316
|
+
You are an expert UI generator.
|
|
1317
|
+
Create a user interface that achieves the following goal: "${goal}".
|
|
1318
|
+
${interactionDescription}.
|
|
1319
|
+
|
|
1320
|
+
Available data schema:
|
|
1321
|
+
${schemaInfo}
|
|
1322
|
+
|
|
1323
|
+
Recent user interactions:
|
|
1324
|
+
${recentEvents}${userContextSection}
|
|
1325
|
+
|
|
1326
|
+
Generate a complete UI specification in JSON format that matches the following TypeScript type:
|
|
1327
|
+
type UISpecNode = { id: string; node_type: string; props?: Record<string, unknown>; bindings?: Record<string, unknown>; events?: Record<string, { action: string; target: string; payload?: Record<string, unknown>; }>; children?: UISpecNode[]; };
|
|
1328
|
+
${COMMON_UI_GUIDANCE}
|
|
1329
|
+
|
|
1330
|
+
Respond ONLY with the JSON UI specification and no other text.
|
|
1331
|
+
`;
|
|
1332
|
+
}
|
|
1333
|
+
var ActionRouter = class {
|
|
1334
|
+
constructor(apiKey, planningConfig) {
|
|
1335
|
+
this.apiKey = apiKey;
|
|
1336
|
+
this.planningConfig = planningConfig || {
|
|
1337
|
+
prefetchDepth: 1,
|
|
1338
|
+
temperature: 0.5,
|
|
1339
|
+
streaming: false
|
|
1340
|
+
};
|
|
1341
|
+
}
|
|
1342
|
+
/**
|
|
1343
|
+
* Find the appropriate route for an event
|
|
1344
|
+
* @param event - UI event
|
|
1345
|
+
* @param layout - Current UI layout
|
|
1346
|
+
* @param dataContext - Current data context
|
|
1347
|
+
* @returns Route resolution or null if no match
|
|
1348
|
+
*/
|
|
1349
|
+
async resolveRoute(event, schema, layout, dataContext, goal, apiKey, userContext) {
|
|
1350
|
+
console.log(
|
|
1351
|
+
`[ActionRouter Debug] resolveRoute called for event type: ${event.type}, nodeId: ${event.nodeId}`
|
|
1352
|
+
);
|
|
1353
|
+
const sourceNode = layout ? findNodeById(layout, event.nodeId) : void 0;
|
|
1354
|
+
const nodeConfig = sourceNode?.events?.[event.type];
|
|
1355
|
+
let actionType;
|
|
1356
|
+
let determinedTargetNodeId;
|
|
1357
|
+
let determinedNodeConfigPayload = null;
|
|
1358
|
+
if (nodeConfig?.action) {
|
|
1359
|
+
actionType = nodeConfig.action;
|
|
1360
|
+
determinedTargetNodeId = nodeConfig.target || sourceNode?.id || "root";
|
|
1361
|
+
determinedNodeConfigPayload = nodeConfig.payload ? { ...nodeConfig.payload } : null;
|
|
1362
|
+
} else {
|
|
1363
|
+
switch (event.type) {
|
|
1364
|
+
case "INIT":
|
|
1365
|
+
actionType = "FULL_REFRESH" /* FULL_REFRESH */;
|
|
1366
|
+
determinedTargetNodeId = "root";
|
|
1367
|
+
break;
|
|
1368
|
+
case "CLICK":
|
|
1369
|
+
actionType = "FULL_REFRESH" /* FULL_REFRESH */;
|
|
1370
|
+
determinedTargetNodeId = "root";
|
|
1371
|
+
break;
|
|
1372
|
+
case "CHANGE":
|
|
1373
|
+
if (sourceNode && ["Input", "Select", "Textarea", "Checkbox", "RadioGroup"].includes(sourceNode.node_type)) {
|
|
1374
|
+
actionType = "UPDATE_DATA" /* UPDATE_DATA */;
|
|
1375
|
+
determinedTargetNodeId = sourceNode.id;
|
|
1376
|
+
} else {
|
|
1377
|
+
actionType = "FULL_REFRESH" /* FULL_REFRESH */;
|
|
1378
|
+
determinedTargetNodeId = "root";
|
|
1379
|
+
}
|
|
1380
|
+
break;
|
|
1381
|
+
default:
|
|
1382
|
+
actionType = "FULL_REFRESH" /* FULL_REFRESH */;
|
|
1383
|
+
determinedTargetNodeId = "root";
|
|
1384
|
+
break;
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
if (!Object.values(ActionType).includes(actionType)) {
|
|
1388
|
+
throw new Error(`Invalid action type: ${actionType}`);
|
|
1389
|
+
}
|
|
1390
|
+
const additionalContext = {};
|
|
1391
|
+
if (sourceNode) {
|
|
1392
|
+
additionalContext.sourceNode = sourceNode;
|
|
1393
|
+
}
|
|
1394
|
+
const targetNode = layout ? findNodeById(layout, determinedTargetNodeId) : void 0;
|
|
1395
|
+
if (targetNode) {
|
|
1396
|
+
additionalContext.targetNode = targetNode;
|
|
1397
|
+
}
|
|
1398
|
+
let finalEventPayload = event.payload && Object.keys(event.payload).length > 0 ? { ...event.payload } : null;
|
|
1399
|
+
if (determinedNodeConfigPayload) {
|
|
1400
|
+
finalEventPayload = { ...finalEventPayload || {}, ...determinedNodeConfigPayload };
|
|
1401
|
+
}
|
|
1402
|
+
if (finalEventPayload) {
|
|
1403
|
+
additionalContext.eventPayload = finalEventPayload;
|
|
1404
|
+
}
|
|
1405
|
+
const plannerInput2 = {
|
|
640
1406
|
schema,
|
|
641
1407
|
goal,
|
|
642
|
-
history: [
|
|
643
|
-
userContext
|
|
1408
|
+
history: [event],
|
|
1409
|
+
userContext: { ...userContext || {}, ...additionalContext }
|
|
644
1410
|
};
|
|
645
|
-
if (
|
|
646
|
-
const
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
const
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
}
|
|
1411
|
+
if (actionType === "SHOW_DETAIL" /* SHOW_DETAIL */) {
|
|
1412
|
+
const itemData = dataContext.tasks?.data?.find(
|
|
1413
|
+
(task) => task.id === (event.payload?.itemId || event.payload?.taskId)
|
|
1414
|
+
);
|
|
1415
|
+
const updatedDataContext = {
|
|
1416
|
+
...dataContext,
|
|
1417
|
+
selectedTask: itemData,
|
|
1418
|
+
isTaskDetailDialogVisible: true
|
|
1419
|
+
};
|
|
1420
|
+
const layoutFromLLM = await callPlannerLLM(plannerInput2, apiKey);
|
|
1421
|
+
return {
|
|
1422
|
+
actionType: "UPDATE_CONTEXT" /* UPDATE_CONTEXT */,
|
|
1423
|
+
targetNodeId: determinedTargetNodeId,
|
|
1424
|
+
updatedDataContext,
|
|
1425
|
+
updatedNode: layoutFromLLM
|
|
1426
|
+
};
|
|
1427
|
+
}
|
|
1428
|
+
if (actionType === "HIDE_DIALOG" /* HIDE_DIALOG */) {
|
|
1429
|
+
if (!layout) {
|
|
1430
|
+
throw new Error("Layout cannot be null when handling HIDE_DIALOG");
|
|
1431
|
+
}
|
|
1432
|
+
return {
|
|
1433
|
+
actionType: "UPDATE_CONTEXT" /* UPDATE_CONTEXT */,
|
|
1434
|
+
targetNodeId: determinedTargetNodeId,
|
|
1435
|
+
updatedDataContext: {
|
|
1436
|
+
...dataContext,
|
|
1437
|
+
selectedTask: null,
|
|
1438
|
+
isTaskDetailDialogVisible: false
|
|
1439
|
+
},
|
|
1440
|
+
updatedNode: layout
|
|
1441
|
+
};
|
|
1442
|
+
}
|
|
1443
|
+
if (["UPDATE_DATA" /* UPDATE_DATA */, "ADD_ITEM" /* ADD_ITEM */, "DELETE_ITEM" /* DELETE_ITEM */, "SAVE_TASK_CHANGES" /* SAVE_TASK_CHANGES */].includes(actionType)) {
|
|
1444
|
+
if (!layout) {
|
|
1445
|
+
throw new Error("Layout cannot be null when handling data update actions");
|
|
1446
|
+
}
|
|
1447
|
+
const targetPathOrId = actionType === "UPDATE_DATA" /* UPDATE_DATA */ ? sourceNode?.bindings?.value || determinedTargetNodeId : determinedTargetNodeId;
|
|
1448
|
+
return {
|
|
1449
|
+
actionType,
|
|
1450
|
+
targetNodeId: determinedTargetNodeId,
|
|
1451
|
+
updatedNode: layout,
|
|
1452
|
+
updatedDataContext: executeAction(actionType, targetPathOrId, finalEventPayload || {}, dataContext)
|
|
1453
|
+
};
|
|
1454
|
+
}
|
|
1455
|
+
if ([
|
|
1456
|
+
"FULL_REFRESH" /* FULL_REFRESH */,
|
|
1457
|
+
"UPDATE_NODE" /* UPDATE_NODE */,
|
|
1458
|
+
"ADD_DROPDOWN" /* ADD_DROPDOWN */,
|
|
1459
|
+
"TOGGLE_STATE" /* TOGGLE_STATE */,
|
|
1460
|
+
"UPDATE_FORM" /* UPDATE_FORM */,
|
|
1461
|
+
"NAVIGATE" /* NAVIGATE */,
|
|
1462
|
+
"UPDATE_CONTEXT" /* UPDATE_CONTEXT */
|
|
1463
|
+
].includes(actionType)) {
|
|
1464
|
+
const layoutFromLLM = await callPlannerLLM(plannerInput2, apiKey);
|
|
1465
|
+
return {
|
|
1466
|
+
actionType,
|
|
1467
|
+
targetNodeId: determinedTargetNodeId,
|
|
1468
|
+
updatedNode: layoutFromLLM,
|
|
1469
|
+
updatedDataContext: dataContext
|
|
1470
|
+
};
|
|
1471
|
+
}
|
|
1472
|
+
throw new Error(`Unhandled action type: ${actionType}`);
|
|
1473
|
+
}
|
|
1474
|
+
};
|
|
1475
|
+
function useUIStateEngine({
|
|
1476
|
+
schema,
|
|
1477
|
+
goal,
|
|
1478
|
+
apiKey = "",
|
|
1479
|
+
userContext,
|
|
1480
|
+
planningConfig,
|
|
1481
|
+
dataContext = {},
|
|
1482
|
+
enablePartialUpdates = true
|
|
1483
|
+
}) {
|
|
1484
|
+
if (userContext === null) {
|
|
1485
|
+
console.warn(
|
|
1486
|
+
"useUIStateEngine: userContext was explicitly set to null. This is an allowed but discouraged value. Consider using undefined if you intend to omit the user context."
|
|
1487
|
+
);
|
|
1488
|
+
}
|
|
1489
|
+
const [state, dispatch] = useReducer(uiReducer, initialState);
|
|
1490
|
+
const stateRef = useRef(state);
|
|
1491
|
+
const router = new ActionRouter(apiKey, planningConfig);
|
|
1492
|
+
useEffect(() => {
|
|
1493
|
+
stateRef.current = state;
|
|
1494
|
+
}, [state]);
|
|
1495
|
+
const handleEvent = useCallback(
|
|
1496
|
+
async (event, currentResolvedLayout, updatedDataContext) => {
|
|
1497
|
+
dispatch({ type: "UI_EVENT", event });
|
|
1498
|
+
dispatch({ type: "LOADING", isLoading: true });
|
|
1499
|
+
try {
|
|
1500
|
+
let resolvedNode;
|
|
1501
|
+
let actionTypeForDispatch = "FULL_REFRESH" /* FULL_REFRESH */;
|
|
1502
|
+
let targetNodeIdForDispatch = "root";
|
|
1503
|
+
const layoutForRouting = currentResolvedLayout || stateRef.current.layout;
|
|
1504
|
+
const contextForRouting = updatedDataContext || dataContext;
|
|
1505
|
+
console.log(
|
|
1506
|
+
"[state.ts handleEvent] About to call router.resolveRoute. enablePartialUpdates:",
|
|
1507
|
+
enablePartialUpdates
|
|
1508
|
+
);
|
|
1509
|
+
console.log(
|
|
1510
|
+
"[state.ts handleEvent] layoutForRouting ID (if exists):",
|
|
1511
|
+
layoutForRouting?.id
|
|
1512
|
+
);
|
|
1513
|
+
console.log(
|
|
1514
|
+
"[state.ts handleEvent] contextForRouting:",
|
|
1515
|
+
JSON.stringify(contextForRouting, null, 2)
|
|
1516
|
+
);
|
|
1517
|
+
const route = await router.resolveRoute(
|
|
1518
|
+
event,
|
|
1519
|
+
schema,
|
|
1520
|
+
layoutForRouting,
|
|
1521
|
+
contextForRouting,
|
|
1522
|
+
goal,
|
|
1523
|
+
apiKey,
|
|
1524
|
+
userContext
|
|
1525
|
+
);
|
|
1526
|
+
console.log(
|
|
1527
|
+
"[state.ts handleEvent] router.resolveRoute returned:",
|
|
1528
|
+
route
|
|
1529
|
+
);
|
|
1530
|
+
if (route) {
|
|
1531
|
+
console.log("Resolved route:", route);
|
|
1532
|
+
actionTypeForDispatch = route.actionType;
|
|
1533
|
+
targetNodeIdForDispatch = route.targetNodeId;
|
|
1534
|
+
if (route.updatedDataContext) {
|
|
1535
|
+
dispatch({
|
|
1536
|
+
type: "SET_DATA_CONTEXT",
|
|
1537
|
+
payload: route.updatedDataContext
|
|
1538
|
+
});
|
|
1539
|
+
}
|
|
1540
|
+
if (!route.updatedNode) {
|
|
1541
|
+
throw new Error(
|
|
1542
|
+
"No updatedNode returned from router.resolveRoute. This should not happen."
|
|
1543
|
+
);
|
|
1544
|
+
}
|
|
1545
|
+
resolvedNode = route.updatedNode;
|
|
1546
|
+
} else {
|
|
1547
|
+
throw new Error("No route returned from router.resolveRoute");
|
|
1548
|
+
}
|
|
1549
|
+
switch (actionTypeForDispatch) {
|
|
1550
|
+
case "UPDATE_NODE" /* UPDATE_NODE */:
|
|
1551
|
+
case "SHOW_DETAIL" /* SHOW_DETAIL */:
|
|
1552
|
+
case "TOGGLE_STATE" /* TOGGLE_STATE */:
|
|
1553
|
+
case "ADD_DROPDOWN" /* ADD_DROPDOWN */:
|
|
1554
|
+
case "UPDATE_FORM" /* UPDATE_FORM */:
|
|
1555
|
+
case "NAVIGATE" /* NAVIGATE */:
|
|
1556
|
+
dispatch({
|
|
1557
|
+
type: "PARTIAL_UPDATE",
|
|
1558
|
+
nodeId: targetNodeIdForDispatch,
|
|
1559
|
+
node: resolvedNode
|
|
1560
|
+
});
|
|
1561
|
+
break;
|
|
1562
|
+
case "HIDE_DIALOG" /* HIDE_DIALOG */:
|
|
1563
|
+
dispatch({
|
|
1564
|
+
type: "PARTIAL_UPDATE",
|
|
1565
|
+
nodeId: targetNodeIdForDispatch,
|
|
1566
|
+
node: resolvedNode
|
|
1567
|
+
});
|
|
1568
|
+
break;
|
|
1569
|
+
case "SAVE_TASK_CHANGES" /* SAVE_TASK_CHANGES */:
|
|
1570
|
+
dispatch({ type: "AI_RESPONSE", node: resolvedNode });
|
|
1571
|
+
break;
|
|
1572
|
+
case "FULL_REFRESH" /* FULL_REFRESH */:
|
|
1573
|
+
default:
|
|
1574
|
+
dispatch({ type: "AI_RESPONSE", node: resolvedNode });
|
|
1575
|
+
break;
|
|
1576
|
+
}
|
|
1577
|
+
} catch (e) {
|
|
1578
|
+
const errorMessage = e instanceof Error ? e.message : String(e);
|
|
1579
|
+
dispatch({ type: "ERROR", message: errorMessage });
|
|
1580
|
+
systemEvents.emit(
|
|
1581
|
+
createSystemEvent("PLAN_ERROR" /* PLAN_ERROR */, {
|
|
1582
|
+
error: e instanceof Error ? e : new Error(String(e))
|
|
1583
|
+
})
|
|
1584
|
+
);
|
|
1585
|
+
} finally {
|
|
1586
|
+
dispatch({ type: "LOADING", isLoading: false });
|
|
1587
|
+
}
|
|
1588
|
+
},
|
|
1589
|
+
[
|
|
1590
|
+
goal,
|
|
1591
|
+
schema,
|
|
1592
|
+
userContext,
|
|
1593
|
+
dataContext,
|
|
1594
|
+
apiKey,
|
|
1595
|
+
enablePartialUpdates,
|
|
1596
|
+
dispatch
|
|
1597
|
+
]
|
|
1598
|
+
);
|
|
1599
|
+
useEffect(() => {
|
|
1600
|
+
const initialFetch = async () => {
|
|
1601
|
+
dispatch({ type: "LOADING", isLoading: true });
|
|
1602
|
+
try {
|
|
1603
|
+
const initEvent = {
|
|
1604
|
+
type: "INIT",
|
|
1605
|
+
nodeId: "system",
|
|
1606
|
+
timestamp: Date.now(),
|
|
1607
|
+
payload: null
|
|
1608
|
+
};
|
|
1609
|
+
const route = await router.resolveRoute(
|
|
1610
|
+
initEvent,
|
|
1611
|
+
schema,
|
|
1612
|
+
stateRef.current.layout,
|
|
1613
|
+
dataContext,
|
|
1614
|
+
goal,
|
|
1615
|
+
apiKey,
|
|
1616
|
+
userContext
|
|
1617
|
+
);
|
|
1618
|
+
if (!route) {
|
|
1619
|
+
console.error(
|
|
1620
|
+
"[UIStateEngine] Initial fetch: Failed to resolve route for INIT event."
|
|
1621
|
+
);
|
|
1622
|
+
throw new Error("Failed to initialize UI due to routing error.");
|
|
1623
|
+
}
|
|
1624
|
+
if (route.updatedDataContext) {
|
|
1625
|
+
dispatch({
|
|
1626
|
+
type: "SET_DATA_CONTEXT",
|
|
1627
|
+
payload: route.updatedDataContext
|
|
1628
|
+
});
|
|
1629
|
+
}
|
|
1630
|
+
if (!route.updatedNode) {
|
|
1631
|
+
throw new Error(
|
|
1632
|
+
"No updatedNode returned from router.resolveRoute on initial fetch. This should not happen."
|
|
1633
|
+
);
|
|
1634
|
+
}
|
|
1635
|
+
const node = route.updatedNode;
|
|
1636
|
+
dispatch({ type: "AI_RESPONSE", node });
|
|
1637
|
+
} catch (e) {
|
|
1638
|
+
const errorMessage = e instanceof Error ? e.message : String(e);
|
|
1639
|
+
dispatch({ type: "ERROR", message: errorMessage });
|
|
1640
|
+
systemEvents.emit(
|
|
1641
|
+
createSystemEvent("PLAN_ERROR" /* PLAN_ERROR */, {
|
|
1642
|
+
error: e instanceof Error ? e : new Error(String(e))
|
|
1643
|
+
})
|
|
1644
|
+
);
|
|
1645
|
+
} finally {
|
|
1646
|
+
dispatch({ type: "LOADING", isLoading: false });
|
|
1647
|
+
}
|
|
1648
|
+
};
|
|
1649
|
+
initialFetch();
|
|
1650
|
+
}, [goal, schema, dispatch]);
|
|
1651
|
+
return {
|
|
1652
|
+
state,
|
|
1653
|
+
dispatch,
|
|
1654
|
+
handleEvent
|
|
1655
|
+
};
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1658
|
+
// src/adapters/shadcn.tsx
|
|
1659
|
+
init_utils();
|
|
1660
|
+
|
|
1661
|
+
// components/ui/dialog.tsx
|
|
1662
|
+
init_utils();
|
|
1663
|
+
function Dialog({
|
|
1664
|
+
...props
|
|
1665
|
+
}) {
|
|
1666
|
+
return /* @__PURE__ */ jsx(DialogPrimitive.Root, { "data-slot": "dialog", ...props });
|
|
1667
|
+
}
|
|
1668
|
+
function DialogPortal({
|
|
1669
|
+
...props
|
|
1670
|
+
}) {
|
|
1671
|
+
return /* @__PURE__ */ jsx(DialogPrimitive.Portal, { "data-slot": "dialog-portal", ...props });
|
|
1672
|
+
}
|
|
1673
|
+
function DialogOverlay({
|
|
1674
|
+
className,
|
|
1675
|
+
...props
|
|
1676
|
+
}) {
|
|
1677
|
+
return /* @__PURE__ */ jsx(
|
|
1678
|
+
DialogPrimitive.Overlay,
|
|
1679
|
+
{
|
|
1680
|
+
"data-slot": "dialog-overlay",
|
|
1681
|
+
className: cn(
|
|
1682
|
+
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
|
|
1683
|
+
className
|
|
1684
|
+
),
|
|
1685
|
+
...props
|
|
1686
|
+
}
|
|
1687
|
+
);
|
|
1688
|
+
}
|
|
1689
|
+
function DialogContent({
|
|
1690
|
+
className,
|
|
1691
|
+
children,
|
|
1692
|
+
...props
|
|
1693
|
+
}) {
|
|
1694
|
+
return /* @__PURE__ */ jsxs(DialogPortal, { "data-slot": "dialog-portal", children: [
|
|
1695
|
+
/* @__PURE__ */ jsx(DialogOverlay, {}),
|
|
1696
|
+
/* @__PURE__ */ jsxs(
|
|
1697
|
+
DialogPrimitive.Content,
|
|
1698
|
+
{
|
|
1699
|
+
"data-slot": "dialog-content",
|
|
1700
|
+
className: cn(
|
|
1701
|
+
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg",
|
|
1702
|
+
className
|
|
1703
|
+
),
|
|
1704
|
+
...props,
|
|
1705
|
+
children: [
|
|
1706
|
+
children,
|
|
1707
|
+
/* @__PURE__ */ jsxs(DialogPrimitive.Close, { className: "ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", children: [
|
|
1708
|
+
/* @__PURE__ */ jsx(XIcon, {}),
|
|
1709
|
+
/* @__PURE__ */ jsx("span", { className: "sr-only", children: "Close" })
|
|
1710
|
+
] })
|
|
1711
|
+
]
|
|
1712
|
+
}
|
|
1713
|
+
)
|
|
1714
|
+
] });
|
|
1715
|
+
}
|
|
1716
|
+
function DialogHeader({ className, ...props }) {
|
|
1717
|
+
return /* @__PURE__ */ jsx(
|
|
1718
|
+
"div",
|
|
1719
|
+
{
|
|
1720
|
+
"data-slot": "dialog-header",
|
|
1721
|
+
className: cn("flex flex-col gap-2 text-center sm:text-left", className),
|
|
1722
|
+
...props
|
|
1723
|
+
}
|
|
1724
|
+
);
|
|
1725
|
+
}
|
|
1726
|
+
function DialogTitle({
|
|
1727
|
+
className,
|
|
1728
|
+
...props
|
|
1729
|
+
}) {
|
|
1730
|
+
return /* @__PURE__ */ jsx(
|
|
1731
|
+
DialogPrimitive.Title,
|
|
1732
|
+
{
|
|
1733
|
+
"data-slot": "dialog-title",
|
|
1734
|
+
className: cn("text-lg leading-none font-semibold", className),
|
|
1735
|
+
...props
|
|
1736
|
+
}
|
|
1737
|
+
);
|
|
1738
|
+
}
|
|
1739
|
+
function DialogDescription({
|
|
1740
|
+
className,
|
|
1741
|
+
...props
|
|
1742
|
+
}) {
|
|
1743
|
+
return /* @__PURE__ */ jsx(
|
|
1744
|
+
DialogPrimitive.Description,
|
|
1745
|
+
{
|
|
1746
|
+
"data-slot": "dialog-description",
|
|
1747
|
+
className: cn("text-muted-foreground text-sm", className),
|
|
1748
|
+
...props
|
|
1749
|
+
}
|
|
1750
|
+
);
|
|
1751
|
+
}
|
|
1752
|
+
|
|
1753
|
+
// components/ui/card.tsx
|
|
1754
|
+
init_utils();
|
|
1755
|
+
function Card({ className, ...props }) {
|
|
1756
|
+
return /* @__PURE__ */ jsx(
|
|
1757
|
+
"div",
|
|
1758
|
+
{
|
|
1759
|
+
"data-slot": "card",
|
|
1760
|
+
className: cn(
|
|
1761
|
+
"bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
|
|
1762
|
+
className
|
|
1763
|
+
),
|
|
1764
|
+
...props
|
|
1765
|
+
}
|
|
1766
|
+
);
|
|
1767
|
+
}
|
|
1768
|
+
function CardContent({ className, ...props }) {
|
|
1769
|
+
return /* @__PURE__ */ jsx(
|
|
1770
|
+
"div",
|
|
1771
|
+
{
|
|
1772
|
+
"data-slot": "card-content",
|
|
1773
|
+
className: cn("px-6", className),
|
|
1774
|
+
...props
|
|
654
1775
|
}
|
|
655
|
-
|
|
656
|
-
|
|
1776
|
+
);
|
|
1777
|
+
}
|
|
1778
|
+
|
|
1779
|
+
// components/ui/input.tsx
|
|
1780
|
+
init_utils();
|
|
1781
|
+
function Input({ className, type, ...props }) {
|
|
1782
|
+
return /* @__PURE__ */ jsx(
|
|
1783
|
+
"input",
|
|
657
1784
|
{
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
1785
|
+
type,
|
|
1786
|
+
"data-slot": "input",
|
|
1787
|
+
className: cn(
|
|
1788
|
+
"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
|
1789
|
+
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
|
|
1790
|
+
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
|
1791
|
+
className
|
|
1792
|
+
),
|
|
1793
|
+
...props
|
|
1794
|
+
}
|
|
1795
|
+
);
|
|
1796
|
+
}
|
|
1797
|
+
|
|
1798
|
+
// components/ui/textarea.tsx
|
|
1799
|
+
init_utils();
|
|
1800
|
+
function Textarea({ className, ...props }) {
|
|
1801
|
+
return /* @__PURE__ */ jsx(
|
|
1802
|
+
"textarea",
|
|
1803
|
+
{
|
|
1804
|
+
"data-slot": "textarea",
|
|
1805
|
+
className: cn(
|
|
1806
|
+
"border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
|
1807
|
+
className
|
|
1808
|
+
),
|
|
1809
|
+
...props
|
|
1810
|
+
}
|
|
1811
|
+
);
|
|
1812
|
+
}
|
|
1813
|
+
|
|
1814
|
+
// components/ui/select.tsx
|
|
1815
|
+
init_utils();
|
|
1816
|
+
function Select({
|
|
1817
|
+
...props
|
|
1818
|
+
}) {
|
|
1819
|
+
return /* @__PURE__ */ jsx(SelectPrimitive.Root, { "data-slot": "select", ...props });
|
|
1820
|
+
}
|
|
1821
|
+
function SelectValue({
|
|
1822
|
+
...props
|
|
1823
|
+
}) {
|
|
1824
|
+
return /* @__PURE__ */ jsx(SelectPrimitive.Value, { "data-slot": "select-value", ...props });
|
|
1825
|
+
}
|
|
1826
|
+
function SelectTrigger({
|
|
1827
|
+
className,
|
|
1828
|
+
size = "default",
|
|
1829
|
+
children,
|
|
1830
|
+
...props
|
|
1831
|
+
}) {
|
|
1832
|
+
return /* @__PURE__ */ jsxs(
|
|
1833
|
+
SelectPrimitive.Trigger,
|
|
1834
|
+
{
|
|
1835
|
+
"data-slot": "select-trigger",
|
|
1836
|
+
"data-size": size,
|
|
1837
|
+
className: cn(
|
|
1838
|
+
"border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
1839
|
+
className
|
|
1840
|
+
),
|
|
1841
|
+
...props,
|
|
1842
|
+
children: [
|
|
1843
|
+
children,
|
|
1844
|
+
/* @__PURE__ */ jsx(SelectPrimitive.Icon, { asChild: true, children: /* @__PURE__ */ jsx(ChevronDownIcon, { className: "size-4 opacity-50" }) })
|
|
1845
|
+
]
|
|
1846
|
+
}
|
|
1847
|
+
);
|
|
1848
|
+
}
|
|
1849
|
+
function SelectContent({
|
|
1850
|
+
className,
|
|
1851
|
+
children,
|
|
1852
|
+
position = "popper",
|
|
1853
|
+
...props
|
|
1854
|
+
}) {
|
|
1855
|
+
return /* @__PURE__ */ jsx(SelectPrimitive.Portal, { children: /* @__PURE__ */ jsxs(
|
|
1856
|
+
SelectPrimitive.Content,
|
|
1857
|
+
{
|
|
1858
|
+
"data-slot": "select-content",
|
|
1859
|
+
className: cn(
|
|
1860
|
+
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-md",
|
|
1861
|
+
position === "popper" && "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
|
1862
|
+
className
|
|
1863
|
+
),
|
|
1864
|
+
position,
|
|
1865
|
+
...props,
|
|
1866
|
+
children: [
|
|
1867
|
+
/* @__PURE__ */ jsx(SelectScrollUpButton, {}),
|
|
1868
|
+
/* @__PURE__ */ jsx(
|
|
1869
|
+
SelectPrimitive.Viewport,
|
|
1870
|
+
{
|
|
1871
|
+
className: cn(
|
|
1872
|
+
"p-1",
|
|
1873
|
+
position === "popper" && "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1"
|
|
1874
|
+
),
|
|
1875
|
+
children
|
|
697
1876
|
}
|
|
698
|
-
|
|
699
|
-
|
|
1877
|
+
),
|
|
1878
|
+
/* @__PURE__ */ jsx(SelectScrollDownButton, {})
|
|
1879
|
+
]
|
|
1880
|
+
}
|
|
1881
|
+
) });
|
|
1882
|
+
}
|
|
1883
|
+
function SelectItem({
|
|
1884
|
+
className,
|
|
1885
|
+
children,
|
|
1886
|
+
...props
|
|
1887
|
+
}) {
|
|
1888
|
+
return /* @__PURE__ */ jsxs(
|
|
1889
|
+
SelectPrimitive.Item,
|
|
1890
|
+
{
|
|
1891
|
+
"data-slot": "select-item",
|
|
1892
|
+
className: cn(
|
|
1893
|
+
"focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
|
|
1894
|
+
className
|
|
1895
|
+
),
|
|
1896
|
+
...props,
|
|
1897
|
+
children: [
|
|
1898
|
+
/* @__PURE__ */ jsx("span", { className: "absolute right-2 flex size-3.5 items-center justify-center", children: /* @__PURE__ */ jsx(SelectPrimitive.ItemIndicator, { children: /* @__PURE__ */ jsx(CheckIcon, { className: "size-4" }) }) }),
|
|
1899
|
+
/* @__PURE__ */ jsx(SelectPrimitive.ItemText, { children })
|
|
1900
|
+
]
|
|
1901
|
+
}
|
|
1902
|
+
);
|
|
1903
|
+
}
|
|
1904
|
+
function SelectScrollUpButton({
|
|
1905
|
+
className,
|
|
1906
|
+
...props
|
|
1907
|
+
}) {
|
|
1908
|
+
return /* @__PURE__ */ jsx(
|
|
1909
|
+
SelectPrimitive.ScrollUpButton,
|
|
1910
|
+
{
|
|
1911
|
+
"data-slot": "select-scroll-up-button",
|
|
1912
|
+
className: cn(
|
|
1913
|
+
"flex cursor-default items-center justify-center py-1",
|
|
1914
|
+
className
|
|
1915
|
+
),
|
|
1916
|
+
...props,
|
|
1917
|
+
children: /* @__PURE__ */ jsx(ChevronUpIcon, { className: "size-4" })
|
|
1918
|
+
}
|
|
1919
|
+
);
|
|
1920
|
+
}
|
|
1921
|
+
function SelectScrollDownButton({
|
|
1922
|
+
className,
|
|
1923
|
+
...props
|
|
1924
|
+
}) {
|
|
1925
|
+
return /* @__PURE__ */ jsx(
|
|
1926
|
+
SelectPrimitive.ScrollDownButton,
|
|
1927
|
+
{
|
|
1928
|
+
"data-slot": "select-scroll-down-button",
|
|
1929
|
+
className: cn(
|
|
1930
|
+
"flex cursor-default items-center justify-center py-1",
|
|
1931
|
+
className
|
|
1932
|
+
),
|
|
1933
|
+
...props,
|
|
1934
|
+
children: /* @__PURE__ */ jsx(ChevronDownIcon, { className: "size-4" })
|
|
1935
|
+
}
|
|
1936
|
+
);
|
|
1937
|
+
}
|
|
1938
|
+
|
|
1939
|
+
// components/ui/checkbox.tsx
|
|
1940
|
+
init_utils();
|
|
1941
|
+
function Checkbox({
|
|
1942
|
+
className,
|
|
1943
|
+
...props
|
|
1944
|
+
}) {
|
|
1945
|
+
return /* @__PURE__ */ jsx(
|
|
1946
|
+
CheckboxPrimitive.Root,
|
|
1947
|
+
{
|
|
1948
|
+
"data-slot": "checkbox",
|
|
1949
|
+
className: cn(
|
|
1950
|
+
"peer border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
|
|
1951
|
+
className
|
|
1952
|
+
),
|
|
1953
|
+
...props,
|
|
1954
|
+
children: /* @__PURE__ */ jsx(
|
|
1955
|
+
CheckboxPrimitive.Indicator,
|
|
1956
|
+
{
|
|
1957
|
+
"data-slot": "checkbox-indicator",
|
|
1958
|
+
className: "flex items-center justify-center text-current transition-none",
|
|
1959
|
+
children: /* @__PURE__ */ jsx(CheckIcon, { className: "size-3.5" })
|
|
700
1960
|
}
|
|
701
|
-
|
|
702
|
-
createSystemEvent("PLAN_COMPLETE" /* PLAN_COMPLETE */, {
|
|
703
|
-
layout: validatedNode,
|
|
704
|
-
executionTimeMs: 0
|
|
705
|
-
// Not available here
|
|
706
|
-
})
|
|
707
|
-
);
|
|
708
|
-
} catch (parseError) {
|
|
709
|
-
console.error("Failed to parse LLM response:", parseError);
|
|
710
|
-
dispatch({
|
|
711
|
-
type: "ERROR",
|
|
712
|
-
message: "Failed to parse LLM response"
|
|
713
|
-
});
|
|
714
|
-
systemEvents.emit(
|
|
715
|
-
createSystemEvent("PLAN_ERROR" /* PLAN_ERROR */, {
|
|
716
|
-
error: parseError instanceof Error ? parseError : new Error("Parse error")
|
|
717
|
-
})
|
|
718
|
-
);
|
|
719
|
-
}
|
|
1961
|
+
)
|
|
720
1962
|
}
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
role: "user"
|
|
737
|
-
});
|
|
1963
|
+
);
|
|
1964
|
+
}
|
|
1965
|
+
|
|
1966
|
+
// components/ui/radio-group.tsx
|
|
1967
|
+
init_utils();
|
|
1968
|
+
function RadioGroup({
|
|
1969
|
+
className,
|
|
1970
|
+
...props
|
|
1971
|
+
}) {
|
|
1972
|
+
return /* @__PURE__ */ jsx(
|
|
1973
|
+
RadioGroupPrimitive.Root,
|
|
1974
|
+
{
|
|
1975
|
+
"data-slot": "radio-group",
|
|
1976
|
+
className: cn("grid gap-3", className),
|
|
1977
|
+
...props
|
|
738
1978
|
}
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
1979
|
+
);
|
|
1980
|
+
}
|
|
1981
|
+
function RadioGroupItem({
|
|
1982
|
+
className,
|
|
1983
|
+
...props
|
|
1984
|
+
}) {
|
|
1985
|
+
return /* @__PURE__ */ jsx(
|
|
1986
|
+
RadioGroupPrimitive.Item,
|
|
1987
|
+
{
|
|
1988
|
+
"data-slot": "radio-group-item",
|
|
1989
|
+
className: cn(
|
|
1990
|
+
"border-input text-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 aspect-square size-4 shrink-0 rounded-full border shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
|
|
1991
|
+
className
|
|
1992
|
+
),
|
|
1993
|
+
...props,
|
|
1994
|
+
children: /* @__PURE__ */ jsx(
|
|
1995
|
+
RadioGroupPrimitive.Indicator,
|
|
1996
|
+
{
|
|
1997
|
+
"data-slot": "radio-group-indicator",
|
|
1998
|
+
className: "relative flex items-center justify-center",
|
|
1999
|
+
children: /* @__PURE__ */ jsx(CircleIcon, { className: "fill-primary absolute top-1/2 left-1/2 size-2 -translate-x-1/2 -translate-y-1/2" })
|
|
2000
|
+
}
|
|
2001
|
+
)
|
|
2002
|
+
}
|
|
2003
|
+
);
|
|
745
2004
|
}
|
|
2005
|
+
|
|
2006
|
+
// components/ui/tabs.tsx
|
|
2007
|
+
init_utils();
|
|
2008
|
+
function Tabs({
|
|
2009
|
+
className,
|
|
2010
|
+
...props
|
|
2011
|
+
}) {
|
|
2012
|
+
return /* @__PURE__ */ jsx(
|
|
2013
|
+
TabsPrimitive.Root,
|
|
2014
|
+
{
|
|
2015
|
+
"data-slot": "tabs",
|
|
2016
|
+
className: cn("flex flex-col gap-2", className),
|
|
2017
|
+
...props
|
|
2018
|
+
}
|
|
2019
|
+
);
|
|
2020
|
+
}
|
|
2021
|
+
function TabsList({
|
|
2022
|
+
className,
|
|
2023
|
+
...props
|
|
2024
|
+
}) {
|
|
2025
|
+
return /* @__PURE__ */ jsx(
|
|
2026
|
+
TabsPrimitive.List,
|
|
2027
|
+
{
|
|
2028
|
+
"data-slot": "tabs-list",
|
|
2029
|
+
className: cn(
|
|
2030
|
+
"bg-muted text-muted-foreground inline-flex h-9 w-fit items-center justify-center rounded-lg p-[3px]",
|
|
2031
|
+
className
|
|
2032
|
+
),
|
|
2033
|
+
...props
|
|
2034
|
+
}
|
|
2035
|
+
);
|
|
2036
|
+
}
|
|
2037
|
+
function TabsTrigger({
|
|
2038
|
+
className,
|
|
2039
|
+
...props
|
|
2040
|
+
}) {
|
|
2041
|
+
return /* @__PURE__ */ jsx(
|
|
2042
|
+
TabsPrimitive.Trigger,
|
|
2043
|
+
{
|
|
2044
|
+
"data-slot": "tabs-trigger",
|
|
2045
|
+
className: cn(
|
|
2046
|
+
"data-[state=active]:bg-background dark:data-[state=active]:text-foreground focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 text-foreground dark:text-muted-foreground inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:shadow-sm [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
2047
|
+
className
|
|
2048
|
+
),
|
|
2049
|
+
...props
|
|
2050
|
+
}
|
|
2051
|
+
);
|
|
2052
|
+
}
|
|
2053
|
+
function TabsContent({
|
|
2054
|
+
className,
|
|
2055
|
+
...props
|
|
2056
|
+
}) {
|
|
2057
|
+
return /* @__PURE__ */ jsx(
|
|
2058
|
+
TabsPrimitive.Content,
|
|
2059
|
+
{
|
|
2060
|
+
"data-slot": "tabs-content",
|
|
2061
|
+
className: cn("flex-1 outline-none", className),
|
|
2062
|
+
...props
|
|
2063
|
+
}
|
|
2064
|
+
);
|
|
2065
|
+
}
|
|
2066
|
+
|
|
2067
|
+
// components/ui/label.tsx
|
|
2068
|
+
init_utils();
|
|
2069
|
+
function Label2({
|
|
2070
|
+
className,
|
|
2071
|
+
...props
|
|
2072
|
+
}) {
|
|
2073
|
+
return /* @__PURE__ */ jsx(
|
|
2074
|
+
LabelPrimitive.Root,
|
|
2075
|
+
{
|
|
2076
|
+
"data-slot": "label",
|
|
2077
|
+
className: cn(
|
|
2078
|
+
"flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
|
|
2079
|
+
className
|
|
2080
|
+
),
|
|
2081
|
+
...props
|
|
2082
|
+
}
|
|
2083
|
+
);
|
|
2084
|
+
}
|
|
2085
|
+
var parseStyleString = (styleString) => {
|
|
2086
|
+
if (typeof styleString !== "string") {
|
|
2087
|
+
return typeof styleString === "object" ? styleString : {};
|
|
2088
|
+
}
|
|
2089
|
+
const style = {};
|
|
2090
|
+
styleString.split(";").forEach((declaration) => {
|
|
2091
|
+
const [property, value] = declaration.split(":");
|
|
2092
|
+
if (property && value) {
|
|
2093
|
+
const camelCasedProperty = property.trim().replace(/-([a-z])/g, (g) => g[1].toUpperCase());
|
|
2094
|
+
style[camelCasedProperty] = value.trim();
|
|
2095
|
+
}
|
|
2096
|
+
});
|
|
2097
|
+
return style;
|
|
2098
|
+
};
|
|
2099
|
+
var isArrayOf = (guard) => (arr) => Array.isArray(arr) && arr.every(guard);
|
|
746
2100
|
var ShimmerBlock = () => /* @__PURE__ */ jsx("div", { className: "w-full h-8 bg-gray-200 animate-pulse rounded" });
|
|
747
2101
|
var ShimmerTable = ({ rows = 3 }) => /* @__PURE__ */ jsxs("div", { className: "w-full space-y-2", children: [
|
|
748
2102
|
/* @__PURE__ */ jsx("div", { className: "w-full h-10 bg-gray-200 animate-pulse rounded" }),
|
|
@@ -756,117 +2110,846 @@ var ShimmerCard = () => /* @__PURE__ */ jsxs("div", { className: "w-full p-4 spa
|
|
|
756
2110
|
/* @__PURE__ */ jsx("div", { className: "w-5/6 h-4 bg-gray-200 animate-pulse rounded" })
|
|
757
2111
|
] })
|
|
758
2112
|
] });
|
|
759
|
-
var Container = (props) => /* @__PURE__ */ jsx("div", { className: `
|
|
760
|
-
var Header = ({
|
|
2113
|
+
var Container = (props) => /* @__PURE__ */ jsx("div", { className: `autoui-mock-container ${props.className || ""}`, children: props.children });
|
|
2114
|
+
var Header = ({
|
|
2115
|
+
title,
|
|
2116
|
+
className
|
|
2117
|
+
}) => /* @__PURE__ */ jsx(
|
|
2118
|
+
"header",
|
|
2119
|
+
{
|
|
2120
|
+
className: `py-4 px-6 border-b border-gray-300 mb-4 bg-gray-50 dark:bg-gray-800 ${className || ""}`,
|
|
2121
|
+
children: /* @__PURE__ */ jsx("h1", { className: "text-xl font-semibold text-gray-800 dark:text-white", children: title })
|
|
2122
|
+
}
|
|
2123
|
+
);
|
|
761
2124
|
var Button = ({ onClick, children, variant = "default" }) => /* @__PURE__ */ jsx(
|
|
762
2125
|
"button",
|
|
763
2126
|
{
|
|
764
|
-
className: `px-4 py-2 rounded font-medium ${variant === "default" ? "bg-blue-600 text-white" : variant === "outline" ? "border border-gray-300 text-gray-700" : "bg-red-600 text-white"}`,
|
|
765
|
-
onClick,
|
|
2127
|
+
className: `px-4 py-2 rounded-md font-medium transition-colors ${variant === "default" ? "bg-blue-600 text-white hover:bg-blue-700" : variant === "outline" ? "border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-800" : "bg-red-600 text-white hover:bg-red-700"}`,
|
|
2128
|
+
onClick: () => onClick?.(),
|
|
766
2129
|
children
|
|
767
2130
|
}
|
|
768
2131
|
);
|
|
769
|
-
var Table = ({ items = [], fields = [], onSelect, selectable }) => /* @__PURE__ */ jsx("div", { className: "w-full border rounded-lg overflow-hidden", children: /* @__PURE__ */ jsxs("table", { className: "w-full", children: [
|
|
770
|
-
/* @__PURE__ */ jsx("thead", { className: "bg-gray-50", children: /* @__PURE__ */ jsx("tr", { children: fields.map((field) => /* @__PURE__ */ jsx("th", { className: "px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider", children: field.label }, field.key)) }) }),
|
|
771
|
-
/* @__PURE__ */ jsx("tbody", { className: "bg-white divide-y divide-gray-200", children: items.map((item, index) => /* @__PURE__ */ jsx(
|
|
772
|
-
"tr",
|
|
773
|
-
{
|
|
774
|
-
onClick: () => selectable && onSelect && onSelect(item),
|
|
775
|
-
className: selectable ? "cursor-pointer hover:bg-gray-50" : "",
|
|
776
|
-
children: fields.map((field) => /* @__PURE__ */ jsx("td", { className: "px-6 py-4 whitespace-nowrap text-sm text-gray-500", children: item[field.key] }, field.key))
|
|
777
|
-
},
|
|
778
|
-
index
|
|
779
|
-
)) })
|
|
780
|
-
] }) });
|
|
781
2132
|
var Detail = ({ data, fields = [], title, visible = true, onBack }) => {
|
|
782
|
-
if (!visible)
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
2133
|
+
if (!visible) {
|
|
2134
|
+
console.log(
|
|
2135
|
+
`[Detail Component Internal] Detail component for node id: ${data?.id || title || "Unknown Detail"} is NOT RENDERING because visible is ${visible}.`
|
|
2136
|
+
);
|
|
2137
|
+
return null;
|
|
2138
|
+
}
|
|
2139
|
+
console.log(
|
|
2140
|
+
`[Detail Component Internal] Detail component for node id: ${data?.id || title || "Unknown Detail"} IS RENDERING because visible is ${visible}.`
|
|
2141
|
+
);
|
|
2142
|
+
return /* @__PURE__ */ jsxs("div", { className: "w-full border border-gray-300 dark:border-gray-700 rounded-lg p-6 space-y-4 bg-white dark:bg-gray-900 shadow-sm", children: [
|
|
2143
|
+
/* @__PURE__ */ jsxs("div", { className: "flex justify-between items-center border-b border-gray-200 dark:border-gray-700 pb-3", children: [
|
|
2144
|
+
title && /* @__PURE__ */ jsx("h2", { className: "text-lg font-medium text-gray-800 dark:text-white", children: title }),
|
|
786
2145
|
onBack && /* @__PURE__ */ jsx(Button, { variant: "outline", onClick: onBack, children: "Back" })
|
|
787
2146
|
] }),
|
|
788
2147
|
/* @__PURE__ */ jsx("div", { className: "space-y-4", children: fields.map((field) => {
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
2148
|
+
try {
|
|
2149
|
+
if (field.type === "heading") {
|
|
2150
|
+
return /* @__PURE__ */ jsx(
|
|
2151
|
+
"h3",
|
|
2152
|
+
{
|
|
2153
|
+
className: "text-xl font-semibold text-gray-800 dark:text-white",
|
|
2154
|
+
children: data?.[field.key] ?? ""
|
|
2155
|
+
},
|
|
2156
|
+
field.key
|
|
2157
|
+
);
|
|
2158
|
+
}
|
|
2159
|
+
if (field.type === "content") {
|
|
2160
|
+
return /* @__PURE__ */ jsx(
|
|
2161
|
+
"div",
|
|
2162
|
+
{
|
|
2163
|
+
className: "text-sm text-gray-700 dark:text-gray-300",
|
|
2164
|
+
children: data?.[field.key] ?? ""
|
|
2165
|
+
},
|
|
2166
|
+
field.key
|
|
2167
|
+
);
|
|
2168
|
+
}
|
|
2169
|
+
return /* @__PURE__ */ jsxs(
|
|
2170
|
+
"div",
|
|
2171
|
+
{
|
|
2172
|
+
className: "flex flex-col border-b border-gray-100 dark:border-gray-800 py-2",
|
|
2173
|
+
children: [
|
|
2174
|
+
field.label && /* @__PURE__ */ jsx("span", { className: "text-xs text-gray-600 dark:text-gray-400 font-medium", children: field.label }),
|
|
2175
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm text-gray-800 dark:text-gray-200", children: data?.[field.key] ?? "" })
|
|
2176
|
+
]
|
|
2177
|
+
},
|
|
2178
|
+
field.key
|
|
2179
|
+
);
|
|
2180
|
+
} catch (e) {
|
|
2181
|
+
console.error(
|
|
2182
|
+
`[Detail Component Internal] Error rendering field: ${field?.key}`,
|
|
2183
|
+
e,
|
|
2184
|
+
"Field data:",
|
|
2185
|
+
field,
|
|
2186
|
+
"Full data object:",
|
|
2187
|
+
data
|
|
2188
|
+
);
|
|
2189
|
+
return /* @__PURE__ */ jsxs("div", { children: [
|
|
2190
|
+
"Error rendering field: ",
|
|
2191
|
+
field?.key
|
|
2192
|
+
] }, field?.key || "error-field");
|
|
794
2193
|
}
|
|
795
|
-
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col", children: [
|
|
796
|
-
field.label && /* @__PURE__ */ jsx("span", { className: "text-xs text-gray-500", children: field.label }),
|
|
797
|
-
/* @__PURE__ */ jsx("span", { className: "text-sm", children: data?.[field.key] })
|
|
798
|
-
] }, field.key);
|
|
799
2194
|
}) })
|
|
800
2195
|
] });
|
|
801
2196
|
};
|
|
802
|
-
var
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
2197
|
+
var getSafeProp = (props, key, validator, defaultValue) => {
|
|
2198
|
+
if (props && typeof props === "object" && key in props) {
|
|
2199
|
+
const value = props[key];
|
|
2200
|
+
if (validator(value)) {
|
|
2201
|
+
return value;
|
|
2202
|
+
}
|
|
2203
|
+
}
|
|
2204
|
+
return defaultValue;
|
|
2205
|
+
};
|
|
2206
|
+
var isObject = (value) => typeof value === "object" && value !== null;
|
|
2207
|
+
var isString = (value) => typeof value === "string";
|
|
2208
|
+
var isBoolean = (value) => typeof value === "boolean";
|
|
2209
|
+
var isButtonVariant = (value) => isString(value) && ["default", "outline", "destructive"].includes(value);
|
|
2210
|
+
var getSafeBinding = (bindings, key, validator, defaultValue) => {
|
|
2211
|
+
if (bindings && typeof bindings === "object" && key in bindings) {
|
|
2212
|
+
const value = bindings[key];
|
|
2213
|
+
if (validator(value)) {
|
|
2214
|
+
return value;
|
|
2215
|
+
}
|
|
2216
|
+
}
|
|
2217
|
+
return defaultValue;
|
|
2218
|
+
};
|
|
2219
|
+
var isReactNode = (value) => {
|
|
2220
|
+
return typeof value === "string" || typeof value === "number" || typeof value === "boolean" || value === null || typeof value === "undefined" || typeof value === "object" && value !== null && "$$typeof" in value;
|
|
2221
|
+
};
|
|
2222
|
+
var isRecordWithReactNodeValues = (value) => isObject(value) && Object.values(value).every(isReactNode);
|
|
2223
|
+
var isSelectOptionObject = (item) => isObject(item) && isString(item.value) && isString(item.label);
|
|
2224
|
+
var isTabObject = (item) => (
|
|
2225
|
+
// Allow content to be optional initially
|
|
2226
|
+
isObject(item) && isString(item.value) && isString(item.label) && (item.content === void 0 || isUISpecNode(item.content))
|
|
2227
|
+
);
|
|
2228
|
+
var isUISpecNode = (value) => {
|
|
2229
|
+
if (!isObject(value))
|
|
2230
|
+
return false;
|
|
2231
|
+
return isString(value.id) && isString(value.node_type);
|
|
2232
|
+
};
|
|
2233
|
+
var isDetailFieldObject = (item) => isObject(item) && isString(item.key) && isString(item.label) && (item.type === void 0 || isString(item.type));
|
|
2234
|
+
var createEventHandler = (node, eventName, uiEventType2, processEvent) => {
|
|
2235
|
+
const eventConfig = node.events?.[uiEventType2];
|
|
2236
|
+
if (!processEvent || !eventConfig)
|
|
2237
|
+
return void 0;
|
|
2238
|
+
return (eventPayload) => {
|
|
2239
|
+
const fullEvent = {
|
|
2240
|
+
type: uiEventType2,
|
|
2241
|
+
nodeId: node.id,
|
|
2242
|
+
timestamp: Date.now(),
|
|
2243
|
+
payload: {
|
|
2244
|
+
...eventConfig.payload || {},
|
|
2245
|
+
...eventPayload || {}
|
|
2246
|
+
}
|
|
2247
|
+
};
|
|
2248
|
+
processEvent(fullEvent);
|
|
811
2249
|
};
|
|
812
2250
|
};
|
|
813
2251
|
var adapterMap = {
|
|
814
|
-
Container: (node
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
2252
|
+
Container: (node, processEvent) => {
|
|
2253
|
+
const { className, style: styleProp, key, ...restProps } = node.props || {};
|
|
2254
|
+
const children = node.children?.map(
|
|
2255
|
+
(child) => (
|
|
2256
|
+
// Use React.cloneElement to add the key prop to the element returned by renderNode
|
|
2257
|
+
React.cloneElement(renderNode(child, processEvent), { key: child.id })
|
|
2258
|
+
)
|
|
2259
|
+
);
|
|
2260
|
+
const style = typeof styleProp === "string" ? parseStyleString(styleProp) : styleProp;
|
|
2261
|
+
return /* @__PURE__ */ jsxs(
|
|
2262
|
+
"div",
|
|
2263
|
+
{
|
|
2264
|
+
className: cn("autoui-container", className),
|
|
2265
|
+
style,
|
|
2266
|
+
...restProps,
|
|
2267
|
+
"data-id": node.id,
|
|
2268
|
+
children: [
|
|
2269
|
+
(() => {
|
|
2270
|
+
console.log(
|
|
2271
|
+
`[Adapter Debug] Rendering Container: id=${node.id}, props=`,
|
|
2272
|
+
node.props
|
|
2273
|
+
);
|
|
2274
|
+
return null;
|
|
2275
|
+
})(),
|
|
2276
|
+
children
|
|
2277
|
+
]
|
|
2278
|
+
},
|
|
2279
|
+
key
|
|
2280
|
+
);
|
|
2281
|
+
},
|
|
2282
|
+
Header: (node) => /* @__PURE__ */ jsx(
|
|
2283
|
+
Header,
|
|
818
2284
|
{
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
children: node.props?.label || "Button"
|
|
2285
|
+
title: getSafeProp(node.props, "title", isString, "Untitled"),
|
|
2286
|
+
className: getSafeProp(node.props, "className", isString, "")
|
|
822
2287
|
}
|
|
823
2288
|
),
|
|
824
|
-
|
|
825
|
-
|
|
2289
|
+
Button: (node, processEvent) => /* @__PURE__ */ jsx(
|
|
2290
|
+
Button,
|
|
826
2291
|
{
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
onSelect: createEventHandler(node, "onSelect")
|
|
2292
|
+
variant: getSafeProp(node.props, "variant", isButtonVariant, "default"),
|
|
2293
|
+
onClick: createEventHandler(node, "onClick", "CLICK", processEvent),
|
|
2294
|
+
children: getSafeProp(node.props, "label", isString, "Button")
|
|
831
2295
|
}
|
|
832
2296
|
),
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
2297
|
+
ListView: (node, processEvent) => {
|
|
2298
|
+
const {
|
|
2299
|
+
className,
|
|
2300
|
+
style: styleProp,
|
|
2301
|
+
key,
|
|
2302
|
+
// Exclude data prop (array) from being spread onto the DOM element
|
|
2303
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2304
|
+
data: _data,
|
|
2305
|
+
...restProps
|
|
2306
|
+
} = node.props || {};
|
|
2307
|
+
const style = typeof styleProp === "string" ? parseStyleString(styleProp) : styleProp;
|
|
2308
|
+
console.log(
|
|
2309
|
+
`[Adapter Debug] Rendering ListView: id=${node.id}, props=`,
|
|
2310
|
+
node.props
|
|
2311
|
+
);
|
|
2312
|
+
const children = node.children?.map(
|
|
2313
|
+
(child) => React.cloneElement(renderNode(child, processEvent), { key: child.id })
|
|
2314
|
+
);
|
|
2315
|
+
return /* @__PURE__ */ jsx(
|
|
2316
|
+
"div",
|
|
2317
|
+
{
|
|
2318
|
+
className: cn(
|
|
2319
|
+
"autoui-listview-container space-y-2",
|
|
2320
|
+
className
|
|
2321
|
+
),
|
|
2322
|
+
style,
|
|
2323
|
+
...restProps,
|
|
2324
|
+
"data-id": node.id,
|
|
2325
|
+
children
|
|
2326
|
+
},
|
|
2327
|
+
key
|
|
2328
|
+
);
|
|
2329
|
+
},
|
|
2330
|
+
Detail: (node, processEvent) => {
|
|
2331
|
+
const data = getSafeProp(
|
|
2332
|
+
node.props,
|
|
2333
|
+
"data",
|
|
2334
|
+
isRecordWithReactNodeValues,
|
|
2335
|
+
{}
|
|
2336
|
+
);
|
|
2337
|
+
const fields = getSafeProp(
|
|
2338
|
+
node.props,
|
|
2339
|
+
"fields",
|
|
2340
|
+
isArrayOf(isDetailFieldObject),
|
|
2341
|
+
[]
|
|
2342
|
+
);
|
|
2343
|
+
const title = getSafeProp(node.props, "title", isString, "");
|
|
2344
|
+
const visible = getSafeProp(node.props, "visible", isBoolean, true);
|
|
2345
|
+
console.log(
|
|
2346
|
+
`[Adapter Debug] Rendering Detail: id=${node.id}, props=`,
|
|
2347
|
+
JSON.stringify(node.props),
|
|
2348
|
+
`effective visible=${visible}`,
|
|
2349
|
+
`typeof fields=${typeof fields}`,
|
|
2350
|
+
`Array.isArray(fields)=${Array.isArray(fields)}`
|
|
2351
|
+
);
|
|
2352
|
+
return /* @__PURE__ */ jsx(
|
|
2353
|
+
Detail,
|
|
2354
|
+
{
|
|
2355
|
+
data,
|
|
2356
|
+
fields,
|
|
2357
|
+
title,
|
|
2358
|
+
visible,
|
|
2359
|
+
onBack: createEventHandler(node, "onBack", "CLICK", processEvent)
|
|
2360
|
+
}
|
|
2361
|
+
);
|
|
2362
|
+
},
|
|
2363
|
+
Card: (node, processEvent) => {
|
|
2364
|
+
const { className, style: styleProp, key, ...restProps } = node.props || {};
|
|
2365
|
+
const children = node.children?.map(
|
|
2366
|
+
(child) => React.cloneElement(renderNode(child, processEvent), { key: child.id })
|
|
2367
|
+
);
|
|
2368
|
+
const style = typeof styleProp === "string" ? parseStyleString(styleProp) : styleProp;
|
|
2369
|
+
return /* @__PURE__ */ jsx(
|
|
2370
|
+
Card,
|
|
2371
|
+
{
|
|
2372
|
+
className: cn("autoui-card", className),
|
|
2373
|
+
style,
|
|
2374
|
+
...restProps,
|
|
2375
|
+
"data-id": node.id,
|
|
2376
|
+
children: /* @__PURE__ */ jsxs(CardContent, { className: "p-0", children: [
|
|
2377
|
+
" ",
|
|
2378
|
+
children
|
|
2379
|
+
] })
|
|
2380
|
+
},
|
|
2381
|
+
key
|
|
2382
|
+
);
|
|
2383
|
+
},
|
|
2384
|
+
Input: (node, processEvent) => {
|
|
2385
|
+
const name = getSafeProp(node.props, "name", isString, "inputName");
|
|
2386
|
+
const label = getSafeProp(node.props, "label", isString, "");
|
|
2387
|
+
const value = getSafeBinding(node.bindings, "value", isString, "");
|
|
2388
|
+
const placeholder = getSafeProp(node.props, "placeholder", isString, "");
|
|
2389
|
+
const disabled = getSafeProp(node.props, "disabled", isBoolean, false);
|
|
2390
|
+
const className = getSafeProp(node.props, "className", isString, "");
|
|
2391
|
+
const handleChange = (e) => {
|
|
2392
|
+
const handler = createEventHandler(
|
|
2393
|
+
node,
|
|
2394
|
+
"onChange",
|
|
2395
|
+
"CHANGE",
|
|
2396
|
+
processEvent
|
|
2397
|
+
);
|
|
2398
|
+
if (handler)
|
|
2399
|
+
handler({ value: e.target.value });
|
|
2400
|
+
};
|
|
2401
|
+
const handleFocus = () => {
|
|
2402
|
+
const handler = createEventHandler(
|
|
2403
|
+
node,
|
|
2404
|
+
"onFocus",
|
|
2405
|
+
"FOCUS",
|
|
2406
|
+
processEvent
|
|
2407
|
+
);
|
|
2408
|
+
if (handler)
|
|
2409
|
+
handler({});
|
|
2410
|
+
};
|
|
2411
|
+
const handleBlur = () => {
|
|
2412
|
+
const handler = createEventHandler(node, "onBlur", "BLUR", processEvent);
|
|
2413
|
+
if (handler)
|
|
2414
|
+
handler({});
|
|
2415
|
+
};
|
|
2416
|
+
return /* @__PURE__ */ jsxs("div", { className: "grid w-full max-w-sm items-center gap-1.5", children: [
|
|
2417
|
+
label && /* @__PURE__ */ jsx(Label2, { htmlFor: name, children: label }),
|
|
2418
|
+
/* @__PURE__ */ jsx(
|
|
2419
|
+
Input,
|
|
2420
|
+
{
|
|
2421
|
+
id: name,
|
|
2422
|
+
name,
|
|
2423
|
+
placeholder,
|
|
2424
|
+
disabled,
|
|
2425
|
+
value,
|
|
2426
|
+
onChange: handleChange,
|
|
2427
|
+
onFocus: handleFocus,
|
|
2428
|
+
onBlur: handleBlur,
|
|
2429
|
+
className
|
|
2430
|
+
}
|
|
2431
|
+
)
|
|
2432
|
+
] });
|
|
2433
|
+
},
|
|
2434
|
+
Select: (node, processEvent) => {
|
|
2435
|
+
const name = getSafeProp(node.props, "name", isString, "selectName");
|
|
2436
|
+
const label = getSafeProp(node.props, "label", isString, "");
|
|
2437
|
+
const placeholder = getSafeProp(
|
|
2438
|
+
node.props,
|
|
2439
|
+
"placeholder",
|
|
2440
|
+
isString,
|
|
2441
|
+
"Select..."
|
|
2442
|
+
);
|
|
2443
|
+
const disabled = getSafeProp(node.props, "disabled", isBoolean, false);
|
|
2444
|
+
const value = getSafeBinding(node.bindings, "value", isString, "");
|
|
2445
|
+
const options = getSafeBinding(
|
|
2446
|
+
node.bindings,
|
|
2447
|
+
"options",
|
|
2448
|
+
isArrayOf(isSelectOptionObject),
|
|
2449
|
+
[]
|
|
2450
|
+
);
|
|
2451
|
+
const className = getSafeProp(node.props, "className", isString, "");
|
|
2452
|
+
const handleValueChange = (selectedValue) => {
|
|
2453
|
+
const handler = createEventHandler(
|
|
2454
|
+
node,
|
|
2455
|
+
"onValueChange",
|
|
2456
|
+
"CHANGE",
|
|
2457
|
+
processEvent
|
|
2458
|
+
);
|
|
2459
|
+
if (handler)
|
|
2460
|
+
handler({ value: selectedValue });
|
|
2461
|
+
};
|
|
2462
|
+
return /* @__PURE__ */ jsxs(
|
|
2463
|
+
"div",
|
|
2464
|
+
{
|
|
2465
|
+
className: cn("grid w-full max-w-sm items-center gap-1.5", className),
|
|
2466
|
+
children: [
|
|
2467
|
+
label && /* @__PURE__ */ jsx(Label2, { htmlFor: name, children: label }),
|
|
2468
|
+
/* @__PURE__ */ jsxs(
|
|
2469
|
+
Select,
|
|
2470
|
+
{
|
|
2471
|
+
name,
|
|
2472
|
+
value,
|
|
2473
|
+
onValueChange: handleValueChange,
|
|
2474
|
+
disabled,
|
|
2475
|
+
children: [
|
|
2476
|
+
/* @__PURE__ */ jsx(SelectTrigger, { id: name, children: /* @__PURE__ */ jsx(SelectValue, { placeholder }) }),
|
|
2477
|
+
/* @__PURE__ */ jsx(SelectContent, { children: options.map((option) => /* @__PURE__ */ jsx(SelectItem, { value: option.value, children: option.label }, option.value)) })
|
|
2478
|
+
]
|
|
2479
|
+
}
|
|
2480
|
+
)
|
|
2481
|
+
]
|
|
2482
|
+
}
|
|
2483
|
+
);
|
|
2484
|
+
},
|
|
2485
|
+
Textarea: (node, processEvent) => {
|
|
2486
|
+
const { key, ...propsWithoutKey } = node.props || {};
|
|
2487
|
+
const name = getSafeProp(propsWithoutKey, "name", isString, "textareaName");
|
|
2488
|
+
const label = getSafeProp(propsWithoutKey, "label", isString, "");
|
|
2489
|
+
const placeholder = getSafeProp(
|
|
2490
|
+
propsWithoutKey,
|
|
2491
|
+
"placeholder",
|
|
2492
|
+
isString,
|
|
2493
|
+
""
|
|
2494
|
+
);
|
|
2495
|
+
const disabled = getSafeProp(propsWithoutKey, "disabled", isBoolean, false);
|
|
2496
|
+
const rows = getSafeProp(
|
|
2497
|
+
propsWithoutKey,
|
|
2498
|
+
"rows",
|
|
2499
|
+
(v) => typeof v === "number",
|
|
2500
|
+
3
|
|
2501
|
+
);
|
|
2502
|
+
const value = getSafeBinding(node.bindings, "value", isString, "");
|
|
2503
|
+
const className = getSafeProp(propsWithoutKey, "className", isString, "");
|
|
2504
|
+
const handleChange = (e) => {
|
|
2505
|
+
const handler = createEventHandler(
|
|
2506
|
+
node,
|
|
2507
|
+
"onChange",
|
|
2508
|
+
"CHANGE",
|
|
2509
|
+
processEvent
|
|
2510
|
+
);
|
|
2511
|
+
if (handler)
|
|
2512
|
+
handler({ value: e.target.value });
|
|
2513
|
+
};
|
|
2514
|
+
const handleFocus = () => {
|
|
2515
|
+
const handler = createEventHandler(
|
|
2516
|
+
node,
|
|
2517
|
+
"onFocus",
|
|
2518
|
+
"FOCUS",
|
|
2519
|
+
processEvent
|
|
2520
|
+
);
|
|
2521
|
+
if (handler)
|
|
2522
|
+
handler({});
|
|
2523
|
+
};
|
|
2524
|
+
const handleBlur = () => {
|
|
2525
|
+
const handler = createEventHandler(node, "onBlur", "BLUR", processEvent);
|
|
2526
|
+
if (handler)
|
|
2527
|
+
handler({});
|
|
2528
|
+
};
|
|
2529
|
+
return /* @__PURE__ */ jsxs("div", { className: "grid w-full gap-1.5", children: [
|
|
2530
|
+
label && /* @__PURE__ */ jsx(Label2, { htmlFor: name, children: label }),
|
|
2531
|
+
/* @__PURE__ */ jsx(
|
|
2532
|
+
Textarea,
|
|
2533
|
+
{
|
|
2534
|
+
id: name,
|
|
2535
|
+
name,
|
|
2536
|
+
placeholder,
|
|
2537
|
+
disabled,
|
|
2538
|
+
rows,
|
|
2539
|
+
value,
|
|
2540
|
+
onChange: handleChange,
|
|
2541
|
+
onFocus: handleFocus,
|
|
2542
|
+
onBlur: handleBlur,
|
|
2543
|
+
className
|
|
2544
|
+
}
|
|
2545
|
+
)
|
|
2546
|
+
] }, key);
|
|
2547
|
+
},
|
|
2548
|
+
Checkbox: (node, processEvent) => {
|
|
2549
|
+
const { key, ...propsWithoutKey } = node.props || {};
|
|
2550
|
+
const name = getSafeProp(propsWithoutKey, "name", isString, "checkboxName");
|
|
2551
|
+
const label = getSafeProp(propsWithoutKey, "label", isString, "");
|
|
2552
|
+
const checked = getSafeBinding(node.bindings, "checked", isBoolean, false);
|
|
2553
|
+
const disabled = getSafeProp(propsWithoutKey, "disabled", isBoolean, false);
|
|
2554
|
+
const className = getSafeProp(propsWithoutKey, "className", isString, "");
|
|
2555
|
+
const handleCheckedChange = (isChecked) => {
|
|
2556
|
+
if (typeof isChecked === "boolean") {
|
|
2557
|
+
const handler = createEventHandler(
|
|
2558
|
+
node,
|
|
2559
|
+
"onCheckedChange",
|
|
2560
|
+
"CHANGE",
|
|
2561
|
+
processEvent
|
|
2562
|
+
);
|
|
2563
|
+
if (handler)
|
|
2564
|
+
handler({ checked: isChecked });
|
|
2565
|
+
}
|
|
2566
|
+
};
|
|
2567
|
+
return /* @__PURE__ */ jsxs(
|
|
2568
|
+
"div",
|
|
2569
|
+
{
|
|
2570
|
+
className: cn("flex items-center space-x-2", className),
|
|
2571
|
+
children: [
|
|
2572
|
+
/* @__PURE__ */ jsx(
|
|
2573
|
+
Checkbox,
|
|
2574
|
+
{
|
|
2575
|
+
id: name,
|
|
2576
|
+
name,
|
|
2577
|
+
checked,
|
|
2578
|
+
disabled,
|
|
2579
|
+
onCheckedChange: handleCheckedChange
|
|
2580
|
+
}
|
|
2581
|
+
),
|
|
2582
|
+
label && /* @__PURE__ */ jsx(Label2, { htmlFor: name, className: "cursor-pointer", children: label })
|
|
2583
|
+
]
|
|
2584
|
+
},
|
|
2585
|
+
key
|
|
2586
|
+
);
|
|
2587
|
+
},
|
|
2588
|
+
RadioGroup: (node, processEvent) => {
|
|
2589
|
+
const { key, ...propsWithoutKey } = node.props || {};
|
|
2590
|
+
const name = getSafeProp(
|
|
2591
|
+
propsWithoutKey,
|
|
2592
|
+
"name",
|
|
2593
|
+
isString,
|
|
2594
|
+
"radioGroupName"
|
|
2595
|
+
);
|
|
2596
|
+
const label = getSafeProp(propsWithoutKey, "label", isString, "");
|
|
2597
|
+
const value = getSafeBinding(node.bindings, "value", isString, "");
|
|
2598
|
+
const options = getSafeBinding(
|
|
2599
|
+
node.bindings,
|
|
2600
|
+
"options",
|
|
2601
|
+
isArrayOf(isSelectOptionObject),
|
|
2602
|
+
[]
|
|
2603
|
+
);
|
|
2604
|
+
const disabled = getSafeProp(propsWithoutKey, "disabled", isBoolean, false);
|
|
2605
|
+
const className = getSafeProp(propsWithoutKey, "className", isString, "");
|
|
2606
|
+
const handleValueChange = (selectedValue) => {
|
|
2607
|
+
const handler = createEventHandler(
|
|
2608
|
+
node,
|
|
2609
|
+
"onValueChange",
|
|
2610
|
+
"CHANGE",
|
|
2611
|
+
processEvent
|
|
2612
|
+
);
|
|
2613
|
+
if (handler)
|
|
2614
|
+
handler({ value: selectedValue });
|
|
2615
|
+
};
|
|
2616
|
+
return /* @__PURE__ */ jsxs(
|
|
2617
|
+
"div",
|
|
2618
|
+
{
|
|
2619
|
+
className: cn("grid gap-1.5", className),
|
|
2620
|
+
children: [
|
|
2621
|
+
label && /* @__PURE__ */ jsx(Label2, { className: "mb-1", children: label }),
|
|
2622
|
+
/* @__PURE__ */ jsx(
|
|
2623
|
+
RadioGroup,
|
|
2624
|
+
{
|
|
2625
|
+
name,
|
|
2626
|
+
value,
|
|
2627
|
+
onValueChange: handleValueChange,
|
|
2628
|
+
disabled,
|
|
2629
|
+
className: "flex flex-col space-y-1",
|
|
2630
|
+
children: options.map((option) => /* @__PURE__ */ jsxs("div", { className: "flex items-center space-x-2", children: [
|
|
2631
|
+
/* @__PURE__ */ jsx(
|
|
2632
|
+
RadioGroupItem,
|
|
2633
|
+
{
|
|
2634
|
+
value: option.value,
|
|
2635
|
+
id: `${name}-${option.value}`
|
|
2636
|
+
}
|
|
2637
|
+
),
|
|
2638
|
+
/* @__PURE__ */ jsx(
|
|
2639
|
+
Label2,
|
|
2640
|
+
{
|
|
2641
|
+
htmlFor: `${name}-${option.value}`,
|
|
2642
|
+
className: "cursor-pointer",
|
|
2643
|
+
children: option.label
|
|
2644
|
+
}
|
|
2645
|
+
)
|
|
2646
|
+
] }, option.value))
|
|
2647
|
+
}
|
|
2648
|
+
)
|
|
2649
|
+
]
|
|
2650
|
+
},
|
|
2651
|
+
key
|
|
2652
|
+
);
|
|
2653
|
+
},
|
|
2654
|
+
Tabs: (node, processEvent) => {
|
|
2655
|
+
const { key, ...propsWithoutKey } = node.props || {};
|
|
2656
|
+
const rawTabs = getSafeBinding(
|
|
2657
|
+
node.bindings,
|
|
2658
|
+
"tabs",
|
|
2659
|
+
isArrayOf(isTabObject),
|
|
2660
|
+
[]
|
|
2661
|
+
);
|
|
2662
|
+
const defaultValue = getSafeProp(
|
|
2663
|
+
propsWithoutKey,
|
|
2664
|
+
"defaultValue",
|
|
2665
|
+
isString,
|
|
2666
|
+
rawTabs[0]?.value || ""
|
|
2667
|
+
);
|
|
2668
|
+
const className = getSafeProp(propsWithoutKey, "className", isString, "");
|
|
2669
|
+
const handleValueChange = (value) => {
|
|
2670
|
+
const handler = createEventHandler(
|
|
2671
|
+
node,
|
|
2672
|
+
"onValueChange",
|
|
2673
|
+
"CHANGE",
|
|
2674
|
+
processEvent
|
|
2675
|
+
);
|
|
2676
|
+
if (handler)
|
|
2677
|
+
handler({ value });
|
|
2678
|
+
};
|
|
2679
|
+
return /* @__PURE__ */ jsxs(
|
|
2680
|
+
Tabs,
|
|
2681
|
+
{
|
|
2682
|
+
defaultValue,
|
|
2683
|
+
onValueChange: handleValueChange,
|
|
2684
|
+
className: cn("autoui-tabs w-full", className),
|
|
2685
|
+
"data-id": node.id,
|
|
2686
|
+
children: [
|
|
2687
|
+
/* @__PURE__ */ jsx(TabsList, { children: rawTabs.map((tab) => /* @__PURE__ */ jsx(TabsTrigger, { value: tab.value, children: tab.label }, tab.value)) }),
|
|
2688
|
+
rawTabs.map((tab) => /* @__PURE__ */ jsx(TabsContent, { value: tab.value, children: tab.content ? renderNode(tab.content, processEvent) : null }, tab.value))
|
|
2689
|
+
]
|
|
2690
|
+
},
|
|
2691
|
+
key
|
|
2692
|
+
);
|
|
2693
|
+
},
|
|
2694
|
+
Dialog: (node, processEvent) => {
|
|
2695
|
+
const isOpen = getSafeBinding(
|
|
2696
|
+
node.bindings,
|
|
2697
|
+
"visible",
|
|
2698
|
+
isBoolean,
|
|
2699
|
+
// Check bindings.visible (planner output)
|
|
2700
|
+
getSafeProp(
|
|
2701
|
+
node.props,
|
|
2702
|
+
"open",
|
|
2703
|
+
isBoolean,
|
|
2704
|
+
// Then props.open (if binding resolution put it there)
|
|
2705
|
+
getSafeProp(node.props, "visible", isBoolean, false)
|
|
2706
|
+
// Then props.visible (if binding resolution put it there under 'visible')
|
|
2707
|
+
)
|
|
2708
|
+
);
|
|
2709
|
+
const {
|
|
2710
|
+
title,
|
|
2711
|
+
description,
|
|
2712
|
+
className,
|
|
2713
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2714
|
+
style: _styleProp,
|
|
2715
|
+
key,
|
|
2716
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2717
|
+
open: _openProp,
|
|
2718
|
+
// ADD 'visible' to destructuring to prevent it from going into restProps
|
|
2719
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2720
|
+
visible: _visibleProp,
|
|
2721
|
+
...restProps
|
|
2722
|
+
} = node.props || {};
|
|
2723
|
+
const children = node.children?.map(
|
|
2724
|
+
(child) => React.cloneElement(renderNode(child, processEvent), { key: child.id })
|
|
2725
|
+
);
|
|
2726
|
+
const handleOpenChange = (open) => {
|
|
2727
|
+
if (!open) {
|
|
2728
|
+
const handler = createEventHandler(
|
|
2729
|
+
node,
|
|
2730
|
+
"onClose",
|
|
2731
|
+
// Assumed event name in UISpec
|
|
2732
|
+
"CLICK",
|
|
2733
|
+
// Use CLICK as the event type for closing dialogs
|
|
2734
|
+
processEvent
|
|
2735
|
+
);
|
|
2736
|
+
if (handler) {
|
|
2737
|
+
handler({});
|
|
2738
|
+
}
|
|
2739
|
+
}
|
|
2740
|
+
};
|
|
2741
|
+
console.log(
|
|
2742
|
+
`[Adapter Debug] Rendering Dialog: id=${node.id}, props=`,
|
|
2743
|
+
node.props,
|
|
2744
|
+
`isOpen=${isOpen}`
|
|
2745
|
+
);
|
|
2746
|
+
return /* @__PURE__ */ jsx(
|
|
2747
|
+
Dialog,
|
|
2748
|
+
{
|
|
2749
|
+
open: isOpen,
|
|
2750
|
+
onOpenChange: handleOpenChange,
|
|
2751
|
+
children: /* @__PURE__ */ jsxs(
|
|
2752
|
+
DialogContent,
|
|
2753
|
+
{
|
|
2754
|
+
className: cn("autoui-dialog-content", className),
|
|
2755
|
+
...restProps,
|
|
2756
|
+
"data-id": node.id,
|
|
2757
|
+
children: [
|
|
2758
|
+
(title || description) && /* @__PURE__ */ jsxs(DialogHeader, { children: [
|
|
2759
|
+
title && /* @__PURE__ */ jsx(DialogTitle, { children: title }),
|
|
2760
|
+
description && /* @__PURE__ */ jsx(DialogDescription, { children: description })
|
|
2761
|
+
] }),
|
|
2762
|
+
children
|
|
2763
|
+
]
|
|
2764
|
+
}
|
|
2765
|
+
)
|
|
2766
|
+
},
|
|
2767
|
+
key
|
|
2768
|
+
);
|
|
2769
|
+
},
|
|
2770
|
+
Heading: (node) => {
|
|
2771
|
+
const { className, style: styleProp, key, ...restProps } = node.props || {};
|
|
2772
|
+
const text = getSafeProp(node.props, "text", isString, "Heading");
|
|
2773
|
+
let level = getSafeProp(
|
|
2774
|
+
node.props,
|
|
2775
|
+
"level",
|
|
2776
|
+
(v) => typeof v === "number" && v >= 1 && v <= 6,
|
|
2777
|
+
2
|
|
2778
|
+
);
|
|
2779
|
+
if (typeof level !== "number" || level < 1 || level > 6) {
|
|
2780
|
+
level = 2;
|
|
841
2781
|
}
|
|
842
|
-
|
|
2782
|
+
const Tag = `h${level}`;
|
|
2783
|
+
const style = typeof styleProp === "string" ? parseStyleString(styleProp) : styleProp;
|
|
2784
|
+
const headingStyles = {
|
|
2785
|
+
1: "scroll-m-20 text-4xl font-extrabold tracking-tight lg:text-5xl",
|
|
2786
|
+
2: "scroll-m-20 border-b pb-2 text-3xl font-semibold tracking-tight first:mt-0",
|
|
2787
|
+
3: "scroll-m-20 text-2xl font-semibold tracking-tight",
|
|
2788
|
+
4: "scroll-m-20 text-xl font-semibold tracking-tight"
|
|
2789
|
+
// Add styles for h5, h6 if needed, using text-lg, text-base etc.
|
|
2790
|
+
}[level] || "text-lg font-semibold";
|
|
2791
|
+
return /* @__PURE__ */ jsx(
|
|
2792
|
+
Tag,
|
|
2793
|
+
{
|
|
2794
|
+
className: cn(headingStyles, className),
|
|
2795
|
+
style,
|
|
2796
|
+
...restProps,
|
|
2797
|
+
children: text
|
|
2798
|
+
},
|
|
2799
|
+
key
|
|
2800
|
+
);
|
|
2801
|
+
},
|
|
2802
|
+
Text: (node) => {
|
|
2803
|
+
const { className, style: styleProp, key, ...restProps } = node.props || {};
|
|
2804
|
+
const text = getSafeProp(node.props, "text", isString, "Some text");
|
|
2805
|
+
const style = typeof styleProp === "string" ? parseStyleString(styleProp) : styleProp;
|
|
2806
|
+
return /* @__PURE__ */ jsx(
|
|
2807
|
+
"p",
|
|
2808
|
+
{
|
|
2809
|
+
className: cn("leading-7", className),
|
|
2810
|
+
style,
|
|
2811
|
+
...restProps,
|
|
2812
|
+
children: text
|
|
2813
|
+
},
|
|
2814
|
+
key
|
|
2815
|
+
);
|
|
2816
|
+
},
|
|
2817
|
+
Badge: (node) => {
|
|
2818
|
+
const { className, style: styleProp, key, ...restProps } = node.props || {};
|
|
2819
|
+
const text = getSafeProp(node.props, "text", isString, "");
|
|
2820
|
+
const style = typeof styleProp === "string" ? parseStyleString(styleProp) : styleProp;
|
|
2821
|
+
return /* @__PURE__ */ jsx(
|
|
2822
|
+
"span",
|
|
2823
|
+
{
|
|
2824
|
+
className: cn(
|
|
2825
|
+
"inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium bg-gray-100 text-gray-800",
|
|
2826
|
+
className
|
|
2827
|
+
),
|
|
2828
|
+
style,
|
|
2829
|
+
...restProps,
|
|
2830
|
+
children: text
|
|
2831
|
+
},
|
|
2832
|
+
key
|
|
2833
|
+
);
|
|
2834
|
+
}
|
|
843
2835
|
};
|
|
844
|
-
function renderNode(node) {
|
|
845
|
-
const
|
|
846
|
-
if (
|
|
847
|
-
return
|
|
848
|
-
}
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
] });
|
|
2836
|
+
function renderNode(node, processEvent) {
|
|
2837
|
+
const mappedComponent = adapterMap[node.node_type];
|
|
2838
|
+
if (mappedComponent) {
|
|
2839
|
+
return mappedComponent(node, processEvent);
|
|
2840
|
+
}
|
|
2841
|
+
console.warn(`Unknown node type: ${node.node_type}`);
|
|
2842
|
+
return React.createElement(
|
|
2843
|
+
Container,
|
|
2844
|
+
{},
|
|
2845
|
+
`Unknown node type: ${node.node_type}`
|
|
2846
|
+
);
|
|
856
2847
|
}
|
|
857
|
-
|
|
2848
|
+
var renderedNodesCache = /* @__PURE__ */ new Map();
|
|
2849
|
+
var MAX_CACHE_SIZE = 10;
|
|
2850
|
+
var CACHE_TTL = 5e3;
|
|
2851
|
+
var clearRenderedNodeCacheEntry = (cacheKey) => {
|
|
2852
|
+
renderedNodesCache.delete(cacheKey);
|
|
2853
|
+
console.log(`[Renderer Cache] Cleared entry FOR REAL for key: ${cacheKey}`);
|
|
2854
|
+
};
|
|
2855
|
+
var getRendererCacheKey = (node) => {
|
|
2856
|
+
if (node.id === "task-detail") {
|
|
2857
|
+
const dataId = node.props?.data?.id;
|
|
2858
|
+
return `${node.id}:${node.props?.visible}:${dataId || "no-data-selected"}`;
|
|
2859
|
+
}
|
|
2860
|
+
let propsString = "null";
|
|
2861
|
+
try {
|
|
2862
|
+
propsString = JSON.stringify(node.props);
|
|
2863
|
+
} catch (_e) {
|
|
2864
|
+
console.warn(
|
|
2865
|
+
`[Renderer Cache Key] Error stringifying node.props for ID ${node.id}, using 'null' for props part of key.`
|
|
2866
|
+
);
|
|
2867
|
+
}
|
|
2868
|
+
let bindingsString = "null";
|
|
2869
|
+
try {
|
|
2870
|
+
bindingsString = JSON.stringify(node.bindings);
|
|
2871
|
+
} catch (_e) {
|
|
2872
|
+
console.warn(
|
|
2873
|
+
`[Renderer Cache Key] Error stringifying node.bindings for ID ${node.id}, using 'null' for bindings part of key.`
|
|
2874
|
+
);
|
|
2875
|
+
}
|
|
2876
|
+
return `${node.id}:${propsString}:${bindingsString}`;
|
|
2877
|
+
};
|
|
2878
|
+
async function renderNode2(node, adapter = "shadcn", processEvent) {
|
|
858
2879
|
const startTime = Date.now();
|
|
2880
|
+
const cacheKey = getRendererCacheKey(node);
|
|
2881
|
+
const cachedItem = renderedNodesCache.get(cacheKey);
|
|
2882
|
+
if (cachedItem && startTime - cachedItem.timestamp < CACHE_TTL) {
|
|
2883
|
+
return cachedItem.element;
|
|
2884
|
+
}
|
|
2885
|
+
if (node.id === "task-detail") {
|
|
2886
|
+
let safeNodeString = "Error stringifying node for log";
|
|
2887
|
+
let propsToLog = "{}";
|
|
2888
|
+
try {
|
|
2889
|
+
const clonedProps = node.props ? { ...node.props } : {};
|
|
2890
|
+
if (clonedProps && clonedProps.data) {
|
|
2891
|
+
clonedProps.data = `Data present (type: ${typeof clonedProps.data}, logging suppressed)`;
|
|
2892
|
+
}
|
|
2893
|
+
if (clonedProps && Array.isArray(clonedProps.fields)) {
|
|
2894
|
+
clonedProps.fields = `Fields array (length: ${clonedProps.fields.length}, logging suppressed)`;
|
|
2895
|
+
} else if (clonedProps && clonedProps.fields) {
|
|
2896
|
+
clonedProps.fields = `Fields present (type: ${typeof clonedProps.fields}, logging suppressed)`;
|
|
2897
|
+
}
|
|
2898
|
+
propsToLog = JSON.stringify(clonedProps);
|
|
2899
|
+
const nodeToLog = {
|
|
2900
|
+
id: node.id,
|
|
2901
|
+
node_type: node.node_type,
|
|
2902
|
+
props: "See props above",
|
|
2903
|
+
bindings: node.bindings,
|
|
2904
|
+
events: node.events,
|
|
2905
|
+
children: node.children ? `Children array (length: ${node.children.length}, logging suppressed)` : null
|
|
2906
|
+
};
|
|
2907
|
+
safeNodeString = JSON.stringify(nodeToLog);
|
|
2908
|
+
} catch (e) {
|
|
2909
|
+
if (e instanceof Error) {
|
|
2910
|
+
safeNodeString = `Error stringifying node for log: ${e.message}`;
|
|
2911
|
+
} else {
|
|
2912
|
+
safeNodeString = `Error stringifying node for log: Unknown error`;
|
|
2913
|
+
}
|
|
2914
|
+
if (node.props === void 0)
|
|
2915
|
+
propsToLog = "undefined";
|
|
2916
|
+
else if (node.props === null)
|
|
2917
|
+
propsToLog = "null";
|
|
2918
|
+
}
|
|
2919
|
+
console.log(
|
|
2920
|
+
`[Renderer renderNode BEFORE RENDER_START event] About to call adapter for task-detail. ID: ${node.id}, Visible: ${node.props?.visible}, Props (safe): ${propsToLog}, Bindings: ${JSON.stringify(
|
|
2921
|
+
node.bindings
|
|
2922
|
+
)}, Node (safe): ${safeNodeString}`
|
|
2923
|
+
);
|
|
2924
|
+
}
|
|
859
2925
|
await systemEvents.emit(
|
|
860
2926
|
createSystemEvent("RENDER_START" /* RENDER_START */, { layout: node })
|
|
861
2927
|
);
|
|
862
2928
|
let result;
|
|
863
2929
|
switch (adapter) {
|
|
864
2930
|
case "shadcn":
|
|
865
|
-
result = renderNode(node);
|
|
2931
|
+
result = renderNode(node, processEvent);
|
|
866
2932
|
break;
|
|
867
2933
|
default:
|
|
868
2934
|
console.warn(`Unsupported adapter: ${adapter}, falling back to shadcn`);
|
|
869
|
-
result = renderNode(node);
|
|
2935
|
+
result = renderNode(node, processEvent);
|
|
2936
|
+
}
|
|
2937
|
+
if (node.id === "task-detail") {
|
|
2938
|
+
let elementType = void 0;
|
|
2939
|
+
if (result && typeof result.type === "function") {
|
|
2940
|
+
elementType = result.type.name;
|
|
2941
|
+
} else if (result && typeof result.type === "string") {
|
|
2942
|
+
elementType = result.type;
|
|
2943
|
+
} else if (result && typeof result.type === "object" && result.type !== null) {
|
|
2944
|
+
const componentType2 = result.type;
|
|
2945
|
+
if (typeof componentType2.displayName === "string") {
|
|
2946
|
+
elementType = componentType2.displayName;
|
|
2947
|
+
}
|
|
2948
|
+
}
|
|
2949
|
+
console.log(
|
|
2950
|
+
`[Renderer renderNode] Adapter for task-detail returned element. Element type:`,
|
|
2951
|
+
elementType
|
|
2952
|
+
);
|
|
870
2953
|
}
|
|
871
2954
|
await systemEvents.emit(
|
|
872
2955
|
createSystemEvent("RENDER_COMPLETE" /* RENDER_COMPLETE */, {
|
|
@@ -874,13 +2957,23 @@ async function renderNode2(node, adapter = "shadcn") {
|
|
|
874
2957
|
renderTimeMs: Date.now() - startTime
|
|
875
2958
|
})
|
|
876
2959
|
);
|
|
2960
|
+
renderedNodesCache.set(cacheKey, {
|
|
2961
|
+
element: result,
|
|
2962
|
+
timestamp: startTime
|
|
2963
|
+
});
|
|
2964
|
+
if (renderedNodesCache.size > MAX_CACHE_SIZE) {
|
|
2965
|
+
const oldestEntry = renderedNodesCache.entries().next().value;
|
|
2966
|
+
if (oldestEntry) {
|
|
2967
|
+
renderedNodesCache.delete(oldestEntry[0]);
|
|
2968
|
+
}
|
|
2969
|
+
}
|
|
877
2970
|
return result;
|
|
878
2971
|
}
|
|
879
2972
|
function renderShimmer(node, adapter = "shadcn") {
|
|
880
2973
|
if (!node) {
|
|
881
2974
|
return /* @__PURE__ */ jsx(ShimmerBlock, {});
|
|
882
2975
|
}
|
|
883
|
-
switch (node.
|
|
2976
|
+
switch (node.node_type) {
|
|
884
2977
|
case "ListView":
|
|
885
2978
|
return /* @__PURE__ */ jsx(ShimmerTable, { rows: 3 });
|
|
886
2979
|
case "Detail":
|
|
@@ -892,113 +2985,6 @@ function renderShimmer(node, adapter = "shadcn") {
|
|
|
892
2985
|
}
|
|
893
2986
|
}
|
|
894
2987
|
|
|
895
|
-
// src/core/bindings.ts
|
|
896
|
-
function getValueByPath(context, path) {
|
|
897
|
-
const parts = path.split(".");
|
|
898
|
-
let current = context;
|
|
899
|
-
for (const part of parts) {
|
|
900
|
-
if (current === null || current === void 0) {
|
|
901
|
-
return void 0;
|
|
902
|
-
}
|
|
903
|
-
if (typeof current !== "object") {
|
|
904
|
-
return void 0;
|
|
905
|
-
}
|
|
906
|
-
current = current[part];
|
|
907
|
-
}
|
|
908
|
-
return current;
|
|
909
|
-
}
|
|
910
|
-
function setValueByPath(context, path, value) {
|
|
911
|
-
const result = { ...context };
|
|
912
|
-
const parts = path.split(".");
|
|
913
|
-
let current = result;
|
|
914
|
-
for (let i = 0; i < parts.length - 1; i++) {
|
|
915
|
-
const part = parts[i];
|
|
916
|
-
if (!(part in current) || current[part] === null || current[part] === void 0) {
|
|
917
|
-
current[part] = {};
|
|
918
|
-
}
|
|
919
|
-
current = current[part];
|
|
920
|
-
if (typeof current !== "object") {
|
|
921
|
-
current = {};
|
|
922
|
-
}
|
|
923
|
-
}
|
|
924
|
-
const lastPart = parts[parts.length - 1];
|
|
925
|
-
current[lastPart] = value;
|
|
926
|
-
return result;
|
|
927
|
-
}
|
|
928
|
-
function processBinding(binding, context) {
|
|
929
|
-
if (typeof binding === "string") {
|
|
930
|
-
return getValueByPath(context, binding);
|
|
931
|
-
}
|
|
932
|
-
if (Array.isArray(binding)) {
|
|
933
|
-
return binding.map((item) => processBinding(item, context));
|
|
934
|
-
}
|
|
935
|
-
if (binding !== null && typeof binding === "object") {
|
|
936
|
-
const result = {};
|
|
937
|
-
for (const [key, value] of Object.entries(binding)) {
|
|
938
|
-
result[key] = processBinding(value, context);
|
|
939
|
-
}
|
|
940
|
-
return result;
|
|
941
|
-
}
|
|
942
|
-
return binding;
|
|
943
|
-
}
|
|
944
|
-
async function resolveBindings(node, context) {
|
|
945
|
-
await systemEvents.emit(
|
|
946
|
-
createSystemEvent("BINDING_RESOLUTION_START" /* BINDING_RESOLUTION_START */, { layout: node })
|
|
947
|
-
);
|
|
948
|
-
const result = {
|
|
949
|
-
...node,
|
|
950
|
-
props: node.props ? { ...node.props } : void 0,
|
|
951
|
-
events: node.events ? { ...node.events } : void 0
|
|
952
|
-
};
|
|
953
|
-
if (node.bindings) {
|
|
954
|
-
for (const [key, binding] of Object.entries(node.bindings)) {
|
|
955
|
-
const value = processBinding(binding, context);
|
|
956
|
-
if (value !== void 0) {
|
|
957
|
-
if (!result.props) {
|
|
958
|
-
result.props = {};
|
|
959
|
-
}
|
|
960
|
-
result.props[key] = value;
|
|
961
|
-
}
|
|
962
|
-
}
|
|
963
|
-
}
|
|
964
|
-
if (node.children) {
|
|
965
|
-
result.children = await Promise.all(node.children.map((child) => resolveBindings(child, context)));
|
|
966
|
-
}
|
|
967
|
-
await systemEvents.emit(
|
|
968
|
-
createSystemEvent("BINDING_RESOLUTION_COMPLETE" /* BINDING_RESOLUTION_COMPLETE */, {
|
|
969
|
-
originalLayout: node,
|
|
970
|
-
resolvedLayout: result
|
|
971
|
-
})
|
|
972
|
-
);
|
|
973
|
-
return result;
|
|
974
|
-
}
|
|
975
|
-
function executeAction(action, targetId, payload, context = {}, layoutTree) {
|
|
976
|
-
let newContext = { ...context };
|
|
977
|
-
switch (action) {
|
|
978
|
-
case "VIEW_DETAIL": {
|
|
979
|
-
if (payload?.item) {
|
|
980
|
-
newContext = setValueByPath(newContext, "selected", payload.item);
|
|
981
|
-
}
|
|
982
|
-
break;
|
|
983
|
-
}
|
|
984
|
-
case "HIDE_DETAIL": {
|
|
985
|
-
newContext = setValueByPath(newContext, "selected", null);
|
|
986
|
-
break;
|
|
987
|
-
}
|
|
988
|
-
case "SET_VALUE": {
|
|
989
|
-
if (payload?.path && "value" in payload) {
|
|
990
|
-
const path = String(payload.path);
|
|
991
|
-
newContext = setValueByPath(newContext, path, payload.value);
|
|
992
|
-
}
|
|
993
|
-
break;
|
|
994
|
-
}
|
|
995
|
-
// Add more actions as needed
|
|
996
|
-
default:
|
|
997
|
-
console.warn(`Unknown action: ${action}`);
|
|
998
|
-
}
|
|
999
|
-
return newContext;
|
|
1000
|
-
}
|
|
1001
|
-
|
|
1002
2988
|
// src/core/events.ts
|
|
1003
2989
|
var EventManager = class {
|
|
1004
2990
|
constructor() {
|
|
@@ -1006,7 +2992,7 @@ var EventManager = class {
|
|
|
1006
2992
|
}
|
|
1007
2993
|
/**
|
|
1008
2994
|
* Register a hook for specific event types
|
|
1009
|
-
*
|
|
2995
|
+
*
|
|
1010
2996
|
* @param eventTypes - Event types to register for, or 'all' for all events
|
|
1011
2997
|
* @param hook - Hook function to execute
|
|
1012
2998
|
* @returns Unregister function
|
|
@@ -1039,7 +3025,7 @@ var EventManager = class {
|
|
|
1039
3025
|
}
|
|
1040
3026
|
/**
|
|
1041
3027
|
* Process an event through all registered hooks
|
|
1042
|
-
*
|
|
3028
|
+
*
|
|
1043
3029
|
* @param event - The UI event to process
|
|
1044
3030
|
* @returns Whether the default action should proceed
|
|
1045
3031
|
*/
|
|
@@ -1060,13 +3046,15 @@ var EventManager = class {
|
|
|
1060
3046
|
if (this.hooks.all) {
|
|
1061
3047
|
for (const hook of this.hooks.all) {
|
|
1062
3048
|
await hook(context);
|
|
1063
|
-
if (propagationStopped)
|
|
3049
|
+
if (propagationStopped)
|
|
3050
|
+
break;
|
|
1064
3051
|
}
|
|
1065
3052
|
}
|
|
1066
3053
|
if (!propagationStopped && this.hooks[event.type]) {
|
|
1067
3054
|
for (const hook of this.hooks[event.type] || []) {
|
|
1068
3055
|
await hook(context);
|
|
1069
|
-
if (propagationStopped)
|
|
3056
|
+
if (propagationStopped)
|
|
3057
|
+
break;
|
|
1070
3058
|
}
|
|
1071
3059
|
}
|
|
1072
3060
|
return !defaultPrevented;
|
|
@@ -1083,6 +3071,45 @@ function createEventHook(eventTypes, hook, options) {
|
|
|
1083
3071
|
}
|
|
1084
3072
|
};
|
|
1085
3073
|
}
|
|
3074
|
+
|
|
3075
|
+
// src/core/component-detection.ts
|
|
3076
|
+
function areShadcnComponentsAvailable() {
|
|
3077
|
+
try {
|
|
3078
|
+
init_button();
|
|
3079
|
+
return true;
|
|
3080
|
+
} catch (error) {
|
|
3081
|
+
return false;
|
|
3082
|
+
}
|
|
3083
|
+
}
|
|
3084
|
+
function getMissingComponentsMessage() {
|
|
3085
|
+
return `Missing required shadcn components. Please run:
|
|
3086
|
+
> npm run setup-shadcn`;
|
|
3087
|
+
}
|
|
3088
|
+
function correctListBindingsRecursive(node, dataContext) {
|
|
3089
|
+
const correctedNode = JSON.parse(JSON.stringify(node));
|
|
3090
|
+
if ((correctedNode.node_type === "ListView" || correctedNode.node_type === "Table") && correctedNode.bindings?.data) {
|
|
3091
|
+
const bindingPath = correctedNode.bindings.data;
|
|
3092
|
+
if (typeof bindingPath === "string") {
|
|
3093
|
+
const pathSegments = bindingPath.split(".");
|
|
3094
|
+
const mainKey = pathSegments[0];
|
|
3095
|
+
if (pathSegments.length === 1) {
|
|
3096
|
+
const potentialDataContextEntry = dataContext[mainKey];
|
|
3097
|
+
if (potentialDataContextEntry && typeof potentialDataContextEntry === "object" && potentialDataContextEntry !== null && "data" in potentialDataContextEntry && Array.isArray(potentialDataContextEntry.data)) {
|
|
3098
|
+
correctedNode.bindings.data = `${mainKey}.data`;
|
|
3099
|
+
console.log(
|
|
3100
|
+
`[AutoUI Debug] Corrected list binding for node '${correctedNode.id}': from '${mainKey}' to '${mainKey}.data'`
|
|
3101
|
+
);
|
|
3102
|
+
}
|
|
3103
|
+
}
|
|
3104
|
+
}
|
|
3105
|
+
}
|
|
3106
|
+
if (correctedNode.children) {
|
|
3107
|
+
correctedNode.children = correctedNode.children.map(
|
|
3108
|
+
(child) => correctListBindingsRecursive(child, dataContext)
|
|
3109
|
+
);
|
|
3110
|
+
}
|
|
3111
|
+
return correctedNode;
|
|
3112
|
+
}
|
|
1086
3113
|
var AutoUI = ({
|
|
1087
3114
|
schema,
|
|
1088
3115
|
goal,
|
|
@@ -1092,24 +3119,42 @@ var AutoUI = ({
|
|
|
1092
3119
|
eventHooks,
|
|
1093
3120
|
systemEventHooks,
|
|
1094
3121
|
debugMode = false,
|
|
1095
|
-
mockMode = true,
|
|
1096
|
-
databaseConfig,
|
|
1097
3122
|
planningConfig,
|
|
1098
3123
|
integration = {},
|
|
1099
|
-
|
|
1100
|
-
|
|
3124
|
+
enablePartialUpdates = true,
|
|
3125
|
+
apiKey
|
|
1101
3126
|
}) => {
|
|
1102
|
-
const
|
|
3127
|
+
const stableGoal = React.useMemo(() => goal, [goal]);
|
|
3128
|
+
const schemaStringified = React.useMemo(() => JSON.stringify(schema), [schema]);
|
|
3129
|
+
const stableSchemaProp = React.useMemo(() => schema, [schemaStringified]);
|
|
3130
|
+
const [schemaAdapterInstance] = useState(null);
|
|
1103
3131
|
const [dataContext, setDataContext] = useState({});
|
|
1104
|
-
const
|
|
1105
|
-
const
|
|
3132
|
+
const [componentsAvailable, setComponentsAvailable] = useState(true);
|
|
3133
|
+
const [uiStatus, setUiStatus] = useState("initializing");
|
|
3134
|
+
const [currentResolvedLayoutForRender, setCurrentResolvedLayoutForRender] = useState(null);
|
|
3135
|
+
const [isResolvingBindings, setIsResolvingBindings] = useState(false);
|
|
3136
|
+
const [renderedNode, setRenderedNode] = useState(
|
|
3137
|
+
null
|
|
3138
|
+
);
|
|
3139
|
+
const currentResolvedLayoutRef = useRef(null);
|
|
3140
|
+
const effectiveSchema = stableSchemaProp;
|
|
3141
|
+
const scopedGoal = stableGoal;
|
|
3142
|
+
useEffect(() => {
|
|
3143
|
+
if (componentAdapter === "shadcn") {
|
|
3144
|
+
setComponentsAvailable(areShadcnComponentsAvailable());
|
|
3145
|
+
}
|
|
3146
|
+
}, [componentAdapter]);
|
|
1106
3147
|
useEffect(() => {
|
|
1107
3148
|
const unregisters = [];
|
|
1108
3149
|
if (systemEventHooks) {
|
|
1109
3150
|
Object.entries(systemEventHooks).forEach(([eventType, hooks]) => {
|
|
1110
|
-
if (!hooks)
|
|
3151
|
+
if (!hooks)
|
|
3152
|
+
return;
|
|
1111
3153
|
hooks.forEach((hook) => {
|
|
1112
|
-
const unregister = systemEvents.on(
|
|
3154
|
+
const unregister = systemEvents.on(
|
|
3155
|
+
eventType,
|
|
3156
|
+
hook
|
|
3157
|
+
);
|
|
1113
3158
|
unregisters.push(unregister);
|
|
1114
3159
|
});
|
|
1115
3160
|
});
|
|
@@ -1118,7 +3163,9 @@ var AutoUI = ({
|
|
|
1118
3163
|
const debugHook = (event) => {
|
|
1119
3164
|
console.debug(`[AutoUI Debug] System Event:`, event);
|
|
1120
3165
|
};
|
|
1121
|
-
Object.values(SystemEventType).
|
|
3166
|
+
Object.values(SystemEventType).filter(
|
|
3167
|
+
(eventType) => eventType !== "RENDER_START" /* RENDER_START */ && eventType !== "BINDING_RESOLUTION_START" /* BINDING_RESOLUTION_START */
|
|
3168
|
+
).forEach((eventType) => {
|
|
1122
3169
|
const unregister = systemEvents.on(eventType, debugHook);
|
|
1123
3170
|
unregisters.push(unregister);
|
|
1124
3171
|
});
|
|
@@ -1136,7 +3183,6 @@ var AutoUI = ({
|
|
|
1136
3183
|
Object.entries(effectiveSchema).forEach(([key, tableSchema]) => {
|
|
1137
3184
|
initialData[key] = {
|
|
1138
3185
|
schema: tableSchema,
|
|
1139
|
-
// For development, add sample data if available
|
|
1140
3186
|
data: tableSchema?.sampleData || [],
|
|
1141
3187
|
selected: null
|
|
1142
3188
|
};
|
|
@@ -1153,118 +3199,387 @@ var AutoUI = ({
|
|
|
1153
3199
|
schema: effectiveSchema,
|
|
1154
3200
|
goal: scopedGoal,
|
|
1155
3201
|
userContext,
|
|
1156
|
-
mockMode,
|
|
1157
3202
|
planningConfig,
|
|
1158
|
-
router: void 0,
|
|
1159
3203
|
dataContext,
|
|
1160
|
-
|
|
3204
|
+
// Pass the local dataContext here, engine will use it if its own is empty initially
|
|
3205
|
+
enablePartialUpdates,
|
|
3206
|
+
apiKey
|
|
1161
3207
|
});
|
|
1162
3208
|
const eventManagerRef = useRef(new EventManager());
|
|
1163
3209
|
useEffect(() => {
|
|
1164
|
-
if (!eventHooks)
|
|
3210
|
+
if (!eventHooks)
|
|
3211
|
+
return;
|
|
1165
3212
|
const unregisters = [];
|
|
1166
3213
|
if (eventHooks.all) {
|
|
1167
|
-
const unregister = eventManagerRef.current.register(
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
3214
|
+
const unregister = eventManagerRef.current.register(
|
|
3215
|
+
"all",
|
|
3216
|
+
async (ctx) => {
|
|
3217
|
+
for (const hook of eventHooks.all || []) {
|
|
3218
|
+
await hook(ctx);
|
|
3219
|
+
if (ctx.isPropagationStopped())
|
|
3220
|
+
break;
|
|
3221
|
+
}
|
|
1171
3222
|
}
|
|
1172
|
-
|
|
3223
|
+
);
|
|
1173
3224
|
unregisters.push(unregister);
|
|
1174
3225
|
}
|
|
1175
3226
|
Object.entries(eventHooks).forEach(([type, hooks]) => {
|
|
1176
|
-
if (type === "all" || !hooks)
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
3227
|
+
if (type === "all" || !hooks)
|
|
3228
|
+
return;
|
|
3229
|
+
const unregister = eventManagerRef.current.register(
|
|
3230
|
+
[type],
|
|
3231
|
+
async (ctx) => {
|
|
3232
|
+
for (const hook of hooks) {
|
|
3233
|
+
await hook(ctx);
|
|
3234
|
+
if (ctx.isPropagationStopped())
|
|
3235
|
+
break;
|
|
3236
|
+
}
|
|
1181
3237
|
}
|
|
1182
|
-
|
|
3238
|
+
);
|
|
1183
3239
|
unregisters.push(unregister);
|
|
1184
3240
|
});
|
|
1185
3241
|
return () => {
|
|
1186
3242
|
unregisters.forEach((unregister) => unregister());
|
|
1187
3243
|
};
|
|
1188
3244
|
}, [eventHooks]);
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
3245
|
+
useEffect(() => {
|
|
3246
|
+
currentResolvedLayoutRef.current = currentResolvedLayoutForRender;
|
|
3247
|
+
}, [currentResolvedLayoutForRender]);
|
|
3248
|
+
const processEvent = useCallback(
|
|
3249
|
+
async (event) => {
|
|
3250
|
+
setUiStatus("event_processing");
|
|
3251
|
+
const layoutAtEventTime = currentResolvedLayoutRef.current;
|
|
3252
|
+
const shouldProceed = await eventManagerRef.current.processEvent(event);
|
|
3253
|
+
if (onEvent)
|
|
3254
|
+
onEvent(event);
|
|
3255
|
+
if (!shouldProceed) {
|
|
3256
|
+
console.info(
|
|
3257
|
+
"[AutoUI.processEvent] Event processing stopped by local hooks",
|
|
3258
|
+
event
|
|
3259
|
+
);
|
|
3260
|
+
setUiStatus("idle");
|
|
3261
|
+
return;
|
|
3262
|
+
}
|
|
3263
|
+
if (event.type === "CLICK" && layoutAtEventTime && event.nodeId.includes("view-details-button")) {
|
|
3264
|
+
const mainContent = layoutAtEventTime.children?.find(
|
|
3265
|
+
(c) => c.id === "main-content"
|
|
3266
|
+
);
|
|
3267
|
+
const taskList = mainContent?.children?.find((c) => c.id === "tasks-container")?.children?.find((c) => c.id === "task-list");
|
|
3268
|
+
const eventSourceListItemCard = taskList?.children?.find(
|
|
3269
|
+
(item) => item.children?.some((btn) => btn.id === event.nodeId)
|
|
3270
|
+
);
|
|
3271
|
+
const buttonNode = eventSourceListItemCard?.children?.find(
|
|
3272
|
+
(btn) => btn.id === event.nodeId
|
|
3273
|
+
);
|
|
3274
|
+
if (buttonNode?.events?.CLICK?.action === "SHOW_DETAIL" /* SHOW_DETAIL */ && buttonNode?.events?.CLICK?.target === "task-detail") {
|
|
3275
|
+
const cacheKeyToClear = `task-detail:false:no-data-selected`;
|
|
3276
|
+
clearRenderedNodeCacheEntry(cacheKeyToClear);
|
|
3277
|
+
console.log(
|
|
3278
|
+
`[AutoUI.processEvent] Attempted to clear cache for task-detail using simplified key: ${cacheKeyToClear}`
|
|
3279
|
+
);
|
|
3280
|
+
}
|
|
3281
|
+
}
|
|
3282
|
+
console.log(
|
|
3283
|
+
"[AutoUI.processEvent] Forwarding event to engine:",
|
|
3284
|
+
JSON.stringify(event, null, 2)
|
|
3285
|
+
);
|
|
3286
|
+
console.log(
|
|
3287
|
+
"[AutoUI.processEvent] Passing currentLayout ID (at event time):",
|
|
3288
|
+
layoutAtEventTime?.id
|
|
3289
|
+
);
|
|
3290
|
+
console.log(
|
|
3291
|
+
"[AutoUI.processEvent] Passing dataContext keys to engine:",
|
|
3292
|
+
Object.keys(dataContext)
|
|
3293
|
+
);
|
|
3294
|
+
handleEvent(event, layoutAtEventTime, dataContext);
|
|
3295
|
+
},
|
|
3296
|
+
[
|
|
3297
|
+
dataContext,
|
|
3298
|
+
handleEvent,
|
|
3299
|
+
onEvent,
|
|
3300
|
+
eventManagerRef
|
|
3301
|
+
// currentResolvedLayoutForRender removed - using ref instead to prevent infinite loops
|
|
3302
|
+
// setUiStatus is implicitly available
|
|
3303
|
+
]
|
|
3304
|
+
);
|
|
3305
|
+
useEffect(() => {
|
|
3306
|
+
if (state.dataContext && Object.keys(state.dataContext).length > 0) {
|
|
3307
|
+
if (JSON.stringify(dataContext) !== JSON.stringify(state.dataContext)) {
|
|
3308
|
+
console.log(
|
|
3309
|
+
"[AutoUI] Syncing local dataContext from engine state. New keys:",
|
|
3310
|
+
Object.keys(state.dataContext),
|
|
3311
|
+
"Old keys:",
|
|
3312
|
+
Object.keys(dataContext)
|
|
3313
|
+
);
|
|
3314
|
+
setDataContext(state.dataContext);
|
|
3315
|
+
}
|
|
1197
3316
|
}
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
3317
|
+
}, [state.dataContext, dataContext]);
|
|
3318
|
+
useEffect(() => {
|
|
3319
|
+
const resolveAndSetLayout = async () => {
|
|
3320
|
+
const hasMeaningfulDataContext = dataContext && Object.keys(dataContext).some(
|
|
3321
|
+
(key) => key !== "user" || Object.keys(dataContext["user"]).length > 0
|
|
3322
|
+
);
|
|
3323
|
+
if (state.layout && hasMeaningfulDataContext) {
|
|
3324
|
+
console.log(
|
|
3325
|
+
`[AutoUI resolveAndSetLayout] Calling core resolveBindings for layout ID: ${state.layout.id}. DataContext keys: ${Object.keys(dataContext).join(", ")}`
|
|
3326
|
+
);
|
|
3327
|
+
setIsResolvingBindings(true);
|
|
3328
|
+
setUiStatus("resolving_bindings");
|
|
3329
|
+
try {
|
|
3330
|
+
const correctedLayout = correctListBindingsRecursive(
|
|
3331
|
+
state.layout,
|
|
3332
|
+
dataContext
|
|
3333
|
+
);
|
|
3334
|
+
systemEvents.emit(
|
|
3335
|
+
createSystemEvent("BINDING_RESOLUTION_START" /* BINDING_RESOLUTION_START */, {
|
|
3336
|
+
layout: correctedLayout
|
|
3337
|
+
// Log corrected layout before resolving
|
|
3338
|
+
})
|
|
3339
|
+
);
|
|
3340
|
+
const resolved = await resolveBindings(correctedLayout, dataContext);
|
|
3341
|
+
setCurrentResolvedLayoutForRender(resolved);
|
|
3342
|
+
systemEvents.emit(
|
|
3343
|
+
createSystemEvent("BINDING_RESOLUTION_COMPLETE" /* BINDING_RESOLUTION_COMPLETE */, {
|
|
3344
|
+
originalLayout: correctedLayout,
|
|
3345
|
+
// Log corrected layout
|
|
3346
|
+
resolvedLayout: resolved
|
|
3347
|
+
})
|
|
3348
|
+
);
|
|
3349
|
+
} catch (err) {
|
|
3350
|
+
console.error("Error resolving bindings:", err);
|
|
3351
|
+
setUiStatus("error");
|
|
3352
|
+
setCurrentResolvedLayoutForRender(null);
|
|
3353
|
+
} finally {
|
|
3354
|
+
setIsResolvingBindings(false);
|
|
3355
|
+
}
|
|
3356
|
+
} else {
|
|
3357
|
+
if (!state.layout)
|
|
3358
|
+
console.log(
|
|
3359
|
+
"[AutoUI] Skipping resolveBindings: state.layout is null/undefined"
|
|
3360
|
+
);
|
|
3361
|
+
if (!hasMeaningfulDataContext)
|
|
3362
|
+
console.log(
|
|
3363
|
+
"[AutoUI] Skipping resolveBindings: dataContext is not meaningfully populated yet"
|
|
3364
|
+
);
|
|
3365
|
+
setCurrentResolvedLayoutForRender(null);
|
|
3366
|
+
if (!state.loading && uiStatus !== "initializing" && !isResolvingBindings)
|
|
3367
|
+
setUiStatus("idle");
|
|
3368
|
+
}
|
|
3369
|
+
};
|
|
3370
|
+
resolveAndSetLayout();
|
|
3371
|
+
}, [state.layout, dataContext]);
|
|
3372
|
+
useEffect(() => {
|
|
3373
|
+
const renderLayout = async () => {
|
|
3374
|
+
if (currentResolvedLayoutForRender && !isResolvingBindings) {
|
|
3375
|
+
setUiStatus("rendering");
|
|
3376
|
+
console.log(
|
|
3377
|
+
"[AutoUI Debug] Rendering with currentResolvedLayoutForRender:",
|
|
3378
|
+
JSON.stringify(currentResolvedLayoutForRender, null, 2)
|
|
3379
|
+
);
|
|
3380
|
+
try {
|
|
3381
|
+
systemEvents.emit(
|
|
3382
|
+
createSystemEvent("RENDER_START" /* RENDER_START */, {
|
|
3383
|
+
layout: currentResolvedLayoutForRender
|
|
3384
|
+
})
|
|
3385
|
+
);
|
|
3386
|
+
const rendered = await renderNode2(
|
|
3387
|
+
currentResolvedLayoutForRender,
|
|
3388
|
+
componentAdapter,
|
|
3389
|
+
processEvent
|
|
3390
|
+
);
|
|
3391
|
+
setRenderedNode(rendered);
|
|
3392
|
+
systemEvents.emit(
|
|
3393
|
+
createSystemEvent("RENDER_COMPLETE" /* RENDER_COMPLETE */, {
|
|
3394
|
+
layout: currentResolvedLayoutForRender,
|
|
3395
|
+
renderTimeMs: 0
|
|
3396
|
+
// Placeholder, actual timing would be more complex
|
|
3397
|
+
})
|
|
3398
|
+
);
|
|
3399
|
+
setUiStatus("idle");
|
|
3400
|
+
} catch (err) {
|
|
3401
|
+
console.error("Error rendering node:", err);
|
|
3402
|
+
setUiStatus("error");
|
|
1205
3403
|
}
|
|
3404
|
+
} else if (!currentResolvedLayoutForRender && !state.loading && !isResolvingBindings && uiStatus !== "initializing") {
|
|
3405
|
+
setRenderedNode(null);
|
|
3406
|
+
setUiStatus("idle");
|
|
1206
3407
|
}
|
|
1207
|
-
return void 0;
|
|
1208
3408
|
};
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
3409
|
+
renderLayout();
|
|
3410
|
+
}, [
|
|
3411
|
+
currentResolvedLayoutForRender,
|
|
3412
|
+
componentAdapter,
|
|
3413
|
+
processEvent,
|
|
3414
|
+
isResolvingBindings,
|
|
3415
|
+
state.loading
|
|
3416
|
+
]);
|
|
3417
|
+
useEffect(() => {
|
|
3418
|
+
if (uiStatus !== "error") {
|
|
3419
|
+
setUiStatus("initializing");
|
|
1214
3420
|
}
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
3421
|
+
}, []);
|
|
3422
|
+
useEffect(() => {
|
|
3423
|
+
if (uiStatus === "initializing") {
|
|
3424
|
+
if (state.loading) ; else if (state.layout && !isResolvingBindings) ; else if (!state.layout && !state.loading && !isResolvingBindings) {
|
|
3425
|
+
if (state.error) {
|
|
3426
|
+
setUiStatus("error");
|
|
3427
|
+
} else {
|
|
3428
|
+
setUiStatus("idle");
|
|
3429
|
+
}
|
|
3430
|
+
}
|
|
1220
3431
|
}
|
|
1221
|
-
|
|
1222
|
-
eventConfig.action,
|
|
1223
|
-
eventConfig.target,
|
|
1224
|
-
{
|
|
1225
|
-
...eventConfig.payload,
|
|
1226
|
-
...event.payload
|
|
1227
|
-
},
|
|
1228
|
-
dataContext,
|
|
1229
|
-
state.layout
|
|
1230
|
-
);
|
|
1231
|
-
setDataContext(newContext);
|
|
1232
|
-
handleEvent(event);
|
|
1233
|
-
}, [dataContext, handleEvent, onEvent, state.layout]);
|
|
1234
|
-
const [resolvedLayout, setResolvedLayout] = useState(void 0);
|
|
3432
|
+
}, [state.loading, state.layout, state.error, uiStatus, isResolvingBindings]);
|
|
1235
3433
|
useEffect(() => {
|
|
1236
|
-
if (
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
3434
|
+
if (!debugMode)
|
|
3435
|
+
return;
|
|
3436
|
+
const pipelineState = {
|
|
3437
|
+
uiStatus,
|
|
3438
|
+
hasLayout: !!state.layout,
|
|
3439
|
+
layoutId: state.layout?.id || null,
|
|
3440
|
+
hasResolvedLayout: !!currentResolvedLayoutForRender,
|
|
3441
|
+
resolvedLayoutId: currentResolvedLayoutForRender?.id || null,
|
|
3442
|
+
hasRenderedNode: !!renderedNode,
|
|
3443
|
+
isResolvingBindings,
|
|
3444
|
+
isLoading: state.loading,
|
|
3445
|
+
hasError: !!state.error,
|
|
3446
|
+
error: state.error || null,
|
|
3447
|
+
dataContextKeys: Object.keys(dataContext),
|
|
3448
|
+
timestamp: Date.now()
|
|
3449
|
+
};
|
|
3450
|
+
if (typeof window !== "undefined") {
|
|
3451
|
+
window.__autoui_pipeline_state = pipelineState;
|
|
3452
|
+
window.__autoui_pipeline_history = window.__autoui_pipeline_history || [];
|
|
3453
|
+
window.__autoui_pipeline_history.push(pipelineState);
|
|
3454
|
+
console.log("[PIPELINE_STATE]", JSON.stringify(pipelineState));
|
|
1240
3455
|
}
|
|
1241
|
-
}, [
|
|
3456
|
+
}, [
|
|
3457
|
+
debugMode,
|
|
3458
|
+
uiStatus,
|
|
3459
|
+
state.layout,
|
|
3460
|
+
state.loading,
|
|
3461
|
+
state.error,
|
|
3462
|
+
currentResolvedLayoutForRender,
|
|
3463
|
+
renderedNode,
|
|
3464
|
+
isResolvingBindings,
|
|
3465
|
+
dataContext
|
|
3466
|
+
]);
|
|
3467
|
+
if (!componentsAvailable) {
|
|
3468
|
+
return /* @__PURE__ */ jsxs("div", { className: "autoui-error p-4 border border-red-300 bg-red-50 text-red-700 rounded", children: [
|
|
3469
|
+
/* @__PURE__ */ jsx("p", { className: "font-medium", children: "Component Library Not Found" }),
|
|
3470
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm whitespace-pre-line", children: getMissingComponentsMessage() })
|
|
3471
|
+
] });
|
|
3472
|
+
}
|
|
3473
|
+
const showShimmer = (uiStatus === "initializing" || state.loading || isResolvingBindings) && !state.error;
|
|
1242
3474
|
return /* @__PURE__ */ jsxs(
|
|
1243
3475
|
"div",
|
|
1244
3476
|
{
|
|
1245
3477
|
className: `autoui-root ${integration.className || ""}`,
|
|
1246
3478
|
id: integration.id,
|
|
1247
|
-
"data-mode": integration.mode,
|
|
1248
|
-
"data-scope": scope?.type || "full",
|
|
1249
3479
|
children: [
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
] }) })
|
|
1256
|
-
) : (
|
|
1257
|
-
// Render the resolved layout
|
|
1258
|
-
/* @__PURE__ */ jsx("div", { className: "autoui-content", children: renderNode2(resolvedLayout, componentAdapter) })
|
|
1259
|
-
),
|
|
3480
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
3481
|
+
"Current Status: ",
|
|
3482
|
+
uiStatus
|
|
3483
|
+
] }),
|
|
3484
|
+
uiStatus === "idle" && !isResolvingBindings && renderedNode && /* @__PURE__ */ jsx("div", { className: "autoui-content", children: renderedNode }),
|
|
1260
3485
|
state.error && /* @__PURE__ */ jsxs("div", { className: "autoui-error", children: [
|
|
1261
|
-
|
|
1262
|
-
|
|
3486
|
+
"Error: ",
|
|
3487
|
+
state.error
|
|
3488
|
+
] }),
|
|
3489
|
+
showShimmer && state.layout && // Show shimmer if we have a layout to base it on
|
|
3490
|
+
/* @__PURE__ */ jsx("div", { className: "autoui-loading", children: renderShimmer(state.layout, componentAdapter) }),
|
|
3491
|
+
showShimmer && !state.layout && // Fallback shimmer if no layout yet
|
|
3492
|
+
/* @__PURE__ */ jsxs("div", { className: "autoui-loading p-4", children: [
|
|
3493
|
+
/* @__PURE__ */ jsx("div", { className: "w-full h-20 bg-gray-200 animate-pulse rounded mb-4" }),
|
|
3494
|
+
/* @__PURE__ */ jsx("div", { className: "w-full h-40 bg-gray-200 animate-pulse rounded" })
|
|
1263
3495
|
] })
|
|
1264
3496
|
]
|
|
1265
3497
|
}
|
|
1266
3498
|
);
|
|
1267
3499
|
};
|
|
3500
|
+
var uiEventType = z.enum([
|
|
3501
|
+
"INIT",
|
|
3502
|
+
"CLICK",
|
|
3503
|
+
"CHANGE",
|
|
3504
|
+
"SUBMIT",
|
|
3505
|
+
"MOUSEOVER",
|
|
3506
|
+
"MOUSEOUT",
|
|
3507
|
+
"FOCUS",
|
|
3508
|
+
"BLUR"
|
|
3509
|
+
]);
|
|
3510
|
+
var uiEvent = z.object({
|
|
3511
|
+
type: uiEventType,
|
|
3512
|
+
nodeId: z.string(),
|
|
3513
|
+
timestamp: z.number().nullable(),
|
|
3514
|
+
payload: z.record(z.unknown()).nullable()
|
|
3515
|
+
});
|
|
3516
|
+
z.enum(["AI_RESPONSE", "ERROR"]);
|
|
3517
|
+
var runtimeRecord = z.record(z.unknown()).nullable();
|
|
3518
|
+
var uiSpecNode = z.object({
|
|
3519
|
+
id: z.string(),
|
|
3520
|
+
node_type: z.string(),
|
|
3521
|
+
props: runtimeRecord,
|
|
3522
|
+
bindings: runtimeRecord,
|
|
3523
|
+
events: z.record(
|
|
3524
|
+
z.string(),
|
|
3525
|
+
z.object({
|
|
3526
|
+
action: z.string(),
|
|
3527
|
+
target: z.string(),
|
|
3528
|
+
payload: runtimeRecord
|
|
3529
|
+
})
|
|
3530
|
+
).nullable(),
|
|
3531
|
+
children: z.lazy(() => z.array(uiSpecNode)).nullable()
|
|
3532
|
+
});
|
|
3533
|
+
z.discriminatedUnion("type", [
|
|
3534
|
+
z.object({
|
|
3535
|
+
type: z.literal("UI_EVENT"),
|
|
3536
|
+
event: uiEvent
|
|
3537
|
+
}),
|
|
3538
|
+
z.object({
|
|
3539
|
+
type: z.literal("AI_RESPONSE"),
|
|
3540
|
+
node: uiSpecNode
|
|
3541
|
+
}),
|
|
3542
|
+
z.object({
|
|
3543
|
+
type: z.literal("PARTIAL_UPDATE"),
|
|
3544
|
+
nodeId: z.string(),
|
|
3545
|
+
node: uiSpecNode
|
|
3546
|
+
}),
|
|
3547
|
+
z.object({
|
|
3548
|
+
type: z.literal("ADD_NODE"),
|
|
3549
|
+
parentId: z.string(),
|
|
3550
|
+
node: uiSpecNode,
|
|
3551
|
+
index: z.number().nullable()
|
|
3552
|
+
}),
|
|
3553
|
+
z.object({
|
|
3554
|
+
type: z.literal("REMOVE_NODE"),
|
|
3555
|
+
nodeId: z.string()
|
|
3556
|
+
}),
|
|
3557
|
+
z.object({
|
|
3558
|
+
type: z.literal("ERROR"),
|
|
3559
|
+
message: z.string()
|
|
3560
|
+
}),
|
|
3561
|
+
z.object({
|
|
3562
|
+
type: z.literal("LOADING"),
|
|
3563
|
+
isLoading: z.boolean()
|
|
3564
|
+
}),
|
|
3565
|
+
z.object({
|
|
3566
|
+
type: z.literal("SET_DATA_CONTEXT"),
|
|
3567
|
+
payload: z.record(z.string(), z.unknown())
|
|
3568
|
+
})
|
|
3569
|
+
]);
|
|
3570
|
+
z.object({
|
|
3571
|
+
layout: uiSpecNode.nullable(),
|
|
3572
|
+
loading: z.boolean(),
|
|
3573
|
+
history: z.array(uiEvent),
|
|
3574
|
+
error: z.string().nullable(),
|
|
3575
|
+
dataContext: z.record(z.string(), z.unknown())
|
|
3576
|
+
});
|
|
3577
|
+
z.object({
|
|
3578
|
+
schema: z.record(z.unknown()),
|
|
3579
|
+
goal: z.string(),
|
|
3580
|
+
history: z.array(uiEvent).nullable(),
|
|
3581
|
+
userContext: z.record(z.unknown()).nullable().optional()
|
|
3582
|
+
});
|
|
1268
3583
|
|
|
1269
3584
|
// src/adapters/schema/drizzle.ts
|
|
1270
3585
|
var DrizzleAdapter = class {
|
|
@@ -1312,26 +3627,26 @@ var DrizzleAdapter = class {
|
|
|
1312
3627
|
*/
|
|
1313
3628
|
mapDataType(drizzleType) {
|
|
1314
3629
|
const typeMap = {
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
3630
|
+
serial: "integer",
|
|
3631
|
+
integer: "integer",
|
|
3632
|
+
int: "integer",
|
|
3633
|
+
bigint: "integer",
|
|
3634
|
+
text: "string",
|
|
3635
|
+
varchar: "string",
|
|
3636
|
+
char: "string",
|
|
3637
|
+
boolean: "boolean",
|
|
3638
|
+
bool: "boolean",
|
|
3639
|
+
timestamp: "datetime",
|
|
3640
|
+
timestamptz: "datetime",
|
|
3641
|
+
date: "date",
|
|
3642
|
+
time: "time",
|
|
3643
|
+
json: "object",
|
|
3644
|
+
jsonb: "object",
|
|
3645
|
+
real: "number",
|
|
3646
|
+
float: "number",
|
|
3647
|
+
double: "number",
|
|
3648
|
+
numeric: "number",
|
|
3649
|
+
decimal: "number"
|
|
1335
3650
|
};
|
|
1336
3651
|
return typeMap[drizzleType.toLowerCase()] || "string";
|
|
1337
3652
|
}
|
|
@@ -1374,10 +3689,32 @@ function createSchemaAdapter(options) {
|
|
|
1374
3689
|
case "custom":
|
|
1375
3690
|
return options.adapter;
|
|
1376
3691
|
default:
|
|
1377
|
-
throw new Error(
|
|
3692
|
+
throw new Error(
|
|
3693
|
+
`Unsupported schema adapter type: ${options.type}`
|
|
3694
|
+
);
|
|
1378
3695
|
}
|
|
1379
3696
|
}
|
|
1380
3697
|
|
|
1381
|
-
|
|
1382
|
-
|
|
3698
|
+
// src/ai-utils.ts
|
|
3699
|
+
var generateComponent = async (prompt) => {
|
|
3700
|
+
console.warn(
|
|
3701
|
+
"generateComponent is a placeholder and will be implemented in a future version"
|
|
3702
|
+
);
|
|
3703
|
+
return `<div>Generated Component for: ${prompt}</div>`;
|
|
3704
|
+
};
|
|
3705
|
+
var generateUIDescription = async (prompt) => {
|
|
3706
|
+
console.warn(
|
|
3707
|
+
"generateUIDescription is a placeholder and will be implemented in a future version"
|
|
3708
|
+
);
|
|
3709
|
+
return `Description for ${prompt}`;
|
|
3710
|
+
};
|
|
3711
|
+
var generateUIComponent = async (prompt) => {
|
|
3712
|
+
console.warn(
|
|
3713
|
+
"generateUIComponent is a placeholder and will be implemented in a future version"
|
|
3714
|
+
);
|
|
3715
|
+
return `<div>Generated UI Component for: ${prompt}</div>`;
|
|
3716
|
+
};
|
|
3717
|
+
|
|
3718
|
+
export { ActionRouter, ActionType, AutoUI, DrizzleAdapter, SystemEventType, componentType, createEventHook, createSchemaAdapter, createSystemEvent, generateComponent, generateUIComponent, generateUIDescription, openAIUISpec, systemEvents, uiEvent, uiEventType, uiSpecNode };
|
|
3719
|
+
//# sourceMappingURL=out.js.map
|
|
1383
3720
|
//# sourceMappingURL=index.mjs.map
|