UNPKG

jpglens

Version:

๐Ÿ” Universal AI-Powered UI Testing - See your interfaces through the lens of intelligence

3,048 lines โ€ข 111 kB
import fs, { writeFileSync, readFileSync } from 'fs';
import path, { join } from 'path';
import 'url';

/**
 * ๐Ÿ” jpglens - Master Prompt System
 * Universal AI-Powered UI Testing
 *
 * @author Taha Bahrami (Kaito)
 * @license MIT
 */
/**
 * Master prompt template that provides comprehensive context to AI models
 * This is the core intelligence that makes jpglens understand user experience
 */
function createMasterPrompt(context, analysisTypes) {
    const { userContext, businessContext, technicalContext, stage, userIntent, criticalElements } = context;
    return `You are a world-class UX expert, accessibility specialist, and design systems consultant analyzing a user interface through the lens of REAL USER EXPERIENCE.

๐ŸŽฏ **ANALYSIS CONTEXT**
User Stage: ${stage}
User Intent: ${userIntent}
Critical Elements: ${criticalElements?.join(', ') || 'All visible elements'}

๐Ÿ‘ค **USER CONTEXT**
${formatUserContext(userContext)}

๐Ÿข **BUSINESS CONTEXT**
${formatBusinessContext(businessContext)}

โš™๏ธ **TECHNICAL CONTEXT**
${formatTechnicalContext(technicalContext)}

๐Ÿ” **ANALYSIS REQUIREMENTS**
You must analyze this interface for: ${analysisTypes.join(', ')}

${generateAnalysisInstructions(analysisTypes)}

๐Ÿ“Š **RESPONSE FORMAT**
Provide your analysis in this EXACT format:

**๐ŸŽฏ OVERALL UX SCORE: X/10**

**โœ… STRENGTHS:**
- [What works exceptionally well for this specific user context]

**๐Ÿšจ CRITICAL ISSUES:** (Blocks user success)
- [Issues that prevent task completion or cause significant user frustration]

**โš ๏ธ MAJOR ISSUES:** (Impacts user experience)
- [Problems that make the interface difficult or unpleasant to use]

**๐Ÿ’ก MINOR ISSUES:** (Polish opportunities)
- [Small improvements that would enhance the experience]

**๐Ÿ› ๏ธ SPECIFIC RECOMMENDATIONS:**
For each issue, provide:
1. **Problem**: Clear description
2. **Impact**: How it affects the user in this context
3. **Solution**: Specific, actionable fix
4. **Priority**: Critical/Major/Minor

**๐ŸŽจ CONTEXTUAL INSIGHTS:**
- How well does this interface serve the user's specific intent: "${userIntent}"?
- What would make this experience more successful for this user context?
- Are there any missed opportunities for this business context?

**๐Ÿ“ฑ DEVICE-SPECIFIC NOTES:**
- Any issues specific to the ${userContext.deviceContext} experience?
- Touch targets, readability, interaction patterns?

Remember: This user is ${getUserPersonaDescription(userContext.persona)} in the context of ${stage}. Every recommendation should consider their specific needs, constraints, and goals.

Be specific, actionable, and focused on REAL USER SUCCESS.`;
}
/**
 * Format user context for the prompt
 */
function formatUserContext(userContext) {
    const persona = typeof userContext.persona === 'string'
        ? `Persona: ${userContext.persona}`
        : formatPersonaDetails(userContext.persona);
    return `${persona}
Device Context: ${userContext.deviceContext}
Expertise Level: ${userContext.expertise || 'Not specified'}
Time Constraint: ${userContext.timeConstraint || 'Normal'}
Trust Level: ${userContext.trustLevel || 'Medium'}
Business Goals: ${userContext.businessGoals?.join(', ') || 'Not specified'}`;
}
/**
 * Format business context for the prompt
 */
function formatBusinessContext(businessContext) {
    if (!businessContext)
        return 'Not specified';
    return `Industry: ${businessContext.industry}
Conversion Goal: ${businessContext.conversionGoal}
Competitive Advantage: ${businessContext.competitiveAdvantage || 'Not specified'}
Brand Personality: ${businessContext.brandPersonality || 'Not specified'}
Target Audience: ${businessContext.targetAudience || 'Not specified'}`;
}
/**
 * Format technical context for the prompt
 */
function formatTechnicalContext(technicalContext) {
    if (!technicalContext)
        return 'Not specified';
    return `Framework: ${technicalContext.framework || 'Not specified'}
Design System: ${technicalContext.designSystem || 'Not specified'}
Device Support: ${technicalContext.deviceSupport}
Performance Target: ${technicalContext.performanceTarget || 'Not specified'}
Accessibility Target: ${technicalContext.accessibilityTarget || 'WCAG AA'}`;
}
/**
 * Format persona details for the prompt
 */
function formatPersonaDetails(persona) {
    return `Persona: ${persona.name}
Expertise: ${persona.expertise}
Primary Device: ${persona.device}
Urgency: ${persona.urgency}
Goals: ${persona.goals.join(', ')}
Pain Points: ${persona.painPoints?.join(', ') || 'Not specified'}
Context: ${persona.context || 'Not specified'}`;
}
/**
 * Get user persona description for contextual understanding
 */
function getUserPersonaDescription(persona) {
    if (!persona)
        return 'a general user';
    if (typeof persona === 'string')
        return persona;
    return `${persona.name} (${persona.expertise} level, ${persona.device} user, ${persona.urgency} urgency)`;
}
/**
 * Generate specific analysis instructions based on requested types
 */
function generateAnalysisInstructions(analysisTypes) {
    const instructions = [];
    if (analysisTypes.includes('usability')) {
        instructions.push(`
**๐Ÿงญ USABILITY ANALYSIS:**
- Is the interface intuitive for this user's expertise level?
- Can the user complete their intended task efficiently?
- Are there any confusing or misleading elements?
- Does the information architecture make sense?
- Are interactive elements clearly identifiable?`);
    }
    if (analysisTypes.includes('accessibility')) {
        instructions.push(`
**โ™ฟ ACCESSIBILITY ANALYSIS:**
- WCAG 2.1 compliance (AA minimum, AAA preferred)
- Color contrast ratios (4.5:1 for normal text, 3:1 for large text)
- Keyboard navigation support
- Screen reader compatibility (semantic HTML, ARIA labels)
- Focus management and visual focus indicators
- Alternative text for images
- Form labels and error handling`);
    }
    if (analysisTypes.includes('visual-design')) {
        instructions.push(`
**๐ŸŽจ VISUAL DESIGN ANALYSIS:**
- Typography hierarchy and readability
- Color usage and brand consistency
- Visual balance and composition
- Spacing and layout effectiveness
- Responsive design quality
- Visual feedback for interactions
- Overall aesthetic appeal and professionalism`);
    }
    if (analysisTypes.includes('performance')) {
        instructions.push(`
**โšก PERFORMANCE ANALYSIS:**
- Perceived performance and loading states
- Image optimization and lazy loading
- Critical rendering path
- User perception of speed
- Progressive enhancement
- Mobile performance considerations`);
    }
    if (analysisTypes.includes('mobile-optimization')) {
        instructions.push(`
**๐Ÿ“ฑ MOBILE OPTIMIZATION ANALYSIS:**
- Touch target sizes (minimum 44px)
- Thumb-friendly navigation
- Mobile-first responsive design
- Portrait/landscape orientation support
- Mobile-specific interaction patterns
- One-handed usage considerations`);
    }
    if (analysisTypes.includes('conversion-optimization')) {
        instructions.push(`
**๐Ÿ’ฐ CONVERSION OPTIMIZATION ANALYSIS:**
- Clear value proposition presentation
- Friction points in the conversion funnel
- Trust signals and credibility indicators
- Call-to-action effectiveness
- Form optimization
- User motivation and persuasion elements`);
    }
    if (analysisTypes.includes('brand-consistency')) {
        instructions.push(`
**๐ŸŽฏ BRAND CONSISTENCY ANALYSIS:**
- Design system adherence
- Brand voice and tone in copy
- Visual identity consistency
- Component usage patterns
- Brand personality reflection
- Cross-platform consistency`);
    }
    if (analysisTypes.includes('error-handling')) {
        instructions.push(`
**๐Ÿšจ ERROR HANDLING ANALYSIS:**
- Error prevention strategies
- Clear error messaging
- Recovery path availability
- User guidance during errors
- Validation feedback timing
- Graceful degradation`);
    }
    return instructions.join('\n');
}
/**
 * Create specialized prompts for specific scenarios
 */
const SpecializedPrompts = {
    /**
     * E-commerce focused analysis
     */
    ecommerce: (context) => `
${createMasterPrompt(context, ['usability', 'conversion-optimization', 'mobile-optimization'])}

**๐Ÿ›’ E-COMMERCE SPECIFIC FOCUS:**
- Product discoverability and presentation
- Shopping cart and checkout flow optimization
- Trust signals and security indicators
- Mobile shopping experience
- Price presentation and value communication
- Return policy and customer service accessibility`,
    /**
     * SaaS application analysis
     */
    saas: (context) => `
${createMasterPrompt(context, ['usability', 'accessibility', 'performance'])}

**๐Ÿ’ผ SAAS SPECIFIC FOCUS:**
- Dashboard clarity and information hierarchy
- Feature discoverability and onboarding
- Data visualization effectiveness
- Workflow efficiency
- User role and permission clarity
- Integration and API documentation accessibility`,
    /**
     * Design system component analysis
     */
    designSystem: (context) => `
${createMasterPrompt(context, ['visual-design', 'accessibility', 'brand-consistency'])}

**๐ŸŽจ DESIGN SYSTEM SPECIFIC FOCUS:**
- Component consistency and reusability
- Documentation clarity and completeness
- Implementation flexibility
- Accessibility built-in by default
- Responsive behavior patterns
- Cross-browser compatibility`,
    /**
     * Mobile app analysis
     */
    mobileApp: (context) => `
${createMasterPrompt(context, ['mobile-optimization', 'usability', 'performance'])}

**๐Ÿ“ฑ MOBILE APP SPECIFIC FOCUS:**
- Native platform conventions adherence
- Gesture support and touch interactions
- Offline functionality and sync
- App store guidelines compliance
- Battery and performance optimization
- Push notification integration`,
};

/**
 * ๐Ÿ” jpglens - OpenRouter AI Provider
 * Universal AI-Powered UI Testing
 *
 * @author Taha Bahrami (Kaito)
 * @license MIT
 */
/**
 * OpenRouter AI provider for jpglens
 * Supports multiple AI models through OpenRouter's unified API
 */
