@memberjunction/ng-ai-test-harness
Version:
MemberJunction AI Test Harness - A reusable component for testing AI agents and prompts with beautiful UX
711 lines • 28.7 kB
TypeScript
import { EventEmitter, OnInit, OnDestroy, OnChanges, SimpleChanges, AfterViewChecked, ChangeDetectorRef } from '@angular/core';
import { BaseAngularComponent } from '@memberjunction/ng-base-types';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
import { MJAIAgentEntityExtended, MJAIPromptEntityExtended, MJAIAgentRunEntityExtended, MJAIAgentRunStepEntityExtended } from "@memberjunction/ai-core-plus";
import { MJAIConfigurationEntity } from '@memberjunction/core-entities';
import { ChatMessage } from '@memberjunction/ai';
import * as i0 from "@angular/core";
/**
* Supported modes for the test harness
*/
export type TestHarnessMode = 'agent' | 'prompt';
/**
* Result interface for AI agent execution operations.
* Contains comprehensive information about the execution outcome, timing, and data.
*/
/**
* Represents a variable in the data context or template data for agent execution.
* Used to pass dynamic data to agents during testing and execution.
*/
export interface DataContextVariable {
/** Variable name used in agent prompts and templates */
name: string;
/** String representation of the variable value */
value: string;
/** Data type for proper conversion during execution */
type: 'string' | 'number' | 'boolean' | 'object';
}
/**
* Enhanced chat message interface extending the base ChatMessage from @memberjunction/ai.
* Includes additional properties for streaming, timing, error handling, and content management.
*/
export interface ConversationMessage extends ChatMessage {
/** Unique identifier for this message */
id: string;
/** Timestamp when the message was created */
timestamp: Date;
/** Whether this message is currently being streamed */
isStreaming?: boolean;
/** Accumulated content during streaming (temporary) */
streamingContent?: string;
/** Total execution time for AI-generated messages */
executionTime?: number;
/** Associated agent run ID for tracking */
agentRunId?: string;
/** Error message if processing failed */
error?: string;
/** Original unprocessed content from the AI model */
rawContent?: string;
/** Whether to display raw content instead of processed content */
showRaw?: boolean;
/** Timestamp when streaming started (for elapsed time calculation) */
streamingStartTime?: number;
/** Current elapsed time during streaming */
elapsedTime?: number;
/** Whether JSON raw section is expanded in collapsible view */
showJsonRaw?: boolean;
/** Payload data from agent execution to display separately */
payload?: any;
/** Whether the payload section is collapsed */
payloadCollapsed?: boolean;
/** Execution data for agent runs */
executionData?: any;
}
/**
* Represents a saved conversation session with complete state information.
* Includes all messages, context data, and metadata for restoration.
*/
export interface SavedConversation {
/** Unique identifier for the saved conversation */
id: string;
/** User-provided name for the conversation */
name: string;
/** ID of the agent used in this conversation */
agentId: string;
/** Name of the agent for display purposes */
agentName: string;
/** Complete message history */
messages: ConversationMessage[];
/** Data context variables used during the conversation */
dataContext: Record<string, any>;
/** Template data variables used during the conversation (agent mode) */
templateData: Record<string, any>;
/** Template variables used during the conversation (prompt mode) */
templateVariables?: Record<string, any>;
/** Advanced parameters used during the conversation (prompt mode) */
advancedParams?: any;
/** Selected model ID (prompt mode) */
selectedModelId?: string;
/** Selected vendor ID (prompt mode) */
selectedVendorId?: string;
/** Selected configuration ID (prompt mode) */
selectedConfigurationId?: string;
/** Skip validation setting (prompt mode) */
skipValidation?: boolean;
/** Selected configuration ID (agent mode) */
agentConfigurationId?: string;
/** When the conversation was first created */
createdAt: Date;
/** When the conversation was last modified */
updatedAt: Date;
}
/**
* Comprehensive test harness component for AI Agent development and testing.
* Provides a full-featured chat interface with conversation management, data context configuration,
* template data management, streaming responses, conversation persistence, and import/export capabilities.
*
* ## Key Features:
* - **Interactive Chat**: Real-time conversation with AI agents
* - **Streaming Support**: Live streaming of agent responses with elapsed time tracking
* - **Data Context Management**: Configure variables passed to agent during execution
* - **Template Data Management**: Manage template variables for agent prompts
* - **Conversation Persistence**: Save/load conversations with full state restoration
* - **Import/Export**: JSON-based conversation backup and sharing
* - **Content Formatting**: Automatic detection and rendering of Markdown, JSON, and plain text
* - **Raw Content Toggle**: View both processed and raw AI responses
* - **Error Handling**: Comprehensive error display and user feedback
*
* ## Usage:
* ```html
* <mj-ai-agent-test-harness
* [aiAgent]="myAgent"
* [isVisible]="true"
* (visibilityChange)="onVisibilityChanged($event)">
* </mj-ai-agent-test-harness>
* ```
*
* @example
* ```typescript
* // Using with agent entity
* const agent = await metadata.GetEntityObject<MJAIAgentEntityExtended>('MJ: AI Agents');
* await agent.Load('agent-id');
* this.testHarness.aiAgent = agent;
* this.testHarness.isVisible = true;
* ```
*/
export declare class AITestHarnessComponent extends BaseAngularComponent implements OnInit, OnDestroy, OnChanges, AfterViewChecked {
private sanitizer;
private cdr;
/**
* Creates a new AI Test Harness component instance.
* @param sanitizer - Angular DomSanitizer for safe HTML rendering of formatted content
* @param cdr - Angular ChangeDetectorRef for managing change detection
*/
constructor(sanitizer: DomSanitizer, cdr: ChangeDetectorRef);
/** The mode of operation - either 'agent' or 'prompt' */
mode: TestHarnessMode;
/** The entity to test - either an AI Agent or AI Prompt */
entity: MJAIAgentEntityExtended | MJAIPromptEntityExtended | null;
/** The original prompt run ID when re-running a previous prompt execution */
originalPromptRunId: string | null;
/** The system prompt override to use instead of rendering from template */
systemPromptOverride: string | null;
/** Whether a re-run has been executed (shows Reset button instead of Re-Run) */
hasExecutedRerun: boolean;
/** Original messages from the prompt run for reset functionality */
private originalPromptRunMessages;
/** @deprecated Use 'entity' instead. Kept for backward compatibility. */
get aiAgent(): MJAIAgentEntityExtended | null;
set aiAgent(value: MJAIAgentEntityExtended | null);
private _isVisible;
/**
* Controls the visibility of the test harness. When set to true, automatically resets the harness state.
* This property is typically controlled by parent components or dialog wrappers.
*/
get isVisible(): boolean;
set isVisible(value: boolean);
/** Event emitted when the visibility state changes, allowing parent components to react */
visibilityChange: EventEmitter<boolean>;
/**
* Emitted when the user navigates to view a run (agent or prompt)
*/
runOpened: EventEmitter<{
runId: string;
runType: "agent" | "prompt";
}>;
/**
* Event emitted when the component requests to be minimized (e.g., when navigating to a run)
*/
minimizeRequested: EventEmitter<void>;
/** Reference to the scrollable messages container for auto-scrolling functionality */
private messagesContainer;
/** Reference to the hidden file input element for conversation import functionality */
private fileInput;
/** Reference to the message input textarea */
private messageInput;
/** Reference to the save dialog input */
private saveDialogInput?;
/** Complete array of all messages in the current conversation session */
conversationMessages: ConversationMessage[];
/** Current text input by the user (bound to textarea) */
currentUserMessage: string;
/** Whether an agent execution is currently in progress */
isExecuting: boolean;
/** Unified variables for agent execution (combines data context and template data) */
agentVariables: DataContextVariable[];
/** Variables for prompt template rendering */
templateVariables: DataContextVariable[];
/** Selected AI model for prompt execution */
selectedModelId: string;
/** Selected AI vendor for prompt execution */
selectedVendorId: string;
/** Available AI vendors for the selected model */
availableVendors: any[];
/** Selected AI configuration for prompt execution */
selectedConfigurationId: string;
/** Available AI configurations */
availableConfigurations: MJAIConfigurationEntity[];
/** Default model for the prompt (cached for display) */
private defaultModelName;
/** Maximum tokens for prompt execution */
maxTokens: number | null;
/** Whether to skip validation when running prompts */
skipValidation: boolean;
/** Selected AI configuration for agent execution */
agentConfigurationId: string;
/** Advanced LLM Parameters */
advancedParams: {
temperature: number | null;
topP: number | null;
topK: number | null;
minP: number | null;
frequencyPenalty: number | null;
presencePenalty: number | null;
seed: number | null;
stopSequences: string[];
includeLogProbs: boolean;
topLogProbs: number;
};
/** Raw stop sequences input for textarea */
stopSequencesText: string;
/** Whether advanced parameters panel is expanded */
advancedParamsExpanded: boolean;
/** Available AI models for prompt execution */
availableModels: any[];
/** Available response format options */
protected responseFormatOptions: {
text: string;
value: string;
}[];
/** Selected response format for prompt execution */
selectedResponseFormat: {
text: string;
value: string;
};
/** Whether the configuration sidebar is currently visible */
showSidebar: boolean;
/** Currently active tab in the sidebar */
activeTab: 'agentVariables' | 'executionMonitor' | 'agentSettings' | 'templateVariables' | 'modelSettings' | 'savedConversations';
/** Array of saved conversation sessions loaded from localStorage */
savedConversations: SavedConversation[];
/** ID of the currently active/loaded conversation, if any */
currentConversationId: string | null;
/** Flag to control JSON dialog visibility */
showJsonDialog: boolean;
/** Current JSON content to display in the dialog */
currentJsonContent: string;
/** Whether the JSON viewer window is visible */
showJsonWindow: boolean;
/** Mode for the execution monitor component */
executionMonitorMode: 'live' | 'historical';
/** Current agent run being displayed in execution monitor */
currentAgentRun: MJAIAgentRunEntityExtended | null;
/**
* Tracks agent steps during live execution (deprecated - now using agent run's Steps directly)
*/
liveAgentSteps: MJAIAgentRunStepEntityExtended[];
/** Track the last processed run ID to avoid reprocessing same data */
private lastProcessedRunId;
/** Track the last agent run ID for run chaining */
private lastAgentRunId;
/** Agent conversation state tracking */
private agentConversationState;
private lastAgentPayload;
private subAgentHistory;
/** Whether to show the save conversation dialog */
showSaveDialog: boolean;
/** Name for the new conversation being saved */
newConversationName: string;
/** Temporary name for the dialog input to avoid binding conflicts */
tempConversationName: string;
/** Whether to show the load confirmation dialog */
showLoadConfirmDialog: boolean;
/** Conversation pending to be loaded */
private pendingLoadConversation;
/** Reference to the current message for JSON display */
private currentJsonMessage;
/** Subject for component destruction cleanup */
private destroy$;
/** Flag indicating that auto-scroll is needed on next view check */
private scrollNeeded;
/** Interval handle for elapsed time counter during streaming */
private elapsedTimeInterval;
/** MemberJunction metadata instance for entity operations */
private _metadata;
/** Track if input has been focused to prevent repeated focusing */
private _hasFocused;
/** Subscription to MJGlobal events for streaming updates */
private _mjGlobalEventSub;
/** Direct GraphQL subscription for agent execution streaming */
private _agentStreamSub;
/**
* Component initialization. Loads saved conversations, sets up event subscriptions,
* and initializes the harness to a clean state.
*/
ngOnInit(): Promise<void>;
/**
* Responds to input property changes, particularly when entity is set/changed
*/
ngOnChanges(changes: SimpleChanges): void;
/**
* Component cleanup. Destroys subscriptions and clears any active intervals
* to prevent memory leaks.
*/
ngOnDestroy(): void;
/**
* Post-view check hook that handles auto-scrolling to the bottom of the message container
* when new content is added.
*/
ngAfterViewChecked(): void;
private subscribeToEvents;
/**
* Loads available AI models for prompt execution.
* Only called when the harness is in prompt mode.
*/
private loadAvailableModels;
/** Default model object for the prompt (cached for vendor lookup) */
private defaultModel;
/**
* Gets the default model name for a prompt based on its configuration
*/
private getDefaultModelName;
/**
* Loads the vendors for the default model
*/
private loadDefaultVendor;
/**
* Loads available AI configurations from the database
* Only configurations with 'Active' or 'Preview' status are shown
*/
private loadAvailableConfigurations;
/**
* Handles model selection change and loads available vendors for the selected model
*/
onModelSelectionChange(): void;
/**
* Loads available vendors for the selected model
*/
private loadVendorsForModel;
/**
* Loads default parameter values from the AI prompt entity
*/
private loadPromptDefaults;
/**
* Loads template parameters from the prompt's template and pre-populates
* the template variables with their default values
*/
private loadTemplateParameters;
/**
* Maps template parameter types to variable types
*/
private getVariableTypeFromParamType;
/**
* Resets all model settings to the prompt defaults
*/
resetToPromptDefaults(): void;
/**
* Resets the test harness to its initial state, clearing all conversations,
* variables, and UI state. Called automatically when the harness becomes visible.
*/
resetHarness(): void;
/**
* Starts a new conversation
*/
newConversation(): void;
/**
* Closes the test harness by setting visibility to false and emitting the change event.
* Used by dialog implementations and close buttons.
*/
close(): void;
/**
* Toggles the visibility of the configuration sidebar.
* Allows users to show/hide the data context and conversation management panels.
*/
toggleSidebar(): void;
/**
* Switches to the specified tab in the configuration sidebar.
* @param tab - The tab to activate
*/
selectTab(tab: 'agentVariables' | 'executionMonitor' | 'agentSettings' | 'templateVariables' | 'modelSettings' | 'savedConversations'): void;
/**
* Shows the execution history for a specific message
* @param message - The message to show execution history for
*/
showMessageExecutionHistory(message: ConversationMessage): Promise<void>;
/**
* Loads an agent run entity by ID
* @param runId - The ID of the agent run to load
*/
private loadAgentRun;
private loadAgentRunFromData;
private internalLoadAgenRun;
/**
* Sends the current user message to the AI agent and initiates execution.
* Handles message validation, conversation updates, UI state management, and agent execution.
* Automatically clears the input field and triggers auto-scroll after sending.
*
* @example
* ```typescript
* // Called when user presses Enter or clicks Send button
* await this.sendMessage();
* ```
*/
/**
* Executes a re-run of a previously loaded prompt run.
* This bypasses the need for a new user message since we're re-running with existing messages.
*/
executeRerun(): Promise<void>;
/**
* Resets the conversation back to the original messages from the prompt run.
* This is available after a re-run has been executed.
*/
resetToOriginalMessages(): void;
sendMessage(): Promise<void>;
private executeAgent;
private executePrompt;
private startTypingAnimation;
private buildDataContext;
private buildTemplateData;
private buildTemplateVariables;
private convertVariableValue;
/**
* Adds a new empty agent variable to the collection.
* Agent variables are passed to the agent during execution for dynamic content and template rendering.
*/
addAgentVariable(): void;
/**
* Removes an agent variable at the specified index.
* @param index - Zero-based index of the variable to remove
*/
removeAgentVariable(index: number): void;
/**
* Adds a new empty template variable to the collection (prompt mode only).
* Template variables are used for prompt template rendering.
*/
addTemplateVariable(): void;
/**
* Removes a template variable at the specified index (prompt mode only).
* @param index - Zero-based index of the variable to remove
*/
removeTemplateVariable(index: number): void;
/**
* Clears the current conversation after user confirmation.
* Resets both the message history and the current conversation ID.
*/
clearConversation(): void;
private scrollToBottom;
private generateMessageId;
private loadSavedConversations;
/**
* Gets the storage key for saved conversations based on entity type and ID
*/
private getStorageKey;
/**
* Saves the current conversation to localStorage with user-provided name.
* Handles both creating new conversations and updating existing ones.
* Automatically limits storage to 50 conversations to prevent excessive memory usage.
*
* @example
* ```typescript
* // User clicks save button
* this.saveConversation(); // Prompts for name and saves
* ```
*/
saveConversation(): void;
updateTempConversation(): void;
/**
* Handles the save dialog confirmation
*/
confirmSaveConversation(): void;
/**
* Cancels the save dialog
*/
cancelSaveDialog(): void;
/**
* Saves conversations to localStorage
*/
private saveConversationsToStorage;
private autoSaveConversation;
loadConversation(conversation: SavedConversation): void;
/**
* Confirms loading a conversation after dialog confirmation
*/
confirmLoadConversation(): void;
/**
* Cancels loading a conversation
*/
cancelLoadConversation(): void;
/**
* Actually loads the conversation
*/
private doLoadConversation;
deleteConversation(conversation: SavedConversation, event: Event): Promise<void>;
exportConversation(): void;
importConversation(): void;
onFileSelected(event: Event): void;
/**
* Handles keyboard input in the message textarea.
* Sends message on Enter (without Shift) and allows multi-line input with Shift+Enter.
* @param event - Keyboard event from the textarea
*/
handleKeyPress(event: KeyboardEvent): void;
/**
* Generates CSS class names for message display based on message properties.
* @param message - The conversation message to generate classes for
* @returns Space-separated CSS class string for styling
*/
getMessageClass(message: ConversationMessage): string;
/**
* Formats a Date object into a user-friendly time string.
* @param date - Date object to format
* @returns Formatted time string in HH:MM format
*/
formatTimestamp(date: Date): string;
/**
* Formats execution time from milliseconds into a human-readable string.
* Automatically selects appropriate units (minutes, seconds, or milliseconds).
* @param milliseconds - Execution time in milliseconds
* @returns Formatted time string (e.g., "2m 30.5s", "1.23s", "500ms")
*/
formatExecutionTime(milliseconds: number): string;
formatElapsedTime(milliseconds: number): string;
private startElapsedTimeCounter;
/**
* Shows the raw JSON dialog for a specific message
* @param message - The message to show raw JSON for
*/
showRawJsonDialog(message: ConversationMessage): void;
/** Closes the JSON viewer window */
closeJsonWindow(): void;
/**
* Get the last run ID from conversation messages
*/
getLastRunId(): string | null;
/**
* Expands all nodes in the execution monitor
* Called automatically when execution completes
*/
private expandAllMonitoringNodes;
/**
* Navigate to the run details form
*/
navigateToRun(event: {
runId: string;
runType: 'agent' | 'prompt';
}): void;
/**
* Copies the message content to clipboard.
* @param message - The message to copy
*/
copyMessage(message: ConversationMessage): Promise<void>;
/**
* Focuses the message input textarea.
*/
private focusMessageInput;
/**
* Determines if the provided content is valid JSON.
* @param content - String content to test
* @returns True if content can be parsed as JSON, false otherwise
*/
isJsonContent(content: string): boolean;
/**
* Formats JSON content with proper indentation for display.
* Also recursively parses any nested JSON strings.
* @param content - JSON string to format
* @returns Formatted JSON string or original content if parsing fails
*/
formatJson(content: any): string;
/**
* Checks if the payload should be displayed (not null, undefined, or empty object)
* @param payload - The payload to check
* @returns true if the payload has content, false otherwise
*/
hasPayload(payload: any): boolean;
/**
* Toggles the collapsed state of a message's payload section
* @param message - The message to toggle payload visibility for
*/
togglePayloadCollapse(message: ConversationMessage): void;
/**
* Formats streaming content with markdown rendering
* @param message - The message containing streaming content
* @returns SafeHtml formatted content
*/
getFormattedStreamingContent(message: ConversationMessage): SafeHtml;
/**
* Automatically detects the content type of a message for appropriate rendering.
* Uses pattern matching to identify JSON, Markdown, or plain text content.
* @param content - Content string to analyze
* @returns Detected content type ('markdown', 'json', or 'text')
*/
detectContentType(content: string): 'markdown' | 'json' | 'text';
renderMarkdown(content: string): SafeHtml;
getFormattedContent(message: ConversationMessage): SafeHtml;
/**
* Renders JSON content using an inline code editor component
*/
private renderJsonWithCodeEditor;
/**
* Escapes HTML content for use in attributes
*/
private escapeHtmlAttribute;
/**
* Renders JSON content with human-readable content prominently displayed
* and raw JSON in a collapsible section below with proper text wrapping.
*/
/**
* Gets a summary of the execution for tooltip display
* @param message - The message to get execution summary for
* @returns A brief summary of the execution steps
*/
getExecutionSummary(message: ConversationMessage): string;
/**
* Closes the JSON dialog
*/
closeJsonDialog(): void;
/**
* Copies the JSON content to clipboard
*/
copyJsonContent(): void;
/**
* Extracts human-readable content from JSON responses.
* Prioritizes userMessage field first, then checks other common fields.
* @param jsonStr - JSON string to extract content from
* @returns Extracted human-readable content or null if none found
*/
private extractHumanReadableContent;
/**
* Checks if a message contains JSON content that has extractable human-readable content.
* Used to determine if the raw toggle should be shown.
* @param message - The conversation message to check
* @returns True if the message has extractable content different from raw JSON
*/
hasExtractableContent(message: ConversationMessage): boolean;
/**
* Determines whether to show the raw toggle button for a message.
* Combines the logic for raw content availability and extractable content.
* @param message - The conversation message to check
* @returns True if the raw toggle should be displayed
*/
showRawToggle(message: ConversationMessage): boolean;
private escapeHtml;
/**
* Formats JSON for proper display with text wrapping instead of wide code blocks.
* Creates a structured, readable format that respects container width.
*/
private formatJsonForDisplay;
/**
* Creates formatted HTML for JSON display with proper indentation and wrapping.
*/
private createJsonDisplayHtml;
/**
* Type guard to check if entity is an AI Agent
*/
private isAgentEntity;
/**
* Type guard to check if entity is an AI Prompt
*/
private isPromptEntity;
/**
* Gets the display name of the current entity
*/
getEntityName(): string;
/**
* Gets the icon class for the current entity
*/
getEntityIconClass(): string;
/**
* Checks if the entity has a logo URL
*/
hasEntityLogo(): boolean;
/**
* Gets the logo URL for the entity (agent only)
*/
getEntityLogoURL(): string;
/**
* Updates stop sequences from the textarea input
*/
updateStopSequences(): void;
/**
* Toggles the advanced parameters expansion panel
*/
toggleAdvancedParams(): void;
/**
* Navigates to the AI Agent Run form to view detailed execution information
* @param agentRunId - The ID of the agent run to view
*/
navigateToAgentRun({ runId, runType }: {
runId: string;
runType: 'agent' | 'prompt';
}): void;
/**
* Loads data from an existing prompt run to pre-populate the test harness
* @param promptRunId - The ID of the prompt run to load
*/
private loadFromPromptRun;
/** Case-insensitive UUID comparison for configuration ID matching in templates. */
IsConfigMatchById(config: MJAIConfigurationEntity, id: string | undefined): boolean;
static ɵfac: i0.ɵɵFactoryDeclaration<AITestHarnessComponent, never>;
static ɵcmp: i0.ɵɵComponentDeclaration<AITestHarnessComponent, "mj-ai-test-harness", never, { "mode": { "alias": "mode"; "required": false; }; "entity": { "alias": "entity"; "required": false; }; "originalPromptRunId": { "alias": "originalPromptRunId"; "required": false; }; "systemPromptOverride": { "alias": "systemPromptOverride"; "required": false; }; "aiAgent": { "alias": "aiAgent"; "required": false; }; "isVisible": { "alias": "isVisible"; "required": false; }; }, { "visibilityChange": "visibilityChange"; "runOpened": "runOpened"; "minimizeRequested": "minimizeRequested"; }, never, never, false, never>;
}
//# sourceMappingURL=ai-test-harness.component.d.ts.map