/** * Report Generator - HTML dashboard reports with charts * * Generates comprehensive HTML reports with visualizations. */ import * as fs from 'fs'; import * as path from 'path'; import { BenchResult, BenchSummary } from './bench'; interface ModelStats { model: string; total_tests: number; success_count: number; success_rate: number; pass_count: number; pass_rate: number; avg_score: number; avg_time: number; } /** * Generate comprehensive HTML reports with all UI features. */ export class ReportGenerator { /** * Generate enhanced HTML report. */ static generate( results: BenchResult[], summary: BenchSummary, outputPath?: string ): string | null { if (!results.length) { console.log('No results to generate report from'); return null; } const timestamp = new Date() .toISOString() .replace(/[:.]/g, '') .slice(0, 15); const finalPath = outputPath || `output/reports/benchmark_report_${timestamp}.html`; // Ensure directory exists const outputDir = path.dirname(finalPath); fs.mkdirSync(outputDir, { recursive: true }); // Generate HTML content const htmlContent = ReportGenerator.buildHtml(results, summary); // Write to file fs.writeFileSync(finalPath, htmlContent); console.log(`๐Ÿ“Š Report generated: ${finalPath}`); return finalPath; } private static buildHtml( results: BenchResult[], summary: BenchSummary ): string { const stats = ReportGenerator.calculateStats(results); const css = ReportGenerator.generateCss(); const summaryCards = ReportGenerator.generateSummaryCards(summary, stats); const resultsTable = ReportGenerator.generateResultsTable(results); const leaderboard = ReportGenerator.generateLeaderboard(stats); const javascript = ReportGenerator.generateJavaScript(stats); return ` PraisonAI Bench TypeScript - Report

๐Ÿ”ท PraisonAI Bench TypeScript

Benchmark Report - ${new Date().toLocaleString()}

๐Ÿ“Š Dashboard

${summaryCards}

Status Distribution

Execution Time by Model

๐Ÿ† Leaderboard

${leaderboard}

๐Ÿ“‹ All Test Results

${resultsTable}
`; } private static calculateStats(results: BenchResult[]): { modelStats: ModelStats[]; successCount: number; failureCount: number; } { const byModel: Record = {}; for (const r of results) { const model = r.model || 'unknown'; if (!byModel[model]) { byModel[model] = []; } byModel[model].push(r); } const modelStats: ModelStats[] = []; for (const [model, modelResults] of Object.entries(byModel)) { const total = modelResults.length; const success = modelResults.filter((r) => r.status === 'success').length; const withEval = modelResults.filter((r) => r.evaluation); const passed = withEval.filter( (r) => (r.evaluation as Record)?.passed ).length; const scores = withEval .map( (r) => (r.evaluation as Record)?.score || (r.evaluation as Record)?.overall_score || 0 ) .filter((s) => s > 0); const avgScore = scores.length ? scores.reduce((a, b) => a + b, 0) / scores.length : 0; const avgTime = modelResults.reduce((sum, r) => sum + r.execution_time, 0) / total; modelStats.push({ model, total_tests: total, success_count: success, success_rate: (success / total) * 100, pass_count: passed, pass_rate: withEval.length ? (passed / withEval.length) * 100 : 0, avg_score: avgScore, avg_time: avgTime, }); } // Sort by average score modelStats.sort((a, b) => b.avg_score - a.avg_score); return { modelStats, successCount: results.filter((r) => r.status === 'success').length, failureCount: results.filter((r) => r.status !== 'success').length, }; } private static generateSummaryCards( summary: BenchSummary, _stats: { modelStats: ModelStats[]; successCount: number; failureCount: number } ): string { const totalCost = summary.cost_summary?.total_cost_usd || 0; const totalTokens = summary.cost_summary?.total_tokens || 0; return `
๐Ÿงช
${summary.total_tests}
Total Tests
๐Ÿค–
${summary.models_tested.length}
Models
โœ…
${summary.success_rate}
Success Rate
โฑ๏ธ
${summary.average_execution_time}
Avg Time
๐Ÿ’ฐ
$${totalCost.toFixed(4)}
Total Cost
๐Ÿ“Š
${totalTokens.toLocaleString()}
Total Tokens
`; } private static generateLeaderboard(stats: { modelStats: ModelStats[]; }): string { const medals = ['๐Ÿฅ‡', '๐Ÿฅˆ', '๐Ÿฅ‰']; const rows = stats.modelStats .map((m, i) => { const medal = i < 3 ? medals[i] : ''; return ` ${medal} ${m.model} ${m.avg_score.toFixed(1)} ${m.pass_rate.toFixed(1)}% ${m.success_rate.toFixed(1)}% ${m.avg_time.toFixed(2)}s ${m.total_tests} `; }) .join(''); return ` ${rows}
Model Avg Score Pass Rate Success Rate Avg Time Tests
`; } private static generateResultsTable(results: BenchResult[]): string { const rows = results .map((r) => { const statusIcon = r.status === 'success' ? 'โœ…' : 'โŒ'; const score = (r.evaluation as Record)?.score || (r.evaluation as Record)?.overall_score || '-'; const passed = r.evaluation !== undefined ? (r.evaluation as Record)?.passed ? 'โœ…' : 'โŒ' : '-'; return ` ${r.test_name} ${statusIcon} ${r.status} ${r.model} ${score} ${passed} ${r.execution_time.toFixed(2)}s ${r.token_usage?.total_tokens || '-'} $${r.cost?.total_usd?.toFixed(6) || '-'} `; }) .join(''); return ` ${rows}
Test Name Status Model Score Passed Time Tokens Cost
`; } private static generateCss(): string { return ` * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f7fa; color: #2c3e50; line-height: 1.6; } .header { background: linear-gradient(135deg, #3b82f6 0%, #8b5cf6 100%); color: white; padding: 2rem; text-align: center; } .header h1 { font-size: 2.5em; margin-bottom: 0.5rem; } .header p { opacity: 0.9; } .nav-tabs { background: white; border-bottom: 2px solid #e5e7eb; display: flex; justify-content: center; } .nav-tab { padding: 1rem 2rem; cursor: pointer; border-bottom: 3px solid transparent; transition: all 0.3s; font-weight: 500; } .nav-tab:hover { background: #f9fafb; } .nav-tab.active { border-bottom-color: #3b82f6; color: #3b82f6; } .container { max-width: 1400px; margin: 0 auto; padding: 2rem; } .tab-content { display: none; } .tab-content.active { display: block; } .section-title { font-size: 1.5em; margin-bottom: 1.5rem; color: #1f2937; } .summary-cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 1rem; margin-bottom: 2rem; } .card { background: white; border-radius: 12px; padding: 1.5rem; text-align: center; box-shadow: 0 2px 8px rgba(0,0,0,0.1); } .card-icon { font-size: 2em; margin-bottom: 0.5rem; } .card-value { font-size: 1.8em; font-weight: bold; color: #3b82f6; } .card-label { color: #6b7280; font-size: 0.9em; } .charts-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(400px, 1fr)); gap: 1.5rem; margin-bottom: 2rem; } .chart-container { background: white; border-radius: 12px; padding: 1.5rem; box-shadow: 0 2px 8px rgba(0,0,0,0.1); } .chart-container h3 { margin-bottom: 1rem; color: #374151; } .results-table { width: 100%; border-collapse: collapse; background: white; border-radius: 12px; overflow: hidden; box-shadow: 0 2px 8px rgba(0,0,0,0.1); } .results-table th, .results-table td { padding: 1rem; text-align: left; border-bottom: 1px solid #e5e7eb; } .results-table th { background: #f9fafb; font-weight: 600; color: #374151; } .results-table tr:hover { background: #f9fafb; } .results-table .top-1 { background: #fef3c7; } .results-table .top-2 { background: #e5e7eb; } .results-table .top-3 { background: #fed7aa; } footer { text-align: center; padding: 2rem; color: #6b7280; } footer a { color: #3b82f6; text-decoration: none; } `; } private static generateJavaScript(stats: { modelStats: ModelStats[]; successCount: number; failureCount: number; }): string { const modelNames = stats.modelStats.map((m) => m.model); const avgTimes = stats.modelStats.map((m) => m.avg_time); return ` function showTab(tabId) { document.querySelectorAll('.tab-content').forEach(t => t.classList.remove('active')); document.querySelectorAll('.nav-tab').forEach(t => t.classList.remove('active')); document.getElementById(tabId).classList.add('active'); event.target.classList.add('active'); } // Status Chart new Chart(document.getElementById('statusChart'), { type: 'doughnut', data: { labels: ['Success', 'Failed'], datasets: [{ data: [${stats.successCount}, ${stats.failureCount}], backgroundColor: ['#10b981', '#ef4444'] }] }, options: { responsive: true, plugins: { legend: { position: 'bottom' } } } }); // Time Chart new Chart(document.getElementById('timeChart'), { type: 'bar', data: { labels: ${JSON.stringify(modelNames)}, datasets: [{ label: 'Avg Time (s)', data: ${JSON.stringify(avgTimes)}, backgroundColor: '#3b82f6' }] }, options: { responsive: true, plugins: { legend: { display: false } } } }); `; } }