autoui-react 0.0.5-alpha → 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/dist/index.d.mts +347 -68
- package/dist/index.d.ts +347 -68
- package/dist/index.js +1716 -1354
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1715 -1353
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -5,10 +5,10 @@ var tailwindMerge = require('tailwind-merge');
|
|
|
5
5
|
var reactSlot = require('@radix-ui/react-slot');
|
|
6
6
|
var classVarianceAuthority = require('class-variance-authority');
|
|
7
7
|
var jsxRuntime = require('react/jsx-runtime');
|
|
8
|
-
var
|
|
9
|
-
var openai = require('@ai-sdk/openai');
|
|
8
|
+
var anthropic = require('@ai-sdk/anthropic');
|
|
10
9
|
var ai = require('ai');
|
|
11
10
|
var zod = require('zod');
|
|
11
|
+
var React = require('react');
|
|
12
12
|
var DialogPrimitive = require('@radix-ui/react-dialog');
|
|
13
13
|
var lucideReact = require('lucide-react');
|
|
14
14
|
var SelectPrimitive = require('@radix-ui/react-select');
|
|
@@ -223,6 +223,10 @@ function removeNodeById(tree, nodeId) {
|
|
|
223
223
|
function uiReducer(state, action) {
|
|
224
224
|
switch (action.type) {
|
|
225
225
|
case "UI_EVENT": {
|
|
226
|
+
if (action.event.type === "INIT" && state.history.some((e) => e.type === "INIT")) {
|
|
227
|
+
console.log("[AutoUI uiReducer] Ignoring duplicate INIT event");
|
|
228
|
+
return state;
|
|
229
|
+
}
|
|
226
230
|
return {
|
|
227
231
|
...state,
|
|
228
232
|
loading: true,
|
|
@@ -318,276 +322,105 @@ function uiReducer(state, action) {
|
|
|
318
322
|
loading: action.isLoading
|
|
319
323
|
};
|
|
320
324
|
}
|
|
325
|
+
case "SET_DATA_CONTEXT": {
|
|
326
|
+
return {
|
|
327
|
+
...state,
|
|
328
|
+
dataContext: action.payload
|
|
329
|
+
};
|
|
330
|
+
}
|
|
321
331
|
default:
|
|
322
332
|
return state;
|
|
323
333
|
}
|
|
324
334
|
}
|
|
325
335
|
var initialState = {
|
|
326
336
|
layout: null,
|
|
327
|
-
loading:
|
|
337
|
+
loading: true,
|
|
338
|
+
error: null,
|
|
328
339
|
history: [],
|
|
329
|
-
|
|
340
|
+
dataContext: {}
|
|
330
341
|
};
|
|
331
342
|
|
|
332
|
-
// src/
|
|
333
|
-
var UI_GUIDANCE_BASE = `
|
|
334
|
-
UI Guidance:
|
|
335
|
-
1. Create a focused interface that directly addresses the goal
|
|
336
|
-
2. Use appropriate UI patterns (lists, forms, details, etc.)
|
|
337
|
-
3. Include navigation between related views when needed
|
|
338
|
-
4. Keep the interface simple and intuitive
|
|
339
|
-
5. Bind to schema data where appropriate
|
|
340
|
-
6. Provide event handlers for user interactions - make sure to always include both action and target properties`;
|
|
341
|
-
var LIST_BINDING_GUIDANCE = `7. **CRITICAL:** For \`ListView\` or \`Table\` nodes, the \`data\` binding key **MUST** point to the *exact path* of the data *array* within the context.`;
|
|
342
|
-
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" } }\`).`;
|
|
343
|
-
var COMMON_UI_GUIDANCE = UI_GUIDANCE_BASE + "\n" + // Add a newline separator
|
|
344
|
-
LIST_BINDING_GUIDANCE + // Add the specific list binding rule
|
|
345
|
-
"\n" + // Add a newline separator
|
|
346
|
-
LIST_BINDING_EXAMPLE;
|
|
347
|
-
function processTemplate(template, values) {
|
|
348
|
-
return template.replace(/\${(.*?)}/g, (match, key) => {
|
|
349
|
-
const trimmedKey = key.trim();
|
|
350
|
-
return trimmedKey in values ? String(values[trimmedKey]) : match;
|
|
351
|
-
});
|
|
352
|
-
}
|
|
353
|
-
function buildPrompt(input, promptTemplate, templateValues) {
|
|
354
|
-
const { schema, goal, history, userContext } = input;
|
|
355
|
-
const schemaInfo = Object.entries(schema).map(([tableName, tableSchema]) => {
|
|
356
|
-
const schemaString = typeof tableSchema === "object" && tableSchema !== null ? JSON.stringify(tableSchema) : String(tableSchema);
|
|
357
|
-
return `Table: ${tableName}
|
|
358
|
-
Schema: ${schemaString}`;
|
|
359
|
-
}).join("\n\n");
|
|
360
|
-
const recentEvents = history && history.length > 0 ? history.slice(-5).map(
|
|
361
|
-
(event) => `Event: ${event.type} on node ${event.nodeId}${event.payload ? ` with payload ${JSON.stringify(event.payload)}` : ""}`
|
|
362
|
-
).join("\n") : "No recent events";
|
|
363
|
-
const userContextSection = userContext ? `
|
|
364
|
-
|
|
365
|
-
User Context:
|
|
366
|
-
${JSON.stringify(userContext)}` : "";
|
|
367
|
-
if (promptTemplate && templateValues) {
|
|
368
|
-
const fullTemplateValues = {
|
|
369
|
-
...templateValues,
|
|
370
|
-
schemaInfo,
|
|
371
|
-
recentEvents,
|
|
372
|
-
userContextString: userContextSection.trim(),
|
|
373
|
-
// Use trimmed version
|
|
374
|
-
commonUIGuidance: COMMON_UI_GUIDANCE,
|
|
375
|
-
goal
|
|
376
|
-
// Ensure goal is always available to templates
|
|
377
|
-
};
|
|
378
|
-
return processTemplate(promptTemplate, fullTemplateValues);
|
|
379
|
-
}
|
|
380
|
-
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";
|
|
381
|
-
return `
|
|
382
|
-
You are an expert UI generator.
|
|
383
|
-
Create a user interface that achieves the following goal: "${goal}".
|
|
384
|
-
${interactionDescription}.
|
|
385
|
-
|
|
386
|
-
Available data schema:
|
|
387
|
-
${schemaInfo}
|
|
388
|
-
|
|
389
|
-
Recent user interactions:
|
|
390
|
-
${recentEvents}${userContextSection}
|
|
391
|
-
|
|
392
|
-
Generate a complete UI specification in JSON format that matches the following TypeScript type:
|
|
393
|
-
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[]; };
|
|
394
|
-
${COMMON_UI_GUIDANCE}
|
|
395
|
-
|
|
396
|
-
Respond ONLY with the JSON UI specification and no other text.
|
|
397
|
-
`;
|
|
398
|
-
}
|
|
343
|
+
// src/schema/action-types.ts
|
|
399
344
|
var ActionType = /* @__PURE__ */ ((ActionType2) => {
|
|
400
345
|
ActionType2["FULL_REFRESH"] = "FULL_REFRESH";
|
|
401
346
|
ActionType2["UPDATE_NODE"] = "UPDATE_NODE";
|
|
347
|
+
ActionType2["UPDATE_DATA"] = "UPDATE_DATA";
|
|
348
|
+
ActionType2["ADD_ITEM"] = "ADD_ITEM";
|
|
349
|
+
ActionType2["DELETE_ITEM"] = "DELETE_ITEM";
|
|
402
350
|
ActionType2["ADD_DROPDOWN"] = "ADD_DROPDOWN";
|
|
403
351
|
ActionType2["SHOW_DETAIL"] = "SHOW_DETAIL";
|
|
404
352
|
ActionType2["HIDE_DETAIL"] = "HIDE_DETAIL";
|
|
353
|
+
ActionType2["HIDE_DIALOG"] = "HIDE_DIALOG";
|
|
354
|
+
ActionType2["SAVE_TASK_CHANGES"] = "SAVE_TASK_CHANGES";
|
|
405
355
|
ActionType2["TOGGLE_STATE"] = "TOGGLE_STATE";
|
|
406
356
|
ActionType2["UPDATE_FORM"] = "UPDATE_FORM";
|
|
407
357
|
ActionType2["NAVIGATE"] = "NAVIGATE";
|
|
358
|
+
ActionType2["OPEN_DIALOG"] = "OPEN_DIALOG";
|
|
359
|
+
ActionType2["CLOSE_DIALOG"] = "CLOSE_DIALOG";
|
|
360
|
+
ActionType2["UPDATE_CONTEXT"] = "UPDATE_CONTEXT";
|
|
408
361
|
return ActionType2;
|
|
409
362
|
})(ActionType || {});
|
|
410
|
-
|
|
363
|
+
|
|
364
|
+
// src/core/system-events.ts
|
|
365
|
+
var SystemEventType = /* @__PURE__ */ ((SystemEventType2) => {
|
|
366
|
+
SystemEventType2["PLAN_START"] = "PLAN_START";
|
|
367
|
+
SystemEventType2["PLAN_PROMPT_CREATED"] = "PLAN_PROMPT_CREATED";
|
|
368
|
+
SystemEventType2["PLAN_RESPONSE_CHUNK"] = "PLAN_RESPONSE_CHUNK";
|
|
369
|
+
SystemEventType2["PLAN_COMPLETE"] = "PLAN_COMPLETE";
|
|
370
|
+
SystemEventType2["PLAN_ERROR"] = "PLAN_ERROR";
|
|
371
|
+
SystemEventType2["BINDING_RESOLUTION_START"] = "BINDING_RESOLUTION_START";
|
|
372
|
+
SystemEventType2["BINDING_RESOLUTION_COMPLETE"] = "BINDING_RESOLUTION_COMPLETE";
|
|
373
|
+
SystemEventType2["DATA_FETCH_START"] = "DATA_FETCH_START";
|
|
374
|
+
SystemEventType2["DATA_FETCH_COMPLETE"] = "DATA_FETCH_COMPLETE";
|
|
375
|
+
SystemEventType2["RENDER_START"] = "RENDER_START";
|
|
376
|
+
SystemEventType2["RENDER_COMPLETE"] = "RENDER_COMPLETE";
|
|
377
|
+
SystemEventType2["PREFETCH_START"] = "PREFETCH_START";
|
|
378
|
+
SystemEventType2["PREFETCH_COMPLETE"] = "PREFETCH_COMPLETE";
|
|
379
|
+
return SystemEventType2;
|
|
380
|
+
})(SystemEventType || {});
|
|
381
|
+
var SystemEventManager = class {
|
|
411
382
|
constructor() {
|
|
412
|
-
this.
|
|
413
|
-
}
|
|
414
|
-
/**
|
|
415
|
-
* Register a new action route
|
|
416
|
-
* @param eventType - UI event type to route
|
|
417
|
-
* @param config - Route configuration
|
|
418
|
-
*/
|
|
419
|
-
registerRoute(eventType, config) {
|
|
420
|
-
if (!this.routes[eventType]) {
|
|
421
|
-
this.routes[eventType] = [];
|
|
422
|
-
}
|
|
423
|
-
this.routes[eventType].push(config);
|
|
383
|
+
this.listeners = {};
|
|
424
384
|
}
|
|
425
385
|
/**
|
|
426
|
-
*
|
|
427
|
-
*
|
|
428
|
-
* @param
|
|
429
|
-
* @param
|
|
430
|
-
* @returns
|
|
386
|
+
* Register a listener for a specific system event type
|
|
387
|
+
*
|
|
388
|
+
* @param eventType - The system event type to listen for
|
|
389
|
+
* @param listener - The listener function
|
|
390
|
+
* @returns Function to unregister the listener
|
|
431
391
|
*/
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
);
|
|
436
|
-
const routes = this.routes[event.type] || [];
|
|
437
|
-
console.log(
|
|
438
|
-
`[ActionRouter Debug] Found ${routes.length} routes for ${event.type}`
|
|
439
|
-
);
|
|
440
|
-
if (routes.length === 0) {
|
|
441
|
-
console.log(
|
|
442
|
-
`[ActionRouter Debug] No specific route found for ${event.type}, using default FULL_REFRESH.`
|
|
443
|
-
);
|
|
444
|
-
const defaultPlannerInput = {
|
|
445
|
-
schema,
|
|
446
|
-
goal,
|
|
447
|
-
history: [event],
|
|
448
|
-
userContext: userContext || null
|
|
449
|
-
};
|
|
450
|
-
const defaultPrompt = buildPrompt(
|
|
451
|
-
defaultPlannerInput,
|
|
452
|
-
void 0,
|
|
453
|
-
void 0
|
|
454
|
-
);
|
|
455
|
-
const defaultResolution = {
|
|
456
|
-
actionType: "FULL_REFRESH" /* FULL_REFRESH */,
|
|
457
|
-
targetNodeId: layout?.id || "root",
|
|
458
|
-
plannerInput: defaultPlannerInput,
|
|
459
|
-
prompt: defaultPrompt
|
|
460
|
-
};
|
|
461
|
-
console.log(
|
|
462
|
-
"[ActionRouter Debug] Default Resolution:",
|
|
463
|
-
defaultResolution
|
|
464
|
-
);
|
|
465
|
-
return defaultResolution;
|
|
466
|
-
}
|
|
467
|
-
const sourceNode = layout ? findNodeById(layout, event.nodeId) : void 0;
|
|
468
|
-
const nodeConfig = sourceNode?.events?.[event.type];
|
|
469
|
-
let matchingRoute;
|
|
470
|
-
if (nodeConfig) {
|
|
471
|
-
matchingRoute = routes.find(
|
|
472
|
-
(route) => route.actionType.toString() === nodeConfig.action
|
|
473
|
-
);
|
|
474
|
-
}
|
|
475
|
-
if (!matchingRoute) {
|
|
476
|
-
matchingRoute = routes[0];
|
|
477
|
-
}
|
|
478
|
-
console.log("[ActionRouter Debug] Matching Route Config:", matchingRoute);
|
|
479
|
-
const targetNodeId = nodeConfig?.target || matchingRoute.targetNodeId || event.nodeId;
|
|
480
|
-
const additionalContext = {};
|
|
481
|
-
if (matchingRoute.contextKeys) {
|
|
482
|
-
matchingRoute.contextKeys.forEach((key) => {
|
|
483
|
-
additionalContext[key] = dataContext[key];
|
|
484
|
-
});
|
|
485
|
-
}
|
|
486
|
-
if (sourceNode) {
|
|
487
|
-
additionalContext.sourceNode = sourceNode;
|
|
488
|
-
}
|
|
489
|
-
if (layout) {
|
|
490
|
-
const targetNode = findNodeById(layout, targetNodeId);
|
|
491
|
-
if (targetNode) {
|
|
492
|
-
additionalContext.targetNode = targetNode;
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
if (event.payload) {
|
|
496
|
-
additionalContext.eventPayload = event.payload;
|
|
497
|
-
}
|
|
498
|
-
if (nodeConfig?.payload) {
|
|
499
|
-
Object.entries(nodeConfig.payload).forEach(([key, value]) => {
|
|
500
|
-
additionalContext[key] = value;
|
|
501
|
-
});
|
|
392
|
+
on(eventType, listener) {
|
|
393
|
+
if (!this.listeners[eventType]) {
|
|
394
|
+
this.listeners[eventType] = [];
|
|
502
395
|
}
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
...additionalContext
|
|
396
|
+
this.listeners[eventType]?.push(listener);
|
|
397
|
+
return () => {
|
|
398
|
+
if (this.listeners[eventType]) {
|
|
399
|
+
this.listeners[eventType] = this.listeners[eventType]?.filter(
|
|
400
|
+
(l) => l !== listener
|
|
401
|
+
);
|
|
510
402
|
}
|
|
511
403
|
};
|
|
512
|
-
const templateValues = {
|
|
513
|
-
goal,
|
|
514
|
-
eventType: event.type,
|
|
515
|
-
nodeId: event.nodeId,
|
|
516
|
-
targetNodeId,
|
|
517
|
-
actionType: matchingRoute.actionType,
|
|
518
|
-
...userContext || {},
|
|
519
|
-
// Spread the original userContext (passed to resolveRoute)
|
|
520
|
-
...additionalContext
|
|
521
|
-
// Spread additionalContext afterwards (can override userContext keys)
|
|
522
|
-
};
|
|
523
|
-
console.log("[ActionRouter Debug] Template Values:", templateValues);
|
|
524
|
-
const finalPrompt = buildPrompt(
|
|
525
|
-
plannerInput2,
|
|
526
|
-
matchingRoute.promptTemplate,
|
|
527
|
-
// Pass template if it exists (can be undefined)
|
|
528
|
-
templateValues
|
|
529
|
-
// Pass templateValues (used only if promptTemplate exists)
|
|
530
|
-
);
|
|
531
|
-
console.log("[ActionRouter Debug] Generated Prompt:", finalPrompt);
|
|
532
|
-
const finalResolution = {
|
|
533
|
-
actionType: matchingRoute.actionType,
|
|
534
|
-
targetNodeId,
|
|
535
|
-
plannerInput: plannerInput2,
|
|
536
|
-
prompt: finalPrompt
|
|
537
|
-
// Use the generated prompt
|
|
538
|
-
};
|
|
539
|
-
console.log("[ActionRouter Debug] Final Resolution:", finalResolution);
|
|
540
|
-
return finalResolution;
|
|
541
404
|
}
|
|
542
405
|
/**
|
|
543
|
-
*
|
|
544
|
-
*
|
|
545
|
-
* @param
|
|
546
|
-
* @returns Processed string
|
|
406
|
+
* Emit a system event to all registered listeners
|
|
407
|
+
*
|
|
408
|
+
* @param event - The system event to emit
|
|
547
409
|
*/
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
410
|
+
async emit(event) {
|
|
411
|
+
const listeners = this.listeners[event.type] || [];
|
|
412
|
+
for (const listener of listeners) {
|
|
413
|
+
await listener(event);
|
|
414
|
+
}
|
|
552
415
|
}
|
|
553
416
|
};
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
actionType: "FULL_REFRESH" /* FULL_REFRESH */,
|
|
562
|
-
targetNodeId: "root"
|
|
563
|
-
});
|
|
564
|
-
router.registerRoute("CLICK", {
|
|
565
|
-
actionType: "SHOW_DETAIL" /* SHOW_DETAIL */,
|
|
566
|
-
targetNodeId: "${targetNodeId}",
|
|
567
|
-
promptTemplate: "Update the UI to show details for the selected item. Current node: ${nodeId}, Target node: ${targetNodeId}",
|
|
568
|
-
contextKeys: ["selected"]
|
|
569
|
-
});
|
|
570
|
-
router.registerRoute("CLICK", {
|
|
571
|
-
actionType: "NAVIGATE" /* NAVIGATE */,
|
|
572
|
-
targetNodeId: "root",
|
|
573
|
-
promptTemplate: "Navigate to a new view based on the user clicking ${nodeId}. Current goal: ${goal}"
|
|
574
|
-
});
|
|
575
|
-
router.registerRoute("CLICK", {
|
|
576
|
-
actionType: "ADD_DROPDOWN" /* ADD_DROPDOWN */,
|
|
577
|
-
targetNodeId: "${targetNodeId}",
|
|
578
|
-
promptTemplate: "Add a dropdown menu to node ${targetNodeId} with options relevant to the clicked element ${nodeId}"
|
|
579
|
-
});
|
|
580
|
-
router.registerRoute("CLICK", {
|
|
581
|
-
actionType: "TOGGLE_STATE" /* TOGGLE_STATE */,
|
|
582
|
-
targetNodeId: "${nodeId}",
|
|
583
|
-
promptTemplate: "Toggle the state of node ${nodeId} (e.g., expanded/collapsed, selected/unselected)"
|
|
584
|
-
});
|
|
585
|
-
router.registerRoute("CHANGE", {
|
|
586
|
-
actionType: "UPDATE_FORM" /* UPDATE_FORM */,
|
|
587
|
-
targetNodeId: "${targetNodeId}",
|
|
588
|
-
promptTemplate: "Update the form based on the user changing the value of ${nodeId}"
|
|
589
|
-
});
|
|
590
|
-
return router;
|
|
417
|
+
var systemEvents = new SystemEventManager();
|
|
418
|
+
function createSystemEvent(type, data) {
|
|
419
|
+
return {
|
|
420
|
+
type,
|
|
421
|
+
timestamp: Date.now(),
|
|
422
|
+
...data
|
|
423
|
+
};
|
|
591
424
|
}
|
|
592
425
|
var componentType = zod.z.enum([
|
|
593
426
|
// Layout components
|
|
@@ -606,30 +439,29 @@ var componentType = zod.z.enum([
|
|
|
606
439
|
"Detail",
|
|
607
440
|
"Tabs",
|
|
608
441
|
"Dialog",
|
|
442
|
+
"Badge",
|
|
609
443
|
// Typography
|
|
610
444
|
"Heading",
|
|
611
445
|
"Text"
|
|
612
446
|
]);
|
|
613
447
|
|
|
614
|
-
// src/schema/ui.ts
|
|
615
|
-
var
|
|
616
|
-
"
|
|
617
|
-
"
|
|
618
|
-
"
|
|
619
|
-
"
|
|
620
|
-
"
|
|
621
|
-
"
|
|
622
|
-
"
|
|
623
|
-
"
|
|
448
|
+
// src/schema/openai-ui-spec.ts
|
|
449
|
+
var actionTypeEnum = zod.z.enum([
|
|
450
|
+
"FULL_REFRESH",
|
|
451
|
+
"UPDATE_NODE",
|
|
452
|
+
"UPDATE_DATA",
|
|
453
|
+
"ADD_DROPDOWN",
|
|
454
|
+
"SHOW_DETAIL",
|
|
455
|
+
"HIDE_DETAIL",
|
|
456
|
+
"HIDE_DIALOG",
|
|
457
|
+
"SAVE_TASK_CHANGES",
|
|
458
|
+
"TOGGLE_STATE",
|
|
459
|
+
"UPDATE_FORM",
|
|
460
|
+
"NAVIGATE",
|
|
461
|
+
"OPEN_DIALOG",
|
|
462
|
+
"CLOSE_DIALOG",
|
|
463
|
+
"UPDATE_CONTEXT"
|
|
624
464
|
]);
|
|
625
|
-
var uiEvent = zod.z.object({
|
|
626
|
-
type: uiEventType,
|
|
627
|
-
nodeId: zod.z.string(),
|
|
628
|
-
timestamp: zod.z.number().nullable(),
|
|
629
|
-
payload: zod.z.record(zod.z.unknown()).nullable()
|
|
630
|
-
});
|
|
631
|
-
zod.z.enum(["AI_RESPONSE", "ERROR"]);
|
|
632
|
-
var runtimeRecord = zod.z.record(zod.z.any()).nullable();
|
|
633
465
|
var openAISimplifiedValue = zod.z.string().nullable();
|
|
634
466
|
var openAIRecordSimplifiedNullable = zod.z.record(openAISimplifiedValue).nullable();
|
|
635
467
|
var openAIEventPayloadSimplifiedNullable = zod.z.record(openAISimplifiedValue).nullable();
|
|
@@ -639,22 +471,26 @@ var openAIBaseNode = zod.z.object({
|
|
|
639
471
|
"The type of UI component (e.g., Container, Text, Button, ListView)."
|
|
640
472
|
),
|
|
641
473
|
props: openAIRecordSimplifiedNullable.describe(
|
|
642
|
-
'Component-specific properties (attributes). Values should be strings or null. E.g., for Header use { "title": "My Title" }; for Text use { "text": "My Text" }.'
|
|
474
|
+
'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" }.'
|
|
643
475
|
),
|
|
644
476
|
bindings: openAIRecordSimplifiedNullable.describe(
|
|
645
|
-
'Data bindings map context paths to component props. Values are paths (e.g., "user.name") or templates (e.g., "{{item.title}}")
|
|
477
|
+
'Data bindings map context paths to component props. Values are paths (e.g., "user.name") or templates (e.g., "{{item.title}}")...'
|
|
646
478
|
),
|
|
647
479
|
events: zod.z.record(
|
|
648
480
|
zod.z.string(),
|
|
649
481
|
zod.z.object({
|
|
650
|
-
action:
|
|
651
|
-
|
|
652
|
-
|
|
482
|
+
action: actionTypeEnum.describe(
|
|
483
|
+
'Action identifier (e.g., "UPDATE_DATA", "ADD_ITEM", "DELETE_ITEM", "VIEW_DETAIL", "HIDE_DETAIL"). Defines what operation to perform when the event occurs.'
|
|
484
|
+
),
|
|
485
|
+
target: zod.z.string().describe("Target identifier."),
|
|
486
|
+
payload: openAIEventPayloadSimplifiedNullable.describe(
|
|
487
|
+
"Static payload to merge with the event's runtime payload."
|
|
488
|
+
)
|
|
653
489
|
})
|
|
654
|
-
).nullable()
|
|
655
|
-
|
|
490
|
+
).nullable().describe(
|
|
491
|
+
'Defines event handlers mapped from UIEventType (e.g., "CLICK", "CHANGE") to an action configuration.'
|
|
492
|
+
),
|
|
656
493
|
children: zod.z.null()
|
|
657
|
-
// Base children are null. When extended, it will be an array or null.
|
|
658
494
|
});
|
|
659
495
|
var openAINodeL4 = openAIBaseNode;
|
|
660
496
|
var openAINodeL3 = openAIBaseNode.extend({
|
|
@@ -666,395 +502,1014 @@ var openAINodeL2 = openAIBaseNode.extend({
|
|
|
666
502
|
var openAIUISpec = openAIBaseNode.extend({
|
|
667
503
|
children: zod.z.array(openAINodeL2).nullable()
|
|
668
504
|
});
|
|
669
|
-
var uiSpecNode = zod.z.object({
|
|
670
|
-
id: zod.z.string(),
|
|
671
|
-
node_type: zod.z.string(),
|
|
672
|
-
props: runtimeRecord,
|
|
673
|
-
bindings: runtimeRecord,
|
|
674
|
-
events: zod.z.record(
|
|
675
|
-
zod.z.string(),
|
|
676
|
-
zod.z.object({
|
|
677
|
-
action: zod.z.string(),
|
|
678
|
-
target: zod.z.string(),
|
|
679
|
-
payload: runtimeRecord
|
|
680
|
-
})
|
|
681
|
-
).nullable(),
|
|
682
|
-
children: zod.z.lazy(() => zod.z.array(uiSpecNode)).nullable()
|
|
683
|
-
});
|
|
684
|
-
zod.z.discriminatedUnion("type", [
|
|
685
|
-
zod.z.object({
|
|
686
|
-
type: zod.z.literal("UI_EVENT"),
|
|
687
|
-
event: uiEvent
|
|
688
|
-
}),
|
|
689
|
-
zod.z.object({
|
|
690
|
-
type: zod.z.literal("AI_RESPONSE"),
|
|
691
|
-
node: uiSpecNode
|
|
692
|
-
}),
|
|
693
|
-
zod.z.object({
|
|
694
|
-
type: zod.z.literal("PARTIAL_UPDATE"),
|
|
695
|
-
nodeId: zod.z.string(),
|
|
696
|
-
node: uiSpecNode
|
|
697
|
-
}),
|
|
698
|
-
zod.z.object({
|
|
699
|
-
type: zod.z.literal("ADD_NODE"),
|
|
700
|
-
parentId: zod.z.string(),
|
|
701
|
-
node: uiSpecNode,
|
|
702
|
-
index: zod.z.number().nullable()
|
|
703
|
-
}),
|
|
704
|
-
zod.z.object({
|
|
705
|
-
type: zod.z.literal("REMOVE_NODE"),
|
|
706
|
-
nodeId: zod.z.string()
|
|
707
|
-
}),
|
|
708
|
-
zod.z.object({
|
|
709
|
-
type: zod.z.literal("ERROR"),
|
|
710
|
-
message: zod.z.string()
|
|
711
|
-
}),
|
|
712
|
-
zod.z.object({
|
|
713
|
-
type: zod.z.literal("LOADING"),
|
|
714
|
-
isLoading: zod.z.boolean()
|
|
715
|
-
})
|
|
716
|
-
]);
|
|
717
|
-
zod.z.object({
|
|
718
|
-
layout: uiSpecNode.nullable(),
|
|
719
|
-
loading: zod.z.boolean(),
|
|
720
|
-
history: zod.z.array(uiEvent),
|
|
721
|
-
error: zod.z.string().nullable()
|
|
722
|
-
});
|
|
723
|
-
zod.z.object({
|
|
724
|
-
schema: zod.z.record(zod.z.unknown()),
|
|
725
|
-
goal: zod.z.string(),
|
|
726
|
-
history: zod.z.array(uiEvent).nullable(),
|
|
727
|
-
userContext: zod.z.record(zod.z.unknown()).nullable().optional()
|
|
728
|
-
});
|
|
729
|
-
|
|
730
|
-
// src/core/system-events.ts
|
|
731
|
-
var SystemEventType = /* @__PURE__ */ ((SystemEventType2) => {
|
|
732
|
-
SystemEventType2["PLAN_START"] = "PLAN_START";
|
|
733
|
-
SystemEventType2["PLAN_PROMPT_CREATED"] = "PLAN_PROMPT_CREATED";
|
|
734
|
-
SystemEventType2["PLAN_RESPONSE_CHUNK"] = "PLAN_RESPONSE_CHUNK";
|
|
735
|
-
SystemEventType2["PLAN_COMPLETE"] = "PLAN_COMPLETE";
|
|
736
|
-
SystemEventType2["PLAN_ERROR"] = "PLAN_ERROR";
|
|
737
|
-
SystemEventType2["BINDING_RESOLUTION_START"] = "BINDING_RESOLUTION_START";
|
|
738
|
-
SystemEventType2["BINDING_RESOLUTION_COMPLETE"] = "BINDING_RESOLUTION_COMPLETE";
|
|
739
|
-
SystemEventType2["DATA_FETCH_START"] = "DATA_FETCH_START";
|
|
740
|
-
SystemEventType2["DATA_FETCH_COMPLETE"] = "DATA_FETCH_COMPLETE";
|
|
741
|
-
SystemEventType2["RENDER_START"] = "RENDER_START";
|
|
742
|
-
SystemEventType2["RENDER_COMPLETE"] = "RENDER_COMPLETE";
|
|
743
|
-
SystemEventType2["PREFETCH_START"] = "PREFETCH_START";
|
|
744
|
-
SystemEventType2["PREFETCH_COMPLETE"] = "PREFETCH_COMPLETE";
|
|
745
|
-
return SystemEventType2;
|
|
746
|
-
})(SystemEventType || {});
|
|
747
|
-
var SystemEventManager = class {
|
|
748
|
-
constructor() {
|
|
749
|
-
this.listeners = {};
|
|
750
|
-
}
|
|
751
|
-
/**
|
|
752
|
-
* Register a listener for a specific system event type
|
|
753
|
-
*
|
|
754
|
-
* @param eventType - The system event type to listen for
|
|
755
|
-
* @param listener - The listener function
|
|
756
|
-
* @returns Function to unregister the listener
|
|
757
|
-
*/
|
|
758
|
-
on(eventType, listener) {
|
|
759
|
-
if (!this.listeners[eventType]) {
|
|
760
|
-
this.listeners[eventType] = [];
|
|
761
|
-
}
|
|
762
|
-
this.listeners[eventType]?.push(listener);
|
|
763
|
-
return () => {
|
|
764
|
-
if (this.listeners[eventType]) {
|
|
765
|
-
this.listeners[eventType] = this.listeners[eventType]?.filter(
|
|
766
|
-
(l) => l !== listener
|
|
767
|
-
);
|
|
768
|
-
}
|
|
769
|
-
};
|
|
770
|
-
}
|
|
771
|
-
/**
|
|
772
|
-
* Emit a system event to all registered listeners
|
|
773
|
-
*
|
|
774
|
-
* @param event - The system event to emit
|
|
775
|
-
*/
|
|
776
|
-
async emit(event) {
|
|
777
|
-
const listeners = this.listeners[event.type] || [];
|
|
778
|
-
for (const listener of listeners) {
|
|
779
|
-
await listener(event);
|
|
780
|
-
}
|
|
781
|
-
}
|
|
782
|
-
};
|
|
783
|
-
var systemEvents = new SystemEventManager();
|
|
784
|
-
function createSystemEvent(type, data) {
|
|
785
|
-
return {
|
|
786
|
-
type,
|
|
787
|
-
timestamp: Date.now(),
|
|
788
|
-
...data
|
|
789
|
-
};
|
|
790
|
-
}
|
|
791
|
-
|
|
792
|
-
// src/env.ts
|
|
793
|
-
var rawApiKeyFromEnv = process.env.VITE_OPENAI_API_KEY;
|
|
794
|
-
var env = {
|
|
795
|
-
MOCK_PLANNER: process.env.VITE_MOCK_PLANNER || "0",
|
|
796
|
-
// Simplified MOCK_PLANNER assignment
|
|
797
|
-
NODE_ENV: process.env.VITE_NODE_ENV || "production",
|
|
798
|
-
// Simplified NODE_ENV assignment
|
|
799
|
-
OPENAI_API_KEY: rawApiKeyFromEnv || ""
|
|
800
|
-
};
|
|
801
505
|
|
|
802
506
|
// src/core/planner.ts
|
|
803
|
-
var
|
|
804
|
-
return
|
|
805
|
-
apiKey
|
|
806
|
-
// Use the provided key directly
|
|
807
|
-
compatibility: "strict"
|
|
507
|
+
var getAnthropicClient = (apiKey) => {
|
|
508
|
+
return anthropic.createAnthropic({
|
|
509
|
+
apiKey
|
|
808
510
|
});
|
|
809
511
|
};
|
|
810
|
-
function mockPlanner(
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
}
|
|
814
|
-
const taskSchema = input.schema.tasks;
|
|
815
|
-
const taskData = taskSchema?.sampleData || [
|
|
816
|
-
{
|
|
817
|
-
id: "1",
|
|
818
|
-
title: "Example Task 1",
|
|
819
|
-
description: "This is a sample task",
|
|
820
|
-
status: "pending",
|
|
821
|
-
priority: "medium"
|
|
822
|
-
},
|
|
823
|
-
{
|
|
824
|
-
id: "2",
|
|
825
|
-
title: "Example Task 2",
|
|
826
|
-
description: "Another sample task",
|
|
827
|
-
status: "completed",
|
|
828
|
-
priority: "high"
|
|
829
|
-
}
|
|
830
|
-
];
|
|
831
|
-
const mockNode = {
|
|
832
|
-
id: targetNodeId || "root",
|
|
512
|
+
function mockPlanner(_input) {
|
|
513
|
+
return {
|
|
514
|
+
id: "task-dashboard",
|
|
833
515
|
node_type: "Container",
|
|
834
|
-
props: {
|
|
835
|
-
className: "p-4 space-y-6"
|
|
836
|
-
},
|
|
516
|
+
props: { className: "p-4 space-y-4" },
|
|
837
517
|
bindings: null,
|
|
838
518
|
events: null,
|
|
839
519
|
children: [
|
|
840
520
|
{
|
|
841
|
-
id: "header
|
|
842
|
-
node_type: "
|
|
843
|
-
props: {
|
|
844
|
-
title: "Task Management Dashboard",
|
|
845
|
-
className: "mb-4"
|
|
846
|
-
},
|
|
521
|
+
id: "header",
|
|
522
|
+
node_type: "Container",
|
|
523
|
+
props: { className: "flex justify-between items-center mb-4" },
|
|
847
524
|
bindings: null,
|
|
848
525
|
events: null,
|
|
849
|
-
children:
|
|
526
|
+
children: [
|
|
527
|
+
{
|
|
528
|
+
id: "title",
|
|
529
|
+
node_type: "Text",
|
|
530
|
+
props: { text: "Task Dashboard", className: "text-2xl font-bold" },
|
|
531
|
+
bindings: null,
|
|
532
|
+
events: null,
|
|
533
|
+
children: null
|
|
534
|
+
},
|
|
535
|
+
{
|
|
536
|
+
id: "add-task-button",
|
|
537
|
+
node_type: "Button",
|
|
538
|
+
props: { label: "Add Task", variant: "default" },
|
|
539
|
+
bindings: null,
|
|
540
|
+
events: {
|
|
541
|
+
CLICK: { action: "SHOW_DETAIL", target: "new-task-form" }
|
|
542
|
+
},
|
|
543
|
+
children: null
|
|
544
|
+
}
|
|
545
|
+
]
|
|
850
546
|
},
|
|
851
547
|
{
|
|
852
548
|
id: "main-content",
|
|
853
549
|
node_type: "Container",
|
|
854
|
-
props: {
|
|
855
|
-
className: "grid grid-cols-1 gap-6 md:grid-cols-3"
|
|
856
|
-
},
|
|
550
|
+
props: { className: "flex gap-4" },
|
|
857
551
|
bindings: null,
|
|
858
552
|
events: null,
|
|
859
553
|
children: [
|
|
860
554
|
{
|
|
861
555
|
id: "tasks-container",
|
|
862
556
|
node_type: "Container",
|
|
863
|
-
props: {
|
|
864
|
-
className: "md:col-span-2"
|
|
865
|
-
},
|
|
557
|
+
props: { className: "flex-1" },
|
|
866
558
|
bindings: null,
|
|
867
559
|
events: null,
|
|
868
560
|
children: [
|
|
869
561
|
{
|
|
870
|
-
id: "list
|
|
871
|
-
node_type: "
|
|
872
|
-
props: {
|
|
873
|
-
|
|
874
|
-
},
|
|
875
|
-
bindings: null,
|
|
562
|
+
id: "task-list",
|
|
563
|
+
node_type: "ListView",
|
|
564
|
+
props: { className: "space-y-2" },
|
|
565
|
+
bindings: { data: "tasks.data" },
|
|
876
566
|
events: null,
|
|
877
567
|
children: [
|
|
878
568
|
{
|
|
879
|
-
id: "
|
|
880
|
-
node_type: "
|
|
881
|
-
props: {
|
|
882
|
-
title: "Tasks",
|
|
883
|
-
className: "border-none p-0 m-0"
|
|
884
|
-
},
|
|
569
|
+
id: "task-item-{{index}}",
|
|
570
|
+
node_type: "Card",
|
|
571
|
+
props: { className: "p-3 border rounded" },
|
|
885
572
|
bindings: null,
|
|
886
573
|
events: null,
|
|
887
|
-
children:
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
574
|
+
children: [
|
|
575
|
+
{
|
|
576
|
+
id: "task-title-{{index}}",
|
|
577
|
+
node_type: "Text",
|
|
578
|
+
props: { className: "font-medium" },
|
|
579
|
+
bindings: { text: "item.title" },
|
|
580
|
+
events: null,
|
|
581
|
+
children: null
|
|
582
|
+
},
|
|
583
|
+
{
|
|
584
|
+
id: "task-status-{{index}}",
|
|
585
|
+
node_type: "Badge",
|
|
586
|
+
props: {},
|
|
587
|
+
bindings: { text: "item.status" },
|
|
588
|
+
events: null,
|
|
589
|
+
children: null
|
|
590
|
+
},
|
|
591
|
+
{
|
|
592
|
+
id: "view-details-button-{{index}}",
|
|
593
|
+
node_type: "Button",
|
|
594
|
+
props: { label: "View Details", variant: "outline", size: "sm" },
|
|
595
|
+
bindings: null,
|
|
596
|
+
events: {
|
|
597
|
+
CLICK: { action: "SHOW_DETAIL", target: "task-detail" }
|
|
598
|
+
},
|
|
599
|
+
children: null
|
|
902
600
|
}
|
|
903
|
-
|
|
904
|
-
children: null
|
|
601
|
+
]
|
|
905
602
|
}
|
|
906
603
|
]
|
|
604
|
+
}
|
|
605
|
+
]
|
|
606
|
+
},
|
|
607
|
+
{
|
|
608
|
+
id: "task-detail",
|
|
609
|
+
node_type: "Container",
|
|
610
|
+
props: { className: "w-1/3 border-l pl-4", visible: false },
|
|
611
|
+
bindings: null,
|
|
612
|
+
events: null,
|
|
613
|
+
children: [
|
|
614
|
+
{
|
|
615
|
+
id: "detail-title",
|
|
616
|
+
node_type: "Text",
|
|
617
|
+
props: { text: "Task Details", className: "text-lg font-bold mb-2" },
|
|
618
|
+
bindings: null,
|
|
619
|
+
events: null,
|
|
620
|
+
children: null
|
|
907
621
|
},
|
|
908
622
|
{
|
|
909
|
-
id: "
|
|
910
|
-
node_type: "
|
|
911
|
-
props: {
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
])
|
|
922
|
-
},
|
|
623
|
+
id: "detail-content",
|
|
624
|
+
node_type: "Text",
|
|
625
|
+
props: {},
|
|
626
|
+
bindings: { text: "tasks.selected.description" },
|
|
627
|
+
events: null,
|
|
628
|
+
children: null
|
|
629
|
+
},
|
|
630
|
+
{
|
|
631
|
+
id: "close-detail-button",
|
|
632
|
+
node_type: "Button",
|
|
633
|
+
props: { label: "Close", variant: "ghost" },
|
|
634
|
+
bindings: null,
|
|
923
635
|
events: {
|
|
924
|
-
|
|
925
|
-
action: "SELECT_TASK",
|
|
926
|
-
target: "task-detail",
|
|
927
|
-
payload: {
|
|
928
|
-
source: "task-list"
|
|
929
|
-
}
|
|
930
|
-
}
|
|
636
|
+
CLICK: { action: "HIDE_DETAIL", target: "task-detail" }
|
|
931
637
|
},
|
|
932
638
|
children: null
|
|
933
639
|
}
|
|
934
640
|
]
|
|
641
|
+
}
|
|
642
|
+
]
|
|
643
|
+
}
|
|
644
|
+
]
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
async function callPlannerLLM(input, apiKey, useMock) {
|
|
648
|
+
await systemEvents.emit(
|
|
649
|
+
createSystemEvent("PLAN_START" /* PLAN_START */, { plannerInput: input })
|
|
650
|
+
);
|
|
651
|
+
if (useMock || typeof window !== "undefined" && window.__USE_MOCK_PLANNER) {
|
|
652
|
+
console.log("[Mock Planner] Using mock planner for testing");
|
|
653
|
+
const mockLayout = mockPlanner();
|
|
654
|
+
await systemEvents.emit(
|
|
655
|
+
createSystemEvent("PLAN_COMPLETE" /* PLAN_COMPLETE */, {
|
|
656
|
+
layout: mockLayout,
|
|
657
|
+
executionTimeMs: 0
|
|
658
|
+
})
|
|
659
|
+
);
|
|
660
|
+
return mockLayout;
|
|
661
|
+
}
|
|
662
|
+
if (!apiKey) {
|
|
663
|
+
console.warn(
|
|
664
|
+
`Anthropic API key was not provided to callPlannerLLM. Returning a placeholder UI.`
|
|
665
|
+
);
|
|
666
|
+
return {
|
|
667
|
+
id: "root-no-api-key",
|
|
668
|
+
node_type: "Container",
|
|
669
|
+
props: {
|
|
670
|
+
className: "p-4 flex flex-col items-center justify-center h-full"
|
|
671
|
+
},
|
|
672
|
+
bindings: null,
|
|
673
|
+
events: null,
|
|
674
|
+
children: [
|
|
675
|
+
{
|
|
676
|
+
id: "no-api-key-message",
|
|
677
|
+
node_type: "Text",
|
|
678
|
+
props: {
|
|
679
|
+
text: "Anthropic API Key is required to generate the UI. Please provide one in your environment configuration.",
|
|
680
|
+
className: "text-red-500 text-center"
|
|
935
681
|
},
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
682
|
+
bindings: null,
|
|
683
|
+
events: null,
|
|
684
|
+
children: null
|
|
685
|
+
}
|
|
686
|
+
]
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
const startTime = Date.now();
|
|
690
|
+
const templateValuesForPrompt = input.userContext ? { ...input.userContext } : void 0;
|
|
691
|
+
const promptTemplateFromInput = typeof input.userContext?.promptTemplate === "string" ? input.userContext.promptTemplate : void 0;
|
|
692
|
+
const prompt = buildPrompt(
|
|
693
|
+
input,
|
|
694
|
+
promptTemplateFromInput,
|
|
695
|
+
// Use template from input.userContext
|
|
696
|
+
templateValuesForPrompt
|
|
697
|
+
// Use values from input.userContext
|
|
698
|
+
);
|
|
699
|
+
await systemEvents.emit(
|
|
700
|
+
createSystemEvent("PLAN_PROMPT_CREATED" /* PLAN_PROMPT_CREATED */, { prompt })
|
|
701
|
+
);
|
|
702
|
+
try {
|
|
703
|
+
let uiSpec;
|
|
704
|
+
if (typeof window !== "undefined") {
|
|
705
|
+
const response = await fetch("/api/generate-ui", {
|
|
706
|
+
method: "POST",
|
|
707
|
+
headers: {
|
|
708
|
+
"Content-Type": "application/json"
|
|
709
|
+
},
|
|
710
|
+
body: JSON.stringify({ prompt, apiKey })
|
|
711
|
+
});
|
|
712
|
+
if (!response.ok) {
|
|
713
|
+
const errorData = await response.json();
|
|
714
|
+
throw new Error(errorData.error || `HTTP error! status: ${response.status}`);
|
|
715
|
+
}
|
|
716
|
+
const data = await response.json();
|
|
717
|
+
uiSpec = data.uiSpec;
|
|
718
|
+
} else {
|
|
719
|
+
const { object } = await ai.generateObject({
|
|
720
|
+
model: getAnthropicClient(apiKey)("claude-haiku-4-5"),
|
|
721
|
+
schema: openAIUISpec,
|
|
722
|
+
mode: "tool",
|
|
723
|
+
messages: [{ role: "user", content: prompt }],
|
|
724
|
+
temperature: 0.2,
|
|
725
|
+
maxTokens: 4e3
|
|
726
|
+
});
|
|
727
|
+
uiSpec = object;
|
|
728
|
+
}
|
|
729
|
+
await systemEvents.emit(
|
|
730
|
+
createSystemEvent("PLAN_COMPLETE" /* PLAN_COMPLETE */, {
|
|
731
|
+
layout: uiSpec,
|
|
732
|
+
executionTimeMs: Date.now() - startTime
|
|
733
|
+
})
|
|
734
|
+
);
|
|
735
|
+
return uiSpec;
|
|
736
|
+
} catch (error) {
|
|
737
|
+
console.error("Error calling LLM planner:", error);
|
|
738
|
+
await systemEvents.emit(
|
|
739
|
+
createSystemEvent("PLAN_ERROR" /* PLAN_ERROR */, {
|
|
740
|
+
error: error instanceof Error ? error : new Error(String(error))
|
|
741
|
+
})
|
|
742
|
+
);
|
|
743
|
+
throw error;
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
// src/core/bindings.ts
|
|
748
|
+
function getValueByPath(context, path) {
|
|
749
|
+
const parts = path.split(".");
|
|
750
|
+
let current = context;
|
|
751
|
+
for (const part of parts) {
|
|
752
|
+
if (current === null || current === void 0) {
|
|
753
|
+
return void 0;
|
|
754
|
+
}
|
|
755
|
+
if (typeof current !== "object") {
|
|
756
|
+
return void 0;
|
|
757
|
+
}
|
|
758
|
+
current = current[part];
|
|
759
|
+
}
|
|
760
|
+
return current;
|
|
761
|
+
}
|
|
762
|
+
function setValueByPath(context, path, value) {
|
|
763
|
+
if (!path) {
|
|
764
|
+
return context;
|
|
765
|
+
}
|
|
766
|
+
const result = { ...context };
|
|
767
|
+
const parts = path.split(".");
|
|
768
|
+
let current = result;
|
|
769
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
770
|
+
const part = parts[i];
|
|
771
|
+
if (typeof current !== "object" || current === null) {
|
|
772
|
+
console.warn(
|
|
773
|
+
`setValueByPath: Cannot traverse path "${path}". Parent segment "${parts[i - 1] || "(root)"}" is not an object.`
|
|
774
|
+
);
|
|
775
|
+
return context;
|
|
776
|
+
}
|
|
777
|
+
const currentAsObject = current;
|
|
778
|
+
const nextPartValue = currentAsObject[part];
|
|
779
|
+
if (nextPartValue === void 0 || nextPartValue === null) {
|
|
780
|
+
currentAsObject[part] = {};
|
|
781
|
+
} else if (typeof nextPartValue !== "object") {
|
|
782
|
+
console.warn(
|
|
783
|
+
`setValueByPath: Cannot create nested path "${path}". Segment "${part}" is not an object.`
|
|
784
|
+
);
|
|
785
|
+
return context;
|
|
786
|
+
} else {
|
|
787
|
+
currentAsObject[part] = { ...nextPartValue };
|
|
788
|
+
}
|
|
789
|
+
current = currentAsObject[part];
|
|
790
|
+
}
|
|
791
|
+
const lastPart = parts[parts.length - 1];
|
|
792
|
+
if (typeof current === "object" && current !== null) {
|
|
793
|
+
current[lastPart] = value;
|
|
794
|
+
} else {
|
|
795
|
+
console.warn(
|
|
796
|
+
`setValueByPath: Could not set value for path "${path}". Final segment parent is not an object.`
|
|
797
|
+
);
|
|
798
|
+
return context;
|
|
799
|
+
}
|
|
800
|
+
return result;
|
|
801
|
+
}
|
|
802
|
+
function processBinding(binding, context, itemData) {
|
|
803
|
+
if (typeof binding === "string") {
|
|
804
|
+
const exactMatchArr = binding.match(/^{{(.*)}}$/);
|
|
805
|
+
const pathInsideExact = exactMatchArr ? exactMatchArr[1].trim() : null;
|
|
806
|
+
if (pathInsideExact !== null && !pathInsideExact.includes("{{") && !pathInsideExact.includes("}}")) {
|
|
807
|
+
const pathToResolve = pathInsideExact;
|
|
808
|
+
let resolvedValue = void 0;
|
|
809
|
+
if ((pathToResolve.startsWith("item.") || pathToResolve.startsWith("row.")) && itemData) {
|
|
810
|
+
if (pathToResolve.startsWith("item.")) {
|
|
811
|
+
resolvedValue = getValueByPath(itemData, pathToResolve.substring(5));
|
|
812
|
+
} else {
|
|
813
|
+
resolvedValue = getValueByPath(itemData, pathToResolve.substring(4));
|
|
814
|
+
}
|
|
815
|
+
} else if (itemData && pathToResolve in itemData) {
|
|
816
|
+
resolvedValue = getValueByPath(itemData, pathToResolve);
|
|
817
|
+
}
|
|
818
|
+
if (resolvedValue === void 0) {
|
|
819
|
+
resolvedValue = getValueByPath(context, pathToResolve);
|
|
820
|
+
}
|
|
821
|
+
return resolvedValue;
|
|
822
|
+
} else if (binding.includes("{{") && binding.includes("}}")) {
|
|
823
|
+
const resolvedString = binding.replaceAll(
|
|
824
|
+
/{{(.*?)}}/g,
|
|
825
|
+
// Non-greedy match inside braces
|
|
826
|
+
(match, path) => {
|
|
827
|
+
const trimmedPath = path.trim();
|
|
828
|
+
let resolvedValue = void 0;
|
|
829
|
+
if ((trimmedPath.startsWith("item.") || trimmedPath.startsWith("row.")) && itemData) {
|
|
830
|
+
if (trimmedPath.startsWith("item.")) {
|
|
831
|
+
resolvedValue = getValueByPath(
|
|
832
|
+
itemData,
|
|
833
|
+
trimmedPath.substring(5)
|
|
834
|
+
);
|
|
835
|
+
} else {
|
|
836
|
+
resolvedValue = getValueByPath(
|
|
837
|
+
itemData,
|
|
838
|
+
trimmedPath.substring(4)
|
|
839
|
+
);
|
|
840
|
+
}
|
|
841
|
+
} else if (itemData && trimmedPath in itemData) {
|
|
842
|
+
resolvedValue = getValueByPath(itemData, trimmedPath);
|
|
843
|
+
}
|
|
844
|
+
if (resolvedValue === void 0) {
|
|
845
|
+
resolvedValue = getValueByPath(context, trimmedPath);
|
|
846
|
+
}
|
|
847
|
+
return resolvedValue === null || resolvedValue === void 0 ? "" : String(resolvedValue);
|
|
848
|
+
}
|
|
849
|
+
);
|
|
850
|
+
return resolvedString;
|
|
851
|
+
} else {
|
|
852
|
+
const pathToResolve = binding;
|
|
853
|
+
let resolvedValue = void 0;
|
|
854
|
+
if ((pathToResolve.startsWith("item.") || pathToResolve.startsWith("row.")) && itemData) {
|
|
855
|
+
if (pathToResolve.startsWith("item.")) {
|
|
856
|
+
resolvedValue = getValueByPath(itemData, pathToResolve.substring(5));
|
|
857
|
+
} else {
|
|
858
|
+
resolvedValue = getValueByPath(itemData, pathToResolve.substring(4));
|
|
859
|
+
}
|
|
860
|
+
if (resolvedValue !== void 0) {
|
|
861
|
+
return resolvedValue;
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
if (itemData && !pathToResolve.includes(".") && pathToResolve in itemData) {
|
|
865
|
+
resolvedValue = getValueByPath(itemData, pathToResolve);
|
|
866
|
+
}
|
|
867
|
+
if (resolvedValue === void 0) {
|
|
868
|
+
resolvedValue = getValueByPath(context, pathToResolve);
|
|
869
|
+
}
|
|
870
|
+
return resolvedValue;
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
if (Array.isArray(binding)) {
|
|
874
|
+
return binding.map((item) => processBinding(item, context, itemData));
|
|
875
|
+
}
|
|
876
|
+
if (binding !== null && typeof binding === "object") {
|
|
877
|
+
const result = {};
|
|
878
|
+
for (const [key, value] of Object.entries(binding)) {
|
|
879
|
+
result[key] = processBinding(value, context, itemData);
|
|
880
|
+
}
|
|
881
|
+
return result;
|
|
882
|
+
}
|
|
883
|
+
return binding;
|
|
884
|
+
}
|
|
885
|
+
async function resolveBindings(node, context, itemData) {
|
|
886
|
+
if (node.id === "task-detail") {
|
|
887
|
+
console.log(
|
|
888
|
+
`[resolveBindings task-detail SPECIFIC CHECK] Context for task-detail:`,
|
|
889
|
+
JSON.stringify(context)
|
|
890
|
+
);
|
|
891
|
+
console.log(
|
|
892
|
+
`[resolveBindings task-detail SPECIFIC CHECK] context.isTaskDetailDialogVisible = ${context.isTaskDetailDialogVisible}`
|
|
893
|
+
);
|
|
894
|
+
console.log(
|
|
895
|
+
`[resolveBindings task-detail SPECIFIC CHECK] context.selectedTask = ${JSON.stringify(
|
|
896
|
+
context.selectedTask
|
|
897
|
+
)}`
|
|
898
|
+
);
|
|
899
|
+
}
|
|
900
|
+
console.log(
|
|
901
|
+
`[resolveBindings ENTRY] Node ID: ${node.id}, Type: ${node.node_type}, Has itemData: ${!!itemData}, Context keys: ${Object.keys(
|
|
902
|
+
context || {}
|
|
903
|
+
// Ensure context is not null before Object.keys
|
|
904
|
+
).join(", ")}`
|
|
905
|
+
);
|
|
906
|
+
const result = {
|
|
907
|
+
...node,
|
|
908
|
+
props: node.props ? JSON.parse(JSON.stringify(node.props)) : null,
|
|
909
|
+
events: node.events ? JSON.parse(JSON.stringify(node.events)) : null,
|
|
910
|
+
bindings: node.bindings ? JSON.parse(JSON.stringify(node.bindings)) : null,
|
|
911
|
+
children: null
|
|
912
|
+
// Initialize children to null
|
|
913
|
+
};
|
|
914
|
+
let mergedProps = node.props ? { ...JSON.parse(JSON.stringify(node.props)) } : null;
|
|
915
|
+
const PROP_KEYS_TO_RESOLVE = /* @__PURE__ */ new Set([
|
|
916
|
+
"text",
|
|
917
|
+
"label",
|
|
918
|
+
"title",
|
|
919
|
+
"placeholder",
|
|
920
|
+
"value"
|
|
921
|
+
]);
|
|
922
|
+
if (node.props) {
|
|
923
|
+
for (const [key, value] of Object.entries(node.props)) {
|
|
924
|
+
if (!mergedProps)
|
|
925
|
+
mergedProps = {};
|
|
926
|
+
if (PROP_KEYS_TO_RESOLVE.has(key)) {
|
|
927
|
+
const resolvedPropValue = processBinding(
|
|
928
|
+
value,
|
|
929
|
+
context,
|
|
930
|
+
itemData ?? void 0
|
|
931
|
+
);
|
|
932
|
+
if (resolvedPropValue !== void 0) {
|
|
933
|
+
mergedProps[key] = resolvedPropValue;
|
|
934
|
+
} else {
|
|
935
|
+
if (typeof value === "string" && (value.includes("{{") || value.includes("."))) {
|
|
936
|
+
mergedProps[key] = "";
|
|
937
|
+
} else {
|
|
938
|
+
mergedProps[key] = value;
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
} else if (value !== void 0) {
|
|
942
|
+
mergedProps[key] = value;
|
|
943
|
+
} else {
|
|
944
|
+
delete mergedProps[key];
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
result.props = mergedProps;
|
|
949
|
+
if (node.bindings) {
|
|
950
|
+
for (const [key, bindingValue] of Object.entries(node.bindings)) {
|
|
951
|
+
const resolvedValue = processBinding(
|
|
952
|
+
bindingValue,
|
|
953
|
+
context,
|
|
954
|
+
itemData ?? void 0
|
|
955
|
+
);
|
|
956
|
+
if (node.id === "task-detail") {
|
|
957
|
+
console.log(
|
|
958
|
+
`[resolveBindings - ${node.id}] Binding for '${key}': '${String(
|
|
959
|
+
bindingValue
|
|
960
|
+
)}' -> Resolved:`,
|
|
961
|
+
resolvedValue
|
|
962
|
+
);
|
|
963
|
+
}
|
|
964
|
+
if (resolvedValue !== void 0) {
|
|
965
|
+
if (!result.props)
|
|
966
|
+
result.props = {};
|
|
967
|
+
result.props[key] = resolvedValue;
|
|
968
|
+
if (node.id === "task-detail") {
|
|
969
|
+
console.log(
|
|
970
|
+
`[resolveBindings - ${node.id}] Set result.props.${key} =`,
|
|
971
|
+
resolvedValue
|
|
972
|
+
);
|
|
973
|
+
}
|
|
974
|
+
} else {
|
|
975
|
+
if (node.id === "task-detail") {
|
|
976
|
+
console.log(
|
|
977
|
+
`[resolveBindings - ${node.id}] Binding for '${key}' ('${String(
|
|
978
|
+
bindingValue
|
|
979
|
+
)}') resolved to undefined. Not setting prop.`
|
|
980
|
+
);
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
if (node.events) {
|
|
986
|
+
const processedEvents = {};
|
|
987
|
+
for (const eventType in node.events) {
|
|
988
|
+
const eventConfig = node.events[eventType];
|
|
989
|
+
processedEvents[eventType] = {
|
|
990
|
+
...eventConfig,
|
|
991
|
+
payload: eventConfig.payload ? processBinding(
|
|
992
|
+
eventConfig.payload,
|
|
993
|
+
context,
|
|
994
|
+
itemData ?? void 0
|
|
995
|
+
) : null
|
|
996
|
+
};
|
|
997
|
+
}
|
|
998
|
+
result.events = processedEvents;
|
|
999
|
+
} else {
|
|
1000
|
+
result.events = null;
|
|
1001
|
+
}
|
|
1002
|
+
const dataBindingValue = result.props?.data ?? result.props?.items;
|
|
1003
|
+
if ((node.node_type === "ListView" || node.node_type === "Table") && Array.isArray(dataBindingValue) && node.children && node.children.length > 0) {
|
|
1004
|
+
const templateChild = node.children[0];
|
|
1005
|
+
const looksLikeTemplate = node.children.length === 1 && (templateChild.id.includes("{{") || templateChild.id.includes("-template"));
|
|
1006
|
+
const isAlreadyExpanded = !looksLikeTemplate && node.children.length > 1;
|
|
1007
|
+
if (isAlreadyExpanded) {
|
|
1008
|
+
result.children = await Promise.all(
|
|
1009
|
+
node.children.map(
|
|
1010
|
+
(existingChild) => resolveBindings(existingChild, context, itemData)
|
|
1011
|
+
)
|
|
1012
|
+
// itemData is tricky here, should it be from parent or is it irrelevant?
|
|
1013
|
+
// For now, assume itemData is not applicable for re-resolving top-level list items this way.
|
|
1014
|
+
);
|
|
1015
|
+
} else {
|
|
1016
|
+
const mappedChildren = await Promise.all(
|
|
1017
|
+
dataBindingValue.map(async (currentItemData, index) => {
|
|
1018
|
+
try {
|
|
1019
|
+
if (typeof currentItemData !== "object" || currentItemData === null) {
|
|
1020
|
+
console.warn(
|
|
1021
|
+
`List item at index ${index} for node ${node.id} is not an object:`,
|
|
1022
|
+
currentItemData
|
|
1023
|
+
);
|
|
1024
|
+
return null;
|
|
1025
|
+
}
|
|
1026
|
+
const currentItemAsRecord = currentItemData;
|
|
1027
|
+
const itemId = currentItemAsRecord.id;
|
|
1028
|
+
const instanceId = `${templateChild.id}-${itemId || index}`;
|
|
1029
|
+
const childNodeInstance = JSON.parse(
|
|
1030
|
+
JSON.stringify(templateChild)
|
|
1031
|
+
);
|
|
1032
|
+
childNodeInstance.id = instanceId;
|
|
1033
|
+
makeChildIdsUniqueInInstance(
|
|
1034
|
+
childNodeInstance,
|
|
1035
|
+
instanceId,
|
|
1036
|
+
templateChild.id
|
|
1037
|
+
);
|
|
1038
|
+
const resolvedChild = await resolveBindings(
|
|
1039
|
+
childNodeInstance,
|
|
1040
|
+
context,
|
|
1041
|
+
currentItemAsRecord
|
|
1042
|
+
);
|
|
1043
|
+
if (resolvedChild && !resolvedChild.props)
|
|
1044
|
+
resolvedChild.props = {};
|
|
1045
|
+
if (resolvedChild && resolvedChild.props) {
|
|
1046
|
+
resolvedChild.props.key = itemId || `${node.id}-item-${index}`;
|
|
1047
|
+
}
|
|
1048
|
+
return resolvedChild;
|
|
1049
|
+
} catch (error) {
|
|
1050
|
+
console.error(
|
|
1051
|
+
`[resolveBindings Error] Error processing item at index ${index} for node ${node.id}:`,
|
|
1052
|
+
error,
|
|
1053
|
+
"Item Data:",
|
|
1054
|
+
currentItemData
|
|
1055
|
+
);
|
|
1056
|
+
return null;
|
|
1057
|
+
}
|
|
1058
|
+
})
|
|
1059
|
+
);
|
|
1060
|
+
result.children = mappedChildren.filter(
|
|
1061
|
+
(child) => child !== null
|
|
1062
|
+
);
|
|
1063
|
+
}
|
|
1064
|
+
} else if (node.children && node.children.length > 0) {
|
|
1065
|
+
result.children = await Promise.all(
|
|
1066
|
+
node.children.map((child) => resolveBindings(child, context, itemData))
|
|
1067
|
+
);
|
|
1068
|
+
} else {
|
|
1069
|
+
result.children = [];
|
|
1070
|
+
}
|
|
1071
|
+
return result;
|
|
1072
|
+
}
|
|
1073
|
+
function executeAction(action, target, payload, context = {}) {
|
|
1074
|
+
let newContext = { ...context };
|
|
1075
|
+
switch (action) {
|
|
1076
|
+
case "SHOW_DETAIL" /* SHOW_DETAIL */: {
|
|
1077
|
+
const taskId = payload?.taskId;
|
|
1078
|
+
const dialogNodeId = target;
|
|
1079
|
+
if (taskId && dialogNodeId) {
|
|
1080
|
+
const tasksData = getValueByPath(context, "tasks.data");
|
|
1081
|
+
if (Array.isArray(tasksData)) {
|
|
1082
|
+
const foundTask = tasksData.find(
|
|
1083
|
+
(t) => t.id === taskId
|
|
1084
|
+
);
|
|
1085
|
+
if (foundTask) {
|
|
1086
|
+
newContext = setValueByPath(newContext, "selectedTask", foundTask);
|
|
1087
|
+
} else {
|
|
1088
|
+
console.warn(
|
|
1089
|
+
`[executeAction] ${"SHOW_DETAIL" /* SHOW_DETAIL */}: Task with id "${taskId}" not found in tasks.data.`
|
|
1090
|
+
);
|
|
1091
|
+
newContext = setValueByPath(newContext, "selectedTask", null);
|
|
1092
|
+
}
|
|
1093
|
+
} else {
|
|
1094
|
+
console.warn(
|
|
1095
|
+
`[executeAction] ${"SHOW_DETAIL" /* SHOW_DETAIL */}: context.tasks.data is not an array or not found.`
|
|
1096
|
+
);
|
|
1097
|
+
newContext = setValueByPath(newContext, "selectedTask", null);
|
|
1098
|
+
}
|
|
1099
|
+
} else {
|
|
1100
|
+
console.warn(
|
|
1101
|
+
`[executeAction] ${"SHOW_DETAIL" /* SHOW_DETAIL */}: payload.taskId or target (dialogNodeId) was missing. Dialog will be shown without a selected task.`
|
|
1102
|
+
);
|
|
1103
|
+
newContext = setValueByPath(newContext, "selectedTask", null);
|
|
1104
|
+
}
|
|
1105
|
+
newContext = setValueByPath(
|
|
1106
|
+
newContext,
|
|
1107
|
+
// Use the potentially modified newContext (with selectedTask set or cleared)
|
|
1108
|
+
"isTaskDetailDialogVisible",
|
|
1109
|
+
true
|
|
1110
|
+
);
|
|
1111
|
+
break;
|
|
1112
|
+
}
|
|
1113
|
+
case "HIDE_DIALOG" /* HIDE_DIALOG */: {
|
|
1114
|
+
newContext = setValueByPath(newContext, "selectedTask", null);
|
|
1115
|
+
newContext = setValueByPath(
|
|
1116
|
+
newContext,
|
|
1117
|
+
"isTaskDetailDialogVisible",
|
|
1118
|
+
false
|
|
1119
|
+
);
|
|
1120
|
+
break;
|
|
1121
|
+
}
|
|
1122
|
+
case "HIDE_DETAIL" /* HIDE_DETAIL */: {
|
|
1123
|
+
newContext = setValueByPath(newContext, "selectedItemForDetail", null);
|
|
1124
|
+
newContext = setValueByPath(newContext, "isDetailViewOpen", false);
|
|
1125
|
+
break;
|
|
1126
|
+
}
|
|
1127
|
+
case "OPEN_DIALOG" /* OPEN_DIALOG */: {
|
|
1128
|
+
const dialogId = target || payload?.dialogId;
|
|
1129
|
+
if (dialogId === "taskDetailDialogNodeId" || !dialogId) {
|
|
1130
|
+
newContext = setValueByPath(
|
|
1131
|
+
newContext,
|
|
1132
|
+
"isTaskDetailDialogVisible",
|
|
1133
|
+
true
|
|
1134
|
+
);
|
|
1135
|
+
} else {
|
|
1136
|
+
console.warn(
|
|
1137
|
+
`[executeAction] ${"OPEN_DIALOG" /* OPEN_DIALOG */}: Unhandled dialogId: ${dialogId}.`
|
|
1138
|
+
);
|
|
1139
|
+
}
|
|
1140
|
+
break;
|
|
1141
|
+
}
|
|
1142
|
+
case "CLOSE_DIALOG" /* CLOSE_DIALOG */: {
|
|
1143
|
+
const dialogId = target || payload?.dialogId;
|
|
1144
|
+
if (dialogId === "taskDetailDialogNodeId" || !dialogId) {
|
|
1145
|
+
newContext = setValueByPath(
|
|
1146
|
+
newContext,
|
|
1147
|
+
"isTaskDetailDialogVisible",
|
|
1148
|
+
false
|
|
1149
|
+
);
|
|
1150
|
+
newContext = setValueByPath(newContext, "selectedTask", null);
|
|
1151
|
+
} else {
|
|
1152
|
+
console.warn(
|
|
1153
|
+
`[executeAction] ${"CLOSE_DIALOG" /* CLOSE_DIALOG */}: Unhandled dialogId: ${dialogId}.`
|
|
1154
|
+
);
|
|
1155
|
+
}
|
|
1156
|
+
break;
|
|
1157
|
+
}
|
|
1158
|
+
case "UPDATE_DATA" /* UPDATE_DATA */: {
|
|
1159
|
+
let updatedContext = context;
|
|
1160
|
+
if (target && payload && "value" in payload) {
|
|
1161
|
+
updatedContext = setValueByPath(context, target, payload.value);
|
|
1162
|
+
} else {
|
|
1163
|
+
console.warn(
|
|
1164
|
+
`[executeAction] ${"UPDATE_DATA" /* UPDATE_DATA */} requires targetPath (data path) and payload with 'value' property.`
|
|
1165
|
+
);
|
|
1166
|
+
}
|
|
1167
|
+
newContext = updatedContext;
|
|
1168
|
+
break;
|
|
1169
|
+
}
|
|
1170
|
+
case "ADD_ITEM" /* ADD_ITEM */: {
|
|
1171
|
+
if (!target) {
|
|
1172
|
+
console.warn(`[executeAction] ADD_ITEM requires target path.`);
|
|
1173
|
+
break;
|
|
1174
|
+
}
|
|
1175
|
+
if (!payload?.item) {
|
|
1176
|
+
console.warn(
|
|
1177
|
+
`[executeAction] ADD_ITEM requires payload with item property.`
|
|
1178
|
+
);
|
|
1179
|
+
break;
|
|
1180
|
+
}
|
|
1181
|
+
const list = getValueByPath(newContext, target);
|
|
1182
|
+
if (!Array.isArray(list)) {
|
|
1183
|
+
console.warn(
|
|
1184
|
+
`[executeAction] ADD_ITEM failed: target path "${target}" does not resolve to an array.`
|
|
1185
|
+
);
|
|
1186
|
+
break;
|
|
1187
|
+
}
|
|
1188
|
+
const newItem = payload.item;
|
|
1189
|
+
const position = payload.position;
|
|
1190
|
+
let newList;
|
|
1191
|
+
if (position === "start") {
|
|
1192
|
+
newList = [newItem, ...list];
|
|
1193
|
+
} else {
|
|
1194
|
+
newList = [...list, newItem];
|
|
1195
|
+
}
|
|
1196
|
+
newContext = setValueByPath(newContext, target, newList);
|
|
1197
|
+
break;
|
|
1198
|
+
}
|
|
1199
|
+
case "DELETE_ITEM" /* DELETE_ITEM */: {
|
|
1200
|
+
if (!target) {
|
|
1201
|
+
console.warn(`[executeAction] DELETE_ITEM requires target path.`);
|
|
1202
|
+
break;
|
|
1203
|
+
}
|
|
1204
|
+
const itemId = payload?.id;
|
|
1205
|
+
if (itemId === void 0 || itemId === null) {
|
|
1206
|
+
console.warn(
|
|
1207
|
+
`[executeAction] DELETE_ITEM requires payload with id property.`
|
|
1208
|
+
);
|
|
1209
|
+
break;
|
|
1210
|
+
}
|
|
1211
|
+
const list = getValueByPath(newContext, target);
|
|
1212
|
+
if (!Array.isArray(list)) {
|
|
1213
|
+
console.warn(
|
|
1214
|
+
`[executeAction] DELETE_ITEM failed: target path "${target}" does not resolve to an array.`
|
|
1215
|
+
);
|
|
1216
|
+
break;
|
|
1217
|
+
}
|
|
1218
|
+
const newList = list.filter(
|
|
1219
|
+
(item) => item?.id !== itemId
|
|
1220
|
+
);
|
|
1221
|
+
if (newList.length !== list.length) {
|
|
1222
|
+
newContext = setValueByPath(newContext, target, newList);
|
|
1223
|
+
} else {
|
|
1224
|
+
console.warn(
|
|
1225
|
+
`[executeAction] DELETE_ITEM: Item with id "${itemId}" not found in list at path "${target}".`
|
|
1226
|
+
);
|
|
1227
|
+
}
|
|
1228
|
+
break;
|
|
1229
|
+
}
|
|
1230
|
+
case "SAVE_TASK_CHANGES" /* SAVE_TASK_CHANGES */: {
|
|
1231
|
+
if (!target) {
|
|
1232
|
+
console.warn(
|
|
1233
|
+
"[executeAction] SAVE_TASK_CHANGES requires target (task ID)."
|
|
1234
|
+
);
|
|
1235
|
+
break;
|
|
1236
|
+
}
|
|
1237
|
+
const taskIdToSave = target;
|
|
1238
|
+
const currentTasks = getValueByPath(newContext, "tasks.data");
|
|
1239
|
+
const selectedTaskData = getValueByPath(newContext, "selectedTask");
|
|
1240
|
+
if (currentTasks && selectedTaskData && selectedTaskData.id === taskIdToSave) {
|
|
1241
|
+
const updatedTasks = currentTasks.map(
|
|
1242
|
+
(task) => task.id === taskIdToSave ? { ...task, ...selectedTaskData, ...payload } : task
|
|
1243
|
+
// Merge selectedTaskData and any direct payload changes
|
|
1244
|
+
);
|
|
1245
|
+
newContext = setValueByPath(newContext, "tasks.data", updatedTasks);
|
|
1246
|
+
newContext = setValueByPath(newContext, "selectedTask", null);
|
|
1247
|
+
newContext = setValueByPath(
|
|
1248
|
+
newContext,
|
|
1249
|
+
"isTaskDetailDialogVisible",
|
|
1250
|
+
false
|
|
1251
|
+
);
|
|
1252
|
+
} else {
|
|
1253
|
+
console.warn(
|
|
1254
|
+
"[executeAction] SAVE_TASK_CHANGES: Could not save. Task list, selected task, or ID mismatch.",
|
|
1255
|
+
{
|
|
1256
|
+
taskIdToSave,
|
|
1257
|
+
selectedTaskDataId: selectedTaskData?.id,
|
|
1258
|
+
currentTasksExists: !!currentTasks
|
|
961
1259
|
}
|
|
962
|
-
|
|
1260
|
+
);
|
|
963
1261
|
}
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
}
|
|
968
|
-
async function callPlannerLLM(input, openaiApiKey, routeResolution) {
|
|
969
|
-
await systemEvents.emit(
|
|
970
|
-
createSystemEvent("PLAN_START" /* PLAN_START */, { plannerInput: input })
|
|
971
|
-
);
|
|
972
|
-
if (env.MOCK_PLANNER === "1") {
|
|
973
|
-
console.warn(
|
|
974
|
-
`Using mock planner because MOCK_PLANNER environment variable is set to "1".`
|
|
975
|
-
);
|
|
976
|
-
return mockPlanner(input);
|
|
977
|
-
}
|
|
978
|
-
if (!openaiApiKey) {
|
|
979
|
-
console.warn(
|
|
980
|
-
`OpenAI API key was not provided to callPlannerLLM. Falling back to mock planner.`
|
|
981
|
-
);
|
|
982
|
-
return mockPlanner(input);
|
|
983
|
-
}
|
|
984
|
-
const startTime = Date.now();
|
|
985
|
-
const prompt = routeResolution?.prompt;
|
|
986
|
-
if (!prompt) {
|
|
987
|
-
throw new Error("ActionRouter did not provide a prompt to callPlannerLLM.");
|
|
1262
|
+
break;
|
|
1263
|
+
}
|
|
1264
|
+
default:
|
|
1265
|
+
console.warn(`[executeAction] Unhandled action type: ${action}`);
|
|
988
1266
|
}
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1267
|
+
return newContext;
|
|
1268
|
+
}
|
|
1269
|
+
function makeChildIdsUniqueInInstance(parentNode, baseInstanceId, originalTemplateRootId) {
|
|
1270
|
+
if (parentNode.children) {
|
|
1271
|
+
parentNode.children = parentNode.children.map((child) => {
|
|
1272
|
+
let originalChildId = child.id;
|
|
1273
|
+
if (child.id.startsWith(originalTemplateRootId + "-")) {
|
|
1274
|
+
const parts = child.id.split("-");
|
|
1275
|
+
if (parts.length > 1) {
|
|
1276
|
+
originalChildId = parts[parts.length - 1];
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
const newChildId = `${baseInstanceId}-${originalChildId}`;
|
|
1280
|
+
const newChild = {
|
|
1281
|
+
...JSON.parse(JSON.stringify(child)),
|
|
1282
|
+
// Deep clone child
|
|
1283
|
+
id: newChildId
|
|
1284
|
+
};
|
|
1285
|
+
makeChildIdsUniqueInInstance(
|
|
1286
|
+
newChild,
|
|
1287
|
+
baseInstanceId,
|
|
1288
|
+
originalTemplateRootId
|
|
1289
|
+
);
|
|
1290
|
+
return newChild;
|
|
1001
1291
|
});
|
|
1002
|
-
await systemEvents.emit(
|
|
1003
|
-
createSystemEvent("PLAN_COMPLETE" /* PLAN_COMPLETE */, {
|
|
1004
|
-
layout: uiSpec,
|
|
1005
|
-
executionTimeMs: Date.now() - startTime
|
|
1006
|
-
})
|
|
1007
|
-
);
|
|
1008
|
-
return uiSpec;
|
|
1009
|
-
} catch (error) {
|
|
1010
|
-
console.error("Error calling LLM planner:", error);
|
|
1011
|
-
await systemEvents.emit(
|
|
1012
|
-
createSystemEvent("PLAN_ERROR" /* PLAN_ERROR */, {
|
|
1013
|
-
error: error instanceof Error ? error : new Error(String(error))
|
|
1014
|
-
})
|
|
1015
|
-
);
|
|
1016
|
-
throw error;
|
|
1017
1292
|
}
|
|
1018
1293
|
}
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
}
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
openaiApiKey || "",
|
|
1042
|
-
routeResolution
|
|
1043
|
-
);
|
|
1044
|
-
return newLayout;
|
|
1294
|
+
|
|
1295
|
+
// src/core/action-router.ts
|
|
1296
|
+
var UI_GUIDANCE_BASE = `
|
|
1297
|
+
UI Guidance:
|
|
1298
|
+
1. Create a focused interface that directly addresses the goal
|
|
1299
|
+
2. Use appropriate UI patterns (lists, forms, details, etc.)
|
|
1300
|
+
3. Include navigation between related views when needed
|
|
1301
|
+
4. Keep the interface simple and intuitive
|
|
1302
|
+
5. Bind to schema data where appropriate
|
|
1303
|
+
6. **CRITICAL for Buttons:** All \`Button\` nodes **MUST** include a \`label\` property in their \`props\` (e.g., \`{ "props": { "label": "Click Me" } }\`).
|
|
1304
|
+
7. Provide event handlers for user interactions - make sure to always include both action and target properties`;
|
|
1305
|
+
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.`;
|
|
1306
|
+
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" } }\`).`;
|
|
1307
|
+
var COMMON_UI_GUIDANCE = UI_GUIDANCE_BASE + "\n" + // Add a newline separator
|
|
1308
|
+
LIST_BINDING_GUIDANCE + // Add the specific list binding rule
|
|
1309
|
+
"\n" + // Add a newline separator
|
|
1310
|
+
LIST_BINDING_EXAMPLE;
|
|
1311
|
+
function processTemplate(template, values) {
|
|
1312
|
+
return template.replace(/\${(.*?)}/g, (match, key) => {
|
|
1313
|
+
const trimmedKey = key.trim();
|
|
1314
|
+
return trimmedKey in values ? String(values[trimmedKey]) : match;
|
|
1315
|
+
});
|
|
1045
1316
|
}
|
|
1317
|
+
function buildPrompt(input, promptTemplate, templateValues) {
|
|
1318
|
+
const { schema, goal, history, userContext } = input;
|
|
1319
|
+
const schemaInfo = Object.entries(schema).map(([tableName, tableSchema]) => {
|
|
1320
|
+
const schemaString = typeof tableSchema === "object" && tableSchema !== null ? JSON.stringify(tableSchema) : String(tableSchema);
|
|
1321
|
+
return `Table: ${tableName}
|
|
1322
|
+
Schema: ${schemaString}`;
|
|
1323
|
+
}).join("\n\n");
|
|
1324
|
+
const recentEvents = history && history.length > 0 ? history.slice(-5).map(
|
|
1325
|
+
(event) => `Event: ${event.type} on node ${event.nodeId}${event.payload ? ` with payload ${JSON.stringify(event.payload)}` : ""}`
|
|
1326
|
+
).join("\n") : "No recent events";
|
|
1327
|
+
const userContextSection = userContext ? `
|
|
1328
|
+
|
|
1329
|
+
User Context:
|
|
1330
|
+
${JSON.stringify(userContext)}` : "";
|
|
1331
|
+
if (promptTemplate && templateValues) {
|
|
1332
|
+
const fullTemplateValues = {
|
|
1333
|
+
...templateValues,
|
|
1334
|
+
schemaInfo,
|
|
1335
|
+
recentEvents,
|
|
1336
|
+
userContextString: userContextSection.trim(),
|
|
1337
|
+
// Use trimmed version
|
|
1338
|
+
commonUIGuidance: COMMON_UI_GUIDANCE,
|
|
1339
|
+
goal
|
|
1340
|
+
// Ensure goal is always available to templates
|
|
1341
|
+
};
|
|
1342
|
+
return processTemplate(promptTemplate, fullTemplateValues);
|
|
1343
|
+
}
|
|
1344
|
+
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";
|
|
1345
|
+
return `
|
|
1346
|
+
You are an expert UI generator.
|
|
1347
|
+
Create a user interface that achieves the following goal: "${goal}".
|
|
1348
|
+
${interactionDescription}.
|
|
1349
|
+
|
|
1350
|
+
Available data schema:
|
|
1351
|
+
${schemaInfo}
|
|
1046
1352
|
|
|
1047
|
-
|
|
1353
|
+
Recent user interactions:
|
|
1354
|
+
${recentEvents}${userContextSection}
|
|
1355
|
+
|
|
1356
|
+
Generate a complete UI specification in JSON format that matches the following TypeScript type:
|
|
1357
|
+
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[]; };
|
|
1358
|
+
${COMMON_UI_GUIDANCE}
|
|
1359
|
+
|
|
1360
|
+
Respond ONLY with the JSON UI specification and no other text.
|
|
1361
|
+
`;
|
|
1362
|
+
}
|
|
1363
|
+
var ActionRouter = class {
|
|
1364
|
+
constructor(apiKey, planningConfig) {
|
|
1365
|
+
this.apiKey = apiKey;
|
|
1366
|
+
this.planningConfig = planningConfig || {
|
|
1367
|
+
prefetchDepth: 1,
|
|
1368
|
+
temperature: 0.5,
|
|
1369
|
+
streaming: false
|
|
1370
|
+
};
|
|
1371
|
+
}
|
|
1372
|
+
/**
|
|
1373
|
+
* Find the appropriate route for an event
|
|
1374
|
+
* @param event - UI event
|
|
1375
|
+
* @param layout - Current UI layout
|
|
1376
|
+
* @param dataContext - Current data context
|
|
1377
|
+
* @returns Route resolution or null if no match
|
|
1378
|
+
*/
|
|
1379
|
+
async resolveRoute(event, schema, layout, dataContext, goal, apiKey, userContext) {
|
|
1380
|
+
console.log(
|
|
1381
|
+
`[ActionRouter Debug] resolveRoute called for event type: ${event.type}, nodeId: ${event.nodeId}`
|
|
1382
|
+
);
|
|
1383
|
+
const sourceNode = layout ? findNodeById(layout, event.nodeId) : void 0;
|
|
1384
|
+
const nodeConfig = sourceNode?.events?.[event.type];
|
|
1385
|
+
let actionType;
|
|
1386
|
+
let determinedTargetNodeId;
|
|
1387
|
+
let determinedNodeConfigPayload = null;
|
|
1388
|
+
if (nodeConfig?.action) {
|
|
1389
|
+
actionType = nodeConfig.action;
|
|
1390
|
+
determinedTargetNodeId = nodeConfig.target || sourceNode?.id || "root";
|
|
1391
|
+
determinedNodeConfigPayload = nodeConfig.payload ? { ...nodeConfig.payload } : null;
|
|
1392
|
+
} else {
|
|
1393
|
+
switch (event.type) {
|
|
1394
|
+
case "INIT":
|
|
1395
|
+
actionType = "FULL_REFRESH" /* FULL_REFRESH */;
|
|
1396
|
+
determinedTargetNodeId = "root";
|
|
1397
|
+
break;
|
|
1398
|
+
case "CLICK":
|
|
1399
|
+
actionType = "FULL_REFRESH" /* FULL_REFRESH */;
|
|
1400
|
+
determinedTargetNodeId = "root";
|
|
1401
|
+
break;
|
|
1402
|
+
case "CHANGE":
|
|
1403
|
+
if (sourceNode && ["Input", "Select", "Textarea", "Checkbox", "RadioGroup"].includes(sourceNode.node_type)) {
|
|
1404
|
+
actionType = "UPDATE_DATA" /* UPDATE_DATA */;
|
|
1405
|
+
determinedTargetNodeId = sourceNode.id;
|
|
1406
|
+
} else {
|
|
1407
|
+
actionType = "FULL_REFRESH" /* FULL_REFRESH */;
|
|
1408
|
+
determinedTargetNodeId = "root";
|
|
1409
|
+
}
|
|
1410
|
+
break;
|
|
1411
|
+
default:
|
|
1412
|
+
actionType = "FULL_REFRESH" /* FULL_REFRESH */;
|
|
1413
|
+
determinedTargetNodeId = "root";
|
|
1414
|
+
break;
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
if (!Object.values(ActionType).includes(actionType)) {
|
|
1418
|
+
throw new Error(`Invalid action type: ${actionType}`);
|
|
1419
|
+
}
|
|
1420
|
+
const additionalContext = {};
|
|
1421
|
+
if (sourceNode) {
|
|
1422
|
+
additionalContext.sourceNode = sourceNode;
|
|
1423
|
+
}
|
|
1424
|
+
const targetNode = layout ? findNodeById(layout, determinedTargetNodeId) : void 0;
|
|
1425
|
+
if (targetNode) {
|
|
1426
|
+
additionalContext.targetNode = targetNode;
|
|
1427
|
+
}
|
|
1428
|
+
let finalEventPayload = event.payload && Object.keys(event.payload).length > 0 ? { ...event.payload } : null;
|
|
1429
|
+
if (determinedNodeConfigPayload) {
|
|
1430
|
+
finalEventPayload = { ...finalEventPayload || {}, ...determinedNodeConfigPayload };
|
|
1431
|
+
}
|
|
1432
|
+
if (finalEventPayload) {
|
|
1433
|
+
additionalContext.eventPayload = finalEventPayload;
|
|
1434
|
+
}
|
|
1435
|
+
const plannerInput2 = {
|
|
1436
|
+
schema,
|
|
1437
|
+
goal,
|
|
1438
|
+
history: [event],
|
|
1439
|
+
userContext: { ...userContext || {}, ...additionalContext }
|
|
1440
|
+
};
|
|
1441
|
+
if (actionType === "SHOW_DETAIL" /* SHOW_DETAIL */) {
|
|
1442
|
+
const itemData = dataContext.tasks?.data?.find(
|
|
1443
|
+
(task) => task.id === (event.payload?.itemId || event.payload?.taskId)
|
|
1444
|
+
);
|
|
1445
|
+
const updatedDataContext = {
|
|
1446
|
+
...dataContext,
|
|
1447
|
+
selectedTask: itemData,
|
|
1448
|
+
isTaskDetailDialogVisible: true
|
|
1449
|
+
};
|
|
1450
|
+
const layoutFromLLM = await callPlannerLLM(plannerInput2, apiKey);
|
|
1451
|
+
return {
|
|
1452
|
+
actionType: "UPDATE_CONTEXT" /* UPDATE_CONTEXT */,
|
|
1453
|
+
targetNodeId: determinedTargetNodeId,
|
|
1454
|
+
updatedDataContext,
|
|
1455
|
+
updatedNode: layoutFromLLM
|
|
1456
|
+
};
|
|
1457
|
+
}
|
|
1458
|
+
if (actionType === "HIDE_DIALOG" /* HIDE_DIALOG */) {
|
|
1459
|
+
if (!layout) {
|
|
1460
|
+
throw new Error("Layout cannot be null when handling HIDE_DIALOG");
|
|
1461
|
+
}
|
|
1462
|
+
return {
|
|
1463
|
+
actionType: "UPDATE_CONTEXT" /* UPDATE_CONTEXT */,
|
|
1464
|
+
targetNodeId: determinedTargetNodeId,
|
|
1465
|
+
updatedDataContext: {
|
|
1466
|
+
...dataContext,
|
|
1467
|
+
selectedTask: null,
|
|
1468
|
+
isTaskDetailDialogVisible: false
|
|
1469
|
+
},
|
|
1470
|
+
updatedNode: layout
|
|
1471
|
+
};
|
|
1472
|
+
}
|
|
1473
|
+
if (["UPDATE_DATA" /* UPDATE_DATA */, "ADD_ITEM" /* ADD_ITEM */, "DELETE_ITEM" /* DELETE_ITEM */, "SAVE_TASK_CHANGES" /* SAVE_TASK_CHANGES */].includes(actionType)) {
|
|
1474
|
+
if (!layout) {
|
|
1475
|
+
throw new Error("Layout cannot be null when handling data update actions");
|
|
1476
|
+
}
|
|
1477
|
+
const targetPathOrId = actionType === "UPDATE_DATA" /* UPDATE_DATA */ ? sourceNode?.bindings?.value || determinedTargetNodeId : determinedTargetNodeId;
|
|
1478
|
+
return {
|
|
1479
|
+
actionType,
|
|
1480
|
+
targetNodeId: determinedTargetNodeId,
|
|
1481
|
+
updatedNode: layout,
|
|
1482
|
+
updatedDataContext: executeAction(actionType, targetPathOrId, finalEventPayload || {}, dataContext)
|
|
1483
|
+
};
|
|
1484
|
+
}
|
|
1485
|
+
if ([
|
|
1486
|
+
"FULL_REFRESH" /* FULL_REFRESH */,
|
|
1487
|
+
"UPDATE_NODE" /* UPDATE_NODE */,
|
|
1488
|
+
"ADD_DROPDOWN" /* ADD_DROPDOWN */,
|
|
1489
|
+
"TOGGLE_STATE" /* TOGGLE_STATE */,
|
|
1490
|
+
"UPDATE_FORM" /* UPDATE_FORM */,
|
|
1491
|
+
"NAVIGATE" /* NAVIGATE */,
|
|
1492
|
+
"UPDATE_CONTEXT" /* UPDATE_CONTEXT */
|
|
1493
|
+
].includes(actionType)) {
|
|
1494
|
+
const layoutFromLLM = await callPlannerLLM(plannerInput2, apiKey);
|
|
1495
|
+
return {
|
|
1496
|
+
actionType,
|
|
1497
|
+
targetNodeId: determinedTargetNodeId,
|
|
1498
|
+
updatedNode: layoutFromLLM,
|
|
1499
|
+
updatedDataContext: dataContext
|
|
1500
|
+
};
|
|
1501
|
+
}
|
|
1502
|
+
throw new Error(`Unhandled action type: ${actionType}`);
|
|
1503
|
+
}
|
|
1504
|
+
};
|
|
1048
1505
|
function useUIStateEngine({
|
|
1049
1506
|
schema,
|
|
1050
1507
|
goal,
|
|
1051
|
-
|
|
1508
|
+
apiKey = "",
|
|
1052
1509
|
userContext,
|
|
1053
|
-
mockMode = false,
|
|
1054
1510
|
planningConfig,
|
|
1055
|
-
router = createDefaultRouter(),
|
|
1056
1511
|
dataContext = {},
|
|
1057
|
-
enablePartialUpdates =
|
|
1512
|
+
enablePartialUpdates = true
|
|
1058
1513
|
}) {
|
|
1059
1514
|
if (userContext === null) {
|
|
1060
1515
|
console.warn(
|
|
@@ -1062,84 +1517,68 @@ function useUIStateEngine({
|
|
|
1062
1517
|
);
|
|
1063
1518
|
}
|
|
1064
1519
|
const [state, dispatch] = React.useReducer(uiReducer, initialState);
|
|
1520
|
+
const stateRef = React.useRef(state);
|
|
1521
|
+
const router = new ActionRouter(apiKey, planningConfig);
|
|
1522
|
+
React.useEffect(() => {
|
|
1523
|
+
stateRef.current = state;
|
|
1524
|
+
}, [state]);
|
|
1065
1525
|
const handleEvent = React.useCallback(
|
|
1066
|
-
async (event) => {
|
|
1526
|
+
async (event, currentResolvedLayout, updatedDataContext) => {
|
|
1067
1527
|
dispatch({ type: "UI_EVENT", event });
|
|
1068
1528
|
dispatch({ type: "LOADING", isLoading: true });
|
|
1069
1529
|
try {
|
|
1070
1530
|
let resolvedNode;
|
|
1071
1531
|
let actionTypeForDispatch = "FULL_REFRESH" /* FULL_REFRESH */;
|
|
1072
1532
|
let targetNodeIdForDispatch = "root";
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
userContext
|
|
1110
|
-
};
|
|
1111
|
-
if (mockMode) {
|
|
1112
|
-
resolvedNode = mockPlanner(input);
|
|
1113
|
-
} else {
|
|
1114
|
-
resolvedNode = await callPlannerLLM(
|
|
1115
|
-
input,
|
|
1116
|
-
openaiApiKey || "",
|
|
1117
|
-
void 0
|
|
1118
|
-
);
|
|
1119
|
-
}
|
|
1533
|
+
const layoutForRouting = currentResolvedLayout || stateRef.current.layout;
|
|
1534
|
+
const contextForRouting = updatedDataContext || dataContext;
|
|
1535
|
+
console.log(
|
|
1536
|
+
"[state.ts handleEvent] About to call router.resolveRoute. enablePartialUpdates:",
|
|
1537
|
+
enablePartialUpdates
|
|
1538
|
+
);
|
|
1539
|
+
console.log(
|
|
1540
|
+
"[state.ts handleEvent] layoutForRouting ID (if exists):",
|
|
1541
|
+
layoutForRouting?.id
|
|
1542
|
+
);
|
|
1543
|
+
console.log(
|
|
1544
|
+
"[state.ts handleEvent] contextForRouting:",
|
|
1545
|
+
JSON.stringify(contextForRouting, null, 2)
|
|
1546
|
+
);
|
|
1547
|
+
const route = await router.resolveRoute(
|
|
1548
|
+
event,
|
|
1549
|
+
schema,
|
|
1550
|
+
layoutForRouting,
|
|
1551
|
+
contextForRouting,
|
|
1552
|
+
goal,
|
|
1553
|
+
apiKey,
|
|
1554
|
+
userContext
|
|
1555
|
+
);
|
|
1556
|
+
console.log(
|
|
1557
|
+
"[state.ts handleEvent] router.resolveRoute returned:",
|
|
1558
|
+
route
|
|
1559
|
+
);
|
|
1560
|
+
if (route) {
|
|
1561
|
+
console.log("Resolved route:", route);
|
|
1562
|
+
actionTypeForDispatch = route.actionType;
|
|
1563
|
+
targetNodeIdForDispatch = route.targetNodeId;
|
|
1564
|
+
if (route.updatedDataContext) {
|
|
1565
|
+
dispatch({
|
|
1566
|
+
type: "SET_DATA_CONTEXT",
|
|
1567
|
+
payload: route.updatedDataContext
|
|
1568
|
+
});
|
|
1120
1569
|
}
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
goal,
|
|
1125
|
-
history: [...state.history, event],
|
|
1126
|
-
// event is already in history from UI_EVENT dispatch
|
|
1127
|
-
userContext
|
|
1128
|
-
};
|
|
1129
|
-
if (mockMode) {
|
|
1130
|
-
resolvedNode = mockPlanner(input);
|
|
1131
|
-
} else {
|
|
1132
|
-
resolvedNode = await callPlannerLLM(
|
|
1133
|
-
input,
|
|
1134
|
-
openaiApiKey || "",
|
|
1135
|
-
void 0
|
|
1570
|
+
if (!route.updatedNode) {
|
|
1571
|
+
throw new Error(
|
|
1572
|
+
"No updatedNode returned from router.resolveRoute. This should not happen."
|
|
1136
1573
|
);
|
|
1137
1574
|
}
|
|
1575
|
+
resolvedNode = route.updatedNode;
|
|
1576
|
+
} else {
|
|
1577
|
+
throw new Error("No route returned from router.resolveRoute");
|
|
1138
1578
|
}
|
|
1139
1579
|
switch (actionTypeForDispatch) {
|
|
1140
1580
|
case "UPDATE_NODE" /* UPDATE_NODE */:
|
|
1141
1581
|
case "SHOW_DETAIL" /* SHOW_DETAIL */:
|
|
1142
|
-
case "HIDE_DETAIL" /* HIDE_DETAIL */:
|
|
1143
1582
|
case "TOGGLE_STATE" /* TOGGLE_STATE */:
|
|
1144
1583
|
case "ADD_DROPDOWN" /* ADD_DROPDOWN */:
|
|
1145
1584
|
case "UPDATE_FORM" /* UPDATE_FORM */:
|
|
@@ -1150,6 +1589,16 @@ function useUIStateEngine({
|
|
|
1150
1589
|
node: resolvedNode
|
|
1151
1590
|
});
|
|
1152
1591
|
break;
|
|
1592
|
+
case "HIDE_DIALOG" /* HIDE_DIALOG */:
|
|
1593
|
+
dispatch({
|
|
1594
|
+
type: "PARTIAL_UPDATE",
|
|
1595
|
+
nodeId: targetNodeIdForDispatch,
|
|
1596
|
+
node: resolvedNode
|
|
1597
|
+
});
|
|
1598
|
+
break;
|
|
1599
|
+
case "SAVE_TASK_CHANGES" /* SAVE_TASK_CHANGES */:
|
|
1600
|
+
dispatch({ type: "AI_RESPONSE", node: resolvedNode });
|
|
1601
|
+
break;
|
|
1153
1602
|
case "FULL_REFRESH" /* FULL_REFRESH */:
|
|
1154
1603
|
default:
|
|
1155
1604
|
dispatch({ type: "AI_RESPONSE", node: resolvedNode });
|
|
@@ -1168,80 +1617,57 @@ function useUIStateEngine({
|
|
|
1168
1617
|
}
|
|
1169
1618
|
},
|
|
1170
1619
|
[
|
|
1171
|
-
// append, // REMOVE
|
|
1172
1620
|
goal,
|
|
1173
1621
|
schema,
|
|
1174
|
-
state.history,
|
|
1175
|
-
// Keep state.history if input preparation needs it
|
|
1176
|
-
state.layout,
|
|
1177
|
-
// stop, // REMOVE
|
|
1178
1622
|
userContext,
|
|
1179
|
-
router,
|
|
1180
|
-
mockMode,
|
|
1181
1623
|
dataContext,
|
|
1182
|
-
|
|
1624
|
+
apiKey,
|
|
1183
1625
|
enablePartialUpdates,
|
|
1184
1626
|
dispatch
|
|
1185
|
-
// Add dispatch
|
|
1186
1627
|
]
|
|
1187
1628
|
);
|
|
1188
1629
|
React.useEffect(() => {
|
|
1189
1630
|
const initialFetch = async () => {
|
|
1190
1631
|
dispatch({ type: "LOADING", isLoading: true });
|
|
1191
1632
|
try {
|
|
1192
|
-
const
|
|
1633
|
+
const initEvent = {
|
|
1634
|
+
type: "INIT",
|
|
1635
|
+
nodeId: "system",
|
|
1636
|
+
timestamp: Date.now(),
|
|
1637
|
+
payload: null
|
|
1638
|
+
};
|
|
1639
|
+
const route = await router.resolveRoute(
|
|
1640
|
+
initEvent,
|
|
1193
1641
|
schema,
|
|
1642
|
+
stateRef.current.layout,
|
|
1643
|
+
dataContext,
|
|
1194
1644
|
goal,
|
|
1195
|
-
|
|
1196
|
-
// Initial history is empty
|
|
1645
|
+
apiKey,
|
|
1197
1646
|
userContext
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
} else {
|
|
1203
|
-
const initEvent = {
|
|
1204
|
-
type: "INIT",
|
|
1205
|
-
// Assuming "INIT" is your initial event type
|
|
1206
|
-
nodeId: "system",
|
|
1207
|
-
// Or some other appropriate initial nodeId
|
|
1208
|
-
timestamp: Date.now(),
|
|
1209
|
-
payload: null
|
|
1210
|
-
};
|
|
1211
|
-
const route = router.resolveRoute(
|
|
1212
|
-
initEvent,
|
|
1213
|
-
schema,
|
|
1214
|
-
null,
|
|
1215
|
-
// No existing layout on initial fetch
|
|
1216
|
-
dataContext,
|
|
1217
|
-
goal,
|
|
1218
|
-
userContext
|
|
1219
|
-
);
|
|
1220
|
-
if (!route || !route.prompt) {
|
|
1221
|
-
console.error(
|
|
1222
|
-
"[UIStateEngine] Initial fetch: Failed to resolve route or get prompt for INIT event."
|
|
1223
|
-
);
|
|
1224
|
-
throw new Error("Failed to initialize UI due to routing error.");
|
|
1225
|
-
}
|
|
1226
|
-
systemEvents.emit(
|
|
1227
|
-
createSystemEvent("PLAN_START" /* PLAN_START */, {
|
|
1228
|
-
plannerInput: route.plannerInput
|
|
1229
|
-
})
|
|
1647
|
+
);
|
|
1648
|
+
if (!route) {
|
|
1649
|
+
console.error(
|
|
1650
|
+
"[UIStateEngine] Initial fetch: Failed to resolve route for INIT event."
|
|
1230
1651
|
);
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1652
|
+
throw new Error("Failed to initialize UI due to routing error.");
|
|
1653
|
+
}
|
|
1654
|
+
if (route.updatedDataContext) {
|
|
1655
|
+
dispatch({
|
|
1656
|
+
type: "SET_DATA_CONTEXT",
|
|
1657
|
+
payload: route.updatedDataContext
|
|
1658
|
+
});
|
|
1659
|
+
}
|
|
1660
|
+
if (!route.updatedNode) {
|
|
1661
|
+
throw new Error(
|
|
1662
|
+
"No updatedNode returned from router.resolveRoute on initial fetch. This should not happen."
|
|
1237
1663
|
);
|
|
1238
1664
|
}
|
|
1665
|
+
const node = route.updatedNode;
|
|
1239
1666
|
dispatch({ type: "AI_RESPONSE", node });
|
|
1240
1667
|
} catch (e) {
|
|
1241
1668
|
const errorMessage = e instanceof Error ? e.message : String(e);
|
|
1242
1669
|
dispatch({ type: "ERROR", message: errorMessage });
|
|
1243
1670
|
systemEvents.emit(
|
|
1244
|
-
// Also emit system event for initial load error
|
|
1245
1671
|
createSystemEvent("PLAN_ERROR" /* PLAN_ERROR */, {
|
|
1246
1672
|
error: e instanceof Error ? e : new Error(String(e))
|
|
1247
1673
|
})
|
|
@@ -1251,7 +1677,7 @@ function useUIStateEngine({
|
|
|
1251
1677
|
}
|
|
1252
1678
|
};
|
|
1253
1679
|
initialFetch();
|
|
1254
|
-
}, [goal, schema,
|
|
1680
|
+
}, [goal, schema, dispatch]);
|
|
1255
1681
|
return {
|
|
1256
1682
|
state,
|
|
1257
1683
|
dispatch,
|
|
@@ -1734,45 +2160,67 @@ var Button = ({ onClick, children, variant = "default" }) => /* @__PURE__ */ jsx
|
|
|
1734
2160
|
}
|
|
1735
2161
|
);
|
|
1736
2162
|
var Detail = ({ data, fields = [], title, visible = true, onBack }) => {
|
|
1737
|
-
if (!visible)
|
|
2163
|
+
if (!visible) {
|
|
2164
|
+
console.log(
|
|
2165
|
+
`[Detail Component Internal] Detail component for node id: ${data?.id || title || "Unknown Detail"} is NOT RENDERING because visible is ${visible}.`
|
|
2166
|
+
);
|
|
1738
2167
|
return null;
|
|
2168
|
+
}
|
|
2169
|
+
console.log(
|
|
2170
|
+
`[Detail Component Internal] Detail component for node id: ${data?.id || title || "Unknown Detail"} IS RENDERING because visible is ${visible}.`
|
|
2171
|
+
);
|
|
1739
2172
|
return /* @__PURE__ */ jsxRuntime.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: [
|
|
1740
2173
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-between items-center border-b border-gray-200 dark:border-gray-700 pb-3", children: [
|
|
1741
2174
|
title && /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-lg font-medium text-gray-800 dark:text-white", children: title }),
|
|
1742
2175
|
onBack && /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "outline", onClick: onBack, children: "Back" })
|
|
1743
2176
|
] }),
|
|
1744
2177
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-4", children: fields.map((field) => {
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
2178
|
+
try {
|
|
2179
|
+
if (field.type === "heading") {
|
|
2180
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2181
|
+
"h3",
|
|
2182
|
+
{
|
|
2183
|
+
className: "text-xl font-semibold text-gray-800 dark:text-white",
|
|
2184
|
+
children: data?.[field.key] ?? ""
|
|
2185
|
+
},
|
|
2186
|
+
field.key
|
|
2187
|
+
);
|
|
2188
|
+
}
|
|
2189
|
+
if (field.type === "content") {
|
|
2190
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2191
|
+
"div",
|
|
2192
|
+
{
|
|
2193
|
+
className: "text-sm text-gray-700 dark:text-gray-300",
|
|
2194
|
+
children: data?.[field.key] ?? ""
|
|
2195
|
+
},
|
|
2196
|
+
field.key
|
|
2197
|
+
);
|
|
2198
|
+
}
|
|
2199
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1757
2200
|
"div",
|
|
1758
2201
|
{
|
|
1759
|
-
className: "
|
|
1760
|
-
children:
|
|
2202
|
+
className: "flex flex-col border-b border-gray-100 dark:border-gray-800 py-2",
|
|
2203
|
+
children: [
|
|
2204
|
+
field.label && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-gray-600 dark:text-gray-400 font-medium", children: field.label }),
|
|
2205
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-gray-800 dark:text-gray-200", children: data?.[field.key] ?? "" })
|
|
2206
|
+
]
|
|
1761
2207
|
},
|
|
1762
2208
|
field.key
|
|
1763
2209
|
);
|
|
2210
|
+
} catch (e) {
|
|
2211
|
+
console.error(
|
|
2212
|
+
`[Detail Component Internal] Error rendering field: ${field?.key}`,
|
|
2213
|
+
e,
|
|
2214
|
+
"Field data:",
|
|
2215
|
+
field,
|
|
2216
|
+
"Full data object:",
|
|
2217
|
+
data
|
|
2218
|
+
);
|
|
2219
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
2220
|
+
"Error rendering field: ",
|
|
2221
|
+
field?.key
|
|
2222
|
+
] }, field?.key || "error-field");
|
|
1764
2223
|
}
|
|
1765
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1766
|
-
"div",
|
|
1767
|
-
{
|
|
1768
|
-
className: "flex flex-col border-b border-gray-100 dark:border-gray-800 py-2",
|
|
1769
|
-
children: [
|
|
1770
|
-
field.label && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-gray-600 dark:text-gray-400 font-medium", children: field.label }),
|
|
1771
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-gray-800 dark:text-gray-200", children: data?.[field.key] ?? "" })
|
|
1772
|
-
]
|
|
1773
|
-
},
|
|
1774
|
-
field.key
|
|
1775
|
-
);
|
|
1776
2224
|
}) })
|
|
1777
2225
|
] });
|
|
1778
2226
|
};
|
|
@@ -1813,9 +2261,9 @@ var isUISpecNode = (value) => {
|
|
|
1813
2261
|
return isString(value.id) && isString(value.node_type);
|
|
1814
2262
|
};
|
|
1815
2263
|
var isDetailFieldObject = (item) => isObject(item) && isString(item.key) && isString(item.label) && (item.type === void 0 || isString(item.type));
|
|
1816
|
-
var createEventHandler = (node, eventName, uiEventType2,
|
|
2264
|
+
var createEventHandler = (node, eventName, uiEventType2, processEvent) => {
|
|
1817
2265
|
const eventConfig = node.events?.[uiEventType2];
|
|
1818
|
-
if (!
|
|
2266
|
+
if (!processEvent || !eventConfig)
|
|
1819
2267
|
return void 0;
|
|
1820
2268
|
return (eventPayload) => {
|
|
1821
2269
|
const fullEvent = {
|
|
@@ -1827,16 +2275,16 @@ var createEventHandler = (node, eventName, uiEventType2, processEvent2) => {
|
|
|
1827
2275
|
...eventPayload || {}
|
|
1828
2276
|
}
|
|
1829
2277
|
};
|
|
1830
|
-
|
|
2278
|
+
processEvent(fullEvent);
|
|
1831
2279
|
};
|
|
1832
2280
|
};
|
|
1833
2281
|
var adapterMap = {
|
|
1834
|
-
Container: (node,
|
|
2282
|
+
Container: (node, processEvent) => {
|
|
1835
2283
|
const { className, style: styleProp, key, ...restProps } = node.props || {};
|
|
1836
2284
|
const children = node.children?.map(
|
|
1837
2285
|
(child) => (
|
|
1838
2286
|
// Use React.cloneElement to add the key prop to the element returned by renderNode
|
|
1839
|
-
React__default.default.cloneElement(renderNode(child,
|
|
2287
|
+
React__default.default.cloneElement(renderNode(child, processEvent), { key: child.id })
|
|
1840
2288
|
)
|
|
1841
2289
|
);
|
|
1842
2290
|
const style = typeof styleProp === "string" ? parseStyleString(styleProp) : styleProp;
|
|
@@ -1868,23 +2316,31 @@ var adapterMap = {
|
|
|
1868
2316
|
className: getSafeProp(node.props, "className", isString, "")
|
|
1869
2317
|
}
|
|
1870
2318
|
),
|
|
1871
|
-
Button: (node,
|
|
2319
|
+
Button: (node, processEvent) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
1872
2320
|
Button,
|
|
1873
2321
|
{
|
|
1874
2322
|
variant: getSafeProp(node.props, "variant", isButtonVariant, "default"),
|
|
1875
|
-
onClick: createEventHandler(node, "onClick", "CLICK",
|
|
2323
|
+
onClick: createEventHandler(node, "onClick", "CLICK", processEvent),
|
|
1876
2324
|
children: getSafeProp(node.props, "label", isString, "Button")
|
|
1877
2325
|
}
|
|
1878
2326
|
),
|
|
1879
|
-
ListView: (node,
|
|
1880
|
-
const {
|
|
2327
|
+
ListView: (node, processEvent) => {
|
|
2328
|
+
const {
|
|
2329
|
+
className,
|
|
2330
|
+
style: styleProp,
|
|
2331
|
+
key,
|
|
2332
|
+
// Exclude data prop (array) from being spread onto the DOM element
|
|
2333
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2334
|
+
data: _data,
|
|
2335
|
+
...restProps
|
|
2336
|
+
} = node.props || {};
|
|
1881
2337
|
const style = typeof styleProp === "string" ? parseStyleString(styleProp) : styleProp;
|
|
1882
2338
|
console.log(
|
|
1883
2339
|
`[Adapter Debug] Rendering ListView: id=${node.id}, props=`,
|
|
1884
2340
|
node.props
|
|
1885
2341
|
);
|
|
1886
2342
|
const children = node.children?.map(
|
|
1887
|
-
(child) => React__default.default.cloneElement(renderNode(child,
|
|
2343
|
+
(child) => React__default.default.cloneElement(renderNode(child, processEvent), { key: child.id })
|
|
1888
2344
|
);
|
|
1889
2345
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1890
2346
|
"div",
|
|
@@ -1901,21 +2357,28 @@ var adapterMap = {
|
|
|
1901
2357
|
key
|
|
1902
2358
|
);
|
|
1903
2359
|
},
|
|
1904
|
-
Detail: (node,
|
|
1905
|
-
const data =
|
|
1906
|
-
node.
|
|
2360
|
+
Detail: (node, processEvent) => {
|
|
2361
|
+
const data = getSafeProp(
|
|
2362
|
+
node.props,
|
|
1907
2363
|
"data",
|
|
1908
2364
|
isRecordWithReactNodeValues,
|
|
1909
2365
|
{}
|
|
1910
2366
|
);
|
|
1911
|
-
const fields =
|
|
1912
|
-
node.
|
|
2367
|
+
const fields = getSafeProp(
|
|
2368
|
+
node.props,
|
|
1913
2369
|
"fields",
|
|
1914
2370
|
isArrayOf(isDetailFieldObject),
|
|
1915
2371
|
[]
|
|
1916
2372
|
);
|
|
1917
2373
|
const title = getSafeProp(node.props, "title", isString, "");
|
|
1918
2374
|
const visible = getSafeProp(node.props, "visible", isBoolean, true);
|
|
2375
|
+
console.log(
|
|
2376
|
+
`[Adapter Debug] Rendering Detail: id=${node.id}, props=`,
|
|
2377
|
+
JSON.stringify(node.props),
|
|
2378
|
+
`effective visible=${visible}`,
|
|
2379
|
+
`typeof fields=${typeof fields}`,
|
|
2380
|
+
`Array.isArray(fields)=${Array.isArray(fields)}`
|
|
2381
|
+
);
|
|
1919
2382
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1920
2383
|
Detail,
|
|
1921
2384
|
{
|
|
@@ -1923,14 +2386,14 @@ var adapterMap = {
|
|
|
1923
2386
|
fields,
|
|
1924
2387
|
title,
|
|
1925
2388
|
visible,
|
|
1926
|
-
onBack: createEventHandler(node, "onBack", "CLICK",
|
|
2389
|
+
onBack: createEventHandler(node, "onBack", "CLICK", processEvent)
|
|
1927
2390
|
}
|
|
1928
2391
|
);
|
|
1929
2392
|
},
|
|
1930
|
-
Card: (node,
|
|
2393
|
+
Card: (node, processEvent) => {
|
|
1931
2394
|
const { className, style: styleProp, key, ...restProps } = node.props || {};
|
|
1932
2395
|
const children = node.children?.map(
|
|
1933
|
-
(child) => React__default.default.cloneElement(renderNode(child,
|
|
2396
|
+
(child) => React__default.default.cloneElement(renderNode(child, processEvent), { key: child.id })
|
|
1934
2397
|
);
|
|
1935
2398
|
const style = typeof styleProp === "string" ? parseStyleString(styleProp) : styleProp;
|
|
1936
2399
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -1948,7 +2411,7 @@ var adapterMap = {
|
|
|
1948
2411
|
key
|
|
1949
2412
|
);
|
|
1950
2413
|
},
|
|
1951
|
-
Input: (node,
|
|
2414
|
+
Input: (node, processEvent) => {
|
|
1952
2415
|
const name = getSafeProp(node.props, "name", isString, "inputName");
|
|
1953
2416
|
const label = getSafeProp(node.props, "label", isString, "");
|
|
1954
2417
|
const value = getSafeBinding(node.bindings, "value", isString, "");
|
|
@@ -1960,7 +2423,7 @@ var adapterMap = {
|
|
|
1960
2423
|
node,
|
|
1961
2424
|
"onChange",
|
|
1962
2425
|
"CHANGE",
|
|
1963
|
-
|
|
2426
|
+
processEvent
|
|
1964
2427
|
);
|
|
1965
2428
|
if (handler)
|
|
1966
2429
|
handler({ value: e.target.value });
|
|
@@ -1970,13 +2433,13 @@ var adapterMap = {
|
|
|
1970
2433
|
node,
|
|
1971
2434
|
"onFocus",
|
|
1972
2435
|
"FOCUS",
|
|
1973
|
-
|
|
2436
|
+
processEvent
|
|
1974
2437
|
);
|
|
1975
2438
|
if (handler)
|
|
1976
2439
|
handler({});
|
|
1977
2440
|
};
|
|
1978
2441
|
const handleBlur = () => {
|
|
1979
|
-
const handler = createEventHandler(node, "onBlur", "BLUR",
|
|
2442
|
+
const handler = createEventHandler(node, "onBlur", "BLUR", processEvent);
|
|
1980
2443
|
if (handler)
|
|
1981
2444
|
handler({});
|
|
1982
2445
|
};
|
|
@@ -1998,7 +2461,7 @@ var adapterMap = {
|
|
|
1998
2461
|
)
|
|
1999
2462
|
] });
|
|
2000
2463
|
},
|
|
2001
|
-
Select: (node,
|
|
2464
|
+
Select: (node, processEvent) => {
|
|
2002
2465
|
const name = getSafeProp(node.props, "name", isString, "selectName");
|
|
2003
2466
|
const label = getSafeProp(node.props, "label", isString, "");
|
|
2004
2467
|
const placeholder = getSafeProp(
|
|
@@ -2021,7 +2484,7 @@ var adapterMap = {
|
|
|
2021
2484
|
node,
|
|
2022
2485
|
"onValueChange",
|
|
2023
2486
|
"CHANGE",
|
|
2024
|
-
|
|
2487
|
+
processEvent
|
|
2025
2488
|
);
|
|
2026
2489
|
if (handler)
|
|
2027
2490
|
handler({ value: selectedValue });
|
|
@@ -2049,7 +2512,7 @@ var adapterMap = {
|
|
|
2049
2512
|
}
|
|
2050
2513
|
);
|
|
2051
2514
|
},
|
|
2052
|
-
Textarea: (node,
|
|
2515
|
+
Textarea: (node, processEvent) => {
|
|
2053
2516
|
const { key, ...propsWithoutKey } = node.props || {};
|
|
2054
2517
|
const name = getSafeProp(propsWithoutKey, "name", isString, "textareaName");
|
|
2055
2518
|
const label = getSafeProp(propsWithoutKey, "label", isString, "");
|
|
@@ -2073,7 +2536,7 @@ var adapterMap = {
|
|
|
2073
2536
|
node,
|
|
2074
2537
|
"onChange",
|
|
2075
2538
|
"CHANGE",
|
|
2076
|
-
|
|
2539
|
+
processEvent
|
|
2077
2540
|
);
|
|
2078
2541
|
if (handler)
|
|
2079
2542
|
handler({ value: e.target.value });
|
|
@@ -2083,13 +2546,13 @@ var adapterMap = {
|
|
|
2083
2546
|
node,
|
|
2084
2547
|
"onFocus",
|
|
2085
2548
|
"FOCUS",
|
|
2086
|
-
|
|
2549
|
+
processEvent
|
|
2087
2550
|
);
|
|
2088
2551
|
if (handler)
|
|
2089
2552
|
handler({});
|
|
2090
2553
|
};
|
|
2091
2554
|
const handleBlur = () => {
|
|
2092
|
-
const handler = createEventHandler(node, "onBlur", "BLUR",
|
|
2555
|
+
const handler = createEventHandler(node, "onBlur", "BLUR", processEvent);
|
|
2093
2556
|
if (handler)
|
|
2094
2557
|
handler({});
|
|
2095
2558
|
};
|
|
@@ -2112,7 +2575,7 @@ var adapterMap = {
|
|
|
2112
2575
|
)
|
|
2113
2576
|
] }, key);
|
|
2114
2577
|
},
|
|
2115
|
-
Checkbox: (node,
|
|
2578
|
+
Checkbox: (node, processEvent) => {
|
|
2116
2579
|
const { key, ...propsWithoutKey } = node.props || {};
|
|
2117
2580
|
const name = getSafeProp(propsWithoutKey, "name", isString, "checkboxName");
|
|
2118
2581
|
const label = getSafeProp(propsWithoutKey, "label", isString, "");
|
|
@@ -2125,7 +2588,7 @@ var adapterMap = {
|
|
|
2125
2588
|
node,
|
|
2126
2589
|
"onCheckedChange",
|
|
2127
2590
|
"CHANGE",
|
|
2128
|
-
|
|
2591
|
+
processEvent
|
|
2129
2592
|
);
|
|
2130
2593
|
if (handler)
|
|
2131
2594
|
handler({ checked: isChecked });
|
|
@@ -2152,7 +2615,7 @@ var adapterMap = {
|
|
|
2152
2615
|
key
|
|
2153
2616
|
);
|
|
2154
2617
|
},
|
|
2155
|
-
RadioGroup: (node,
|
|
2618
|
+
RadioGroup: (node, processEvent) => {
|
|
2156
2619
|
const { key, ...propsWithoutKey } = node.props || {};
|
|
2157
2620
|
const name = getSafeProp(
|
|
2158
2621
|
propsWithoutKey,
|
|
@@ -2175,7 +2638,7 @@ var adapterMap = {
|
|
|
2175
2638
|
node,
|
|
2176
2639
|
"onValueChange",
|
|
2177
2640
|
"CHANGE",
|
|
2178
|
-
|
|
2641
|
+
processEvent
|
|
2179
2642
|
);
|
|
2180
2643
|
if (handler)
|
|
2181
2644
|
handler({ value: selectedValue });
|
|
@@ -2218,7 +2681,7 @@ var adapterMap = {
|
|
|
2218
2681
|
key
|
|
2219
2682
|
);
|
|
2220
2683
|
},
|
|
2221
|
-
Tabs: (node,
|
|
2684
|
+
Tabs: (node, processEvent) => {
|
|
2222
2685
|
const { key, ...propsWithoutKey } = node.props || {};
|
|
2223
2686
|
const rawTabs = getSafeBinding(
|
|
2224
2687
|
node.bindings,
|
|
@@ -2238,7 +2701,7 @@ var adapterMap = {
|
|
|
2238
2701
|
node,
|
|
2239
2702
|
"onValueChange",
|
|
2240
2703
|
"CHANGE",
|
|
2241
|
-
|
|
2704
|
+
processEvent
|
|
2242
2705
|
);
|
|
2243
2706
|
if (handler)
|
|
2244
2707
|
handler({ value });
|
|
@@ -2252,18 +2715,26 @@ var adapterMap = {
|
|
|
2252
2715
|
"data-id": node.id,
|
|
2253
2716
|
children: [
|
|
2254
2717
|
/* @__PURE__ */ jsxRuntime.jsx(TabsList, { children: rawTabs.map((tab) => /* @__PURE__ */ jsxRuntime.jsx(TabsTrigger, { value: tab.value, children: tab.label }, tab.value)) }),
|
|
2255
|
-
rawTabs.map((tab) => /* @__PURE__ */ jsxRuntime.jsx(TabsContent, { value: tab.value, children: tab.content ? renderNode(tab.content,
|
|
2718
|
+
rawTabs.map((tab) => /* @__PURE__ */ jsxRuntime.jsx(TabsContent, { value: tab.value, children: tab.content ? renderNode(tab.content, processEvent) : null }, tab.value))
|
|
2256
2719
|
]
|
|
2257
2720
|
},
|
|
2258
2721
|
key
|
|
2259
2722
|
);
|
|
2260
2723
|
},
|
|
2261
|
-
Dialog: (node,
|
|
2724
|
+
Dialog: (node, processEvent) => {
|
|
2262
2725
|
const isOpen = getSafeBinding(
|
|
2263
2726
|
node.bindings,
|
|
2264
|
-
"
|
|
2727
|
+
"visible",
|
|
2265
2728
|
isBoolean,
|
|
2266
|
-
|
|
2729
|
+
// Check bindings.visible (planner output)
|
|
2730
|
+
getSafeProp(
|
|
2731
|
+
node.props,
|
|
2732
|
+
"open",
|
|
2733
|
+
isBoolean,
|
|
2734
|
+
// Then props.open (if binding resolution put it there)
|
|
2735
|
+
getSafeProp(node.props, "visible", isBoolean, false)
|
|
2736
|
+
// Then props.visible (if binding resolution put it there under 'visible')
|
|
2737
|
+
)
|
|
2267
2738
|
);
|
|
2268
2739
|
const {
|
|
2269
2740
|
title,
|
|
@@ -2274,10 +2745,13 @@ var adapterMap = {
|
|
|
2274
2745
|
key,
|
|
2275
2746
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2276
2747
|
open: _openProp,
|
|
2748
|
+
// ADD 'visible' to destructuring to prevent it from going into restProps
|
|
2749
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2750
|
+
visible: _visibleProp,
|
|
2277
2751
|
...restProps
|
|
2278
2752
|
} = node.props || {};
|
|
2279
2753
|
const children = node.children?.map(
|
|
2280
|
-
(child) => React__default.default.cloneElement(renderNode(child,
|
|
2754
|
+
(child) => React__default.default.cloneElement(renderNode(child, processEvent), { key: child.id })
|
|
2281
2755
|
);
|
|
2282
2756
|
const handleOpenChange = (open) => {
|
|
2283
2757
|
if (!open) {
|
|
@@ -2287,7 +2761,7 @@ var adapterMap = {
|
|
|
2287
2761
|
// Assumed event name in UISpec
|
|
2288
2762
|
"CLICK",
|
|
2289
2763
|
// Use CLICK as the event type for closing dialogs
|
|
2290
|
-
|
|
2764
|
+
processEvent
|
|
2291
2765
|
);
|
|
2292
2766
|
if (handler) {
|
|
2293
2767
|
handler({});
|
|
@@ -2369,12 +2843,30 @@ var adapterMap = {
|
|
|
2369
2843
|
},
|
|
2370
2844
|
key
|
|
2371
2845
|
);
|
|
2846
|
+
},
|
|
2847
|
+
Badge: (node) => {
|
|
2848
|
+
const { className, style: styleProp, key, ...restProps } = node.props || {};
|
|
2849
|
+
const text = getSafeProp(node.props, "text", isString, "");
|
|
2850
|
+
const style = typeof styleProp === "string" ? parseStyleString(styleProp) : styleProp;
|
|
2851
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2852
|
+
"span",
|
|
2853
|
+
{
|
|
2854
|
+
className: cn(
|
|
2855
|
+
"inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium bg-gray-100 text-gray-800",
|
|
2856
|
+
className
|
|
2857
|
+
),
|
|
2858
|
+
style,
|
|
2859
|
+
...restProps,
|
|
2860
|
+
children: text
|
|
2861
|
+
},
|
|
2862
|
+
key
|
|
2863
|
+
);
|
|
2372
2864
|
}
|
|
2373
2865
|
};
|
|
2374
|
-
function renderNode(node,
|
|
2866
|
+
function renderNode(node, processEvent) {
|
|
2375
2867
|
const mappedComponent = adapterMap[node.node_type];
|
|
2376
2868
|
if (mappedComponent) {
|
|
2377
|
-
return mappedComponent(node,
|
|
2869
|
+
return mappedComponent(node, processEvent);
|
|
2378
2870
|
}
|
|
2379
2871
|
console.warn(`Unknown node type: ${node.node_type}`);
|
|
2380
2872
|
return React__default.default.createElement(
|
|
@@ -2386,370 +2878,141 @@ function renderNode(node, processEvent2) {
|
|
|
2386
2878
|
var renderedNodesCache = /* @__PURE__ */ new Map();
|
|
2387
2879
|
var MAX_CACHE_SIZE = 10;
|
|
2388
2880
|
var CACHE_TTL = 5e3;
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
createSystemEvent("RENDER_START" /* RENDER_START */, { layout: node })
|
|
2398
|
-
);
|
|
2399
|
-
let result;
|
|
2400
|
-
switch (adapter) {
|
|
2401
|
-
case "shadcn":
|
|
2402
|
-
result = renderNode(node, processEvent2);
|
|
2403
|
-
break;
|
|
2404
|
-
default:
|
|
2405
|
-
console.warn(`Unsupported adapter: ${adapter}, falling back to shadcn`);
|
|
2406
|
-
result = renderNode(node, processEvent2);
|
|
2407
|
-
}
|
|
2408
|
-
await systemEvents.emit(
|
|
2409
|
-
createSystemEvent("RENDER_COMPLETE" /* RENDER_COMPLETE */, {
|
|
2410
|
-
layout: node,
|
|
2411
|
-
renderTimeMs: Date.now() - startTime
|
|
2412
|
-
})
|
|
2413
|
-
);
|
|
2414
|
-
renderedNodesCache.set(nodeId, {
|
|
2415
|
-
element: result,
|
|
2416
|
-
timestamp: startTime
|
|
2417
|
-
});
|
|
2418
|
-
if (renderedNodesCache.size > MAX_CACHE_SIZE) {
|
|
2419
|
-
const oldestKey = [...renderedNodesCache.entries()].sort(
|
|
2420
|
-
([, a], [, b]) => a.timestamp - b.timestamp
|
|
2421
|
-
)[0][0];
|
|
2422
|
-
renderedNodesCache.delete(oldestKey);
|
|
2423
|
-
}
|
|
2424
|
-
return result;
|
|
2425
|
-
}
|
|
2426
|
-
function renderShimmer(node, adapter = "shadcn") {
|
|
2427
|
-
if (!node) {
|
|
2428
|
-
return /* @__PURE__ */ jsxRuntime.jsx(ShimmerBlock, {});
|
|
2429
|
-
}
|
|
2430
|
-
switch (node.node_type) {
|
|
2431
|
-
case "ListView":
|
|
2432
|
-
return /* @__PURE__ */ jsxRuntime.jsx(ShimmerTable, { rows: 3 });
|
|
2433
|
-
case "Detail":
|
|
2434
|
-
return /* @__PURE__ */ jsxRuntime.jsx(ShimmerCard, {});
|
|
2435
|
-
case "Container":
|
|
2436
|
-
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-4", children: node.children?.map((child, index) => /* @__PURE__ */ jsxRuntime.jsx("div", { children: renderShimmer(child, adapter) }, index)) });
|
|
2437
|
-
default:
|
|
2438
|
-
return /* @__PURE__ */ jsxRuntime.jsx(ShimmerBlock, {});
|
|
2439
|
-
}
|
|
2440
|
-
}
|
|
2441
|
-
|
|
2442
|
-
// src/core/bindings.ts
|
|
2443
|
-
var bindingsCache = /* @__PURE__ */ new Map();
|
|
2444
|
-
var MAX_CACHE_SIZE2 = 50;
|
|
2445
|
-
var CACHE_TTL2 = 2e3;
|
|
2446
|
-
var nodeCacheTimestamps = /* @__PURE__ */ new Map();
|
|
2447
|
-
function hashDataContext(context) {
|
|
2448
|
-
return JSON.stringify(context);
|
|
2449
|
-
}
|
|
2450
|
-
function createCacheKey(nodeId, context) {
|
|
2451
|
-
return `${nodeId}:${hashDataContext(context)}`;
|
|
2452
|
-
}
|
|
2453
|
-
function getValueByPath(context, path) {
|
|
2454
|
-
const parts = path.split(".");
|
|
2455
|
-
let current = context;
|
|
2456
|
-
for (const part of parts) {
|
|
2457
|
-
if (current === null || current === void 0) {
|
|
2458
|
-
return void 0;
|
|
2459
|
-
}
|
|
2460
|
-
if (typeof current !== "object") {
|
|
2461
|
-
return void 0;
|
|
2462
|
-
}
|
|
2463
|
-
current = current[part];
|
|
2464
|
-
}
|
|
2465
|
-
return current;
|
|
2466
|
-
}
|
|
2467
|
-
function setValueByPath(context, path, value) {
|
|
2468
|
-
if (!path) {
|
|
2469
|
-
return context;
|
|
2470
|
-
}
|
|
2471
|
-
const result = { ...context };
|
|
2472
|
-
const parts = path.split(".");
|
|
2473
|
-
let current = result;
|
|
2474
|
-
for (let i = 0; i < parts.length - 1; i++) {
|
|
2475
|
-
const part = parts[i];
|
|
2476
|
-
if (typeof current !== "object" || current === null) {
|
|
2477
|
-
console.warn(
|
|
2478
|
-
`setValueByPath: Cannot traverse path "${path}". Parent segment "${parts[i - 1] || "(root)"}" is not an object.`
|
|
2479
|
-
);
|
|
2480
|
-
return context;
|
|
2481
|
-
}
|
|
2482
|
-
const currentAsObject = current;
|
|
2483
|
-
const nextPartValue = currentAsObject[part];
|
|
2484
|
-
if (nextPartValue === void 0 || nextPartValue === null) {
|
|
2485
|
-
currentAsObject[part] = {};
|
|
2486
|
-
} else if (typeof nextPartValue !== "object") {
|
|
2487
|
-
console.warn(
|
|
2488
|
-
`setValueByPath: Cannot create nested path "${path}". Segment "${part}" is not an object.`
|
|
2489
|
-
);
|
|
2490
|
-
return context;
|
|
2491
|
-
} else {
|
|
2492
|
-
currentAsObject[part] = { ...nextPartValue };
|
|
2493
|
-
}
|
|
2494
|
-
current = currentAsObject[part];
|
|
2495
|
-
}
|
|
2496
|
-
const lastPart = parts[parts.length - 1];
|
|
2497
|
-
if (typeof current === "object" && current !== null) {
|
|
2498
|
-
current[lastPart] = value;
|
|
2499
|
-
} else {
|
|
2500
|
-
console.warn(
|
|
2501
|
-
`setValueByPath: Could not set value for path "${path}". Final segment parent is not an object.`
|
|
2502
|
-
);
|
|
2503
|
-
return context;
|
|
2504
|
-
}
|
|
2505
|
-
return result;
|
|
2506
|
-
}
|
|
2507
|
-
function processBinding(binding, context, itemData) {
|
|
2508
|
-
if (typeof binding === "string") {
|
|
2509
|
-
const exactMatchArr = binding.match(/^{{(.*)}}$/);
|
|
2510
|
-
const pathInsideExact = exactMatchArr ? exactMatchArr[1].trim() : null;
|
|
2511
|
-
if (pathInsideExact !== null && !pathInsideExact.includes("{{") && !pathInsideExact.includes("}}")) {
|
|
2512
|
-
const pathToResolve = pathInsideExact;
|
|
2513
|
-
let resolvedValue = void 0;
|
|
2514
|
-
console.log(
|
|
2515
|
-
`[processBinding Debug] Processing EXACT template: "${binding}", Path: "${pathToResolve}", Has itemData: ${!!itemData}`
|
|
2516
|
-
);
|
|
2517
|
-
if (itemData) {
|
|
2518
|
-
try {
|
|
2519
|
-
console.log(
|
|
2520
|
-
`[processBinding Debug] itemData content (EXACT):`,
|
|
2521
|
-
JSON.parse(JSON.stringify(itemData))
|
|
2522
|
-
);
|
|
2523
|
-
} catch {
|
|
2524
|
-
}
|
|
2525
|
-
}
|
|
2526
|
-
if ((pathToResolve.startsWith("item.") || pathToResolve.startsWith("row.")) && itemData) {
|
|
2527
|
-
if (pathToResolve.startsWith("item.")) {
|
|
2528
|
-
resolvedValue = getValueByPath(itemData, pathToResolve.substring(5));
|
|
2529
|
-
} else {
|
|
2530
|
-
resolvedValue = getValueByPath(itemData, pathToResolve.substring(4));
|
|
2531
|
-
}
|
|
2532
|
-
} else if (itemData && pathToResolve in itemData) {
|
|
2533
|
-
resolvedValue = getValueByPath(itemData, pathToResolve);
|
|
2534
|
-
}
|
|
2535
|
-
if (resolvedValue === void 0) {
|
|
2536
|
-
resolvedValue = getValueByPath(context, pathToResolve);
|
|
2537
|
-
}
|
|
2538
|
-
return resolvedValue;
|
|
2539
|
-
} else if (binding.includes("{{") && binding.includes("}}")) {
|
|
2540
|
-
console.log(
|
|
2541
|
-
`[processBinding Debug] Processing EMBEDDED templates: "${binding}", Has itemData: ${!!itemData}`
|
|
2542
|
-
);
|
|
2543
|
-
if (itemData) {
|
|
2544
|
-
try {
|
|
2545
|
-
console.log(
|
|
2546
|
-
`[processBinding Debug] itemData content (EMBEDDED):`,
|
|
2547
|
-
JSON.parse(JSON.stringify(itemData))
|
|
2548
|
-
);
|
|
2549
|
-
} catch {
|
|
2550
|
-
}
|
|
2551
|
-
}
|
|
2552
|
-
const resolvedString = binding.replaceAll(
|
|
2553
|
-
/{{(.*?)}}/g,
|
|
2554
|
-
// Non-greedy match inside braces
|
|
2555
|
-
(match, path) => {
|
|
2556
|
-
const trimmedPath = path.trim();
|
|
2557
|
-
let resolvedValue = void 0;
|
|
2558
|
-
if ((trimmedPath.startsWith("item.") || trimmedPath.startsWith("row.")) && itemData) {
|
|
2559
|
-
if (trimmedPath.startsWith("item.")) {
|
|
2560
|
-
resolvedValue = getValueByPath(
|
|
2561
|
-
itemData,
|
|
2562
|
-
trimmedPath.substring(5)
|
|
2563
|
-
);
|
|
2564
|
-
} else {
|
|
2565
|
-
resolvedValue = getValueByPath(
|
|
2566
|
-
itemData,
|
|
2567
|
-
trimmedPath.substring(4)
|
|
2568
|
-
);
|
|
2569
|
-
}
|
|
2570
|
-
} else if (itemData && trimmedPath in itemData) {
|
|
2571
|
-
resolvedValue = getValueByPath(itemData, trimmedPath);
|
|
2572
|
-
}
|
|
2573
|
-
if (resolvedValue === void 0) {
|
|
2574
|
-
resolvedValue = getValueByPath(context, trimmedPath);
|
|
2575
|
-
}
|
|
2576
|
-
return resolvedValue === null || resolvedValue === void 0 ? "" : String(resolvedValue);
|
|
2577
|
-
}
|
|
2578
|
-
);
|
|
2579
|
-
return resolvedString;
|
|
2580
|
-
} else {
|
|
2581
|
-
const pathToResolve = binding;
|
|
2582
|
-
let resolvedValue = void 0;
|
|
2583
|
-
console.log(
|
|
2584
|
-
`[processBinding Debug] Processing PATH string: "${pathToResolve}", Has itemData: ${!!itemData}`
|
|
2585
|
-
);
|
|
2586
|
-
if (itemData && !pathToResolve.includes(".") && pathToResolve in itemData) {
|
|
2587
|
-
resolvedValue = getValueByPath(itemData, pathToResolve);
|
|
2588
|
-
}
|
|
2589
|
-
if (resolvedValue === void 0) {
|
|
2590
|
-
resolvedValue = getValueByPath(context, pathToResolve);
|
|
2591
|
-
}
|
|
2592
|
-
return resolvedValue;
|
|
2593
|
-
}
|
|
2594
|
-
}
|
|
2595
|
-
if (Array.isArray(binding)) {
|
|
2596
|
-
return binding.map((item) => processBinding(item, context, itemData));
|
|
2597
|
-
}
|
|
2598
|
-
if (binding !== null && typeof binding === "object") {
|
|
2599
|
-
const result = {};
|
|
2600
|
-
for (const [key, value] of Object.entries(binding)) {
|
|
2601
|
-
result[key] = processBinding(value, context, itemData);
|
|
2602
|
-
}
|
|
2603
|
-
return result;
|
|
2604
|
-
}
|
|
2605
|
-
return binding;
|
|
2606
|
-
}
|
|
2607
|
-
async function resolveBindings(node, context, itemData) {
|
|
2608
|
-
const effectiveContext = itemData ? { ...context, item: itemData } : context;
|
|
2609
|
-
const currentTime = Date.now();
|
|
2610
|
-
const cacheKey = createCacheKey(node.id, effectiveContext);
|
|
2611
|
-
const cachedNode = bindingsCache.get(cacheKey);
|
|
2612
|
-
const cachedTimestamp = nodeCacheTimestamps.get(cacheKey);
|
|
2613
|
-
if (cachedNode && cachedTimestamp && currentTime - cachedTimestamp < CACHE_TTL2) {
|
|
2614
|
-
return cachedNode;
|
|
2615
|
-
}
|
|
2616
|
-
if (!itemData) {
|
|
2617
|
-
await systemEvents.emit(
|
|
2618
|
-
createSystemEvent("BINDING_RESOLUTION_START" /* BINDING_RESOLUTION_START */, {
|
|
2619
|
-
layout: node
|
|
2620
|
-
})
|
|
2621
|
-
);
|
|
2622
|
-
}
|
|
2623
|
-
const result = {
|
|
2624
|
-
...node,
|
|
2625
|
-
props: node.props ? JSON.parse(JSON.stringify(node.props)) : null,
|
|
2626
|
-
events: node.events ? JSON.parse(JSON.stringify(node.events)) : null,
|
|
2627
|
-
bindings: node.bindings ? JSON.parse(JSON.stringify(node.bindings)) : null,
|
|
2628
|
-
children: null
|
|
2629
|
-
// Initialize children to null
|
|
2630
|
-
};
|
|
2631
|
-
const resolvedBindings = {};
|
|
2632
|
-
if (node.bindings) {
|
|
2633
|
-
for (const [key, bindingValue] of Object.entries(node.bindings)) {
|
|
2634
|
-
const resolvedValue = processBinding(bindingValue, context, itemData);
|
|
2635
|
-
resolvedBindings[key] = resolvedValue;
|
|
2636
|
-
if (resolvedValue !== void 0) {
|
|
2637
|
-
if (!result.props)
|
|
2638
|
-
result.props = {};
|
|
2639
|
-
result.props[key] = resolvedValue;
|
|
2640
|
-
}
|
|
2641
|
-
}
|
|
2642
|
-
}
|
|
2643
|
-
result.bindings = null;
|
|
2644
|
-
if (node.events) {
|
|
2645
|
-
result.events = processBinding(
|
|
2646
|
-
node.events,
|
|
2647
|
-
context,
|
|
2648
|
-
itemData
|
|
2649
|
-
);
|
|
2650
|
-
} else {
|
|
2651
|
-
result.events = null;
|
|
2881
|
+
var clearRenderedNodeCacheEntry = (cacheKey) => {
|
|
2882
|
+
renderedNodesCache.delete(cacheKey);
|
|
2883
|
+
console.log(`[Renderer Cache] Cleared entry FOR REAL for key: ${cacheKey}`);
|
|
2884
|
+
};
|
|
2885
|
+
var getRendererCacheKey = (node) => {
|
|
2886
|
+
if (node.id === "task-detail") {
|
|
2887
|
+
const dataId = node.props?.data?.id;
|
|
2888
|
+
return `${node.id}:${node.props?.visible}:${dataId || "no-data-selected"}`;
|
|
2652
2889
|
}
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
if (typeof currentItemData !== "object" || currentItemData === null) {
|
|
2660
|
-
console.warn(
|
|
2661
|
-
`List item at index ${index} for node ${node.id} is not an object:`,
|
|
2662
|
-
currentItemData
|
|
2663
|
-
);
|
|
2664
|
-
return null;
|
|
2665
|
-
}
|
|
2666
|
-
const currentItemAsRecord = currentItemData;
|
|
2667
|
-
const itemId = currentItemAsRecord.id;
|
|
2668
|
-
const instanceId = `${templateChild.id}-${itemId || index}`;
|
|
2669
|
-
const childNodeInstance = JSON.parse(
|
|
2670
|
-
JSON.stringify(templateChild)
|
|
2671
|
-
);
|
|
2672
|
-
childNodeInstance.id = instanceId;
|
|
2673
|
-
const resolvedChild = await resolveBindings(
|
|
2674
|
-
childNodeInstance,
|
|
2675
|
-
context,
|
|
2676
|
-
currentItemAsRecord
|
|
2677
|
-
);
|
|
2678
|
-
if (!resolvedChild.props)
|
|
2679
|
-
resolvedChild.props = {};
|
|
2680
|
-
resolvedChild.props.key = itemId || `${node.id}-item-${index}`;
|
|
2681
|
-
return resolvedChild;
|
|
2682
|
-
} catch (error) {
|
|
2683
|
-
console.error(
|
|
2684
|
-
`[resolveBindings Error] Error processing item at index ${index} for node ${node.id}:`,
|
|
2685
|
-
error,
|
|
2686
|
-
"Item Data:",
|
|
2687
|
-
currentItemData
|
|
2688
|
-
);
|
|
2689
|
-
return null;
|
|
2690
|
-
}
|
|
2691
|
-
})
|
|
2692
|
-
);
|
|
2693
|
-
result.children = mappedChildren.filter(
|
|
2694
|
-
(child) => child !== null
|
|
2695
|
-
);
|
|
2696
|
-
} else if (node.children && node.children.length > 0) {
|
|
2697
|
-
result.children = await Promise.all(
|
|
2698
|
-
node.children.map((child) => resolveBindings(child, context, itemData))
|
|
2890
|
+
let propsString = "null";
|
|
2891
|
+
try {
|
|
2892
|
+
propsString = JSON.stringify(node.props);
|
|
2893
|
+
} catch (_e) {
|
|
2894
|
+
console.warn(
|
|
2895
|
+
`[Renderer Cache Key] Error stringifying node.props for ID ${node.id}, using 'null' for props part of key.`
|
|
2699
2896
|
);
|
|
2700
|
-
} else {
|
|
2701
|
-
result.children = [];
|
|
2702
2897
|
}
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
}
|
|
2898
|
+
let bindingsString = "null";
|
|
2899
|
+
try {
|
|
2900
|
+
bindingsString = JSON.stringify(node.bindings);
|
|
2901
|
+
} catch (_e) {
|
|
2902
|
+
console.warn(
|
|
2903
|
+
`[Renderer Cache Key] Error stringifying node.bindings for ID ${node.id}, using 'null' for bindings part of key.`
|
|
2709
2904
|
);
|
|
2710
2905
|
}
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
oldestKey = key;
|
|
2720
|
-
}
|
|
2721
|
-
}
|
|
2722
|
-
if (oldestKey) {
|
|
2723
|
-
bindingsCache.delete(oldestKey);
|
|
2724
|
-
nodeCacheTimestamps.delete(oldestKey);
|
|
2725
|
-
}
|
|
2906
|
+
return `${node.id}:${propsString}:${bindingsString}`;
|
|
2907
|
+
};
|
|
2908
|
+
async function renderNode2(node, adapter = "shadcn", processEvent) {
|
|
2909
|
+
const startTime = Date.now();
|
|
2910
|
+
const cacheKey = getRendererCacheKey(node);
|
|
2911
|
+
const cachedItem = renderedNodesCache.get(cacheKey);
|
|
2912
|
+
if (cachedItem && startTime - cachedItem.timestamp < CACHE_TTL) {
|
|
2913
|
+
return cachedItem.element;
|
|
2726
2914
|
}
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
newContext = setValueByPath(newContext, "selected", payload.item);
|
|
2915
|
+
if (node.id === "task-detail") {
|
|
2916
|
+
let safeNodeString = "Error stringifying node for log";
|
|
2917
|
+
let propsToLog = "{}";
|
|
2918
|
+
try {
|
|
2919
|
+
const clonedProps = node.props ? { ...node.props } : {};
|
|
2920
|
+
if (clonedProps && clonedProps.data) {
|
|
2921
|
+
clonedProps.data = `Data present (type: ${typeof clonedProps.data}, logging suppressed)`;
|
|
2735
2922
|
}
|
|
2736
|
-
|
|
2923
|
+
if (clonedProps && Array.isArray(clonedProps.fields)) {
|
|
2924
|
+
clonedProps.fields = `Fields array (length: ${clonedProps.fields.length}, logging suppressed)`;
|
|
2925
|
+
} else if (clonedProps && clonedProps.fields) {
|
|
2926
|
+
clonedProps.fields = `Fields present (type: ${typeof clonedProps.fields}, logging suppressed)`;
|
|
2927
|
+
}
|
|
2928
|
+
propsToLog = JSON.stringify(clonedProps);
|
|
2929
|
+
const nodeToLog = {
|
|
2930
|
+
id: node.id,
|
|
2931
|
+
node_type: node.node_type,
|
|
2932
|
+
props: "See props above",
|
|
2933
|
+
bindings: node.bindings,
|
|
2934
|
+
events: node.events,
|
|
2935
|
+
children: node.children ? `Children array (length: ${node.children.length}, logging suppressed)` : null
|
|
2936
|
+
};
|
|
2937
|
+
safeNodeString = JSON.stringify(nodeToLog);
|
|
2938
|
+
} catch (e) {
|
|
2939
|
+
if (e instanceof Error) {
|
|
2940
|
+
safeNodeString = `Error stringifying node for log: ${e.message}`;
|
|
2941
|
+
} else {
|
|
2942
|
+
safeNodeString = `Error stringifying node for log: Unknown error`;
|
|
2943
|
+
}
|
|
2944
|
+
if (node.props === void 0)
|
|
2945
|
+
propsToLog = "undefined";
|
|
2946
|
+
else if (node.props === null)
|
|
2947
|
+
propsToLog = "null";
|
|
2737
2948
|
}
|
|
2738
|
-
|
|
2739
|
-
|
|
2949
|
+
console.log(
|
|
2950
|
+
`[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(
|
|
2951
|
+
node.bindings
|
|
2952
|
+
)}, Node (safe): ${safeNodeString}`
|
|
2953
|
+
);
|
|
2954
|
+
}
|
|
2955
|
+
await systemEvents.emit(
|
|
2956
|
+
createSystemEvent("RENDER_START" /* RENDER_START */, { layout: node })
|
|
2957
|
+
);
|
|
2958
|
+
let result;
|
|
2959
|
+
switch (adapter) {
|
|
2960
|
+
case "shadcn":
|
|
2961
|
+
result = renderNode(node, processEvent);
|
|
2740
2962
|
break;
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2963
|
+
default:
|
|
2964
|
+
console.warn(`Unsupported adapter: ${adapter}, falling back to shadcn`);
|
|
2965
|
+
result = renderNode(node, processEvent);
|
|
2966
|
+
}
|
|
2967
|
+
if (node.id === "task-detail") {
|
|
2968
|
+
let elementType = void 0;
|
|
2969
|
+
if (result && typeof result.type === "function") {
|
|
2970
|
+
elementType = result.type.name;
|
|
2971
|
+
} else if (result && typeof result.type === "string") {
|
|
2972
|
+
elementType = result.type;
|
|
2973
|
+
} else if (result && typeof result.type === "object" && result.type !== null) {
|
|
2974
|
+
const componentType2 = result.type;
|
|
2975
|
+
if (typeof componentType2.displayName === "string") {
|
|
2976
|
+
elementType = componentType2.displayName;
|
|
2746
2977
|
}
|
|
2747
|
-
break;
|
|
2748
2978
|
}
|
|
2979
|
+
console.log(
|
|
2980
|
+
`[Renderer renderNode] Adapter for task-detail returned element. Element type:`,
|
|
2981
|
+
elementType
|
|
2982
|
+
);
|
|
2983
|
+
}
|
|
2984
|
+
await systemEvents.emit(
|
|
2985
|
+
createSystemEvent("RENDER_COMPLETE" /* RENDER_COMPLETE */, {
|
|
2986
|
+
layout: node,
|
|
2987
|
+
renderTimeMs: Date.now() - startTime
|
|
2988
|
+
})
|
|
2989
|
+
);
|
|
2990
|
+
renderedNodesCache.set(cacheKey, {
|
|
2991
|
+
element: result,
|
|
2992
|
+
timestamp: startTime
|
|
2993
|
+
});
|
|
2994
|
+
if (renderedNodesCache.size > MAX_CACHE_SIZE) {
|
|
2995
|
+
const oldestEntry = renderedNodesCache.entries().next().value;
|
|
2996
|
+
if (oldestEntry) {
|
|
2997
|
+
renderedNodesCache.delete(oldestEntry[0]);
|
|
2998
|
+
}
|
|
2999
|
+
}
|
|
3000
|
+
return result;
|
|
3001
|
+
}
|
|
3002
|
+
function renderShimmer(node, adapter = "shadcn") {
|
|
3003
|
+
if (!node) {
|
|
3004
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ShimmerBlock, {});
|
|
3005
|
+
}
|
|
3006
|
+
switch (node.node_type) {
|
|
3007
|
+
case "ListView":
|
|
3008
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ShimmerTable, { rows: 3 });
|
|
3009
|
+
case "Detail":
|
|
3010
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ShimmerCard, {});
|
|
3011
|
+
case "Container":
|
|
3012
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-4", children: node.children?.map((child, index) => /* @__PURE__ */ jsxRuntime.jsx("div", { children: renderShimmer(child, adapter) }, index)) });
|
|
2749
3013
|
default:
|
|
2750
|
-
|
|
3014
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ShimmerBlock, {});
|
|
2751
3015
|
}
|
|
2752
|
-
return newContext;
|
|
2753
3016
|
}
|
|
2754
3017
|
|
|
2755
3018
|
// src/core/events.ts
|
|
@@ -2886,18 +3149,26 @@ var AutoUI = ({
|
|
|
2886
3149
|
eventHooks,
|
|
2887
3150
|
systemEventHooks,
|
|
2888
3151
|
debugMode = false,
|
|
2889
|
-
mockMode = false,
|
|
2890
3152
|
planningConfig,
|
|
2891
3153
|
integration = {},
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
openaiApiKey
|
|
3154
|
+
enablePartialUpdates = true,
|
|
3155
|
+
apiKey
|
|
2895
3156
|
}) => {
|
|
3157
|
+
const stableGoal = React__default.default.useMemo(() => goal, [goal]);
|
|
3158
|
+
const schemaStringified = React__default.default.useMemo(() => JSON.stringify(schema), [schema]);
|
|
3159
|
+
const stableSchemaProp = React__default.default.useMemo(() => schema, [schemaStringified]);
|
|
2896
3160
|
const [schemaAdapterInstance] = React.useState(null);
|
|
2897
3161
|
const [dataContext, setDataContext] = React.useState({});
|
|
2898
3162
|
const [componentsAvailable, setComponentsAvailable] = React.useState(true);
|
|
2899
|
-
const
|
|
2900
|
-
const
|
|
3163
|
+
const [uiStatus, setUiStatus] = React.useState("initializing");
|
|
3164
|
+
const [currentResolvedLayoutForRender, setCurrentResolvedLayoutForRender] = React.useState(null);
|
|
3165
|
+
const [isResolvingBindings, setIsResolvingBindings] = React.useState(false);
|
|
3166
|
+
const [renderedNode, setRenderedNode] = React.useState(
|
|
3167
|
+
null
|
|
3168
|
+
);
|
|
3169
|
+
const currentResolvedLayoutRef = React.useRef(null);
|
|
3170
|
+
const effectiveSchema = stableSchemaProp;
|
|
3171
|
+
const scopedGoal = stableGoal;
|
|
2901
3172
|
React.useEffect(() => {
|
|
2902
3173
|
if (componentAdapter === "shadcn") {
|
|
2903
3174
|
setComponentsAvailable(areShadcnComponentsAvailable());
|
|
@@ -2942,7 +3213,6 @@ var AutoUI = ({
|
|
|
2942
3213
|
Object.entries(effectiveSchema).forEach(([key, tableSchema]) => {
|
|
2943
3214
|
initialData[key] = {
|
|
2944
3215
|
schema: tableSchema,
|
|
2945
|
-
// For development, add sample data if available
|
|
2946
3216
|
data: tableSchema?.sampleData || [],
|
|
2947
3217
|
selected: null
|
|
2948
3218
|
};
|
|
@@ -2959,12 +3229,11 @@ var AutoUI = ({
|
|
|
2959
3229
|
schema: effectiveSchema,
|
|
2960
3230
|
goal: scopedGoal,
|
|
2961
3231
|
userContext,
|
|
2962
|
-
mockMode,
|
|
2963
3232
|
planningConfig,
|
|
2964
|
-
router: void 0,
|
|
2965
3233
|
dataContext,
|
|
3234
|
+
// Pass the local dataContext here, engine will use it if its own is empty initially
|
|
2966
3235
|
enablePartialUpdates,
|
|
2967
|
-
|
|
3236
|
+
apiKey
|
|
2968
3237
|
});
|
|
2969
3238
|
const eventManagerRef = React.useRef(new EventManager());
|
|
2970
3239
|
React.useEffect(() => {
|
|
@@ -3003,161 +3272,344 @@ var AutoUI = ({
|
|
|
3003
3272
|
unregisters.forEach((unregister) => unregister());
|
|
3004
3273
|
};
|
|
3005
3274
|
}, [eventHooks]);
|
|
3006
|
-
|
|
3275
|
+
React.useEffect(() => {
|
|
3276
|
+
currentResolvedLayoutRef.current = currentResolvedLayoutForRender;
|
|
3277
|
+
}, [currentResolvedLayoutForRender]);
|
|
3278
|
+
const processEvent = React.useCallback(
|
|
3007
3279
|
async (event) => {
|
|
3280
|
+
setUiStatus("event_processing");
|
|
3281
|
+
const layoutAtEventTime = currentResolvedLayoutRef.current;
|
|
3008
3282
|
const shouldProceed = await eventManagerRef.current.processEvent(event);
|
|
3009
|
-
if (onEvent)
|
|
3283
|
+
if (onEvent)
|
|
3010
3284
|
onEvent(event);
|
|
3011
|
-
}
|
|
3012
3285
|
if (!shouldProceed) {
|
|
3013
|
-
console.info(
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
return void 0;
|
|
3019
|
-
if (node.id === id)
|
|
3020
|
-
return node;
|
|
3021
|
-
if (node.children) {
|
|
3022
|
-
for (const child of node.children) {
|
|
3023
|
-
const found = findNodeById2(child, id);
|
|
3024
|
-
if (found)
|
|
3025
|
-
return found;
|
|
3026
|
-
}
|
|
3027
|
-
}
|
|
3028
|
-
return void 0;
|
|
3029
|
-
};
|
|
3030
|
-
const sourceNode = findNodeById2(state.layout, event.nodeId);
|
|
3031
|
-
if (!sourceNode) {
|
|
3032
|
-
console.warn(`Node not found for event: ${event.nodeId}`);
|
|
3033
|
-
handleEvent(event);
|
|
3286
|
+
console.info(
|
|
3287
|
+
"[AutoUI.processEvent] Event processing stopped by local hooks",
|
|
3288
|
+
event
|
|
3289
|
+
);
|
|
3290
|
+
setUiStatus("idle");
|
|
3034
3291
|
return;
|
|
3035
3292
|
}
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
`No event config found for ${event.type} on node ${event.nodeId}`
|
|
3293
|
+
if (event.type === "CLICK" && layoutAtEventTime && event.nodeId.includes("view-details-button")) {
|
|
3294
|
+
const mainContent = layoutAtEventTime.children?.find(
|
|
3295
|
+
(c) => c.id === "main-content"
|
|
3040
3296
|
);
|
|
3041
|
-
|
|
3042
|
-
|
|
3297
|
+
const taskList = mainContent?.children?.find((c) => c.id === "tasks-container")?.children?.find((c) => c.id === "task-list");
|
|
3298
|
+
const eventSourceListItemCard = taskList?.children?.find(
|
|
3299
|
+
(item) => item.children?.some((btn) => btn.id === event.nodeId)
|
|
3300
|
+
);
|
|
3301
|
+
const buttonNode = eventSourceListItemCard?.children?.find(
|
|
3302
|
+
(btn) => btn.id === event.nodeId
|
|
3303
|
+
);
|
|
3304
|
+
if (buttonNode?.events?.CLICK?.action === "SHOW_DETAIL" /* SHOW_DETAIL */ && buttonNode?.events?.CLICK?.target === "task-detail") {
|
|
3305
|
+
const cacheKeyToClear = `task-detail:false:no-data-selected`;
|
|
3306
|
+
clearRenderedNodeCacheEntry(cacheKeyToClear);
|
|
3307
|
+
console.log(
|
|
3308
|
+
`[AutoUI.processEvent] Attempted to clear cache for task-detail using simplified key: ${cacheKeyToClear}`
|
|
3309
|
+
);
|
|
3310
|
+
}
|
|
3043
3311
|
}
|
|
3044
|
-
const newContext = executeAction(
|
|
3045
|
-
eventConfig.action,
|
|
3046
|
-
eventConfig.target || "",
|
|
3047
|
-
// Provide empty string as fallback if target is null
|
|
3048
|
-
{
|
|
3049
|
-
...eventConfig.payload,
|
|
3050
|
-
...event.payload
|
|
3051
|
-
},
|
|
3052
|
-
dataContext,
|
|
3053
|
-
state.layout || void 0
|
|
3054
|
-
);
|
|
3055
|
-
setDataContext(newContext);
|
|
3056
|
-
handleEvent(event);
|
|
3057
|
-
},
|
|
3058
|
-
[dataContext, handleEvent, onEvent, state.layout]
|
|
3059
|
-
);
|
|
3060
|
-
const [resolvedLayout, setResolvedLayout] = React.useState(
|
|
3061
|
-
void 0
|
|
3062
|
-
);
|
|
3063
|
-
const [renderedNode, setRenderedNode] = React.useState(
|
|
3064
|
-
null
|
|
3065
|
-
);
|
|
3066
|
-
const resolveLayoutBindings = React.useCallback(async () => {
|
|
3067
|
-
if (state.layout && dataContext) {
|
|
3068
|
-
console.log(
|
|
3069
|
-
"[AutoUI Debug] DataContext before resolving bindings:",
|
|
3070
|
-
JSON.stringify(dataContext, null, 2)
|
|
3071
|
-
);
|
|
3072
3312
|
console.log(
|
|
3073
|
-
"[AutoUI
|
|
3074
|
-
JSON.stringify(
|
|
3075
|
-
);
|
|
3076
|
-
const correctedLayout = correctListBindingsRecursive(
|
|
3077
|
-
state.layout,
|
|
3078
|
-
dataContext
|
|
3313
|
+
"[AutoUI.processEvent] Forwarding event to engine:",
|
|
3314
|
+
JSON.stringify(event, null, 2)
|
|
3079
3315
|
);
|
|
3080
3316
|
console.log(
|
|
3081
|
-
"[AutoUI
|
|
3082
|
-
|
|
3317
|
+
"[AutoUI.processEvent] Passing currentLayout ID (at event time):",
|
|
3318
|
+
layoutAtEventTime?.id
|
|
3083
3319
|
);
|
|
3084
|
-
const resolved = await resolveBindings(correctedLayout, dataContext);
|
|
3085
|
-
setResolvedLayout(resolved);
|
|
3086
3320
|
console.log(
|
|
3087
|
-
"[AutoUI
|
|
3088
|
-
|
|
3321
|
+
"[AutoUI.processEvent] Passing dataContext keys to engine:",
|
|
3322
|
+
Object.keys(dataContext)
|
|
3089
3323
|
);
|
|
3090
|
-
|
|
3091
|
-
|
|
3324
|
+
handleEvent(event, layoutAtEventTime, dataContext);
|
|
3325
|
+
},
|
|
3326
|
+
[
|
|
3327
|
+
dataContext,
|
|
3328
|
+
handleEvent,
|
|
3329
|
+
onEvent,
|
|
3330
|
+
eventManagerRef
|
|
3331
|
+
// currentResolvedLayoutForRender removed - using ref instead to prevent infinite loops
|
|
3332
|
+
// setUiStatus is implicitly available
|
|
3333
|
+
]
|
|
3334
|
+
);
|
|
3335
|
+
React.useEffect(() => {
|
|
3336
|
+
if (state.dataContext && Object.keys(state.dataContext).length > 0) {
|
|
3337
|
+
if (JSON.stringify(dataContext) !== JSON.stringify(state.dataContext)) {
|
|
3338
|
+
console.log(
|
|
3339
|
+
"[AutoUI] Syncing local dataContext from engine state. New keys:",
|
|
3340
|
+
Object.keys(state.dataContext),
|
|
3341
|
+
"Old keys:",
|
|
3342
|
+
Object.keys(dataContext)
|
|
3343
|
+
);
|
|
3344
|
+
setDataContext(state.dataContext);
|
|
3345
|
+
}
|
|
3092
3346
|
}
|
|
3347
|
+
}, [state.dataContext, dataContext]);
|
|
3348
|
+
React.useEffect(() => {
|
|
3349
|
+
const resolveAndSetLayout = async () => {
|
|
3350
|
+
const hasMeaningfulDataContext = dataContext && Object.keys(dataContext).some(
|
|
3351
|
+
(key) => key !== "user" || Object.keys(dataContext["user"]).length > 0
|
|
3352
|
+
);
|
|
3353
|
+
if (state.layout && hasMeaningfulDataContext) {
|
|
3354
|
+
console.log(
|
|
3355
|
+
`[AutoUI resolveAndSetLayout] Calling core resolveBindings for layout ID: ${state.layout.id}. DataContext keys: ${Object.keys(dataContext).join(", ")}`
|
|
3356
|
+
);
|
|
3357
|
+
setIsResolvingBindings(true);
|
|
3358
|
+
setUiStatus("resolving_bindings");
|
|
3359
|
+
try {
|
|
3360
|
+
const correctedLayout = correctListBindingsRecursive(
|
|
3361
|
+
state.layout,
|
|
3362
|
+
dataContext
|
|
3363
|
+
);
|
|
3364
|
+
systemEvents.emit(
|
|
3365
|
+
createSystemEvent("BINDING_RESOLUTION_START" /* BINDING_RESOLUTION_START */, {
|
|
3366
|
+
layout: correctedLayout
|
|
3367
|
+
// Log corrected layout before resolving
|
|
3368
|
+
})
|
|
3369
|
+
);
|
|
3370
|
+
const resolved = await resolveBindings(correctedLayout, dataContext);
|
|
3371
|
+
setCurrentResolvedLayoutForRender(resolved);
|
|
3372
|
+
systemEvents.emit(
|
|
3373
|
+
createSystemEvent("BINDING_RESOLUTION_COMPLETE" /* BINDING_RESOLUTION_COMPLETE */, {
|
|
3374
|
+
originalLayout: correctedLayout,
|
|
3375
|
+
// Log corrected layout
|
|
3376
|
+
resolvedLayout: resolved
|
|
3377
|
+
})
|
|
3378
|
+
);
|
|
3379
|
+
} catch (err) {
|
|
3380
|
+
console.error("Error resolving bindings:", err);
|
|
3381
|
+
setUiStatus("error");
|
|
3382
|
+
setCurrentResolvedLayoutForRender(null);
|
|
3383
|
+
} finally {
|
|
3384
|
+
setIsResolvingBindings(false);
|
|
3385
|
+
}
|
|
3386
|
+
} else {
|
|
3387
|
+
if (!state.layout)
|
|
3388
|
+
console.log(
|
|
3389
|
+
"[AutoUI] Skipping resolveBindings: state.layout is null/undefined"
|
|
3390
|
+
);
|
|
3391
|
+
if (!hasMeaningfulDataContext)
|
|
3392
|
+
console.log(
|
|
3393
|
+
"[AutoUI] Skipping resolveBindings: dataContext is not meaningfully populated yet"
|
|
3394
|
+
);
|
|
3395
|
+
setCurrentResolvedLayoutForRender(null);
|
|
3396
|
+
if (!state.loading && uiStatus !== "initializing" && !isResolvingBindings)
|
|
3397
|
+
setUiStatus("idle");
|
|
3398
|
+
}
|
|
3399
|
+
};
|
|
3400
|
+
resolveAndSetLayout();
|
|
3093
3401
|
}, [state.layout, dataContext]);
|
|
3094
3402
|
React.useEffect(() => {
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
resolvedLayout,
|
|
3102
|
-
componentAdapter,
|
|
3103
|
-
processEvent2
|
|
3403
|
+
const renderLayout = async () => {
|
|
3404
|
+
if (currentResolvedLayoutForRender && !isResolvingBindings) {
|
|
3405
|
+
setUiStatus("rendering");
|
|
3406
|
+
console.log(
|
|
3407
|
+
"[AutoUI Debug] Rendering with currentResolvedLayoutForRender:",
|
|
3408
|
+
JSON.stringify(currentResolvedLayoutForRender, null, 2)
|
|
3104
3409
|
);
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3410
|
+
try {
|
|
3411
|
+
systemEvents.emit(
|
|
3412
|
+
createSystemEvent("RENDER_START" /* RENDER_START */, {
|
|
3413
|
+
layout: currentResolvedLayoutForRender
|
|
3414
|
+
})
|
|
3415
|
+
);
|
|
3416
|
+
const rendered = await renderNode2(
|
|
3417
|
+
currentResolvedLayoutForRender,
|
|
3418
|
+
componentAdapter,
|
|
3419
|
+
processEvent
|
|
3420
|
+
);
|
|
3421
|
+
setRenderedNode(rendered);
|
|
3422
|
+
systemEvents.emit(
|
|
3423
|
+
createSystemEvent("RENDER_COMPLETE" /* RENDER_COMPLETE */, {
|
|
3424
|
+
layout: currentResolvedLayoutForRender,
|
|
3425
|
+
renderTimeMs: 0
|
|
3426
|
+
// Placeholder, actual timing would be more complex
|
|
3427
|
+
})
|
|
3428
|
+
);
|
|
3429
|
+
setUiStatus("idle");
|
|
3430
|
+
} catch (err) {
|
|
3431
|
+
console.error("Error rendering node:", err);
|
|
3432
|
+
setUiStatus("error");
|
|
3433
|
+
}
|
|
3434
|
+
} else if (!currentResolvedLayoutForRender && !state.loading && !isResolvingBindings && uiStatus !== "initializing") {
|
|
3435
|
+
setRenderedNode(null);
|
|
3436
|
+
setUiStatus("idle");
|
|
3437
|
+
}
|
|
3438
|
+
};
|
|
3439
|
+
renderLayout();
|
|
3440
|
+
}, [
|
|
3441
|
+
currentResolvedLayoutForRender,
|
|
3442
|
+
componentAdapter,
|
|
3443
|
+
processEvent,
|
|
3444
|
+
isResolvingBindings,
|
|
3445
|
+
state.loading
|
|
3446
|
+
]);
|
|
3447
|
+
React.useEffect(() => {
|
|
3448
|
+
if (uiStatus !== "error") {
|
|
3449
|
+
setUiStatus("initializing");
|
|
3450
|
+
}
|
|
3451
|
+
}, []);
|
|
3452
|
+
React.useEffect(() => {
|
|
3453
|
+
if (uiStatus === "initializing") {
|
|
3454
|
+
if (state.loading) ; else if (state.layout && !isResolvingBindings) ; else if (!state.layout && !state.loading && !isResolvingBindings) {
|
|
3455
|
+
if (state.error) {
|
|
3456
|
+
setUiStatus("error");
|
|
3457
|
+
} else {
|
|
3458
|
+
setUiStatus("idle");
|
|
3459
|
+
}
|
|
3108
3460
|
}
|
|
3109
|
-
} else {
|
|
3110
|
-
setRenderedNode(null);
|
|
3111
3461
|
}
|
|
3112
|
-
}, [
|
|
3462
|
+
}, [state.loading, state.layout, state.error, uiStatus, isResolvingBindings]);
|
|
3113
3463
|
React.useEffect(() => {
|
|
3114
|
-
|
|
3115
|
-
|
|
3464
|
+
if (!debugMode)
|
|
3465
|
+
return;
|
|
3466
|
+
const pipelineState = {
|
|
3467
|
+
uiStatus,
|
|
3468
|
+
hasLayout: !!state.layout,
|
|
3469
|
+
layoutId: state.layout?.id || null,
|
|
3470
|
+
hasResolvedLayout: !!currentResolvedLayoutForRender,
|
|
3471
|
+
resolvedLayoutId: currentResolvedLayoutForRender?.id || null,
|
|
3472
|
+
hasRenderedNode: !!renderedNode,
|
|
3473
|
+
isResolvingBindings,
|
|
3474
|
+
isLoading: state.loading,
|
|
3475
|
+
hasError: !!state.error,
|
|
3476
|
+
error: state.error || null,
|
|
3477
|
+
dataContextKeys: Object.keys(dataContext),
|
|
3478
|
+
timestamp: Date.now()
|
|
3479
|
+
};
|
|
3480
|
+
if (typeof window !== "undefined") {
|
|
3481
|
+
window.__autoui_pipeline_state = pipelineState;
|
|
3482
|
+
window.__autoui_pipeline_history = window.__autoui_pipeline_history || [];
|
|
3483
|
+
window.__autoui_pipeline_history.push(pipelineState);
|
|
3484
|
+
console.log("[PIPELINE_STATE]", JSON.stringify(pipelineState));
|
|
3485
|
+
}
|
|
3486
|
+
}, [
|
|
3487
|
+
debugMode,
|
|
3488
|
+
uiStatus,
|
|
3489
|
+
state.layout,
|
|
3490
|
+
state.loading,
|
|
3491
|
+
state.error,
|
|
3492
|
+
currentResolvedLayoutForRender,
|
|
3493
|
+
renderedNode,
|
|
3494
|
+
isResolvingBindings,
|
|
3495
|
+
dataContext
|
|
3496
|
+
]);
|
|
3116
3497
|
if (!componentsAvailable) {
|
|
3117
3498
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "autoui-error p-4 border border-red-300 bg-red-50 text-red-700 rounded", children: [
|
|
3118
3499
|
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "font-medium", children: "Component Library Not Found" }),
|
|
3119
3500
|
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm whitespace-pre-line", children: getMissingComponentsMessage() })
|
|
3120
3501
|
] });
|
|
3121
3502
|
}
|
|
3503
|
+
const showShimmer = (uiStatus === "initializing" || state.loading || isResolvingBindings) && !state.error;
|
|
3122
3504
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3123
3505
|
"div",
|
|
3124
3506
|
{
|
|
3125
3507
|
className: `autoui-root ${integration.className || ""}`,
|
|
3126
3508
|
id: integration.id,
|
|
3127
|
-
"data-mode": integration.mode,
|
|
3128
|
-
"data-scope": scope?.type || "full",
|
|
3129
3509
|
children: [
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
/* @__PURE__ */ jsxRuntime.jsxs("ul", { className: "list-disc pl-5 mt-2", children: [
|
|
3146
|
-
/* @__PURE__ */ jsxRuntime.jsx("li", { children: "Your OpenAI API key is missing or invalid" }),
|
|
3147
|
-
/* @__PURE__ */ jsxRuntime.jsx("li", { children: "The OpenAI service is experiencing issues" }),
|
|
3148
|
-
/* @__PURE__ */ jsxRuntime.jsx("li", { children: "Your API rate limit has been exceeded" })
|
|
3149
|
-
] }),
|
|
3150
|
-
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: "mt-2", children: [
|
|
3151
|
-
"Try setting ",
|
|
3152
|
-
/* @__PURE__ */ jsxRuntime.jsx("code", { children: "mockMode=true" }),
|
|
3153
|
-
" to use sample data instead."
|
|
3154
|
-
] })
|
|
3155
|
-
] })
|
|
3510
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
3511
|
+
"Current Status: ",
|
|
3512
|
+
uiStatus
|
|
3513
|
+
] }),
|
|
3514
|
+
uiStatus === "idle" && !isResolvingBindings && renderedNode && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "autoui-content", children: renderedNode }),
|
|
3515
|
+
state.error && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "autoui-error", children: [
|
|
3516
|
+
"Error: ",
|
|
3517
|
+
state.error
|
|
3518
|
+
] }),
|
|
3519
|
+
showShimmer && state.layout && // Show shimmer if we have a layout to base it on
|
|
3520
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "autoui-loading", children: renderShimmer(state.layout, componentAdapter) }),
|
|
3521
|
+
showShimmer && !state.layout && // Fallback shimmer if no layout yet
|
|
3522
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "autoui-loading p-4", children: [
|
|
3523
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full h-20 bg-gray-200 animate-pulse rounded mb-4" }),
|
|
3524
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full h-40 bg-gray-200 animate-pulse rounded" })
|
|
3156
3525
|
] })
|
|
3157
3526
|
]
|
|
3158
3527
|
}
|
|
3159
3528
|
);
|
|
3160
3529
|
};
|
|
3530
|
+
var uiEventType = zod.z.enum([
|
|
3531
|
+
"INIT",
|
|
3532
|
+
"CLICK",
|
|
3533
|
+
"CHANGE",
|
|
3534
|
+
"SUBMIT",
|
|
3535
|
+
"MOUSEOVER",
|
|
3536
|
+
"MOUSEOUT",
|
|
3537
|
+
"FOCUS",
|
|
3538
|
+
"BLUR"
|
|
3539
|
+
]);
|
|
3540
|
+
var uiEvent = zod.z.object({
|
|
3541
|
+
type: uiEventType,
|
|
3542
|
+
nodeId: zod.z.string(),
|
|
3543
|
+
timestamp: zod.z.number().nullable(),
|
|
3544
|
+
payload: zod.z.record(zod.z.unknown()).nullable()
|
|
3545
|
+
});
|
|
3546
|
+
zod.z.enum(["AI_RESPONSE", "ERROR"]);
|
|
3547
|
+
var runtimeRecord = zod.z.record(zod.z.unknown()).nullable();
|
|
3548
|
+
var uiSpecNode = zod.z.object({
|
|
3549
|
+
id: zod.z.string(),
|
|
3550
|
+
node_type: zod.z.string(),
|
|
3551
|
+
props: runtimeRecord,
|
|
3552
|
+
bindings: runtimeRecord,
|
|
3553
|
+
events: zod.z.record(
|
|
3554
|
+
zod.z.string(),
|
|
3555
|
+
zod.z.object({
|
|
3556
|
+
action: zod.z.string(),
|
|
3557
|
+
target: zod.z.string(),
|
|
3558
|
+
payload: runtimeRecord
|
|
3559
|
+
})
|
|
3560
|
+
).nullable(),
|
|
3561
|
+
children: zod.z.lazy(() => zod.z.array(uiSpecNode)).nullable()
|
|
3562
|
+
});
|
|
3563
|
+
zod.z.discriminatedUnion("type", [
|
|
3564
|
+
zod.z.object({
|
|
3565
|
+
type: zod.z.literal("UI_EVENT"),
|
|
3566
|
+
event: uiEvent
|
|
3567
|
+
}),
|
|
3568
|
+
zod.z.object({
|
|
3569
|
+
type: zod.z.literal("AI_RESPONSE"),
|
|
3570
|
+
node: uiSpecNode
|
|
3571
|
+
}),
|
|
3572
|
+
zod.z.object({
|
|
3573
|
+
type: zod.z.literal("PARTIAL_UPDATE"),
|
|
3574
|
+
nodeId: zod.z.string(),
|
|
3575
|
+
node: uiSpecNode
|
|
3576
|
+
}),
|
|
3577
|
+
zod.z.object({
|
|
3578
|
+
type: zod.z.literal("ADD_NODE"),
|
|
3579
|
+
parentId: zod.z.string(),
|
|
3580
|
+
node: uiSpecNode,
|
|
3581
|
+
index: zod.z.number().nullable()
|
|
3582
|
+
}),
|
|
3583
|
+
zod.z.object({
|
|
3584
|
+
type: zod.z.literal("REMOVE_NODE"),
|
|
3585
|
+
nodeId: zod.z.string()
|
|
3586
|
+
}),
|
|
3587
|
+
zod.z.object({
|
|
3588
|
+
type: zod.z.literal("ERROR"),
|
|
3589
|
+
message: zod.z.string()
|
|
3590
|
+
}),
|
|
3591
|
+
zod.z.object({
|
|
3592
|
+
type: zod.z.literal("LOADING"),
|
|
3593
|
+
isLoading: zod.z.boolean()
|
|
3594
|
+
}),
|
|
3595
|
+
zod.z.object({
|
|
3596
|
+
type: zod.z.literal("SET_DATA_CONTEXT"),
|
|
3597
|
+
payload: zod.z.record(zod.z.string(), zod.z.unknown())
|
|
3598
|
+
})
|
|
3599
|
+
]);
|
|
3600
|
+
zod.z.object({
|
|
3601
|
+
layout: uiSpecNode.nullable(),
|
|
3602
|
+
loading: zod.z.boolean(),
|
|
3603
|
+
history: zod.z.array(uiEvent),
|
|
3604
|
+
error: zod.z.string().nullable(),
|
|
3605
|
+
dataContext: zod.z.record(zod.z.string(), zod.z.unknown())
|
|
3606
|
+
});
|
|
3607
|
+
zod.z.object({
|
|
3608
|
+
schema: zod.z.record(zod.z.unknown()),
|
|
3609
|
+
goal: zod.z.string(),
|
|
3610
|
+
history: zod.z.array(uiEvent).nullable(),
|
|
3611
|
+
userContext: zod.z.record(zod.z.unknown()).nullable().optional()
|
|
3612
|
+
});
|
|
3161
3613
|
|
|
3162
3614
|
// src/adapters/schema/drizzle.ts
|
|
3163
3615
|
var DrizzleAdapter = class {
|
|
@@ -3292,113 +3744,23 @@ var generateUIComponent = async (prompt) => {
|
|
|
3292
3744
|
);
|
|
3293
3745
|
return `<div>Generated UI Component for: ${prompt}</div>`;
|
|
3294
3746
|
};
|
|
3295
|
-
function usePlanner(options) {
|
|
3296
|
-
const {
|
|
3297
|
-
schema,
|
|
3298
|
-
goal,
|
|
3299
|
-
openaiApiKey,
|
|
3300
|
-
userContext,
|
|
3301
|
-
initialLayout,
|
|
3302
|
-
mockMode: optionsMockMode
|
|
3303
|
-
} = options;
|
|
3304
|
-
const [layout, setLayout] = React.useState(
|
|
3305
|
-
initialLayout || void 0
|
|
3306
|
-
);
|
|
3307
|
-
const [loading, setLoading] = React.useState(false);
|
|
3308
|
-
const [error, setError] = React.useState(null);
|
|
3309
|
-
const router = options.router || createDefaultRouter();
|
|
3310
|
-
const dataContext = {};
|
|
3311
|
-
const initialFetchAttempted = React.useRef(false);
|
|
3312
|
-
const mockMode = optionsMockMode || !openaiApiKey;
|
|
3313
|
-
const generateInitialLayout = React.useCallback(async () => {
|
|
3314
|
-
setLoading(true);
|
|
3315
|
-
setError(null);
|
|
3316
|
-
try {
|
|
3317
|
-
const plannerInput2 = {
|
|
3318
|
-
schema,
|
|
3319
|
-
goal,
|
|
3320
|
-
userContext: userContext || null,
|
|
3321
|
-
history: null
|
|
3322
|
-
};
|
|
3323
|
-
let generatedLayout;
|
|
3324
|
-
if (mockMode) {
|
|
3325
|
-
console.warn(
|
|
3326
|
-
"Using mock planner in usePlanner hook (mockMode enabled or API key missing)."
|
|
3327
|
-
);
|
|
3328
|
-
generatedLayout = mockPlanner(plannerInput2);
|
|
3329
|
-
} else {
|
|
3330
|
-
generatedLayout = await callPlannerLLM(
|
|
3331
|
-
plannerInput2,
|
|
3332
|
-
openaiApiKey,
|
|
3333
|
-
void 0
|
|
3334
|
-
);
|
|
3335
|
-
}
|
|
3336
|
-
setLayout(generatedLayout);
|
|
3337
|
-
} catch (err) {
|
|
3338
|
-
setError(err instanceof Error ? err : new Error(String(err)));
|
|
3339
|
-
} finally {
|
|
3340
|
-
setLoading(false);
|
|
3341
|
-
}
|
|
3342
|
-
}, [schema, goal, userContext, openaiApiKey, mockMode]);
|
|
3343
|
-
const handleEvent = React.useCallback(
|
|
3344
|
-
async (event) => {
|
|
3345
|
-
if (!layout) {
|
|
3346
|
-
setError(new Error("Cannot handle event - no layout exists"));
|
|
3347
|
-
return;
|
|
3348
|
-
}
|
|
3349
|
-
setLoading(true);
|
|
3350
|
-
setError(null);
|
|
3351
|
-
try {
|
|
3352
|
-
const updatedLayout = await processEvent(
|
|
3353
|
-
event,
|
|
3354
|
-
router,
|
|
3355
|
-
schema,
|
|
3356
|
-
layout,
|
|
3357
|
-
dataContext,
|
|
3358
|
-
goal,
|
|
3359
|
-
userContext,
|
|
3360
|
-
openaiApiKey
|
|
3361
|
-
);
|
|
3362
|
-
setLayout(updatedLayout);
|
|
3363
|
-
} catch (err) {
|
|
3364
|
-
setError(err instanceof Error ? err : new Error(String(err)));
|
|
3365
|
-
} finally {
|
|
3366
|
-
setLoading(false);
|
|
3367
|
-
}
|
|
3368
|
-
},
|
|
3369
|
-
[layout, router, schema, dataContext, goal, userContext, openaiApiKey]
|
|
3370
|
-
);
|
|
3371
|
-
React.useEffect(() => {
|
|
3372
|
-
if (options.initialLayout === void 0 && layout === void 0 && !initialFetchAttempted.current) {
|
|
3373
|
-
initialFetchAttempted.current = true;
|
|
3374
|
-
generateInitialLayout();
|
|
3375
|
-
}
|
|
3376
|
-
}, [options.initialLayout, layout, generateInitialLayout]);
|
|
3377
|
-
return {
|
|
3378
|
-
layout,
|
|
3379
|
-
loading,
|
|
3380
|
-
error,
|
|
3381
|
-
handleEvent,
|
|
3382
|
-
generateInitialLayout
|
|
3383
|
-
};
|
|
3384
|
-
}
|
|
3385
3747
|
|
|
3386
3748
|
exports.ActionRouter = ActionRouter;
|
|
3387
3749
|
exports.ActionType = ActionType;
|
|
3388
3750
|
exports.AutoUI = AutoUI;
|
|
3389
3751
|
exports.DrizzleAdapter = DrizzleAdapter;
|
|
3390
3752
|
exports.SystemEventType = SystemEventType;
|
|
3391
|
-
exports.
|
|
3753
|
+
exports.componentType = componentType;
|
|
3392
3754
|
exports.createEventHook = createEventHook;
|
|
3393
3755
|
exports.createSchemaAdapter = createSchemaAdapter;
|
|
3394
3756
|
exports.createSystemEvent = createSystemEvent;
|
|
3395
3757
|
exports.generateComponent = generateComponent;
|
|
3396
3758
|
exports.generateUIComponent = generateUIComponent;
|
|
3397
3759
|
exports.generateUIDescription = generateUIDescription;
|
|
3760
|
+
exports.openAIUISpec = openAIUISpec;
|
|
3398
3761
|
exports.systemEvents = systemEvents;
|
|
3399
3762
|
exports.uiEvent = uiEvent;
|
|
3400
3763
|
exports.uiEventType = uiEventType;
|
|
3401
3764
|
exports.uiSpecNode = uiSpecNode;
|
|
3402
|
-
exports.usePlanner = usePlanner;
|
|
3403
3765
|
//# sourceMappingURL=out.js.map
|
|
3404
3766
|
//# sourceMappingURL=index.js.map
|