claude-presentation-master 8.1.0 → 8.3.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/dist/index.d.mts +32 -1
- package/dist/index.d.ts +32 -1
- package/dist/index.js +357 -77
- package/dist/index.mjs +357 -77
- 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
|
}
|
|
@@ -1053,7 +1117,7 @@ var ContentAnalyzer = class {
|
|
|
1053
1117
|
logger.step(`Detected type: ${detectedType}`);
|
|
1054
1118
|
const sections = this.extractSections(text);
|
|
1055
1119
|
logger.step(`Found ${sections.length} sections`);
|
|
1056
|
-
const scqa = this.extractSCQA(text);
|
|
1120
|
+
const scqa = this.extractSCQA(text, subtitle);
|
|
1057
1121
|
const sparkline = this.extractSparkline(text);
|
|
1058
1122
|
const keyMessages = this.extractKeyMessages(text);
|
|
1059
1123
|
const dataPoints = this.extractDataPoints(text);
|
|
@@ -1320,15 +1384,24 @@ var ContentAnalyzer = class {
|
|
|
1320
1384
|
/**
|
|
1321
1385
|
* Extract SCQA structure (Barbara Minto)
|
|
1322
1386
|
* CRITICAL: Answer must be clean prose, NOT bullet points
|
|
1387
|
+
* @param text - The content text to analyze
|
|
1388
|
+
* @param subtitle - Optional subtitle to exclude (prevents redundancy with title slide)
|
|
1323
1389
|
*/
|
|
1324
|
-
extractSCQA(text) {
|
|
1390
|
+
extractSCQA(text, subtitle) {
|
|
1325
1391
|
const paragraphs = text.split(/\n\n+/).filter((p) => p.trim());
|
|
1392
|
+
const normalizedSubtitle = (subtitle || "").toLowerCase().trim();
|
|
1393
|
+
const isSubtitle = (para) => {
|
|
1394
|
+
if (!normalizedSubtitle) return false;
|
|
1395
|
+
const normalized = para.toLowerCase().trim();
|
|
1396
|
+
return normalized === normalizedSubtitle || normalized.includes(normalizedSubtitle) || normalizedSubtitle.includes(normalized);
|
|
1397
|
+
};
|
|
1326
1398
|
let situation = "";
|
|
1327
1399
|
let complication = "";
|
|
1328
1400
|
let question = "";
|
|
1329
1401
|
let answer = "";
|
|
1330
1402
|
for (const para of paragraphs.slice(0, 5)) {
|
|
1331
1403
|
if (para.startsWith("-") || para.startsWith("#") || para.startsWith("|")) continue;
|
|
1404
|
+
if (isSubtitle(para)) continue;
|
|
1332
1405
|
if (this.containsSignals(para.toLowerCase(), this.situationSignals)) {
|
|
1333
1406
|
situation = this.extractFirstSentence(para);
|
|
1334
1407
|
break;
|
|
@@ -1356,6 +1429,7 @@ var ContentAnalyzer = class {
|
|
|
1356
1429
|
if (!situation && paragraphs.length > 0) {
|
|
1357
1430
|
for (const para of paragraphs.slice(0, 5)) {
|
|
1358
1431
|
if (!para.startsWith("-") && !para.startsWith("#") && para.length > 50) {
|
|
1432
|
+
if (isSubtitle(para)) continue;
|
|
1359
1433
|
situation = this.extractFirstSentence(para);
|
|
1360
1434
|
break;
|
|
1361
1435
|
}
|
|
@@ -1802,7 +1876,8 @@ var ContentPatternClassifier = class {
|
|
|
1802
1876
|
if (!match || !match[1]) return null;
|
|
1803
1877
|
const value = match[1];
|
|
1804
1878
|
const afterNumber = content.slice(content.indexOf(value) + value.length);
|
|
1805
|
-
const
|
|
1879
|
+
const contextRaw = afterNumber.trim().split(/[\n\r]|-\s|[.!?]|(?=\d+%)|(?=\$\d)/)[0] || "";
|
|
1880
|
+
const context = contextRaw.trim().slice(0, 60);
|
|
1806
1881
|
return { value, context };
|
|
1807
1882
|
}
|
|
1808
1883
|
/**
|
|
@@ -2499,6 +2574,16 @@ var SlideFactory = class {
|
|
|
2499
2574
|
continue;
|
|
2500
2575
|
}
|
|
2501
2576
|
this.usedContent.add(contentKey);
|
|
2577
|
+
const normalizedHeader = section.header.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
2578
|
+
const normalizedTitle = analysis.title.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
2579
|
+
const normalizedSubtitle = (analysis.subtitle || "").toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
2580
|
+
const normalizedContent = (section.content || "").toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
2581
|
+
const headerMatchesTitle = normalizedHeader === normalizedTitle || normalizedTitle.includes(normalizedHeader) || normalizedHeader.includes(normalizedTitle);
|
|
2582
|
+
const contentMatchesSubtitle = normalizedContent && (normalizedContent === normalizedSubtitle || normalizedSubtitle.includes(normalizedContent) || normalizedContent.includes(normalizedSubtitle));
|
|
2583
|
+
if (headerMatchesTitle && (contentMatchesSubtitle || !section.content || section.content.length < 30)) {
|
|
2584
|
+
logger.warn(`Section "${section.header}" skipped - duplicates title slide`);
|
|
2585
|
+
continue;
|
|
2586
|
+
}
|
|
2502
2587
|
const headerLower = section.header.toLowerCase();
|
|
2503
2588
|
const isCTASection = headerLower.includes("call to action") || headerLower.includes("next step") || headerLower.includes("take action") || headerLower.includes("get started") || headerLower.includes("start today");
|
|
2504
2589
|
if (isCTASection) {
|
|
@@ -2678,47 +2763,69 @@ var SlideFactory = class {
|
|
|
2678
2763
|
const scqa = analysis.scqa;
|
|
2679
2764
|
const titles = this.config.scqaTitles;
|
|
2680
2765
|
const minBodyLength = 20;
|
|
2766
|
+
const normalizedTitle = analysis.title.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
2767
|
+
const normalizedSubtitle = (analysis.subtitle || "").toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
2768
|
+
const duplicatesHeadline = (body) => {
|
|
2769
|
+
if (!body) return true;
|
|
2770
|
+
const normalizedBody = body.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
2771
|
+
if (!normalizedBody || normalizedBody.length < 10) return false;
|
|
2772
|
+
const titleMatch = normalizedTitle.length > 5 && (normalizedBody === normalizedTitle || normalizedTitle.includes(normalizedBody) || normalizedBody.includes(normalizedTitle));
|
|
2773
|
+
const subtitleMatch = normalizedSubtitle.length > 5 && (normalizedBody === normalizedSubtitle || normalizedSubtitle.includes(normalizedBody) || normalizedBody.includes(normalizedSubtitle));
|
|
2774
|
+
return titleMatch || subtitleMatch;
|
|
2775
|
+
};
|
|
2681
2776
|
const situationBody = scqa?.situation ? this.truncateText(scqa.situation, this.config.rules.wordsPerSlide.max) : "";
|
|
2682
2777
|
if (situationBody.length >= minBodyLength && !this.usedContent.has("scqa-situation")) {
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2778
|
+
if (duplicatesHeadline(situationBody)) {
|
|
2779
|
+
logger.warn(`SCQA Situation skipped - duplicates title/subtitle: "${situationBody.slice(0, 50)}..."`);
|
|
2780
|
+
} else {
|
|
2781
|
+
this.usedContent.add("scqa-situation");
|
|
2782
|
+
slides.push({
|
|
2783
|
+
index: slides.length,
|
|
2784
|
+
type: "single-statement",
|
|
2785
|
+
data: {
|
|
2786
|
+
title: titles.situation,
|
|
2787
|
+
// FROM KB - not hardcoded 'Current Situation'
|
|
2788
|
+
body: situationBody
|
|
2789
|
+
},
|
|
2790
|
+
classes: ["situation-slide"]
|
|
2791
|
+
});
|
|
2792
|
+
}
|
|
2694
2793
|
}
|
|
2695
2794
|
const complicationBody = scqa?.complication ? this.truncateText(scqa.complication, this.config.rules.wordsPerSlide.max) : "";
|
|
2696
2795
|
if (complicationBody.length >= minBodyLength && !this.usedContent.has("scqa-complication")) {
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2796
|
+
if (duplicatesHeadline(complicationBody)) {
|
|
2797
|
+
logger.warn(`SCQA Complication skipped - duplicates title/subtitle`);
|
|
2798
|
+
} else {
|
|
2799
|
+
this.usedContent.add("scqa-complication");
|
|
2800
|
+
slides.push({
|
|
2801
|
+
index: slides.length,
|
|
2802
|
+
type: "single-statement",
|
|
2803
|
+
data: {
|
|
2804
|
+
title: titles.complication,
|
|
2805
|
+
// FROM KB - not hardcoded 'The Challenge'
|
|
2806
|
+
body: complicationBody
|
|
2807
|
+
},
|
|
2808
|
+
classes: ["complication-slide"]
|
|
2809
|
+
});
|
|
2810
|
+
}
|
|
2708
2811
|
}
|
|
2709
2812
|
const questionBody = scqa?.question ? this.truncateText(scqa.question, this.config.rules.wordsPerSlide.max) : "";
|
|
2710
2813
|
if (questionBody.length >= minBodyLength && !this.usedContent.has("scqa-question")) {
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2814
|
+
if (duplicatesHeadline(questionBody)) {
|
|
2815
|
+
logger.warn(`SCQA Question skipped - duplicates title/subtitle`);
|
|
2816
|
+
} else {
|
|
2817
|
+
this.usedContent.add("scqa-question");
|
|
2818
|
+
slides.push({
|
|
2819
|
+
index: slides.length,
|
|
2820
|
+
type: "single-statement",
|
|
2821
|
+
data: {
|
|
2822
|
+
title: titles.question,
|
|
2823
|
+
// FROM KB - not hardcoded 'The Question'
|
|
2824
|
+
body: questionBody
|
|
2825
|
+
},
|
|
2826
|
+
classes: ["question-slide"]
|
|
2827
|
+
});
|
|
2828
|
+
}
|
|
2722
2829
|
}
|
|
2723
2830
|
}
|
|
2724
2831
|
addSparklineSlides(slides, analysis) {
|
|
@@ -2771,14 +2878,41 @@ var SlideFactory = class {
|
|
|
2771
2878
|
// CONTENT SLIDES - ALL values from KB
|
|
2772
2879
|
// ===========================================================================
|
|
2773
2880
|
createBigNumberSlide(index, section, pattern) {
|
|
2774
|
-
|
|
2775
|
-
|
|
2881
|
+
let bigNumber = null;
|
|
2882
|
+
let sourceBullet = null;
|
|
2883
|
+
for (const bullet of section.bullets) {
|
|
2884
|
+
const extracted = this.classifier.extractBigNumber(bullet);
|
|
2885
|
+
if (extracted) {
|
|
2886
|
+
bigNumber = extracted;
|
|
2887
|
+
sourceBullet = bullet;
|
|
2888
|
+
break;
|
|
2889
|
+
}
|
|
2890
|
+
}
|
|
2891
|
+
if (!bigNumber) {
|
|
2892
|
+
bigNumber = this.classifier.extractBigNumber(section.header);
|
|
2893
|
+
}
|
|
2894
|
+
if (!bigNumber && section.content) {
|
|
2895
|
+
bigNumber = this.classifier.extractBigNumber(section.content);
|
|
2896
|
+
}
|
|
2776
2897
|
const actualValue = bigNumber?.value || pattern.bigNumberValue;
|
|
2777
2898
|
if (!actualValue) {
|
|
2778
2899
|
logger.warn(`No number found for big-number slide, falling back to single-statement`);
|
|
2779
2900
|
return this.createSingleStatementSlide(index, section);
|
|
2780
2901
|
}
|
|
2781
|
-
|
|
2902
|
+
let contextContent = bigNumber?.context || "";
|
|
2903
|
+
if (sourceBullet && !contextContent) {
|
|
2904
|
+
contextContent = sourceBullet.replace(actualValue, "").trim();
|
|
2905
|
+
}
|
|
2906
|
+
if (!contextContent) {
|
|
2907
|
+
contextContent = section.content || section.header;
|
|
2908
|
+
}
|
|
2909
|
+
let cleanTitle = section.header;
|
|
2910
|
+
if (actualValue && cleanTitle.includes(actualValue.replace(/[,$%]/g, ""))) {
|
|
2911
|
+
cleanTitle = cleanTitle.replace(new RegExp(`\\$?${actualValue.replace(/[,$%]/g, "").replace(/\./g, "\\.?")}[KMBkmb]?`, "gi"), "").replace(/\s+/g, " ").trim();
|
|
2912
|
+
}
|
|
2913
|
+
if (!cleanTitle || cleanTitle.length < 5) {
|
|
2914
|
+
cleanTitle = bigNumber?.context?.split(/[.!?]/)[0] || section.header;
|
|
2915
|
+
}
|
|
2782
2916
|
const craftedContext = this.craftExpertContent(
|
|
2783
2917
|
contextContent,
|
|
2784
2918
|
"big_number",
|
|
@@ -2788,7 +2922,7 @@ var SlideFactory = class {
|
|
|
2788
2922
|
index,
|
|
2789
2923
|
type: "big-number",
|
|
2790
2924
|
data: {
|
|
2791
|
-
title:
|
|
2925
|
+
title: cleanTitle,
|
|
2792
2926
|
keyMessage: actualValue,
|
|
2793
2927
|
body: craftedContext
|
|
2794
2928
|
},
|
|
@@ -2798,25 +2932,43 @@ var SlideFactory = class {
|
|
|
2798
2932
|
createComparisonSlide(index, section) {
|
|
2799
2933
|
const comparison = this.classifier.extractComparison(section);
|
|
2800
2934
|
const labels = this.config.defaults.comparison;
|
|
2935
|
+
const title = this.createTitle(section.header, section);
|
|
2936
|
+
const normalizedTitle = title.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
2801
2937
|
const leftFallback = labels.optionLabels[0] ?? "Option A";
|
|
2802
2938
|
const rightFallback = labels.optionLabels[1] ?? "Option B";
|
|
2803
|
-
|
|
2804
|
-
|
|
2939
|
+
let leftColumn = comparison?.left || section.bullets[0] || "";
|
|
2940
|
+
let rightColumn = comparison?.right || section.bullets[1] || "";
|
|
2941
|
+
const normalizedLeft = leftColumn.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
2942
|
+
const normalizedRight = rightColumn.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
2943
|
+
const leftIsDuplicate = normalizedLeft === normalizedTitle || normalizedTitle.includes(normalizedLeft);
|
|
2944
|
+
const rightIsDuplicate = normalizedRight === normalizedTitle || normalizedTitle.includes(normalizedRight);
|
|
2945
|
+
if (leftIsDuplicate || rightIsDuplicate || !leftColumn && !rightColumn) {
|
|
2946
|
+
if (section.bullets.length >= 2) {
|
|
2947
|
+
logger.warn(`Comparison slide "${title}" has duplicate content, using bullet points instead`);
|
|
2948
|
+
return this.createBulletSlide(index, section);
|
|
2949
|
+
}
|
|
2950
|
+
if (leftIsDuplicate && rightIsDuplicate) {
|
|
2951
|
+
logger.warn(`Skipping comparison slide "${title}" - content duplicates title`);
|
|
2952
|
+
return null;
|
|
2953
|
+
}
|
|
2954
|
+
if (leftIsDuplicate) leftColumn = leftFallback;
|
|
2955
|
+
if (rightIsDuplicate) rightColumn = rightFallback;
|
|
2956
|
+
}
|
|
2805
2957
|
return {
|
|
2806
2958
|
index,
|
|
2807
2959
|
type: "comparison",
|
|
2808
2960
|
data: {
|
|
2809
|
-
title
|
|
2961
|
+
title,
|
|
2810
2962
|
columns: [
|
|
2811
2963
|
{
|
|
2812
2964
|
title: labels.leftLabel,
|
|
2813
2965
|
// FROM KB - not hardcoded 'Before'
|
|
2814
|
-
content: this.truncateText(leftColumn, this.config.rules.wordsPerSlide.max)
|
|
2966
|
+
content: this.truncateText(leftColumn || leftFallback, this.config.rules.wordsPerSlide.max)
|
|
2815
2967
|
},
|
|
2816
2968
|
{
|
|
2817
2969
|
title: labels.rightLabel,
|
|
2818
2970
|
// FROM KB - not hardcoded 'After'
|
|
2819
|
-
content: this.truncateText(rightColumn, this.config.rules.wordsPerSlide.max)
|
|
2971
|
+
content: this.truncateText(rightColumn || rightFallback, this.config.rules.wordsPerSlide.max)
|
|
2820
2972
|
}
|
|
2821
2973
|
]
|
|
2822
2974
|
},
|
|
@@ -3087,10 +3239,21 @@ var SlideFactory = class {
|
|
|
3087
3239
|
* Used for section headers with strong conclusions.
|
|
3088
3240
|
*/
|
|
3089
3241
|
createTitleImpactSlide(index, section) {
|
|
3242
|
+
const title = this.cleanText(section.header);
|
|
3090
3243
|
const supportingText = section.content || section.bullets.slice(0, 2).join(". ");
|
|
3091
3244
|
const truncatedSupport = this.truncateText(supportingText, this.config.defaults.context.maxWords);
|
|
3245
|
+
const normalizedTitle = title.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
3246
|
+
const normalizedBody = truncatedSupport.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
3247
|
+
const bodyIsDuplicate = normalizedBody === normalizedTitle || normalizedTitle.includes(normalizedBody) || normalizedBody.includes(normalizedTitle) || truncatedSupport.length < 10;
|
|
3248
|
+
if (bodyIsDuplicate) {
|
|
3249
|
+
if (section.bullets.length >= 2 && this.config.mode === "business") {
|
|
3250
|
+
return this.createBulletSlide(index, section);
|
|
3251
|
+
}
|
|
3252
|
+
logger.warn(`Skipping title-impact slide "${title}" - no distinct body content`);
|
|
3253
|
+
return null;
|
|
3254
|
+
}
|
|
3092
3255
|
const data = {
|
|
3093
|
-
title
|
|
3256
|
+
title
|
|
3094
3257
|
};
|
|
3095
3258
|
if (truncatedSupport) {
|
|
3096
3259
|
data.body = truncatedSupport;
|
|
@@ -5506,12 +5669,15 @@ var VisualQualityEvaluator = class {
|
|
|
5506
5669
|
glanceTest += 1;
|
|
5507
5670
|
glanceNotes.push("Clean, minimal text - passes glance test easily");
|
|
5508
5671
|
}
|
|
5509
|
-
|
|
5672
|
+
const isAgendaSlide = slideType === "agenda" || (slideData.classes || []).includes("agenda-slide");
|
|
5673
|
+
const bulletLimit = isAgendaSlide ? 8 : 5;
|
|
5674
|
+
const bulletWarning = isAgendaSlide ? 6 : 3;
|
|
5675
|
+
if (slideData.bullets.length > bulletLimit) {
|
|
5510
5676
|
glanceTest -= 4;
|
|
5511
5677
|
glanceNotes.push("Too many bullets - cannot scan in 3 seconds");
|
|
5512
|
-
} else if (slideData.bullets.length >
|
|
5513
|
-
glanceTest -= 2;
|
|
5514
|
-
glanceNotes.push("Multiple bullets - needs careful structuring");
|
|
5678
|
+
} else if (slideData.bullets.length > bulletWarning) {
|
|
5679
|
+
glanceTest -= isAgendaSlide ? 1 : 2;
|
|
5680
|
+
glanceNotes.push(isAgendaSlide ? "Agenda with many items" : "Multiple bullets - needs careful structuring");
|
|
5515
5681
|
}
|
|
5516
5682
|
if (slideData.hasImage || slideData.hasChart) {
|
|
5517
5683
|
glanceTest += 1;
|
|
@@ -7606,13 +7772,63 @@ var PowerPointGenerator = class {
|
|
|
7606
7772
|
|
|
7607
7773
|
// src/generators/PDFGenerator.ts
|
|
7608
7774
|
import puppeteer from "puppeteer";
|
|
7775
|
+
var cachedBrowser = null;
|
|
7776
|
+
var browserIdleTimeout = null;
|
|
7777
|
+
var BROWSER_IDLE_MS = 3e4;
|
|
7609
7778
|
var PDFGenerator = class {
|
|
7610
7779
|
defaultOptions = {
|
|
7611
7780
|
orientation: "landscape",
|
|
7612
7781
|
printBackground: true,
|
|
7613
7782
|
format: "Slide",
|
|
7614
|
-
scale: 1
|
|
7783
|
+
scale: 1,
|
|
7784
|
+
reuseBrowser: true
|
|
7615
7785
|
};
|
|
7786
|
+
/**
|
|
7787
|
+
* Get or create a browser instance
|
|
7788
|
+
*/
|
|
7789
|
+
async getBrowser(reuse) {
|
|
7790
|
+
if (browserIdleTimeout) {
|
|
7791
|
+
clearTimeout(browserIdleTimeout);
|
|
7792
|
+
browserIdleTimeout = null;
|
|
7793
|
+
}
|
|
7794
|
+
if (reuse && cachedBrowser && cachedBrowser.connected) {
|
|
7795
|
+
logger.progress("Using cached browser instance");
|
|
7796
|
+
return cachedBrowser;
|
|
7797
|
+
}
|
|
7798
|
+
if (cachedBrowser && !cachedBrowser.connected) {
|
|
7799
|
+
cachedBrowser = null;
|
|
7800
|
+
}
|
|
7801
|
+
logger.progress("Launching new browser instance...");
|
|
7802
|
+
const browser = await puppeteer.launch({
|
|
7803
|
+
headless: true,
|
|
7804
|
+
args: [
|
|
7805
|
+
"--no-sandbox",
|
|
7806
|
+
"--disable-setuid-sandbox",
|
|
7807
|
+
"--disable-dev-shm-usage",
|
|
7808
|
+
"--disable-gpu"
|
|
7809
|
+
]
|
|
7810
|
+
});
|
|
7811
|
+
if (reuse) {
|
|
7812
|
+
cachedBrowser = browser;
|
|
7813
|
+
}
|
|
7814
|
+
return browser;
|
|
7815
|
+
}
|
|
7816
|
+
/**
|
|
7817
|
+
* Schedule browser cleanup after idle period
|
|
7818
|
+
*/
|
|
7819
|
+
scheduleBrowserCleanup() {
|
|
7820
|
+
if (browserIdleTimeout) {
|
|
7821
|
+
clearTimeout(browserIdleTimeout);
|
|
7822
|
+
}
|
|
7823
|
+
browserIdleTimeout = setTimeout(async () => {
|
|
7824
|
+
if (cachedBrowser) {
|
|
7825
|
+
logger.progress("Closing idle browser instance");
|
|
7826
|
+
await cachedBrowser.close().catch(() => {
|
|
7827
|
+
});
|
|
7828
|
+
cachedBrowser = null;
|
|
7829
|
+
}
|
|
7830
|
+
}, BROWSER_IDLE_MS);
|
|
7831
|
+
}
|
|
7616
7832
|
/**
|
|
7617
7833
|
* Generate PDF from HTML presentation
|
|
7618
7834
|
* @param html - The HTML content of the presentation
|
|
@@ -7621,18 +7837,14 @@ var PDFGenerator = class {
|
|
|
7621
7837
|
*/
|
|
7622
7838
|
async generate(html, options = {}) {
|
|
7623
7839
|
const opts = { ...this.defaultOptions, ...options };
|
|
7840
|
+
const reuseBrowser = opts.reuseBrowser ?? true;
|
|
7624
7841
|
logger.progress("\u{1F4C4} Generating PDF...");
|
|
7625
|
-
let browser;
|
|
7842
|
+
let browser = null;
|
|
7843
|
+
let shouldCloseBrowser = !reuseBrowser;
|
|
7844
|
+
let page = null;
|
|
7626
7845
|
try {
|
|
7627
|
-
browser = await
|
|
7628
|
-
|
|
7629
|
-
args: [
|
|
7630
|
-
"--no-sandbox",
|
|
7631
|
-
"--disable-setuid-sandbox",
|
|
7632
|
-
"--disable-dev-shm-usage"
|
|
7633
|
-
]
|
|
7634
|
-
});
|
|
7635
|
-
const page = await browser.newPage();
|
|
7846
|
+
browser = await this.getBrowser(reuseBrowser);
|
|
7847
|
+
page = await browser.newPage();
|
|
7636
7848
|
const printHtml = this.preparePrintHtml(html);
|
|
7637
7849
|
await page.setViewport({
|
|
7638
7850
|
width: 1920,
|
|
@@ -7668,13 +7880,38 @@ var PDFGenerator = class {
|
|
|
7668
7880
|
} catch (error) {
|
|
7669
7881
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
7670
7882
|
logger.error(`PDF generation failed: ${errorMessage}`);
|
|
7883
|
+
shouldCloseBrowser = true;
|
|
7671
7884
|
throw new Error(`PDF generation failed: ${errorMessage}`);
|
|
7672
7885
|
} finally {
|
|
7673
|
-
if (
|
|
7674
|
-
await
|
|
7886
|
+
if (page) {
|
|
7887
|
+
await page.close().catch(() => {
|
|
7888
|
+
});
|
|
7889
|
+
}
|
|
7890
|
+
if (shouldCloseBrowser && browser) {
|
|
7891
|
+
await browser.close().catch(() => {
|
|
7892
|
+
});
|
|
7893
|
+
if (browser === cachedBrowser) {
|
|
7894
|
+
cachedBrowser = null;
|
|
7895
|
+
}
|
|
7896
|
+
} else if (reuseBrowser) {
|
|
7897
|
+
this.scheduleBrowserCleanup();
|
|
7675
7898
|
}
|
|
7676
7899
|
}
|
|
7677
7900
|
}
|
|
7901
|
+
/**
|
|
7902
|
+
* Manually close the cached browser (call before process exit)
|
|
7903
|
+
*/
|
|
7904
|
+
static async closeBrowser() {
|
|
7905
|
+
if (browserIdleTimeout) {
|
|
7906
|
+
clearTimeout(browserIdleTimeout);
|
|
7907
|
+
browserIdleTimeout = null;
|
|
7908
|
+
}
|
|
7909
|
+
if (cachedBrowser) {
|
|
7910
|
+
await cachedBrowser.close().catch(() => {
|
|
7911
|
+
});
|
|
7912
|
+
cachedBrowser = null;
|
|
7913
|
+
}
|
|
7914
|
+
}
|
|
7678
7915
|
/**
|
|
7679
7916
|
* Prepare HTML for print/PDF output
|
|
7680
7917
|
* Modifies the HTML to work better as a printed document
|
|
@@ -7688,12 +7925,24 @@ var PDFGenerator = class {
|
|
|
7688
7925
|
margin: 0;
|
|
7689
7926
|
}
|
|
7690
7927
|
|
|
7928
|
+
/* Font rendering optimization for print */
|
|
7929
|
+
* {
|
|
7930
|
+
-webkit-font-smoothing: antialiased;
|
|
7931
|
+
-moz-osx-font-smoothing: grayscale;
|
|
7932
|
+
text-rendering: optimizeLegibility;
|
|
7933
|
+
font-feature-settings: "liga" 1, "kern" 1;
|
|
7934
|
+
}
|
|
7935
|
+
|
|
7691
7936
|
@media print {
|
|
7692
7937
|
html, body {
|
|
7693
7938
|
margin: 0;
|
|
7694
7939
|
padding: 0;
|
|
7695
7940
|
width: 1920px;
|
|
7696
7941
|
height: 1080px;
|
|
7942
|
+
/* Print color optimization */
|
|
7943
|
+
color-adjust: exact;
|
|
7944
|
+
-webkit-print-color-adjust: exact;
|
|
7945
|
+
print-color-adjust: exact;
|
|
7697
7946
|
}
|
|
7698
7947
|
|
|
7699
7948
|
.reveal .slides {
|
|
@@ -7710,7 +7959,8 @@ var PDFGenerator = class {
|
|
|
7710
7959
|
width: 1920px !important;
|
|
7711
7960
|
height: 1080px !important;
|
|
7712
7961
|
margin: 0 !important;
|
|
7713
|
-
|
|
7962
|
+
/* Professional safe margins: 80px (4.2%) */
|
|
7963
|
+
padding: 80px !important;
|
|
7714
7964
|
box-sizing: border-box;
|
|
7715
7965
|
position: relative !important;
|
|
7716
7966
|
top: auto !important;
|
|
@@ -7734,7 +7984,7 @@ var PDFGenerator = class {
|
|
|
7734
7984
|
}
|
|
7735
7985
|
|
|
7736
7986
|
/* Disable animations for print */
|
|
7737
|
-
|
|
7987
|
+
*, *::before, *::after {
|
|
7738
7988
|
animation: none !important;
|
|
7739
7989
|
transition: none !important;
|
|
7740
7990
|
}
|
|
@@ -7750,6 +8000,30 @@ var PDFGenerator = class {
|
|
|
7750
8000
|
.reveal .navigate-down {
|
|
7751
8001
|
display: none !important;
|
|
7752
8002
|
}
|
|
8003
|
+
|
|
8004
|
+
/* Typography refinements for print */
|
|
8005
|
+
h1, h2, h3, h4 {
|
|
8006
|
+
orphans: 3;
|
|
8007
|
+
widows: 3;
|
|
8008
|
+
page-break-after: avoid;
|
|
8009
|
+
}
|
|
8010
|
+
|
|
8011
|
+
p, li {
|
|
8012
|
+
orphans: 2;
|
|
8013
|
+
widows: 2;
|
|
8014
|
+
}
|
|
8015
|
+
|
|
8016
|
+
/* Ensure links are readable in print */
|
|
8017
|
+
a {
|
|
8018
|
+
text-decoration: none;
|
|
8019
|
+
}
|
|
8020
|
+
|
|
8021
|
+
/* Prevent image overflow */
|
|
8022
|
+
img {
|
|
8023
|
+
max-width: 100%;
|
|
8024
|
+
height: auto;
|
|
8025
|
+
page-break-inside: avoid;
|
|
8026
|
+
}
|
|
7753
8027
|
}
|
|
7754
8028
|
|
|
7755
8029
|
/* Force print mode in Puppeteer */
|
|
@@ -7757,6 +8031,11 @@ var PDFGenerator = class {
|
|
|
7757
8031
|
page-break-after: always;
|
|
7758
8032
|
page-break-inside: avoid;
|
|
7759
8033
|
}
|
|
8034
|
+
|
|
8035
|
+
/* High contrast mode for business presentations */
|
|
8036
|
+
.reveal section {
|
|
8037
|
+
color-scheme: light;
|
|
8038
|
+
}
|
|
7760
8039
|
</style>
|
|
7761
8040
|
`;
|
|
7762
8041
|
if (html.includes("</head>")) {
|
|
@@ -8349,6 +8628,7 @@ export {
|
|
|
8349
8628
|
CompositeImageProvider,
|
|
8350
8629
|
ContentAnalyzer,
|
|
8351
8630
|
ContentPatternClassifier,
|
|
8631
|
+
KnowledgeBaseError,
|
|
8352
8632
|
KnowledgeGateway,
|
|
8353
8633
|
LocalImageProvider,
|
|
8354
8634
|
MermaidProvider,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-presentation-master",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.3.0",
|
|
4
4
|
"description": "Generate world-class presentations using expert methodologies from Duarte, Reynolds, Gallo, and Anderson. Enforces rigorous quality standards through real visual validation.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|