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.js
CHANGED
|
@@ -35,6 +35,7 @@ __export(index_exports, {
|
|
|
35
35
|
CompositeImageProvider: () => CompositeImageProvider,
|
|
36
36
|
ContentAnalyzer: () => ContentAnalyzer,
|
|
37
37
|
ContentPatternClassifier: () => ContentPatternClassifier,
|
|
38
|
+
KnowledgeBaseError: () => KnowledgeBaseError,
|
|
38
39
|
KnowledgeGateway: () => KnowledgeGateway,
|
|
39
40
|
LocalImageProvider: () => LocalImageProvider,
|
|
40
41
|
MermaidProvider: () => MermaidProvider,
|
|
@@ -67,32 +68,100 @@ __export(index_exports, {
|
|
|
67
68
|
module.exports = __toCommonJS(index_exports);
|
|
68
69
|
|
|
69
70
|
// src/types/index.ts
|
|
70
|
-
var ValidationError = class extends Error {
|
|
71
|
+
var ValidationError = class _ValidationError extends Error {
|
|
71
72
|
constructor(errors, message = "Validation failed") {
|
|
72
|
-
|
|
73
|
+
const errorList = errors.map((e) => ` \u2022 ${e}`).join("\n");
|
|
74
|
+
const suggestions = _ValidationError.getSuggestions(errors);
|
|
75
|
+
const suggestionList = suggestions.length > 0 ? "\n\nHow to fix:\n" + suggestions.map((s) => ` \u2192 ${s}`).join("\n") : "";
|
|
76
|
+
super(`${message}
|
|
77
|
+
|
|
78
|
+
Issues found:
|
|
79
|
+
${errorList}${suggestionList}`);
|
|
73
80
|
this.errors = errors;
|
|
74
81
|
this.name = "ValidationError";
|
|
82
|
+
this.suggestions = suggestions;
|
|
83
|
+
}
|
|
84
|
+
suggestions;
|
|
85
|
+
static getSuggestions(errors) {
|
|
86
|
+
const suggestions = [];
|
|
87
|
+
for (const error of errors) {
|
|
88
|
+
if (error.includes("Content is required")) {
|
|
89
|
+
suggestions.push("Provide markdown content with at least one ## section header");
|
|
90
|
+
}
|
|
91
|
+
if (error.includes("Mode must be")) {
|
|
92
|
+
suggestions.push('Use mode: "keynote" for TED-style or mode: "business" for corporate');
|
|
93
|
+
}
|
|
94
|
+
if (error.includes("Title is required")) {
|
|
95
|
+
suggestions.push('Add a title: "Your Presentation Title" to your config');
|
|
96
|
+
}
|
|
97
|
+
if (error.includes("output format")) {
|
|
98
|
+
suggestions.push('Specify format: ["html"] or format: ["html", "pdf"]');
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return [...new Set(suggestions)];
|
|
75
102
|
}
|
|
76
103
|
};
|
|
77
|
-
var QAFailureError = class extends Error {
|
|
78
|
-
constructor(score, threshold, qaResults, message
|
|
79
|
-
|
|
104
|
+
var QAFailureError = class _QAFailureError extends Error {
|
|
105
|
+
constructor(score, threshold, qaResults, message) {
|
|
106
|
+
const topIssues = _QAFailureError.getTopIssues(qaResults);
|
|
107
|
+
const issueList = topIssues.map((i) => ` \u2022 ${i}`).join("\n");
|
|
108
|
+
const helpText = `
|
|
109
|
+
|
|
110
|
+
Score: ${score}/100 (threshold: ${threshold})
|
|
111
|
+
|
|
112
|
+
Top issues to fix:
|
|
113
|
+
${issueList}
|
|
114
|
+
|
|
115
|
+
How to improve:
|
|
116
|
+
\u2192 Ensure each slide has meaningful content (not just a title)
|
|
117
|
+
\u2192 Keep text concise - aim for <40 words per slide
|
|
118
|
+
\u2192 Use timeline/process slides for step-by-step content
|
|
119
|
+
\u2192 Add visuals for data-heavy slides`;
|
|
120
|
+
super(message || `QA score ${score} below threshold ${threshold}${helpText}`);
|
|
80
121
|
this.score = score;
|
|
81
122
|
this.threshold = threshold;
|
|
82
123
|
this.qaResults = qaResults;
|
|
83
124
|
this.name = "QAFailureError";
|
|
125
|
+
this.topIssues = topIssues;
|
|
84
126
|
}
|
|
127
|
+
topIssues;
|
|
85
128
|
getIssues() {
|
|
86
129
|
return this.qaResults.issues.map((issue) => issue.message);
|
|
87
130
|
}
|
|
131
|
+
static getTopIssues(qaResults) {
|
|
132
|
+
const issues = [];
|
|
133
|
+
if (qaResults.slideScores) {
|
|
134
|
+
for (const slide of qaResults.slideScores) {
|
|
135
|
+
if (slide.criticalIssues) {
|
|
136
|
+
issues.push(...slide.criticalIssues);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (qaResults.issues) {
|
|
141
|
+
issues.push(...qaResults.issues.map((i) => i.message));
|
|
142
|
+
}
|
|
143
|
+
return [...new Set(issues)].slice(0, 5);
|
|
144
|
+
}
|
|
88
145
|
};
|
|
89
146
|
var TemplateNotFoundError = class extends Error {
|
|
90
|
-
constructor(templatePath, message
|
|
91
|
-
super(message
|
|
147
|
+
constructor(templatePath, message) {
|
|
148
|
+
super(message || `Template not found: ${templatePath}
|
|
149
|
+
|
|
150
|
+
Available templates: title, content, timeline, process, metrics, quote, cta, thank-you`);
|
|
92
151
|
this.templatePath = templatePath;
|
|
93
152
|
this.name = "TemplateNotFoundError";
|
|
94
153
|
}
|
|
95
154
|
};
|
|
155
|
+
var KnowledgeBaseError = class extends Error {
|
|
156
|
+
constructor(field, context, message) {
|
|
157
|
+
super(message || `Knowledge base configuration error: Missing "${field}" in ${context}
|
|
158
|
+
|
|
159
|
+
Check assets/presentation-knowledge.yaml for the correct structure.`);
|
|
160
|
+
this.field = field;
|
|
161
|
+
this.context = context;
|
|
162
|
+
this.name = "KnowledgeBaseError";
|
|
163
|
+
}
|
|
164
|
+
};
|
|
96
165
|
|
|
97
166
|
// src/core/PresentationEngine.ts
|
|
98
167
|
var fs2 = __toESM(require("fs"));
|
|
@@ -102,7 +171,6 @@ var os = __toESM(require("os"));
|
|
|
102
171
|
// src/kb/KnowledgeGateway.ts
|
|
103
172
|
var import_fs = require("fs");
|
|
104
173
|
var import_path = require("path");
|
|
105
|
-
var import_url = require("url");
|
|
106
174
|
var yaml = __toESM(require("yaml"));
|
|
107
175
|
|
|
108
176
|
// src/utils/Logger.ts
|
|
@@ -173,11 +241,7 @@ var logger = new Logger({
|
|
|
173
241
|
});
|
|
174
242
|
|
|
175
243
|
// src/kb/KnowledgeGateway.ts
|
|
176
|
-
var import_meta = {};
|
|
177
244
|
function getModuleDir() {
|
|
178
|
-
if (typeof import_meta !== "undefined" && import_meta.url) {
|
|
179
|
-
return (0, import_path.dirname)((0, import_url.fileURLToPath)(import_meta.url));
|
|
180
|
-
}
|
|
181
245
|
if (typeof __dirname !== "undefined") {
|
|
182
246
|
return __dirname;
|
|
183
247
|
}
|
|
@@ -847,7 +911,8 @@ var KnowledgeGateway = class {
|
|
|
847
911
|
// Subtitle needs more room for complete phrases - at least 10 words for keynote
|
|
848
912
|
subtitle: { maxWords: mode === "keynote" ? 12 : Math.min(20, Math.floor(maxWords / 2)) },
|
|
849
913
|
context: { maxWords: Math.min(30, Math.floor(maxWords / 2)) },
|
|
850
|
-
|
|
914
|
+
// Step descriptions need minimum 8 words for meaningful content
|
|
915
|
+
step: { maxWords: Math.max(8, Math.min(20, Math.floor(maxWords / 3))) },
|
|
851
916
|
columnContent: { maxWords: Math.min(25, Math.floor(maxWords / 3)) }
|
|
852
917
|
};
|
|
853
918
|
}
|
|
@@ -2866,25 +2931,43 @@ var SlideFactory = class {
|
|
|
2866
2931
|
createComparisonSlide(index, section) {
|
|
2867
2932
|
const comparison = this.classifier.extractComparison(section);
|
|
2868
2933
|
const labels = this.config.defaults.comparison;
|
|
2934
|
+
const title = this.createTitle(section.header, section);
|
|
2935
|
+
const normalizedTitle = title.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
2869
2936
|
const leftFallback = labels.optionLabels[0] ?? "Option A";
|
|
2870
2937
|
const rightFallback = labels.optionLabels[1] ?? "Option B";
|
|
2871
|
-
|
|
2872
|
-
|
|
2938
|
+
let leftColumn = comparison?.left || section.bullets[0] || "";
|
|
2939
|
+
let rightColumn = comparison?.right || section.bullets[1] || "";
|
|
2940
|
+
const normalizedLeft = leftColumn.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
2941
|
+
const normalizedRight = rightColumn.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
2942
|
+
const leftIsDuplicate = normalizedLeft === normalizedTitle || normalizedTitle.includes(normalizedLeft);
|
|
2943
|
+
const rightIsDuplicate = normalizedRight === normalizedTitle || normalizedTitle.includes(normalizedRight);
|
|
2944
|
+
if (leftIsDuplicate || rightIsDuplicate || !leftColumn && !rightColumn) {
|
|
2945
|
+
if (section.bullets.length >= 2) {
|
|
2946
|
+
logger.warn(`Comparison slide "${title}" has duplicate content, using bullet points instead`);
|
|
2947
|
+
return this.createBulletSlide(index, section);
|
|
2948
|
+
}
|
|
2949
|
+
if (leftIsDuplicate && rightIsDuplicate) {
|
|
2950
|
+
logger.warn(`Skipping comparison slide "${title}" - content duplicates title`);
|
|
2951
|
+
return null;
|
|
2952
|
+
}
|
|
2953
|
+
if (leftIsDuplicate) leftColumn = leftFallback;
|
|
2954
|
+
if (rightIsDuplicate) rightColumn = rightFallback;
|
|
2955
|
+
}
|
|
2873
2956
|
return {
|
|
2874
2957
|
index,
|
|
2875
2958
|
type: "comparison",
|
|
2876
2959
|
data: {
|
|
2877
|
-
title
|
|
2960
|
+
title,
|
|
2878
2961
|
columns: [
|
|
2879
2962
|
{
|
|
2880
2963
|
title: labels.leftLabel,
|
|
2881
2964
|
// FROM KB - not hardcoded 'Before'
|
|
2882
|
-
content: this.truncateText(leftColumn, this.config.rules.wordsPerSlide.max)
|
|
2965
|
+
content: this.truncateText(leftColumn || leftFallback, this.config.rules.wordsPerSlide.max)
|
|
2883
2966
|
},
|
|
2884
2967
|
{
|
|
2885
2968
|
title: labels.rightLabel,
|
|
2886
2969
|
// FROM KB - not hardcoded 'After'
|
|
2887
|
-
content: this.truncateText(rightColumn, this.config.rules.wordsPerSlide.max)
|
|
2970
|
+
content: this.truncateText(rightColumn || rightFallback, this.config.rules.wordsPerSlide.max)
|
|
2888
2971
|
}
|
|
2889
2972
|
]
|
|
2890
2973
|
},
|
|
@@ -2895,9 +2978,8 @@ var SlideFactory = class {
|
|
|
2895
2978
|
const steps = this.classifier.extractSteps(section);
|
|
2896
2979
|
const maxSteps = Math.min(
|
|
2897
2980
|
steps.length,
|
|
2898
|
-
this.config.rules.bulletsPerSlide.max,
|
|
2899
2981
|
this.config.millersLaw.maxItems
|
|
2900
|
-
// FROM KB - 7±2 rule
|
|
2982
|
+
// FROM KB - 7±2 rule (max 9)
|
|
2901
2983
|
);
|
|
2902
2984
|
return {
|
|
2903
2985
|
index,
|
|
@@ -2919,7 +3001,7 @@ var SlideFactory = class {
|
|
|
2919
3001
|
}
|
|
2920
3002
|
createProcessSlide(index, section) {
|
|
2921
3003
|
const steps = this.classifier.extractSteps(section);
|
|
2922
|
-
const maxSteps = Math.min(steps.length, this.config.
|
|
3004
|
+
const maxSteps = Math.min(steps.length, this.config.millersLaw.maxItems);
|
|
2923
3005
|
return {
|
|
2924
3006
|
index,
|
|
2925
3007
|
type: "process",
|
|
@@ -3156,10 +3238,21 @@ var SlideFactory = class {
|
|
|
3156
3238
|
* Used for section headers with strong conclusions.
|
|
3157
3239
|
*/
|
|
3158
3240
|
createTitleImpactSlide(index, section) {
|
|
3241
|
+
const title = this.cleanText(section.header);
|
|
3159
3242
|
const supportingText = section.content || section.bullets.slice(0, 2).join(". ");
|
|
3160
3243
|
const truncatedSupport = this.truncateText(supportingText, this.config.defaults.context.maxWords);
|
|
3244
|
+
const normalizedTitle = title.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
3245
|
+
const normalizedBody = truncatedSupport.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
3246
|
+
const bodyIsDuplicate = normalizedBody === normalizedTitle || normalizedTitle.includes(normalizedBody) || normalizedBody.includes(normalizedTitle) || truncatedSupport.length < 10;
|
|
3247
|
+
if (bodyIsDuplicate) {
|
|
3248
|
+
if (section.bullets.length >= 2 && this.config.mode === "business") {
|
|
3249
|
+
return this.createBulletSlide(index, section);
|
|
3250
|
+
}
|
|
3251
|
+
logger.warn(`Skipping title-impact slide "${title}" - no distinct body content`);
|
|
3252
|
+
return null;
|
|
3253
|
+
}
|
|
3161
3254
|
const data = {
|
|
3162
|
-
title
|
|
3255
|
+
title
|
|
3163
3256
|
};
|
|
3164
3257
|
if (truncatedSupport) {
|
|
3165
3258
|
data.body = truncatedSupport;
|
|
@@ -5326,169 +5419,422 @@ var VisualQualityEvaluator = class {
|
|
|
5326
5419
|
const title = currentSlide.querySelector("h1, h2, .title")?.textContent?.trim() || "";
|
|
5327
5420
|
const body = currentSlide.querySelector(".body, p:not(.subtitle)")?.textContent?.trim() || "";
|
|
5328
5421
|
const bullets = Array.from(currentSlide.querySelectorAll("li")).map((li) => li.textContent?.trim() || "");
|
|
5422
|
+
const hasSteps = !!currentSlide.querySelector(".steps, .process-steps, .timeline");
|
|
5423
|
+
const steps = Array.from(currentSlide.querySelectorAll(".step, .process-step, .timeline-item")).map(
|
|
5424
|
+
(s) => s.textContent?.trim() || ""
|
|
5425
|
+
);
|
|
5426
|
+
const hasMetrics = !!currentSlide.querySelector(".metrics, .metric");
|
|
5329
5427
|
const hasImage = !!currentSlide.querySelector("img");
|
|
5330
5428
|
const hasChart = !!currentSlide.querySelector(".chart, svg, canvas");
|
|
5331
5429
|
const classList = Array.from(currentSlide.classList);
|
|
5332
5430
|
const backgroundColor = window.getComputedStyle(currentSlide).backgroundColor;
|
|
5333
5431
|
const titleEl = currentSlide.querySelector("h1, h2, .title");
|
|
5334
5432
|
const titleStyles = titleEl ? window.getComputedStyle(titleEl) : null;
|
|
5433
|
+
const truncatedElements = [];
|
|
5434
|
+
const contentElements = currentSlide.querySelectorAll("h1, h2, h3, p, span, li, .body, .step-desc, .step-title, .timeline-content");
|
|
5435
|
+
contentElements.forEach((el, idx) => {
|
|
5436
|
+
const styles = window.getComputedStyle(el);
|
|
5437
|
+
const text = el.textContent?.trim() || "";
|
|
5438
|
+
if (text.length < 15) return;
|
|
5439
|
+
const isLayoutContainer = el.classList?.contains("slide-content") || el.classList?.contains("steps") || el.classList?.contains("timeline") || el.classList?.contains("process-steps");
|
|
5440
|
+
if (isLayoutContainer) return;
|
|
5441
|
+
if (styles.textOverflow === "ellipsis") {
|
|
5442
|
+
if (el.scrollWidth > el.clientWidth + 5) {
|
|
5443
|
+
truncatedElements.push(`Element ${idx}: "${text.substring(0, 30)}..." is truncated horizontally`);
|
|
5444
|
+
}
|
|
5445
|
+
}
|
|
5446
|
+
const scrollHeight = el.scrollHeight;
|
|
5447
|
+
const clientHeight = el.clientHeight;
|
|
5448
|
+
if (scrollHeight > clientHeight + 20) {
|
|
5449
|
+
const overflow = styles.overflow || styles.overflowY;
|
|
5450
|
+
if (overflow === "hidden" || overflow === "clip") {
|
|
5451
|
+
truncatedElements.push(`Element ${idx}: "${text.substring(0, 30)}..." is truncated vertically`);
|
|
5452
|
+
}
|
|
5453
|
+
}
|
|
5454
|
+
});
|
|
5455
|
+
const allVisibleText = Array.from(currentSlide.querySelectorAll("*")).map((el) => el.textContent?.trim() || "").join(" ").trim();
|
|
5456
|
+
const isEmptySlide = allVisibleText.length < 10 && !hasImage && !hasChart;
|
|
5457
|
+
const hasOnlyTitle = title.length > 0 && body.length === 0 && bullets.length === 0 && steps.length === 0 && !hasSteps && !hasMetrics && !hasImage && !hasChart;
|
|
5458
|
+
const titleLower = title.toLowerCase();
|
|
5459
|
+
const bodyLower = body.toLowerCase();
|
|
5460
|
+
const isRedundant = titleLower.length > 10 && bodyLower.length > 10 && (titleLower.includes(bodyLower) || bodyLower.includes(titleLower));
|
|
5461
|
+
const contrastIssues = [];
|
|
5462
|
+
const parseRGB = (color) => {
|
|
5463
|
+
const match = color.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
|
|
5464
|
+
if (match && match[1] !== void 0 && match[2] !== void 0 && match[3] !== void 0) {
|
|
5465
|
+
return { r: parseInt(match[1]), g: parseInt(match[2]), b: parseInt(match[3]) };
|
|
5466
|
+
}
|
|
5467
|
+
return null;
|
|
5468
|
+
};
|
|
5469
|
+
const getLuminance = (rgb) => {
|
|
5470
|
+
const values = [rgb.r, rgb.g, rgb.b].map((c) => {
|
|
5471
|
+
c = c / 255;
|
|
5472
|
+
return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
|
|
5473
|
+
});
|
|
5474
|
+
return 0.2126 * (values[0] ?? 0) + 0.7152 * (values[1] ?? 0) + 0.0722 * (values[2] ?? 0);
|
|
5475
|
+
};
|
|
5476
|
+
const getContrastRatio = (fg, bg) => {
|
|
5477
|
+
const l1 = getLuminance(fg);
|
|
5478
|
+
const l2 = getLuminance(bg);
|
|
5479
|
+
const lighter = Math.max(l1, l2);
|
|
5480
|
+
const darker = Math.min(l1, l2);
|
|
5481
|
+
return (lighter + 0.05) / (darker + 0.05);
|
|
5482
|
+
};
|
|
5483
|
+
const bgRGB = parseRGB(backgroundColor);
|
|
5484
|
+
if (titleEl && bgRGB) {
|
|
5485
|
+
const titleRGB = parseRGB(titleStyles?.color || "");
|
|
5486
|
+
if (titleRGB) {
|
|
5487
|
+
const contrast = getContrastRatio(titleRGB, bgRGB);
|
|
5488
|
+
if (contrast < 4.5) {
|
|
5489
|
+
contrastIssues.push(`Title contrast ${contrast.toFixed(1)}:1 (need 4.5:1)`);
|
|
5490
|
+
}
|
|
5491
|
+
}
|
|
5492
|
+
}
|
|
5493
|
+
const bodyEl = currentSlide.querySelector(".body, p:not(.subtitle)");
|
|
5494
|
+
if (bodyEl && bgRGB) {
|
|
5495
|
+
const bodyStyles2 = window.getComputedStyle(bodyEl);
|
|
5496
|
+
const bodyRGB = parseRGB(bodyStyles2.color);
|
|
5497
|
+
if (bodyRGB) {
|
|
5498
|
+
const contrast = getContrastRatio(bodyRGB, bgRGB);
|
|
5499
|
+
if (contrast < 4.5) {
|
|
5500
|
+
contrastIssues.push(`Body contrast ${contrast.toFixed(1)}:1 (need 4.5:1)`);
|
|
5501
|
+
}
|
|
5502
|
+
}
|
|
5503
|
+
}
|
|
5504
|
+
const hasContrastIssues = contrastIssues.length > 0;
|
|
5505
|
+
const layoutIssues = [];
|
|
5506
|
+
const slideRect = currentSlide.getBoundingClientRect();
|
|
5507
|
+
const layoutElements = currentSlide.querySelectorAll("h1, h2, h3, p, ul, ol, .number, .metric, img, canvas, svg");
|
|
5508
|
+
let topHeavy = 0;
|
|
5509
|
+
let bottomHeavy = 0;
|
|
5510
|
+
let leftHeavy = 0;
|
|
5511
|
+
let rightHeavy = 0;
|
|
5512
|
+
let centerY = slideRect.height / 2;
|
|
5513
|
+
let centerX = slideRect.width / 2;
|
|
5514
|
+
layoutElements.forEach((el) => {
|
|
5515
|
+
const rect = el.getBoundingClientRect();
|
|
5516
|
+
const elCenterY = rect.top + rect.height / 2 - slideRect.top;
|
|
5517
|
+
const elCenterX = rect.left + rect.width / 2 - slideRect.left;
|
|
5518
|
+
if (elCenterY < centerY) topHeavy++;
|
|
5519
|
+
else bottomHeavy++;
|
|
5520
|
+
if (elCenterX < centerX) leftHeavy++;
|
|
5521
|
+
else rightHeavy++;
|
|
5522
|
+
});
|
|
5523
|
+
const verticalBalance = Math.abs(topHeavy - bottomHeavy) / Math.max(1, topHeavy + bottomHeavy);
|
|
5524
|
+
const horizontalBalance = Math.abs(leftHeavy - rightHeavy) / Math.max(1, leftHeavy + rightHeavy);
|
|
5525
|
+
if (verticalBalance > 0.6 && contentElements.length > 2) {
|
|
5526
|
+
layoutIssues.push(`Vertical imbalance: ${topHeavy} top vs ${bottomHeavy} bottom`);
|
|
5527
|
+
}
|
|
5528
|
+
if (horizontalBalance > 0.6 && contentElements.length > 2) {
|
|
5529
|
+
layoutIssues.push(`Horizontal imbalance: ${leftHeavy} left vs ${rightHeavy} right`);
|
|
5530
|
+
}
|
|
5531
|
+
const hasLayoutIssues = layoutIssues.length > 0;
|
|
5532
|
+
const typographyIssues = [];
|
|
5533
|
+
const titleSize = parseFloat(titleStyles?.fontSize || "0");
|
|
5534
|
+
const bodyStyles = bodyEl ? window.getComputedStyle(bodyEl) : null;
|
|
5535
|
+
const bodySize = parseFloat(bodyStyles?.fontSize || "0");
|
|
5536
|
+
if (titleSize > 0 && bodySize > 0 && titleSize <= bodySize) {
|
|
5537
|
+
typographyIssues.push("Title not larger than body - hierarchy broken");
|
|
5538
|
+
}
|
|
5539
|
+
if (titleSize > 0 && bodySize > 0 && titleSize < bodySize * 1.3) {
|
|
5540
|
+
typographyIssues.push("Title not prominent enough vs body");
|
|
5541
|
+
}
|
|
5542
|
+
const hasTypographyIssues = typographyIssues.length > 0;
|
|
5543
|
+
const completenessIssues = [];
|
|
5544
|
+
const emptySteps = currentSlide.querySelectorAll(".steps:empty, .timeline:empty, .process-steps:empty, .steps > :empty");
|
|
5545
|
+
if (emptySteps.length > 0) {
|
|
5546
|
+
completenessIssues.push(`${emptySteps.length} empty step/timeline container(s) - content missing`);
|
|
5547
|
+
}
|
|
5548
|
+
if (body.includes("Lorem") || body.includes("placeholder") || body.includes("TODO")) {
|
|
5549
|
+
completenessIssues.push("Contains placeholder text");
|
|
5550
|
+
}
|
|
5551
|
+
const emptyBullets = bullets.filter((b) => b.length < 3).length;
|
|
5552
|
+
if (emptyBullets > 0 && bullets.length > 0) {
|
|
5553
|
+
completenessIssues.push(`${emptyBullets} empty/minimal bullets`);
|
|
5554
|
+
}
|
|
5555
|
+
const hasCompletenessIssues = completenessIssues.length > 0;
|
|
5556
|
+
const visualNeedsIssues = [];
|
|
5557
|
+
const visualRequiredTypes = ["data", "chart", "metrics", "comparison", "process", "timeline"];
|
|
5558
|
+
const visualRecommendedTypes = ["big-number", "quote", "testimonial"];
|
|
5559
|
+
const slideClasses = Array.from(currentSlide.classList).join(" ").toLowerCase();
|
|
5560
|
+
const needsVisual = visualRequiredTypes.some((t) => slideClasses.includes(t));
|
|
5561
|
+
const visualRecommended = visualRecommendedTypes.some((t) => slideClasses.includes(t));
|
|
5562
|
+
const contentText = (title + " " + body).toLowerCase();
|
|
5563
|
+
const dataIndicators = /\d+%|\$[\d,]+|\d+x|million|billion|percent|growth|increase|decrease|comparison|versus|vs\b|before|after/i;
|
|
5564
|
+
const hasDataContent = dataIndicators.test(contentText);
|
|
5565
|
+
const hasAnyVisual = hasImage || hasChart;
|
|
5566
|
+
if (needsVisual && !hasAnyVisual) {
|
|
5567
|
+
visualNeedsIssues.push("Data/process slide missing visual - needs chart, diagram, or image");
|
|
5568
|
+
} else if (hasDataContent && !hasAnyVisual && bullets.length === 0) {
|
|
5569
|
+
visualNeedsIssues.push("Content has numbers/data but no visualization - chart would help");
|
|
5570
|
+
} else if (visualRecommended && !hasAnyVisual) {
|
|
5571
|
+
visualNeedsIssues.push("Visual recommended for this slide type");
|
|
5572
|
+
}
|
|
5573
|
+
if (hasImage) {
|
|
5574
|
+
const img = currentSlide.querySelector("img");
|
|
5575
|
+
const imgSrc = img?.getAttribute("src") || "";
|
|
5576
|
+
if (imgSrc.includes("placeholder") || imgSrc.includes("picsum") || imgSrc.includes("via.placeholder")) {
|
|
5577
|
+
visualNeedsIssues.push("Placeholder image detected - needs real visual");
|
|
5578
|
+
}
|
|
5579
|
+
}
|
|
5580
|
+
const needsVisualButMissing = visualNeedsIssues.length > 0;
|
|
5581
|
+
const visualScore = hasAnyVisual ? 10 : needsVisual || hasDataContent ? 0 : 5;
|
|
5335
5582
|
return {
|
|
5336
5583
|
title,
|
|
5337
5584
|
body,
|
|
5338
5585
|
bullets,
|
|
5586
|
+
steps,
|
|
5587
|
+
// NEW: Timeline/process step content
|
|
5588
|
+
hasSteps,
|
|
5589
|
+
// NEW: Has .steps/.process-steps container
|
|
5590
|
+
hasMetrics,
|
|
5591
|
+
// NEW: Has metrics content
|
|
5339
5592
|
hasImage,
|
|
5340
5593
|
hasChart,
|
|
5341
5594
|
classList,
|
|
5342
5595
|
backgroundColor,
|
|
5343
5596
|
titleFontSize: titleStyles?.fontSize || "",
|
|
5344
5597
|
titleColor: titleStyles?.color || "",
|
|
5345
|
-
|
|
5598
|
+
// Include steps in content length calculation
|
|
5599
|
+
contentLength: (title + body + bullets.join(" ") + steps.join(" ")).length,
|
|
5600
|
+
// Visual quality flags
|
|
5601
|
+
hasTruncatedText: truncatedElements.length > 0,
|
|
5602
|
+
truncatedElements,
|
|
5603
|
+
isEmptySlide,
|
|
5604
|
+
hasOnlyTitle,
|
|
5605
|
+
isRedundant,
|
|
5606
|
+
allVisibleTextLength: allVisibleText.length,
|
|
5607
|
+
// NEW: Expert quality checks
|
|
5608
|
+
hasContrastIssues,
|
|
5609
|
+
contrastIssues,
|
|
5610
|
+
hasLayoutIssues,
|
|
5611
|
+
layoutIssues,
|
|
5612
|
+
hasTypographyIssues,
|
|
5613
|
+
typographyIssues,
|
|
5614
|
+
hasCompletenessIssues,
|
|
5615
|
+
completenessIssues,
|
|
5616
|
+
// Visual needs analysis
|
|
5617
|
+
needsVisualButMissing,
|
|
5618
|
+
visualNeedsIssues,
|
|
5619
|
+
visualScore
|
|
5346
5620
|
};
|
|
5347
5621
|
});
|
|
5348
5622
|
return this.scoreSlide(slideIndex, slideData, screenshotPath);
|
|
5349
5623
|
}
|
|
5624
|
+
/**
|
|
5625
|
+
* EXPERT-LEVEL SLIDE SCORING
|
|
5626
|
+
*
|
|
5627
|
+
* This evaluates each slide like Nancy Duarte, Carmine Gallo, or a McKinsey partner would.
|
|
5628
|
+
* It's not about rules - it's about whether the slide WORKS.
|
|
5629
|
+
*/
|
|
5350
5630
|
scoreSlide(slideIndex, slideData, screenshotPath) {
|
|
5351
5631
|
if (!slideData) {
|
|
5352
|
-
return
|
|
5353
|
-
slideIndex,
|
|
5354
|
-
slideType: "unknown",
|
|
5355
|
-
visualImpact: 0,
|
|
5356
|
-
visualImpactNotes: "Could not analyze slide",
|
|
5357
|
-
contentClarity: 0,
|
|
5358
|
-
contentClarityNotes: "Could not analyze slide",
|
|
5359
|
-
professionalPolish: 0,
|
|
5360
|
-
professionalPolishNotes: "Could not analyze slide",
|
|
5361
|
-
themeCoherence: 0,
|
|
5362
|
-
themeCoherenceNotes: "Could not analyze slide",
|
|
5363
|
-
totalScore: 0,
|
|
5364
|
-
screenshotPath
|
|
5365
|
-
};
|
|
5632
|
+
return this.createFailedSlideScore(slideIndex, "unknown", "Could not analyze slide", screenshotPath);
|
|
5366
5633
|
}
|
|
5367
5634
|
const slideType = this.inferSlideType(slideData);
|
|
5368
|
-
|
|
5369
|
-
|
|
5370
|
-
|
|
5371
|
-
visualImpact += 2;
|
|
5372
|
-
visualNotes.push("Has imagery");
|
|
5635
|
+
const criticalFailures = [];
|
|
5636
|
+
if (slideData.isEmptySlide) {
|
|
5637
|
+
criticalFailures.push("EMPTY SLIDE: No meaningful content - slide serves no purpose");
|
|
5373
5638
|
}
|
|
5374
|
-
if (slideData.
|
|
5375
|
-
|
|
5376
|
-
visualNotes.push("Has data visualization");
|
|
5639
|
+
if (slideData.hasOnlyTitle && !["title", "agenda", "thank-you", "cta"].includes(slideType)) {
|
|
5640
|
+
criticalFailures.push("INCOMPLETE: Slide has title but no body content - looks unfinished");
|
|
5377
5641
|
}
|
|
5378
|
-
|
|
5379
|
-
|
|
5380
|
-
"big_number",
|
|
5381
|
-
"metrics-grid",
|
|
5382
|
-
"metrics_grid",
|
|
5383
|
-
"three-column",
|
|
5384
|
-
"three_column",
|
|
5385
|
-
"three-points",
|
|
5386
|
-
"three_points",
|
|
5387
|
-
"title-impact",
|
|
5388
|
-
"title_impact",
|
|
5389
|
-
"cta",
|
|
5390
|
-
"call-to-action",
|
|
5391
|
-
"comparison",
|
|
5392
|
-
"timeline",
|
|
5393
|
-
"process",
|
|
5394
|
-
"quote",
|
|
5395
|
-
"testimonial"
|
|
5396
|
-
];
|
|
5397
|
-
if (highImpactTypes.some((t) => slideType.includes(t.replace(/_/g, "-")) || slideType.includes(t.replace(/-/g, "_")))) {
|
|
5398
|
-
visualImpact += 2;
|
|
5399
|
-
visualNotes.push("High-impact slide type");
|
|
5642
|
+
if (slideData.hasTruncatedText) {
|
|
5643
|
+
criticalFailures.push(`TRUNCATED TEXT: ${slideData.truncatedElements.length} element(s) cut off - message incomplete`);
|
|
5400
5644
|
}
|
|
5401
|
-
if (
|
|
5402
|
-
|
|
5403
|
-
visualNotes.push("Clean single statement");
|
|
5645
|
+
if (slideData.isRedundant) {
|
|
5646
|
+
criticalFailures.push("REDUNDANT: Title and body say the same thing - violates one-idea rule");
|
|
5404
5647
|
}
|
|
5405
|
-
if (
|
|
5406
|
-
|
|
5407
|
-
|
|
5648
|
+
if (slideData.hasCompletenessIssues) {
|
|
5649
|
+
slideData.completenessIssues.forEach((issue) => {
|
|
5650
|
+
if (issue.includes("empty")) {
|
|
5651
|
+
criticalFailures.push(`INCOMPLETE: ${issue}`);
|
|
5652
|
+
}
|
|
5653
|
+
});
|
|
5408
5654
|
}
|
|
5409
|
-
if (
|
|
5410
|
-
|
|
5411
|
-
|
|
5655
|
+
if (criticalFailures.length > 0) {
|
|
5656
|
+
return this.createFailedSlideScore(slideIndex, slideType, criticalFailures.join("; "), screenshotPath, criticalFailures);
|
|
5657
|
+
}
|
|
5658
|
+
let glanceTest = 8;
|
|
5659
|
+
const glanceNotes = [];
|
|
5660
|
+
const wordCount = slideData.contentLength / 6;
|
|
5661
|
+
if (wordCount > 40) {
|
|
5662
|
+
glanceTest = 3;
|
|
5663
|
+
glanceNotes.push("Too much text - fails glance test");
|
|
5664
|
+
} else if (wordCount > 25) {
|
|
5665
|
+
glanceTest -= 3;
|
|
5666
|
+
glanceNotes.push("Text-heavy - borderline glance test");
|
|
5667
|
+
} else if (wordCount < 15) {
|
|
5668
|
+
glanceTest += 1;
|
|
5669
|
+
glanceNotes.push("Clean, minimal text - passes glance test easily");
|
|
5412
5670
|
}
|
|
5413
5671
|
if (slideData.bullets.length > 5) {
|
|
5414
|
-
|
|
5415
|
-
|
|
5416
|
-
}
|
|
5417
|
-
|
|
5418
|
-
|
|
5419
|
-
|
|
5420
|
-
if (slideData.
|
|
5421
|
-
|
|
5422
|
-
|
|
5423
|
-
}
|
|
5424
|
-
|
|
5425
|
-
|
|
5426
|
-
|
|
5427
|
-
|
|
5428
|
-
|
|
5429
|
-
|
|
5430
|
-
|
|
5431
|
-
|
|
5432
|
-
|
|
5433
|
-
if (
|
|
5434
|
-
|
|
5435
|
-
|
|
5436
|
-
}
|
|
5437
|
-
|
|
5438
|
-
|
|
5439
|
-
|
|
5672
|
+
glanceTest -= 4;
|
|
5673
|
+
glanceNotes.push("Too many bullets - cannot scan in 3 seconds");
|
|
5674
|
+
} else if (slideData.bullets.length > 3) {
|
|
5675
|
+
glanceTest -= 2;
|
|
5676
|
+
glanceNotes.push("Multiple bullets - needs careful structuring");
|
|
5677
|
+
}
|
|
5678
|
+
if (slideData.hasImage || slideData.hasChart) {
|
|
5679
|
+
glanceTest += 1;
|
|
5680
|
+
glanceNotes.push("Visual element aids quick comprehension");
|
|
5681
|
+
}
|
|
5682
|
+
glanceTest = Math.max(0, Math.min(10, glanceTest));
|
|
5683
|
+
let oneIdea = 7;
|
|
5684
|
+
const oneIdeaNotes = [];
|
|
5685
|
+
if (slideData.title.length > 0 && slideData.title.length < 60) {
|
|
5686
|
+
oneIdea += 1;
|
|
5687
|
+
oneIdeaNotes.push("Clear, focused title");
|
|
5688
|
+
} else if (slideData.title.length > 80) {
|
|
5689
|
+
oneIdea -= 2;
|
|
5690
|
+
oneIdeaNotes.push("Title too long - multiple ideas?");
|
|
5691
|
+
} else if (slideData.title.length === 0) {
|
|
5692
|
+
oneIdea -= 4;
|
|
5693
|
+
oneIdeaNotes.push("No title - what is the one idea?");
|
|
5694
|
+
}
|
|
5695
|
+
const focusedTypes = ["big-number", "single-statement", "quote", "cta", "title"];
|
|
5696
|
+
if (focusedTypes.some((t) => slideType.includes(t))) {
|
|
5697
|
+
oneIdea += 2;
|
|
5698
|
+
oneIdeaNotes.push("Slide type naturally focuses on one idea");
|
|
5699
|
+
}
|
|
5700
|
+
if (slideData.bullets.length > 4) {
|
|
5701
|
+
oneIdea -= 3;
|
|
5702
|
+
oneIdeaNotes.push("Multiple bullets dilute the message");
|
|
5703
|
+
}
|
|
5704
|
+
oneIdea = Math.max(0, Math.min(10, oneIdea));
|
|
5705
|
+
let dataInkRatio = 7;
|
|
5706
|
+
const dataInkNotes = [];
|
|
5707
|
+
const structuredTypes = ["big-number", "metrics-grid", "three-column", "timeline", "process", "comparison"];
|
|
5708
|
+
if (structuredTypes.some((t) => slideType.includes(t))) {
|
|
5709
|
+
dataInkRatio += 1;
|
|
5710
|
+
dataInkNotes.push("Structured layout");
|
|
5711
|
+
}
|
|
5712
|
+
if (slideData.hasChart) {
|
|
5713
|
+
dataInkRatio += 2;
|
|
5714
|
+
dataInkNotes.push("Data visualization present - excellent");
|
|
5715
|
+
}
|
|
5716
|
+
if (slideData.hasImage) {
|
|
5717
|
+
dataInkRatio += 1;
|
|
5718
|
+
dataInkNotes.push("Supporting visual present");
|
|
5719
|
+
}
|
|
5720
|
+
if (slideData.needsVisualButMissing) {
|
|
5721
|
+
dataInkRatio -= 3;
|
|
5722
|
+
slideData.visualNeedsIssues.forEach((issue) => {
|
|
5723
|
+
dataInkNotes.push(`Visual: ${issue}`);
|
|
5724
|
+
});
|
|
5725
|
+
} else if (slideData.visualScore === 10) {
|
|
5726
|
+
dataInkNotes.push("Appropriate visuals for content type");
|
|
5727
|
+
}
|
|
5728
|
+
if (slideData.contentLength > 400) {
|
|
5729
|
+
dataInkRatio -= 4;
|
|
5730
|
+
dataInkNotes.push("Excessive text - low information density");
|
|
5731
|
+
}
|
|
5732
|
+
dataInkRatio = Math.max(0, Math.min(10, dataInkRatio));
|
|
5733
|
+
let professionalExecution = 7;
|
|
5734
|
+
const executionNotes = [];
|
|
5440
5735
|
const titleFontSize = parseFloat(slideData.titleFontSize || "0");
|
|
5441
5736
|
if (titleFontSize >= 40) {
|
|
5442
|
-
|
|
5443
|
-
|
|
5444
|
-
} else if (titleFontSize < 24) {
|
|
5445
|
-
|
|
5446
|
-
|
|
5737
|
+
professionalExecution += 1;
|
|
5738
|
+
executionNotes.push("Strong title typography");
|
|
5739
|
+
} else if (titleFontSize > 0 && titleFontSize < 24) {
|
|
5740
|
+
professionalExecution -= 1;
|
|
5741
|
+
executionNotes.push("Title could be more prominent");
|
|
5447
5742
|
}
|
|
5448
5743
|
if (slideData.contentLength > 10 && slideData.contentLength < 200) {
|
|
5449
|
-
|
|
5450
|
-
|
|
5451
|
-
}
|
|
5452
|
-
const polishedTypes = [
|
|
5453
|
-
"big-number",
|
|
5454
|
-
"metrics-grid",
|
|
5455
|
-
"three-column",
|
|
5456
|
-
"three-points",
|
|
5457
|
-
"comparison",
|
|
5458
|
-
"timeline",
|
|
5459
|
-
"process",
|
|
5460
|
-
"cta",
|
|
5461
|
-
"title",
|
|
5462
|
-
"thank-you"
|
|
5463
|
-
];
|
|
5464
|
-
if (polishedTypes.some((t) => slideType.includes(t))) {
|
|
5465
|
-
professionalPolish += 1;
|
|
5466
|
-
polishNotes.push("Well-structured layout");
|
|
5744
|
+
professionalExecution += 1;
|
|
5745
|
+
executionNotes.push("Well-balanced content density");
|
|
5467
5746
|
}
|
|
5468
|
-
professionalPolish = Math.max(0, Math.min(10, professionalPolish));
|
|
5469
|
-
let themeCoherence = 7;
|
|
5470
|
-
const coherenceNotes = [];
|
|
5471
5747
|
if (slideData.classList.some((c) => c.includes("slide-"))) {
|
|
5472
|
-
|
|
5473
|
-
|
|
5748
|
+
professionalExecution += 1;
|
|
5749
|
+
executionNotes.push("Consistent with design system");
|
|
5474
5750
|
}
|
|
5475
|
-
|
|
5476
|
-
|
|
5751
|
+
if (slideData.hasContrastIssues) {
|
|
5752
|
+
professionalExecution -= 3;
|
|
5753
|
+
slideData.contrastIssues.forEach((issue) => {
|
|
5754
|
+
executionNotes.push(`Contrast: ${issue}`);
|
|
5755
|
+
});
|
|
5756
|
+
} else {
|
|
5757
|
+
executionNotes.push("Good text contrast");
|
|
5758
|
+
}
|
|
5759
|
+
if (slideData.hasLayoutIssues) {
|
|
5760
|
+
professionalExecution -= 2;
|
|
5761
|
+
slideData.layoutIssues.forEach((issue) => {
|
|
5762
|
+
executionNotes.push(`Layout: ${issue}`);
|
|
5763
|
+
});
|
|
5764
|
+
} else if (slideData.contentLength > 50) {
|
|
5765
|
+
executionNotes.push("Balanced layout");
|
|
5766
|
+
}
|
|
5767
|
+
if (slideData.hasTypographyIssues) {
|
|
5768
|
+
professionalExecution -= 2;
|
|
5769
|
+
slideData.typographyIssues.forEach((issue) => {
|
|
5770
|
+
executionNotes.push(`Typography: ${issue}`);
|
|
5771
|
+
});
|
|
5772
|
+
} else if (titleFontSize > 0) {
|
|
5773
|
+
executionNotes.push("Good type hierarchy");
|
|
5774
|
+
}
|
|
5775
|
+
professionalExecution = Math.max(0, Math.min(10, professionalExecution));
|
|
5776
|
+
const totalScore = glanceTest + oneIdea + dataInkRatio + professionalExecution;
|
|
5777
|
+
const visualImpact = Math.round((glanceTest + oneIdea) / 2);
|
|
5778
|
+
const contentClarity = oneIdea;
|
|
5779
|
+
const professionalPolish = Math.round((dataInkRatio + professionalExecution) / 2);
|
|
5780
|
+
const themeCoherence = professionalExecution;
|
|
5477
5781
|
return {
|
|
5478
5782
|
slideIndex,
|
|
5479
5783
|
slideType,
|
|
5784
|
+
// New expert dimensions
|
|
5785
|
+
glanceTest,
|
|
5786
|
+
glanceTestNotes: glanceNotes.join("; ") || "Passes glance test",
|
|
5787
|
+
oneIdea,
|
|
5788
|
+
oneIdeaNotes: oneIdeaNotes.join("; ") || "Clear single message",
|
|
5789
|
+
dataInkRatio,
|
|
5790
|
+
dataInkNotes: dataInkNotes.join("; ") || "Good information density",
|
|
5791
|
+
professionalExecution,
|
|
5792
|
+
professionalExecutionNotes: executionNotes.join("; ") || "Professional quality",
|
|
5793
|
+
// Critical failures
|
|
5794
|
+
hasCriticalFailure: false,
|
|
5795
|
+
criticalFailures: [],
|
|
5796
|
+
// Legacy dimensions (for compatibility)
|
|
5480
5797
|
visualImpact,
|
|
5481
|
-
visualImpactNotes:
|
|
5798
|
+
visualImpactNotes: glanceNotes.join("; ") || "Standard",
|
|
5482
5799
|
contentClarity,
|
|
5483
|
-
contentClarityNotes:
|
|
5800
|
+
contentClarityNotes: oneIdeaNotes.join("; ") || "Good",
|
|
5484
5801
|
professionalPolish,
|
|
5485
|
-
professionalPolishNotes:
|
|
5802
|
+
professionalPolishNotes: executionNotes.join("; ") || "Acceptable",
|
|
5486
5803
|
themeCoherence,
|
|
5487
|
-
themeCoherenceNotes:
|
|
5804
|
+
themeCoherenceNotes: "Consistent",
|
|
5488
5805
|
totalScore,
|
|
5489
5806
|
screenshotPath
|
|
5490
5807
|
};
|
|
5491
5808
|
}
|
|
5809
|
+
/**
|
|
5810
|
+
* Create a failed slide score (for critical failures)
|
|
5811
|
+
*/
|
|
5812
|
+
createFailedSlideScore(slideIndex, slideType, reason, screenshotPath, criticalFailures = []) {
|
|
5813
|
+
return {
|
|
5814
|
+
slideIndex,
|
|
5815
|
+
slideType,
|
|
5816
|
+
glanceTest: 0,
|
|
5817
|
+
glanceTestNotes: "CRITICAL FAILURE: " + reason,
|
|
5818
|
+
oneIdea: 0,
|
|
5819
|
+
oneIdeaNotes: "CRITICAL FAILURE: " + reason,
|
|
5820
|
+
dataInkRatio: 0,
|
|
5821
|
+
dataInkNotes: "CRITICAL FAILURE: " + reason,
|
|
5822
|
+
professionalExecution: 0,
|
|
5823
|
+
professionalExecutionNotes: "CRITICAL FAILURE: " + reason,
|
|
5824
|
+
hasCriticalFailure: true,
|
|
5825
|
+
criticalFailures: criticalFailures.length > 0 ? criticalFailures : [reason],
|
|
5826
|
+
visualImpact: 0,
|
|
5827
|
+
visualImpactNotes: "CRITICAL: " + reason,
|
|
5828
|
+
contentClarity: 0,
|
|
5829
|
+
contentClarityNotes: "CRITICAL: " + reason,
|
|
5830
|
+
professionalPolish: 0,
|
|
5831
|
+
professionalPolishNotes: "CRITICAL: " + reason,
|
|
5832
|
+
themeCoherence: 0,
|
|
5833
|
+
themeCoherenceNotes: "CRITICAL: " + reason,
|
|
5834
|
+
totalScore: 0,
|
|
5835
|
+
screenshotPath
|
|
5836
|
+
};
|
|
5837
|
+
}
|
|
5492
5838
|
inferSlideType(slideData) {
|
|
5493
5839
|
const classList = slideData.classList || [];
|
|
5494
5840
|
for (const cls of classList) {
|
|
@@ -5600,34 +5946,44 @@ var VisualQualityEvaluator = class {
|
|
|
5600
5946
|
evaluateContentQuality(slideScores) {
|
|
5601
5947
|
let score = 25;
|
|
5602
5948
|
const notes = [];
|
|
5603
|
-
const
|
|
5604
|
-
|
|
5605
|
-
|
|
5606
|
-
|
|
5607
|
-
|
|
5608
|
-
|
|
5949
|
+
const criticalFailureSlides = slideScores.filter((s) => s.hasCriticalFailure);
|
|
5950
|
+
if (criticalFailureSlides.length > 0) {
|
|
5951
|
+
score = Math.max(0, 25 - criticalFailureSlides.length * 8);
|
|
5952
|
+
notes.push(`CRITICAL: ${criticalFailureSlides.length} slide(s) have critical failures`);
|
|
5953
|
+
criticalFailureSlides.forEach((s) => {
|
|
5954
|
+
s.criticalFailures.forEach((f) => notes.push(` - Slide ${s.slideIndex}: ${f}`));
|
|
5955
|
+
});
|
|
5609
5956
|
}
|
|
5610
|
-
const
|
|
5611
|
-
const
|
|
5957
|
+
const avgGlance = slideScores.reduce((sum, s) => sum + s.glanceTest, 0) / slideScores.length;
|
|
5958
|
+
const passesGlanceTest = avgGlance >= 6;
|
|
5959
|
+
if (!passesGlanceTest) {
|
|
5960
|
+
score -= 5;
|
|
5961
|
+
notes.push(`Glance Test: Average ${avgGlance.toFixed(1)}/10 - too text-heavy`);
|
|
5962
|
+
}
|
|
5963
|
+
const avgOneIdea = slideScores.reduce((sum, s) => sum + s.oneIdea, 0) / slideScores.length;
|
|
5964
|
+
const hasOneIdeaPerSlide = avgOneIdea >= 6;
|
|
5965
|
+
if (!hasOneIdeaPerSlide) {
|
|
5966
|
+
score -= 5;
|
|
5967
|
+
notes.push(`One Idea Rule: Average ${avgOneIdea.toFixed(1)}/10 - messages diluted`);
|
|
5968
|
+
}
|
|
5969
|
+
const messagesAreClear = avgOneIdea >= 7;
|
|
5970
|
+
const lowScoreSlides = slideScores.filter((s) => s.totalScore < 20).length;
|
|
5971
|
+
const appropriateDepth = lowScoreSlides < slideScores.length * 0.2;
|
|
5612
5972
|
if (!appropriateDepth) {
|
|
5613
5973
|
score -= 5;
|
|
5614
|
-
notes.push("Some slides have
|
|
5974
|
+
notes.push("Some slides have quality issues");
|
|
5615
5975
|
}
|
|
5616
|
-
const overloadedSlides = slideScores.filter(
|
|
5617
|
-
(s) => s.contentClarityNotes.includes("too long") || s.visualImpactNotes.includes("Too much")
|
|
5618
|
-
).length;
|
|
5976
|
+
const overloadedSlides = slideScores.filter((s) => s.glanceTest < 5).length;
|
|
5619
5977
|
const noOverload = overloadedSlides === 0;
|
|
5620
5978
|
if (!noOverload) {
|
|
5621
|
-
score -=
|
|
5622
|
-
notes.push(`${overloadedSlides} slides
|
|
5979
|
+
score -= 3;
|
|
5980
|
+
notes.push(`${overloadedSlides} slides fail glance test - too dense`);
|
|
5623
5981
|
}
|
|
5624
|
-
const
|
|
5625
|
-
|
|
5626
|
-
).length;
|
|
5627
|
-
const actionableInsights = insightSlides >= slideScores.length * 0.3;
|
|
5982
|
+
const excellentSlides = slideScores.filter((s) => s.totalScore >= 30).length;
|
|
5983
|
+
const actionableInsights = excellentSlides >= slideScores.length * 0.3;
|
|
5628
5984
|
if (!actionableInsights) {
|
|
5629
|
-
score -=
|
|
5630
|
-
notes.push("Need more high-impact
|
|
5985
|
+
score -= 3;
|
|
5986
|
+
notes.push("Need more high-impact slides");
|
|
5631
5987
|
}
|
|
5632
5988
|
return {
|
|
5633
5989
|
score: Math.max(0, score),
|
|
@@ -5641,30 +5997,50 @@ var VisualQualityEvaluator = class {
|
|
|
5641
5997
|
evaluateExecutiveReadiness(slideScores) {
|
|
5642
5998
|
let score = 25;
|
|
5643
5999
|
const notes = [];
|
|
5644
|
-
const
|
|
5645
|
-
|
|
5646
|
-
|
|
6000
|
+
const criticalFailureSlides = slideScores.filter((s) => s.hasCriticalFailure);
|
|
6001
|
+
if (criticalFailureSlides.length > 0) {
|
|
6002
|
+
score = 0;
|
|
6003
|
+
notes.push(`CRITICAL: ${criticalFailureSlides.length} slide(s) have critical failures - CANNOT show to executives`);
|
|
6004
|
+
criticalFailureSlides.forEach((s) => {
|
|
6005
|
+
notes.push(` - Slide ${s.slideIndex} (${s.slideType}): ${s.criticalFailures[0]}`);
|
|
6006
|
+
});
|
|
6007
|
+
return {
|
|
6008
|
+
score: 0,
|
|
6009
|
+
wouldImpress: false,
|
|
6010
|
+
readyForBoardroom: false,
|
|
6011
|
+
compelling: false,
|
|
6012
|
+
shareworthy: false,
|
|
6013
|
+
notes: notes.join(". ")
|
|
6014
|
+
};
|
|
6015
|
+
}
|
|
6016
|
+
const avgGlance = slideScores.reduce((sum, s) => sum + s.glanceTest, 0) / slideScores.length;
|
|
6017
|
+
const avgOneIdea = slideScores.reduce((sum, s) => sum + s.oneIdea, 0) / slideScores.length;
|
|
6018
|
+
const avgDataInk = slideScores.reduce((sum, s) => sum + s.dataInkRatio, 0) / slideScores.length;
|
|
6019
|
+
const avgExecution = slideScores.reduce((sum, s) => sum + s.professionalExecution, 0) / slideScores.length;
|
|
5647
6020
|
const avgTotal = slideScores.reduce((sum, s) => sum + s.totalScore, 0) / slideScores.length;
|
|
5648
|
-
const wouldImpress =
|
|
6021
|
+
const wouldImpress = avgGlance >= 7 && avgOneIdea >= 7 && avgExecution >= 7;
|
|
5649
6022
|
if (!wouldImpress) {
|
|
5650
6023
|
score -= 7;
|
|
5651
6024
|
notes.push("Needs more visual impact to impress");
|
|
5652
6025
|
}
|
|
5653
|
-
const readyForBoardroom =
|
|
6026
|
+
const readyForBoardroom = avgExecution >= 6 && avgDataInk >= 6;
|
|
5654
6027
|
if (!readyForBoardroom) {
|
|
5655
6028
|
score -= 7;
|
|
5656
|
-
notes.push(
|
|
6029
|
+
notes.push(`Boardroom readiness: Execution ${avgExecution.toFixed(1)}/10, Data-Ink ${avgDataInk.toFixed(1)}/10`);
|
|
5657
6030
|
}
|
|
5658
|
-
const compelling =
|
|
6031
|
+
const compelling = avgGlance >= 6 && avgOneIdea >= 6;
|
|
5659
6032
|
if (!compelling) {
|
|
5660
6033
|
score -= 5;
|
|
5661
|
-
notes.push(
|
|
6034
|
+
notes.push(`Compelling: Glance ${avgGlance.toFixed(1)}/10, OneIdea ${avgOneIdea.toFixed(1)}/10`);
|
|
5662
6035
|
}
|
|
5663
6036
|
const excellentSlides = slideScores.filter((s) => s.totalScore >= 30).length;
|
|
5664
6037
|
const shareworthy = excellentSlides >= slideScores.length * 0.4;
|
|
5665
6038
|
if (!shareworthy) {
|
|
5666
6039
|
score -= 5;
|
|
5667
|
-
notes.push(
|
|
6040
|
+
notes.push(`Shareworthy: ${excellentSlides}/${slideScores.length} excellent slides (need 40%)`);
|
|
6041
|
+
}
|
|
6042
|
+
if (wouldImpress && readyForBoardroom && compelling) {
|
|
6043
|
+
notes.push("Meets expert standards - McKinsey/TED quality");
|
|
5668
6044
|
}
|
|
5669
6045
|
return {
|
|
5670
6046
|
score: Math.max(0, score),
|
|
@@ -7392,13 +7768,63 @@ var PowerPointGenerator = class {
|
|
|
7392
7768
|
|
|
7393
7769
|
// src/generators/PDFGenerator.ts
|
|
7394
7770
|
var import_puppeteer = __toESM(require("puppeteer"));
|
|
7771
|
+
var cachedBrowser = null;
|
|
7772
|
+
var browserIdleTimeout = null;
|
|
7773
|
+
var BROWSER_IDLE_MS = 3e4;
|
|
7395
7774
|
var PDFGenerator = class {
|
|
7396
7775
|
defaultOptions = {
|
|
7397
7776
|
orientation: "landscape",
|
|
7398
7777
|
printBackground: true,
|
|
7399
7778
|
format: "Slide",
|
|
7400
|
-
scale: 1
|
|
7779
|
+
scale: 1,
|
|
7780
|
+
reuseBrowser: true
|
|
7401
7781
|
};
|
|
7782
|
+
/**
|
|
7783
|
+
* Get or create a browser instance
|
|
7784
|
+
*/
|
|
7785
|
+
async getBrowser(reuse) {
|
|
7786
|
+
if (browserIdleTimeout) {
|
|
7787
|
+
clearTimeout(browserIdleTimeout);
|
|
7788
|
+
browserIdleTimeout = null;
|
|
7789
|
+
}
|
|
7790
|
+
if (reuse && cachedBrowser && cachedBrowser.connected) {
|
|
7791
|
+
logger.progress("Using cached browser instance");
|
|
7792
|
+
return cachedBrowser;
|
|
7793
|
+
}
|
|
7794
|
+
if (cachedBrowser && !cachedBrowser.connected) {
|
|
7795
|
+
cachedBrowser = null;
|
|
7796
|
+
}
|
|
7797
|
+
logger.progress("Launching new browser instance...");
|
|
7798
|
+
const browser = await import_puppeteer.default.launch({
|
|
7799
|
+
headless: true,
|
|
7800
|
+
args: [
|
|
7801
|
+
"--no-sandbox",
|
|
7802
|
+
"--disable-setuid-sandbox",
|
|
7803
|
+
"--disable-dev-shm-usage",
|
|
7804
|
+
"--disable-gpu"
|
|
7805
|
+
]
|
|
7806
|
+
});
|
|
7807
|
+
if (reuse) {
|
|
7808
|
+
cachedBrowser = browser;
|
|
7809
|
+
}
|
|
7810
|
+
return browser;
|
|
7811
|
+
}
|
|
7812
|
+
/**
|
|
7813
|
+
* Schedule browser cleanup after idle period
|
|
7814
|
+
*/
|
|
7815
|
+
scheduleBrowserCleanup() {
|
|
7816
|
+
if (browserIdleTimeout) {
|
|
7817
|
+
clearTimeout(browserIdleTimeout);
|
|
7818
|
+
}
|
|
7819
|
+
browserIdleTimeout = setTimeout(async () => {
|
|
7820
|
+
if (cachedBrowser) {
|
|
7821
|
+
logger.progress("Closing idle browser instance");
|
|
7822
|
+
await cachedBrowser.close().catch(() => {
|
|
7823
|
+
});
|
|
7824
|
+
cachedBrowser = null;
|
|
7825
|
+
}
|
|
7826
|
+
}, BROWSER_IDLE_MS);
|
|
7827
|
+
}
|
|
7402
7828
|
/**
|
|
7403
7829
|
* Generate PDF from HTML presentation
|
|
7404
7830
|
* @param html - The HTML content of the presentation
|
|
@@ -7407,18 +7833,14 @@ var PDFGenerator = class {
|
|
|
7407
7833
|
*/
|
|
7408
7834
|
async generate(html, options = {}) {
|
|
7409
7835
|
const opts = { ...this.defaultOptions, ...options };
|
|
7836
|
+
const reuseBrowser = opts.reuseBrowser ?? true;
|
|
7410
7837
|
logger.progress("\u{1F4C4} Generating PDF...");
|
|
7411
|
-
let browser;
|
|
7838
|
+
let browser = null;
|
|
7839
|
+
let shouldCloseBrowser = !reuseBrowser;
|
|
7840
|
+
let page = null;
|
|
7412
7841
|
try {
|
|
7413
|
-
browser = await
|
|
7414
|
-
|
|
7415
|
-
args: [
|
|
7416
|
-
"--no-sandbox",
|
|
7417
|
-
"--disable-setuid-sandbox",
|
|
7418
|
-
"--disable-dev-shm-usage"
|
|
7419
|
-
]
|
|
7420
|
-
});
|
|
7421
|
-
const page = await browser.newPage();
|
|
7842
|
+
browser = await this.getBrowser(reuseBrowser);
|
|
7843
|
+
page = await browser.newPage();
|
|
7422
7844
|
const printHtml = this.preparePrintHtml(html);
|
|
7423
7845
|
await page.setViewport({
|
|
7424
7846
|
width: 1920,
|
|
@@ -7454,13 +7876,38 @@ var PDFGenerator = class {
|
|
|
7454
7876
|
} catch (error) {
|
|
7455
7877
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
7456
7878
|
logger.error(`PDF generation failed: ${errorMessage}`);
|
|
7879
|
+
shouldCloseBrowser = true;
|
|
7457
7880
|
throw new Error(`PDF generation failed: ${errorMessage}`);
|
|
7458
7881
|
} finally {
|
|
7459
|
-
if (
|
|
7460
|
-
await
|
|
7882
|
+
if (page) {
|
|
7883
|
+
await page.close().catch(() => {
|
|
7884
|
+
});
|
|
7885
|
+
}
|
|
7886
|
+
if (shouldCloseBrowser && browser) {
|
|
7887
|
+
await browser.close().catch(() => {
|
|
7888
|
+
});
|
|
7889
|
+
if (browser === cachedBrowser) {
|
|
7890
|
+
cachedBrowser = null;
|
|
7891
|
+
}
|
|
7892
|
+
} else if (reuseBrowser) {
|
|
7893
|
+
this.scheduleBrowserCleanup();
|
|
7461
7894
|
}
|
|
7462
7895
|
}
|
|
7463
7896
|
}
|
|
7897
|
+
/**
|
|
7898
|
+
* Manually close the cached browser (call before process exit)
|
|
7899
|
+
*/
|
|
7900
|
+
static async closeBrowser() {
|
|
7901
|
+
if (browserIdleTimeout) {
|
|
7902
|
+
clearTimeout(browserIdleTimeout);
|
|
7903
|
+
browserIdleTimeout = null;
|
|
7904
|
+
}
|
|
7905
|
+
if (cachedBrowser) {
|
|
7906
|
+
await cachedBrowser.close().catch(() => {
|
|
7907
|
+
});
|
|
7908
|
+
cachedBrowser = null;
|
|
7909
|
+
}
|
|
7910
|
+
}
|
|
7464
7911
|
/**
|
|
7465
7912
|
* Prepare HTML for print/PDF output
|
|
7466
7913
|
* Modifies the HTML to work better as a printed document
|
|
@@ -7474,12 +7921,24 @@ var PDFGenerator = class {
|
|
|
7474
7921
|
margin: 0;
|
|
7475
7922
|
}
|
|
7476
7923
|
|
|
7924
|
+
/* Font rendering optimization for print */
|
|
7925
|
+
* {
|
|
7926
|
+
-webkit-font-smoothing: antialiased;
|
|
7927
|
+
-moz-osx-font-smoothing: grayscale;
|
|
7928
|
+
text-rendering: optimizeLegibility;
|
|
7929
|
+
font-feature-settings: "liga" 1, "kern" 1;
|
|
7930
|
+
}
|
|
7931
|
+
|
|
7477
7932
|
@media print {
|
|
7478
7933
|
html, body {
|
|
7479
7934
|
margin: 0;
|
|
7480
7935
|
padding: 0;
|
|
7481
7936
|
width: 1920px;
|
|
7482
7937
|
height: 1080px;
|
|
7938
|
+
/* Print color optimization */
|
|
7939
|
+
color-adjust: exact;
|
|
7940
|
+
-webkit-print-color-adjust: exact;
|
|
7941
|
+
print-color-adjust: exact;
|
|
7483
7942
|
}
|
|
7484
7943
|
|
|
7485
7944
|
.reveal .slides {
|
|
@@ -7496,7 +7955,8 @@ var PDFGenerator = class {
|
|
|
7496
7955
|
width: 1920px !important;
|
|
7497
7956
|
height: 1080px !important;
|
|
7498
7957
|
margin: 0 !important;
|
|
7499
|
-
|
|
7958
|
+
/* Professional safe margins: 80px (4.2%) */
|
|
7959
|
+
padding: 80px !important;
|
|
7500
7960
|
box-sizing: border-box;
|
|
7501
7961
|
position: relative !important;
|
|
7502
7962
|
top: auto !important;
|
|
@@ -7520,7 +7980,7 @@ var PDFGenerator = class {
|
|
|
7520
7980
|
}
|
|
7521
7981
|
|
|
7522
7982
|
/* Disable animations for print */
|
|
7523
|
-
|
|
7983
|
+
*, *::before, *::after {
|
|
7524
7984
|
animation: none !important;
|
|
7525
7985
|
transition: none !important;
|
|
7526
7986
|
}
|
|
@@ -7536,6 +7996,30 @@ var PDFGenerator = class {
|
|
|
7536
7996
|
.reveal .navigate-down {
|
|
7537
7997
|
display: none !important;
|
|
7538
7998
|
}
|
|
7999
|
+
|
|
8000
|
+
/* Typography refinements for print */
|
|
8001
|
+
h1, h2, h3, h4 {
|
|
8002
|
+
orphans: 3;
|
|
8003
|
+
widows: 3;
|
|
8004
|
+
page-break-after: avoid;
|
|
8005
|
+
}
|
|
8006
|
+
|
|
8007
|
+
p, li {
|
|
8008
|
+
orphans: 2;
|
|
8009
|
+
widows: 2;
|
|
8010
|
+
}
|
|
8011
|
+
|
|
8012
|
+
/* Ensure links are readable in print */
|
|
8013
|
+
a {
|
|
8014
|
+
text-decoration: none;
|
|
8015
|
+
}
|
|
8016
|
+
|
|
8017
|
+
/* Prevent image overflow */
|
|
8018
|
+
img {
|
|
8019
|
+
max-width: 100%;
|
|
8020
|
+
height: auto;
|
|
8021
|
+
page-break-inside: avoid;
|
|
8022
|
+
}
|
|
7539
8023
|
}
|
|
7540
8024
|
|
|
7541
8025
|
/* Force print mode in Puppeteer */
|
|
@@ -7543,6 +8027,11 @@ var PDFGenerator = class {
|
|
|
7543
8027
|
page-break-after: always;
|
|
7544
8028
|
page-break-inside: avoid;
|
|
7545
8029
|
}
|
|
8030
|
+
|
|
8031
|
+
/* High contrast mode for business presentations */
|
|
8032
|
+
.reveal section {
|
|
8033
|
+
color-scheme: light;
|
|
8034
|
+
}
|
|
7546
8035
|
</style>
|
|
7547
8036
|
`;
|
|
7548
8037
|
if (html.includes("</head>")) {
|
|
@@ -8136,6 +8625,7 @@ var index_default = {
|
|
|
8136
8625
|
CompositeImageProvider,
|
|
8137
8626
|
ContentAnalyzer,
|
|
8138
8627
|
ContentPatternClassifier,
|
|
8628
|
+
KnowledgeBaseError,
|
|
8139
8629
|
KnowledgeGateway,
|
|
8140
8630
|
LocalImageProvider,
|
|
8141
8631
|
MermaidProvider,
|