UNPKG

@memberjunction/ng-ai-test-harness

Version:

MemberJunction AI Test Harness - A reusable component for testing AI agents and prompts with beautiful UX

528 lines (526 loc) 30.9 kB
import { Component, Input, Output, EventEmitter, ViewChild } from '@angular/core'; import { Metadata } from '@memberjunction/core'; import { AITestHarnessComponent } from './ai-test-harness.component'; import * as i0 from "@angular/core"; import * as i1 from "@progress/kendo-angular-dialog"; import * as i2 from "@progress/kendo-angular-indicators"; import * as i3 from "./ai-test-harness.component"; const _c0 = ["kendoWindow"]; function TestHarnessCustomWindowComponent_Conditional_4_Template(rf, ctx) { if (rf & 1) { i0.ɵɵelement(0, "img", 3); } if (rf & 2) { const ctx_r1 = i0.ɵɵnextContext(); i0.ɵɵproperty("src", ctx_r1.agent == null ? null : ctx_r1.agent.LogoURL, i0.ɵɵsanitizeUrl); } } function TestHarnessCustomWindowComponent_Conditional_5_Template(rf, ctx) { if (rf & 1) { i0.ɵɵelement(0, "i", 4); } } function TestHarnessCustomWindowComponent_Conditional_6_Template(rf, ctx) { if (rf & 1) { i0.ɵɵelement(0, "i", 5); } } function TestHarnessCustomWindowComponent_Conditional_17_Template(rf, ctx) { if (rf & 1) { i0.ɵɵelementStart(0, "div", 14); i0.ɵɵelement(1, "kendo-loader", 17); i0.ɵɵelementStart(2, "p"); i0.ɵɵtext(3); i0.ɵɵelementEnd()(); } if (rf & 2) { const ctx_r1 = i0.ɵɵnextContext(); i0.ɵɵadvance(3); i0.ɵɵtextInterpolate1("Loading ", ctx_r1.mode === "agent" ? "AI Agent" : "AI Prompt", "..."); } } function TestHarnessCustomWindowComponent_Conditional_18_Template(rf, ctx) { if (rf & 1) { i0.ɵɵelementStart(0, "div", 15); i0.ɵɵelement(1, "i", 18); i0.ɵɵelementStart(2, "p"); i0.ɵɵtext(3); i0.ɵɵelementEnd()(); } if (rf & 2) { const ctx_r1 = i0.ɵɵnextContext(); i0.ɵɵadvance(3); i0.ɵɵtextInterpolate(ctx_r1.error); } } function TestHarnessCustomWindowComponent_Conditional_19_Template(rf, ctx) { if (rf & 1) { const _r3 = i0.ɵɵgetCurrentView(); i0.ɵɵelementStart(0, "mj-ai-test-harness", 19); i0.ɵɵlistener("runOpened", function TestHarnessCustomWindowComponent_Conditional_19_Template_mj_ai_test_harness_runOpened_0_listener($event) { i0.ɵɵrestoreView(_r3); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onRunOpened($event)); }); i0.ɵɵelementEnd(); } if (rf & 2) { const ctx_r1 = i0.ɵɵnextContext(); i0.ɵɵproperty("entity", ctx_r1.agent || ctx_r1.prompt || null)("mode", ctx_r1.mode)("isVisible", true); } } export class TestHarnessCustomWindowComponent { constructor(renderer, elementRef, cdr) { this.renderer = renderer; this.elementRef = elementRef; this.cdr = cdr; this.data = {}; this.closeWindow = new EventEmitter(); this.minimizeWindow = new EventEmitter(); this.restoreWindow = new EventEmitter(); this.executionStateChange = new EventEmitter(); this.windowTitle = 'AI Test Harness'; this.width = 1200; this.height = 800; this.windowTop = 100; this.windowLeft = 100; this.loading = true; this.error = ''; this.windowState = 'default'; this.isMaximized = false; this.isMinimized = false; // Store original dimensions for restore this.originalWidth = 1200; this.originalHeight = 800; this.originalTop = 100; this.originalLeft = 100; // Minimized dimensions this.MINIMIZED_WIDTH = 400; this.MINIMIZED_HEIGHT = 60; this.mode = 'agent'; this.metadata = new Metadata(); } ngOnInit() { // Set window dimensions this.width = this.convertToNumber(this.data.width) || 1200; this.height = this.convertToNumber(this.data.height) || 800; // Store original dimensions this.originalWidth = this.width; this.originalHeight = this.height; // Calculate centered position const windowWidth = window.innerWidth; const windowHeight = window.innerHeight; this.windowLeft = Math.max(0, (windowWidth - this.width) / 2); this.windowTop = Math.max(0, (windowHeight - this.height) / 2); // Store original position this.originalLeft = this.windowLeft; this.originalTop = this.windowTop; // Determine mode this.mode = this.data.mode || (this.data.promptId || this.data.prompt ? 'prompt' : 'agent'); // Load entity this.loadEntity(); } async loadEntity() { try { if (this.mode === 'agent') { if (this.data.agent) { this.agent = this.data.agent; this.windowTitle = this.data.title || `Test: ${this.agent.Name}`; } else if (this.data.agentId) { const agentEntity = await this.metadata.GetEntityObject('AI Agents'); await agentEntity.Load(this.data.agentId); if (agentEntity.IsSaved) { this.agent = agentEntity; this.windowTitle = this.data.title || `Test: ${this.agent.Name}`; } else { throw new Error('Agent not found'); } } else { throw new Error('No agent provided'); } } else { if (this.data.prompt) { this.prompt = this.data.prompt; this.windowTitle = this.data.title || `Test: ${this.prompt.Name}`; } else if (this.data.promptId) { const promptEntity = await this.metadata.GetEntityObject('AI Prompts'); await promptEntity.Load(this.data.promptId); if (promptEntity.IsSaved) { this.prompt = promptEntity; this.windowTitle = this.data.title || `Test: ${this.prompt.Name}`; } else { throw new Error('Prompt not found'); } } else { throw new Error('No prompt provided'); } } this.loading = false; } catch (err) { this.error = err.message || 'Failed to load entity'; this.loading = false; } } onClose() { // When the Kendo Window's close event fires (from the default X button), // emit our closeWindow event to notify the window manager this.closeWindow.emit(); } closeButtonClick() { // When our custom close button is clicked, emit the close event // This will trigger the window to close and call our onClose handler if (this.kendoWindow) { this.kendoWindow.close.emit(); } else { // If kendoWindow is not available, directly emit the close event this.onClose(); } } onStateChange(state) { this.windowState = state; this.isMaximized = state === 'maximized'; this.isMinimized = state === 'minimized'; // Handle restore from minimized if (this.isMinimized && state !== 'minimized') { this.restoreFromMinimized(); } // Adjust content height on any state change if (state !== 'minimized') { setTimeout(() => this.adjustContentHeight(), 100); } } onWindowResize() { // Handle Kendo Window resize event if (!this.isMinimized) { // Use a small delay to ensure the DOM has updated setTimeout(() => this.adjustContentHeight(), 50); } } onRunOpened(event) { // Auto-minimize the test harness window when a run is opened this.minimize(); } minimize() { if (!this.isMinimized) { // Store current dimensions before minimizing this.originalWidth = this.width; this.originalHeight = this.height; this.originalTop = this.windowTop; this.originalLeft = this.windowLeft; // Hide the window when minimized (dock will show icon) this.isMinimized = true; this.minimizeWindow.emit(); // Hide the window element let windowElement = this.elementRef.nativeElement.querySelector('.k-window'); if (!windowElement && this.elementRef.nativeElement.closest) { windowElement = this.elementRef.nativeElement.closest('.k-window'); } if (windowElement) { this.renderer.setStyle(windowElement, 'display', 'none'); } } } restoreFromMinimized() { this.width = this.originalWidth; this.height = this.originalHeight; this.windowTop = this.originalTop; this.windowLeft = this.originalLeft; this.windowState = 'default'; this.isMinimized = false; // Show the window element let windowElement = this.elementRef.nativeElement.querySelector('.k-window'); if (!windowElement && this.elementRef.nativeElement.closest) { windowElement = this.elementRef.nativeElement.closest('.k-window'); } if (windowElement) { this.renderer.setStyle(windowElement, 'display', 'block'); } // Update position after state change setTimeout(() => { this.updateWindowPosition(); // Force Angular change detection this.cdr.detectChanges(); // Force the Kendo Window to recalculate its internal dimensions if (this.kendoWindow) { // Trigger resize event to fix content sizing window.dispatchEvent(new Event('resize')); // Use the shared method to adjust content height this.adjustContentHeight(); } }, 100); // Emit restore event this.restoreWindow.emit(); } toggleMaximize() { if (this.isMinimized) { // First restore from minimized, then maximize this.restoreFromMinimized(); setTimeout(() => { this.windowState = 'maximized'; this.isMaximized = true; }, 600); } else { this.windowState = this.isMaximized ? 'default' : 'maximized'; this.isMaximized = !this.isMaximized; } } convertToNumber(value) { if (!value) return undefined; if (typeof value === 'number') return value; // Handle percentage values if (value.endsWith('vw') || value.endsWith('vh')) { const percentage = parseFloat(value) / 100; if (value.endsWith('vw')) { return window.innerWidth * percentage; } else { return window.innerHeight * percentage; } } // Handle pixel values if (value.endsWith('px')) { return parseFloat(value); } // Try to parse as number const parsed = parseFloat(value); return isNaN(parsed) ? undefined : parsed; } ngAfterViewInit() { // Set initial position if needed setTimeout(() => { this.updateWindowPosition(); this.adjustContentHeight(); }, 100); // Set up execution tracking this.setupExecutionTracking(); // Set up window resize listener this.setupResizeListener(); } adjustContentHeight() { // Ensure the window content wrapper has proper height const contentWrapper = this.elementRef.nativeElement.querySelector('.k-window-content'); if (contentWrapper && this.kendoWindow) { // Get the actual window element to check current dimensions const windowElement = this.elementRef.nativeElement.querySelector('.k-window') || this.elementRef.nativeElement.closest('.k-window'); // Update our tracked dimensions if the window has been resized if (windowElement) { const rect = windowElement.getBoundingClientRect(); if (rect.width > 0) this.width = rect.width; if (rect.height > 0) this.height = rect.height; } // Calculate the actual content height (window height minus titlebar and some padding) const windowHeight = this.height; const titlebarHeight = 40; // Titlebar height const bottomPadding = 10; // Extra space to prevent clipping const contentHeight = windowHeight - titlebarHeight - bottomPadding; this.renderer.setStyle(contentWrapper, 'height', `${contentHeight}px`); this.renderer.setStyle(contentWrapper, 'display', 'flex'); this.renderer.setStyle(contentWrapper, 'flex-direction', 'column'); } // Also update the test harness container const testHarnessContainer = this.elementRef.nativeElement.querySelector('mj-ai-test-harness'); if (testHarnessContainer) { this.renderer.setStyle(testHarnessContainer, 'flex', '1'); this.renderer.setStyle(testHarnessContainer, 'height', '100%'); this.renderer.setStyle(testHarnessContainer, 'overflow', 'hidden'); } } setupResizeListener() { // Debounced resize handler let resizeTimeout; const handleResize = () => { clearTimeout(resizeTimeout); resizeTimeout = setTimeout(() => { if (!this.isMinimized) { this.adjustContentHeight(); } }, 100); }; // Listen for window resize events window.addEventListener('resize', handleResize); // Store the handler for cleanup this.resizeHandler = handleResize; // Note: Kendo Window resize events are handled by the (resize) event binding in the template } setupExecutionTracking() { // Use a timer to check the test harness execution state // This is needed because the test harness doesn't emit events for execution state changes if (this.testHarness) { // Initial state let lastExecutingState = false; // Check execution state periodically const checkInterval = setInterval(() => { if (this.testHarness && this.testHarness.isExecuting !== lastExecutingState) { lastExecutingState = this.testHarness.isExecuting; this.executionStateChange.emit({ isExecuting: lastExecutingState }); } }, 100); // Store interval for cleanup this.executionCheckInterval = checkInterval; } } ngOnDestroy() { // Clean up execution tracking interval if (this.executionCheckInterval) { clearInterval(this.executionCheckInterval); } // Clean up resize listener if (this.resizeHandler) { window.removeEventListener('resize', this.resizeHandler); } // Ensure window is properly closed and cleaned up if (this.kendoWindow) { this.kendoWindow.close.emit(); } } updateWindowPosition() { // Find the window element - it might be in the parent if we're inside a wrapper let windowElement = this.elementRef.nativeElement.querySelector('.k-window'); if (!windowElement && this.elementRef.nativeElement.closest) { windowElement = this.elementRef.nativeElement.closest('.k-window'); } if (windowElement && (this.windowTop !== undefined || this.windowLeft !== undefined)) { if (this.windowTop !== undefined) { this.renderer.setStyle(windowElement, 'top', `${this.windowTop}px`); } if (this.windowLeft !== undefined) { this.renderer.setStyle(windowElement, 'left', `${this.windowLeft}px`); } } } static { this.ɵfac = function TestHarnessCustomWindowComponent_Factory(t) { return new (t || TestHarnessCustomWindowComponent)(i0.ɵɵdirectiveInject(i0.Renderer2), i0.ɵɵdirectiveInject(i0.ElementRef), i0.ɵɵdirectiveInject(i0.ChangeDetectorRef)); }; } static { this.ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: TestHarnessCustomWindowComponent, selectors: [["mj-test-harness-custom-window"]], viewQuery: function TestHarnessCustomWindowComponent_Query(rf, ctx) { if (rf & 1) { i0.ɵɵviewQuery(_c0, 5); i0.ɵɵviewQuery(AITestHarnessComponent, 5); } if (rf & 2) { let _t; i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.kendoWindow = _t.first); i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.testHarness = _t.first); } }, inputs: { data: "data" }, outputs: { closeWindow: "closeWindow", minimizeWindow: "minimizeWindow", restoreWindow: "restoreWindow", executionStateChange: "executionStateChange" }, decls: 20, vars: 20, consts: [["kendoWindow", ""], [3, "close", "stateChange", "resize", "title", "width", "height", "top", "left", "minWidth", "minHeight", "draggable", "resizable", "state"], [1, "window-title"], ["alt", "Agent logo", 1, "title-logo", 3, "src"], [1, "fa-solid", "fa-robot", "title-icon"], [1, "fa-solid", "fa-comment-dots", "title-icon"], [1, "window-actions"], ["title", "Minimize", 1, "window-action-btn", 3, "click"], [1, "fa-solid", "fa-window-minimize"], [1, "window-action-btn", 3, "click", "title"], [1, "fa-solid"], ["title", "Close", 1, "window-action-btn", 3, "click"], [1, "fa-solid", "fa-xmark"], [1, "window-content"], [1, "loading-container"], [1, "error-container"], [3, "entity", "mode", "isVisible"], ["type", "converging-spinner", "size", "large"], [1, "fa-solid", "fa-exclamation-triangle"], [3, "runOpened", "entity", "mode", "isVisible"]], template: function TestHarnessCustomWindowComponent_Template(rf, ctx) { if (rf & 1) { const _r1 = i0.ɵɵgetCurrentView(); i0.ɵɵelementStart(0, "kendo-window", 1, 0); i0.ɵɵlistener("close", function TestHarnessCustomWindowComponent_Template_kendo_window_close_0_listener() { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.onClose()); })("stateChange", function TestHarnessCustomWindowComponent_Template_kendo_window_stateChange_0_listener($event) { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.onStateChange($event)); })("resize", function TestHarnessCustomWindowComponent_Template_kendo_window_resize_0_listener() { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.onWindowResize()); }); i0.ɵɵelementStart(2, "kendo-window-titlebar")(3, "div", 2); i0.ɵɵtemplate(4, TestHarnessCustomWindowComponent_Conditional_4_Template, 1, 1, "img", 3)(5, TestHarnessCustomWindowComponent_Conditional_5_Template, 1, 0, "i", 4)(6, TestHarnessCustomWindowComponent_Conditional_6_Template, 1, 0, "i", 5); i0.ɵɵelementStart(7, "span"); i0.ɵɵtext(8); i0.ɵɵelementEnd()(); i0.ɵɵelementStart(9, "div", 6)(10, "button", 7); i0.ɵɵlistener("click", function TestHarnessCustomWindowComponent_Template_button_click_10_listener() { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.minimize()); }); i0.ɵɵelement(11, "i", 8); i0.ɵɵelementEnd(); i0.ɵɵelementStart(12, "button", 9); i0.ɵɵlistener("click", function TestHarnessCustomWindowComponent_Template_button_click_12_listener() { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.toggleMaximize()); }); i0.ɵɵelement(13, "i", 10); i0.ɵɵelementEnd(); i0.ɵɵelementStart(14, "button", 11); i0.ɵɵlistener("click", function TestHarnessCustomWindowComponent_Template_button_click_14_listener() { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.closeButtonClick()); }); i0.ɵɵelement(15, "i", 12); i0.ɵɵelementEnd()()(); i0.ɵɵelementStart(16, "div", 13); i0.ɵɵtemplate(17, TestHarnessCustomWindowComponent_Conditional_17_Template, 4, 1, "div", 14)(18, TestHarnessCustomWindowComponent_Conditional_18_Template, 4, 1, "div", 15)(19, TestHarnessCustomWindowComponent_Conditional_19_Template, 1, 3, "mj-ai-test-harness", 16); i0.ɵɵelementEnd()(); } if (rf & 2) { i0.ɵɵclassProp("minimized-window", ctx.isMinimized); i0.ɵɵproperty("title", ctx.windowTitle)("width", ctx.width)("height", ctx.height)("top", ctx.windowTop)("left", ctx.windowLeft)("minWidth", ctx.isMinimized ? 400 : 800)("minHeight", ctx.isMinimized ? 60 : 600)("draggable", true)("resizable", !ctx.isMinimized)("state", ctx.windowState); i0.ɵɵadvance(4); i0.ɵɵconditional(ctx.mode === "agent" && (ctx.agent == null ? null : ctx.agent.LogoURL) ? 4 : ctx.mode === "agent" ? 5 : 6); i0.ɵɵadvance(4); i0.ɵɵtextInterpolate(ctx.windowTitle); i0.ɵɵadvance(4); i0.ɵɵproperty("title", ctx.isMaximized ? "Restore" : "Maximize"); i0.ɵɵadvance(); i0.ɵɵclassProp("fa-window-maximize", !ctx.isMaximized)("fa-window-restore", ctx.isMaximized); i0.ɵɵadvance(4); i0.ɵɵconditional(ctx.loading ? 17 : ctx.error ? 18 : 19); } }, dependencies: [i1.WindowComponent, i1.WindowTitleBarComponent, i2.LoaderComponent, i3.AITestHarnessComponent], styles: ["[_nghost-%COMP%] {\n display: contents;\n }\n \n \n\n\n\n\n \n .minimized-window {\n .k-window-content {\n display: none !important;\n }\n \n .k-window-titlebar {\n border-radius: 4px !important;\n }\n }\n \n .window-title[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n flex: 1;\n \n .title-icon {\n color: #666;\n font-size: 16px;\n }\n \n .title-logo {\n width: 20px;\n height: 20px;\n object-fit: contain;\n }\n }\n \n .window-actions[_ngcontent-%COMP%] {\n display: flex;\n gap: 4px;\n align-items: center;\n }\n \n .window-action-btn[_ngcontent-%COMP%] {\n background: transparent;\n border: none;\n padding: 8px;\n cursor: pointer;\n border-radius: 4px;\n transition: background-color 0.2s;\n display: flex;\n align-items: center;\n justify-content: center;\n \n &:hover {\n background-color: rgba(0, 0, 0, 0.08);\n }\n \n i {\n font-size: 14px;\n color: #666;\n }\n }\n \n .window-content[_ngcontent-%COMP%] {\n height: 100%;\n display: flex;\n flex-direction: column;\n }\n \n .loading-container[_ngcontent-%COMP%], \n .error-container[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n gap: 1rem;\n }\n \n .error-container[_ngcontent-%COMP%] {\n color: #dc3545;\n \n i {\n font-size: 3rem;\n }\n }\n \n mj-ai-test-harness[_ngcontent-%COMP%] {\n flex: 1;\n overflow: hidden;\n }\n \n \n\n .k-window-content {\n display: flex;\n flex-direction: column;\n }"] }); } } (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(TestHarnessCustomWindowComponent, [{ type: Component, args: [{ selector: 'mj-test-harness-custom-window', template: ` <kendo-window #kendoWindow [title]="windowTitle" [width]="width" [height]="height" [top]="windowTop" [left]="windowLeft" [minWidth]="isMinimized ? 400 : 800" [minHeight]="isMinimized ? 60 : 600" [draggable]="true" [resizable]="!isMinimized" [state]="windowState" [class.minimized-window]="isMinimized" (close)="onClose()" (stateChange)="onStateChange($event)" (resize)="onWindowResize()"> <kendo-window-titlebar> <div class="window-title"> @if (mode === 'agent' && agent?.LogoURL) { <img [src]="agent?.LogoURL" class="title-logo" alt="Agent logo" /> } @else if (mode === 'agent') { <i class="fa-solid fa-robot title-icon"></i> } @else { <i class="fa-solid fa-comment-dots title-icon"></i> } <span>{{ windowTitle }}</span> </div> <div class="window-actions"> <button (click)="minimize()" title="Minimize" class="window-action-btn"> <i class="fa-solid fa-window-minimize"></i> </button> <button (click)="toggleMaximize()" [title]="isMaximized ? 'Restore' : 'Maximize'" class="window-action-btn"> <i class="fa-solid" [class.fa-window-maximize]="!isMaximized" [class.fa-window-restore]="isMaximized"></i> </button> <button (click)="closeButtonClick()" title="Close" class="window-action-btn"> <i class="fa-solid fa-xmark"></i> </button> </div> </kendo-window-titlebar> <div class="window-content"> @if (loading) { <div class="loading-container"> <kendo-loader type="converging-spinner" size="large"></kendo-loader> <p>Loading {{mode === 'agent' ? 'AI Agent' : 'AI Prompt'}}...</p> </div> } @else if (error) { <div class="error-container"> <i class="fa-solid fa-exclamation-triangle"></i> <p>{{ error }}</p> </div> } @else { <mj-ai-test-harness [entity]="(agent || prompt) || null" [mode]="mode" [isVisible]="true" (runOpened)="onRunOpened($event)"> </mj-ai-test-harness> } </div> </kendo-window> `, styles: ["\n :host {\n display: contents;\n }\n \n /* Remove animation for snappy performance\n ::ng-deep .window-transition {\n transition: all 0.6s ease-in-out !important;\n } */\n \n ::ng-deep .minimized-window {\n .k-window-content {\n display: none !important;\n }\n \n .k-window-titlebar {\n border-radius: 4px !important;\n }\n }\n \n .window-title {\n display: flex;\n align-items: center;\n gap: 8px;\n flex: 1;\n \n .title-icon {\n color: #666;\n font-size: 16px;\n }\n \n .title-logo {\n width: 20px;\n height: 20px;\n object-fit: contain;\n }\n }\n \n .window-actions {\n display: flex;\n gap: 4px;\n align-items: center;\n }\n \n .window-action-btn {\n background: transparent;\n border: none;\n padding: 8px;\n cursor: pointer;\n border-radius: 4px;\n transition: background-color 0.2s;\n display: flex;\n align-items: center;\n justify-content: center;\n \n &:hover {\n background-color: rgba(0, 0, 0, 0.08);\n }\n \n i {\n font-size: 14px;\n color: #666;\n }\n }\n \n .window-content {\n height: 100%;\n display: flex;\n flex-direction: column;\n }\n \n .loading-container,\n .error-container {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n gap: 1rem;\n }\n \n .error-container {\n color: #dc3545;\n \n i {\n font-size: 3rem;\n }\n }\n \n mj-ai-test-harness {\n flex: 1;\n overflow: hidden;\n }\n \n /* Ensure Kendo Window content wrapper maintains proper height */\n ::ng-deep .k-window-content {\n display: flex;\n flex-direction: column;\n }\n "] }] }], () => [{ type: i0.Renderer2 }, { type: i0.ElementRef }, { type: i0.ChangeDetectorRef }], { kendoWindow: [{ type: ViewChild, args: ['kendoWindow', { static: false }] }], testHarness: [{ type: ViewChild, args: [AITestHarnessComponent, { static: false }] }], data: [{ type: Input }], closeWindow: [{ type: Output }], minimizeWindow: [{ type: Output }], restoreWindow: [{ type: Output }], executionStateChange: [{ type: Output }] }); })(); (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(TestHarnessCustomWindowComponent, { className: "TestHarnessCustomWindowComponent", filePath: "lib/test-harness-custom-window.component.ts", lineNumber: 200 }); })(); //# sourceMappingURL=test-harness-custom-window.component.js.map