@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.
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/component-linter.d.ts +31 -1
- package/dist/lib/component-linter.d.ts.map +1 -1
- package/dist/lib/component-linter.js +2552 -483
- package/dist/lib/component-linter.js.map +1 -1
- package/dist/lib/component-runner.d.ts +5 -2
- package/dist/lib/component-runner.d.ts.map +1 -1
- package/dist/lib/component-runner.js +305 -50
- package/dist/lib/component-runner.js.map +1 -1
- package/dist/lib/library-lint-cache.d.ts +46 -0
- package/dist/lib/library-lint-cache.d.ts.map +1 -0
- package/dist/lib/library-lint-cache.js +119 -0
- package/dist/lib/library-lint-cache.js.map +1 -0
- package/dist/lib/linter-test-tool.d.ts +42 -0
- package/dist/lib/linter-test-tool.d.ts.map +1 -0
- package/dist/lib/linter-test-tool.js +272 -0
- package/dist/lib/linter-test-tool.js.map +1 -0
- package/dist/lib/test-harness.d.ts.map +1 -1
- package/dist/lib/test-harness.js +1 -1
- package/dist/lib/test-harness.js.map +1 -1
- package/package.json +3 -3
|
@@ -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,
|
|
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
|
|
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(
|
|
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}
|
|
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('
|
|
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
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
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('
|
|
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(
|
|
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
|
|
291
|
-
|
|
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: `
|
|
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(`
|
|
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
|
-
|
|
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
|
-
|
|
346
|
-
|
|
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
|
-
//
|
|
353
|
-
|
|
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(`
|
|
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(`
|
|
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(`
|
|
673
|
+
console.log(` 📦 Loaded ${specLib.name} (available as ${libDef.GlobalVariable})`);
|
|
644
674
|
}
|
|
645
675
|
}
|
|
646
676
|
else {
|
|
647
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
//
|
|
832
|
-
const
|
|
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 (
|
|
1028
|
+
if (util.md?.Entities) {
|
|
841
1029
|
// Serialize the entities data (remove functions, keep data)
|
|
842
|
-
entitiesData = JSON.parse(JSON.stringify(
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
1131
|
+
const results = await util.rv.RunViews(params, options.contextUser);
|
|
944
1132
|
// Debug logging for successful calls
|
|
945
1133
|
if (debug) {
|
|
946
|
-
console.log(
|
|
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
|
|
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(
|
|
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
|
-
|
|
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
|