@ai-lighthouse/cli 1.0.1 โ†’ 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/README.md +126 -53
  2. package/dist/index.js +2788 -12
  3. package/package.json +10 -4
  4. package/.ai-lighthouse/audit_example.com_2025-12-15T12-10-43.json +0 -183
  5. package/.ai-lighthouse/audit_fayeed.dev_2026-01-07T19-32-28.html +0 -743
  6. package/.ai-lighthouse/audit_fayeed.dev_2026-01-07T19-33-02.html +0 -757
  7. package/.ai-lighthouse/audit_github.com_2025-12-15T11-53-21.json +0 -168
  8. package/.ai-lighthouse/audit_github.com_2025-12-15T12-04-06.json +0 -168
  9. package/.ai-lighthouse/audit_github.com_2025-12-15T12-05-10.json +0 -168
  10. package/.ai-lighthouse/audit_github.com_2025-12-15T12-09-45.json +0 -168
  11. package/.ai-lighthouse/audit_github.com_2025-12-15T12-11-07.json +0 -168
  12. package/.ai-lighthouse/audit_github.com_2025-12-15T12-13-28.json +0 -168
  13. package/.ai-lighthouse/audit_github.com_2025-12-15T12-14-59.json +0 -205
  14. package/.ai-lighthouse/audit_github.com_2025-12-15T12-18-07.json +0 -205
  15. package/.ai-lighthouse/audit_github.com_2025-12-15T12-18-44.json +0 -205
  16. package/.ai-lighthouse/audit_github.com_2025-12-15T12-21-38.json +0 -205
  17. package/.ai-lighthouse/audit_github.com_2025-12-15T12-22-21.json +0 -205
  18. package/.ai-lighthouse/audit_github.com_2025-12-15T12-22-46.json +0 -205
  19. package/.ai-lighthouse/audit_github.com_2025-12-15T12-23-18.json +0 -205
  20. package/.ai-lighthouse/audit_github.com_2025-12-15T12-24-43.json +0 -205
  21. package/.ai-lighthouse/audit_github.com_2025-12-17T12-15-08.json +0 -168
  22. package/.ai-lighthouse/audit_github.com_2025-12-17T12-15-57.json +0 -168
  23. package/.ai-lighthouse/audit_github.com_2025-12-17T12-17-11.json +0 -168
  24. package/.ai-lighthouse/audit_github.com_2025-12-17T12-22-17.json +0 -168
  25. package/.ai-lighthouse/audit_github.com_2025-12-17T12-22-42.json +0 -168
  26. package/.ai-lighthouse/audit_github.com_2025-12-17T12-23-56.json +0 -168
  27. package/.ai-lighthouse/audit_github.com_2025-12-17T12-25-24.json +0 -168
  28. package/.ai-lighthouse/audit_github.com_2025-12-17T12-25-40.json +0 -168
  29. package/.ai-lighthouse/audit_github.com_2025-12-17T12-27-02.json +0 -168
  30. package/.ai-lighthouse/audit_github.com_2025-12-17T12-27-20.json +0 -168
  31. package/.ai-lighthouse/audit_github.com_2025-12-17T12-29-56.json +0 -168
  32. package/.ai-lighthouse/audit_github.com_2025-12-17T12-32-27.json +0 -168
  33. package/.ai-lighthouse/audit_github.com_2025-12-17T12-33-00.json +0 -168
  34. package/.ai-lighthouse/audit_github.com_2025-12-17T12-34-49.json +0 -168
  35. package/.ai-lighthouse/audit_stripe.com_2025-12-15T12-11-31.json +0 -168
  36. package/.ai-lighthouse/audit_stripe.com_2025-12-15T12-11-45.json +0 -168
  37. package/.ai-lighthouse/audit_tailwindcss.com_2025-12-15T12-12-01.json +0 -169
  38. package/.ai-lighthouse/crawl_example.com_2025-12-15T12-03-08.json +0 -24
  39. package/.ai-lighthouse/crawl_example.com_2025-12-15T12-03-23.json +0 -24
  40. package/.ai-lighthouse/crawl_github.com_2025-12-15T11-41-34.json +0 -21
  41. package/.ai-lighthouse/crawl_github.com_2025-12-15T11-42-09.json +0 -21
  42. package/.ai-lighthouse/crawl_github.com_2025-12-15T11-42-45.json +0 -21
  43. package/.ai-lighthouse/crawl_github.com_2025-12-15T11-43-02.json +0 -21
  44. package/.ai-lighthouse/crawl_github.com_2025-12-15T11-43-26.json +0 -21
  45. package/.ai-lighthouse/crawl_github.com_2025-12-15T11-47-46.json +0 -906
  46. package/.ai-lighthouse/crawl_github.com_2025-12-15T11-50-27.json +0 -906
  47. package/.ai-lighthouse/crawl_github.com_2025-12-15T11-52-59.json +0 -906
  48. package/.ai-lighthouse/crawl_github.com_2025-12-15T12-03-33.json +0 -28
  49. package/CLI_UI_README.md +0 -211
  50. package/EXAMPLES.md +0 -87
  51. package/IMPLEMENTATION.md +0 -215
  52. package/USAGE.md +0 -264
  53. package/WIZARD_GUIDE.md +0 -340
  54. package/bin/cli.js +0 -2
  55. package/dist/commands/audit-interactive.d.ts +0 -2
  56. package/dist/commands/audit-interactive.js +0 -106
  57. package/dist/commands/audit-wizard.d.ts +0 -2
  58. package/dist/commands/audit-wizard.js +0 -110
  59. package/dist/commands/audit.d.ts +0 -2
  60. package/dist/commands/audit.js +0 -940
  61. package/dist/commands/crawl.d.ts +0 -2
  62. package/dist/commands/crawl.js +0 -267
  63. package/dist/commands/report.d.ts +0 -2
  64. package/dist/commands/report.js +0 -304
  65. package/dist/index.d.ts +0 -1
  66. package/dist/ui/AuditReportUI.d.ts +0 -10
  67. package/dist/ui/AuditReportUI.js +0 -76
  68. package/dist/ui/SetupWizard.d.ts +0 -18
  69. package/dist/ui/SetupWizard.js +0 -179
  70. package/dist/ui/components/AIUnderstandingSection.d.ts +0 -6
  71. package/dist/ui/components/AIUnderstandingSection.js +0 -87
  72. package/dist/ui/components/HallucinationSection.d.ts +0 -6
  73. package/dist/ui/components/HallucinationSection.js +0 -84
  74. package/dist/ui/components/IssuesSection.d.ts +0 -6
  75. package/dist/ui/components/IssuesSection.js +0 -84
  76. package/dist/ui/components/MessageAlignmentSection.d.ts +0 -6
  77. package/dist/ui/components/MessageAlignmentSection.js +0 -108
  78. package/dist/ui/components/OverviewSection.d.ts +0 -6
  79. package/dist/ui/components/OverviewSection.js +0 -107
  80. package/dist/ui/components/ScoreDisplay.d.ts +0 -8
  81. package/dist/ui/components/ScoreDisplay.js +0 -41
  82. package/dist/ui/components/TechnicalSection.d.ts +0 -7
  83. package/dist/ui/components/TechnicalSection.js +0 -110
  84. package/dist/utils/comprehensive-formatter.d.ts +0 -5
  85. package/dist/utils/comprehensive-formatter.js +0 -370
  86. package/src/commands/audit-interactive.ts +0 -149
  87. package/src/commands/audit-wizard.ts +0 -137
  88. package/src/commands/audit.ts +0 -1012
  89. package/src/commands/crawl.ts +0 -307
  90. package/src/commands/report.ts +0 -321
  91. package/src/index.ts +0 -22
  92. package/src/ui/AuditReportUI.tsx +0 -151
  93. package/src/ui/SetupWizard.tsx +0 -294
  94. package/src/ui/components/AIUnderstandingSection.tsx +0 -183
  95. package/src/ui/components/HallucinationSection.tsx +0 -172
  96. package/src/ui/components/IssuesSection.tsx +0 -140
  97. package/src/ui/components/MessageAlignmentSection.tsx +0 -203
  98. package/src/ui/components/OverviewSection.tsx +0 -157
  99. package/src/ui/components/ScoreDisplay.tsx +0 -58
  100. package/src/ui/components/TechnicalSection.tsx +0 -200
  101. package/src/utils/comprehensive-formatter.ts +0 -455
  102. package/test.sh +0 -31
  103. package/tsconfig.json +0 -25