class OpenRouterProvider {
    config;
    name = 'OpenRouter';
    baseUrl;
    apiKey;
    model;
    constructor(config) {
        this.config = config;
        this.baseUrl = config.ai.baseUrl || 'https://openrouter.ai/api/v1';
        this.apiKey = config.ai.apiKey;
        this.model = config.ai.model;
        if (!this.apiKey) {
            throw new Error('OpenRouter API key is required. Set JPGLENS_API_KEY environment variable.');
        }
    }
    /**
     * Check if OpenRouter is available
     */
    async isAvailable() {
        try {
            const response = await fetch(`${this.baseUrl}/models`, {
                headers: {
                    Authorization: `Bearer ${this.apiKey}`,
                    'HTTP-Referer': 'https://jpglens.dev',
                    'X-Title': 'jpglens - Universal AI UI Testing',
                },
            });
            return response.ok;
        }
        catch (error) {
            console.warn('OpenRouter availability check failed:', error);
            return false;
        }
    }
    /**
     * Get model information
     */
    getModelInfo() {
        return {
            name: this.model,
            capabilities: ['vision', 'text-analysis', 'code-generation', 'accessibility-analysis'],
        };
    }
    /**
     * Analyze screenshot with OpenRouter
     */
    async analyze(screenshot, context, prompt) {
        const startTime = Date.now();
        try {
            // Validate inputs
            if (!screenshot.buffer || screenshot.buffer.length === 0) {
                throw new Error('Invalid screenshot data');
            }
            // Convert screenshot to base64
            const base64Image = screenshot.buffer.toString('base64');
            // Prepare request body
            const requestBody = {
                model: this.model,
                messages: [
                    {
                        role: 'user',
                        content: [
                            {
                                type: 'text',
                                text: prompt,
                            },
                            {
                                type: 'image_url',
                                image_url: {
                                    url: `data:image/png;base64,${base64Image}`,
                                    detail: this.config.analysis.depth === 'comprehensive' ? 'high' : 'auto',
                                },
                            },
                        ],
                    },
                ],
                max_tokens: this.config.ai.maxTokens || 4000,
                temperature: this.config.ai.temperature || 0.1,
                stream: false,
            };
            // Make API request
            const response = await fetch(`${this.baseUrl}/chat/completions`, {
                method: 'POST',
                headers: {
                    Authorization: `Bearer ${this.apiKey}`,
                    'Content-Type': 'application/json',
                    'HTTP-Referer': 'https://jpglens.dev',
                    'X-Title': 'jpglens - Universal AI UI Testing',
                },
                body: JSON.stringify(requestBody),
            });
            if (!response.ok) {
                const errorText = await response.text();
                throw new Error(`OpenRouter API error: ${response.status} ${response.statusText} - ${errorText}`);
            }
            const result = await response.json();
            if (!result.choices || result.choices.length === 0) {
                throw new Error('No analysis result returned from OpenRouter');
            }
            const analysisText = result.choices[0].message.content;
            const tokensUsed = result.usage?.total_tokens || 0;
            // Parse the analysis text into structured result
            const structuredResult = this.parseAnalysisResult(analysisText, context, tokensUsed, startTime);
            return structuredResult;
        }
        catch (error) {
            console.error('OpenRouter analysis failed:', error);
            throw new Error(`OpenRouter analysis failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
        }
    }
    /**
     * Parse AI analysis text into structured result
     */
    parseAnalysisResult(analysisText, context, tokensUsed, startTime) {
        const analysisTime = Date.now() - startTime;
        // Extract overall score
        const scoreMatch = analysisText.match(/(?:OVERALL UX SCORE|QUALITY SCORE):\s*(\d+)\/10/i);
        const overallScore = scoreMatch ? parseInt(scoreMatch[1]) : 5;
        // Extract sections using regex patterns
        const strengths = this.extractSection(analysisText, 'STRENGTHS');
        const criticalIssues = this.extractIssues(analysisText, 'CRITICAL ISSUES', 'critical');
        const majorIssues = this.extractIssues(analysisText, 'MAJOR ISSUES', 'major');
        const minorIssues = this.extractIssues(analysisText, 'MINOR ISSUES', 'minor');
        const recommendations = this.extractRecommendations(analysisText);
        // Extract specific scores if available
        const scores = {
            usability: this.extractSpecificScore(analysisText, 'usability') || overallScore,
            accessibility: this.extractSpecificScore(analysisText, 'accessibility') || overallScore,
            visualDesign: this.extractSpecificScore(analysisText, 'visual') || overallScore,
            performance: this.extractSpecificScore(analysisText, 'performance') || overallScore,
        };
        return {
            id: `jpglens-openrouter-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
            timestamp: new Date().toISOString(),
            page: context.pageInfo?.url || 'unknown',
            context,
            overallScore,
            scores,
            strengths,
            criticalIssues,
            majorIssues,
            minorIssues,
            recommendations,
            model: this.model,
            tokensUsed,
            analysisTime,
            provider: 'OpenRouter',
            rawAnalysis: analysisText, // Keep raw text for debugging
        };
    }
    /**
     * Extract a section from the analysis text
     */
    extractSection(text, sectionName) {
        const regex = new RegExp(`\\*\\*${sectionName}[:\\s]*\\*\\*([\\s\\S]*?)(?=\\*\\*[A-Z]|$)`, 'i');
        const match = text.match(regex);
        if (!match)
            return [];
        return match[1]
            .split(/[-โ€ข]\s+/)
            .filter(item => item.trim().length > 0)
            .map(item => item.trim().replace(/\n/g, ' '));
    }
    /**
     * Extract issues from a section
     */
    extractIssues(text, sectionName, severity) {
        const items = this.extractSection(text, sectionName);
        return items.map(item => ({
            severity,
            category: this.categorizeIssue(item),
            title: this.extractTitle(item),
            description: item,
            impact: this.getImpactBySeverity(severity),
            selector: this.extractSelector(item),
            fix: this.extractFix(item),
        }));
    }
    /**
     * Extract recommendations from text
     */
    extractRecommendations(text) {
        const items = this.extractSection(text, 'RECOMMENDATIONS');
        return items.map(item => ({
            type: this.categorizeRecommendation(item),
            title: this.extractTitle(item),
            description: item,
            implementation: this.extractCodeBlock(item) || item,
            impact: this.assessImpact(item),
            effort: this.assessEffort(item),
        }));
    }
    /**
     * Categorize issue by analyzing content
     */
    categorizeIssue(issueText) {
        const text = issueText.toLowerCase();
        if (text.includes('contrast') ||
            text.includes('accessibility') ||
            text.includes('wcag') ||
            text.includes('screen reader')) {
            return 'accessibility';
        }
        if (text.includes('mobile') || text.includes('touch') || text.includes('responsive') || text.includes('44px')) {
            return 'mobile-optimization';
        }
        if (text.includes('performance') || text.includes('loading') || text.includes('speed') || text.includes('slow')) {
            return 'performance';
        }
        if (text.includes('visual') || text.includes('design') || text.includes('color') || text.includes('typography')) {
            return 'visual-design';
        }
        if (text.includes('conversion') || text.includes('cta') || text.includes('purchase') || text.includes('signup')) {
            return 'conversion-optimization';
        }
        return 'usability';
    }
    /**
     * Extract title from text (first sentence or 50 chars)
     */
    extractTitle(text) {
        const firstSentence = text.split('.')[0];
        return firstSentence.length > 50 ? firstSentence.substring(0, 47) + '...' : firstSentence;
    }
    /**
     * Get impact description by severity
     */
    getImpactBySeverity(severity) {
        const impacts = {
            critical: 'Prevents users from completing their tasks or causes significant frustration',
            major: 'Makes the interface difficult or unpleasant to use, reducing user satisfaction',
            minor: 'Small improvement that would enhance the overall user experience',
        };
        return impacts[severity];
    }
    /**
     * Extract CSS selector from issue text
     */
    extractSelector(text) {
        const selectorPatterns = [
            /\.[\w-]+/g, // CSS classes
            /#[\w-]+/g, // CSS IDs
            /\[[\w-]+=[\w-]+\]/g, // Attribute selectors
            /button|input|form|div|span|a/gi, // HTML elements
        ];
        for (const pattern of selectorPatterns) {
            const matches = text.match(pattern);
            if (matches) {
                return matches[0];
            }
        }
        return undefined;
    }
    /**
     * Extract fix suggestion from text
     */
    extractFix(text) {
        const fixPatterns = [/(?:fix|solution|recommend)[:\s]+([^.]+)/i, /should[:\s]+([^.]+)/i, /change[:\s]+([^.]+)/i];
        for (const pattern of fixPatterns) {
            const match = text.match(pattern);
            if (match) {
                return match[1].trim();
            }
        }
        return undefined;
    }
    /**
     * Categorize recommendation type
     */
    categorizeRecommendation(text) {
        const lower = text.toLowerCase();
        if (lower.includes('css') || lower.includes('html') || lower.includes('javascript') || lower.includes('```')) {
            return 'code';
        }
        if (lower.includes('content') || lower.includes('copy') || lower.includes('text') || lower.includes('wording')) {
            return 'content';
        }
        if (lower.includes('process') ||
            lower.includes('workflow') ||
            lower.includes('team') ||
            lower.includes('testing')) {
            return 'process';
        }
        return 'design';
    }
    /**
     * Extract code block from text
     */
    extractCodeBlock(text) {
        const codeMatch = text.match(/```[\s\S]*?```/);
        return codeMatch ? codeMatch[0] : undefined;
    }
    /**
     * Assess recommendation impact
     */
    assessImpact(text) {
        const lower = text.toLowerCase();
        if (lower.includes('critical') ||
            lower.includes('conversion') ||
            lower.includes('accessibility') ||
            lower.includes('revenue')) {
            return 'high';
        }
        if (lower.includes('major') || lower.includes('usability') || lower.includes('satisfaction')) {
            return 'medium';
        }
        return 'low';
    }
    /**
     * Assess implementation effort
     */
    assessEffort(text) {
        const lower = text.toLowerCase();
        if (lower.includes('simple') ||
            lower.includes('quick') ||
            lower.includes('css change') ||
            lower.includes('one line')) {
            return 'low';
        }
        if (lower.includes('redesign') ||
            lower.includes('refactor') ||
            lower.includes('complex') ||
            lower.includes('major change')) {
            return 'high';
        }
        return 'medium';
    }
    /**
     * Extract specific score from text
     */
    extractSpecificScore(text, category) {
        const regex = new RegExp(`${category}[:\\s]*([\\d]+)(?:\/10)?`, 'i');
        const match = text.match(regex);
        return match ? parseInt(match[1]) : undefined;
    }
}

/**
 * ๐Ÿ” jpglens - OpenAI Provider
 * Universal AI-Powered UI Testing
 *
 * @author Taha Bahrami (Kaito)
 * @license MIT
 */
/**
 * OpenAI provider for jpglens
 * Direct integration with OpenAI's GPT-4 Vision and other models
 */
class OpenAIProvider {
    config;
    name = 'OpenAI';
    baseUrl;
    apiKey;
    model;
    constructor(config) {
        this.config = config;
        this.apiKey = config.ai.apiKey;
        this.model = config.ai.model?.includes('/') ? config.ai.model.split('/')[1] : config.ai.model;
        this.baseUrl = config.ai.baseUrl || 'https://api.openai.com/v1';
        if (!this.apiKey) {
            throw new Error('OpenAI API key is required');
        }
    }
    async isAvailable() {
        try {
            const response = await fetch(`${this.baseUrl}/models`, {
                headers: {
                    Authorization: `Bearer ${this.apiKey}`,
                    'Content-Type': 'application/json',
                },
            });
            return response.ok;
        }
        catch {
            return false;
        }
    }
    getModelInfo() {
        return {
            name: this.model,
            capabilities: ['vision', 'text-analysis', 'code-generation'],
        };
    }
    async analyze(screenshot, context, prompt) {
        const startTime = Date.now();
        try {
            const base64Image = screenshot.buffer.toString('base64');
            const requestBody = {
                model: this.model,
                messages: [
                    {
                        role: 'user',
                        content: [
                            {
                                type: 'text',
                                text: prompt,
                            },
                            {
                                type: 'image_url',
                                image_url: {
                                    url: `data:image/png;base64,${base64Image}`,
                                    detail: this.config.analysis.depth === 'comprehensive' ? 'high' : 'auto',
                                },
                            },
                        ],
                    },
                ],
                max_tokens: this.config.ai.maxTokens || 4000,
                temperature: this.config.ai.temperature || 0.1,
            };
            const response = await fetch(`${this.baseUrl}/chat/completions`, {
                method: 'POST',
                headers: {
                    Authorization: `Bearer ${this.apiKey}`,
                    'Content-Type': 'application/json',
                },
                body: JSON.stringify(requestBody),
            });
            if (!response.ok) {
                const errorText = await response.text();
                throw new Error(`OpenAI API error: ${response.status} - ${errorText}`);
            }
            const result = await response.json();
            const analysisText = result.choices[0].message.content;
            const tokensUsed = result.usage?.total_tokens || 0;
            return this.parseAnalysisResult(analysisText, context, tokensUsed, startTime);
        }
        catch (error) {
            throw new Error(`OpenAI analysis failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
        }
    }
    parseAnalysisResult(analysisText, context, tokensUsed, startTime) {
        // Similar parsing logic as OpenRouter provider
        // This is a simplified version - in production you'd want shared parsing utilities
        const scoreMatch = analysisText.match(/(?:OVERALL UX SCORE|QUALITY SCORE):\s*(\d+)\/10/i);
        const overallScore = scoreMatch ? parseInt(scoreMatch[1]) : 5;
        return {
            id: `jpglens-openai-${Date.now()}`,
            timestamp: new Date().toISOString(),
            page: context.pageInfo?.url || 'unknown',
            context,
            overallScore,
            scores: {
                usability: overallScore,
                accessibility: overallScore,
                visualDesign: overallScore,
                performance: overallScore,
            },
            strengths: [],
            criticalIssues: [],
            majorIssues: [],
            minorIssues: [],
            recommendations: [],
            model: this.model,
            tokensUsed,
            analysisTime: Date.now() - startTime,
            provider: 'OpenAI',
            rawAnalysis: analysisText,
        };
    }
}

/**
 * ๐Ÿ” jpglens - Anthropic Provider
 * Universal AI-Powered UI Testing
 *
 * @author Taha Bahrami (Kaito)
 * @license MIT
 */
/**
 * Anthropic Claude provider for jpglens
 * Direct integration with Claude's vision capabilities
 */
class AnthropicProvider {
    config;
    name = 'Anthropic';
    baseUrl = 'https://api.anthropic.com/v1';
    apiKey;
    model;
    constructor(config) {
        this.config = config;
        this.apiKey = config.ai.apiKey;
        this.model = config.ai.model?.includes('/') ? config.ai.model.split('/')[1] : config.ai.model;
        if (!this.apiKey) {
            throw new Error('Anthropic API key is required');
        }
    }
    async isAvailable() {
        try {
            const response = await fetch(`${this.baseUrl}/messages`, {
                method: 'POST',
                headers: {
                    'x-api-key': this.apiKey,
                    'content-type': 'application/json',
                    'anthropic-version': '2023-06-01',
                },
                body: JSON.stringify({
                    model: this.model,
                    max_tokens: 10,
                    messages: [{ role: 'user', content: 'test' }],
                }),
            });
            return response.status !== 401; // Not unauthorized
        }
        catch {
            return false;
        }
    }
    getModelInfo() {
        return {
            name: this.model,
            capabilities: ['vision', 'text-analysis', 'detailed-reasoning'],
        };
    }
    async analyze(screenshot, context, prompt) {
        const startTime = Date.now();
        try {
            const base64Image = screenshot.buffer.toString('base64');
            const requestBody = {
                model: this.model,
                max_tokens: this.config.ai.maxTokens || 4000,
                messages: [
                    {
                        role: 'user',
                        content: [
                            {
                                type: 'text',
                                text: prompt,
                            },
                            {
                                type: 'image',
                                source: {
                                    type: 'base64',
                                    media_type: 'image/png',
                                    data: base64Image,
                                },
                            },
                        ],
                    },
                ],
            };
            const response = await fetch(`${this.baseUrl}/messages`, {
                method: 'POST',
                headers: {
                    'x-api-key': this.apiKey,
                    'content-type': 'application/json',
                    'anthropic-version': '2023-06-01',
                },
                body: JSON.stringify(requestBody),
            });
            if (!response.ok) {
                const errorText = await response.text();
                throw new Error(`Anthropic API error: ${response.status} - ${errorText}`);
            }
            const result = await response.json();
            const analysisText = result.content[0].text;
            const tokensUsed = result.usage?.input_tokens + result.usage?.output_tokens || 0;
            return this.parseAnalysisResult(analysisText, context, tokensUsed, startTime);
        }
        catch (error) {
            throw new Error(`Anthropic analysis failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
        }
    }
    parseAnalysisResult(analysisText, context, tokensUsed, startTime) {
        const scoreMatch = analysisText.match(/(?:OVERALL UX SCORE|QUALITY SCORE):\s*(\d+)\/10/i);
        const overallScore = scoreMatch ? parseInt(scoreMatch[1]) : 5;
        return {
            id: `jpglens-anthropic-${Date.now()}`,
            timestamp: new Date().toISOString(),
            page: context.pageInfo?.url || 'unknown',
            context,
            overallScore,
            scores: {
                usability: overallScore,
                accessibility: overallScore,
                visualDesign: overallScore,
                performance: overallScore,
            },
            strengths: [],
            criticalIssues: [],
            majorIssues: [],
            minorIssues: [],
            recommendations: [],
            model: this.model,
            tokensUsed,
            analysisTime: Date.now() - startTime,
            provider: 'Anthropic',
            rawAnalysis: analysisText,
        };
    }
}

/**
 * ๐Ÿ” jpglens - Report Generator
 * Configurable AI Analysis Report Generation System
 *
 * @author Taha Bahrami (Kaito)
 * @license MIT
 */
/**
 * Default report templates for different output formats
 */
const DEFAULT_REPORT_TEMPLATES = {
    detailed: {
        name: 'Detailed Analysis Report',
        format: 'markdown',
        sections: [
            'executive_summary',
            'overall_score',
            'visual_hierarchy',
            'accessibility',
            'usability',
            'critical_issues',
            'recommendations',
            'technical_details',
        ],
        prompts: {
            executive_summary: 'Provide a comprehensive executive summary of the UI analysis',
            overall_score: 'Rate the overall UI quality from 1-10 with detailed reasoning',
            visual_hierarchy: 'Analyze the visual hierarchy and information architecture',
            accessibility: 'Evaluate accessibility compliance and provide specific recommendations',
            usability: 'Assess usability patterns and user experience quality',
            critical_issues: 'Identify critical issues that must be addressed immediately',
            recommendations: 'Provide actionable recommendations for improvement',
            technical_details: 'Include technical implementation details and metrics',
        },
    },
    summary: {
        name: 'Quick Summary Report',
        format: 'json',
        sections: ['overall_score', 'top_issues', 'quick_wins'],
        prompts: {
            overall_score: 'Provide an overall quality score from 1-10',
            top_issues: 'List the top 3 most critical issues',
            quick_wins: 'Suggest 3 quick improvements that can be implemented immediately',
        },
    },
    executive: {
        name: 'Executive Dashboard Report',
        format: 'json',
        sections: ['executive_summary', 'key_metrics', 'business_impact', 'next_actions'],
        prompts: {
            executive_summary: 'Provide a business-focused summary suitable for executives',
            key_metrics: 'Present key performance indicators and quality metrics',
            business_impact: 'Explain the business impact of identified issues and improvements',
            next_actions: 'Recommend prioritized actions with timeline and resource estimates',
        },
    },
};
/**
 * Default report configuration
 */
const DEFAULT_REPORT_CONFIG = {
    enabled: true,
    outputDir: './jpglens-reports',
    template: 'detailed',
    format: 'markdown',
    includeScreenshots: true,
    includeRawAnalysis: false,
    timestampFormat: 'ISO',
    fileNaming: '{timestamp}-{component}-{page}',
    customPrompts: {},
    apiCompatibility: 'auto', // auto-detect based on provider
};
/**
 * Report Generator Class
 */
class ReportGenerator {
    config;
    templates;
    constructor(config = {}) {
        this.config = { ...DEFAULT_REPORT_CONFIG, ...config };
        this.templates = { ...DEFAULT_REPORT_TEMPLATES };
        // Ensure output directory exists
        this.ensureOutputDirectory();
    }
    /**
     * Generate a report from analysis results
     */
    async generateReport(analysisResult, customConfig) {
        if (!this.config.enabled) {
            return '';
        }
        const reportConfig = { ...this.config, ...customConfig };
        const template = this.getTemplate(reportConfig.template);
        // Generate report content based on format
        const reportContent = await this.generateReportContent(analysisResult, template, reportConfig);
        // Save report to file
        const filePath = await this.saveReport(reportContent, analysisResult, reportConfig);
        return filePath;
    }
    /**
     * Generate report content based on template and format
     */
    async generateReportContent(result, template, config) {
        switch (template.format) {
            case 'markdown':
                return this.generateMarkdownReport(result, template, config);
            case 'json':
                return this.generateJsonReport(result, template, config);
            case 'html':
                return this.generateHtmlReport(result, template, config);
            default:
                throw new Error(`Unsupported report format: ${template.format}`);
        }
    }
    /**
     * Generate markdown report
     */
    generateMarkdownReport(result, template, config) {
        let markdown = `# ${template.name}\n\n`;
        markdown += `**Generated:** ${this.formatTimestamp(result.timestamp, config.timestampFormat)}\n`;
        markdown += `**Component:** ${result.component || 'N/A'}\n`;
        markdown += `**Page:** ${result.page}\n`;
        markdown += `**Model:** ${result.model}\n`;
        markdown += `**Analysis Time:** ${result.analysisTime}ms\n\n`;
        // Add sections based on template
        for (const section of template.sections) {
            markdown += this.generateMarkdownSection(section, result, template, config);
        }
        // Add technical details if requested
        if (config.includeRawAnalysis && result.rawAnalysis) {
            markdown += `## Raw Analysis\n\n\`\`\`\n${result.rawAnalysis}\n\`\`\`\n\n`;
        }
        return markdown;
    }
    /**
     * Generate JSON report
     */
    generateJsonReport(result, template, config) {
        const jsonReport = {
            metadata: {
                generated: this.formatTimestamp(result.timestamp, config.timestampFormat),
                template: template.name,
                component: result.component,
                page: result.page,
                model: result.model,
                analysisTime: result.analysisTime,
                tokensUsed: result.tokensUsed,
            },
            analysis: {},
        };
        // Add sections based on template
        for (const section of template.sections) {
            jsonReport.analysis[section] = this.extractSectionData(section, result);
        }
        if (config.includeRawAnalysis) {
            jsonReport.rawAnalysis = result.rawAnalysis;
        }
        return JSON.stringify(jsonReport, null, 2);
    }
    /**
     * Generate HTML report
     */
    generateHtmlReport(result, template, config) {
        let html = `<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>${template.name}</title>
    <style>
        body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; margin: 2rem; }
        .header { border-bottom: 2px solid #eee; padding-bottom: 1rem; margin-bottom: 2rem; }
        .section { margin: 2rem 0; }
        .score { font-size: 2rem; font-weight: bold; color: #007acc; }
        .issue { background: #fee; padding: 1rem; border-left: 4px solid #e74c3c; margin: 1rem 0; }
        .recommendation { background: #efe; padding: 1rem; border-left: 4px solid #27ae60; margin: 1rem 0; }
    </style>
</head>
<body>
    <div class="header">
        <h1>${template.name}</h1>
        <p><strong>Generated:</strong> ${this.formatTimestamp(result.timestamp, config.timestampFormat)}</p>
        <p><strong>Component:</strong> ${result.component || 'N/A'}</p>
        <p><strong>Page:</strong> ${result.page}</p>
    </div>`;
        // Add sections
        for (const section of template.sections) {
            html += this.generateHtmlSection(section, result, template, config);
        }
        html += `</body></html>`;
        return html;
    }
    /**
     * Generate markdown section
     */
    generateMarkdownSection(section, result, template, config) {
        let content = `## ${this.formatSectionTitle(section)}\n\n`;
        switch (section) {
            case 'executive_summary':
                content += `${this.extractExecutiveSummary(result)}\n\n`;
                break;
            case 'overall_score':
                content += `**Score:** ${result.overallScore}/10\n\n`;
                break;
            case 'visual_hierarchy':
                content += `**Visual Design Score:** ${result.scores.visualDesign || 'N/A'}/10\n\n`;
                break;
            case 'accessibility':
                content += `**Accessibility Score:** ${result.scores.accessibility || 'N/A'}/10\n\n`;
                break;
            case 'usability':
                content += `**Usability Score:** ${result.scores.usability || 'N/A'}/10\n\n`;
                break;
            case 'critical_issues':
                content += this.formatIssues(result.criticalIssues, 'Critical');
                break;
            case 'recommendations':
                content += this.formatRecommendations(result.recommendations);
                break;
            case 'technical_details':
                content += this.formatTechnicalDetails(result);
                break;
            default:
                content += `Data for ${section} not available.\n\n`;
        }
        return content;
    }
    /**
     * Generate HTML section
     */
    generateHtmlSection(section, result, template, config) {
        let content = `<div class="section"><h2>${this.formatSectionTitle(section)}</h2>`;
        switch (section) {
            case 'overall_score':
                content += `<div class="score">${result.overallScore}/10</div>`;
                break;
            case 'critical_issues':
                result.criticalIssues.forEach(issue => {
                    content += `<div class="issue"><strong>${issue.title}</strong><br>${issue.description}</div>`;
                });
                break;
            case 'recommendations':
                result.recommendations.forEach(rec => {
                    content += `<div class="recommendation"><strong>${rec.title}</strong><br>${rec.description}</div>`;
                });
                break;
            default:
                content += `<p>Data for ${section} not available.</p>`;
        }
        content += `</div>`;
        return content;
    }
    /**
     * Extract section data for JSON format
     */
    extractSectionData(section, result) {
        switch (section) {
            case 'overall_score':
                return result.overallScore;
            case 'top_issues':
                return result.criticalIssues.slice(0, 3).map(issue => ({
                    title: issue.title,
                    severity: issue.severity,
                    impact: issue.impact,
                }));
            case 'quick_wins':
                return result.recommendations.slice(0, 3).map(rec => ({
                    title: rec.title,
                    effort: rec.effort,
                    impact: rec.impact,
                }));
            default:
                return null;
        }
    }
    /**
     * Save report to file
     */
    async saveReport(content, result, config) {
        const fileName = this.generateFileName(result, config);
        const filePath = path.join(config.outputDir, fileName);
        await fs.promises.writeFile(filePath, content, 'utf-8');
        return filePath;
    }
    /**
     * Generate file name based on configuration
     */
    generateFileName(result, config) {
        const template = config.fileNaming;
        const timestamp = this.formatTimestamp(result.timestamp, 'filename');
        const extension = this.getFileExtension(config.format);
        return (template
            .replace('{timestamp}', timestamp)
            .replace('{component}', result.component || 'unknown')
            .replace('{page}', result.page || 'unknown')
            .replace('{id}', result.id) + extension);
    }
    /**
     * Get file extension for format
     */
    getFileExtension(format) {
        switch (format) {
            case 'markdown':
                return '.md';
            case 'json':
                return '.json';
            case 'html':
                return '.html';
            default:
                return '.txt';
        }
    }
    /**
     * Format timestamp based on configuration
     */
    formatTimestamp(timestamp, format) {
        const date = new Date(timestamp);
        switch (format) {
            case 'ISO':
                return date.toISOString();
            case 'filename':
                return date.toISOString().replace(/[:.]/g, '-').slice(0, 19);
            case 'readable':
                return date.toLocaleString();
            default:
                return timestamp;
        }
    }
    /**
     * Format section title
     */
    formatSectionTitle(section) {
        return section
            .split('_')
            .map(word => word.charAt(0).toUpperCase() + word.slice(1))
            .join(' ');
    }
    /**
     * Extract executive summary from result
     */
    extractExecutiveSummary(result) {
        // Try to extract from raw analysis or generate from available data
        if (result.rawAnalysis && result.rawAnalysis.includes('EXECUTIVE SUMMARY')) {
            const match = result.rawAnalysis.match(/EXECUTIVE SUMMARY[:\n]+(.*?)(?=\n\n|\n[A-Z]|$)/s);
            if (match)
                return match[1].trim();
        }
        // Generate summary from available data
        return (`UI analysis completed with an overall score of ${result.overallScore}/10. ` +
            `${result.criticalIssues.length} critical issues identified. ` +
            `${result.recommendations.length} recommendations provided for improvement.`);
    }
    /**
     * Format issues for display
     */
    formatIssues(issues, severity) {
        if (!issues.length)
            return `No ${severity.toLowerCase()} issues found.\n\n`;
        let content = '';
        issues.forEach((issue, index) => {
            content += `### ${index + 1}. ${issue.title}\n`;
            content += `**Severity:** ${issue.severity}\n`;
            content += `**Description:** ${issue.description}\n`;
            if (issue.fix)
                content += `**Fix:** ${issue.fix}\n`;
            content += '\n';
        });
        return content;
    }
    /**
     * Format recommendations for display
     */
    formatRecommendations(recommendations) {
        if (!recommendations.length)
            return `No recommendations available.\n\n`;
        let content = '';
        recommendations.forEach((rec, index) => {
            content += `### ${index + 1}. ${rec.title}\n`;
            content += `**Impact:** ${rec.impact}\n`;
            content += `**Effort:** ${rec.effort}\n`;
            content += `**Description:** ${rec.description}\n\n`;
        });
        return content;
    }
    /**
     * Format technical details
     */
    formatTechnicalDetails(result) {
        return (`**Analysis ID:** ${result.id}\n` +
            `**Model Used:** ${result.model}\n` +
            `**Tokens Used:** ${result.tokensUsed}\n` +
            `**Analysis Time:** ${result.analysisTime}ms\n` +
            `**Provider:** ${result.provider || 'Unknown'}\n\n`);
    }
    /**
     * Get template by name
     */
    getTemplate(templateName) {
        const template = this.templates[templateName];
        if (!template) {
            throw new Error(`Template '${templateName}' not found`);
        }
        return template;
    }
    /**
     * Ensure output directory exists
     */
    ensureOutputDirectory() {
        try {
            if (!fs.existsSync(this.config.outputDir)) {
                fs.mkdirSync(this.config.outputDir, { recursive: true });
            }
        }
        catch (error) {
            console.warn(`Failed to create report output directory: ${error}`);
        }
    }
    /**
     * Add custom template
     */
    addTemplate(name, template) {
        this.templates[name] = template;
    }
    /**
     * Update configuration
     */
    updateConfig(config) {
        this.config = { ...this.config, ...config };
        this.ensureOutputDirectory();
    }
    /**
     * Get current configuration
     */
    getConfig() {
        return { ...this.config };
    }
}

/**
 * ๐Ÿ” jpglens - API Compatibility Layer
 * Handles differences between OpenAI and Anthropic API formats
 *
 * @author Taha Bahrami (Kaito)
 * @license MIT
 */
/**
 * API Compatibility Handler
 */
class APICompatibilityHandler {
    config;
    constructor(config) {
        this.config = config;
    }
    /**
     * Detect API format based on provider and model
     */
    detectAPIFormat() {
        if (this.config.messageFormat && this.config.messageFormat !== 'auto') {
            return this.config.messageFormat;
        }
        // Auto-detect based on provider and model
        if (this.config.provider === 'anthropic') {
            return 'anthropic';
        }
        if (this.config.provider === 'openai') {
            return 'openai';
        }
        // For OpenRouter, detect based on model name
        if (this.config.provider === 'openrouter') {
            if (this.config.model?.includes('anthropic/') || this.config.model?.includes('claude')) {
                return 'anthropic';
            }
            if (this.config.model?.includes('openai/') || this.config.model?.includes('gpt')) {
                return 'openai';
            }
        }
        // Default to OpenAI format for compatibility
        return 'openai';
    }
    /**
     * Convert prompt and image to appropriate API format
     */
    formatRequest(prompt, imageBase64, systemPrompt) {
        const apiFormat = this.detectAPIFormat();
        if (apiFormat === 'anthropic') {
            return this.formatAnthropicRequest(prompt, imageBase64, systemPrompt);
        }
        else {
            return this.formatOpenAIRequest(prompt, imageBase64, systemPrompt);
        }
    }
    /**
     * Format request for OpenAI API
     */
    formatOpenAIRequest(prompt, imageBase64, systemPrompt) {
        const messages = [];
        // Add system message if provided
        if (systemPrompt) {
            messages.push({
                role: 'system',
                content: systemPrompt,
            });
        }
        // Format user message with text and optional image
        const userContent = [{ type: 'text', text: prompt }];
        if (imageBase64) {
            userContent.push({
                type: 'image_url',
                image_url: {
                    url: `data:image/png;base64,${imageBase64}`,
                },
            });
        }
        messages.push({
            role: 'user',
            content: userContent,
        });
        return {
            model: this.config.model,
            messages,
            max_tokens: this.config.maxTokens || 2000,
            temperature: this.config.temperature || 0.1,
        };
    }
    /**
     * Format request for Anthropic API
     */
    formatAnthropicRequest(prompt, imageBase64, systemPrompt) {
        const messages = [];
        // Format user message with text and optional image
        const userContent = [{ type: 'text', text: prompt }];
        if (imageBase64) {
            userContent.push({
                type: 'image',
                source: {
                    type: 'base64',
                    media_type: 'image/png',
                    data: imageBase64,
                },
            });
        }
        messages.push({
            role: 'user',
            content: userContent,
        });
        const request = {
            model: this.config.model,
            max_tokens: this.config.maxTokens || 2000,
            messages,
            temperature: this.config.temperature || 0.1,
        };
        // Add system prompt if provided
        if (systemPrompt) {
            request.system = systemPrompt;
        }
        return request;
    }
    /**
     * Parse response from either API format
     */
    parseResponse(response) {
        const apiFormat = this.detectAPIFormat();
        if (apiFormat === 'anthropic') {
            return this.parseAnthropicResponse(response);
        }
        else {
            return this.parseOpenAIResponse(response);
        }
    }
    /**
     * Parse OpenAI API response
     */
    parseOpenAIResponse(response) {
        return {
            content: response.choices[0]?.message?.content || '',
            tokensUsed: response.usage?.total_tokens || 0,
            model: response.model,
        };
    }
    /**
     * Parse Anthropic API response
     */
    parseAnthropicResponse(response) {
        const content = response.content
            ?.filter(item => item.type === 'text')
            ?.map(item => item.text)
            ?.join('') || '';
        const tokensUsed = (response.usage?.input_tokens || 0) + (response.usage?.output_tokens || 0);
        return {
            content,
            tokensUsed,
            model: response.model,
        };
    }
    /**
     * Get appropriate headers for the API
     */
    getHeaders() {
        const apiFormat = this.detectAPIFormat();
        const baseHeaders = {
            'Content-Type': 'application/json',
        };
        if (apiFormat === 'anthropic') {
            return {
                ...baseHeaders,
                Authorization: `Bearer ${this.config.apiKey}`,
                'anthropic-version': '2023-06-01',
            };
        }
        else {
            return {
                ...baseHeaders,
                Authorization: `Bearer ${this.config.apiKey}`,
            };
        }
    }
    /**
     * Get appropriate API endpoint
     */
    getEndpoint() {
        if (this.config.baseUrl) {
            const apiFormat = this.detectAPIFormat();
            if (apiFormat === 'anthropic') {
                return `${this.config.baseUrl}/v1/messages`;
            }
            else {
                return `${this.config.baseUrl}/v1/chat/completions`;
            }
        }
        // Default endpoints
        if (this.config.provider === 'anthropic') {
            return 'https://api.anthropic.com/v1/messages';
        }
        else if (this.config.provider === 'openai') {
            return 'https://api.openai.com/v1/chat/completions';
        }
        else if (this.config.provider === 'openrouter') {
            return 'https://openrouter.ai/api/v1/chat/completions';
        }
        throw new Error(`Unknown provider: ${this.config.provider}`);
    }
    /**
     * Validate configuration for the detected API format
     */
    validateConfig() {
        const apiFormat = this.detectAPIFormat();
        if (!this.config.apiKey) {
            throw new Error('API key is required');
        }
        if (!this.config.model) {
            throw new Error('Model is required');
        }
        if (apiFormat === 'anthropic') {
            if (!this.config.maxTokens) {
                throw new Error('max_tokens is required for Anthropic API');
            }
        }
    }
    /**
     * Get configuration summary
     */
    getConfigSummary() {
        return {
            provider: this.config.provider,
            model: this.config.model,
            apiFormat: this.detectAPIFormat(),
            endpoint: this.getEndpoint(),
        };
    }
}

/**
 * ๐Ÿ” jpglens - Console Output Formatter
 * Beautiful console display for AI analysis results when reports are disabled
 *
 * @author Taha Bahrami (Kaito)
 * @license MIT
 */
/**
 * Console formatting utilities
 */
class ConsoleFormatter {
    static COLORS = {
        // Text colors
        reset: '\x1b[0m',
        bright: '\x1b[1m',
        dim: '\x1b[2m',
        // Foreground colors
        black: '\x1b[30m',
        red: '\x1b[31m',
        green: '\x1b[32m',
        yellow: '\x1b[33m',
        blue: '\x1b[34m',
        magenta: '\x1b[35m',
        cyan: '\x1b[36m',
        white: '\x1b[37m',
        // Background colors
        bgRed: '\x1b[41m',
        bgGreen: '\x1b[42m',
        bgYellow: '\x1b[43m',
        bgBlue: '\x1b[44m',
        bgMagenta: '\x1b[45m',
        bgCyan: '\x1b[46m',
    };
    static ICONS = {
        success: 'โœ…',
        warning: 'โš ๏ธ',
        error: 'โŒ',
        info: 'โ„น๏ธ',
        score: '๐Ÿ“Š',
        issue: '๐Ÿ”',
        recommendation: '๐Ÿ’ก',
        strength: '๐ŸŽฏ',
        time: 'โฑ๏ธ',
        model: '๐Ÿค–',
        tokens: '๐Ÿ”ข',
        component: '๐Ÿงฉ',
        page: '๐Ÿ“„',
        user: '๐Ÿ‘ค',
        device: '๐Ÿ“ฑ',
        critical: '๐Ÿšจ',
        major: 'โšก',
        minor: '๐Ÿ“',
        fix: '๐Ÿ”ง',
        impact: '๐Ÿ“ˆ',
        effort: '๐Ÿ’ช',
        accessibility: 'โ™ฟ',
        usability: '๐ŸŽจ',
        performance: 'โšก',
        visual: '๐Ÿ‘๏ธ',
        mobile: '๐Ÿ“ฑ',
        desktop: '๐Ÿ’ป',
        star: 'โญ',
        arrow: 'โžค',
        bullet: 'โ€ข',
        separator: 'โ”€',
    };
    /**
     * Format complete analysis result for console display
     */
    static formatAnalysisResult(result, options = {}) {
        const { showRawAnalysis = false, showTechnicalDetails = true, compact = false } = options;
        // Clear console and show header
        this.showHeader(result);
        if (!compact) {
            this.showMetadata(result);
            this.showScores(result);
        }
        this.showStrengths(result);
        this.showIssues(result);
        this.showRecommendations(result);
        if (showTechnicalDetails && !compact) {
            this.showTechnicalDetails(result);
        }
        if (showRawAnalysis && result.rawAnalysis) {
            this.showRawAnalysis(result.rawAnalysis);
        }
        this.showFooter(result);
    }
    /**
     * Show analysis header
     */
    static showHeader(result) {
        const title = `${this.ICONS.success} jpglens AI Analysis Complete`;
        const line = 'โ•'.repeat(60);
        console.log(`\n${this.color(line, 'cyan')}`);
        console.log(`${this.color(title, 'bright')}${this.color(' ' + this.ICONS.component, 'cyan')}`);
        console.log(`${this.color(line, 'cyan')}\n`);
    }
    /**
     * Show metadata information
     */
    static showMetadata(result) {
        const metadata = [
            `${this.ICONS.component} Component: ${this.color(result.component || 'Unknown', 'bright')}`,
            `${this.ICONS.page} Page: ${this.color(result.page || 'Unknown', 'bright')}`,
            `${this.ICONS.model} Model: ${this.color(result.model, 'cyan')}`,
            `${this.ICONS.time} Analysis Time: ${this.color(result.analysisTime + 'ms', 'yellow')}`,
        ];
        if (result.context?.userContext) {
            const ctx = result.context.userContext;
            if (ctx.deviceContext) {
                const deviceIcon = ctx.deviceContext === 'mobile' ? this.ICONS.mobile : this.ICONS.desktop;
                metadata.push(`${deviceIcon} Device: ${this.color(ctx.deviceContext, 'magenta')}`);
            }
            if (ctx.persona) {
                const personaName = typeof ctx.persona === 'string' ? ctx.persona : ctx.persona.name || 'Unknown';
                metadata.push(`${this.ICONS.user} Persona: ${this.color(personaName, 'magenta')}`);
            }
        }
        metadata.forEach(item => console.log(`  ${item}`));
        console.log();
    }
    /**
     * Show scores section
     */
    static showScores(result) {
        console.log(`${this.color('๐Ÿ“Š ANALYSIS SCORES', 'bright')}`);
        console.log(`${this.ICONS.separator.repeat(30)}`);
        // Overall score with visual bar
        const overallScore = result.overallScore || 0;
        const scoreBar = this.createScoreBar(overallScore);
        const scoreColor = this.getScoreColor(overallScore);
        console.log(`${this.ICONS.star} Overall Score: ${this.color(overallScore.toFixed(1) + '/10', scoreColor)} ${scoreBar}`);
        // Individual scores
        if (result.scores && Object.keys(result.scores).length > 0) {
            console.log();
            Object.entries(result.scores).forEach(([category, score]) => {
                const icon = this.getCategoryIcon(category);
                const bar = this.createScoreBar(score);
                const color = this.getScoreColor(score);
                const categoryName = this.formatCategoryName(category);
                console.log(`  ${icon} ${categoryName}: ${this.color(score.toFixed(1) + '/10', color)} ${bar}`);
            });
        }
        console.log();
    }
    /**
     * Show strengths section
     */
    static showStrengths(result) {
        if (!result.strengths || result.strengths.length === 0)
            return;
        console.log(`${this.color('๐ŸŽฏ STRENGTHS', 'green')}`);
        console.log(`${this.ICONS.separator.repeat(30)}`);
        result.strengths.forEach((strength, index) => {
            console.log(`  ${this.color(index + 1 + '.', 'dim')} ${this.ICONS.success} ${strength}`);
        });
        console.log();
    }
    /**
     * Show issues section
     */
    static showIssues(result) {
        const allIssues = [
            ...(result.criticalIssues || []).map(issue => ({ ...issue, type: 'critical' })),
            ...(result.majorIssues || []).map(issue => ({ ...issue, type: 'major' })),
            ...(result.minorIssues || []).map(issue => ({ ...issue, type: 'minor' })),
        ];
        if (allIssues.length === 0) {
            console.log(`${this.color('๐ŸŽ‰ NO ISSUES FOUND', 'green')}`);
            console.log(`${this.ICONS.separator.repeat(30)}`);
            console.log(`  ${this.ICONS.success} Great job! No critical issues detected.\n`);
            return;
        }
        console.log(`${this.color('๐Ÿ” ISSUES FOUND', 'yellow')}`);
        console.log(`${this.ICONS.separator.repeat(30)}`);
        // Group by severity
        const critical = allIssues.filter(i => i.type === 'critical');
        const major = allIssues.filter(i => i.type === 'major');
        const minor = allIssues.filter(i => i.type === 'minor');
        if (critical.length > 0) {
            console.log(`\n  ${this.color('๐Ÿšจ CRITICAL ISSUES', 'red')}`);
            critical.forEach((issue, index) => {
                this.formatIssue(issue, index + 1, 'critical');
            });
        }
        if (major.length > 0) {
            console.log(`\n  ${this.color('โšก MAJOR ISSUES', 'yellow')}`);
            major.forEach((issue, index) => {
                this.formatIssue(issue, index + 1, 'major');
            });
        }
        if (minor.length > 0) {
            console.log(`\n  ${this.color('๐Ÿ“ MINOR ISSUES', 'cyan')}`);
            minor.forEach((issue, index) => {
                this.formatIssue(issue, index + 1, 'minor');
            });
        }
        console.log();
    }
    /**
     * Format individual issue
     */
    static formatIssue(issue, index, type) {
        const icons = {
            critical: this.ICONS.critical,
            major: this.ICONS.major,
            minor: this.ICONS.minor,
        };
        const colors = {
            critical: 'red',
            major: 'yellow',
            minor: 'cyan',
        };
        console.log(`    ${this.color(index + '.', 'dim')} ${icons[type]} ${this.color(issue.title, colors[type])}`);
        if (issue.description) {
            console.log(`       ${this.color('Description:', 'dim')} ${issue.description}`);
        }
        if (issue.impact) {
            console.log(`       ${this.ICONS.impact} ${this.color('Impact:', 'dim')} ${issue.impact}`);
        }
        if (issue.fix) {
            console.log(`       ${this.ICONS.fix} ${this.color('Fix:', 'green')} ${issue.fix}`);
        }
    }
    /**
     * Show recommendations section
     */
    static showRecommendations(result) {
        if (!result.recommendations || result.recommendations.length === 0)
            return;
        console.log(`${this.color('๐Ÿ’ก RECOMMENDATIONS', 'blue')}`);
        console.log(`${this.ICONS.separator.repeat(30)}`);
        result.recommendations.forEach((rec, index) => {
            console.log(`  ${this.color(index + 1 + '.', 'dim')} ${this.ICONS.recommendation} ${this.color(rec.title, 'bright')}`);
            if (rec.description) {
                console.log(`     ${rec.description}`);
            }
            const details = [];
            if (rec.impact)
                details.push(`${this.ICONS.impact} Impact: ${this.color(rec.impact, 'green')}`);
            if (rec.effort)
                details.push(`${this.ICONS.effort} Effort: ${this.color(rec.effort, 'yellow')}`);
            if (details.length > 0) {
                console.log(`     ${details.join(' | ')}`);
            }
            if (index < result.recommendations.length - 1)
                console.log();
        });
        console.log();
    }
    /**
     * Show technical details
     */
    static showTechnicalDetails(result) {
        console.log(`${this.color('๐Ÿ”ง TECHNICAL DETAILS', 'dim')}`);
        console.log(`${this.ICONS.separator.repeat(30)}`);
        const details = [
            `${this.ICONS.info} Analysis ID: ${result.id}`,
            `${this.ICONS.tokens} Tokens Used: ${result.tokensUsed || 0}`,
            `${this.ICONS.time} Processing Time: ${result.analysisTime}ms`,
        ];
        if (result.provider) {
            details.push(`${this.ICONS.model} Provider: ${result.provider}`);
        }
        details.forEach(detail => console.log(`  ${this.color(detail, 'dim')}`));
        console.log();
    }
    /**
     * Show raw analysis if requested
     */
    static showRawAnalysis(rawAnalysis) {
        console.log(`${this.color('๐Ÿ“‹ RAW AI ANALYSIS', 'dim')}`);
        console.log(`${this.ICONS.separator.repeat(30)}`);
        console.log(`${this.color(rawAnalysis, 'dim')}\n`);
    }
    /**
     * Show footer
     */
    static showFooter(result) {
        const line = 'โ•'.repeat(60);
        const timestamp = new Date(result.timestamp).toLocaleString();
        console.log(`${this.color(line, 'cyan')}`);
        console.log(`${this.color('Analysis completed at ' + timestamp, 'dim')}`);
        console.log(`${this.color('Powered by jpglens AI ๐Ÿ”', 'cyan')}`);
        console.log(`${this.color(line, 'cyan')}\n`);
    }
    /**
     * Create visual score bar
     */
    static createScoreBar(score, width = 10) {
        const filled = Math.round((score / 10) * width);
        const empty = width - filled;
        const filledBar = 'โ–ˆ'.repeat(filled);
        const emptyBar = 'โ–‘'.repeat(empty);
        return `[${this.color(filledBar, this.getScoreColor(score))}${this.color(emptyBar, 'dim')}]`;
    }
    /**
     * Get color for score
     */
    static getScoreColor(score) {
        if (score >= 8)
            return 'green';
        if (score >= 6)
            return 'yellow';
        if (score >= 4)
            return 'yellow';
        return 'red';
    }
    /**
     * Get icon for category
     */
    static getCategoryIcon(category) {
        const iconMap = {
            accessibility: this.ICONS.accessibility,
            usability: this.ICONS.usability,
            performance: this.ICONS.performance,
            visualDesign: this.ICONS.visual,
            'visual-design': this.ICONS.visual,
            mobile: this.ICONS.mobile,
            desktop: this.ICONS.desktop,
        };
        return iconMap[category] || this.ICONS.info;
    }
    /**
     * Format category name
     */
    static formatCategoryName(category) {
        return category
            .split(/[-_]/)
            .map(word => word.charAt(0).toUpperCase() + word.slice(1))
            .join(' ');
    }
    /**
     * Apply color to text
     */
    static color(text, color) {
        return `${this.COLORS[color]}${text}${this.COLORS.reset}`;
    }
    /**
     * Show compact summary (for quick results)
     */
    static showCompactSummary(result) {
        const score = result.overallScore || 0;
        const scoreColor = this.getScoreColor(score);
        const scoreBar = this.createScoreBar(score, 5);
        const criticalCount = result.criticalIssues?.length || 0;
        const majorCount = result.majorIssues?.length || 0;
        const recCount = result.recommendations?.length || 0;
        console.log(`\n${this.ICONS.success} ${this.color('jpglens Analysis:', 'bright')} ${this.color(score.toFixed(1) + '/10', scoreColor)} ${scoreBar}`);
        console.log(`${this.ICONS.issue} Issues: ${this.color(criticalCount + ' critical', criticalCount > 0 ? 'red' : 'green')}, ${this.color(majorCount + ' major', majorCount > 0 ? 'yellow' : 'green')}`);
        console.log(`${this.ICONS.recommendation} Recommendations: ${this.color(recCount.toString(), 'blue')}\n`);
    }
    /**
     * Show error message with formatting
     */
    static showError(error, details) {
        console.log(`\n${this.color('โŒ jpglens Analysis Failed', 'red')}`);
        console.log(`${this.ICONS.separator.repeat(40)}`);
        console.log(`${this.ICONS.error} ${error}`);
        if (details) {
            console.log(`${this.ICONS.info} ${this.color('Details:', 'dim')} ${details}`);
        }
        console.log();
    }
    /**
     * Show loading/progress indicator
     */
    static showProgress(message) {
        console.log(`${this.ICONS.info} ${this.color(message, 'cyan')}`);
    }
}

/**
 * ๐Ÿ” jpglens - AI Analyzer Core
 * Universal AI-Powered UI Testing
 *
 * @author Taha Bahrami (Kaito)
 * @license MIT
 */
/**
 * Core AI analysis engine for jpglens
 * Orchestrates AI providers and manages analysis workflow
 */
class AIAnalyzer {
    config;
    providers = new Map();
    primaryProvider;
    fallbackProvider;
    reportGenerator;
    apiHandler;
    constructor(config) {
        this.config = config;
        this.initializeProviders();
        this.primaryProvider = this.getProvider(config.ai.provider);
        if (config.ai.fallbackModel) {
            this.fallbackProvider = this.getProvider(this.extractProviderFromModel(config.ai.fallbackModel));
        }
        // Initialize reporting system
        const reportConfig = { ...DEFAULT_REPORT_CONFIG, ...(config.reporting || {}) };
        this.reportGenerator = new ReportGenerator(reportConfig);
        // Initialize API compatibility handler
        this.apiHandler = new APICompatibilityHandler({
            provider: config.ai.provider,
            model: config.ai.model,
            apiKey: config.ai.apiKey,
            baseUrl: config.ai.baseUrl,
            maxTokens: config.ai.maxTokens,
            temperature: config.ai.temperature,
            messageFormat: config.ai.messageFormat || 'auto',
        });
    }
    /**
     * Initialize available AI providers
     */
    initializeProviders() {
        this.providers.set('openrouter', new OpenRouterProvider(this.config));
        this.providers.set('openai', new OpenAIProvider(this.config));
        this.providers.set('anthropic', new AnthropicProvider(this.config));
    }
    /**
     * Get provider by name
     */
    getProvider(providerName) {
        const provider = this.providers.get(providerName);
        if (!provider) {
            throw new Error(`AI provider '${providerName}' not found. Available: ${Array.from(this.providers.keys()).join(', ')}`);
        }
        return provider;
    }
    /**
     * Extract provider name from model string (e.g., "openai/gpt-4" -> "openai")
     */
    extractProviderFromModel(model) {
        if (model.includes('/')) {
            const [provider] = model.split('/');
            return provider;
        }
        return this.config.ai.provider; // Default to primary provider
    }
    /**
     * Main analysis method - the heart of jpglens
     */
    async analyze(screenshot, context) {
        const startTime = Date.now();
        try {
            // Validate inputs
            this.validateInputs(screenshot, context);
            // Generate appropriate prompt based on context
            const prompt = this.generatePrompt(context);
            // Log analysis start (if debugging)
            if (process.env.JPGLENS_DEBUG) {
                console.log(`๐Ÿ” Starting jpglens analysis:`, {
                    stage: context.stage,
                    userIntent: context.userIntent,
                    provider: this.config.ai.provider,
                    model: this.config.ai.model,
                });
            }
            // Attempt analysis with primary provider
            let result;
            try {
                result = await this.primaryProvider.analyze(screenshot, context, prompt);
            }
            catch (primaryError) {
                console.warn(`Primary AI provider failed, trying fallback:`, primaryError);
                if (this.fallbackProvider) {
                    result = await this.fallbackProvider.analyze(screenshot, context, prompt);
                }
                else {
                    throw primaryError;
                }
            }
            // Post-process and enhance the result
            const enhancedResult = await this.enhanceResult(result, context, startTime);
            // Log successful analysis
            if (process.env.JPGLENS_DEBUG) {
                console.log(`โœ… jpglens analysis completed:`, {
                    score: enhancedResult.overallScore,
                    issues: enhancedResult.criticalIssues.length + enhancedResult.majorIssues.length,
                    analysisTime: enhancedResult.analysisTime,
                });
            }
            return enhancedResult;
        }
        catch (error) {
            const analysisTime = Date.now() - startTime;
            const errorMessage = error instanceof Error ? error.message : String(error);
            ConsoleFormatter.showError('jpglens analysis failed: ' + errorMessage, 'Check your configuration and try again');
            // Return error result instead of throwing
            return this.createErrorResult(error, context, analysisTime);
        }
    }
    /**
     * Analyze multiple screenshots in parallel (for journey analysis)
     */
    async analyzeMultiple(screenshots, contexts) {
        if (screenshots.length !== contexts.length) {
            throw new Error('Screenshots and contexts arrays must have the same length');
        }
        // Process in parallel with concurrency limit
        const concurrencyLimit = 3; // Avoid overwhelming AI providers
        const results = [];
        for (let i = 0; i < screenshots.length; i += concurrencyLimit) {
            const batch = screenshots.slice(i, i + concurrencyLimit);
            const batchContexts = contexts.slice(i, i + concurrencyLimit);
            const batchPromises = batch.map((screenshot, index) => this.analyze(screenshot, batchContexts[index]));
            const batchResults = await Promise.all(batchPromises);
            results.push(...batchResults);
        }
        return results;
    }
    /**
     * Generate appropriate prompt based on context
     */
    generatePrompt(context) {
        const analysisTypes = this.config.analysis.types;
        // Use specialized prompts for specific scenarios
        if (context.businessContext?.industry === 'e-commerce') {
            return SpecializedPrompts.ecommerce(context);
        }
        if (context.businessContext?.industry === 'saas') {
            return SpecializedPrompts.saas(context);
        }
        if (context.technicalContext?.designSystem) {
            return SpecializedPrompts.designSystem(context);
        }
        if (context.userContext?.deviceContext?.includes('mobile')) {
            return SpecializedPrompts.mobileApp(context);
        }
        // Default to master prompt
        return createMasterPrompt(context, analysisTypes);
    }
    /**
     * Validate analysis inputs
     */
    validateInputs(screenshot, context) {
        if (!screenshot?.buffer || screenshot.buffer.length === 0) {
            throw new Error('Invalid screenshot data provided');
        }
        if (!context.stage || !context.userIntent) {
            throw new Error('Analysis context must include stage and userIntent');
        }
        if (!context.userContext) {
            throw new Error('User context is required for meaningful analysis');
        }
    }
    /**
     * Enhance AI result with additional processing
     */
    async enhanceResult(result, context, startTime) {
        const analysisTime = Date.now() - startTime;
        // Parse and structure the AI response if needed
        const structuredResult = this.parseAIResponse(result, context);
        // Add metadata
        structuredResult.analysisTime = analysisTime;
        structuredResult.config = {
            provider: this.config.ai.provider,
            model: this.config.ai.model,
            analysisTypes: this.config.analysis.types,
            depth: this.config.analysis.depth,
        };
        // Validate and score the result
        this.validateResult(structuredResult);
        // Generate report if enabled, otherwise show console output
        const reportingConfig = this.reportGenerator.getConfig();
        if (reportingConfig.enabled) {
            try {
                const reportPath = await this.reportGenerator.generateReport(structuredResult);
                if (reportPath) {
                    structuredResult.reportPath = reportPath;
                    ConsoleFormatter.showProgress(`๐Ÿ“„ Report saved: ${reportPath}`);
                }
            }
            catch (error) {
                console.warn('Failed to generate report:', error);
                // Fallback to console output
                ConsoleFormatter.formatAnalysisResult(structuredResult, {
                    showTechnicalDetails: true,
                    compact: false,
                });
            }
        }
        else {
            // Show beautiful console output when reports are disabled
            ConsoleFormatter.formatAnalysisResult(structuredResult, {
                showTechnicalDetails: true,
                showRawAnalysis: false,
                compact: false,
            });
        }
        return structuredResult;
    }
    /**
     * Parse AI response and structure it properly
     */
    parseAIResponse(result, context) {
        // If the result is already structured, return as-is
        if (result.overallScore !== undefined && result.criticalIssues) {
            return result;
        }
        // Parse raw AI text response (fallback for simpler providers)
        if (typeof result === 'string' || result.rawResponse) {
            const rawText = typeof result === 'string' ? result : result.rawResponse;
            return this.parseTextResponse(rawText, context);
        }
        return result;
    }
    /**
     * Parse raw text response from AI
     */
    parseTextResponse(rawText, context) {
        // Extract overall score
        const scoreMatch = rawText.match(/(?:OVERALL UX SCORE|QUALITY SCORE):\s*(\d+)\/10/i);
        const overallScore = scoreMatch ? parseInt(scoreMatch[1]) : 5;
        // Extract issues by section
        const criticalIssues = this.extractIssues(rawText, 'CRITICAL ISSUES', 'critical');
        const majorIssues = this.extractIssues(rawText, 'MAJOR ISSUES', 'major');
        const minorIssues = this.extractIssues(rawText, 'MINOR ISSUES', 'minor');
        // Extract strengths
        const strengths = this.extractListItems(rawText, 'STRENGTHS');
        // Extract recommendations
        const recommendations = this.extractRecommendations(rawText);
        return {
            id: `jpglens-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
            timestamp: new Date().toISOString(),
            page: context.pageInfo?.url || context.stage || 'unknown',
            context,
            overallScore,
            scores: {
                usability: this.extractSpecificScore(rawText, 'usability') || overallScore,
                accessibility: this.extractSpecificScore(rawText, 'accessibility') || overallScore,
                visualDesign: this.extractSpecificScore(rawText, 'visual') || overallScore,
                performance: this.extractSpecificScore(rawText, 'performance') || overallScore,
            },
            strengths,
            criticalIssues,
            majorIssues,
            minorIssues,
            recommendations,
            model: this.config.ai.model,
            tokensUsed: 0, // Will be filled by provider
            analysisTime: 0, // Will be filled by enhanceResult
        };
    }
    /**
     * Extract issues from text by section
     */
    extractIssues(text, sectionName, severity) {
        const issues = [];
        const items = this.extractListItems(text, sectionName);
        items.forEach(item => {
            issues.push({
                severity,
                category: this.categorizeIssue(item),
                title: this.extractIssueTitle(item),
                description: item,
                impact: this.assessImpact(item, severity),
                fix: this.extractFix(item),
            });
        });
        return issues;
    }
    /**
     * Extract list items from a section
     */
    extractListItems(text, sectionName) {
        const regex = new RegExp(`${sectionName}[:\\s]*([\\s\\S]*?)(?=\\*\\*[A-Z]|$)`, 'i');
        const match = text.match(regex);
        if (!match)
            return [];
        return match[1]
            .split(/[-โ€ข]\s+/)
            .filter(item => item.trim().length > 0)
            .map(item => item.trim());
    }
    /**
     * Categorize issue by content
     */
    categorizeIssue(issueText) {
        const text = issueText.toLowerCase();
        if (text.includes('contrast') || text.includes('accessibility') || text.includes('wcag')) {
            return 'accessibility';
        }
        if (text.includes('mobile') || text.includes('touch') || text.includes('responsive')) {
            return 'mobile-optimization';
        }
        if (text.includes('performance') || text.includes('loading') || text.includes('speed')) {
            return 'performance';
        }
        if (text.includes('visual') || text.includes('design') || text.includes('color')) {
            return 'visual-design';
        }
        return 'usability';
    }
    /**
     * Extract issue title from description
     */
    extractIssueTitle(issueText) {
        // Take first sentence or first 50 characters
        const firstSentence = issueText.split('.')[0];
        return firstSentence.length > 50 ? firstSentence.substring(0, 47) + '...' : firstSentence;
    }
    /**
     * Assess impact based on issue content and severity
     */
    assessImpact(issueText, severity) {
        const impactMap = {
            critical: 'Blocks user task completion or causes significant frustration',
            major: 'Makes the interface difficult or unpleasant to use',
            minor: 'Small improvement opportunity that would enhance the experience',
        };
        return impactMap[severity];
    }
    /**
     * Extract fix suggestion from issue text
     */
    extractFix(issueText) {
        // Look for common fix patterns
        const fixPatterns = [
            /fix[:\s]+([^.]+)/i,
            /solution[:\s]+([^.]+)/i,
            /recommend[:\s]+([^.]+)/i,
            /should[:\s]+([^.]+)/i,
        ];
        for (const pattern of fixPatterns) {
            const match = issueText.match(pattern);
            if (match) {
                return match[1].trim();
            }
        }
        return undefined;
    }
    /**
     * Extract recommendations from text
     */
    extractRecommendations(text) {
        const recommendations = [];
        const items = this.extractListItems(text, 'RECOMMENDATIONS');
        items.forEach(item => {
            recommendations.push({
                type: this.categorizeRecommendation(item),
                title: this.extractIssueTitle(item),
                description: item,
                implementation: this.extractImplementation(item),
                impact: this.assessRecommendationImpact(item),
                effort: this.assessRecommendationEffort(item),
            });
        });
        return recommendations;
    }
    /**
     * Categorize recommendation by type
     */
    categorizeRecommendation(text) {
        const lower = text.toLowerCase();
        if (lower.includes('css') || lower.includes('html') || lower.includes('javascript') || lower.includes('code')) {
            return 'code';
        }
        if (lower.includes('content') || lower.includes('copy') || lower.includes('text')) {
            return 'content';
        }
        if (lower.includes('process') || lower.includes('workflow') || lower.includes('team')) {
            return 'process';
        }
        return 'design';
    }
    /**
     * Extract implementation details
     */
    extractImplementation(text) {
        // Look for code blocks or specific instructions
        const codeMatch = text.match(/```[\s\S]*?```/);
        if (codeMatch) {
            return codeMatch[0];
        }
        // Look for implementation keywords
        const implMatch = text.match(/implement[:\s]+([^.]+)/i);
        if (implMatch) {
            return implMatch[1].trim();
        }
        return text;
    }
    /**
     * Assess recommendation impact
     */
    assessRecommendationImpact(text) {
        const lower = text.toLowerCase();
        if (lower.includes('critical') || lower.includes('conversion') || lower.includes('accessibility')) {
            return 'high';
        }
        if (lower.includes('major') || lower.includes('usability')) {
            return 'medium';
        }
        return 'low';
    }
    /**
     * Assess recommendation effort
     */
    assessRecommendationEffort(text) {
        const lower = text.toLowerCase();
        if (lower.includes('simple') || lower.includes('quick') || lower.includes('css')) {
            return 'low';
        }
        if (lower.includes('redesign') || lower.includes('refactor') || lower.includes('complex')) {
            return 'high';
        }
        return 'medium';
    }
    /**
     * Extract specific score from text
     */
    extractSpecificScore(text, category) {
        const regex = new RegExp(`${category}[:\\s]*([\\d]+)\/10`, 'i');
        const match = text.match(regex);
        return match ? parseInt(match[1]) : undefined;
    }
    /**
     * Validate analysis result
     */
    validateResult(result) {
        if (result.overallScore < 0 || result.overallScore > 10) {
            console.warn('Invalid overall score, clamping to 0-10 range');
            result.overallScore = Math.max(0, Math.min(10, result.overallScore));
        }
        if (!result.criticalIssues)
            result.criticalIssues = [];
        if (!result.majorIssues)
            result.majorIssues = [];
        if (!result.minorIssues)
            result.minorIssues = [];
        if (!result.strengths)
            result.strengths = [];
        if (!result.recommendations)
            result.recommendations = [];
    }
    /**
     * Create error result for failed analyses
     */
    createErrorResult(error, context, analysisTime) {
        // Safely extract page information with proper null checks
        const pageUrl = context?.pageInfo?.url || context?.stage || 'unknown';
        return {
            id: `jpglens-error-${Date.now()}`,
            timestamp: new Date().toISOString(),
            page: pageUrl,
            context,
            overallScore: 0,
            scores: {},
            strengths: [],
            criticalIssues: [
                {
                    severity: 'critical',
                    category: 'error-handling',
                    title: 'Analysis Failed',
                    description: `jpglens analysis failed: ${error instanceof Error ? error.message : 'Unknown error'}`,
                    impact: 'Could not analyze user experience',
                    fix: 'Check configuration and try again',
                },
            ],
            majorIssues: [],
            minorIssues: [],
            recommendations: [],
            model: this.config.ai.model,
            tokensUsed: 0,
            analysisTime,
            error: true,
        };
    }
    /**
     * Configure reporting system
     */
    configureReporting(config) {
        this.reportGenerator.updateConfig(config);
    }
    /**
     * Get current reporting configuration
     */
    getReportingConfig() {
        return this.reportGenerator.getConfig();
    }
    /**
     * Add custom report template
     */
    addReportTemplate(name, template) {
        this.reportGenerator.addTemplate(name, template);
    }
    /**
     * Get API compatibility information
     */
    getAPICompatibilityInfo() {
        return this.apiHandler.getConfigSummary();
    }
    /**
     * Manually generate report for existing result
     */
    async generateReport(result, config) {
        return this.reportGenerator.generateReport(result, config);
    }
}

/**
 * ๐Ÿ” jpglens - Screenshot Capture System
 * Universal AI-Powered UI Testing
 *
 * @author Taha Bahrami (Kaito)
 * @license MIT
 */
/**
 * Universal screenshot capture and management system
 * Handles screenshots from multiple testing frameworks
 */
class ScreenshotCapture {
    outputDir;
    constructor(outputDir = './jpglens-screenshots') {
        this.outputDir = outputDir;
        // Initialize directory creation asynchronously
        this.ensureOutputDir().catch(error => console.warn('Failed to initialize screenshot directory:', error));
    }
    /**
     * Capture screenshot from Playwright page
     */
    async capturePlaywrightPage(page, options = {}) {
        const timestamp = new Date().toISOString();
        const filename = `playwright-${Date.now()}.png`;
        const filepath = join(this.outputDir, filename);
        // Capture screenshot with Playwright
        const buffer = await page.screenshot({
            path: filepath,
            fullPage: options.fullPage ?? true,
            animations: options.animations ?? 'disabled',
            mask: options.mask ?? [],
            type: 'png',
        });
        // Get viewport information
        const viewport = page.viewportSize() || { width: 1280, height: 720 };
        return {
            buffer,
            path: filepath,
            metadata: {
                width: viewport.width,
                height: viewport.height,
                devicePixelRatio: (await page.evaluate(() => window.devicePixelRatio)) || 1,
                timestamp,
            },
        };
    }
    /**
     * Capture screenshot from Selenium WebDriver
     */
    async captureSeleniumDriver(driver) {
        const timestamp = new Date().toISOString();
        const filename = `selenium-${Date.now()}.png`;
        const filepath = join(this.outputDir, filename);
        // Capture screenshot with Selenium
        const base64Data = await driver.takeScreenshot();
        const buffer = Buffer.from(base64Data, 'base64');
        // Save to file
        writeFileSync(filepath, buffer);
        // Get window size
        const windowSize = await driver.manage().window().getSize();
        return {
            buffer,
            path: filepath,
            metadata: {
                width: windowSize.width,
                height: windowSize.height,
                devicePixelRatio: 1, // Selenium doesn't provide this easily
                timestamp,
            },
        };
    }
    /**
     * Load screenshot from Cypress (which saves to filesystem)
     */
    async loadFromPath(filepath, metadata) {
        const buffer = readFileSync(filepath);
        return {
            buffer,
            path: filepath,
            metadata,
        };
    }
    /**
     * Create screenshot data from buffer
     */
    createFromBuffer(buffer, metadata) {
        const timestamp = metadata.timestamp || new Date().toISOString();
        const filename = `buffer-${Date.now()}.png`;
        const filepath = join(this.outputDir, filename);
        // Save buffer to file
        writeFileSync(filepath, buffer);
        return {
            buffer,
            path: filepath,
            metadata: {
                width: metadata.width,
                height: metadata.height,
                devicePixelRatio: metadata.devicePixelRatio || 1,
                timestamp,
            },
        };
    }
    /**
     * Capture multiple screenshots for user journey
     * Returns them in a zip-like structure for batch AI analysis
     */
    async captureJourneyScreenshots(captureFunction, stages) {
        const screenshots = [];
        for (const stage of stages) {
            try {
                const screenshot = await captureFunction();
                // Add stage metadata
                screenshot.stageInfo = {
                    stageName: stage,
                    stageIndex: stages.indexOf(stage),
                    totalStages: stages.length,
                };
                screenshots.push(screenshot);
            }
            catch (error) {
                console.error(`Failed to capture screenshot for stage ${stage}:`, error);
            }
        }
        // For user journey analysis, we can create a zip file
        // This allows sending all screenshots to AI in one request for better context
        const zipPath = await this.createScreenshotZip(screenshots);
        return { screenshots, zipPath };
    }
    /**
     * Create a zip file containing all screenshots for batch analysis
     * This enables better contextual analysis across user journey stages
     */
    async createScreenshotZip(screenshots) {
        try {
            // This is a placeholder for zip creation
            // In a real implementation, you'd use a library like 'adm-zip'
            const zipPath = join(this.outputDir, `journey-${Date.now()}.zip`);
            // For now, we'll create a JSON manifest of the screenshots
            // Real implementation would create actual zip file
            const manifest = {
                screenshots: screenshots.map(s => ({
                    path: s.path,
                    metadata: s.metadata,
                    stageInfo: s.stageInfo,
                })),
                createdAt: new Date().toISOString(),
                totalScreenshots: screenshots.length,
            };
            writeFileSync(zipPath.replace('.zip', '.json'), JSON.stringify(manifest, null, 2));
            return zipPath;
        }
        catch (error) {
            console.warn('Failed to create screenshot zip:', error);
            return undefined;
        }
    }
    /**
     * Optimize screenshot for AI analysis
     * Reduces file size while maintaining quality for AI vision models
     */
    async optimizeForAI(screenshot) {
        // For now, return as-is
        // Real implementation could:
        // - Resize if too large (AI models have input limits)
        // - Compress to reduce API costs
        // - Convert format if needed
        // - Add visual annotations for focus areas
        return screenshot;
    }
    /**
     * Add visual annotations to screenshot for better AI analysis
     */
    async annotateScreenshot(screenshot, annotations) {
        // This would use a library like 'sharp' or 'jimp' to add annotations
        // For now, we'll store the annotation data in metadata
        const annotatedScreenshot = { ...screenshot };
        annotatedScreenshot.annotations = annotations;
        return annotatedScreenshot;
    }
    /**
     * Convert screenshot to base64 for API transmission
     */
    toBase64(screenshot) {
        return screenshot.buffer.toString('base64');
    }
    /**
     * Get screenshot file size in bytes
     */
    getFileSize(screenshot) {
        return screenshot.buffer.length;
    }
    /**
     * Validate screenshot data
     */
    validateScreenshot(screenshot) {
        const errors = [];
        if (!screenshot.buffer || screenshot.buffer.length === 0) {
            errors.push('Screenshot buffer is empty');
        }
        if (!screenshot.path) {
            errors.push('Screenshot path is missing');
        }
        if (!screenshot.metadata) {
            errors.push('Screenshot metadata is missing');
        }
        else {
            if (!screenshot.metadata.width || !screenshot.metadata.height) {
                errors.push('Screenshot dimensions are missing');
            }
            if (screenshot.metadata.width < 100 || screenshot.metadata.height < 100) {
                errors.push('Screenshot dimensions are too small');
            }
            if (screenshot.metadata.width > 5000 || screenshot.metadata.height > 5000) {
                errors.push('Screenshot dimensions are too large');
            }
        }
        // Check file size limits (most AI APIs have limits)
        const maxSize = 20 * 1024 * 1024; // 20MB
        if (screenshot.buffer.length > maxSize) {
            errors.push(`Screenshot file size (${Math.round(screenshot.buffer.length / 1024 / 1024)}MB) exceeds maximum (20MB)`);
        }
        return {
            valid: errors.length === 0,
            errors,
        };
    }
    /**
     * Ensure output directory exists
     */
    async ensureOutputDir() {
        try {
            const fs = await import('fs');
            if (!fs.existsSync(this.outputDir)) {
                fs.mkdirSync(this.outputDir, { recursive: true });
            }
        }
        catch (error) {
            console.warn(`Failed to create screenshot output directory: ${error}`);
        }
    }
    /**
     * Clean up old screenshots to save disk space
     */
    async cleanup(olderThanHours = 24) {
        try {
            const fs = await import('fs');
            const path = await import('path');
            if (!fs.existsSync(this.outputDir))
                return;
            const files = fs.readdirSync(this.outputDir);
            const cutoffTime = Date.now() - olderThanHours * 60 * 60 * 1000;
            let deletedCount = 0;
            for (const file of files) {
                const filepath = path.join(this.outputDir, file);
                const stats = fs.statSync(filepath);
                if (stats.mtime.getTime() < cutoffTime) {
                    fs.unlinkSync(filepath);
                    deletedCount++;
                }
            }
            if (deletedCount > 0) {
                console.log(`๐Ÿงน jpglens cleaned up ${deletedCount} old screenshots`);
            }
        }
        catch (error) {
            console.warn('Failed to cleanup old screenshots:', error);
        }
    }
}

/**
 * ๐Ÿ” jpglens - Configuration Management
 * Universal AI-Powered UI Testing
 *
 * @author Taha Bahrami (Kaito)
 * @license MIT
 */
/**
 * Default configuration for jpglens
 */
const DEFAULT_CONFIG = {
    ai: {
        provider: 'openrouter',
        apiKey: process.env.JPGLENS_API_KEY || '',
        model: process.env.JPGLENS_MODEL || 'openai/gpt-4-vision-preview',
        fallbackModel: 'anthropic/claude-3-5-sonnet',
        maxTokens: 4000,
        temperature: 0.1,
        baseUrl: 'https://openrouter.ai/api/v1',
    },
    analysis: {
        types: ['usability', 'accessibility', 'visual-design'],
        depth: 'standard',
        includeScreenshots: true,
        generateReports: true,
        outputDir: './jpglens-reports',
    },
    // Reporting configuration
    reporting: {
        enabled: true,
        outputDir: './jpglens-reports',
        template: 'detailed',
        format: 'markdown',
        includeScreenshots: true,
        includeRawAnalysis: false,
        timestampFormat: 'ISO',
        fileNaming: '{timestamp}-{component}-{page}',
        customPrompts: {},
        apiCompatibility: 'auto',
    },
    // Pre-built user personas for common use cases
    userPersonas: {
        'business-user': {
            name: 'Business Professional',
            expertise: 'intermediate',
            device: 'desktop-primary',
            urgency: 'medium',
            goals: ['efficiency', 'accuracy', 'professional-appearance'],
            painPoints: ['complex interfaces', 'slow loading', 'unclear navigation'],
            context: 'Professional work environment, needs reliable tools',
        },
        'mobile-consumer': {
            name: 'Mobile Consumer',
            expertise: 'novice',
            device: 'mobile-primary',
            urgency: 'high',
            goals: ['speed', 'simplicity', 'trust'],
            painPoints: ['small touch targets', 'slow loading', 'complex forms'],
            context: 'On-the-go usage, limited attention, thumb navigation',
        },
        'power-user': {
            name: 'Power User',
            expertise: 'expert',
            device: 'mixed',
            urgency: 'low',
            goals: ['customization', 'advanced-features', 'keyboard-shortcuts'],
            painPoints: ['lack of shortcuts', 'limited customization', 'dumbed-down interfaces'],
            context: 'Daily heavy usage, values efficiency over simplicity',
        },
        'accessibility-user': {
            name: 'Accessibility User',
            expertise: 'intermediate',
            device: 'desktop-primary',
            urgency: 'medium',
            goals: ['screen-reader-compatibility', 'keyboard-navigation', 'high-contrast'],
            painPoints: ['poor alt text', 'keyboard traps', 'low contrast'],
            context: 'Uses assistive technologies, relies on semantic HTML',
        },
        'first-time-visitor': {
            name: 'First-Time Visitor',
            expertise: 'novice',
            device: 'mixed',
            urgency: 'high',
            goals: ['understand-value', 'quick-trial', 'low-commitment'],
            painPoints: ['unclear value prop', 'complex signup', 'information overload'],
            context: 'Evaluating product, high bounce risk, needs immediate value',
        },
    },
    // Pre-built journey templates for common scenarios
    journeyTemplates: {
        'e-commerce': ['discovery', 'product-selection', 'add-to-cart', 'checkout', 'confirmation'],
        'saas-onboarding': ['landing', 'signup', 'email-verification', 'setup', 'first-use', 'activation'],
        'content-consumption': ['discovery', 'article-reading', 'engagement', 'sharing', 'related-content'],
        'form-completion': ['form-discovery', 'field-entry', 'validation', 'review', 'submission', 'confirmation'],
        'dashboard-analysis': ['login', 'overview', 'drill-down', 'filter-data', 'export-results'],
        'mobile-app': ['app-launch', 'onboarding', 'core-feature', 'settings', 'sharing'],
    },
};

/**
 * ๐Ÿ” jpglens - Storybook Integration
 * Universal AI-Powered UI Testing
 *
 * @author Taha Bahrami (Kaito)
 * @license MIT
 */
/**
 * Storybook integration for jpglens
 * Analyze component states and interactions within Storybook stories
 */
class StorybookJPGLens {
    aiAnalyzer;
    screenshotCapture;
    config;
    constructor(config) {
        this.config = config || DEFAULT_CONFIG;
        this.aiAnalyzer = new AIAnalyzer(this.config);
        this.screenshotCapture = new ScreenshotCapture();
    }
    /**
     * Analyze component states within Storybook
     */
    async analyzeComponentStates(canvas, context) {
        try {
            // Get canvas element and convert to screenshot
            const canvasElement = canvas.container || canvas;
            const screenshot = await this.captureCanvasScreenshot(canvasElement);
            // Enhance context for component analysis
            const enhancedContext = {
                ...context,
                stage: `component-${context.component.toLowerCase()}`,
                userIntent: `evaluate ${context.component} component usability and design`,
                technicalContext: {
                    ...context.technicalContext,
                    framework: 'Storybook',
                    designSystem: context.designSystem || 'unknown',
                    deviceSupport: 'responsive',
                },
                componentInfo: {
                    name: context.component,
                    states: context.states,
                    framework: 'Storybook',
                },
            };
            // Perform AI analysis
            const result = await this.aiAnalyzer.analyze(screenshot, enhancedContext);
            // Add Storybook-specific metadata
            result.storybookInfo = {
                component: context.component,
                states: context.states,
                designSystem: context.designSystem,
            };
            return result;
        }
        catch (error) {
            throw new Error(`Storybook jpglens analysis failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
        }
    }
    /**
     * Analyze complete component library
     */
    async analyzeComponentLibrary(components) {
        const results = [];
        for (const component of components) {
            try {
                const result = await this.analyzeComponentStates(component.canvas, {
                    userContext: {
                        persona: 'design-system-user',
                        deviceContext: 'storybook-browser',
                        expertise: 'intermediate',
                    },
                    stage: 'component-evaluation',
                    userIntent: `evaluate ${component.name} for design system consistency`,
                    component: component.name,
                    states: component.states,
                    businessContext: {
                        industry: 'design-systems',
                        conversionGoal: 'component-adoption',
                        brandPersonality: 'consistent-professional',
                    },
                });
                result.componentCategory = component.category;
                results.push(result);
            }
            catch (error) {
                console.error(`Failed to analyze component ${component.name}:`, error);
            }
        }
        return results;
    }
    /**
     * Analyze accessibility across component states
     */
    async analyzeComponentAccessibility(canvas, component, states) {
        const context = {
            userContext: {
                persona: 'accessibility-user',
                deviceContext: 'screen-reader-desktop',
                expertise: 'intermediate',
            },
            stage: 'accessibility-evaluation',
            userIntent: 'ensure component is accessible across all states',
            businessContext: {
                industry: 'accessibility-compliance',
                conversionGoal: 'wcag-compliance',
                targetAudience: 'users-with-disabilities',
            },
            technicalContext: {
                framework: 'Storybook',
                accessibilityTarget: 'WCAG-AA',
                deviceSupport: 'responsive',
            },
        };
        return this.analyzeComponentStates(canvas, {
            ...context,
            component,
            states,
        });
    }
    /**
     * Capture screenshot from Storybook canvas
     */
    async captureCanvasScreenshot(canvasElement) {
        // This would typically use html2canvas or similar library
        // For now, we'll simulate the screenshot capture
        const rect = canvasElement.getBoundingClientRect();
        const canvas = document.createElement('canvas');
        const ctx = canvas.getContext('2d');
        if (!ctx) {
            throw new Error('Could not create canvas context for screenshot');
        }
        canvas.width = rect.width;
        canvas.height = rect.height;
        // In a real implementation, you'd use html2canvas:
        // const canvas = await html2canvas(canvasElement);
        // const buffer = Buffer.from(canvas.toDataURL('image/png').split(',')[1], 'base64');
        // For now, create a placeholder screenshot
        ctx.fillStyle = '#ffffff';
        ctx.fillRect(0, 0, canvas.width, canvas.height);
        ctx.fillStyle = '#000000';
        ctx.font = '16px Arial';
        ctx.fillText('Storybook Component Screenshot', 10, 30);
        const dataUrl = canvas.toDataURL('image/png');
        const buffer = Buffer.from(dataUrl.split(',')[1], 'base64');
        return this.screenshotCapture.createFromBuffer(buffer, {
            width: canvas.width,
            height: canvas.height,
            devicePixelRatio: window.devicePixelRatio || 1,
            timestamp: new Date().toISOString(),
        });
    }
}
/**
 * Global functions for easy Storybook integration
 */
let storybookJPGLens;
function getStorybookJPGLens() {
    if (!storybookJPGLens) {
        storybookJPGLens = new StorybookJPGLens();
    }
    return storybookJPGLens;
}
/**
 * Analyze component states (main function for stories)
 */
async function analyzeComponentStates(canvas, context) {
    const jpglens = getStorybookJPGLens();
    return jpglens.analyzeComponentStates(canvas, context);
}
/**
 * Quick component analysis with minimal setup
 */
async function analyzeComponent(canvas, componentName, options = {}) {
    const jpglens = getStorybookJPGLens();
    return jpglens.analyzeComponentStates(canvas, {
        userContext: {
            persona: 'component-user',
            deviceContext: 'storybook-browser',
            expertise: 'intermediate',
        },
        stage: 'component-review',
        userIntent: options.focus || `evaluate ${componentName} component`,
        component: componentName,
        states: options.states || ['default'],
        designSystem: options.designSystem,
    });
}
/**
 * Accessibility-focused component analysis
 */
async function analyzeComponentA11y(canvas, componentName, states = ['default', 'hover', 'focus', 'active', 'disabled']) {
    const jpglens = getStorybookJPGLens();
    return jpglens.analyzeComponentAccessibility(canvas, componentName, states);
}
/**
 * Design system consistency analysis
 */
async function analyzeDesignSystemConsistency(canvas, componentName, designSystemName) {
    const jpglens = getStorybookJPGLens();
    return jpglens.analyzeComponentStates(canvas, {
        userContext: {
            persona: 'design-system-maintainer',
            deviceContext: 'design-review',
            expertise: 'expert',
        },
        stage: 'design-system-audit',
        userIntent: `ensure ${componentName} follows ${designSystemName} guidelines`,
        component: componentName,
        states: ['default', 'hover', 'active'],
        designSystem: designSystemName,
        businessContext: {
            industry: 'design-systems',
            conversionGoal: 'consistency-compliance',
            brandPersonality: 'systematic-professional',
        },
    });
}
// Export the class for advanced usage
// StorybookJPGLens already exported above

export { StorybookJPGLens, analyzeComponent, analyzeComponentA11y, analyzeComponentStates, analyzeDesignSystemConsistency };