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.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 React, { useState, useEffect, useRef, useCallback, useReducer } from 'react';
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: false,
307
+ loading: true,
308
+ error: null,
298
309
  history: [],
299
- error: null
310
+ dataContext: {}
300
311
  };
301
312
 
302
- // src/core/action-router.ts
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
- var ActionRouter = class {
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.routes = {};
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
- * Find the appropriate route for an event
397
- * @param event - UI event
398
- * @param layout - Current UI layout
399
- * @param dataContext - Current data context
400
- * @returns Route resolution or null if no match
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
- resolveRoute(event, schema, layout, dataContext, goal, userContext) {
403
- console.log(
404
- `[ActionRouter Debug] resolveRoute called for event type: ${event.type}`
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
- const plannerInput2 = {
474
- schema,
475
- goal,
476
- history: [event],
477
- userContext: {
478
- ...userContext,
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
- * Process a prompt template with variables
514
- * @param template - Template string with ${var} placeholders
515
- * @param values - Values to substitute
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
- processTemplate(template, values) {
519
- return template.replace(/\${(\w+)}/g, (_, key) => {
520
- return values[key] !== void 0 ? String(values[key]) : `\${${key}}`;
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
- function createDefaultRouter() {
525
- const router = new ActionRouter();
526
- router.registerRoute("CLICK", {
527
- actionType: "FULL_REFRESH" /* FULL_REFRESH */,
528
- targetNodeId: "root"
529
- });
530
- router.registerRoute("INIT", {
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 uiEventType = z.enum([
586
- "INIT",
587
- "CLICK",
588
- "CHANGE",
589
- "SUBMIT",
590
- "MOUSEOVER",
591
- "MOUSEOUT",
592
- "FOCUS",
593
- "BLUR"
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}}"). **CRITICAL for ListView/Table:** the `data` key MUST point to the *exact array path* (e.g., { "data": "tasks.data" } or { "data": "userList" }), NOT the parent object.'
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: z.string(),
621
- target: z.string(),
622
- payload: openAIEventPayloadSimplifiedNullable
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
- // Entire events object is nullable
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 getOpenAIClient = (apiKey) => {
774
- return createOpenAI({
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(input, targetNodeId, customPrompt) {
781
- if (customPrompt) {
782
- console.log("mockPlanner received customPrompt:", customPrompt);
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-1",
812
- node_type: "Header",
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: null
496
+ children: [
497
+ {
498
+ id: "title",
499
+ node_type: "Text",
500
+ props: { text: "Task Dashboard", className: "text-2xl font-bold" },
501
+ bindings: null,
502
+ events: null,
503
+ children: null
504
+ },
505
+ {
506
+ id: "add-task-button",
507
+ node_type: "Button",
508
+ props: { label: "Add Task", variant: "default" },
509
+ bindings: null,
510
+ events: {
511
+ CLICK: { action: "SHOW_DETAIL", target: "new-task-form" }
512
+ },
513
+ children: null
514
+ }
515
+ ]
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-heading",
841
- node_type: "Container",
842
- props: {
843
- className: "flex justify-between items-center mb-4"
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: "list-title",
850
- node_type: "Header",
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: null
858
- },
859
- {
860
- id: "add-task-button",
861
- node_type: "Button",
862
- props: {
863
- label: "Add Task",
864
- variant: "default"
865
- },
866
- bindings: null,
867
- events: {
868
- onClick: {
869
- action: "ADD_TASK",
870
- target: "tasks-container",
871
- payload: {}
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: "task-list",
880
- node_type: "ListView",
881
- props: {
882
- selectable: "true"
883
- },
884
- bindings: {
885
- data: "tasks.data",
886
- fields: JSON.stringify([
887
- { key: "id", label: "ID" },
888
- { key: "title", label: "Title" },
889
- { key: "status", label: "Status" },
890
- { key: "priority", label: "Priority" }
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
- onSelect: {
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
- id: "task-detail",
908
- node_type: "Detail",
909
- props: {
910
- title: "Task Details",
911
- visible: "true"
912
- },
913
- bindings: {
914
- data: JSON.stringify(taskData[0]),
915
- fields: JSON.stringify([
916
- { key: "title", label: "Title", type: "heading" },
917
- { key: "description", label: "Description", type: "content" },
918
- { key: "status", label: "Status" },
919
- { key: "priority", label: "Priority" },
920
- { key: "dueDate", label: "Due Date" }
921
- ])
922
- },
923
- events: {
924
- onBack: {
925
- action: "CLOSE_DETAIL",
926
- target: "task-detail",
927
- payload: {}
928
- }
929
- },
930
- children: null
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
- return mockNode;
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
- await systemEvents.emit(
960
- createSystemEvent("PLAN_PROMPT_CREATED" /* PLAN_PROMPT_CREATED */, { prompt })
961
- );
962
- try {
963
- const { object: uiSpec } = await generateObject({
964
- model: getOpenAIClient(openaiApiKey)("gpt-4o", {
965
- structuredOutputs: true
966
- }),
967
- schema: openAIUISpec,
968
- messages: [{ role: "user", content: prompt }],
969
- temperature: 0.2,
970
- maxTokens: 4e3
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
- async function processEvent(event, router, schema, layout, dataContext, goal, userContext, openaiApiKey) {
990
- const routeResolution = await router.resolveRoute(
991
- event,
992
- schema,
993
- layout || null,
994
- dataContext,
995
- goal,
996
- userContext
997
- );
998
- if (!routeResolution) {
999
- throw new Error(
1000
- `No route found for event type: ${event.type}, node: ${event.nodeId}`
1001
- );
1002
- }
1003
- if (routeResolution.actionType.toString() === "NoOp") {
1004
- if (!layout)
1005
- throw new Error("Layout is undefined and action is NoOp");
1006
- return layout;
1007
- }
1008
- const plannerInputForLLM = routeResolution.plannerInput;
1009
- const newLayout = await callPlannerLLM(
1010
- plannerInputForLLM,
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
- // src/core/state.ts
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
- openaiApiKey,
1478
+ apiKey = "",
1022
1479
  userContext,
1023
- mockMode = false,
1024
1480
  planningConfig,
1025
- router = createDefaultRouter(),
1026
1481
  dataContext = {},
1027
- enablePartialUpdates = false
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
- if (enablePartialUpdates) {
1044
- const route = router.resolveRoute(
1045
- event,
1046
- schema,
1047
- state.layout,
1048
- dataContext,
1049
- goal,
1050
- userContext
1051
- );
1052
- if (route) {
1053
- console.log("Resolved route:", route);
1054
- actionTypeForDispatch = route.actionType;
1055
- targetNodeIdForDispatch = route.targetNodeId;
1056
- systemEvents.emit(
1057
- createSystemEvent("PLAN_START" /* PLAN_START */, {
1058
- plannerInput: route.plannerInput
1059
- })
1060
- );
1061
- if (mockMode) {
1062
- resolvedNode = mockPlanner(
1063
- route.plannerInput,
1064
- route.targetNodeId,
1065
- route.prompt
1066
- );
1067
- } else {
1068
- resolvedNode = await callPlannerLLM(
1069
- route.plannerInput,
1070
- openaiApiKey || "",
1071
- route
1072
- );
1073
- }
1074
- } else {
1075
- const input = {
1076
- schema,
1077
- goal,
1078
- history: [...state.history, event],
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
- } else {
1092
- const input = {
1093
- schema,
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
- openaiApiKey,
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 input = {
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
- history: [],
1166
- // Initial history is empty
1615
+ apiKey,
1167
1616
  userContext
1168
- };
1169
- let node;
1170
- if (mockMode) {
1171
- node = mockPlanner(input);
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
- node = await callPlannerLLM(
1202
- route.plannerInput,
1203
- // Use plannerInput from the resolved route
1204
- openaiApiKey || "",
1205
- route
1206
- // Pass the entire route object
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, userContext, mockMode, dispatch, openaiApiKey]);
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
- if (field.type === "heading") {
1716
- return /* @__PURE__ */ jsx(
1717
- "h3",
1718
- {
1719
- className: "text-xl font-semibold text-gray-800 dark:text-white",
1720
- children: data?.[field.key] ?? ""
1721
- },
1722
- field.key
1723
- );
1724
- }
1725
- if (field.type === "content") {
1726
- return /* @__PURE__ */ jsx(
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: "text-sm text-gray-700 dark:text-gray-300",
1730
- children: data?.[field.key] ?? ""
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, processEvent2) => {
2234
+ var createEventHandler = (node, eventName, uiEventType2, processEvent) => {
1787
2235
  const eventConfig = node.events?.[uiEventType2];
1788
- if (!processEvent2 || !eventConfig)
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
- processEvent2(fullEvent);
2248
+ processEvent(fullEvent);
1801
2249
  };
1802
2250
  };
1803
2251
  var adapterMap = {
1804
- Container: (node, processEvent2) => {
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, processEvent2), { key: child.id })
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, processEvent2) => /* @__PURE__ */ jsx(
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", processEvent2),
2293
+ onClick: createEventHandler(node, "onClick", "CLICK", processEvent),
1846
2294
  children: getSafeProp(node.props, "label", isString, "Button")
1847
2295
  }
1848
2296
  ),
1849
- ListView: (node, processEvent2) => {
1850
- const { className, style: styleProp, key, ...restProps } = node.props || {};
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, processEvent2), { key: child.id })
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, processEvent2) => {
1875
- const data = getSafeBinding(
1876
- node.bindings,
2330
+ Detail: (node, processEvent) => {
2331
+ const data = getSafeProp(
2332
+ node.props,
1877
2333
  "data",
1878
2334
  isRecordWithReactNodeValues,
1879
2335
  {}
1880
2336
  );
1881
- const fields = getSafeBinding(
1882
- node.bindings,
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", processEvent2)
2359
+ onBack: createEventHandler(node, "onBack", "CLICK", processEvent)
1897
2360
  }
1898
2361
  );
1899
2362
  },
1900
- Card: (node, processEvent2) => {
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, processEvent2), { key: child.id })
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, processEvent2) => {
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
- processEvent2
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
- processEvent2
2406
+ processEvent
1944
2407
  );
1945
2408
  if (handler)
1946
2409
  handler({});
1947
2410
  };
1948
2411
  const handleBlur = () => {
1949
- const handler = createEventHandler(node, "onBlur", "BLUR", processEvent2);
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, processEvent2) => {
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
- processEvent2
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, processEvent2) => {
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
- processEvent2
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
- processEvent2
2519
+ processEvent
2057
2520
  );
2058
2521
  if (handler)
2059
2522
  handler({});
2060
2523
  };
2061
2524
  const handleBlur = () => {
2062
- const handler = createEventHandler(node, "onBlur", "BLUR", processEvent2);
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, processEvent2) => {
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
- processEvent2
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, processEvent2) => {
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
- processEvent2
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, processEvent2) => {
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
- processEvent2
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, processEvent2) : null }, tab.value))
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, processEvent2) => {
2694
+ Dialog: (node, processEvent) => {
2232
2695
  const isOpen = getSafeBinding(
2233
2696
  node.bindings,
2234
- "open",
2697
+ "visible",
2235
2698
  isBoolean,
2236
- getSafeProp(node.props, "open", isBoolean, false)
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, processEvent2), { key: child.id })
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
- processEvent2
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, processEvent2) {
2836
+ function renderNode(node, processEvent) {
2345
2837
  const mappedComponent = adapterMap[node.node_type];
2346
2838
  if (mappedComponent) {
2347
- return mappedComponent(node, processEvent2);
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
- async function renderNode2(node, adapter = "shadcn", processEvent2) {
2360
- const startTime = Date.now();
2361
- const nodeId = node.id;
2362
- const cachedItem = renderedNodesCache.get(nodeId);
2363
- if (cachedItem && startTime - cachedItem.timestamp < CACHE_TTL) {
2364
- return cachedItem.element;
2365
- }
2366
- await systemEvents.emit(
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
- const dataBindingValue = resolvedBindings["data"] ?? resolvedBindings["items"];
2624
- if ((node.node_type === "ListView" || node.node_type === "Table") && Array.isArray(dataBindingValue) && node.children && node.children.length > 0) {
2625
- const templateChild = node.children[0];
2626
- const mappedChildren = await Promise.all(
2627
- dataBindingValue.map(async (currentItemData, index) => {
2628
- try {
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
- if (!itemData) {
2674
- await systemEvents.emit(
2675
- createSystemEvent("BINDING_RESOLUTION_COMPLETE" /* BINDING_RESOLUTION_COMPLETE */, {
2676
- originalLayout: node,
2677
- resolvedLayout: result
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
- bindingsCache.set(cacheKey, result);
2682
- nodeCacheTimestamps.set(cacheKey, currentTime);
2683
- if (bindingsCache.size > MAX_CACHE_SIZE2) {
2684
- let oldestKey = null;
2685
- let oldestTimestamp = currentTime;
2686
- for (const [key, timestamp] of nodeCacheTimestamps.entries()) {
2687
- if (timestamp < oldestTimestamp) {
2688
- oldestTimestamp = timestamp;
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
- return result;
2698
- }
2699
- function executeAction(action, targetId, payload, context = {}, layoutTree) {
2700
- let newContext = { ...context };
2701
- switch (action) {
2702
- case "VIEW_DETAIL": {
2703
- if (payload?.item) {
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
- break;
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
- case "HIDE_DETAIL": {
2709
- newContext = setValueByPath(newContext, "selected", null);
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
- case "SET_VALUE": {
2713
- if (payload?.path && "value" in payload) {
2714
- const path = String(payload.path);
2715
- newContext = setValueByPath(newContext, path, payload.value);
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
- console.warn(`Unknown action: ${action}`);
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
- scope = {},
2863
- enablePartialUpdates = false,
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 effectiveSchema = schema;
2870
- const scopedGoal = goal;
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
- openaiApiKey
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
- const processEvent2 = useCallback(
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("Event processing was prevented by hooks", event);
2984
- return;
2985
- }
2986
- const findNodeById2 = (node, id) => {
2987
- if (!node)
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
- const eventConfig = sourceNode.events?.[event.type];
3007
- if (!eventConfig) {
3008
- console.warn(
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
- handleEvent(event);
3012
- return;
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 Debug] Raw layout before resolving (from planner):",
3044
- JSON.stringify(state.layout, null, 2)
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 Debug] Layout after binding correction (before resolving):",
3052
- JSON.stringify(correctedLayout, null, 2)
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 Debug] Resolved layout after bindings:",
3058
- JSON.stringify(resolved, null, 2)
3291
+ "[AutoUI.processEvent] Passing dataContext keys to engine:",
3292
+ Object.keys(dataContext)
3059
3293
  );
3060
- } else {
3061
- setResolvedLayout(void 0);
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
- resolveLayoutBindings();
3066
- }, [resolveLayoutBindings]);
3067
- const renderResolvedLayout = useCallback(async () => {
3068
- if (resolvedLayout) {
3069
- try {
3070
- const rendered = await renderNode2(
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
- setRenderedNode(rendered);
3076
- } catch (err) {
3077
- console.error("Error rendering node:", err);
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
- }, [resolvedLayout, componentAdapter, processEvent2]);
3432
+ }, [state.loading, state.layout, state.error, uiStatus, isResolvingBindings]);
3083
3433
  useEffect(() => {
3084
- renderResolvedLayout();
3085
- }, [renderResolvedLayout]);
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
- state.loading || !resolvedLayout ? (
3101
- // Render shimmer loading state
3102
- /* @__PURE__ */ jsx("div", { className: "autoui-loading", children: state.layout ? renderShimmer(state.layout, componentAdapter) : /* @__PURE__ */ jsxs("div", { className: "autoui-shimmer-container", children: [
3103
- /* @__PURE__ */ jsx("div", { className: "autoui-shimmer-header" }),
3104
- /* @__PURE__ */ jsx("div", { className: "autoui-shimmer-content" })
3105
- ] }) })
3106
- ) : (
3107
- // Render the resolved layout
3108
- /* @__PURE__ */ jsx("div", { className: "autoui-content", children: renderedNode })
3109
- ),
3110
- state.error && /* @__PURE__ */ jsxs("div", { className: "autoui-error p-4 border border-red-300 bg-red-50 dark:bg-red-900 dark:border-red-700 rounded-md", children: [
3111
- /* @__PURE__ */ jsx("p", { className: "autoui-error-title text-lg font-semibold text-red-700 dark:text-red-300 mb-2", children: "Error generating UI" }),
3112
- /* @__PURE__ */ jsx("p", { className: "autoui-error-message text-sm text-red-600 dark:text-red-300", children: state.error }),
3113
- !mockMode && /* @__PURE__ */ jsxs("div", { className: "mt-4 text-sm text-red-600 dark:text-red-300", children: [
3114
- /* @__PURE__ */ jsx("p", { children: "This could be because:" }),
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, createDefaultRouter, createEventHook, createSchemaAdapter, createSystemEvent, generateComponent, generateUIComponent, generateUIDescription, systemEvents, uiEvent, uiEventType, uiSpecNode, usePlanner };
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