@@ -1,1012 +0,0 @@
1
- import { Command } from 'commander';
2
- import chalk from 'chalk';
3
- import ora from 'ora';
4
- import { analyzeUrlWithRules } from '@ai-lighthouse/scanner';
5
- import { calculateAIReadiness, formatAIReadinessReport } from '@ai-lighthouse/scanner';
6
- import { exportAuditReport, generateScoringSummary } from '@ai-lighthouse/scanner';
7
- import type { ScanOptions } from '@ai-lighthouse/scanner';
8
- import { writeFile, mkdir } from 'fs/promises';
9
- import { join, resolve } from 'path';
10
- import { existsSync } from 'fs';
11
- import html_to_pdf from 'html-pdf-node';
12
- import { formatComprehensiveReport, formatDetailedIssues } from '../utils/comprehensive-formatter.js';
13
- import { render } from 'ink';
14
- import React from 'react';
15
- import { AuditReportUI } from '../ui/AuditReportUI.js';
16
- import { SetupWizard, type AuditConfig } from '../ui/SetupWizard.js';
17
-
18
- interface AuditOptions {
19
- output?: string;
20
- rules?: string;
21
- depth?: number;
22
- pages?: string;
23
- cacheTtl?: number;
24
- threshold?: number;
25
- maxChunkTokens?: number;
26
- chunkingStrategy?: 'auto' | 'heading-based' | 'paragraph-based';
27
- enableChunking?: boolean;
28
- enableExtractability?: boolean;
29
- enableHallucination?: boolean;
30
- enableLlm?: boolean;
31
- minImpact?: number;
32
- minConfidence?: number;
33
- maxIssues?: number;
34
- llmProvider?: string;
35
- llmModel?: string;
36
- llmBaseUrl?: string;
37
- llmApiKey?: string;
38
- interactive?: boolean;
39
- }
40
-
41
- export function auditCommand(program: Command) {
42
- program
43
- .command('audit')
44
- .description('Audit a website for AI readiness')
45
- .argument('<url>', 'URL to audit')
46
- .option('-o, --output <format>', 'Output format: json, html, pdf, lhr, csv, interactive', 'interactive')
47
- .option('-r, --rules <preset>', 'Rule preset: default, strict, minimal', 'default')
48
- .option('-d, --depth <number>', 'Crawl depth (for multi-page audits)', parseInt, 1)
49
- .option('-p, --pages <urls>', 'Comma-separated list of specific pages to audit')
50
- .option('--cache-ttl <seconds>', 'Cache TTL in seconds to avoid re-fetching', parseInt)
51
- .option('--threshold <score>', 'Minimum score threshold (exit 1 if below)', parseInt)
52
- .option('--max-chunk-tokens <number>', 'Maximum tokens per content chunk', parseInt, 1200)
53
- .option('--chunking-strategy <strategy>', 'Chunking strategy: auto, heading-based, paragraph-based', 'auto')
54
- .option('--enable-chunking', 'Enable detailed content chunking analysis', false)
55
- .option('--enable-extractability', 'Enable extractability mapping', false)
56
- .option('--enable-hallucination', 'Enable hallucination detection', false)
57
- .option('--enable-llm', 'Enable LLM comprehension analysis', false)
58
- .option('--min-impact <number>', 'Minimum impact score to include', parseInt, 8)
59
- .option('--min-confidence <number>', 'Minimum confidence to include (0-1)', parseFloat, 0.7)
60
- .option('--max-issues <number>', 'Maximum issues to return', parseInt, 20)
61
- .option('--llm-provider <provider>', 'LLM provider: openai, anthropic, ollama, local')
62
- .option('--llm-model <model>', 'LLM model name')
63
- .option('--llm-base-url <url>', 'LLM API base URL')
64
- .option('--llm-api-key <key>', 'LLM API key')
65
- .action(async (url: string, options: AuditOptions) => {
66
- // Detect if user wants wizard (no feature flags provided)
67
- const hasFeatureFlags =
68
- options.enableChunking ||
69
- options.enableExtractability ||
70
- options.enableHallucination ||
71
- options.enableLlm ||
72
- options.llmProvider;
73
-
74
- // If interactive mode and no feature flags, show wizard
75
- if (options.output === 'interactive' && !hasFeatureFlags) {
76
- const originalConsoleError = console.error;
77
- const originalConsoleWarn = console.warn;
78
- console.error = () => {};
79
- console.warn = () => {};
80
-
81
- let auditConfig: AuditConfig | null = null;
82
-
83
- // Show setup wizard
84
- const wizardRender = render(
85
- React.createElement(SetupWizard, {
86
- initialUrl: url,
87
- onComplete: (config: AuditConfig) => {
88
- auditConfig = config;
89
- },
90
- })
91
- );
92
-
93
- // Wait for wizard to complete
94
- await wizardRender.waitUntilExit();
95
-
96
- if (!auditConfig) {
97
- console.error = originalConsoleError;
98
- console.warn = originalConsoleWarn;
99
- console.log('\nAudit cancelled.');
100
- process.exit(0);
101
- }
102
-
103
- // Continue with audit using wizard config
104
- url = auditConfig.url;
105
- options.enableChunking = auditConfig.enableChunking;
106
- options.enableExtractability = auditConfig.enableExtractability;
107
- options.enableHallucination = auditConfig.enableHallucination;
108
- options.enableLlm = auditConfig.enableLlm;
109
- options.llmProvider = auditConfig.llmProvider;
110
- options.llmModel = auditConfig.llmModel;
111
- options.llmApiKey = auditConfig.llmApiKey;
112
- options.llmBaseUrl = auditConfig.llmBaseUrl;
113
- }
114
-
115
- // Check if interactive mode
116
- if (options.output === 'interactive') {
117
- // Suppress console.error and console.warn in interactive mode for cleaner UI
118
- const originalConsoleError = console.error;
119
- const originalConsoleWarn = console.warn;
120
- console.error = () => {}; // Suppress all console.error calls
121
- console.warn = () => {}; // Suppress all console.warn calls
122
-
123
- // Show loading UI
124
- const { waitUntilExit, clear, rerender } = render(
125
- React.createElement(AuditReportUI, {
126
- url,
127
- result: {},
128
- aiReadiness: {},
129
- loading: true,
130
- currentStep: 'Starting audit...',
131
- })
132
- );
133
-
134
- try {
135
- // Validate URL
136
- const urlObj = new URL(url);
137
-
138
- // Build scan options
139
- const scanOptions: ScanOptions = {
140
- maxChunkTokens: options.maxChunkTokens,
141
- chunkingStrategy: options.chunkingStrategy,
142
- enableChunking: options.enableChunking,
143
- enableExtractability: options.enableExtractability,
144
- enableHallucinationDetection: options.enableHallucination,
145
- enableLLM: options.enableLlm,
146
- minImpactScore: options.minImpact,
147
- minConfidence: options.minConfidence,
148
- maxIssues: options.maxIssues,
149
- };
150
-
151
- // Configure LLM if enabled
152
- if (options.enableLlm && options.llmProvider) {
153
- scanOptions.llmConfig = {
154
- provider: options.llmProvider as any,
155
- model: options.llmModel,
156
- baseUrl: options.llmBaseUrl,
157
- apiKey: options.llmApiKey,
158
- };
159
- }
160
-
161
- // Update loading step
162
- rerender(
163
- React.createElement(AuditReportUI, {
164
- url: urlObj.href,
165
- result: {},
166
- aiReadiness: {},
167
- loading: true,
168
- currentStep: 'Scanning page...',
169
- })
170
- );
171
-
172
- // Run the audit
173
- const result = await analyzeUrlWithRules(url, scanOptions);
174
-
175
- // Update loading step
176
- rerender(
177
- React.createElement(AuditReportUI, {
178
- url: urlObj.href,
179
- result,
180
- aiReadiness: {},
181
- loading: true,
182
- currentStep: 'Calculating AI readiness scores...',
183
- })
184
- );
185
-
186
- // Calculate AI readiness
187
- const aiReadiness = calculateAIReadiness(result);
188
-
189
- // Clear loading and show results
190
- clear();
191
- const finalRender = render(
192
- React.createElement(AuditReportUI, {
193
- url: urlObj.href,
194
- result,
195
- aiReadiness,
196
- loading: false,
197
- })
198
- );
199
-
200
- // Wait for user to exit
201
- await finalRender.waitUntilExit();
202
-
203
- // Restore console methods
204
- console.error = originalConsoleError;
205
- console.warn = originalConsoleWarn;
206
-
207
- // Check threshold
208
- if (options.threshold !== undefined) {
209
- const overallScore = aiReadiness.overall;
210
- if (overallScore !== undefined && overallScore < options.threshold) {
211
- process.exit(1);
212
- }
213
- }
214
- } catch (error) {
215
- // Restore console methods before showing error
216
- console.error = originalConsoleError;
217
- console.warn = originalConsoleWarn;
218
-
219
- clear();
220
- // Show error in UI format instead of console.error
221
- console.log('\n' + chalk.bold.red('โŒ Audit Failed'));
222
- console.log(chalk.red('โ”€'.repeat(70)));
223
- console.log(chalk.red(error instanceof Error ? error.message : String(error)));
224
- console.log('\n' + chalk.dim('Please check the URL and your configuration.'));
225
- process.exit(1);
226
- }
227
- return;
228
- }
229
-
230
- // Non-interactive mode - original logic
231
- const spinner = ora('Starting audit...').start();
232
-
233
- try {
234
- // Validate URL
235
- const urlObj = new URL(url);
236
- spinner.text = `Auditing ${chalk.cyan(urlObj.href)}...`;
237
-
238
- // Build scan options
239
- const scanOptions: ScanOptions = {
240
- maxChunkTokens: options.maxChunkTokens,
241
- chunkingStrategy: options.chunkingStrategy,
242
- enableChunking: options.enableChunking,
243
- enableExtractability: options.enableExtractability,
244
- enableHallucinationDetection: options.enableHallucination,
245
- enableLLM: options.enableLlm,
246
- minImpactScore: options.minImpact,
247
- minConfidence: options.minConfidence,
248
- maxIssues: options.maxIssues,
249
- };
250
-
251
- // Configure LLM if enabled
252
- if (options.enableLlm && options.llmProvider) {
253
- scanOptions.llmConfig = {
254
- provider: options.llmProvider as any,
255
- model: options.llmModel,
256
- baseUrl: options.llmBaseUrl,
257
- apiKey: options.llmApiKey,
258
- };
259
- }
260
-
261
- // Run the audit
262
- spinner.text = 'Scanning page...';
263
- const result = await analyzeUrlWithRules(url, scanOptions);
264
-
265
- spinner.text = 'Calculating scores...';
266
- const aiReadiness = calculateAIReadiness(result);
267
-
268
- // Format the report
269
- spinner.text = 'Generating report...';
270
- const auditReportJson = exportAuditReport(result);
271
- const auditReport = JSON.parse(auditReportJson);
272
-
273
- // Save results
274
- const outputDir = resolve(process.cwd(), '.ai-lighthouse');
275
- if (!existsSync(outputDir)) {
276
- await mkdir(outputDir, { recursive: true });
277
- }
278
-
279
- const timestamp = new Date().toISOString().replace(/:/g, '-').split('.')[0];
280
- const baseFilename = `audit_${new URL(url).hostname}_${timestamp}`;
281
-
282
- // Handle different output formats
283
- if (options.output === 'json') {
284
- const jsonPath = join(outputDir, `${baseFilename}.json`);
285
- await writeFile(jsonPath, auditReportJson);
286
- spinner.succeed(chalk.green('Audit complete!'));
287
- console.log(chalk.dim(`Report saved to: ${jsonPath}`));
288
- } else if (options.output === 'html') {
289
- const htmlPath = join(outputDir, `${baseFilename}.html`);
290
- const html = generateHTMLReport(auditReport, aiReadiness, result);
291
- await writeFile(htmlPath, html);
292
- spinner.succeed(chalk.green('Audit complete!'));
293
- console.log(chalk.dim(`HTML report saved to: ${htmlPath}`));
294
- console.log(chalk.dim(`๐Ÿ’ก Tip: Open the HTML file and use your browser's "Print > Save as PDF" to export as PDF`));
295
- } else if (options.output === 'pdf') {
296
- spinner.text = 'Generating PDF...';
297
- const pdfPath = join(outputDir, `${baseFilename}.pdf`);
298
- const html = generateHTMLReport(auditReport, aiReadiness, result);
299
-
300
- const file = { content: html };
301
- const pdfOptions = {
302
- format: 'A4',
303
- printBackground: false,
304
- };
305
-
306
- const pdfBuffer = await html_to_pdf.generatePdf(file, pdfOptions);
307
- await writeFile(pdfPath, pdfBuffer);
308
-
309
- spinner.succeed(chalk.green('PDF generated!'));
310
- console.log(chalk.dim(`PDF report saved to: ${pdfPath}`));
311
- } else if (options.output === 'lhr') {
312
- const lhrPath = join(outputDir, `${baseFilename}.lhr.json`);
313
- const lhr = convertToLighthouseFormat(auditReport);
314
- await writeFile(lhrPath, JSON.stringify(lhr, null, 2));
315
- spinner.succeed(chalk.green('Audit complete!'));
316
- console.log(chalk.dim(`Lighthouse-compatible report saved to: ${lhrPath}`));
317
- } else if (options.output === 'csv') {
318
- const csvPath = join(outputDir, `${baseFilename}.csv`);
319
- const csv = generateCSVReport(auditReport);
320
- await writeFile(csvPath, csv);
321
- spinner.succeed(chalk.green('Audit complete!'));
322
- console.log(chalk.dim(`CSV report saved to: ${csvPath}`));
323
- }
324
-
325
- // Display summary
326
- console.log('\n' + chalk.bold('๐Ÿ“Š AI Readiness Summary'));
327
- console.log(formatAIReadinessReport(aiReadiness));
328
-
329
- console.log('\n' + chalk.bold('๐Ÿ“ˆ Technical Scores'));
330
- console.log(generateScoringSummary(result.scoring!));
331
-
332
- // Display comprehensive report with all website data
333
- console.log('\n' + chalk.bold('โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•'));
334
- console.log(chalk.bold.cyan(' COMPREHENSIVE ANALYSIS '));
335
- console.log(chalk.bold('โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•'));
336
- console.log(formatComprehensiveReport(result, aiReadiness));
337
-
338
- // Display all issues
339
- if (result.issues && result.issues.length > 0) {
340
- console.log('\n' + formatDetailedIssues(result.issues));
341
- }
342
-
343
- // Check threshold
344
- if (options.threshold !== undefined) {
345
- const overallScore = (auditReport as any)?.scores?.overall;
346
- if (overallScore !== undefined && overallScore < options.threshold) {
347
- console.log(chalk.red(`\nโŒ Score ${overallScore} is below threshold ${options.threshold}`));
348
- process.exit(1);
349
- } else if (overallScore !== undefined) {
350
- console.log(chalk.green(`\nโœ… Score ${overallScore} meets threshold ${options.threshold}`));
351
- }
352
- }
353
-
354
- } catch (error) {
355
- spinner.fail(chalk.red('Audit failed'));
356
- if (error instanceof Error) {
357
- console.error(chalk.red(error.message));
358
- }
359
- process.exit(1);
360
- }
361
- });
362
- }
363
-
364
- function generateHTMLReport(report: any, aiReadiness: any, scanResult: any): string {
365
- return `<!DOCTYPE html>
366
- <html lang="en">
367
- <head>
368
- <meta charset="UTF-8">
369
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
370
- <title>AI Lighthouse Report - ${report.input?.requested_url}</title>
371
- <style>
372
- * { margin: 0; padding: 0; box-sizing: border-box; }
373
- body {
374
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
375
- line-height: 1.6;
376
- color: #333;
377
- background: #f5f5f5;
378
- padding: 20px;
379
- }
380
- .container { color: #333; max-width: 1200px; margin: 0 auto; background: white; padding: 40px; border-radius: 8px; }
381
- h1 { color: #2563eb; margin-bottom: 10px; font-size: 2em; }
382
- h2 { color: #1e40af; margin-top: 30px; margin-bottom: 15px; border-bottom: 2px solid #dbeafe; padding-bottom: 10px; }
383
- h3 { color: #1e40af; margin-top: 20px; margin-bottom: 10px; font-size: 1.2em; }
384
- .header { margin-bottom: 30px; }
385
- .url { color: #64748b; font-size: 0.95em; }
386
- .timestamp { color: #94a3b8; font-size: 0.85em; }
387
-
388
- .ai-readiness-banner {
389
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
390
- color: white;
391
- padding: 30px;
392
- border-radius: 8px;
393
- margin: 20px 0;
394
- box-shadow: 0 4px 6px rgba(0,0,0,0.1);
395
- }
396
- .ai-readiness-banner h2 { color: white; border: none; margin: 0 0 20px 0; }
397
- .overall-score { font-size: 3em; font-weight: bold; margin: 10px 0; }
398
- .grade-badge { background: rgba(255,255,255,0.2); padding: 8px 16px; border-radius: 20px; display: inline-block; margin: 10px 0; }
399
- .agent-perspective { background: rgba(255,255,255,0.1); padding: 20px; border-radius: 8px; margin: 20px 0; }
400
- .agent-status { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px; margin: 15px 0; }
401
- .agent-status-item { display: flex; align-items: center; gap: 10px; }
402
-
403
- .scores { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 20px; margin: 20px 0; }
404
- .score-card {
405
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
406
- color: white;
407
- padding: 20px;
408
- border-radius: 8px;
409
- box-shadow: 0 4px 6px rgba(0,0,0,0.1);
410
- }
411
- .score-card h3 { font-size: 0.9em; opacity: 0.9; margin-bottom: 10px; }
412
- .score-value { font-size: 2.5em; font-weight: bold; }
413
- .grade { font-size: 1.2em; opacity: 0.8; margin-left: 10px; }
414
-
415
- .dimensions { display: grid; gap: 15px; margin: 20px 0; }
416
- .dimension { background: #f8fafc; border-left: 4px solid #cbd5e1; padding: 20px; border-radius: 4px; }
417
- .dimension.excellent { border-left-color: #10b981; }
418
- .dimension.good { border-left-color: #84cc16; }
419
- .dimension.needs-work { border-left-color: #f59e0b; }
420
- .dimension.critical { border-left-color: #dc2626; }
421
- .dimension-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; }
422
- .dimension-name { font-weight: 600; font-size: 1.1em; color: #1e293b; }
423
- .dimension-score { font-size: 1.5em; font-weight: bold; }
424
- .dimension-level { color: #64748b; font-size: 0.9em; margin-bottom: 10px; text-transform: uppercase; }
425
- .dimension-recommendations { margin-top: 10px; padding-left: 20px; }
426
- .dimension-recommendations li { margin: 5px 0; color: #475569; }
427
-
428
- .quick-wins { background: #fffbeb; border: 2px solid #fbbf24; padding: 20px; border-radius: 8px; margin: 20px 0; }
429
- .quick-wins h3 { color: #92400e; margin-top: 0; }
430
- .quick-win { background: white; padding: 15px; margin: 10px 0; border-radius: 4px; border-left: 3px solid #f59e0b; }
431
- .quick-win-header { font-weight: 600; color: #1e293b; margin-bottom: 8px; }
432
- .quick-win-meta { font-size: 0.85em; color: #64748b; }
433
-
434
- .priorities { margin: 20px 0; }
435
- .priority-section { background: #f8fafc; padding: 20px; border-radius: 8px; margin: 15px 0; }
436
- .priority-section.immediate { border-left: 4px solid #dc2626; }
437
- .priority-section.short-term { border-left: 4px solid #f59e0b; }
438
- .priority-section.long-term { border-left: 4px solid #3b82f6; }
439
- .priority-item { margin: 10px 0; padding-left: 20px; }
440
-
441
- .issues { margin: 20px 0; }
442
- .issue {
443
- background: #f8fafc;
444
- border-left: 4px solid #cbd5e1;
445
- padding: 15px;
446
- margin-bottom: 15px;
447
- border-radius: 4px;
448
- }
449
- .issue.critical { border-left-color: #dc2626; background: #fef2f2; }
450
- .issue.high { border-left-color: #ea580c; background: #fff7ed; }
451
- .issue.medium { border-left-color: #f59e0b; background: #fffbeb; }
452
- .issue.low { border-left-color: #84cc16; background: #f7fee7; }
453
- .issue-title { font-weight: 600; color: #1e293b; margin-bottom: 8px; }
454
- .issue-meta { font-size: 0.85em; color: #64748b; margin-bottom: 8px; }
455
- .issue-desc { color: #475569; margin-bottom: 8px; }
456
- .issue-fix { color: #0f766e; background: #f0fdfa; padding: 10px; border-radius: 4px; font-size: 0.9em; }
457
-
458
- .entity-list { display: grid; gap: 15px; }
459
- .entity { background: #f0f9ff; border: 1px solid #bae6fd; padding: 15px; border-radius: 4px; }
460
- .entity-name { font-weight: 600; color: #0c4a6e; }
461
- .entity-type { color: #0369a1; font-size: 0.85em; }
462
-
463
- .print-button {
464
- background: #2563eb;
465
- color: white;
466
- padding: 12px 24px;
467
- border: none;
468
- border-radius: 6px;
469
- cursor: pointer;
470
- font-size: 1em;
471
- margin: 20px 0;
472
- box-shadow: 0 2px 4px rgba(0,0,0,0.1);
473
- }
474
- .print-button:hover { background: #1e40af; }
475
-
476
- @media print {
477
- body { background: white; padding: 0; }
478
- .container { box-shadow: none; }
479
- .print-button { display: none; }
480
- }
481
- </style>
482
- </head>
483
- <body>
484
- <div class="container">
485
- <div class="header">
486
- <h1>๐Ÿšจ AI Lighthouse Report</h1>
487
- <div class="url">${report.input?.requested_url}</div>
488
- <div class="timestamp">Generated: ${new Date(report.scanned_at).toLocaleString()}</div>
489
- </div>
490
-
491
- ${aiReadiness ? `
492
- <div class="ai-readiness-banner">
493
- <h2>๐Ÿค– AI Readiness Assessment</h2>
494
- <div class="overall-score">${Math.round(aiReadiness.overall)}/100</div>
495
- <div class="grade-badge">Grade: ${aiReadiness.grade}</div>
496
- <div style="margin: 10px 0; opacity: 0.9;">
497
- Top ${aiReadiness.benchmark?.topPercentile || 0}% of sites โ€ข
498
- Improvement potential: +${Math.round(aiReadiness.benchmark?.improvement || 0)} points to best-in-class
499
- </div>
500
-
501
- <div class="agent-perspective">
502
- <strong>AI Agent Perspective:</strong>
503
- <div class="agent-status">
504
- <div class="agent-status-item">
505
- <span>${aiReadiness.aiPerspective?.canUnderstand ? 'โœ…' : 'โŒ'}</span>
506
- <span>Can Understand</span>
507
- </div>
508
- <div class="agent-status-item">
509
- <span>${aiReadiness.aiPerspective?.canExtract ? 'โœ…' : 'โŒ'}</span>
510
- <span>Can Extract</span>
511
- </div>
512
- <div class="agent-status-item">
513
- <span>${aiReadiness.aiPerspective?.canIndex ? 'โœ…' : 'โŒ'}</span>
514
- <span>Can Index</span>
515
- </div>
516
- <div class="agent-status-item">
517
- <span>${aiReadiness.aiPerspective?.canAnswer ? 'โœ…' : 'โŒ'}</span>
518
- <span>Can Answer Questions</span>
519
- </div>
520
- </div>
521
- <div style="margin-top: 15px;">
522
- <strong>Confidence Level:</strong> ${Math.round((aiReadiness.aiPerspective?.confidence || 0) * 100)}%
523
- </div>
524
- </div>
525
-
526
- ${(aiReadiness.aiPerspective?.mainBlockers || []).length > 0 ? `
527
- <div style="margin-top: 20px;">
528
- <strong>Main Blockers:</strong>
529
- <ul style="margin: 10px 0; padding-left: 20px;">
530
- ${(aiReadiness.aiPerspective?.mainBlockers || []).map((blocker: string) => `<li>${blocker}</li>`).join('')}
531
- </ul>
532
- </div>
533
- ` : ''}
534
- </div>
535
- ` : ''}
536
-
537
- <h2>๐Ÿ“Š Technical Scores</h2>
538
- <div class="scores">
539
- <div class="score-card">
540
- <h3>Overall Score</h3>
541
- <div>
542
- <span class="score-value">${report.scores?.overall || 0}</span>
543
- <span class="grade">${getLetterGrade(report.scores?.overall || 0)}</span>
544
- </div>
545
- </div>
546
- <div class="score-card">
547
- <h3>AI Readiness</h3>
548
- <div class="score-value">${report.scores?.ai_readiness || 0}</div>
549
- </div>
550
- <div class="score-card">
551
- <h3>Crawlability</h3>
552
- <div class="score-value">${report.scores?.crawlability || 0}</div>
553
- </div>
554
- <div class="score-card">
555
- <h3>Content Clarity</h3>
556
- <div class="score-value">${report.scores?.content_clarity || 0}</div>
557
- </div>
558
- <div class="score-card">
559
- <h3>Schema Coverage</h3>
560
- <div class="score-value">${report.scores?.schema_coverage || 0}</div>
561
- </div>
562
- <div class="score-card">
563
- <h3>Structure</h3>
564
- <div class="score-value">${report.scores?.structure || 0}</div>
565
- </div>
566
- </div>
567
-
568
- ${aiReadiness?.dimensions ? `
569
- <h2>๐ŸŽฏ Dimension Analysis</h2>
570
- <div class="dimensions">
571
- ${Object.entries(aiReadiness.dimensions).map(([key, dim]: [string, any]) => `
572
- <div class="dimension ${dim.status}">
573
- <div class="dimension-header">
574
- <div class="dimension-name">${getEmojiForDimension(key)} ${formatDimensionName(key)}</div>
575
- <div class="dimension-score">${Math.round(dim.score)}/100</div>
576
- </div>
577
- <div class="dimension-level">${dim.status}</div>
578
- ${dim.strengths && dim.strengths.length > 0 ? `
579
- <div><strong>Strengths:</strong> ${dim.strengths.join(', ')}</div>
580
- ` : ''}
581
- ${dim.weaknesses && dim.weaknesses.length > 0 ? `
582
- <div style="margin-top: 8px;"><strong>Weaknesses:</strong> ${dim.weaknesses.join(', ')}</div>
583
- ` : ''}
584
- ${dim.recommendation ? `
585
- <div class="dimension-recommendations">
586
- <div style="margin-top: 10px;">โ†’ ${dim.recommendation}</div>
587
- </div>
588
- ` : ''}
589
- </div>
590
- `).join('')}
591
- </div>
592
- ` : ''}
593
-
594
- ${aiReadiness?.quickWins && aiReadiness.quickWins.length > 0 ? `
595
- <div class="quick-wins">
596
- <h3>โšก Quick Wins (High Impact, Low Effort)</h3>
597
- ${aiReadiness.quickWins.slice(0, 5).map((win: any, idx: number) => `
598
- <div class="quick-win">
599
- <div class="quick-win-header">${idx + 1}. ${win.issue}</div>
600
- <div class="quick-win-meta">Impact: ${win.impact} | Effort: ${win.effort}</div>
601
- <div style="margin-top: 8px; color: #0f766e;">โ†’ ${win.fix}</div>
602
- </div>
603
- `).join('')}
604
- </div>
605
- ` : ''}
606
-
607
- ${aiReadiness?.roadmap ? `
608
- <h2>๐Ÿ“‹ Priority Roadmap</h2>
609
- <div class="priorities">
610
- ${aiReadiness.roadmap.immediate && aiReadiness.roadmap.immediate.length > 0 ? `
611
- <div class="priority-section immediate">
612
- <h3>๐Ÿ”ด Immediate (&lt; 1 day)</h3>
613
- ${aiReadiness.roadmap.immediate.slice(0, 5).map((item: string) => `
614
- <div class="priority-item">โ€ข ${item}</div>
615
- `).join('')}
616
- </div>
617
- ` : ''}
618
-
619
- ${aiReadiness.roadmap.shortTerm && aiReadiness.roadmap.shortTerm.length > 0 ? `
620
- <div class="priority-section short-term">
621
- <h3>๐ŸŸก Short-term (1-7 days)</h3>
622
- ${aiReadiness.roadmap.shortTerm.slice(0, 5).map((item: string) => `
623
- <div class="priority-item">โ€ข ${item}</div>
624
- `).join('')}
625
- </div>
626
- ` : ''}
627
-
628
- ${aiReadiness.roadmap.longTerm && aiReadiness.roadmap.longTerm.length > 0 ? `
629
- <div class="priority-section long-term">
630
- <h3>๐Ÿ”ต Long-term (&gt; 7 days)</h3>
631
- ${aiReadiness.roadmap.longTerm.slice(0, 5).map((item: string) => `
632
- <div class="priority-item">โ€ข ${item}</div>
633
- `).join('')}
634
- </div>
635
- ` : ''}
636
- </div>
637
- ` : ''}
638
-
639
- ${scanResult?.llm ? `
640
- <h2>๐Ÿ“ AI Understanding Analysis</h2>
641
- <div style="background: #f0f9ff; border: 2px solid #0ea5e9; padding: 20px; border-radius: 8px; margin: 20px 0;">
642
- <div style="margin-bottom: 15px;">
643
- <strong>Summary:</strong> ${scanResult.llm.summary || 'N/A'}
644
- </div>
645
- ${scanResult.llm.pageType ? `
646
- <div style="margin-bottom: 15px;">
647
- <strong>๐Ÿ“„ Page Type:</strong> ${scanResult.llm.pageType}
648
- </div>
649
- ` : ''}
650
- ${scanResult.llm.keyTopics && scanResult.llm.keyTopics.length > 0 ? `
651
- <div style="margin-bottom: 15px;">
652
- <strong>๐Ÿท๏ธ Key Topics:</strong> ${scanResult.llm.keyTopics.join(', ')}
653
- </div>
654
- ` : ''}
655
- <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px; margin-top: 15px;">
656
- ${scanResult.llm.readingLevel ? `
657
- <div><strong>๐Ÿ“Š Reading Level:</strong> ${scanResult.llm.readingLevel.description}</div>
658
- ` : ''}
659
- ${scanResult.llm.sentiment ? `
660
- <div><strong>๐ŸŽญ Sentiment:</strong> ${scanResult.llm.sentiment}</div>
661
- ` : ''}
662
- ${scanResult.llm.technicalDepth ? `
663
- <div><strong>๐ŸŽฏ Technical Depth:</strong> ${scanResult.llm.technicalDepth}</div>
664
- ` : ''}
665
- </div>
666
-
667
- ${scanResult.llm.pageTypeInsights && scanResult.llm.pageTypeInsights.length > 0 ? `
668
- <div style="margin-top: 20px; background: #dbeafe; padding: 15px; border-radius: 4px; border-left: 4px solid #0ea5e9;">
669
- <div style="font-weight: 600; margin-bottom: 10px;">๐Ÿ’ก AI-Generated Insights for ${scanResult.llm.pageType || 'This Page'}</div>
670
- <ul style="margin: 0; padding-left: 20px;">
671
- ${scanResult.llm.pageTypeInsights.map((insight: string) => `<li style="margin: 5px 0;">${insight}</li>`).join('')}
672
- </ul>
673
- </div>
674
- ` : ''}
675
-
676
-
677
- ${scanResult.llm.topEntities && scanResult.llm.topEntities.length > 0 ? `
678
- <div style="margin-top: 20px;">
679
- <strong>๐Ÿ” Key Entities:</strong>
680
- <div style="margin-top: 10px; display: grid; gap: 10px;">
681
- ${scanResult.llm.topEntities.slice(0, 5).map((entity: any) => `
682
- <div style="background: white; padding: 10px; border-radius: 4px; border-left: 3px solid #0ea5e9;">
683
- <strong>${entity.name}</strong> (${entity.type}) - ${Math.round((entity.relevance || 0) * 100)}% relevance
684
- </div>
685
- `).join('')}
686
- </div>
687
- </div>
688
- ` : ''}
689
-
690
- ${scanResult.llm.questions && scanResult.llm.questions.length > 0 ? `
691
- <div style="margin-top: 20px;">
692
- <strong>โ“ Key Questions AI Can Answer:</strong>
693
- <ol style="margin-top: 10px; padding-left: 25px;">
694
- ${scanResult.llm.questions.slice(0, 5).map((q: any) => `
695
- <li style="margin: 8px 0;"><span style="text-transform: uppercase; font-size: 0.8em; background: #dbeafe; padding: 2px 6px; border-radius: 3px;">${q.difficulty}</span> ${q.question}</li>
696
- `).join('')}
697
- </ol>
698
- </div>
699
- ` : ''}
700
-
701
- ${scanResult.llm.suggestedFAQ && scanResult.llm.suggestedFAQ.length > 0 ? `
702
- <div style="margin-top: 20px;">
703
- <strong>๐Ÿ’ก Suggested FAQs:</strong>
704
- <div style="margin-top: 10px; display: grid; gap: 15px;">
705
- ${scanResult.llm.suggestedFAQ.filter((f: any) => f.importance === 'high').slice(0, 3).map((faq: any) => `
706
- <div style="background: white; padding: 15px; border-radius: 4px; border-left: 3px solid #f59e0b;">
707
- <div style="font-weight: 600; margin-bottom: 5px;">Q: ${faq.question}</div>
708
- <div style="color: #64748b;">A: ${faq.suggestedAnswer}</div>
709
- </div>
710
- `).join('')}
711
- </div>
712
- </div>
713
- ` : ''}
714
- </div>
715
- ` : ''}
716
-
717
- ${scanResult?.chunking ? `
718
- <h2>๐Ÿ“„ Content Chunking Analysis</h2>
719
- <div style="background: #f0fdf4; border: 2px solid #10b981; padding: 20px; border-radius: 8px; margin: 20px 0;">
720
- <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px;">
721
- <div><strong>Strategy:</strong> ${scanResult.chunking.chunkingStrategy}</div>
722
- <div><strong>Total Chunks:</strong> ${scanResult.chunking.totalChunks}</div>
723
- <div><strong>Avg Tokens/Chunk:</strong> ${scanResult.chunking.averageTokensPerChunk}</div>
724
- <div><strong>Avg Noise:</strong> ${(scanResult.chunking.averageNoiseRatio * 100).toFixed(1)}%</div>
725
- </div>
726
- </div>
727
- ` : ''}
728
-
729
- ${scanResult?.extractability ? `
730
- <h2>๐Ÿ”„ Extractability Analysis</h2>
731
- <div style="background: #fef3c7; border: 2px solid #f59e0b; padding: 20px; border-radius: 8px; margin: 20px 0;">
732
- <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px; margin-bottom: 15px;">
733
- <div><strong>Overall Score:</strong> ${scanResult.extractability.score.extractabilityScore}/100</div>
734
- <div><strong>Server-Rendered:</strong> ${scanResult.extractability.score.serverRenderedPercent}%</div>
735
- </div>
736
- <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px;">
737
- <div><strong>Text Extractable:</strong> ${scanResult.extractability.contentTypes.text.percentage}%</div>
738
- <div><strong>Images Extractable:</strong> ${scanResult.extractability.contentTypes.images.percentage}%</div>
739
- </div>
740
- </div>
741
- ` : ''}
742
-
743
- ${scanResult?.hallucinationReport ? `
744
- <h2>โš ๏ธ Hallucination Risk Assessment</h2>
745
- <div style="background: #fef2f2; border: 2px solid #ef4444; padding: 20px; border-radius: 8px; margin: 20px 0;">
746
- <div style="font-size: 1.5em; font-weight: bold; margin-bottom: 15px;">
747
- Risk Score: ${scanResult.hallucinationReport.hallucinationRiskScore}/100
748
- </div>
749
-
750
- ${scanResult.hallucinationReport.factCheckSummary ? `
751
- <div style="margin-top: 20px; background: white; padding: 15px; border-radius: 4px;">
752
- <strong>๐Ÿ“Š Fact Check Summary:</strong>
753
- <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 10px; margin-top: 10px;">
754
- <div>โœ… Verified: ${scanResult.hallucinationReport.factCheckSummary.verifiedFacts}</div>
755
- <div>โ“ Unverified: ${scanResult.hallucinationReport.factCheckSummary.unverifiedFacts}</div>
756
- <div>โš ๏ธ Contradictions: ${scanResult.hallucinationReport.factCheckSummary.contradictions}</div>
757
- <div>๐Ÿค” Ambiguities: ${scanResult.hallucinationReport.factCheckSummary.ambiguities}</div>
758
- </div>
759
- </div>
760
- ` : ''}
761
-
762
- ${scanResult.hallucinationReport.factCheckSummary && scanResult.hallucinationReport.factCheckSummary.unverifiedFacts > 0 ? `
763
- <div style="margin-top: 15px; background: #fef9c3; border-left: 4px solid #eab308; padding: 15px; border-radius: 4px;">
764
- <div style="display: flex; align-items: start; gap: 10px;">
765
- <span style="font-size: 1.5em;">๐Ÿ’ก</span>
766
- <div>
767
- <div style="font-weight: 600; margin-bottom: 8px;">Why Some Facts Can't Be Verified</div>
768
- <div style="color: #854d0e; margin-bottom: 8px;">
769
- AI systems need external sources to verify claims. When information lacks citations, links, or
770
- references, it becomes difficult to confirm accuracy, increasing the risk of AI hallucination or misinformation.
771
- </div>
772
- <div style="font-weight: 600; margin-top: 12px; margin-bottom: 6px;">Best Practices:</div>
773
- <ul style="margin: 0; padding-left: 20px; color: #854d0e;">
774
- <li>Add source links directly in or near claim text</li>
775
- <li>Include dates for time-sensitive information</li>
776
- <li>Link to authoritative sources (research, official docs)</li>
777
- <li>Provide context for statistics and data points</li>
778
- <li>Use schema.org markup to specify citations</li>
779
- </ul>
780
- </div>
781
- </div>
782
- </div>
783
- ` : ''}
784
-
785
- ${scanResult.hallucinationReport.triggers && scanResult.hallucinationReport.triggers.length > 0 ? `
786
- <div style="margin-top: 20px;">
787
- <strong>๐Ÿšจ Identified Triggers:</strong>
788
- <div style="margin-top: 10px; display: grid; gap: 10px;">
789
- ${scanResult.hallucinationReport.triggers.filter((t: any) => t.severity === 'high' || t.severity === 'critical').slice(0, 5).map((trigger: any) => `
790
- <div style="background: white; padding: 15px; border-radius: 4px; border-left: 3px solid #dc2626;">
791
- <div style="font-weight: 600; text-transform: uppercase; font-size: 0.85em; color: #dc2626;">${trigger.type} - ${trigger.severity}</div>
792
- <div style="margin-top: 5px;">${trigger.description}</div>
793
- <div style="margin-top: 5px; font-size: 0.9em; color: #64748b;">Confidence: ${Math.round((trigger.confidence || 0) * 100)}%</div>
794
- </div>
795
- `).join('')}
796
- </div>
797
- </div>
798
- ` : ''}
799
-
800
- ${scanResult.hallucinationReport.recommendations && scanResult.hallucinationReport.recommendations.length > 0 ? `
801
- <div style="margin-top: 20px;">
802
- <strong>๐Ÿ’ก Recommendations:</strong>
803
- <ul style="margin-top: 10px; padding-left: 25px;">
804
- ${scanResult.hallucinationReport.recommendations.slice(0, 3).map((rec: string) => `<li style="margin: 5px 0;">${rec}</li>`).join('')}
805
- </ul>
806
- </div>
807
- ` : ''}
808
- </div>
809
- ` : ''}
810
-
811
- ${scanResult?.mirrorReport ? `
812
- <h2>๐Ÿ” AI Misunderstanding Check</h2>
813
- <div style="background: #faf5ff; border: 2px solid #a855f7; padding: 20px; border-radius: 8px; margin: 20px 0;">
814
- <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px; margin-bottom: 15px;">
815
- <div><strong>๐ŸŽฏ Alignment Score:</strong> ${scanResult.mirrorReport.summary.alignmentScore}/100</div>
816
- <div><strong>๐Ÿ“– Clarity Score:</strong> ${scanResult.mirrorReport.summary.clarityScore}/100</div>
817
- <div><strong>โš ๏ธ Critical Issues:</strong> ${scanResult.mirrorReport.summary.critical}</div>
818
- <div><strong>๐ŸŸก Major Issues:</strong> ${scanResult.mirrorReport.summary.major}</div>
819
- </div>
820
-
821
- ${scanResult.mirrorReport.llmInterpretation ? `
822
- <div style="background: #dbeafe; padding: 16px; border-radius: 8px; margin-bottom: 16px; border: 1px solid #3b82f6;">
823
- <div style="font-weight: bold; margin-bottom: 12px; color: #1e40af;">
824
- ๐Ÿค– What AI Actually Understood (${Math.round(scanResult.mirrorReport.llmInterpretation.confidence * 100)}% confident)
825
- </div>
826
-
827
- <div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px;">
828
- ${scanResult.mirrorReport.llmInterpretation.productName ? `
829
- <div>
830
- <div style="font-size: 11px; font-weight: 600; color: #64748b; margin-bottom: 4px;">PRODUCT NAME</div>
831
- <div style="font-size: 14px;">${scanResult.mirrorReport.llmInterpretation.productName}</div>
832
- </div>
833
- ` : ''}
834
-
835
- ${scanResult.mirrorReport.llmInterpretation.purpose ? `
836
- <div style="grid-column: 1 / -1;">
837
- <div style="font-size: 11px; font-weight: 600; color: #64748b; margin-bottom: 4px;">MAIN PURPOSE</div>
838
- <div style="font-size: 14px;">${scanResult.mirrorReport.llmInterpretation.purpose}</div>
839
- </div>
840
- ` : ''}
841
-
842
- ${scanResult.mirrorReport.llmInterpretation.valueProposition ? `
843
- <div style="grid-column: 1 / -1;">
844
- <div style="font-size: 11px; font-weight: 600; color: #64748b; margin-bottom: 4px;">๐Ÿ’Ž UNIQUE VALUE</div>
845
- <div style="font-size: 14px; font-weight: 600; color: #7c3aed;">${scanResult.mirrorReport.llmInterpretation.valueProposition}</div>
846
- </div>
847
- ` : ''}
848
-
849
- ${scanResult.mirrorReport.llmInterpretation.keyBenefits && scanResult.mirrorReport.llmInterpretation.keyBenefits.length > 0 ? `
850
- <div>
851
- <div style="font-size: 11px; font-weight: 600; color: #64748b; margin-bottom: 4px;">KEY BENEFITS</div>
852
- <ul style="font-size: 14px; margin: 0; padding-left: 20px;">
853
- ${scanResult.mirrorReport.llmInterpretation.keyBenefits.map((b: string) => `<li style="margin: 4px 0;">${b}</li>`).join('')}
854
- </ul>
855
- </div>
856
- ` : ''}
857
-
858
- ${scanResult.mirrorReport.llmInterpretation.keyFeatures && scanResult.mirrorReport.llmInterpretation.keyFeatures.length > 0 ? `
859
- <div>
860
- <div style="font-size: 11px; font-weight: 600; color: #64748b; margin-bottom: 4px;">KEY FEATURES</div>
861
- <ul style="font-size: 14px; margin: 0; padding-left: 20px;">
862
- ${scanResult.mirrorReport.llmInterpretation.keyFeatures.slice(0, 3).map((f: string) => `<li style="margin: 4px 0;">${f}</li>`).join('')}
863
- </ul>
864
- </div>
865
- ` : ''}
866
- </div>
867
- </div>
868
- ` : ''}
869
-
870
- ${scanResult.mirrorReport.mismatches && scanResult.mirrorReport.mismatches.length > 0 ? `
871
- <div style="margin-top: 20px;">
872
- <strong>Priority Mismatches:</strong>
873
- <div style="margin-top: 10px; display: grid; gap: 10px;">
874
- ${scanResult.mirrorReport.mismatches.filter((m: any) => m.severity === 'critical' || m.severity === 'major').slice(0, 5).map((mismatch: any) => `
875
- <div style="background: white; padding: 15px; border-radius: 4px; border-left: 3px solid ${mismatch.severity === 'critical' ? '#dc2626' : '#f59e0b'};">
876
- <div style="font-weight: 600;">${mismatch.severity === 'critical' ? '๐Ÿ”ด' : '๐ŸŸก'} ${mismatch.field}</div>
877
- <div style="margin-top: 5px; color: #64748b;">${mismatch.description}</div>
878
- <div style="margin-top: 10px; padding: 10px; background: #f0fdfa; border-radius: 4px;">
879
- <strong>Fix:</strong> ${mismatch.recommendation}
880
- </div>
881
- </div>
882
- `).join('')}
883
- </div>
884
- </div>
885
- ` : ''}
886
-
887
- ${scanResult.mirrorReport.recommendations && scanResult.mirrorReport.recommendations.length > 0 ? `
888
- <div style="margin-top: 20px;">
889
- <strong>๐Ÿ’ก Top Recommendations:</strong>
890
- <ul style="margin-top: 10px; padding-left: 25px;">
891
- ${scanResult.mirrorReport.recommendations.slice(0, 3).map((rec: string) => `<li style="margin: 5px 0;">${rec}</li>`).join('')}
892
- </ul>
893
- </div>
894
- ` : ''}
895
- </div>
896
- ` : ''}
897
-
898
- <h2>โš ๏ธ All Issues (${report.issues?.length || 0})</h2>
899
- <div class="issues">
900
- ${(report.issues || []).map((issue: any) => `
901
- <div class="issue ${issue.severity}">
902
- <div class="issue-title">${issue.message}</div>
903
- <div class="issue-meta">
904
- <span style="text-transform: uppercase; font-weight: 600;">${issue.severity}</span>
905
- ยท Impact: ${issue.impact}
906
- ยท Category: ${issue.category}
907
- </div>
908
- <div class="issue-desc">${issue.evidence || 'No additional evidence'}</div>
909
- <div class="issue-fix"><strong>Fix:</strong> ${issue.suggested_fix}</div>
910
- </div>
911
- `).join('')}
912
- </div>
913
-
914
- ${(report.entities || []).length > 0 ? `
915
- <h2>๐Ÿท๏ธ Detected Entities (${(report.entities || []).length})</h2>
916
- <div class="entity-list">
917
- ${(report.entities || []).map((entity: any) => `
918
- <div class="entity">
919
- <div class="entity-name">${entity.name}</div>
920
- <div class="entity-type">${entity.type} ยท Source: ${entity.source}</div>
921
- ${entity.description ? `<div style="margin-top: 8px; color: #334155;">${entity.description}</div>` : ''}
922
- </div>
923
- `).join('')}
924
- </div>
925
- ` : ''}
926
- </div>
927
- </body>
928
- </html>`;
929
- }
930
-
931
- function getLetterGrade(score: number): string {
932
- if (score >= 90) return 'A';
933
- if (score >= 80) return 'B';
934
- if (score >= 70) return 'C';
935
- if (score >= 60) return 'D';
936
- return 'F';
937
- }
938
-
939
- function getEmojiForDimension(name: string): string {
940
- const emojiMap: Record<string, string> = {
941
- 'contentQuality': '๐Ÿ“',
942
- 'discoverability': '๐Ÿ”',
943
- 'extractability': '๐Ÿ”„',
944
- 'comprehensibility': '๐Ÿง ',
945
- 'trustworthiness': 'โœ…',
946
- 'structured': '๐Ÿ“Š',
947
- 'semantic': '๐Ÿท๏ธ'
948
- };
949
- return emojiMap[name] || '๐Ÿ“Œ';
950
- }
951
-
952
- function formatDimensionName(key: string): string {
953
- const nameMap: Record<string, string> = {
954
- 'contentQuality': 'Content Quality',
955
- 'discoverability': 'Discoverability',
956
- 'extractability': 'Extractability',
957
- 'comprehensibility': 'Comprehensibility',
958
- 'trustworthiness': 'Trustworthiness'
959
- };
960
- return nameMap[key] || key;
961
- }
962
-
963
- function convertToLighthouseFormat(report: any): any {
964
- return {
965
- lighthouseVersion: '1.0.0',
966
- userAgent: 'AI-Lighthouse/1.0.0',
967
- fetchTime: report.scanned_at,
968
- requestedUrl: report.input?.requested_url,
969
- finalUrl: report.input?.final_url,
970
- categories: {
971
- 'ai-readiness': {
972
- id: 'ai-readiness',
973
- title: 'AI Readiness',
974
- score: (report.scores?.ai_readiness || 0) / 100,
975
- },
976
- 'crawlability': {
977
- id: 'crawlability',
978
- title: 'Crawlability',
979
- score: (report.scores?.crawlability || 0) / 100,
980
- },
981
- 'content-clarity': {
982
- id: 'content-clarity',
983
- title: 'Content Clarity',
984
- score: (report.scores?.content_clarity || 0) / 100,
985
- },
986
- },
987
- audits: (report.issues || []).reduce((acc: any, issue: any, idx: number) => {
988
- acc[`issue-${idx}`] = {
989
- id: issue.id,
990
- title: issue.message,
991
- description: issue.evidence || '',
992
- score: issue.severity === 'critical' ? 0 : issue.severity === 'high' ? 0.25 : 0.5,
993
- displayValue: issue.suggested_fix,
994
- };
995
- return acc;
996
- }, {}),
997
- };
998
- }
999
-
1000
- function generateCSVReport(report: any): string {
1001
- const headers = ['ID', 'Severity', 'Category', 'Message', 'Impact', 'Suggested Fix'];
1002
- const rows = (report.issues || []).map((issue: any) => [
1003
- issue.id,
1004
- issue.severity,
1005
- issue.category,
1006
- `"${issue.message.replace(/"/g, '""')}"`,
1007
- issue.impact,
1008
- `"${issue.suggested_fix.replace(/"/g, '""')}"`,
1009
- ]);
1010
-
1011
- return [headers.join(','), ...rows.map((r: any) => r.join(','))].join('\n');
1012
- }