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