autoui-react 0.0.3-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 +244 -0
- package/dist/index.css +56 -0
- package/dist/index.css.map +1 -0
- package/dist/index.d.mts +546 -0
- package/dist/index.d.ts +546 -0
- package/dist/index.js +1466 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1449 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +65 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,1449 @@
|
|
|
1
|
+
import { useState, useEffect, useRef, useCallback, useReducer } from 'react';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
4
|
+
import { generateText } from 'ai';
|
|
5
|
+
import { openai } from '@ai-sdk/openai';
|
|
6
|
+
|
|
7
|
+
// src/core/reducer.ts
|
|
8
|
+
function cloneNode(node) {
|
|
9
|
+
return {
|
|
10
|
+
...node,
|
|
11
|
+
props: node.props ? { ...node.props } : void 0,
|
|
12
|
+
bindings: node.bindings ? { ...node.bindings } : void 0,
|
|
13
|
+
events: node.events ? { ...node.events } : void 0,
|
|
14
|
+
children: node.children?.map((child) => cloneNode(child))
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
function findNodeById(tree, nodeId) {
|
|
18
|
+
if (!tree)
|
|
19
|
+
return void 0;
|
|
20
|
+
if (tree.id === nodeId)
|
|
21
|
+
return tree;
|
|
22
|
+
if (tree.children) {
|
|
23
|
+
for (const child of tree.children) {
|
|
24
|
+
const found = findNodeById(child, nodeId);
|
|
25
|
+
if (found)
|
|
26
|
+
return found;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return void 0;
|
|
30
|
+
}
|
|
31
|
+
function updateNodeById(tree, nodeId, updater) {
|
|
32
|
+
const result = cloneNode(tree);
|
|
33
|
+
function findPath(node, id, currentPath = []) {
|
|
34
|
+
const newPath = [...currentPath, node];
|
|
35
|
+
if (node.id === id) {
|
|
36
|
+
return newPath;
|
|
37
|
+
}
|
|
38
|
+
if (node.children) {
|
|
39
|
+
for (const child of node.children) {
|
|
40
|
+
const path2 = findPath(child, id, newPath);
|
|
41
|
+
if (path2)
|
|
42
|
+
return path2;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
const path = findPath(result, nodeId);
|
|
48
|
+
if (!path)
|
|
49
|
+
return result;
|
|
50
|
+
const nodeToUpdate = path[path.length - 1];
|
|
51
|
+
const updatedNode = updater(nodeToUpdate);
|
|
52
|
+
if (path.length === 1) {
|
|
53
|
+
return updatedNode;
|
|
54
|
+
}
|
|
55
|
+
const parent = path[path.length - 2];
|
|
56
|
+
const updatedParent = {
|
|
57
|
+
...parent,
|
|
58
|
+
children: parent.children?.map(
|
|
59
|
+
(child) => child.id === nodeId ? updatedNode : child
|
|
60
|
+
)
|
|
61
|
+
};
|
|
62
|
+
if (path.length === 2) {
|
|
63
|
+
return updatedParent;
|
|
64
|
+
}
|
|
65
|
+
return updateNodeById(
|
|
66
|
+
result,
|
|
67
|
+
parent.id,
|
|
68
|
+
() => updatedParent
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
function replaceNodeById(tree, nodeId, newNode) {
|
|
72
|
+
return updateNodeById(tree, nodeId, () => newNode);
|
|
73
|
+
}
|
|
74
|
+
function addChildNode(tree, parentId, newChild, index) {
|
|
75
|
+
return updateNodeById(tree, parentId, (node) => {
|
|
76
|
+
const children = node.children ? [...node.children] : [];
|
|
77
|
+
if (index !== void 0 && index >= 0 && index <= children.length) {
|
|
78
|
+
children.splice(index, 0, newChild);
|
|
79
|
+
} else {
|
|
80
|
+
children.push(newChild);
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
...node,
|
|
84
|
+
children
|
|
85
|
+
};
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
function removeNodeById(tree, nodeId) {
|
|
89
|
+
function findParent(node, id) {
|
|
90
|
+
if (node.children) {
|
|
91
|
+
if (node.children.some((child) => child.id === id)) {
|
|
92
|
+
return node;
|
|
93
|
+
}
|
|
94
|
+
for (const child of node.children) {
|
|
95
|
+
const parent2 = findParent(child, id);
|
|
96
|
+
if (parent2)
|
|
97
|
+
return parent2;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
const result = cloneNode(tree);
|
|
103
|
+
if (result.id === nodeId) {
|
|
104
|
+
throw new Error("Cannot remove root node");
|
|
105
|
+
}
|
|
106
|
+
const parent = findParent(result, nodeId);
|
|
107
|
+
if (!parent)
|
|
108
|
+
return result;
|
|
109
|
+
return updateNodeById(result, parent.id, (node) => ({
|
|
110
|
+
...node,
|
|
111
|
+
children: node.children?.filter((child) => child.id !== nodeId)
|
|
112
|
+
}));
|
|
113
|
+
}
|
|
114
|
+
function uiReducer(state, action) {
|
|
115
|
+
switch (action.type) {
|
|
116
|
+
case "UI_EVENT": {
|
|
117
|
+
return {
|
|
118
|
+
...state,
|
|
119
|
+
loading: true,
|
|
120
|
+
history: [...state.history, action.event]
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
case "AI_RESPONSE": {
|
|
124
|
+
return {
|
|
125
|
+
...state,
|
|
126
|
+
layout: action.node,
|
|
127
|
+
loading: false,
|
|
128
|
+
error: void 0
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
case "PARTIAL_UPDATE": {
|
|
132
|
+
if (!state.layout) {
|
|
133
|
+
return {
|
|
134
|
+
...state,
|
|
135
|
+
layout: action.node,
|
|
136
|
+
loading: false,
|
|
137
|
+
error: void 0
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
if (action.nodeId === "root" || action.nodeId === state.layout.id) {
|
|
141
|
+
return {
|
|
142
|
+
...state,
|
|
143
|
+
layout: action.node,
|
|
144
|
+
loading: false,
|
|
145
|
+
error: void 0
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
return {
|
|
149
|
+
...state,
|
|
150
|
+
layout: replaceNodeById(state.layout, action.nodeId, action.node),
|
|
151
|
+
loading: false,
|
|
152
|
+
error: void 0
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
case "ADD_NODE": {
|
|
156
|
+
if (!state.layout) {
|
|
157
|
+
return state;
|
|
158
|
+
}
|
|
159
|
+
return {
|
|
160
|
+
...state,
|
|
161
|
+
layout: addChildNode(
|
|
162
|
+
state.layout,
|
|
163
|
+
action.parentId,
|
|
164
|
+
action.node,
|
|
165
|
+
action.index
|
|
166
|
+
),
|
|
167
|
+
loading: false,
|
|
168
|
+
error: void 0
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
case "REMOVE_NODE": {
|
|
172
|
+
if (!state.layout) {
|
|
173
|
+
return state;
|
|
174
|
+
}
|
|
175
|
+
return {
|
|
176
|
+
...state,
|
|
177
|
+
layout: removeNodeById(state.layout, action.nodeId),
|
|
178
|
+
loading: false,
|
|
179
|
+
error: void 0
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
case "LOADING": {
|
|
183
|
+
return {
|
|
184
|
+
...state,
|
|
185
|
+
loading: action.isLoading
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
case "ERROR": {
|
|
189
|
+
return {
|
|
190
|
+
...state,
|
|
191
|
+
error: action.message,
|
|
192
|
+
loading: false
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
default:
|
|
196
|
+
return state;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
var initialState = {
|
|
200
|
+
loading: true,
|
|
201
|
+
history: []
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
// src/core/action-router.ts
|
|
205
|
+
var ActionType = /* @__PURE__ */ ((ActionType2) => {
|
|
206
|
+
ActionType2["FULL_REFRESH"] = "FULL_REFRESH";
|
|
207
|
+
ActionType2["UPDATE_NODE"] = "UPDATE_NODE";
|
|
208
|
+
ActionType2["ADD_DROPDOWN"] = "ADD_DROPDOWN";
|
|
209
|
+
ActionType2["SHOW_DETAIL"] = "SHOW_DETAIL";
|
|
210
|
+
ActionType2["HIDE_DETAIL"] = "HIDE_DETAIL";
|
|
211
|
+
ActionType2["TOGGLE_STATE"] = "TOGGLE_STATE";
|
|
212
|
+
ActionType2["UPDATE_FORM"] = "UPDATE_FORM";
|
|
213
|
+
ActionType2["NAVIGATE"] = "NAVIGATE";
|
|
214
|
+
return ActionType2;
|
|
215
|
+
})(ActionType || {});
|
|
216
|
+
var ActionRouter = class {
|
|
217
|
+
constructor() {
|
|
218
|
+
this.routes = {};
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Register a new action route
|
|
222
|
+
* @param eventType - UI event type to route
|
|
223
|
+
* @param config - Route configuration
|
|
224
|
+
*/
|
|
225
|
+
registerRoute(eventType, config) {
|
|
226
|
+
if (!this.routes[eventType]) {
|
|
227
|
+
this.routes[eventType] = [];
|
|
228
|
+
}
|
|
229
|
+
this.routes[eventType].push(config);
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Find the appropriate route for an event
|
|
233
|
+
* @param event - UI event
|
|
234
|
+
* @param layout - Current UI layout
|
|
235
|
+
* @param dataContext - Current data context
|
|
236
|
+
* @returns Route resolution or null if no match
|
|
237
|
+
*/
|
|
238
|
+
resolveRoute(event, schema, layout, dataContext, goal, userContext) {
|
|
239
|
+
const routes = this.routes[event.type] || [];
|
|
240
|
+
if (routes.length === 0) {
|
|
241
|
+
return {
|
|
242
|
+
actionType: "FULL_REFRESH" /* FULL_REFRESH */,
|
|
243
|
+
targetNodeId: layout?.id || "root",
|
|
244
|
+
plannerInput: {
|
|
245
|
+
schema,
|
|
246
|
+
goal,
|
|
247
|
+
history: [event],
|
|
248
|
+
userContext
|
|
249
|
+
},
|
|
250
|
+
prompt: `Generate a new UI for the goal: "${goal}". The user just triggered: ${event.type} on node ${event.nodeId}`
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
const sourceNode = layout ? findNodeById(layout, event.nodeId) : void 0;
|
|
254
|
+
const nodeConfig = sourceNode?.events?.[event.type];
|
|
255
|
+
let matchingRoute;
|
|
256
|
+
if (nodeConfig) {
|
|
257
|
+
matchingRoute = routes.find(
|
|
258
|
+
(route) => route.actionType.toString() === nodeConfig.action
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
if (!matchingRoute) {
|
|
262
|
+
matchingRoute = routes[0];
|
|
263
|
+
}
|
|
264
|
+
const targetNodeId = nodeConfig?.target || matchingRoute.targetNodeId || event.nodeId;
|
|
265
|
+
const additionalContext = {};
|
|
266
|
+
if (matchingRoute.contextKeys) {
|
|
267
|
+
matchingRoute.contextKeys.forEach((key) => {
|
|
268
|
+
additionalContext[key] = dataContext[key];
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
if (sourceNode) {
|
|
272
|
+
additionalContext.sourceNode = sourceNode;
|
|
273
|
+
}
|
|
274
|
+
if (layout) {
|
|
275
|
+
const targetNode = findNodeById(layout, targetNodeId);
|
|
276
|
+
if (targetNode) {
|
|
277
|
+
additionalContext.targetNode = targetNode;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
if (event.payload) {
|
|
281
|
+
additionalContext.eventPayload = event.payload;
|
|
282
|
+
}
|
|
283
|
+
if (nodeConfig?.payload) {
|
|
284
|
+
Object.entries(nodeConfig.payload).forEach(([key, value]) => {
|
|
285
|
+
additionalContext[key] = value;
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
const plannerInput2 = {
|
|
289
|
+
schema,
|
|
290
|
+
goal,
|
|
291
|
+
history: [event],
|
|
292
|
+
userContext: {
|
|
293
|
+
...userContext,
|
|
294
|
+
...additionalContext
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
const prompt = this.processTemplate(
|
|
298
|
+
matchingRoute.promptTemplate,
|
|
299
|
+
{
|
|
300
|
+
goal,
|
|
301
|
+
eventType: event.type,
|
|
302
|
+
nodeId: event.nodeId,
|
|
303
|
+
targetNodeId,
|
|
304
|
+
actionType: matchingRoute.actionType,
|
|
305
|
+
...additionalContext
|
|
306
|
+
}
|
|
307
|
+
);
|
|
308
|
+
return {
|
|
309
|
+
actionType: matchingRoute.actionType,
|
|
310
|
+
targetNodeId,
|
|
311
|
+
plannerInput: plannerInput2,
|
|
312
|
+
prompt
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Process a prompt template with variables
|
|
317
|
+
* @param template - Template string with ${var} placeholders
|
|
318
|
+
* @param values - Values to substitute
|
|
319
|
+
* @returns Processed string
|
|
320
|
+
*/
|
|
321
|
+
processTemplate(template, values) {
|
|
322
|
+
return template.replace(/\${(\w+)}/g, (_, key) => {
|
|
323
|
+
return values[key] !== void 0 ? String(values[key]) : `\${${key}}`;
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
function createDefaultRouter() {
|
|
328
|
+
const router = new ActionRouter();
|
|
329
|
+
router.registerRoute("CLICK", {
|
|
330
|
+
actionType: "FULL_REFRESH" /* FULL_REFRESH */,
|
|
331
|
+
targetNodeId: "root",
|
|
332
|
+
promptTemplate: 'Generate a new UI for the goal: "${goal}". The user just clicked on node ${nodeId}'
|
|
333
|
+
});
|
|
334
|
+
router.registerRoute("CLICK", {
|
|
335
|
+
actionType: "SHOW_DETAIL" /* SHOW_DETAIL */,
|
|
336
|
+
targetNodeId: "${targetNodeId}",
|
|
337
|
+
promptTemplate: "Update the UI to show details for the selected item. Current node: ${nodeId}, Target node: ${targetNodeId}",
|
|
338
|
+
contextKeys: ["selected"]
|
|
339
|
+
});
|
|
340
|
+
router.registerRoute("CLICK", {
|
|
341
|
+
actionType: "NAVIGATE" /* NAVIGATE */,
|
|
342
|
+
targetNodeId: "root",
|
|
343
|
+
promptTemplate: "Navigate to a new view based on the user clicking ${nodeId}. Current goal: ${goal}"
|
|
344
|
+
});
|
|
345
|
+
router.registerRoute("CLICK", {
|
|
346
|
+
actionType: "ADD_DROPDOWN" /* ADD_DROPDOWN */,
|
|
347
|
+
targetNodeId: "${targetNodeId}",
|
|
348
|
+
promptTemplate: "Add a dropdown menu to node ${targetNodeId} with options relevant to the clicked element ${nodeId}"
|
|
349
|
+
});
|
|
350
|
+
router.registerRoute("CLICK", {
|
|
351
|
+
actionType: "TOGGLE_STATE" /* TOGGLE_STATE */,
|
|
352
|
+
targetNodeId: "${nodeId}",
|
|
353
|
+
promptTemplate: "Toggle the state of node ${nodeId} (e.g., expanded/collapsed, selected/unselected)"
|
|
354
|
+
});
|
|
355
|
+
router.registerRoute("CHANGE", {
|
|
356
|
+
actionType: "UPDATE_FORM" /* UPDATE_FORM */,
|
|
357
|
+
targetNodeId: "${targetNodeId}",
|
|
358
|
+
promptTemplate: "Update the form based on the user changing the value of ${nodeId}"
|
|
359
|
+
});
|
|
360
|
+
return router;
|
|
361
|
+
}
|
|
362
|
+
var uiEventType = z.enum([
|
|
363
|
+
"CLICK",
|
|
364
|
+
"CHANGE",
|
|
365
|
+
"SUBMIT",
|
|
366
|
+
"MOUSEOVER",
|
|
367
|
+
"MOUSEOUT",
|
|
368
|
+
"FOCUS",
|
|
369
|
+
"BLUR"
|
|
370
|
+
]);
|
|
371
|
+
var uiEvent = z.object({
|
|
372
|
+
type: uiEventType,
|
|
373
|
+
nodeId: z.string(),
|
|
374
|
+
timestamp: z.number().optional(),
|
|
375
|
+
payload: z.record(z.any()).optional()
|
|
376
|
+
});
|
|
377
|
+
z.enum([
|
|
378
|
+
"AI_RESPONSE",
|
|
379
|
+
"ERROR"
|
|
380
|
+
]);
|
|
381
|
+
var uiSpecNode = z.lazy(() => z.object({
|
|
382
|
+
id: z.string(),
|
|
383
|
+
type: z.string(),
|
|
384
|
+
// e.g., "ListView", "Button", "TextField"
|
|
385
|
+
props: z.record(z.any()).optional(),
|
|
386
|
+
bindings: z.record(z.any()).optional(),
|
|
387
|
+
// Data bindings
|
|
388
|
+
events: z.record(z.string(), z.object({
|
|
389
|
+
action: z.string(),
|
|
390
|
+
target: z.string().optional(),
|
|
391
|
+
payload: z.record(z.any()).optional()
|
|
392
|
+
})).optional(),
|
|
393
|
+
children: z.array(uiSpecNode).optional()
|
|
394
|
+
}));
|
|
395
|
+
z.discriminatedUnion("type", [
|
|
396
|
+
z.object({
|
|
397
|
+
type: z.literal("UI_EVENT"),
|
|
398
|
+
event: uiEvent
|
|
399
|
+
}),
|
|
400
|
+
z.object({
|
|
401
|
+
type: z.literal("AI_RESPONSE"),
|
|
402
|
+
node: uiSpecNode
|
|
403
|
+
}),
|
|
404
|
+
z.object({
|
|
405
|
+
type: z.literal("PARTIAL_UPDATE"),
|
|
406
|
+
nodeId: z.string(),
|
|
407
|
+
node: uiSpecNode
|
|
408
|
+
}),
|
|
409
|
+
z.object({
|
|
410
|
+
type: z.literal("ADD_NODE"),
|
|
411
|
+
parentId: z.string(),
|
|
412
|
+
node: uiSpecNode,
|
|
413
|
+
index: z.number().optional()
|
|
414
|
+
}),
|
|
415
|
+
z.object({
|
|
416
|
+
type: z.literal("REMOVE_NODE"),
|
|
417
|
+
nodeId: z.string()
|
|
418
|
+
}),
|
|
419
|
+
z.object({
|
|
420
|
+
type: z.literal("ERROR"),
|
|
421
|
+
message: z.string()
|
|
422
|
+
}),
|
|
423
|
+
z.object({
|
|
424
|
+
type: z.literal("LOADING"),
|
|
425
|
+
isLoading: z.boolean()
|
|
426
|
+
})
|
|
427
|
+
]);
|
|
428
|
+
z.object({
|
|
429
|
+
layout: uiSpecNode.optional(),
|
|
430
|
+
loading: z.boolean(),
|
|
431
|
+
history: z.array(uiEvent),
|
|
432
|
+
error: z.string().optional()
|
|
433
|
+
});
|
|
434
|
+
z.object({
|
|
435
|
+
schema: z.record(z.unknown()),
|
|
436
|
+
goal: z.string(),
|
|
437
|
+
history: z.array(uiEvent).optional(),
|
|
438
|
+
userContext: z.record(z.unknown()).optional()
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
// src/core/system-events.ts
|
|
442
|
+
var SystemEventType = /* @__PURE__ */ ((SystemEventType2) => {
|
|
443
|
+
SystemEventType2["PLAN_START"] = "PLAN_START";
|
|
444
|
+
SystemEventType2["PLAN_PROMPT_CREATED"] = "PLAN_PROMPT_CREATED";
|
|
445
|
+
SystemEventType2["PLAN_RESPONSE_CHUNK"] = "PLAN_RESPONSE_CHUNK";
|
|
446
|
+
SystemEventType2["PLAN_COMPLETE"] = "PLAN_COMPLETE";
|
|
447
|
+
SystemEventType2["PLAN_ERROR"] = "PLAN_ERROR";
|
|
448
|
+
SystemEventType2["BINDING_RESOLUTION_START"] = "BINDING_RESOLUTION_START";
|
|
449
|
+
SystemEventType2["BINDING_RESOLUTION_COMPLETE"] = "BINDING_RESOLUTION_COMPLETE";
|
|
450
|
+
SystemEventType2["DATA_FETCH_START"] = "DATA_FETCH_START";
|
|
451
|
+
SystemEventType2["DATA_FETCH_COMPLETE"] = "DATA_FETCH_COMPLETE";
|
|
452
|
+
SystemEventType2["RENDER_START"] = "RENDER_START";
|
|
453
|
+
SystemEventType2["RENDER_COMPLETE"] = "RENDER_COMPLETE";
|
|
454
|
+
SystemEventType2["PREFETCH_START"] = "PREFETCH_START";
|
|
455
|
+
SystemEventType2["PREFETCH_COMPLETE"] = "PREFETCH_COMPLETE";
|
|
456
|
+
return SystemEventType2;
|
|
457
|
+
})(SystemEventType || {});
|
|
458
|
+
var SystemEventManager = class {
|
|
459
|
+
constructor() {
|
|
460
|
+
this.listeners = {};
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* Register a listener for a specific system event type
|
|
464
|
+
*
|
|
465
|
+
* @param eventType - The system event type to listen for
|
|
466
|
+
* @param listener - The listener function
|
|
467
|
+
* @returns Function to unregister the listener
|
|
468
|
+
*/
|
|
469
|
+
on(eventType, listener) {
|
|
470
|
+
if (!this.listeners[eventType]) {
|
|
471
|
+
this.listeners[eventType] = [];
|
|
472
|
+
}
|
|
473
|
+
this.listeners[eventType]?.push(listener);
|
|
474
|
+
return () => {
|
|
475
|
+
if (this.listeners[eventType]) {
|
|
476
|
+
this.listeners[eventType] = this.listeners[eventType]?.filter(
|
|
477
|
+
(l) => l !== listener
|
|
478
|
+
);
|
|
479
|
+
}
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
/**
|
|
483
|
+
* Emit a system event to all registered listeners
|
|
484
|
+
*
|
|
485
|
+
* @param event - The system event to emit
|
|
486
|
+
*/
|
|
487
|
+
async emit(event) {
|
|
488
|
+
const listeners = this.listeners[event.type] || [];
|
|
489
|
+
for (const listener of listeners) {
|
|
490
|
+
await listener(event);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
};
|
|
494
|
+
var systemEvents = new SystemEventManager();
|
|
495
|
+
function createSystemEvent(type, data) {
|
|
496
|
+
return {
|
|
497
|
+
type,
|
|
498
|
+
timestamp: Date.now(),
|
|
499
|
+
...data
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// src/env.ts
|
|
504
|
+
({
|
|
505
|
+
MOCK_PLANNER: import.meta.env?.VITE_MOCK_PLANNER || "1",
|
|
506
|
+
NODE_ENV: import.meta.env?.MODE || "development"
|
|
507
|
+
// Add other environment variables as needed
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
// src/core/planner.ts
|
|
511
|
+
function buildPrompt(input, targetNodeId, customPrompt) {
|
|
512
|
+
const { schema, goal, history, userContext } = input;
|
|
513
|
+
const schemaInfo = Object.entries(schema).map(([tableName, tableSchema]) => {
|
|
514
|
+
return `Table: ${tableName}
|
|
515
|
+
Schema: ${JSON.stringify(tableSchema)}`;
|
|
516
|
+
}).join("\n\n");
|
|
517
|
+
const recentEvents = history?.slice(-5).map(
|
|
518
|
+
(event) => `Event: ${event.type} on node ${event.nodeId}${event.payload ? ` with payload ${JSON.stringify(event.payload)}` : ""}`
|
|
519
|
+
).join("\n") || "No recent events";
|
|
520
|
+
const userContextSection = userContext ? `
|
|
521
|
+
|
|
522
|
+
User Context:
|
|
523
|
+
${JSON.stringify(userContext)}` : "";
|
|
524
|
+
return `
|
|
525
|
+
You are an expert UI generator.
|
|
526
|
+
Create a user interface that achieves the following goal: "${goal}"
|
|
527
|
+
|
|
528
|
+
Available data schema:
|
|
529
|
+
${schemaInfo}
|
|
530
|
+
|
|
531
|
+
Recent user interactions:
|
|
532
|
+
${recentEvents}${userContextSection}
|
|
533
|
+
|
|
534
|
+
Generate a complete UI specification in JSON format that matches the following TypeScript type:
|
|
535
|
+
type UISpecNode = {
|
|
536
|
+
id: string;
|
|
537
|
+
type: string;
|
|
538
|
+
props?: Record<string, any>;
|
|
539
|
+
bindings?: Record<string, any>;
|
|
540
|
+
events?: Record<string, { action: string; target?: string; payload?: Record<string, any>; }>;
|
|
541
|
+
children?: UISpecNode[];
|
|
542
|
+
};
|
|
543
|
+
|
|
544
|
+
UI Guidance:
|
|
545
|
+
1. Create a focused interface that directly addresses the goal
|
|
546
|
+
2. Use appropriate UI patterns (lists, forms, details, etc.)
|
|
547
|
+
3. Include navigation between related views when needed
|
|
548
|
+
4. Keep the interface simple and intuitive
|
|
549
|
+
5. Bind to schema data where appropriate
|
|
550
|
+
6. Provide event handlers for user interactions
|
|
551
|
+
|
|
552
|
+
Respond ONLY with the JSON UI specification and no other text.
|
|
553
|
+
`;
|
|
554
|
+
}
|
|
555
|
+
function mockPlanner(input, targetNodeId, customPrompt) {
|
|
556
|
+
const mockNode = {
|
|
557
|
+
id: targetNodeId || "root",
|
|
558
|
+
type: "Container",
|
|
559
|
+
props: { title: "Mock UI" },
|
|
560
|
+
children: [
|
|
561
|
+
{
|
|
562
|
+
id: "text-1",
|
|
563
|
+
type: "Text",
|
|
564
|
+
props: { text: "This is a mock UI for testing" }
|
|
565
|
+
}
|
|
566
|
+
]
|
|
567
|
+
};
|
|
568
|
+
return mockNode;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// src/core/state.ts
|
|
572
|
+
var useChat = (config) => ({
|
|
573
|
+
append: async (message) => {
|
|
574
|
+
},
|
|
575
|
+
data: { content: "{}" },
|
|
576
|
+
isLoading: false,
|
|
577
|
+
error: null,
|
|
578
|
+
stop: () => {
|
|
579
|
+
}
|
|
580
|
+
});
|
|
581
|
+
function useUIStateEngine({
|
|
582
|
+
schema,
|
|
583
|
+
goal,
|
|
584
|
+
userContext,
|
|
585
|
+
mockMode = false,
|
|
586
|
+
planningConfig = {},
|
|
587
|
+
router = createDefaultRouter(),
|
|
588
|
+
dataContext = {},
|
|
589
|
+
enablePartialUpdates = false
|
|
590
|
+
}) {
|
|
591
|
+
const [state, dispatch] = useReducer(uiReducer, initialState);
|
|
592
|
+
const { append, data, isLoading, error, stop } = useChat();
|
|
593
|
+
const handleEvent = useCallback((event) => {
|
|
594
|
+
dispatch({ type: "UI_EVENT", event });
|
|
595
|
+
stop();
|
|
596
|
+
if (enablePartialUpdates) {
|
|
597
|
+
const route = router.resolveRoute(
|
|
598
|
+
event,
|
|
599
|
+
schema,
|
|
600
|
+
state.layout,
|
|
601
|
+
dataContext,
|
|
602
|
+
goal,
|
|
603
|
+
userContext
|
|
604
|
+
);
|
|
605
|
+
if (route) {
|
|
606
|
+
console.log("Resolved route:", route);
|
|
607
|
+
systemEvents.emit(
|
|
608
|
+
createSystemEvent("PLAN_START" /* PLAN_START */, {
|
|
609
|
+
plannerInput: route.plannerInput
|
|
610
|
+
})
|
|
611
|
+
);
|
|
612
|
+
if (mockMode) {
|
|
613
|
+
const node = mockPlanner(route.plannerInput, route.targetNodeId, route.prompt);
|
|
614
|
+
switch (route.actionType) {
|
|
615
|
+
case "FULL_REFRESH" /* FULL_REFRESH */:
|
|
616
|
+
dispatch({ type: "AI_RESPONSE", node });
|
|
617
|
+
break;
|
|
618
|
+
case "UPDATE_NODE" /* UPDATE_NODE */:
|
|
619
|
+
case "SHOW_DETAIL" /* SHOW_DETAIL */:
|
|
620
|
+
case "HIDE_DETAIL" /* HIDE_DETAIL */:
|
|
621
|
+
case "TOGGLE_STATE" /* TOGGLE_STATE */:
|
|
622
|
+
case "ADD_DROPDOWN" /* ADD_DROPDOWN */:
|
|
623
|
+
case "UPDATE_FORM" /* UPDATE_FORM */:
|
|
624
|
+
case "NAVIGATE" /* NAVIGATE */:
|
|
625
|
+
dispatch({
|
|
626
|
+
type: "PARTIAL_UPDATE",
|
|
627
|
+
nodeId: route.targetNodeId,
|
|
628
|
+
node
|
|
629
|
+
});
|
|
630
|
+
break;
|
|
631
|
+
}
|
|
632
|
+
} else {
|
|
633
|
+
const prompt = route.prompt;
|
|
634
|
+
systemEvents.emit(
|
|
635
|
+
createSystemEvent("PLAN_PROMPT_CREATED" /* PLAN_PROMPT_CREATED */, { prompt })
|
|
636
|
+
);
|
|
637
|
+
append({
|
|
638
|
+
content: prompt,
|
|
639
|
+
role: "user"
|
|
640
|
+
});
|
|
641
|
+
sessionStorage.setItem("currentRoute", JSON.stringify({
|
|
642
|
+
actionType: route.actionType,
|
|
643
|
+
targetNodeId: route.targetNodeId
|
|
644
|
+
}));
|
|
645
|
+
}
|
|
646
|
+
return;
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
const input = {
|
|
650
|
+
schema,
|
|
651
|
+
goal,
|
|
652
|
+
history: [...state.history, event],
|
|
653
|
+
userContext
|
|
654
|
+
};
|
|
655
|
+
if (mockMode) {
|
|
656
|
+
const node = mockPlanner();
|
|
657
|
+
dispatch({ type: "AI_RESPONSE", node });
|
|
658
|
+
} else {
|
|
659
|
+
const prompt = buildPrompt(input);
|
|
660
|
+
append({
|
|
661
|
+
content: prompt,
|
|
662
|
+
role: "user"
|
|
663
|
+
});
|
|
664
|
+
}
|
|
665
|
+
}, [append, goal, schema, state.history, state.layout, stop, userContext, router, mockMode, dataContext, enablePartialUpdates]);
|
|
666
|
+
useEffect(() => {
|
|
667
|
+
if (isLoading) {
|
|
668
|
+
dispatch({ type: "LOADING", isLoading: true });
|
|
669
|
+
} else if (error) {
|
|
670
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
671
|
+
dispatch({ type: "ERROR", message: errorMessage });
|
|
672
|
+
systemEvents.emit(
|
|
673
|
+
createSystemEvent("PLAN_ERROR" /* PLAN_ERROR */, {
|
|
674
|
+
error: error instanceof Error ? error : new Error(String(error))
|
|
675
|
+
})
|
|
676
|
+
);
|
|
677
|
+
} else if (data.content) {
|
|
678
|
+
try {
|
|
679
|
+
systemEvents.emit(
|
|
680
|
+
createSystemEvent("PLAN_RESPONSE_CHUNK" /* PLAN_RESPONSE_CHUNK */, {
|
|
681
|
+
chunk: data.content,
|
|
682
|
+
isComplete: true
|
|
683
|
+
})
|
|
684
|
+
);
|
|
685
|
+
const jsonMatch = data.content.match(/```(?:json)?\s*([\s\S]*?)\s*```/) || [null, data.content];
|
|
686
|
+
const jsonStr = jsonMatch[1].trim();
|
|
687
|
+
const parsedJson = JSON.parse(jsonStr);
|
|
688
|
+
const validatedNode = uiSpecNode.parse(parsedJson);
|
|
689
|
+
const routeInfoStr = sessionStorage.getItem("currentRoute");
|
|
690
|
+
if (routeInfoStr && enablePartialUpdates) {
|
|
691
|
+
try {
|
|
692
|
+
const routeInfo = JSON.parse(routeInfoStr);
|
|
693
|
+
switch (routeInfo.actionType) {
|
|
694
|
+
case "FULL_REFRESH" /* FULL_REFRESH */:
|
|
695
|
+
dispatch({ type: "AI_RESPONSE", node: validatedNode });
|
|
696
|
+
break;
|
|
697
|
+
case "UPDATE_NODE" /* UPDATE_NODE */:
|
|
698
|
+
case "SHOW_DETAIL" /* SHOW_DETAIL */:
|
|
699
|
+
case "HIDE_DETAIL" /* HIDE_DETAIL */:
|
|
700
|
+
case "TOGGLE_STATE" /* TOGGLE_STATE */:
|
|
701
|
+
case "ADD_DROPDOWN" /* ADD_DROPDOWN */:
|
|
702
|
+
case "UPDATE_FORM" /* UPDATE_FORM */:
|
|
703
|
+
case "NAVIGATE" /* NAVIGATE */:
|
|
704
|
+
dispatch({
|
|
705
|
+
type: "PARTIAL_UPDATE",
|
|
706
|
+
nodeId: routeInfo.targetNodeId,
|
|
707
|
+
node: validatedNode
|
|
708
|
+
});
|
|
709
|
+
break;
|
|
710
|
+
default:
|
|
711
|
+
dispatch({ type: "AI_RESPONSE", node: validatedNode });
|
|
712
|
+
}
|
|
713
|
+
sessionStorage.removeItem("currentRoute");
|
|
714
|
+
} catch (e) {
|
|
715
|
+
console.error("Error parsing route info:", e);
|
|
716
|
+
dispatch({ type: "AI_RESPONSE", node: validatedNode });
|
|
717
|
+
}
|
|
718
|
+
} else {
|
|
719
|
+
dispatch({ type: "AI_RESPONSE", node: validatedNode });
|
|
720
|
+
}
|
|
721
|
+
systemEvents.emit(
|
|
722
|
+
createSystemEvent("PLAN_COMPLETE" /* PLAN_COMPLETE */, {
|
|
723
|
+
layout: validatedNode,
|
|
724
|
+
executionTimeMs: 0
|
|
725
|
+
// Not available here
|
|
726
|
+
})
|
|
727
|
+
);
|
|
728
|
+
} catch (parseError) {
|
|
729
|
+
console.error("Failed to parse LLM response:", parseError);
|
|
730
|
+
dispatch({
|
|
731
|
+
type: "ERROR",
|
|
732
|
+
message: "Failed to parse LLM response"
|
|
733
|
+
});
|
|
734
|
+
systemEvents.emit(
|
|
735
|
+
createSystemEvent("PLAN_ERROR" /* PLAN_ERROR */, {
|
|
736
|
+
error: parseError instanceof Error ? parseError : new Error("Parse error")
|
|
737
|
+
})
|
|
738
|
+
);
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
}, [data.content, error, isLoading, enablePartialUpdates]);
|
|
742
|
+
useEffect(() => {
|
|
743
|
+
const input = {
|
|
744
|
+
schema,
|
|
745
|
+
goal,
|
|
746
|
+
history: [],
|
|
747
|
+
userContext
|
|
748
|
+
};
|
|
749
|
+
if (mockMode) {
|
|
750
|
+
const node = mockPlanner();
|
|
751
|
+
dispatch({ type: "AI_RESPONSE", node });
|
|
752
|
+
} else {
|
|
753
|
+
const prompt = buildPrompt(input);
|
|
754
|
+
append({
|
|
755
|
+
content: prompt,
|
|
756
|
+
role: "user"
|
|
757
|
+
});
|
|
758
|
+
}
|
|
759
|
+
}, [append, goal, schema, userContext, mockMode]);
|
|
760
|
+
return {
|
|
761
|
+
state,
|
|
762
|
+
dispatch,
|
|
763
|
+
handleEvent
|
|
764
|
+
};
|
|
765
|
+
}
|
|
766
|
+
var ShimmerBlock = () => /* @__PURE__ */ jsx("div", { className: "w-full h-8 bg-gray-200 animate-pulse rounded" });
|
|
767
|
+
var ShimmerTable = ({ rows = 3 }) => /* @__PURE__ */ jsxs("div", { className: "w-full space-y-2", children: [
|
|
768
|
+
/* @__PURE__ */ jsx("div", { className: "w-full h-10 bg-gray-200 animate-pulse rounded" }),
|
|
769
|
+
Array.from({ length: rows }).map((_, i) => /* @__PURE__ */ jsx("div", { className: "w-full h-12 bg-gray-200 animate-pulse rounded" }, i))
|
|
770
|
+
] });
|
|
771
|
+
var ShimmerCard = () => /* @__PURE__ */ jsxs("div", { className: "w-full p-4 space-y-4 border rounded-lg", children: [
|
|
772
|
+
/* @__PURE__ */ jsx("div", { className: "w-3/4 h-6 bg-gray-200 animate-pulse rounded" }),
|
|
773
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
774
|
+
/* @__PURE__ */ jsx("div", { className: "w-full h-4 bg-gray-200 animate-pulse rounded" }),
|
|
775
|
+
/* @__PURE__ */ jsx("div", { className: "w-full h-4 bg-gray-200 animate-pulse rounded" }),
|
|
776
|
+
/* @__PURE__ */ jsx("div", { className: "w-5/6 h-4 bg-gray-200 animate-pulse rounded" })
|
|
777
|
+
] })
|
|
778
|
+
] });
|
|
779
|
+
var Container = (props) => /* @__PURE__ */ jsx("div", { className: `w-full ${props.className || ""}`, style: props.style, children: props.children });
|
|
780
|
+
var Header = ({ title }) => /* @__PURE__ */ jsx("header", { className: "py-4 px-6 border-b mb-4", children: /* @__PURE__ */ jsx("h1", { className: "text-xl font-semibold", children: title }) });
|
|
781
|
+
var Button = ({ onClick, children, variant = "default" }) => /* @__PURE__ */ jsx(
|
|
782
|
+
"button",
|
|
783
|
+
{
|
|
784
|
+
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"}`,
|
|
785
|
+
onClick,
|
|
786
|
+
children
|
|
787
|
+
}
|
|
788
|
+
);
|
|
789
|
+
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: [
|
|
790
|
+
/* @__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)) }) }),
|
|
791
|
+
/* @__PURE__ */ jsx("tbody", { className: "bg-white divide-y divide-gray-200", children: items.map((item, index) => /* @__PURE__ */ jsx(
|
|
792
|
+
"tr",
|
|
793
|
+
{
|
|
794
|
+
onClick: () => selectable && onSelect && onSelect(item),
|
|
795
|
+
className: selectable ? "cursor-pointer hover:bg-gray-50" : "",
|
|
796
|
+
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))
|
|
797
|
+
},
|
|
798
|
+
index
|
|
799
|
+
)) })
|
|
800
|
+
] }) });
|
|
801
|
+
var Detail = ({ data, fields = [], title, visible = true, onBack }) => {
|
|
802
|
+
if (!visible)
|
|
803
|
+
return null;
|
|
804
|
+
return /* @__PURE__ */ jsxs("div", { className: "w-full border rounded-lg p-6 space-y-4", children: [
|
|
805
|
+
/* @__PURE__ */ jsxs("div", { className: "flex justify-between items-center", children: [
|
|
806
|
+
title && /* @__PURE__ */ jsx("h2", { className: "text-lg font-medium", children: title }),
|
|
807
|
+
onBack && /* @__PURE__ */ jsx(Button, { variant: "outline", onClick: onBack, children: "Back" })
|
|
808
|
+
] }),
|
|
809
|
+
/* @__PURE__ */ jsx("div", { className: "space-y-4", children: fields.map((field) => {
|
|
810
|
+
if (field.type === "heading") {
|
|
811
|
+
return /* @__PURE__ */ jsx("h3", { className: "text-xl font-semibold", children: data?.[field.key] }, field.key);
|
|
812
|
+
}
|
|
813
|
+
if (field.type === "content") {
|
|
814
|
+
return /* @__PURE__ */ jsx("div", { className: "text-sm text-gray-700", children: data?.[field.key] }, field.key);
|
|
815
|
+
}
|
|
816
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col", children: [
|
|
817
|
+
field.label && /* @__PURE__ */ jsx("span", { className: "text-xs text-gray-500", children: field.label }),
|
|
818
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm", children: data?.[field.key] })
|
|
819
|
+
] }, field.key);
|
|
820
|
+
}) })
|
|
821
|
+
] });
|
|
822
|
+
};
|
|
823
|
+
var createEventHandler = (node, eventName) => {
|
|
824
|
+
const eventConfig = node.events?.[eventName];
|
|
825
|
+
if (!eventConfig)
|
|
826
|
+
return void 0;
|
|
827
|
+
return () => {
|
|
828
|
+
console.log(`Event triggered: ${eventName} on node ${node.id}`, {
|
|
829
|
+
action: eventConfig.action,
|
|
830
|
+
target: eventConfig.target,
|
|
831
|
+
payload: eventConfig.payload
|
|
832
|
+
});
|
|
833
|
+
};
|
|
834
|
+
};
|
|
835
|
+
var adapterMap = {
|
|
836
|
+
Container: (node) => /* @__PURE__ */ jsx(Container, { style: node.props?.style, className: node.props?.className, children: node.children?.map((child) => renderNode(child)) }),
|
|
837
|
+
Header: (node) => /* @__PURE__ */ jsx(Header, { title: node.props?.title || "Untitled" }),
|
|
838
|
+
Button: (node) => /* @__PURE__ */ jsx(
|
|
839
|
+
Button,
|
|
840
|
+
{
|
|
841
|
+
variant: node.props?.variant,
|
|
842
|
+
onClick: createEventHandler(node, "onClick"),
|
|
843
|
+
children: node.props?.label || "Button"
|
|
844
|
+
}
|
|
845
|
+
),
|
|
846
|
+
ListView: (node) => /* @__PURE__ */ jsx(
|
|
847
|
+
Table,
|
|
848
|
+
{
|
|
849
|
+
items: node.bindings?.items || [],
|
|
850
|
+
fields: node.bindings?.fields || [],
|
|
851
|
+
selectable: node.props?.selectable,
|
|
852
|
+
onSelect: createEventHandler(node, "onSelect")
|
|
853
|
+
}
|
|
854
|
+
),
|
|
855
|
+
Detail: (node) => /* @__PURE__ */ jsx(
|
|
856
|
+
Detail,
|
|
857
|
+
{
|
|
858
|
+
data: node.bindings?.data,
|
|
859
|
+
fields: node.bindings?.fields || [],
|
|
860
|
+
title: node.props?.title,
|
|
861
|
+
visible: node.props?.visible !== false,
|
|
862
|
+
onBack: createEventHandler(node, "onBack")
|
|
863
|
+
}
|
|
864
|
+
)
|
|
865
|
+
};
|
|
866
|
+
function renderNode(node) {
|
|
867
|
+
const Component = adapterMap[node.type];
|
|
868
|
+
if (Component) {
|
|
869
|
+
return Component(node);
|
|
870
|
+
}
|
|
871
|
+
return /* @__PURE__ */ jsxs("div", { className: "p-2 border border-red-300 rounded", children: [
|
|
872
|
+
/* @__PURE__ */ jsxs("p", { className: "text-sm text-red-500", children: [
|
|
873
|
+
"Unsupported component: ",
|
|
874
|
+
node.type
|
|
875
|
+
] }),
|
|
876
|
+
node.children?.map((child) => renderNode(child))
|
|
877
|
+
] });
|
|
878
|
+
}
|
|
879
|
+
async function renderNode2(node, adapter = "shadcn") {
|
|
880
|
+
const startTime = Date.now();
|
|
881
|
+
await systemEvents.emit(
|
|
882
|
+
createSystemEvent("RENDER_START" /* RENDER_START */, { layout: node })
|
|
883
|
+
);
|
|
884
|
+
let result;
|
|
885
|
+
switch (adapter) {
|
|
886
|
+
case "shadcn":
|
|
887
|
+
result = renderNode(node);
|
|
888
|
+
break;
|
|
889
|
+
default:
|
|
890
|
+
console.warn(`Unsupported adapter: ${adapter}, falling back to shadcn`);
|
|
891
|
+
result = renderNode(node);
|
|
892
|
+
}
|
|
893
|
+
await systemEvents.emit(
|
|
894
|
+
createSystemEvent("RENDER_COMPLETE" /* RENDER_COMPLETE */, {
|
|
895
|
+
layout: node,
|
|
896
|
+
renderTimeMs: Date.now() - startTime
|
|
897
|
+
})
|
|
898
|
+
);
|
|
899
|
+
return result;
|
|
900
|
+
}
|
|
901
|
+
function renderShimmer(node, adapter = "shadcn") {
|
|
902
|
+
if (!node) {
|
|
903
|
+
return /* @__PURE__ */ jsx(ShimmerBlock, {});
|
|
904
|
+
}
|
|
905
|
+
switch (node.type) {
|
|
906
|
+
case "ListView":
|
|
907
|
+
return /* @__PURE__ */ jsx(ShimmerTable, { rows: 3 });
|
|
908
|
+
case "Detail":
|
|
909
|
+
return /* @__PURE__ */ jsx(ShimmerCard, {});
|
|
910
|
+
case "Container":
|
|
911
|
+
return /* @__PURE__ */ jsx("div", { className: "space-y-4", children: node.children?.map((child, index) => /* @__PURE__ */ jsx("div", { children: renderShimmer(child, adapter) }, index)) });
|
|
912
|
+
default:
|
|
913
|
+
return /* @__PURE__ */ jsx(ShimmerBlock, {});
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
// src/core/bindings.ts
|
|
918
|
+
function getValueByPath(context, path) {
|
|
919
|
+
const parts = path.split(".");
|
|
920
|
+
let current = context;
|
|
921
|
+
for (const part of parts) {
|
|
922
|
+
if (current === null || current === void 0) {
|
|
923
|
+
return void 0;
|
|
924
|
+
}
|
|
925
|
+
if (typeof current !== "object") {
|
|
926
|
+
return void 0;
|
|
927
|
+
}
|
|
928
|
+
current = current[part];
|
|
929
|
+
}
|
|
930
|
+
return current;
|
|
931
|
+
}
|
|
932
|
+
function setValueByPath(context, path, value) {
|
|
933
|
+
const result = { ...context };
|
|
934
|
+
const parts = path.split(".");
|
|
935
|
+
let current = result;
|
|
936
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
937
|
+
const part = parts[i];
|
|
938
|
+
if (!(part in current) || current[part] === null || current[part] === void 0) {
|
|
939
|
+
current[part] = {};
|
|
940
|
+
}
|
|
941
|
+
current = current[part];
|
|
942
|
+
if (typeof current !== "object") {
|
|
943
|
+
current = {};
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
const lastPart = parts[parts.length - 1];
|
|
947
|
+
current[lastPart] = value;
|
|
948
|
+
return result;
|
|
949
|
+
}
|
|
950
|
+
function processBinding(binding, context) {
|
|
951
|
+
if (typeof binding === "string") {
|
|
952
|
+
return getValueByPath(context, binding);
|
|
953
|
+
}
|
|
954
|
+
if (Array.isArray(binding)) {
|
|
955
|
+
return binding.map((item) => processBinding(item, context));
|
|
956
|
+
}
|
|
957
|
+
if (binding !== null && typeof binding === "object") {
|
|
958
|
+
const result = {};
|
|
959
|
+
for (const [key, value] of Object.entries(binding)) {
|
|
960
|
+
result[key] = processBinding(value, context);
|
|
961
|
+
}
|
|
962
|
+
return result;
|
|
963
|
+
}
|
|
964
|
+
return binding;
|
|
965
|
+
}
|
|
966
|
+
async function resolveBindings(node, context) {
|
|
967
|
+
await systemEvents.emit(
|
|
968
|
+
createSystemEvent("BINDING_RESOLUTION_START" /* BINDING_RESOLUTION_START */, { layout: node })
|
|
969
|
+
);
|
|
970
|
+
const result = {
|
|
971
|
+
...node,
|
|
972
|
+
props: node.props ? { ...node.props } : void 0,
|
|
973
|
+
events: node.events ? { ...node.events } : void 0
|
|
974
|
+
};
|
|
975
|
+
if (node.bindings) {
|
|
976
|
+
for (const [key, binding] of Object.entries(node.bindings)) {
|
|
977
|
+
const value = processBinding(binding, context);
|
|
978
|
+
if (value !== void 0) {
|
|
979
|
+
if (!result.props) {
|
|
980
|
+
result.props = {};
|
|
981
|
+
}
|
|
982
|
+
result.props[key] = value;
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
if (node.children) {
|
|
987
|
+
result.children = await Promise.all(node.children.map((child) => resolveBindings(child, context)));
|
|
988
|
+
}
|
|
989
|
+
await systemEvents.emit(
|
|
990
|
+
createSystemEvent("BINDING_RESOLUTION_COMPLETE" /* BINDING_RESOLUTION_COMPLETE */, {
|
|
991
|
+
originalLayout: node,
|
|
992
|
+
resolvedLayout: result
|
|
993
|
+
})
|
|
994
|
+
);
|
|
995
|
+
return result;
|
|
996
|
+
}
|
|
997
|
+
function executeAction(action, targetId, payload, context = {}, layoutTree) {
|
|
998
|
+
let newContext = { ...context };
|
|
999
|
+
switch (action) {
|
|
1000
|
+
case "VIEW_DETAIL": {
|
|
1001
|
+
if (payload?.item) {
|
|
1002
|
+
newContext = setValueByPath(newContext, "selected", payload.item);
|
|
1003
|
+
}
|
|
1004
|
+
break;
|
|
1005
|
+
}
|
|
1006
|
+
case "HIDE_DETAIL": {
|
|
1007
|
+
newContext = setValueByPath(newContext, "selected", null);
|
|
1008
|
+
break;
|
|
1009
|
+
}
|
|
1010
|
+
case "SET_VALUE": {
|
|
1011
|
+
if (payload?.path && "value" in payload) {
|
|
1012
|
+
const path = String(payload.path);
|
|
1013
|
+
newContext = setValueByPath(newContext, path, payload.value);
|
|
1014
|
+
}
|
|
1015
|
+
break;
|
|
1016
|
+
}
|
|
1017
|
+
default:
|
|
1018
|
+
console.warn(`Unknown action: ${action}`);
|
|
1019
|
+
}
|
|
1020
|
+
return newContext;
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
// src/core/events.ts
|
|
1024
|
+
var EventManager = class {
|
|
1025
|
+
constructor() {
|
|
1026
|
+
this.hooks = {};
|
|
1027
|
+
}
|
|
1028
|
+
/**
|
|
1029
|
+
* Register a hook for specific event types
|
|
1030
|
+
*
|
|
1031
|
+
* @param eventTypes - Event types to register for, or 'all' for all events
|
|
1032
|
+
* @param hook - Hook function to execute
|
|
1033
|
+
* @returns Unregister function
|
|
1034
|
+
*/
|
|
1035
|
+
register(eventTypes, hook) {
|
|
1036
|
+
if (eventTypes === "all") {
|
|
1037
|
+
if (!this.hooks.all) {
|
|
1038
|
+
this.hooks.all = [];
|
|
1039
|
+
}
|
|
1040
|
+
this.hooks.all.push(hook);
|
|
1041
|
+
return () => {
|
|
1042
|
+
if (this.hooks.all) {
|
|
1043
|
+
this.hooks.all = this.hooks.all.filter((h) => h !== hook);
|
|
1044
|
+
}
|
|
1045
|
+
};
|
|
1046
|
+
}
|
|
1047
|
+
eventTypes.forEach((type) => {
|
|
1048
|
+
if (!this.hooks[type]) {
|
|
1049
|
+
this.hooks[type] = [];
|
|
1050
|
+
}
|
|
1051
|
+
this.hooks[type]?.push(hook);
|
|
1052
|
+
});
|
|
1053
|
+
return () => {
|
|
1054
|
+
eventTypes.forEach((type) => {
|
|
1055
|
+
if (this.hooks[type]) {
|
|
1056
|
+
this.hooks[type] = this.hooks[type]?.filter((h) => h !== hook);
|
|
1057
|
+
}
|
|
1058
|
+
});
|
|
1059
|
+
};
|
|
1060
|
+
}
|
|
1061
|
+
/**
|
|
1062
|
+
* Process an event through all registered hooks
|
|
1063
|
+
*
|
|
1064
|
+
* @param event - The UI event to process
|
|
1065
|
+
* @returns Whether the default action should proceed
|
|
1066
|
+
*/
|
|
1067
|
+
async processEvent(event) {
|
|
1068
|
+
let defaultPrevented = false;
|
|
1069
|
+
let propagationStopped = false;
|
|
1070
|
+
const context = {
|
|
1071
|
+
originalEvent: event,
|
|
1072
|
+
preventDefault: () => {
|
|
1073
|
+
defaultPrevented = true;
|
|
1074
|
+
},
|
|
1075
|
+
stopPropagation: () => {
|
|
1076
|
+
propagationStopped = true;
|
|
1077
|
+
},
|
|
1078
|
+
isDefaultPrevented: () => defaultPrevented,
|
|
1079
|
+
isPropagationStopped: () => propagationStopped
|
|
1080
|
+
};
|
|
1081
|
+
if (this.hooks.all) {
|
|
1082
|
+
for (const hook of this.hooks.all) {
|
|
1083
|
+
await hook(context);
|
|
1084
|
+
if (propagationStopped)
|
|
1085
|
+
break;
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
if (!propagationStopped && this.hooks[event.type]) {
|
|
1089
|
+
for (const hook of this.hooks[event.type] || []) {
|
|
1090
|
+
await hook(context);
|
|
1091
|
+
if (propagationStopped)
|
|
1092
|
+
break;
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
return !defaultPrevented;
|
|
1096
|
+
}
|
|
1097
|
+
};
|
|
1098
|
+
function createEventHook(eventTypes, hook, options) {
|
|
1099
|
+
return async (context) => {
|
|
1100
|
+
await hook(context);
|
|
1101
|
+
if (options?.preventDefault) {
|
|
1102
|
+
context.preventDefault();
|
|
1103
|
+
}
|
|
1104
|
+
if (options?.stopPropagation) {
|
|
1105
|
+
context.stopPropagation();
|
|
1106
|
+
}
|
|
1107
|
+
};
|
|
1108
|
+
}
|
|
1109
|
+
var AutoUI = ({
|
|
1110
|
+
schema,
|
|
1111
|
+
goal,
|
|
1112
|
+
componentAdapter = "shadcn",
|
|
1113
|
+
userContext,
|
|
1114
|
+
onEvent,
|
|
1115
|
+
eventHooks,
|
|
1116
|
+
systemEventHooks,
|
|
1117
|
+
debugMode = false,
|
|
1118
|
+
mockMode = true,
|
|
1119
|
+
databaseConfig,
|
|
1120
|
+
planningConfig,
|
|
1121
|
+
integration = {},
|
|
1122
|
+
scope = {},
|
|
1123
|
+
enablePartialUpdates = false
|
|
1124
|
+
}) => {
|
|
1125
|
+
const [schemaAdapterInstance, setSchemaAdapterInstance] = useState(null);
|
|
1126
|
+
const [dataContext, setDataContext] = useState({});
|
|
1127
|
+
const effectiveSchema = schema;
|
|
1128
|
+
const scopedGoal = goal;
|
|
1129
|
+
useEffect(() => {
|
|
1130
|
+
const unregisters = [];
|
|
1131
|
+
if (systemEventHooks) {
|
|
1132
|
+
Object.entries(systemEventHooks).forEach(([eventType, hooks]) => {
|
|
1133
|
+
if (!hooks)
|
|
1134
|
+
return;
|
|
1135
|
+
hooks.forEach((hook) => {
|
|
1136
|
+
const unregister = systemEvents.on(eventType, hook);
|
|
1137
|
+
unregisters.push(unregister);
|
|
1138
|
+
});
|
|
1139
|
+
});
|
|
1140
|
+
}
|
|
1141
|
+
if (debugMode) {
|
|
1142
|
+
const debugHook = (event) => {
|
|
1143
|
+
console.debug(`[AutoUI Debug] System Event:`, event);
|
|
1144
|
+
};
|
|
1145
|
+
Object.values(SystemEventType).forEach((eventType) => {
|
|
1146
|
+
const unregister = systemEvents.on(eventType, debugHook);
|
|
1147
|
+
unregisters.push(unregister);
|
|
1148
|
+
});
|
|
1149
|
+
}
|
|
1150
|
+
return () => {
|
|
1151
|
+
unregisters.forEach((unregister) => unregister());
|
|
1152
|
+
};
|
|
1153
|
+
}, [systemEventHooks, debugMode]);
|
|
1154
|
+
useEffect(() => {
|
|
1155
|
+
const initializeDataContext = async () => {
|
|
1156
|
+
let initialData = {};
|
|
1157
|
+
if (schemaAdapterInstance) {
|
|
1158
|
+
initialData = await schemaAdapterInstance.initializeDataContext();
|
|
1159
|
+
} else if (effectiveSchema) {
|
|
1160
|
+
Object.entries(effectiveSchema).forEach(([key, tableSchema]) => {
|
|
1161
|
+
initialData[key] = {
|
|
1162
|
+
schema: tableSchema,
|
|
1163
|
+
// For development, add sample data if available
|
|
1164
|
+
data: tableSchema?.sampleData || [],
|
|
1165
|
+
selected: null
|
|
1166
|
+
};
|
|
1167
|
+
});
|
|
1168
|
+
}
|
|
1169
|
+
if (userContext) {
|
|
1170
|
+
initialData.user = userContext;
|
|
1171
|
+
}
|
|
1172
|
+
setDataContext(initialData);
|
|
1173
|
+
};
|
|
1174
|
+
initializeDataContext();
|
|
1175
|
+
}, [effectiveSchema, schemaAdapterInstance, userContext]);
|
|
1176
|
+
const { state, handleEvent } = useUIStateEngine({
|
|
1177
|
+
schema: effectiveSchema,
|
|
1178
|
+
goal: scopedGoal,
|
|
1179
|
+
userContext,
|
|
1180
|
+
mockMode,
|
|
1181
|
+
planningConfig,
|
|
1182
|
+
router: void 0,
|
|
1183
|
+
dataContext,
|
|
1184
|
+
enablePartialUpdates
|
|
1185
|
+
});
|
|
1186
|
+
const eventManagerRef = useRef(new EventManager());
|
|
1187
|
+
useEffect(() => {
|
|
1188
|
+
if (!eventHooks)
|
|
1189
|
+
return;
|
|
1190
|
+
const unregisters = [];
|
|
1191
|
+
if (eventHooks.all) {
|
|
1192
|
+
const unregister = eventManagerRef.current.register("all", async (ctx) => {
|
|
1193
|
+
for (const hook of eventHooks.all || []) {
|
|
1194
|
+
await hook(ctx);
|
|
1195
|
+
if (ctx.isPropagationStopped())
|
|
1196
|
+
break;
|
|
1197
|
+
}
|
|
1198
|
+
});
|
|
1199
|
+
unregisters.push(unregister);
|
|
1200
|
+
}
|
|
1201
|
+
Object.entries(eventHooks).forEach(([type, hooks]) => {
|
|
1202
|
+
if (type === "all" || !hooks)
|
|
1203
|
+
return;
|
|
1204
|
+
const unregister = eventManagerRef.current.register([type], async (ctx) => {
|
|
1205
|
+
for (const hook of hooks) {
|
|
1206
|
+
await hook(ctx);
|
|
1207
|
+
if (ctx.isPropagationStopped())
|
|
1208
|
+
break;
|
|
1209
|
+
}
|
|
1210
|
+
});
|
|
1211
|
+
unregisters.push(unregister);
|
|
1212
|
+
});
|
|
1213
|
+
return () => {
|
|
1214
|
+
unregisters.forEach((unregister) => unregister());
|
|
1215
|
+
};
|
|
1216
|
+
}, [eventHooks]);
|
|
1217
|
+
useCallback(async (event) => {
|
|
1218
|
+
const shouldProceed = await eventManagerRef.current.processEvent(event);
|
|
1219
|
+
if (onEvent) {
|
|
1220
|
+
onEvent(event);
|
|
1221
|
+
}
|
|
1222
|
+
if (!shouldProceed) {
|
|
1223
|
+
console.info("Event processing was prevented by hooks", event);
|
|
1224
|
+
return;
|
|
1225
|
+
}
|
|
1226
|
+
const findNodeById2 = (node, id) => {
|
|
1227
|
+
if (!node)
|
|
1228
|
+
return void 0;
|
|
1229
|
+
if (node.id === id)
|
|
1230
|
+
return node;
|
|
1231
|
+
if (node.children) {
|
|
1232
|
+
for (const child of node.children) {
|
|
1233
|
+
const found = findNodeById2(child, id);
|
|
1234
|
+
if (found)
|
|
1235
|
+
return found;
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
return void 0;
|
|
1239
|
+
};
|
|
1240
|
+
const sourceNode = findNodeById2(state.layout, event.nodeId);
|
|
1241
|
+
if (!sourceNode) {
|
|
1242
|
+
console.warn(`Node not found for event: ${event.nodeId}`);
|
|
1243
|
+
handleEvent(event);
|
|
1244
|
+
return;
|
|
1245
|
+
}
|
|
1246
|
+
const eventConfig = sourceNode.events?.[event.type];
|
|
1247
|
+
if (!eventConfig) {
|
|
1248
|
+
console.warn(`No event config found for ${event.type} on node ${event.nodeId}`);
|
|
1249
|
+
handleEvent(event);
|
|
1250
|
+
return;
|
|
1251
|
+
}
|
|
1252
|
+
const newContext = executeAction(
|
|
1253
|
+
eventConfig.action,
|
|
1254
|
+
eventConfig.target,
|
|
1255
|
+
{
|
|
1256
|
+
...eventConfig.payload,
|
|
1257
|
+
...event.payload
|
|
1258
|
+
},
|
|
1259
|
+
dataContext,
|
|
1260
|
+
state.layout
|
|
1261
|
+
);
|
|
1262
|
+
setDataContext(newContext);
|
|
1263
|
+
handleEvent(event);
|
|
1264
|
+
}, [dataContext, handleEvent, onEvent, state.layout]);
|
|
1265
|
+
const [resolvedLayout, setResolvedLayout] = useState(void 0);
|
|
1266
|
+
const [renderedNode, setRenderedNode] = useState(null);
|
|
1267
|
+
useEffect(() => {
|
|
1268
|
+
if (state.layout) {
|
|
1269
|
+
resolveBindings(state.layout, dataContext).then((resolved) => setResolvedLayout(resolved)).catch((err) => console.error("Error resolving bindings:", err));
|
|
1270
|
+
} else {
|
|
1271
|
+
setResolvedLayout(void 0);
|
|
1272
|
+
}
|
|
1273
|
+
}, [state.layout, dataContext]);
|
|
1274
|
+
useEffect(() => {
|
|
1275
|
+
if (resolvedLayout) {
|
|
1276
|
+
renderNode2(resolvedLayout, componentAdapter).then((rendered) => setRenderedNode(rendered)).catch((err) => console.error("Error rendering node:", err));
|
|
1277
|
+
} else {
|
|
1278
|
+
setRenderedNode(null);
|
|
1279
|
+
}
|
|
1280
|
+
}, [resolvedLayout, componentAdapter]);
|
|
1281
|
+
return /* @__PURE__ */ jsxs(
|
|
1282
|
+
"div",
|
|
1283
|
+
{
|
|
1284
|
+
className: `autoui-root ${integration.className || ""}`,
|
|
1285
|
+
id: integration.id,
|
|
1286
|
+
"data-mode": integration.mode,
|
|
1287
|
+
"data-scope": scope?.type || "full",
|
|
1288
|
+
children: [
|
|
1289
|
+
state.loading || !resolvedLayout ? (
|
|
1290
|
+
// Render shimmer loading state
|
|
1291
|
+
/* @__PURE__ */ jsx("div", { className: "autoui-loading", children: state.layout ? renderShimmer(state.layout, componentAdapter) : /* @__PURE__ */ jsxs("div", { className: "autoui-shimmer-container", children: [
|
|
1292
|
+
/* @__PURE__ */ jsx("div", { className: "autoui-shimmer-header" }),
|
|
1293
|
+
/* @__PURE__ */ jsx("div", { className: "autoui-shimmer-content" })
|
|
1294
|
+
] }) })
|
|
1295
|
+
) : (
|
|
1296
|
+
// Render the resolved layout
|
|
1297
|
+
/* @__PURE__ */ jsx("div", { className: "autoui-content", children: renderedNode })
|
|
1298
|
+
),
|
|
1299
|
+
state.error && /* @__PURE__ */ jsxs("div", { className: "autoui-error", children: [
|
|
1300
|
+
/* @__PURE__ */ jsx("p", { className: "autoui-error-title", children: "Error generating UI" }),
|
|
1301
|
+
/* @__PURE__ */ jsx("p", { className: "autoui-error-message", children: state.error })
|
|
1302
|
+
] })
|
|
1303
|
+
]
|
|
1304
|
+
}
|
|
1305
|
+
);
|
|
1306
|
+
};
|
|
1307
|
+
|
|
1308
|
+
// src/adapters/schema/drizzle.ts
|
|
1309
|
+
var DrizzleAdapter = class {
|
|
1310
|
+
constructor(options) {
|
|
1311
|
+
this.schema = options.schema;
|
|
1312
|
+
this.client = options.client;
|
|
1313
|
+
this.useMockData = options.useMockData ?? !options.client;
|
|
1314
|
+
this.mockData = options.mockData ?? {};
|
|
1315
|
+
}
|
|
1316
|
+
/**
|
|
1317
|
+
* Convert Drizzle schema to AutoUI schema format
|
|
1318
|
+
*/
|
|
1319
|
+
getSchema() {
|
|
1320
|
+
const result = {};
|
|
1321
|
+
Object.entries(this.schema).forEach(([tableName, table]) => {
|
|
1322
|
+
result[tableName] = {
|
|
1323
|
+
tableName: table.name,
|
|
1324
|
+
schema: table.schema,
|
|
1325
|
+
columns: this.convertColumns(table.columns),
|
|
1326
|
+
// Include mock data if available and mock mode is enabled
|
|
1327
|
+
...this.useMockData && this.mockData[tableName] ? { sampleData: this.mockData[tableName] } : {}
|
|
1328
|
+
};
|
|
1329
|
+
});
|
|
1330
|
+
return result;
|
|
1331
|
+
}
|
|
1332
|
+
/**
|
|
1333
|
+
* Convert Drizzle columns to AutoUI column format
|
|
1334
|
+
*/
|
|
1335
|
+
convertColumns(columns) {
|
|
1336
|
+
const result = {};
|
|
1337
|
+
Object.entries(columns).forEach(([columnName, column]) => {
|
|
1338
|
+
result[columnName] = {
|
|
1339
|
+
type: this.mapDataType(column.dataType),
|
|
1340
|
+
notNull: column.notNull,
|
|
1341
|
+
defaultValue: column.defaultValue,
|
|
1342
|
+
primaryKey: column.primaryKey,
|
|
1343
|
+
unique: column.unique,
|
|
1344
|
+
references: column.references
|
|
1345
|
+
};
|
|
1346
|
+
});
|
|
1347
|
+
return result;
|
|
1348
|
+
}
|
|
1349
|
+
/**
|
|
1350
|
+
* Map Drizzle data types to standard types
|
|
1351
|
+
*/
|
|
1352
|
+
mapDataType(drizzleType) {
|
|
1353
|
+
const typeMap = {
|
|
1354
|
+
"serial": "integer",
|
|
1355
|
+
"integer": "integer",
|
|
1356
|
+
"int": "integer",
|
|
1357
|
+
"bigint": "integer",
|
|
1358
|
+
"text": "string",
|
|
1359
|
+
"varchar": "string",
|
|
1360
|
+
"char": "string",
|
|
1361
|
+
"boolean": "boolean",
|
|
1362
|
+
"bool": "boolean",
|
|
1363
|
+
"timestamp": "datetime",
|
|
1364
|
+
"timestamptz": "datetime",
|
|
1365
|
+
"date": "date",
|
|
1366
|
+
"time": "time",
|
|
1367
|
+
"json": "object",
|
|
1368
|
+
"jsonb": "object",
|
|
1369
|
+
"real": "number",
|
|
1370
|
+
"float": "number",
|
|
1371
|
+
"double": "number",
|
|
1372
|
+
"numeric": "number",
|
|
1373
|
+
"decimal": "number"
|
|
1374
|
+
};
|
|
1375
|
+
return typeMap[drizzleType.toLowerCase()] || "string";
|
|
1376
|
+
}
|
|
1377
|
+
/**
|
|
1378
|
+
* Execute a query against the database
|
|
1379
|
+
*/
|
|
1380
|
+
async query(tableName, query) {
|
|
1381
|
+
if (this.useMockData) {
|
|
1382
|
+
return this.mockData[tableName] || [];
|
|
1383
|
+
}
|
|
1384
|
+
if (!this.client) {
|
|
1385
|
+
throw new Error("No database client provided and mock mode is disabled");
|
|
1386
|
+
}
|
|
1387
|
+
if (this.client.queryFn) {
|
|
1388
|
+
return this.client.queryFn(tableName, query);
|
|
1389
|
+
}
|
|
1390
|
+
throw new Error("No query function provided in client config");
|
|
1391
|
+
}
|
|
1392
|
+
/**
|
|
1393
|
+
* Initialize the data context with schema information and optional mock data
|
|
1394
|
+
*/
|
|
1395
|
+
async initializeDataContext() {
|
|
1396
|
+
const context = {};
|
|
1397
|
+
for (const [tableName, table] of Object.entries(this.schema)) {
|
|
1398
|
+
context[tableName] = {
|
|
1399
|
+
schema: table,
|
|
1400
|
+
data: this.useMockData ? this.mockData[tableName] || [] : [],
|
|
1401
|
+
selected: null
|
|
1402
|
+
};
|
|
1403
|
+
}
|
|
1404
|
+
return context;
|
|
1405
|
+
}
|
|
1406
|
+
};
|
|
1407
|
+
|
|
1408
|
+
// src/adapters/schema/index.ts
|
|
1409
|
+
function createSchemaAdapter(options) {
|
|
1410
|
+
switch (options.type) {
|
|
1411
|
+
case "drizzle":
|
|
1412
|
+
return new DrizzleAdapter(options.options);
|
|
1413
|
+
case "custom":
|
|
1414
|
+
return options.adapter;
|
|
1415
|
+
default:
|
|
1416
|
+
throw new Error(`Unsupported schema adapter type: ${options.type}`);
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
async function generateComponent(schema) {
|
|
1420
|
+
try {
|
|
1421
|
+
const { text } = await generateText({
|
|
1422
|
+
model: openai("gpt-4"),
|
|
1423
|
+
prompt: `Generate a React component based on this data schema: ${JSON.stringify(schema)}`
|
|
1424
|
+
});
|
|
1425
|
+
return text;
|
|
1426
|
+
} catch (error) {
|
|
1427
|
+
console.error("Error generating component:", error);
|
|
1428
|
+
throw error;
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
async function generateUIDescription(schema) {
|
|
1432
|
+
const { text } = await generateText({
|
|
1433
|
+
model: openai("gpt-4"),
|
|
1434
|
+
prompt: `Generate a React component description based on this data schema: ${JSON.stringify(schema)}.
|
|
1435
|
+
Include what fields should be displayed and what user interactions might be needed.`
|
|
1436
|
+
});
|
|
1437
|
+
return text;
|
|
1438
|
+
}
|
|
1439
|
+
async function generateUIComponent(schema) {
|
|
1440
|
+
const { text } = await generateText({
|
|
1441
|
+
model: openai("gpt-4"),
|
|
1442
|
+
prompt: `Generate a basic React component based on this data schema: ${JSON.stringify(schema)}.`
|
|
1443
|
+
});
|
|
1444
|
+
return text;
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
export { ActionRouter, ActionType, AutoUI, DrizzleAdapter, SystemEventType, createDefaultRouter, createEventHook, createSchemaAdapter, createSystemEvent, generateComponent, generateUIComponent, generateUIDescription, systemEvents, uiEvent, uiEventType, uiSpecNode };
|
|
1448
|
+
//# sourceMappingURL=out.js.map
|
|
1449
|
+
//# sourceMappingURL=index.mjs.map
|