@memberjunction/react-test-harness 2.91.0 → 2.93.0

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.
@@ -2,7 +2,7 @@
2
2
  import { BrowserManager } from './browser-context';
3
3
  import type { UserInfo } from '@memberjunction/core';
4
4
  import { FixSuggestion, Violation } from './component-linter';
5
- import { ComponentSpec } from '@memberjunction/interactive-component-types';
5
+ import { ComponentSpec, ComponentUtilities, SimpleAITools } from '@memberjunction/interactive-component-types';
6
6
  export interface ComponentExecutionOptions {
7
7
  componentSpec: ComponentSpec;
8
8
  props?: Record<string, any>;
@@ -15,6 +15,7 @@ export interface ComponentExecutionOptions {
15
15
  contextUser: UserInfo;
16
16
  isRootComponent?: boolean;
17
17
  debug?: boolean;
18
+ utilities?: ComponentUtilities;
18
19
  }
19
20
  export interface ComponentExecutionResult {
20
21
  success: boolean;
@@ -43,7 +44,7 @@ export declare class ComponentRunner {
43
44
  /**
44
45
  * Lint component code before execution
45
46
  */
46
- lintComponent(componentCode: string, componentName: string, componentSpec?: any, isRootComponent?: boolean): Promise<{
47
+ lintComponent(componentCode: string, componentName: string, componentSpec?: any, isRootComponent?: boolean, contextUser?: UserInfo, options?: any): Promise<{
47
48
  violations: Violation[];
48
49
  suggestions: FixSuggestion[];
49
50
  hasErrors: boolean;
@@ -73,6 +74,8 @@ export declare class ComponentRunner {
73
74
  * Set up console logging
74
75
  */
75
76
  private setupConsoleLogging;
77
+ private buildLocalMJUtilities;
78
+ protected BuildLocalSimpleAITools(contextUser: UserInfo): Promise<SimpleAITools>;
76
79
  /**
77
80
  * Expose MJ utilities to the browser context
78
81
  */
@@ -1 +1 @@
1
- {"version":3,"file":"component-runner.d.ts","sourceRoot":"","sources":["../../src/lib/component-runner.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAEnD,OAAO,KAAK,EAAiC,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AACpF,OAAO,EAAmB,aAAa,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC/E,OAAO,EAAE,aAAa,EAAE,MAAM,6CAA6C,CAAC;AAG5E,MAAM,WAAW,yBAAyB;IACxC,aAAa,EAAE,aAAa,CAAC;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gBAAgB,CAAC,EAAE,MAAM,GAAG,kBAAkB,GAAG,aAAa,CAAC;IAC/D,WAAW,EAAE,QAAQ,CAAC;IACtB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,wBAAwB;IACvC,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,QAAQ,EAAE,SAAS,EAAE,CAAC;IACtB,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC1C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,SAAS,EAAE,CAAC;IAC7B,cAAc,CAAC,EAAE,aAAa,EAAE,CAAC;CAClC;AAED;;GAEG;AACH,qBAAa,eAAe;IAed,OAAO,CAAC,cAAc;IAblC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,yBAAyB,CAS/C;IAEF,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,gBAAgB,CAAQ;gBAE5B,cAAc,EAAE,cAAc;IAElD;;OAEG;IACG,aAAa,CACjB,aAAa,EAAE,MAAM,EACrB,aAAa,EAAE,MAAM,EACrB,aAAa,CAAC,EAAE,GAAG,EACnB,eAAe,CAAC,EAAE,OAAO,GACxB,OAAO,CAAC;QAAE,UAAU,EAAE,SAAS,EAAE,CAAC;QAAC,WAAW,EAAE,aAAa,EAAE,CAAC;QAAC,SAAS,EAAE,OAAO,CAAA;KAAE,CAAC;IAiBnF,gBAAgB,CAAC,OAAO,EAAE,yBAAyB,GAAG,OAAO,CAAC,wBAAwB,CAAC;IAmkB7F;;OAEG;YACW,oBAAoB;IAiElC;;OAEG;YACW,sBAAsB;IA6GpC;;OAEG;YACW,kBAAkB;IAyEhC;;OAEG;YACW,oBAAoB;IAiClC;;OAEG;YACW,eAAe;IAkB7B;;OAEG;IACH,OAAO,CAAC,mBAAmB;IA+B3B;;OAEG;YACW,iBAAiB;IA8O/B;;OAEG;IACH,OAAO,CAAC,aAAa;CA6BtB"}
1
+ {"version":3,"file":"component-runner.d.ts","sourceRoot":"","sources":["../../src/lib/component-runner.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAEnD,OAAO,KAAK,EAAiC,QAAQ,EAAyD,MAAM,sBAAsB,CAAC;AAC3I,OAAO,EAAmB,aAAa,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC/E,OAAO,EACL,aAAa,EACb,kBAAkB,EAClB,aAAa,EAKd,MAAM,6CAA6C,CAAC;AAMrD,MAAM,WAAW,yBAAyB;IACxC,aAAa,EAAE,aAAa,CAAC;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gBAAgB,CAAC,EAAE,MAAM,GAAG,kBAAkB,GAAG,aAAa,CAAC;IAC/D,WAAW,EAAE,QAAQ,CAAC;IACtB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,SAAS,CAAC,EAAE,kBAAkB,CAAC;CAChC;AAED,MAAM,WAAW,wBAAwB;IACvC,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,QAAQ,EAAE,SAAS,EAAE,CAAC;IACtB,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC1C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,SAAS,EAAE,CAAC;IAC7B,cAAc,CAAC,EAAE,aAAa,EAAE,CAAC;CAClC;AAED;;GAEG;AACH,qBAAa,eAAe;IAkBd,OAAO,CAAC,cAAc;IAhBlC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,yBAAyB,CAS/C;IAKF,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,gBAAgB,CAAS;gBAE7B,cAAc,EAAE,cAAc;IAElD;;OAEG;IACG,aAAa,CACjB,aAAa,EAAE,MAAM,EACrB,aAAa,EAAE,MAAM,EACrB,aAAa,CAAC,EAAE,GAAG,EACnB,eAAe,CAAC,EAAE,OAAO,EACzB,WAAW,CAAC,EAAE,QAAQ,EACtB,OAAO,CAAC,EAAE,GAAG,GACZ,OAAO,CAAC;QAAE,UAAU,EAAE,SAAS,EAAE,CAAC;QAAC,WAAW,EAAE,aAAa,EAAE,CAAC;QAAC,SAAS,EAAE,OAAO,CAAA;KAAE,CAAC;IAoBnF,gBAAgB,CAAC,OAAO,EAAE,yBAAyB,GAAG,OAAO,CAAC,wBAAwB,CAAC;IAkmB7F;;OAEG;YACW,oBAAoB;IAiElC;;OAEG;YACW,sBAAsB;IAiHpC;;OAEG;YACW,kBAAkB;IAyEhC;;OAEG;YACW,oBAAoB;IAmClC;;OAEG;YACW,eAAe;IAkB7B;;OAEG;IACH,OAAO,CAAC,mBAAmB;YA+Bb,qBAAqB;cAqBnB,uBAAuB,CAAC,WAAW,EAAE,QAAQ,GAAG,OAAO,CAAC,aAAa,CAAC;IA2JtF;;OAEG;YACW,iBAAiB;IAsT/B;;OAEG;IACH,OAAO,CAAC,aAAa;CA6BtB"}
@@ -27,6 +27,8 @@ exports.ComponentRunner = void 0;
27
27
  const core_1 = require("@memberjunction/core");
28
28
  const component_linter_1 = require("./component-linter");
29
29
  const core_entities_1 = require("@memberjunction/core-entities");
30
+ const ai_vectors_memory_1 = require("@memberjunction/ai-vectors-memory");
31
+ const aiengine_1 = require("@memberjunction/aiengine");
30
32
  /**
31
33
  * ComponentRunner that uses the actual React runtime via Playwright UMD bundle
32
34
  */
@@ -37,8 +39,9 @@ class ComponentRunner {
37
39
  /**
38
40
  * Lint component code before execution
39
41
  */
40
- async lintComponent(componentCode, componentName, componentSpec, isRootComponent) {
41
- const lintResult = await component_linter_1.ComponentLinter.lintComponent(componentCode, componentName, componentSpec, isRootComponent);
42
+ async lintComponent(componentCode, componentName, componentSpec, isRootComponent, contextUser, options) {
43
+ const lintResult = await component_linter_1.ComponentLinter.lintComponent(componentCode, componentName, componentSpec, isRootComponent, contextUser, false, // debugMode
44
+ options);
42
45
  const hasErrors = lintResult.violations.some(v => v.severity === 'critical' || v.severity === 'high');
43
46
  return {
44
47
  violations: lintResult.violations,
@@ -121,7 +124,7 @@ class ComponentRunner {
121
124
  // Set up console logging
122
125
  this.setupConsoleLogging(page, consoleLogs, warnings, criticalWarnings);
123
126
  // Expose MJ utilities to the page
124
- await this.exposeMJUtilities(page, options.contextUser, dataErrors, debug);
127
+ await this.exposeMJUtilities(page, options, dataErrors, debug);
125
128
  if (debug) {
126
129
  console.log('📤 NODE: About to call page.evaluate with:');
127
130
  console.log(' - spec.name:', options.componentSpec.name);
@@ -172,11 +175,11 @@ class ComponentRunner {
172
175
  if (libraryValue) {
173
176
  loadedLibraries[libDef.GlobalVariable] = libraryValue;
174
177
  if (debug) {
175
- console.log(`✅ Added ${libDef.Name} to runtime context as ${libDef.GlobalVariable}`);
178
+ console.log(`📦 Added ${libDef.Name} to runtime context as ${libDef.GlobalVariable}`);
176
179
  }
177
180
  }
178
181
  else {
179
- console.warn(`⚠️ Library ${libDef.Name} not found as window.${libDef.GlobalVariable}`);
182
+ console.warn(`⚠️ Library ${libDef.Name} global variable ${libDef.GlobalVariable} not found on window`);
180
183
  }
181
184
  }
182
185
  }
@@ -198,7 +201,7 @@ class ComponentRunner {
198
201
  // Note: LibraryRegistry.Config expects ComponentLibraryEntity[]
199
202
  await LibraryRegistry.Config(false, componentLibraries || []);
200
203
  if (debug) {
201
- console.log(' Configured LibraryRegistry with', componentLibraries?.length || 0, 'libraries');
204
+ console.log('⚙️ Configured LibraryRegistry with', componentLibraries?.length || 0, 'libraries');
202
205
  }
203
206
  }
204
207
  const registry = new ComponentRegistry();
@@ -234,7 +237,7 @@ class ComponentRunner {
234
237
  })));
235
238
  }
236
239
  }
237
- // Register the component hierarchy
240
+ // Register the component hierarchy with error capture
238
241
  // IMPORTANT: Pass component libraries for library loading to work
239
242
  if (debug) {
240
243
  console.log('📚 Registering component with', componentLibraries?.length || 0, 'libraries');
@@ -242,12 +245,28 @@ class ComponentRunner {
242
245
  console.log(' Passing libraries to registrar:', componentLibraries.slice(0, 2).map((l) => l.Name));
243
246
  }
244
247
  }
245
- const registrationResult = await registrar.registerHierarchy(spec, {
246
- styles,
247
- namespace: 'Global',
248
- version: 'v1', // Use v1 to match the registry defaults
249
- allLibraries: componentLibraries || [] // Pass the component libraries for LibraryRegistry
250
- });
248
+ let registrationResult;
249
+ try {
250
+ registrationResult = await registrar.registerHierarchy(spec, {
251
+ styles,
252
+ namespace: 'Global',
253
+ version: 'v1', // Use v1 to match the registry defaults
254
+ allLibraries: componentLibraries || [] // Pass the component libraries for LibraryRegistry
255
+ });
256
+ }
257
+ catch (registrationError) {
258
+ // Capture the actual error before it gets obscured
259
+ console.error('🔴 Component registration error:', registrationError);
260
+ window.__testHarnessRuntimeErrors = window.__testHarnessRuntimeErrors || [];
261
+ window.__testHarnessRuntimeErrors.push({
262
+ message: `Component registration failed: ${registrationError.message || registrationError}`,
263
+ stack: registrationError.stack,
264
+ type: 'registration-error',
265
+ phase: 'component-compilation'
266
+ });
267
+ window.__testHarnessTestFailed = true;
268
+ throw registrationError; // Re-throw for outer handler
269
+ }
251
270
  if (debug && !registrationResult.success) {
252
271
  console.log('❌ Registration failed:', registrationResult.errors);
253
272
  }
@@ -255,7 +274,7 @@ class ComponentRunner {
255
274
  throw new Error('Component registration failed: ' + JSON.stringify(registrationResult.errors));
256
275
  }
257
276
  if (debug) {
258
- console.log(' Registered components:', registrationResult.registeredComponents);
277
+ console.log('📝 Registered components:', registrationResult.registeredComponents);
259
278
  // Note: ComponentRegistry doesn't expose internal components Map directly
260
279
  // We can see what was registered through the registrationResult
261
280
  }
@@ -277,7 +296,7 @@ class ComponentRunner {
277
296
  // If the library exports an object with multiple components, spread them
278
297
  Object.assign(components, libraryValue);
279
298
  if (debug) {
280
- console.log(`✅ Added ${globalVar} exports to components object`);
299
+ console.log(`📦 Added ${globalVar} exports to components object`);
281
300
  }
282
301
  }
283
302
  }
@@ -287,8 +306,10 @@ class ComponentRunner {
287
306
  throw new Error('Root element not found');
288
307
  }
289
308
  const root = window.ReactDOM.createRoot(rootElement);
290
- // Set up render count protection (Recommendation #5)
291
- const MAX_RENDERS_ALLOWED = 500; // Reasonable limit for complex components
309
+ // Set up render count protection
310
+ // This is for detecting infinite loops during execution
311
+ // Note: counts createElement calls, not re-renders
312
+ const MAX_RENDERS_ALLOWED = 10000; // Complex dashboards can have many createElement calls
292
313
  if (typeof window !== 'undefined') {
293
314
  renderCheckInterval = setInterval(() => {
294
315
  const currentRenderCount = window.__testHarnessRenderCount || 0;
@@ -298,7 +319,7 @@ class ComponentRunner {
298
319
  window.__testHarnessTestFailed = true;
299
320
  window.__testHarnessRuntimeErrors = window.__testHarnessRuntimeErrors || [];
300
321
  window.__testHarnessRuntimeErrors.push({
301
- message: `Excessive re-renders detected: ${currentRenderCount} renders (max: ${MAX_RENDERS_ALLOWED})`,
322
+ message: `Likely infinite render loop: ${currentRenderCount} createElement calls (max: ${MAX_RENDERS_ALLOWED})`,
302
323
  type: 'render-loop'
303
324
  });
304
325
  // Try to unmount to stop the madness
@@ -308,7 +329,7 @@ class ComponentRunner {
308
329
  catch (e) {
309
330
  console.error('Failed to unmount after render loop:', e);
310
331
  }
311
- throw new Error(`Excessive re-renders: ${currentRenderCount} renders detected`);
332
+ throw new Error(`Likely infinite render loop: ${currentRenderCount} createElement calls detected`);
312
333
  }
313
334
  }, 100); // Check every 100ms
314
335
  }
@@ -333,24 +354,33 @@ class ComponentRunner {
333
354
  this.state = { hasError: false, error: null };
334
355
  }
335
356
  static getDerivedStateFromError(error) {
336
- window.__testHarnessTestFailed = true;
337
- return { hasError: true, error };
338
- }
339
- componentDidCatch(error, errorInfo) {
340
- console.error('React Error Boundary caught:', error, errorInfo);
357
+ // Capture the actual error message IMMEDIATELY
341
358
  window.__testHarnessRuntimeErrors = window.__testHarnessRuntimeErrors || [];
342
359
  window.__testHarnessRuntimeErrors.push({
343
- message: error.message,
360
+ message: error.message || error.toString(),
344
361
  stack: error.stack,
345
- componentStack: errorInfo.componentStack,
346
- type: 'react-error-boundary'
362
+ type: 'react-render-error',
363
+ phase: 'component-render'
347
364
  });
348
365
  window.__testHarnessTestFailed = true;
366
+ return { hasError: true, error };
367
+ }
368
+ componentDidCatch(error, errorInfo) {
369
+ console.error('React Error Boundary caught:', error, errorInfo);
370
+ // Update the last error with component stack info
371
+ const errors = window.__testHarnessRuntimeErrors || [];
372
+ if (errors.length > 0) {
373
+ const lastError = errors[errors.length - 1];
374
+ if (lastError.type === 'react-render-error') {
375
+ lastError.componentStack = errorInfo.componentStack;
376
+ }
377
+ }
349
378
  }
350
379
  render() {
351
380
  if (this.state.hasError) {
352
- // Re-throw to fail hard
353
- throw this.state.error;
381
+ // DON'T re-throw - this causes "Script error"
382
+ // Instead, render an error message
383
+ return window.React.createElement('div', { style: { color: 'red', padding: '20px' } }, 'Component failed to render: ' + (this.state.error?.message || 'Unknown error'));
354
384
  }
355
385
  return this.props.children;
356
386
  }
@@ -452,7 +482,7 @@ class ComponentRunner {
452
482
  renderCount <= ComponentRunner.MAX_RENDER_COUNT &&
453
483
  executionResult.success;
454
484
  if (renderCount > ComponentRunner.MAX_RENDER_COUNT) {
455
- errors.push(`Excessive render count: ${renderCount} renders detected`);
485
+ errors.push(`Possible render loop: ${renderCount} createElement calls detected (likely infinite loop)`);
456
486
  }
457
487
  // Combine runtime errors with data errors
458
488
  const allErrors = [...errors, ...dataErrors];
@@ -621,7 +651,7 @@ class ComponentRunner {
621
651
  if (cssUrl) {
622
652
  await page.addStyleTag({ url: cssUrl });
623
653
  if (debug) {
624
- console.log(` Loaded CSS: ${cssUrl}`);
654
+ console.log(` 🎨 Loaded CSS: ${cssUrl}`);
625
655
  }
626
656
  }
627
657
  }
@@ -640,11 +670,15 @@ class ComponentRunner {
640
670
  }, libDef.GlobalVariable);
641
671
  if (isLoaded) {
642
672
  if (debug) {
643
- console.log(` Loaded ${specLib.name} as window.${libDef.GlobalVariable}`);
673
+ console.log(` 📦 Loaded ${specLib.name} (available as ${libDef.GlobalVariable})`);
644
674
  }
645
675
  }
646
676
  else {
647
- console.error(` ❌ Failed to load ${specLib.name} - global variable ${libDef.GlobalVariable} not found`);
677
+ // Some libraries (like @mui/material) may load successfully but not attach to window
678
+ // Check if we can at least verify the script loaded
679
+ if (debug) {
680
+ console.log(` 📦 Loaded ${specLib.name} from CDN (global variable ${libDef.GlobalVariable} may not be exposed)`);
681
+ }
648
682
  }
649
683
  }
650
684
  catch (error) {
@@ -765,7 +799,9 @@ class ComponentRunner {
765
799
  errors.push('Test marked as failed by error handlers');
766
800
  }
767
801
  errorData.runtimeErrors.forEach((error) => {
768
- const errorMsg = `${error.type} error: ${error.message}`;
802
+ // Include phase information if available to help identify where the error occurred
803
+ const phase = error.phase ? ` (during ${error.phase})` : '';
804
+ const errorMsg = `${error.type} error: ${error.message}${phase}`;
769
805
  if (!errors.includes(errorMsg)) {
770
806
  errors.push(errorMsg);
771
807
  }
@@ -819,27 +855,179 @@ class ComponentRunner {
819
855
  consoleLogs.push({ type: 'error', text: error.message });
820
856
  });
821
857
  }
858
+ async buildLocalMJUtilities(contextUser) {
859
+ console.log(" Building local MJ utilities");
860
+ const rv = new core_1.RunView();
861
+ const rq = new core_1.RunQuery();
862
+ const md = new core_1.Metadata();
863
+ return {
864
+ rv: {
865
+ RunView: rv.RunView,
866
+ RunViews: rv.RunViews
867
+ },
868
+ rq: {
869
+ RunQuery: rq.RunQuery
870
+ },
871
+ md: {
872
+ GetEntityObject: md.GetEntityObject, // return the function
873
+ Entities: md.Entities // return the function
874
+ },
875
+ ai: await this.BuildLocalSimpleAITools(contextUser)
876
+ };
877
+ }
878
+ async BuildLocalSimpleAITools(contextUser) {
879
+ // Use AIEngine directly since we're in Node.js with full MJ backend
880
+ const aiEngine = aiengine_1.AIEngine.Instance;
881
+ await aiEngine.Config(false, contextUser);
882
+ return {
883
+ ExecutePrompt: async (params) => {
884
+ try {
885
+ // Get the appropriate model based on power level or preferences
886
+ let model;
887
+ if (params.preferredModels && params.preferredModels.length > 0) {
888
+ // Try to find one of the preferred models
889
+ await aiEngine.Config(false, params.contextUser);
890
+ const models = aiEngine.Models;
891
+ for (const preferredModel of params.preferredModels) {
892
+ model = models.find((m) => m.Name === preferredModel &&
893
+ m.IsActive === true);
894
+ if (model)
895
+ break;
896
+ }
897
+ }
898
+ // If no preferred model found, use power level selection
899
+ if (!model) {
900
+ if (params.modelPower === 'lowest') {
901
+ // Get lowest power model by sorting in reverse
902
+ await aiEngine.Config(false, params.contextUser);
903
+ const llmModels = aiEngine.Models.filter((m) => m.AIModelType === 'LLM' &&
904
+ m.IsActive === true);
905
+ model = llmModels.sort((a, b) => (a.PowerRank || 0) - (b.PowerRank || 0))[0];
906
+ }
907
+ else if (params.modelPower === 'highest') {
908
+ model = await aiEngine.GetHighestPowerLLM(undefined, params.contextUser);
909
+ }
910
+ else {
911
+ // Default to medium - get a model in the middle range
912
+ await aiEngine.Config(false, params.contextUser);
913
+ const llmModels = aiEngine.Models.filter((m) => m.AIModelType === 'LLM' &&
914
+ m.IsActive === true);
915
+ const sortedModels = llmModels.sort((a, b) => (b.PowerRank || 0) - (a.PowerRank || 0));
916
+ const midIndex = Math.floor(sortedModels.length / 2);
917
+ model = sortedModels[midIndex] || sortedModels[0];
918
+ }
919
+ }
920
+ // Build full conversation from messages if provided
921
+ let fullUserPrompt = '';
922
+ if (params.messages && params.messages.length > 0) {
923
+ fullUserPrompt = params.messages
924
+ .map(m => `${m.role === 'user' ? 'User' : 'Assistant'}: ${m.message}`)
925
+ .join('\n');
926
+ }
927
+ // Execute the prompt using AIEngine
928
+ const result = await aiEngine.SimpleLLMCompletion(fullUserPrompt || '', params.contextUser || {}, // Provide empty object if no context user
929
+ params.systemPrompt, model);
930
+ // Try to parse JSON if present
931
+ let resultObject;
932
+ try {
933
+ // Look for JSON in the response
934
+ const jsonMatch = result.match(/\{[\s\S]*\}|\[[\s\S]*\]/);
935
+ if (jsonMatch) {
936
+ resultObject = JSON.parse(jsonMatch[0]);
937
+ }
938
+ }
939
+ catch (e) {
940
+ // Not JSON or failed to parse, that's ok
941
+ }
942
+ return {
943
+ success: true,
944
+ result: result,
945
+ resultObject,
946
+ modelName: model?.Name || 'Unknown'
947
+ };
948
+ }
949
+ catch (error) {
950
+ (0, core_1.LogError)(error);
951
+ return {
952
+ success: false,
953
+ result: 'Failed to execute prompt: ' + (error instanceof Error ? error.message : String(error)),
954
+ modelName: ''
955
+ };
956
+ }
957
+ },
958
+ EmbedText: async (params) => {
959
+ try {
960
+ // Handle both single string and array of strings
961
+ const texts = Array.isArray(params.textToEmbed)
962
+ ? params.textToEmbed
963
+ : [params.textToEmbed];
964
+ // Use appropriate embedding model based on size
965
+ await aiEngine.Config(false, params.contextUser);
966
+ // Get embedding models and filter by size preference
967
+ const embeddingModels = aiEngine.Models.filter((m) => m.AIModelType === 'Embeddings' &&
968
+ m.IsActive === true);
969
+ // Select model based on size preference
970
+ let model;
971
+ if (params.modelSize === 'small') {
972
+ // Prefer local/smaller models for 'small'
973
+ model = embeddingModels.find((m) => m.Vendor === 'LocalEmbeddings') ||
974
+ embeddingModels.sort((a, b) => (a.PowerRank || 0) - (b.PowerRank || 0))[0];
975
+ }
976
+ else {
977
+ // Use more powerful models for 'medium'
978
+ model = embeddingModels.sort((a, b) => (b.PowerRank || 0) - (a.PowerRank || 0))[0];
979
+ }
980
+ if (!model) {
981
+ throw new Error('No embedding model available');
982
+ }
983
+ // Generate embeddings for all texts
984
+ const embeddings = [];
985
+ for (const text of texts) {
986
+ const result = await aiEngine.EmbedText(model, text);
987
+ if (result && result.vector) {
988
+ embeddings.push(result.vector);
989
+ }
990
+ else {
991
+ throw new Error('Failed to generate embedding for text');
992
+ }
993
+ }
994
+ // Return single embedding or array based on input
995
+ const returnEmbeddings = Array.isArray(params.textToEmbed)
996
+ ? embeddings
997
+ : embeddings[0];
998
+ return {
999
+ result: returnEmbeddings,
1000
+ modelName: model.Name,
1001
+ vectorDimensions: embeddings[0]?.length || 0
1002
+ };
1003
+ }
1004
+ catch (error) {
1005
+ (0, core_1.LogError)(error);
1006
+ throw error; // Re-throw for embeddings as they're critical
1007
+ }
1008
+ },
1009
+ VectorService: new ai_vectors_memory_1.SimpleVectorService()
1010
+ };
1011
+ }
822
1012
  /**
823
1013
  * Expose MJ utilities to the browser context
824
1014
  */
825
- async exposeMJUtilities(page, contextUser, dataErrors, debug = false) {
1015
+ async exposeMJUtilities(page, options, dataErrors, debug = false) {
826
1016
  // Don't check if already exposed - we always start fresh after goto('about:blank')
827
1017
  // The page.exposeFunction calls need to be made for each new page instance
828
1018
  // Serialize contextUser to pass to the browser context
829
1019
  // UserInfo is a simple object that can be serialized
830
- const serializedContextUser = JSON.parse(JSON.stringify(contextUser));
831
- // Create instances in Node.js context
832
- const metadata = new core_1.Metadata();
833
- const runView = new core_1.RunView();
834
- const runQuery = new core_1.RunQuery();
1020
+ const serializedContextUser = JSON.parse(JSON.stringify(options.contextUser));
1021
+ // utilities - favor the one passed in by the caller, or fall back to the local ones
1022
+ const util = options.utilities || await this.buildLocalMJUtilities(options.contextUser);
835
1023
  // Create a lightweight mock metadata object with serializable data
836
1024
  // This avoids authentication/provider issues in the browser context
837
1025
  let entitiesData = [];
838
1026
  try {
839
1027
  // Try to get entities if available, otherwise use empty array
840
- if (metadata.Entities) {
1028
+ if (util.md?.Entities) {
841
1029
  // Serialize the entities data (remove functions, keep data)
842
- entitiesData = JSON.parse(JSON.stringify(metadata.Entities));
1030
+ entitiesData = JSON.parse(JSON.stringify(util.md.Entities));
843
1031
  // Serialized entities for browser context
844
1032
  }
845
1033
  else {
@@ -891,7 +1079,7 @@ class ComponentRunner {
891
1079
  // Expose functions
892
1080
  await page.exposeFunction('__mjGetEntityObject', async (entityName) => {
893
1081
  try {
894
- const entity = await metadata.GetEntityObject(entityName, contextUser);
1082
+ const entity = await util.md.GetEntityObject(entityName, options.contextUser);
895
1083
  return entity;
896
1084
  }
897
1085
  catch (error) {
@@ -911,11 +1099,11 @@ class ComponentRunner {
911
1099
  });
912
1100
  await page.exposeFunction('__mjRunView', async (params) => {
913
1101
  try {
914
- const result = await runView.RunView(params, contextUser);
1102
+ const result = await util.rv.RunView(params, options.contextUser);
915
1103
  // Debug logging for successful calls
916
1104
  if (debug) {
917
1105
  const rowCount = result.Results?.length || 0;
918
- console.log(`✅ RunView SUCCESS: Entity="${params.EntityName}" Rows=${rowCount}`);
1106
+ console.log(`💾 RunView SUCCESS: Entity="${params.EntityName}" Rows=${rowCount}`);
919
1107
  if (params.ExtraFilter) {
920
1108
  console.log(` Filter: ${params.ExtraFilter}`);
921
1109
  }
@@ -940,10 +1128,10 @@ class ComponentRunner {
940
1128
  });
941
1129
  await page.exposeFunction('__mjRunViews', async (params) => {
942
1130
  try {
943
- const results = await runView.RunViews(params, contextUser);
1131
+ const results = await util.rv.RunViews(params, options.contextUser);
944
1132
  // Debug logging for successful calls
945
1133
  if (debug) {
946
- console.log(`✅ RunViews SUCCESS: ${params.length} queries executed`);
1134
+ console.log(`💾 RunViews SUCCESS: ${params.length} queries executed`);
947
1135
  params.forEach((p, i) => {
948
1136
  const rowCount = results[i]?.Results?.length || 0;
949
1137
  console.log(` [${i + 1}] Entity="${p.EntityName}" Rows=${rowCount}`);
@@ -970,12 +1158,12 @@ class ComponentRunner {
970
1158
  });
971
1159
  await page.exposeFunction('__mjRunQuery', async (params) => {
972
1160
  try {
973
- const result = await runQuery.RunQuery(params, contextUser);
1161
+ const result = await util.rq.RunQuery(params, options.contextUser);
974
1162
  // Debug logging for successful calls
975
1163
  if (debug) {
976
1164
  const queryIdentifier = params.QueryName || params.QueryID || 'unknown';
977
1165
  const rowCount = result.Results?.length || 0;
978
- console.log(`✅ RunQuery SUCCESS: Query="${queryIdentifier}" Rows=${rowCount}`);
1166
+ console.log(`💾 RunQuery SUCCESS: Query="${queryIdentifier}" Rows=${rowCount}`);
979
1167
  if (params.Parameters && Object.keys(params.Parameters).length > 0) {
980
1168
  console.log(` Parameters:`, params.Parameters);
981
1169
  }
@@ -999,10 +1187,69 @@ class ComponentRunner {
999
1187
  return { Success: false, ErrorMessage: errorMessage, Results: [] };
1000
1188
  }
1001
1189
  });
1190
+ // Expose AI tools
1191
+ await page.exposeFunction('__mjExecutePrompt', async (params) => {
1192
+ try {
1193
+ if (!util.ai) {
1194
+ throw new Error('AI tools not available');
1195
+ }
1196
+ // Add contextUser to params if not provided
1197
+ const paramsWithUser = { ...params, contextUser: options.contextUser };
1198
+ const result = await util.ai.ExecutePrompt(paramsWithUser);
1199
+ if (debug && result.success) {
1200
+ console.log(`🤖 ExecutePrompt SUCCESS: Model="${result.modelName}"`);
1201
+ }
1202
+ return result;
1203
+ }
1204
+ catch (error) {
1205
+ const errorMessage = error instanceof Error ? error.message : String(error);
1206
+ if (debug) {
1207
+ console.log(`❌ ExecutePrompt FAILED: ${errorMessage}`);
1208
+ }
1209
+ dataErrors.push(`AI ExecutePrompt error: ${errorMessage}`);
1210
+ return {
1211
+ success: false,
1212
+ result: errorMessage,
1213
+ modelName: ''
1214
+ };
1215
+ }
1216
+ });
1217
+ await page.exposeFunction('__mjEmbedText', async (params) => {
1218
+ try {
1219
+ if (!util.ai) {
1220
+ throw new Error('AI tools not available');
1221
+ }
1222
+ // Add contextUser to params if not provided
1223
+ const paramsWithUser = { ...params, contextUser: options.contextUser };
1224
+ const result = await util.ai.EmbedText(paramsWithUser);
1225
+ if (debug) {
1226
+ const count = Array.isArray(result.result)
1227
+ ? (Array.isArray(result.result[0]) ? result.result.length : 1)
1228
+ : 1;
1229
+ console.log(`🤖 EmbedText SUCCESS: Model="${result.modelName}" Count=${count} Dims=${result.vectorDimensions}`);
1230
+ }
1231
+ return result;
1232
+ }
1233
+ catch (error) {
1234
+ const errorMessage = error instanceof Error ? error.message : String(error);
1235
+ if (debug) {
1236
+ console.log(`❌ EmbedText FAILED: ${errorMessage}`);
1237
+ }
1238
+ dataErrors.push(`AI EmbedText error: ${errorMessage}`);
1239
+ throw error; // Re-throw for embeddings as they're critical
1240
+ }
1241
+ });
1002
1242
  // Make them available in utilities with the mock metadata
1003
1243
  await page.evaluate(() => {
1004
1244
  // Use the mock metadata for synchronous access
1005
1245
  const mockMd = window.__mjMockMetadata || { Entities: [], CurrentUser: null };
1246
+ // Import SimpleVectorService for use in browser
1247
+ // Note: This will be available as part of the runtime bundle
1248
+ const VectorService = window.MJReactRuntime?.SimpleVectorService ||
1249
+ class {
1250
+ // Stub implementation if not available
1251
+ cosineSimilarity(_a, _b) { return 0; }
1252
+ };
1006
1253
  window.__mjUtilities = {
1007
1254
  md: {
1008
1255
  // Use the mock metadata's Entities directly (synchronous)
@@ -1021,6 +1268,11 @@ class ComponentRunner {
1021
1268
  },
1022
1269
  rq: {
1023
1270
  RunQuery: async (params) => await window.__mjRunQuery(params)
1271
+ },
1272
+ ai: {
1273
+ ExecutePrompt: async (params) => await window.__mjExecutePrompt(params),
1274
+ EmbedText: async (params) => await window.__mjEmbedText(params),
1275
+ VectorService: new VectorService()
1024
1276
  }
1025
1277
  };
1026
1278
  // Update or create global Metadata mock (in case it wasn't created earlier)
@@ -1078,5 +1330,8 @@ ComponentRunner.CRITICAL_WARNING_PATTERNS = [
1078
1330
  /Error: Minified React error/i,
1079
1331
  /too many re-renders/i,
1080
1332
  ];
1081
- ComponentRunner.MAX_RENDER_COUNT = 1000;
1333
+ // Note: This counts React.createElement calls, not component re-renders
1334
+ // A complex dashboard can easily have 5000+ createElement calls on initial mount
1335
+ // Only flag if it's likely an infinite loop (10000+ is suspicious)
1336
+ ComponentRunner.MAX_RENDER_COUNT = 10000;
1082
1337
  //# sourceMappingURL=component-runner.js.map