@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
JavaScript
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">
(mode === 'agent' && agent?.LogoURL) {
<img [src]="agent?.LogoURL" class="title-logo" alt="Agent logo" />
} if (mode === 'agent') {
<i class="fa-solid fa-robot title-icon"></i>
} {
<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">
(loading) {
<div class="loading-container">
<kendo-loader type="converging-spinner" size="large"></kendo-loader>
<p>Loading {{mode === 'agent' ? 'AI Agent' : 'AI Prompt'}}...</p>
</div>
}
if (error) {
<div class="error-container">
<i class="fa-solid fa-exclamation-triangle"></i>
<p>{{ error }}</p>
</div>
}
{
<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