UNPKG

@memberjunction/ng-ai-test-harness

Version:

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

712 lines 28.7 kB
import { EventEmitter, OnInit, OnDestroy, OnChanges, SimpleChanges, AfterViewChecked, ViewContainerRef, ChangeDetectorRef } from '@angular/core'; import { Router } from '@angular/router'; import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; import { WindowService } from '@progress/kendo-angular-dialog'; import { AIAgentEntityExtended, AIPromptEntityExtended, AIAgentRunEntityExtended, AIAgentRunStepEntityExtended, AIConfigurationEntity } 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<AIAgentEntityExtended>('AI Agents'); * await agent.Load('agent-id'); * this.testHarness.aiAgent = agent; * this.testHarness.isVisible = true; * ``` */ export declare class AITestHarnessComponent implements OnInit, OnDestroy, OnChanges, AfterViewChecked { private sanitizer; private windowService; private viewContainerRef; private cdr; private router; /** * Creates a new AI Test Harness component instance. * @param sanitizer - Angular DomSanitizer for safe HTML rendering of formatted content * @param windowService - Kendo WindowService for creating modal windows * @param viewContainerRef - Angular ViewContainerRef for window positioning * @param cdr - Angular ChangeDetectorRef for managing change detection */ constructor(sanitizer: DomSanitizer, windowService: WindowService, viewContainerRef: ViewContainerRef, cdr: ChangeDetectorRef, router: Router); /** The mode of operation - either 'agent' or 'prompt' */ mode: TestHarnessMode; /** The entity to test - either an AI Agent or AI Prompt */ entity: AIAgentEntityExtended | AIPromptEntityExtended | 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(): AIAgentEntityExtended | null; set aiAgent(value: AIAgentEntityExtended | 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: AIConfigurationEntity[]; /** 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; /** Reference to the JSON window when open */ private jsonWindowRef; /** Mode for the execution monitor component */ executionMonitorMode: 'live' | 'historical'; /** Current agent run being displayed in execution monitor */ currentAgentRun: AIAgentRunEntityExtended | null; /** * Tracks agent steps during live execution (deprecated - now using agent run's Steps directly) */ liveAgentSteps: AIAgentRunStepEntityExtended[]; /** 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; /** * 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; 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