claude-presentation-master 8.0.0 → 8.2.0
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.
- package/bin/cli.js +70 -8
- package/dist/index.d.mts +75 -10
- package/dist/index.d.ts +75 -10
- package/dist/index.js +670 -180
- package/dist/index.mjs +670 -180
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,30 +1,98 @@
|
|
|
1
1
|
// src/types/index.ts
|
|
2
|
-
var ValidationError = class extends Error {
|
|
2
|
+
var ValidationError = class _ValidationError extends Error {
|
|
3
3
|
constructor(errors, message = "Validation failed") {
|
|
4
|
-
|
|
4
|
+
const errorList = errors.map((e) => ` \u2022 ${e}`).join("\n");
|
|
5
|
+
const suggestions = _ValidationError.getSuggestions(errors);
|
|
6
|
+
const suggestionList = suggestions.length > 0 ? "\n\nHow to fix:\n" + suggestions.map((s) => ` \u2192 ${s}`).join("\n") : "";
|
|
7
|
+
super(`${message}
|
|
8
|
+
|
|
9
|
+
Issues found:
|
|
10
|
+
${errorList}${suggestionList}`);
|
|
5
11
|
this.errors = errors;
|
|
6
12
|
this.name = "ValidationError";
|
|
13
|
+
this.suggestions = suggestions;
|
|
14
|
+
}
|
|
15
|
+
suggestions;
|
|
16
|
+
static getSuggestions(errors) {
|
|
17
|
+
const suggestions = [];
|
|
18
|
+
for (const error of errors) {
|
|
19
|
+
if (error.includes("Content is required")) {
|
|
20
|
+
suggestions.push("Provide markdown content with at least one ## section header");
|
|
21
|
+
}
|
|
22
|
+
if (error.includes("Mode must be")) {
|
|
23
|
+
suggestions.push('Use mode: "keynote" for TED-style or mode: "business" for corporate');
|
|
24
|
+
}
|
|
25
|
+
if (error.includes("Title is required")) {
|
|
26
|
+
suggestions.push('Add a title: "Your Presentation Title" to your config');
|
|
27
|
+
}
|
|
28
|
+
if (error.includes("output format")) {
|
|
29
|
+
suggestions.push('Specify format: ["html"] or format: ["html", "pdf"]');
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return [...new Set(suggestions)];
|
|
7
33
|
}
|
|
8
34
|
};
|
|
9
|
-
var QAFailureError = class extends Error {
|
|
10
|
-
constructor(score, threshold, qaResults, message
|
|
11
|
-
|
|
35
|
+
var QAFailureError = class _QAFailureError extends Error {
|
|
36
|
+
constructor(score, threshold, qaResults, message) {
|
|
37
|
+
const topIssues = _QAFailureError.getTopIssues(qaResults);
|
|
38
|
+
const issueList = topIssues.map((i) => ` \u2022 ${i}`).join("\n");
|
|
39
|
+
const helpText = `
|
|
40
|
+
|
|
41
|
+
Score: ${score}/100 (threshold: ${threshold})
|
|
42
|
+
|
|
43
|
+
Top issues to fix:
|
|
44
|
+
${issueList}
|
|
45
|
+
|
|
46
|
+
How to improve:
|
|
47
|
+
\u2192 Ensure each slide has meaningful content (not just a title)
|
|
48
|
+
\u2192 Keep text concise - aim for <40 words per slide
|
|
49
|
+
\u2192 Use timeline/process slides for step-by-step content
|
|
50
|
+
\u2192 Add visuals for data-heavy slides`;
|
|
51
|
+
super(message || `QA score ${score} below threshold ${threshold}${helpText}`);
|
|
12
52
|
this.score = score;
|
|
13
53
|
this.threshold = threshold;
|
|
14
54
|
this.qaResults = qaResults;
|
|
15
55
|
this.name = "QAFailureError";
|
|
56
|
+
this.topIssues = topIssues;
|
|
16
57
|
}
|
|
58
|
+
topIssues;
|
|
17
59
|
getIssues() {
|
|
18
60
|
return this.qaResults.issues.map((issue) => issue.message);
|
|
19
61
|
}
|
|
62
|
+
static getTopIssues(qaResults) {
|
|
63
|
+
const issues = [];
|
|
64
|
+
if (qaResults.slideScores) {
|
|
65
|
+
for (const slide of qaResults.slideScores) {
|
|
66
|
+
if (slide.criticalIssues) {
|
|
67
|
+
issues.push(...slide.criticalIssues);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (qaResults.issues) {
|
|
72
|
+
issues.push(...qaResults.issues.map((i) => i.message));
|
|
73
|
+
}
|
|
74
|
+
return [...new Set(issues)].slice(0, 5);
|
|
75
|
+
}
|
|
20
76
|
};
|
|
21
77
|
var TemplateNotFoundError = class extends Error {
|
|
22
|
-
constructor(templatePath, message
|
|
23
|
-
super(message
|
|
78
|
+
constructor(templatePath, message) {
|
|
79
|
+
super(message || `Template not found: ${templatePath}
|
|
80
|
+
|
|
81
|
+
Available templates: title, content, timeline, process, metrics, quote, cta, thank-you`);
|
|
24
82
|
this.templatePath = templatePath;
|
|
25
83
|
this.name = "TemplateNotFoundError";
|
|
26
84
|
}
|
|
27
85
|
};
|
|
86
|
+
var KnowledgeBaseError = class extends Error {
|
|
87
|
+
constructor(field, context, message) {
|
|
88
|
+
super(message || `Knowledge base configuration error: Missing "${field}" in ${context}
|
|
89
|
+
|
|
90
|
+
Check assets/presentation-knowledge.yaml for the correct structure.`);
|
|
91
|
+
this.field = field;
|
|
92
|
+
this.context = context;
|
|
93
|
+
this.name = "KnowledgeBaseError";
|
|
94
|
+
}
|
|
95
|
+
};
|
|
28
96
|
|
|
29
97
|
// src/core/PresentationEngine.ts
|
|
30
98
|
import * as fs2 from "fs";
|
|
@@ -33,8 +101,7 @@ import * as os from "os";
|
|
|
33
101
|
|
|
34
102
|
// src/kb/KnowledgeGateway.ts
|
|
35
103
|
import { readFileSync } from "fs";
|
|
36
|
-
import { join
|
|
37
|
-
import { fileURLToPath } from "url";
|
|
104
|
+
import { join } from "path";
|
|
38
105
|
import * as yaml from "yaml";
|
|
39
106
|
|
|
40
107
|
// src/utils/Logger.ts
|
|
@@ -106,9 +173,6 @@ var logger = new Logger({
|
|
|
106
173
|
|
|
107
174
|
// src/kb/KnowledgeGateway.ts
|
|
108
175
|
function getModuleDir() {
|
|
109
|
-
if (typeof import.meta !== "undefined" && import.meta.url) {
|
|
110
|
-
return dirname(fileURLToPath(import.meta.url));
|
|
111
|
-
}
|
|
112
176
|
if (typeof __dirname !== "undefined") {
|
|
113
177
|
return __dirname;
|
|
114
178
|
}
|
|
@@ -778,7 +842,8 @@ var KnowledgeGateway = class {
|
|
|
778
842
|
// Subtitle needs more room for complete phrases - at least 10 words for keynote
|
|
779
843
|
subtitle: { maxWords: mode === "keynote" ? 12 : Math.min(20, Math.floor(maxWords / 2)) },
|
|
780
844
|
context: { maxWords: Math.min(30, Math.floor(maxWords / 2)) },
|
|
781
|
-
|
|
845
|
+
// Step descriptions need minimum 8 words for meaningful content
|
|
846
|
+
step: { maxWords: Math.max(8, Math.min(20, Math.floor(maxWords / 3))) },
|
|
782
847
|
columnContent: { maxWords: Math.min(25, Math.floor(maxWords / 3)) }
|
|
783
848
|
};
|
|
784
849
|
}
|
|
@@ -2797,25 +2862,43 @@ var SlideFactory = class {
|
|
|
2797
2862
|
createComparisonSlide(index, section) {
|
|
2798
2863
|
const comparison = this.classifier.extractComparison(section);
|
|
2799
2864
|
const labels = this.config.defaults.comparison;
|
|
2865
|
+
const title = this.createTitle(section.header, section);
|
|
2866
|
+
const normalizedTitle = title.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
2800
2867
|
const leftFallback = labels.optionLabels[0] ?? "Option A";
|
|
2801
2868
|
const rightFallback = labels.optionLabels[1] ?? "Option B";
|
|
2802
|
-
|
|
2803
|
-
|
|
2869
|
+
let leftColumn = comparison?.left || section.bullets[0] || "";
|
|
2870
|
+
let rightColumn = comparison?.right || section.bullets[1] || "";
|
|
2871
|
+
const normalizedLeft = leftColumn.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
2872
|
+
const normalizedRight = rightColumn.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
2873
|
+
const leftIsDuplicate = normalizedLeft === normalizedTitle || normalizedTitle.includes(normalizedLeft);
|
|
2874
|
+
const rightIsDuplicate = normalizedRight === normalizedTitle || normalizedTitle.includes(normalizedRight);
|
|
2875
|
+
if (leftIsDuplicate || rightIsDuplicate || !leftColumn && !rightColumn) {
|
|
2876
|
+
if (section.bullets.length >= 2) {
|
|
2877
|
+
logger.warn(`Comparison slide "${title}" has duplicate content, using bullet points instead`);
|
|
2878
|
+
return this.createBulletSlide(index, section);
|
|
2879
|
+
}
|
|
2880
|
+
if (leftIsDuplicate && rightIsDuplicate) {
|
|
2881
|
+
logger.warn(`Skipping comparison slide "${title}" - content duplicates title`);
|
|
2882
|
+
return null;
|
|
2883
|
+
}
|
|
2884
|
+
if (leftIsDuplicate) leftColumn = leftFallback;
|
|
2885
|
+
if (rightIsDuplicate) rightColumn = rightFallback;
|
|
2886
|
+
}
|
|
2804
2887
|
return {
|
|
2805
2888
|
index,
|
|
2806
2889
|
type: "comparison",
|
|
2807
2890
|
data: {
|
|
2808
|
-
title
|
|
2891
|
+
title,
|
|
2809
2892
|
columns: [
|
|
2810
2893
|
{
|
|
2811
2894
|
title: labels.leftLabel,
|
|
2812
2895
|
// FROM KB - not hardcoded 'Before'
|
|
2813
|
-
content: this.truncateText(leftColumn, this.config.rules.wordsPerSlide.max)
|
|
2896
|
+
content: this.truncateText(leftColumn || leftFallback, this.config.rules.wordsPerSlide.max)
|
|
2814
2897
|
},
|
|
2815
2898
|
{
|
|
2816
2899
|
title: labels.rightLabel,
|
|
2817
2900
|
// FROM KB - not hardcoded 'After'
|
|
2818
|
-
content: this.truncateText(rightColumn, this.config.rules.wordsPerSlide.max)
|
|
2901
|
+
content: this.truncateText(rightColumn || rightFallback, this.config.rules.wordsPerSlide.max)
|
|
2819
2902
|
}
|
|
2820
2903
|
]
|
|
2821
2904
|
},
|
|
@@ -2826,9 +2909,8 @@ var SlideFactory = class {
|
|
|
2826
2909
|
const steps = this.classifier.extractSteps(section);
|
|
2827
2910
|
const maxSteps = Math.min(
|
|
2828
2911
|
steps.length,
|
|
2829
|
-
this.config.rules.bulletsPerSlide.max,
|
|
2830
2912
|
this.config.millersLaw.maxItems
|
|
2831
|
-
// FROM KB - 7±2 rule
|
|
2913
|
+
// FROM KB - 7±2 rule (max 9)
|
|
2832
2914
|
);
|
|
2833
2915
|
return {
|
|
2834
2916
|
index,
|
|
@@ -2850,7 +2932,7 @@ var SlideFactory = class {
|
|
|
2850
2932
|
}
|
|
2851
2933
|
createProcessSlide(index, section) {
|
|
2852
2934
|
const steps = this.classifier.extractSteps(section);
|
|
2853
|
-
const maxSteps = Math.min(steps.length, this.config.
|
|
2935
|
+
const maxSteps = Math.min(steps.length, this.config.millersLaw.maxItems);
|
|
2854
2936
|
return {
|
|
2855
2937
|
index,
|
|
2856
2938
|
type: "process",
|
|
@@ -3087,10 +3169,21 @@ var SlideFactory = class {
|
|
|
3087
3169
|
* Used for section headers with strong conclusions.
|
|
3088
3170
|
*/
|
|
3089
3171
|
createTitleImpactSlide(index, section) {
|
|
3172
|
+
const title = this.cleanText(section.header);
|
|
3090
3173
|
const supportingText = section.content || section.bullets.slice(0, 2).join(". ");
|
|
3091
3174
|
const truncatedSupport = this.truncateText(supportingText, this.config.defaults.context.maxWords);
|
|
3175
|
+
const normalizedTitle = title.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
3176
|
+
const normalizedBody = truncatedSupport.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
3177
|
+
const bodyIsDuplicate = normalizedBody === normalizedTitle || normalizedTitle.includes(normalizedBody) || normalizedBody.includes(normalizedTitle) || truncatedSupport.length < 10;
|
|
3178
|
+
if (bodyIsDuplicate) {
|
|
3179
|
+
if (section.bullets.length >= 2 && this.config.mode === "business") {
|
|
3180
|
+
return this.createBulletSlide(index, section);
|
|
3181
|
+
}
|
|
3182
|
+
logger.warn(`Skipping title-impact slide "${title}" - no distinct body content`);
|
|
3183
|
+
return null;
|
|
3184
|
+
}
|
|
3092
3185
|
const data = {
|
|
3093
|
-
title
|
|
3186
|
+
title
|
|
3094
3187
|
};
|
|
3095
3188
|
if (truncatedSupport) {
|
|
3096
3189
|
data.body = truncatedSupport;
|
|
@@ -5257,169 +5350,422 @@ var VisualQualityEvaluator = class {
|
|
|
5257
5350
|
const title = currentSlide.querySelector("h1, h2, .title")?.textContent?.trim() || "";
|
|
5258
5351
|
const body = currentSlide.querySelector(".body, p:not(.subtitle)")?.textContent?.trim() || "";
|
|
5259
5352
|
const bullets = Array.from(currentSlide.querySelectorAll("li")).map((li) => li.textContent?.trim() || "");
|
|
5353
|
+
const hasSteps = !!currentSlide.querySelector(".steps, .process-steps, .timeline");
|
|
5354
|
+
const steps = Array.from(currentSlide.querySelectorAll(".step, .process-step, .timeline-item")).map(
|
|
5355
|
+
(s) => s.textContent?.trim() || ""
|
|
5356
|
+
);
|
|
5357
|
+
const hasMetrics = !!currentSlide.querySelector(".metrics, .metric");
|
|
5260
5358
|
const hasImage = !!currentSlide.querySelector("img");
|
|
5261
5359
|
const hasChart = !!currentSlide.querySelector(".chart, svg, canvas");
|
|
5262
5360
|
const classList = Array.from(currentSlide.classList);
|
|
5263
5361
|
const backgroundColor = window.getComputedStyle(currentSlide).backgroundColor;
|
|
5264
5362
|
const titleEl = currentSlide.querySelector("h1, h2, .title");
|
|
5265
5363
|
const titleStyles = titleEl ? window.getComputedStyle(titleEl) : null;
|
|
5364
|
+
const truncatedElements = [];
|
|
5365
|
+
const contentElements = currentSlide.querySelectorAll("h1, h2, h3, p, span, li, .body, .step-desc, .step-title, .timeline-content");
|
|
5366
|
+
contentElements.forEach((el, idx) => {
|
|
5367
|
+
const styles = window.getComputedStyle(el);
|
|
5368
|
+
const text = el.textContent?.trim() || "";
|
|
5369
|
+
if (text.length < 15) return;
|
|
5370
|
+
const isLayoutContainer = el.classList?.contains("slide-content") || el.classList?.contains("steps") || el.classList?.contains("timeline") || el.classList?.contains("process-steps");
|
|
5371
|
+
if (isLayoutContainer) return;
|
|
5372
|
+
if (styles.textOverflow === "ellipsis") {
|
|
5373
|
+
if (el.scrollWidth > el.clientWidth + 5) {
|
|
5374
|
+
truncatedElements.push(`Element ${idx}: "${text.substring(0, 30)}..." is truncated horizontally`);
|
|
5375
|
+
}
|
|
5376
|
+
}
|
|
5377
|
+
const scrollHeight = el.scrollHeight;
|
|
5378
|
+
const clientHeight = el.clientHeight;
|
|
5379
|
+
if (scrollHeight > clientHeight + 20) {
|
|
5380
|
+
const overflow = styles.overflow || styles.overflowY;
|
|
5381
|
+
if (overflow === "hidden" || overflow === "clip") {
|
|
5382
|
+
truncatedElements.push(`Element ${idx}: "${text.substring(0, 30)}..." is truncated vertically`);
|
|
5383
|
+
}
|
|
5384
|
+
}
|
|
5385
|
+
});
|
|
5386
|
+
const allVisibleText = Array.from(currentSlide.querySelectorAll("*")).map((el) => el.textContent?.trim() || "").join(" ").trim();
|
|
5387
|
+
const isEmptySlide = allVisibleText.length < 10 && !hasImage && !hasChart;
|
|
5388
|
+
const hasOnlyTitle = title.length > 0 && body.length === 0 && bullets.length === 0 && steps.length === 0 && !hasSteps && !hasMetrics && !hasImage && !hasChart;
|
|
5389
|
+
const titleLower = title.toLowerCase();
|
|
5390
|
+
const bodyLower = body.toLowerCase();
|
|
5391
|
+
const isRedundant = titleLower.length > 10 && bodyLower.length > 10 && (titleLower.includes(bodyLower) || bodyLower.includes(titleLower));
|
|
5392
|
+
const contrastIssues = [];
|
|
5393
|
+
const parseRGB = (color) => {
|
|
5394
|
+
const match = color.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
|
|
5395
|
+
if (match && match[1] !== void 0 && match[2] !== void 0 && match[3] !== void 0) {
|
|
5396
|
+
return { r: parseInt(match[1]), g: parseInt(match[2]), b: parseInt(match[3]) };
|
|
5397
|
+
}
|
|
5398
|
+
return null;
|
|
5399
|
+
};
|
|
5400
|
+
const getLuminance = (rgb) => {
|
|
5401
|
+
const values = [rgb.r, rgb.g, rgb.b].map((c) => {
|
|
5402
|
+
c = c / 255;
|
|
5403
|
+
return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
|
|
5404
|
+
});
|
|
5405
|
+
return 0.2126 * (values[0] ?? 0) + 0.7152 * (values[1] ?? 0) + 0.0722 * (values[2] ?? 0);
|
|
5406
|
+
};
|
|
5407
|
+
const getContrastRatio = (fg, bg) => {
|
|
5408
|
+
const l1 = getLuminance(fg);
|
|
5409
|
+
const l2 = getLuminance(bg);
|
|
5410
|
+
const lighter = Math.max(l1, l2);
|
|
5411
|
+
const darker = Math.min(l1, l2);
|
|
5412
|
+
return (lighter + 0.05) / (darker + 0.05);
|
|
5413
|
+
};
|
|
5414
|
+
const bgRGB = parseRGB(backgroundColor);
|
|
5415
|
+
if (titleEl && bgRGB) {
|
|
5416
|
+
const titleRGB = parseRGB(titleStyles?.color || "");
|
|
5417
|
+
if (titleRGB) {
|
|
5418
|
+
const contrast = getContrastRatio(titleRGB, bgRGB);
|
|
5419
|
+
if (contrast < 4.5) {
|
|
5420
|
+
contrastIssues.push(`Title contrast ${contrast.toFixed(1)}:1 (need 4.5:1)`);
|
|
5421
|
+
}
|
|
5422
|
+
}
|
|
5423
|
+
}
|
|
5424
|
+
const bodyEl = currentSlide.querySelector(".body, p:not(.subtitle)");
|
|
5425
|
+
if (bodyEl && bgRGB) {
|
|
5426
|
+
const bodyStyles2 = window.getComputedStyle(bodyEl);
|
|
5427
|
+
const bodyRGB = parseRGB(bodyStyles2.color);
|
|
5428
|
+
if (bodyRGB) {
|
|
5429
|
+
const contrast = getContrastRatio(bodyRGB, bgRGB);
|
|
5430
|
+
if (contrast < 4.5) {
|
|
5431
|
+
contrastIssues.push(`Body contrast ${contrast.toFixed(1)}:1 (need 4.5:1)`);
|
|
5432
|
+
}
|
|
5433
|
+
}
|
|
5434
|
+
}
|
|
5435
|
+
const hasContrastIssues = contrastIssues.length > 0;
|
|
5436
|
+
const layoutIssues = [];
|
|
5437
|
+
const slideRect = currentSlide.getBoundingClientRect();
|
|
5438
|
+
const layoutElements = currentSlide.querySelectorAll("h1, h2, h3, p, ul, ol, .number, .metric, img, canvas, svg");
|
|
5439
|
+
let topHeavy = 0;
|
|
5440
|
+
let bottomHeavy = 0;
|
|
5441
|
+
let leftHeavy = 0;
|
|
5442
|
+
let rightHeavy = 0;
|
|
5443
|
+
let centerY = slideRect.height / 2;
|
|
5444
|
+
let centerX = slideRect.width / 2;
|
|
5445
|
+
layoutElements.forEach((el) => {
|
|
5446
|
+
const rect = el.getBoundingClientRect();
|
|
5447
|
+
const elCenterY = rect.top + rect.height / 2 - slideRect.top;
|
|
5448
|
+
const elCenterX = rect.left + rect.width / 2 - slideRect.left;
|
|
5449
|
+
if (elCenterY < centerY) topHeavy++;
|
|
5450
|
+
else bottomHeavy++;
|
|
5451
|
+
if (elCenterX < centerX) leftHeavy++;
|
|
5452
|
+
else rightHeavy++;
|
|
5453
|
+
});
|
|
5454
|
+
const verticalBalance = Math.abs(topHeavy - bottomHeavy) / Math.max(1, topHeavy + bottomHeavy);
|
|
5455
|
+
const horizontalBalance = Math.abs(leftHeavy - rightHeavy) / Math.max(1, leftHeavy + rightHeavy);
|
|
5456
|
+
if (verticalBalance > 0.6 && contentElements.length > 2) {
|
|
5457
|
+
layoutIssues.push(`Vertical imbalance: ${topHeavy} top vs ${bottomHeavy} bottom`);
|
|
5458
|
+
}
|
|
5459
|
+
if (horizontalBalance > 0.6 && contentElements.length > 2) {
|
|
5460
|
+
layoutIssues.push(`Horizontal imbalance: ${leftHeavy} left vs ${rightHeavy} right`);
|
|
5461
|
+
}
|
|
5462
|
+
const hasLayoutIssues = layoutIssues.length > 0;
|
|
5463
|
+
const typographyIssues = [];
|
|
5464
|
+
const titleSize = parseFloat(titleStyles?.fontSize || "0");
|
|
5465
|
+
const bodyStyles = bodyEl ? window.getComputedStyle(bodyEl) : null;
|
|
5466
|
+
const bodySize = parseFloat(bodyStyles?.fontSize || "0");
|
|
5467
|
+
if (titleSize > 0 && bodySize > 0 && titleSize <= bodySize) {
|
|
5468
|
+
typographyIssues.push("Title not larger than body - hierarchy broken");
|
|
5469
|
+
}
|
|
5470
|
+
if (titleSize > 0 && bodySize > 0 && titleSize < bodySize * 1.3) {
|
|
5471
|
+
typographyIssues.push("Title not prominent enough vs body");
|
|
5472
|
+
}
|
|
5473
|
+
const hasTypographyIssues = typographyIssues.length > 0;
|
|
5474
|
+
const completenessIssues = [];
|
|
5475
|
+
const emptySteps = currentSlide.querySelectorAll(".steps:empty, .timeline:empty, .process-steps:empty, .steps > :empty");
|
|
5476
|
+
if (emptySteps.length > 0) {
|
|
5477
|
+
completenessIssues.push(`${emptySteps.length} empty step/timeline container(s) - content missing`);
|
|
5478
|
+
}
|
|
5479
|
+
if (body.includes("Lorem") || body.includes("placeholder") || body.includes("TODO")) {
|
|
5480
|
+
completenessIssues.push("Contains placeholder text");
|
|
5481
|
+
}
|
|
5482
|
+
const emptyBullets = bullets.filter((b) => b.length < 3).length;
|
|
5483
|
+
if (emptyBullets > 0 && bullets.length > 0) {
|
|
5484
|
+
completenessIssues.push(`${emptyBullets} empty/minimal bullets`);
|
|
5485
|
+
}
|
|
5486
|
+
const hasCompletenessIssues = completenessIssues.length > 0;
|
|
5487
|
+
const visualNeedsIssues = [];
|
|
5488
|
+
const visualRequiredTypes = ["data", "chart", "metrics", "comparison", "process", "timeline"];
|
|
5489
|
+
const visualRecommendedTypes = ["big-number", "quote", "testimonial"];
|
|
5490
|
+
const slideClasses = Array.from(currentSlide.classList).join(" ").toLowerCase();
|
|
5491
|
+
const needsVisual = visualRequiredTypes.some((t) => slideClasses.includes(t));
|
|
5492
|
+
const visualRecommended = visualRecommendedTypes.some((t) => slideClasses.includes(t));
|
|
5493
|
+
const contentText = (title + " " + body).toLowerCase();
|
|
5494
|
+
const dataIndicators = /\d+%|\$[\d,]+|\d+x|million|billion|percent|growth|increase|decrease|comparison|versus|vs\b|before|after/i;
|
|
5495
|
+
const hasDataContent = dataIndicators.test(contentText);
|
|
5496
|
+
const hasAnyVisual = hasImage || hasChart;
|
|
5497
|
+
if (needsVisual && !hasAnyVisual) {
|
|
5498
|
+
visualNeedsIssues.push("Data/process slide missing visual - needs chart, diagram, or image");
|
|
5499
|
+
} else if (hasDataContent && !hasAnyVisual && bullets.length === 0) {
|
|
5500
|
+
visualNeedsIssues.push("Content has numbers/data but no visualization - chart would help");
|
|
5501
|
+
} else if (visualRecommended && !hasAnyVisual) {
|
|
5502
|
+
visualNeedsIssues.push("Visual recommended for this slide type");
|
|
5503
|
+
}
|
|
5504
|
+
if (hasImage) {
|
|
5505
|
+
const img = currentSlide.querySelector("img");
|
|
5506
|
+
const imgSrc = img?.getAttribute("src") || "";
|
|
5507
|
+
if (imgSrc.includes("placeholder") || imgSrc.includes("picsum") || imgSrc.includes("via.placeholder")) {
|
|
5508
|
+
visualNeedsIssues.push("Placeholder image detected - needs real visual");
|
|
5509
|
+
}
|
|
5510
|
+
}
|
|
5511
|
+
const needsVisualButMissing = visualNeedsIssues.length > 0;
|
|
5512
|
+
const visualScore = hasAnyVisual ? 10 : needsVisual || hasDataContent ? 0 : 5;
|
|
5266
5513
|
return {
|
|
5267
5514
|
title,
|
|
5268
5515
|
body,
|
|
5269
5516
|
bullets,
|
|
5517
|
+
steps,
|
|
5518
|
+
// NEW: Timeline/process step content
|
|
5519
|
+
hasSteps,
|
|
5520
|
+
// NEW: Has .steps/.process-steps container
|
|
5521
|
+
hasMetrics,
|
|
5522
|
+
// NEW: Has metrics content
|
|
5270
5523
|
hasImage,
|
|
5271
5524
|
hasChart,
|
|
5272
5525
|
classList,
|
|
5273
5526
|
backgroundColor,
|
|
5274
5527
|
titleFontSize: titleStyles?.fontSize || "",
|
|
5275
5528
|
titleColor: titleStyles?.color || "",
|
|
5276
|
-
|
|
5529
|
+
// Include steps in content length calculation
|
|
5530
|
+
contentLength: (title + body + bullets.join(" ") + steps.join(" ")).length,
|
|
5531
|
+
// Visual quality flags
|
|
5532
|
+
hasTruncatedText: truncatedElements.length > 0,
|
|
5533
|
+
truncatedElements,
|
|
5534
|
+
isEmptySlide,
|
|
5535
|
+
hasOnlyTitle,
|
|
5536
|
+
isRedundant,
|
|
5537
|
+
allVisibleTextLength: allVisibleText.length,
|
|
5538
|
+
// NEW: Expert quality checks
|
|
5539
|
+
hasContrastIssues,
|
|
5540
|
+
contrastIssues,
|
|
5541
|
+
hasLayoutIssues,
|
|
5542
|
+
layoutIssues,
|
|
5543
|
+
hasTypographyIssues,
|
|
5544
|
+
typographyIssues,
|
|
5545
|
+
hasCompletenessIssues,
|
|
5546
|
+
completenessIssues,
|
|
5547
|
+
// Visual needs analysis
|
|
5548
|
+
needsVisualButMissing,
|
|
5549
|
+
visualNeedsIssues,
|
|
5550
|
+
visualScore
|
|
5277
5551
|
};
|
|
5278
5552
|
});
|
|
5279
5553
|
return this.scoreSlide(slideIndex, slideData, screenshotPath);
|
|
5280
5554
|
}
|
|
5555
|
+
/**
|
|
5556
|
+
* EXPERT-LEVEL SLIDE SCORING
|
|
5557
|
+
*
|
|
5558
|
+
* This evaluates each slide like Nancy Duarte, Carmine Gallo, or a McKinsey partner would.
|
|
5559
|
+
* It's not about rules - it's about whether the slide WORKS.
|
|
5560
|
+
*/
|
|
5281
5561
|
scoreSlide(slideIndex, slideData, screenshotPath) {
|
|
5282
5562
|
if (!slideData) {
|
|
5283
|
-
return
|
|
5284
|
-
slideIndex,
|
|
5285
|
-
slideType: "unknown",
|
|
5286
|
-
visualImpact: 0,
|
|
5287
|
-
visualImpactNotes: "Could not analyze slide",
|
|
5288
|
-
contentClarity: 0,
|
|
5289
|
-
contentClarityNotes: "Could not analyze slide",
|
|
5290
|
-
professionalPolish: 0,
|
|
5291
|
-
professionalPolishNotes: "Could not analyze slide",
|
|
5292
|
-
themeCoherence: 0,
|
|
5293
|
-
themeCoherenceNotes: "Could not analyze slide",
|
|
5294
|
-
totalScore: 0,
|
|
5295
|
-
screenshotPath
|
|
5296
|
-
};
|
|
5563
|
+
return this.createFailedSlideScore(slideIndex, "unknown", "Could not analyze slide", screenshotPath);
|
|
5297
5564
|
}
|
|
5298
5565
|
const slideType = this.inferSlideType(slideData);
|
|
5299
|
-
|
|
5300
|
-
|
|
5301
|
-
|
|
5302
|
-
visualImpact += 2;
|
|
5303
|
-
visualNotes.push("Has imagery");
|
|
5566
|
+
const criticalFailures = [];
|
|
5567
|
+
if (slideData.isEmptySlide) {
|
|
5568
|
+
criticalFailures.push("EMPTY SLIDE: No meaningful content - slide serves no purpose");
|
|
5304
5569
|
}
|
|
5305
|
-
if (slideData.
|
|
5306
|
-
|
|
5307
|
-
visualNotes.push("Has data visualization");
|
|
5570
|
+
if (slideData.hasOnlyTitle && !["title", "agenda", "thank-you", "cta"].includes(slideType)) {
|
|
5571
|
+
criticalFailures.push("INCOMPLETE: Slide has title but no body content - looks unfinished");
|
|
5308
5572
|
}
|
|
5309
|
-
|
|
5310
|
-
|
|
5311
|
-
"big_number",
|
|
5312
|
-
"metrics-grid",
|
|
5313
|
-
"metrics_grid",
|
|
5314
|
-
"three-column",
|
|
5315
|
-
"three_column",
|
|
5316
|
-
"three-points",
|
|
5317
|
-
"three_points",
|
|
5318
|
-
"title-impact",
|
|
5319
|
-
"title_impact",
|
|
5320
|
-
"cta",
|
|
5321
|
-
"call-to-action",
|
|
5322
|
-
"comparison",
|
|
5323
|
-
"timeline",
|
|
5324
|
-
"process",
|
|
5325
|
-
"quote",
|
|
5326
|
-
"testimonial"
|
|
5327
|
-
];
|
|
5328
|
-
if (highImpactTypes.some((t) => slideType.includes(t.replace(/_/g, "-")) || slideType.includes(t.replace(/-/g, "_")))) {
|
|
5329
|
-
visualImpact += 2;
|
|
5330
|
-
visualNotes.push("High-impact slide type");
|
|
5573
|
+
if (slideData.hasTruncatedText) {
|
|
5574
|
+
criticalFailures.push(`TRUNCATED TEXT: ${slideData.truncatedElements.length} element(s) cut off - message incomplete`);
|
|
5331
5575
|
}
|
|
5332
|
-
if (
|
|
5333
|
-
|
|
5334
|
-
visualNotes.push("Clean single statement");
|
|
5576
|
+
if (slideData.isRedundant) {
|
|
5577
|
+
criticalFailures.push("REDUNDANT: Title and body say the same thing - violates one-idea rule");
|
|
5335
5578
|
}
|
|
5336
|
-
if (
|
|
5337
|
-
|
|
5338
|
-
|
|
5579
|
+
if (slideData.hasCompletenessIssues) {
|
|
5580
|
+
slideData.completenessIssues.forEach((issue) => {
|
|
5581
|
+
if (issue.includes("empty")) {
|
|
5582
|
+
criticalFailures.push(`INCOMPLETE: ${issue}`);
|
|
5583
|
+
}
|
|
5584
|
+
});
|
|
5339
5585
|
}
|
|
5340
|
-
if (
|
|
5341
|
-
|
|
5342
|
-
|
|
5586
|
+
if (criticalFailures.length > 0) {
|
|
5587
|
+
return this.createFailedSlideScore(slideIndex, slideType, criticalFailures.join("; "), screenshotPath, criticalFailures);
|
|
5588
|
+
}
|
|
5589
|
+
let glanceTest = 8;
|
|
5590
|
+
const glanceNotes = [];
|
|
5591
|
+
const wordCount = slideData.contentLength / 6;
|
|
5592
|
+
if (wordCount > 40) {
|
|
5593
|
+
glanceTest = 3;
|
|
5594
|
+
glanceNotes.push("Too much text - fails glance test");
|
|
5595
|
+
} else if (wordCount > 25) {
|
|
5596
|
+
glanceTest -= 3;
|
|
5597
|
+
glanceNotes.push("Text-heavy - borderline glance test");
|
|
5598
|
+
} else if (wordCount < 15) {
|
|
5599
|
+
glanceTest += 1;
|
|
5600
|
+
glanceNotes.push("Clean, minimal text - passes glance test easily");
|
|
5343
5601
|
}
|
|
5344
5602
|
if (slideData.bullets.length > 5) {
|
|
5345
|
-
|
|
5346
|
-
|
|
5347
|
-
}
|
|
5348
|
-
|
|
5349
|
-
|
|
5350
|
-
|
|
5351
|
-
if (slideData.
|
|
5352
|
-
|
|
5353
|
-
|
|
5354
|
-
}
|
|
5355
|
-
|
|
5356
|
-
|
|
5357
|
-
|
|
5358
|
-
|
|
5359
|
-
|
|
5360
|
-
|
|
5361
|
-
|
|
5362
|
-
|
|
5363
|
-
|
|
5364
|
-
if (
|
|
5365
|
-
|
|
5366
|
-
|
|
5367
|
-
}
|
|
5368
|
-
|
|
5369
|
-
|
|
5370
|
-
|
|
5603
|
+
glanceTest -= 4;
|
|
5604
|
+
glanceNotes.push("Too many bullets - cannot scan in 3 seconds");
|
|
5605
|
+
} else if (slideData.bullets.length > 3) {
|
|
5606
|
+
glanceTest -= 2;
|
|
5607
|
+
glanceNotes.push("Multiple bullets - needs careful structuring");
|
|
5608
|
+
}
|
|
5609
|
+
if (slideData.hasImage || slideData.hasChart) {
|
|
5610
|
+
glanceTest += 1;
|
|
5611
|
+
glanceNotes.push("Visual element aids quick comprehension");
|
|
5612
|
+
}
|
|
5613
|
+
glanceTest = Math.max(0, Math.min(10, glanceTest));
|
|
5614
|
+
let oneIdea = 7;
|
|
5615
|
+
const oneIdeaNotes = [];
|
|
5616
|
+
if (slideData.title.length > 0 && slideData.title.length < 60) {
|
|
5617
|
+
oneIdea += 1;
|
|
5618
|
+
oneIdeaNotes.push("Clear, focused title");
|
|
5619
|
+
} else if (slideData.title.length > 80) {
|
|
5620
|
+
oneIdea -= 2;
|
|
5621
|
+
oneIdeaNotes.push("Title too long - multiple ideas?");
|
|
5622
|
+
} else if (slideData.title.length === 0) {
|
|
5623
|
+
oneIdea -= 4;
|
|
5624
|
+
oneIdeaNotes.push("No title - what is the one idea?");
|
|
5625
|
+
}
|
|
5626
|
+
const focusedTypes = ["big-number", "single-statement", "quote", "cta", "title"];
|
|
5627
|
+
if (focusedTypes.some((t) => slideType.includes(t))) {
|
|
5628
|
+
oneIdea += 2;
|
|
5629
|
+
oneIdeaNotes.push("Slide type naturally focuses on one idea");
|
|
5630
|
+
}
|
|
5631
|
+
if (slideData.bullets.length > 4) {
|
|
5632
|
+
oneIdea -= 3;
|
|
5633
|
+
oneIdeaNotes.push("Multiple bullets dilute the message");
|
|
5634
|
+
}
|
|
5635
|
+
oneIdea = Math.max(0, Math.min(10, oneIdea));
|
|
5636
|
+
let dataInkRatio = 7;
|
|
5637
|
+
const dataInkNotes = [];
|
|
5638
|
+
const structuredTypes = ["big-number", "metrics-grid", "three-column", "timeline", "process", "comparison"];
|
|
5639
|
+
if (structuredTypes.some((t) => slideType.includes(t))) {
|
|
5640
|
+
dataInkRatio += 1;
|
|
5641
|
+
dataInkNotes.push("Structured layout");
|
|
5642
|
+
}
|
|
5643
|
+
if (slideData.hasChart) {
|
|
5644
|
+
dataInkRatio += 2;
|
|
5645
|
+
dataInkNotes.push("Data visualization present - excellent");
|
|
5646
|
+
}
|
|
5647
|
+
if (slideData.hasImage) {
|
|
5648
|
+
dataInkRatio += 1;
|
|
5649
|
+
dataInkNotes.push("Supporting visual present");
|
|
5650
|
+
}
|
|
5651
|
+
if (slideData.needsVisualButMissing) {
|
|
5652
|
+
dataInkRatio -= 3;
|
|
5653
|
+
slideData.visualNeedsIssues.forEach((issue) => {
|
|
5654
|
+
dataInkNotes.push(`Visual: ${issue}`);
|
|
5655
|
+
});
|
|
5656
|
+
} else if (slideData.visualScore === 10) {
|
|
5657
|
+
dataInkNotes.push("Appropriate visuals for content type");
|
|
5658
|
+
}
|
|
5659
|
+
if (slideData.contentLength > 400) {
|
|
5660
|
+
dataInkRatio -= 4;
|
|
5661
|
+
dataInkNotes.push("Excessive text - low information density");
|
|
5662
|
+
}
|
|
5663
|
+
dataInkRatio = Math.max(0, Math.min(10, dataInkRatio));
|
|
5664
|
+
let professionalExecution = 7;
|
|
5665
|
+
const executionNotes = [];
|
|
5371
5666
|
const titleFontSize = parseFloat(slideData.titleFontSize || "0");
|
|
5372
5667
|
if (titleFontSize >= 40) {
|
|
5373
|
-
|
|
5374
|
-
|
|
5375
|
-
} else if (titleFontSize < 24) {
|
|
5376
|
-
|
|
5377
|
-
|
|
5668
|
+
professionalExecution += 1;
|
|
5669
|
+
executionNotes.push("Strong title typography");
|
|
5670
|
+
} else if (titleFontSize > 0 && titleFontSize < 24) {
|
|
5671
|
+
professionalExecution -= 1;
|
|
5672
|
+
executionNotes.push("Title could be more prominent");
|
|
5378
5673
|
}
|
|
5379
5674
|
if (slideData.contentLength > 10 && slideData.contentLength < 200) {
|
|
5380
|
-
|
|
5381
|
-
|
|
5382
|
-
}
|
|
5383
|
-
const polishedTypes = [
|
|
5384
|
-
"big-number",
|
|
5385
|
-
"metrics-grid",
|
|
5386
|
-
"three-column",
|
|
5387
|
-
"three-points",
|
|
5388
|
-
"comparison",
|
|
5389
|
-
"timeline",
|
|
5390
|
-
"process",
|
|
5391
|
-
"cta",
|
|
5392
|
-
"title",
|
|
5393
|
-
"thank-you"
|
|
5394
|
-
];
|
|
5395
|
-
if (polishedTypes.some((t) => slideType.includes(t))) {
|
|
5396
|
-
professionalPolish += 1;
|
|
5397
|
-
polishNotes.push("Well-structured layout");
|
|
5675
|
+
professionalExecution += 1;
|
|
5676
|
+
executionNotes.push("Well-balanced content density");
|
|
5398
5677
|
}
|
|
5399
|
-
professionalPolish = Math.max(0, Math.min(10, professionalPolish));
|
|
5400
|
-
let themeCoherence = 7;
|
|
5401
|
-
const coherenceNotes = [];
|
|
5402
5678
|
if (slideData.classList.some((c) => c.includes("slide-"))) {
|
|
5403
|
-
|
|
5404
|
-
|
|
5679
|
+
professionalExecution += 1;
|
|
5680
|
+
executionNotes.push("Consistent with design system");
|
|
5405
5681
|
}
|
|
5406
|
-
|
|
5407
|
-
|
|
5682
|
+
if (slideData.hasContrastIssues) {
|
|
5683
|
+
professionalExecution -= 3;
|
|
5684
|
+
slideData.contrastIssues.forEach((issue) => {
|
|
5685
|
+
executionNotes.push(`Contrast: ${issue}`);
|
|
5686
|
+
});
|
|
5687
|
+
} else {
|
|
5688
|
+
executionNotes.push("Good text contrast");
|
|
5689
|
+
}
|
|
5690
|
+
if (slideData.hasLayoutIssues) {
|
|
5691
|
+
professionalExecution -= 2;
|
|
5692
|
+
slideData.layoutIssues.forEach((issue) => {
|
|
5693
|
+
executionNotes.push(`Layout: ${issue}`);
|
|
5694
|
+
});
|
|
5695
|
+
} else if (slideData.contentLength > 50) {
|
|
5696
|
+
executionNotes.push("Balanced layout");
|
|
5697
|
+
}
|
|
5698
|
+
if (slideData.hasTypographyIssues) {
|
|
5699
|
+
professionalExecution -= 2;
|
|
5700
|
+
slideData.typographyIssues.forEach((issue) => {
|
|
5701
|
+
executionNotes.push(`Typography: ${issue}`);
|
|
5702
|
+
});
|
|
5703
|
+
} else if (titleFontSize > 0) {
|
|
5704
|
+
executionNotes.push("Good type hierarchy");
|
|
5705
|
+
}
|
|
5706
|
+
professionalExecution = Math.max(0, Math.min(10, professionalExecution));
|
|
5707
|
+
const totalScore = glanceTest + oneIdea + dataInkRatio + professionalExecution;
|
|
5708
|
+
const visualImpact = Math.round((glanceTest + oneIdea) / 2);
|
|
5709
|
+
const contentClarity = oneIdea;
|
|
5710
|
+
const professionalPolish = Math.round((dataInkRatio + professionalExecution) / 2);
|
|
5711
|
+
const themeCoherence = professionalExecution;
|
|
5408
5712
|
return {
|
|
5409
5713
|
slideIndex,
|
|
5410
5714
|
slideType,
|
|
5715
|
+
// New expert dimensions
|
|
5716
|
+
glanceTest,
|
|
5717
|
+
glanceTestNotes: glanceNotes.join("; ") || "Passes glance test",
|
|
5718
|
+
oneIdea,
|
|
5719
|
+
oneIdeaNotes: oneIdeaNotes.join("; ") || "Clear single message",
|
|
5720
|
+
dataInkRatio,
|
|
5721
|
+
dataInkNotes: dataInkNotes.join("; ") || "Good information density",
|
|
5722
|
+
professionalExecution,
|
|
5723
|
+
professionalExecutionNotes: executionNotes.join("; ") || "Professional quality",
|
|
5724
|
+
// Critical failures
|
|
5725
|
+
hasCriticalFailure: false,
|
|
5726
|
+
criticalFailures: [],
|
|
5727
|
+
// Legacy dimensions (for compatibility)
|
|
5411
5728
|
visualImpact,
|
|
5412
|
-
visualImpactNotes:
|
|
5729
|
+
visualImpactNotes: glanceNotes.join("; ") || "Standard",
|
|
5413
5730
|
contentClarity,
|
|
5414
|
-
contentClarityNotes:
|
|
5731
|
+
contentClarityNotes: oneIdeaNotes.join("; ") || "Good",
|
|
5415
5732
|
professionalPolish,
|
|
5416
|
-
professionalPolishNotes:
|
|
5733
|
+
professionalPolishNotes: executionNotes.join("; ") || "Acceptable",
|
|
5417
5734
|
themeCoherence,
|
|
5418
|
-
themeCoherenceNotes:
|
|
5735
|
+
themeCoherenceNotes: "Consistent",
|
|
5419
5736
|
totalScore,
|
|
5420
5737
|
screenshotPath
|
|
5421
5738
|
};
|
|
5422
5739
|
}
|
|
5740
|
+
/**
|
|
5741
|
+
* Create a failed slide score (for critical failures)
|
|
5742
|
+
*/
|
|
5743
|
+
createFailedSlideScore(slideIndex, slideType, reason, screenshotPath, criticalFailures = []) {
|
|
5744
|
+
return {
|
|
5745
|
+
slideIndex,
|
|
5746
|
+
slideType,
|
|
5747
|
+
glanceTest: 0,
|
|
5748
|
+
glanceTestNotes: "CRITICAL FAILURE: " + reason,
|
|
5749
|
+
oneIdea: 0,
|
|
5750
|
+
oneIdeaNotes: "CRITICAL FAILURE: " + reason,
|
|
5751
|
+
dataInkRatio: 0,
|
|
5752
|
+
dataInkNotes: "CRITICAL FAILURE: " + reason,
|
|
5753
|
+
professionalExecution: 0,
|
|
5754
|
+
professionalExecutionNotes: "CRITICAL FAILURE: " + reason,
|
|
5755
|
+
hasCriticalFailure: true,
|
|
5756
|
+
criticalFailures: criticalFailures.length > 0 ? criticalFailures : [reason],
|
|
5757
|
+
visualImpact: 0,
|
|
5758
|
+
visualImpactNotes: "CRITICAL: " + reason,
|
|
5759
|
+
contentClarity: 0,
|
|
5760
|
+
contentClarityNotes: "CRITICAL: " + reason,
|
|
5761
|
+
professionalPolish: 0,
|
|
5762
|
+
professionalPolishNotes: "CRITICAL: " + reason,
|
|
5763
|
+
themeCoherence: 0,
|
|
5764
|
+
themeCoherenceNotes: "CRITICAL: " + reason,
|
|
5765
|
+
totalScore: 0,
|
|
5766
|
+
screenshotPath
|
|
5767
|
+
};
|
|
5768
|
+
}
|
|
5423
5769
|
inferSlideType(slideData) {
|
|
5424
5770
|
const classList = slideData.classList || [];
|
|
5425
5771
|
for (const cls of classList) {
|
|
@@ -5531,34 +5877,44 @@ var VisualQualityEvaluator = class {
|
|
|
5531
5877
|
evaluateContentQuality(slideScores) {
|
|
5532
5878
|
let score = 25;
|
|
5533
5879
|
const notes = [];
|
|
5534
|
-
const
|
|
5535
|
-
|
|
5536
|
-
|
|
5537
|
-
|
|
5538
|
-
|
|
5539
|
-
|
|
5880
|
+
const criticalFailureSlides = slideScores.filter((s) => s.hasCriticalFailure);
|
|
5881
|
+
if (criticalFailureSlides.length > 0) {
|
|
5882
|
+
score = Math.max(0, 25 - criticalFailureSlides.length * 8);
|
|
5883
|
+
notes.push(`CRITICAL: ${criticalFailureSlides.length} slide(s) have critical failures`);
|
|
5884
|
+
criticalFailureSlides.forEach((s) => {
|
|
5885
|
+
s.criticalFailures.forEach((f) => notes.push(` - Slide ${s.slideIndex}: ${f}`));
|
|
5886
|
+
});
|
|
5540
5887
|
}
|
|
5541
|
-
const
|
|
5542
|
-
const
|
|
5888
|
+
const avgGlance = slideScores.reduce((sum, s) => sum + s.glanceTest, 0) / slideScores.length;
|
|
5889
|
+
const passesGlanceTest = avgGlance >= 6;
|
|
5890
|
+
if (!passesGlanceTest) {
|
|
5891
|
+
score -= 5;
|
|
5892
|
+
notes.push(`Glance Test: Average ${avgGlance.toFixed(1)}/10 - too text-heavy`);
|
|
5893
|
+
}
|
|
5894
|
+
const avgOneIdea = slideScores.reduce((sum, s) => sum + s.oneIdea, 0) / slideScores.length;
|
|
5895
|
+
const hasOneIdeaPerSlide = avgOneIdea >= 6;
|
|
5896
|
+
if (!hasOneIdeaPerSlide) {
|
|
5897
|
+
score -= 5;
|
|
5898
|
+
notes.push(`One Idea Rule: Average ${avgOneIdea.toFixed(1)}/10 - messages diluted`);
|
|
5899
|
+
}
|
|
5900
|
+
const messagesAreClear = avgOneIdea >= 7;
|
|
5901
|
+
const lowScoreSlides = slideScores.filter((s) => s.totalScore < 20).length;
|
|
5902
|
+
const appropriateDepth = lowScoreSlides < slideScores.length * 0.2;
|
|
5543
5903
|
if (!appropriateDepth) {
|
|
5544
5904
|
score -= 5;
|
|
5545
|
-
notes.push("Some slides have
|
|
5905
|
+
notes.push("Some slides have quality issues");
|
|
5546
5906
|
}
|
|
5547
|
-
const overloadedSlides = slideScores.filter(
|
|
5548
|
-
(s) => s.contentClarityNotes.includes("too long") || s.visualImpactNotes.includes("Too much")
|
|
5549
|
-
).length;
|
|
5907
|
+
const overloadedSlides = slideScores.filter((s) => s.glanceTest < 5).length;
|
|
5550
5908
|
const noOverload = overloadedSlides === 0;
|
|
5551
5909
|
if (!noOverload) {
|
|
5552
|
-
score -=
|
|
5553
|
-
notes.push(`${overloadedSlides} slides
|
|
5910
|
+
score -= 3;
|
|
5911
|
+
notes.push(`${overloadedSlides} slides fail glance test - too dense`);
|
|
5554
5912
|
}
|
|
5555
|
-
const
|
|
5556
|
-
|
|
5557
|
-
).length;
|
|
5558
|
-
const actionableInsights = insightSlides >= slideScores.length * 0.3;
|
|
5913
|
+
const excellentSlides = slideScores.filter((s) => s.totalScore >= 30).length;
|
|
5914
|
+
const actionableInsights = excellentSlides >= slideScores.length * 0.3;
|
|
5559
5915
|
if (!actionableInsights) {
|
|
5560
|
-
score -=
|
|
5561
|
-
notes.push("Need more high-impact
|
|
5916
|
+
score -= 3;
|
|
5917
|
+
notes.push("Need more high-impact slides");
|
|
5562
5918
|
}
|
|
5563
5919
|
return {
|
|
5564
5920
|
score: Math.max(0, score),
|
|
@@ -5572,30 +5928,50 @@ var VisualQualityEvaluator = class {
|
|
|
5572
5928
|
evaluateExecutiveReadiness(slideScores) {
|
|
5573
5929
|
let score = 25;
|
|
5574
5930
|
const notes = [];
|
|
5575
|
-
const
|
|
5576
|
-
|
|
5577
|
-
|
|
5931
|
+
const criticalFailureSlides = slideScores.filter((s) => s.hasCriticalFailure);
|
|
5932
|
+
if (criticalFailureSlides.length > 0) {
|
|
5933
|
+
score = 0;
|
|
5934
|
+
notes.push(`CRITICAL: ${criticalFailureSlides.length} slide(s) have critical failures - CANNOT show to executives`);
|
|
5935
|
+
criticalFailureSlides.forEach((s) => {
|
|
5936
|
+
notes.push(` - Slide ${s.slideIndex} (${s.slideType}): ${s.criticalFailures[0]}`);
|
|
5937
|
+
});
|
|
5938
|
+
return {
|
|
5939
|
+
score: 0,
|
|
5940
|
+
wouldImpress: false,
|
|
5941
|
+
readyForBoardroom: false,
|
|
5942
|
+
compelling: false,
|
|
5943
|
+
shareworthy: false,
|
|
5944
|
+
notes: notes.join(". ")
|
|
5945
|
+
};
|
|
5946
|
+
}
|
|
5947
|
+
const avgGlance = slideScores.reduce((sum, s) => sum + s.glanceTest, 0) / slideScores.length;
|
|
5948
|
+
const avgOneIdea = slideScores.reduce((sum, s) => sum + s.oneIdea, 0) / slideScores.length;
|
|
5949
|
+
const avgDataInk = slideScores.reduce((sum, s) => sum + s.dataInkRatio, 0) / slideScores.length;
|
|
5950
|
+
const avgExecution = slideScores.reduce((sum, s) => sum + s.professionalExecution, 0) / slideScores.length;
|
|
5578
5951
|
const avgTotal = slideScores.reduce((sum, s) => sum + s.totalScore, 0) / slideScores.length;
|
|
5579
|
-
const wouldImpress =
|
|
5952
|
+
const wouldImpress = avgGlance >= 7 && avgOneIdea >= 7 && avgExecution >= 7;
|
|
5580
5953
|
if (!wouldImpress) {
|
|
5581
5954
|
score -= 7;
|
|
5582
5955
|
notes.push("Needs more visual impact to impress");
|
|
5583
5956
|
}
|
|
5584
|
-
const readyForBoardroom =
|
|
5957
|
+
const readyForBoardroom = avgExecution >= 6 && avgDataInk >= 6;
|
|
5585
5958
|
if (!readyForBoardroom) {
|
|
5586
5959
|
score -= 7;
|
|
5587
|
-
notes.push(
|
|
5960
|
+
notes.push(`Boardroom readiness: Execution ${avgExecution.toFixed(1)}/10, Data-Ink ${avgDataInk.toFixed(1)}/10`);
|
|
5588
5961
|
}
|
|
5589
|
-
const compelling =
|
|
5962
|
+
const compelling = avgGlance >= 6 && avgOneIdea >= 6;
|
|
5590
5963
|
if (!compelling) {
|
|
5591
5964
|
score -= 5;
|
|
5592
|
-
notes.push(
|
|
5965
|
+
notes.push(`Compelling: Glance ${avgGlance.toFixed(1)}/10, OneIdea ${avgOneIdea.toFixed(1)}/10`);
|
|
5593
5966
|
}
|
|
5594
5967
|
const excellentSlides = slideScores.filter((s) => s.totalScore >= 30).length;
|
|
5595
5968
|
const shareworthy = excellentSlides >= slideScores.length * 0.4;
|
|
5596
5969
|
if (!shareworthy) {
|
|
5597
5970
|
score -= 5;
|
|
5598
|
-
notes.push(
|
|
5971
|
+
notes.push(`Shareworthy: ${excellentSlides}/${slideScores.length} excellent slides (need 40%)`);
|
|
5972
|
+
}
|
|
5973
|
+
if (wouldImpress && readyForBoardroom && compelling) {
|
|
5974
|
+
notes.push("Meets expert standards - McKinsey/TED quality");
|
|
5599
5975
|
}
|
|
5600
5976
|
return {
|
|
5601
5977
|
score: Math.max(0, score),
|
|
@@ -7323,13 +7699,63 @@ var PowerPointGenerator = class {
|
|
|
7323
7699
|
|
|
7324
7700
|
// src/generators/PDFGenerator.ts
|
|
7325
7701
|
import puppeteer from "puppeteer";
|
|
7702
|
+
var cachedBrowser = null;
|
|
7703
|
+
var browserIdleTimeout = null;
|
|
7704
|
+
var BROWSER_IDLE_MS = 3e4;
|
|
7326
7705
|
var PDFGenerator = class {
|
|
7327
7706
|
defaultOptions = {
|
|
7328
7707
|
orientation: "landscape",
|
|
7329
7708
|
printBackground: true,
|
|
7330
7709
|
format: "Slide",
|
|
7331
|
-
scale: 1
|
|
7710
|
+
scale: 1,
|
|
7711
|
+
reuseBrowser: true
|
|
7332
7712
|
};
|
|
7713
|
+
/**
|
|
7714
|
+
* Get or create a browser instance
|
|
7715
|
+
*/
|
|
7716
|
+
async getBrowser(reuse) {
|
|
7717
|
+
if (browserIdleTimeout) {
|
|
7718
|
+
clearTimeout(browserIdleTimeout);
|
|
7719
|
+
browserIdleTimeout = null;
|
|
7720
|
+
}
|
|
7721
|
+
if (reuse && cachedBrowser && cachedBrowser.connected) {
|
|
7722
|
+
logger.progress("Using cached browser instance");
|
|
7723
|
+
return cachedBrowser;
|
|
7724
|
+
}
|
|
7725
|
+
if (cachedBrowser && !cachedBrowser.connected) {
|
|
7726
|
+
cachedBrowser = null;
|
|
7727
|
+
}
|
|
7728
|
+
logger.progress("Launching new browser instance...");
|
|
7729
|
+
const browser = await puppeteer.launch({
|
|
7730
|
+
headless: true,
|
|
7731
|
+
args: [
|
|
7732
|
+
"--no-sandbox",
|
|
7733
|
+
"--disable-setuid-sandbox",
|
|
7734
|
+
"--disable-dev-shm-usage",
|
|
7735
|
+
"--disable-gpu"
|
|
7736
|
+
]
|
|
7737
|
+
});
|
|
7738
|
+
if (reuse) {
|
|
7739
|
+
cachedBrowser = browser;
|
|
7740
|
+
}
|
|
7741
|
+
return browser;
|
|
7742
|
+
}
|
|
7743
|
+
/**
|
|
7744
|
+
* Schedule browser cleanup after idle period
|
|
7745
|
+
*/
|
|
7746
|
+
scheduleBrowserCleanup() {
|
|
7747
|
+
if (browserIdleTimeout) {
|
|
7748
|
+
clearTimeout(browserIdleTimeout);
|
|
7749
|
+
}
|
|
7750
|
+
browserIdleTimeout = setTimeout(async () => {
|
|
7751
|
+
if (cachedBrowser) {
|
|
7752
|
+
logger.progress("Closing idle browser instance");
|
|
7753
|
+
await cachedBrowser.close().catch(() => {
|
|
7754
|
+
});
|
|
7755
|
+
cachedBrowser = null;
|
|
7756
|
+
}
|
|
7757
|
+
}, BROWSER_IDLE_MS);
|
|
7758
|
+
}
|
|
7333
7759
|
/**
|
|
7334
7760
|
* Generate PDF from HTML presentation
|
|
7335
7761
|
* @param html - The HTML content of the presentation
|
|
@@ -7338,18 +7764,14 @@ var PDFGenerator = class {
|
|
|
7338
7764
|
*/
|
|
7339
7765
|
async generate(html, options = {}) {
|
|
7340
7766
|
const opts = { ...this.defaultOptions, ...options };
|
|
7767
|
+
const reuseBrowser = opts.reuseBrowser ?? true;
|
|
7341
7768
|
logger.progress("\u{1F4C4} Generating PDF...");
|
|
7342
|
-
let browser;
|
|
7769
|
+
let browser = null;
|
|
7770
|
+
let shouldCloseBrowser = !reuseBrowser;
|
|
7771
|
+
let page = null;
|
|
7343
7772
|
try {
|
|
7344
|
-
browser = await
|
|
7345
|
-
|
|
7346
|
-
args: [
|
|
7347
|
-
"--no-sandbox",
|
|
7348
|
-
"--disable-setuid-sandbox",
|
|
7349
|
-
"--disable-dev-shm-usage"
|
|
7350
|
-
]
|
|
7351
|
-
});
|
|
7352
|
-
const page = await browser.newPage();
|
|
7773
|
+
browser = await this.getBrowser(reuseBrowser);
|
|
7774
|
+
page = await browser.newPage();
|
|
7353
7775
|
const printHtml = this.preparePrintHtml(html);
|
|
7354
7776
|
await page.setViewport({
|
|
7355
7777
|
width: 1920,
|
|
@@ -7385,13 +7807,38 @@ var PDFGenerator = class {
|
|
|
7385
7807
|
} catch (error) {
|
|
7386
7808
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
7387
7809
|
logger.error(`PDF generation failed: ${errorMessage}`);
|
|
7810
|
+
shouldCloseBrowser = true;
|
|
7388
7811
|
throw new Error(`PDF generation failed: ${errorMessage}`);
|
|
7389
7812
|
} finally {
|
|
7390
|
-
if (
|
|
7391
|
-
await
|
|
7813
|
+
if (page) {
|
|
7814
|
+
await page.close().catch(() => {
|
|
7815
|
+
});
|
|
7816
|
+
}
|
|
7817
|
+
if (shouldCloseBrowser && browser) {
|
|
7818
|
+
await browser.close().catch(() => {
|
|
7819
|
+
});
|
|
7820
|
+
if (browser === cachedBrowser) {
|
|
7821
|
+
cachedBrowser = null;
|
|
7822
|
+
}
|
|
7823
|
+
} else if (reuseBrowser) {
|
|
7824
|
+
this.scheduleBrowserCleanup();
|
|
7392
7825
|
}
|
|
7393
7826
|
}
|
|
7394
7827
|
}
|
|
7828
|
+
/**
|
|
7829
|
+
* Manually close the cached browser (call before process exit)
|
|
7830
|
+
*/
|
|
7831
|
+
static async closeBrowser() {
|
|
7832
|
+
if (browserIdleTimeout) {
|
|
7833
|
+
clearTimeout(browserIdleTimeout);
|
|
7834
|
+
browserIdleTimeout = null;
|
|
7835
|
+
}
|
|
7836
|
+
if (cachedBrowser) {
|
|
7837
|
+
await cachedBrowser.close().catch(() => {
|
|
7838
|
+
});
|
|
7839
|
+
cachedBrowser = null;
|
|
7840
|
+
}
|
|
7841
|
+
}
|
|
7395
7842
|
/**
|
|
7396
7843
|
* Prepare HTML for print/PDF output
|
|
7397
7844
|
* Modifies the HTML to work better as a printed document
|
|
@@ -7405,12 +7852,24 @@ var PDFGenerator = class {
|
|
|
7405
7852
|
margin: 0;
|
|
7406
7853
|
}
|
|
7407
7854
|
|
|
7855
|
+
/* Font rendering optimization for print */
|
|
7856
|
+
* {
|
|
7857
|
+
-webkit-font-smoothing: antialiased;
|
|
7858
|
+
-moz-osx-font-smoothing: grayscale;
|
|
7859
|
+
text-rendering: optimizeLegibility;
|
|
7860
|
+
font-feature-settings: "liga" 1, "kern" 1;
|
|
7861
|
+
}
|
|
7862
|
+
|
|
7408
7863
|
@media print {
|
|
7409
7864
|
html, body {
|
|
7410
7865
|
margin: 0;
|
|
7411
7866
|
padding: 0;
|
|
7412
7867
|
width: 1920px;
|
|
7413
7868
|
height: 1080px;
|
|
7869
|
+
/* Print color optimization */
|
|
7870
|
+
color-adjust: exact;
|
|
7871
|
+
-webkit-print-color-adjust: exact;
|
|
7872
|
+
print-color-adjust: exact;
|
|
7414
7873
|
}
|
|
7415
7874
|
|
|
7416
7875
|
.reveal .slides {
|
|
@@ -7427,7 +7886,8 @@ var PDFGenerator = class {
|
|
|
7427
7886
|
width: 1920px !important;
|
|
7428
7887
|
height: 1080px !important;
|
|
7429
7888
|
margin: 0 !important;
|
|
7430
|
-
|
|
7889
|
+
/* Professional safe margins: 80px (4.2%) */
|
|
7890
|
+
padding: 80px !important;
|
|
7431
7891
|
box-sizing: border-box;
|
|
7432
7892
|
position: relative !important;
|
|
7433
7893
|
top: auto !important;
|
|
@@ -7451,7 +7911,7 @@ var PDFGenerator = class {
|
|
|
7451
7911
|
}
|
|
7452
7912
|
|
|
7453
7913
|
/* Disable animations for print */
|
|
7454
|
-
|
|
7914
|
+
*, *::before, *::after {
|
|
7455
7915
|
animation: none !important;
|
|
7456
7916
|
transition: none !important;
|
|
7457
7917
|
}
|
|
@@ -7467,6 +7927,30 @@ var PDFGenerator = class {
|
|
|
7467
7927
|
.reveal .navigate-down {
|
|
7468
7928
|
display: none !important;
|
|
7469
7929
|
}
|
|
7930
|
+
|
|
7931
|
+
/* Typography refinements for print */
|
|
7932
|
+
h1, h2, h3, h4 {
|
|
7933
|
+
orphans: 3;
|
|
7934
|
+
widows: 3;
|
|
7935
|
+
page-break-after: avoid;
|
|
7936
|
+
}
|
|
7937
|
+
|
|
7938
|
+
p, li {
|
|
7939
|
+
orphans: 2;
|
|
7940
|
+
widows: 2;
|
|
7941
|
+
}
|
|
7942
|
+
|
|
7943
|
+
/* Ensure links are readable in print */
|
|
7944
|
+
a {
|
|
7945
|
+
text-decoration: none;
|
|
7946
|
+
}
|
|
7947
|
+
|
|
7948
|
+
/* Prevent image overflow */
|
|
7949
|
+
img {
|
|
7950
|
+
max-width: 100%;
|
|
7951
|
+
height: auto;
|
|
7952
|
+
page-break-inside: avoid;
|
|
7953
|
+
}
|
|
7470
7954
|
}
|
|
7471
7955
|
|
|
7472
7956
|
/* Force print mode in Puppeteer */
|
|
@@ -7474,6 +7958,11 @@ var PDFGenerator = class {
|
|
|
7474
7958
|
page-break-after: always;
|
|
7475
7959
|
page-break-inside: avoid;
|
|
7476
7960
|
}
|
|
7961
|
+
|
|
7962
|
+
/* High contrast mode for business presentations */
|
|
7963
|
+
.reveal section {
|
|
7964
|
+
color-scheme: light;
|
|
7965
|
+
}
|
|
7477
7966
|
</style>
|
|
7478
7967
|
`;
|
|
7479
7968
|
if (html.includes("</head>")) {
|
|
@@ -8066,6 +8555,7 @@ export {
|
|
|
8066
8555
|
CompositeImageProvider,
|
|
8067
8556
|
ContentAnalyzer,
|
|
8068
8557
|
ContentPatternClassifier,
|
|
8558
|
+
KnowledgeBaseError,
|
|
8069
8559
|
KnowledgeGateway,
|
|
8070
8560
|
LocalImageProvider,
|
|
8071
8561
|
MermaidProvider,
|