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.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
|
}
|
|
@@ -1122,7 +1186,7 @@ var ContentAnalyzer = class {
|
|
|
1122
1186
|
logger.step(`Detected type: ${detectedType}`);
|
|
1123
1187
|
const sections = this.extractSections(text);
|
|
1124
1188
|
logger.step(`Found ${sections.length} sections`);
|
|
1125
|
-
const scqa = this.extractSCQA(text);
|
|
1189
|
+
const scqa = this.extractSCQA(text, subtitle);
|
|
1126
1190
|
const sparkline = this.extractSparkline(text);
|
|
1127
1191
|
const keyMessages = this.extractKeyMessages(text);
|
|
1128
1192
|
const dataPoints = this.extractDataPoints(text);
|
|
@@ -1389,15 +1453,24 @@ var ContentAnalyzer = class {
|
|
|
1389
1453
|
/**
|
|
1390
1454
|
* Extract SCQA structure (Barbara Minto)
|
|
1391
1455
|
* CRITICAL: Answer must be clean prose, NOT bullet points
|
|
1456
|
+
* @param text - The content text to analyze
|
|
1457
|
+
* @param subtitle - Optional subtitle to exclude (prevents redundancy with title slide)
|
|
1392
1458
|
*/
|
|
1393
|
-
extractSCQA(text) {
|
|
1459
|
+
extractSCQA(text, subtitle) {
|
|
1394
1460
|
const paragraphs = text.split(/\n\n+/).filter((p) => p.trim());
|
|
1461
|
+
const normalizedSubtitle = (subtitle || "").toLowerCase().trim();
|
|
1462
|
+
const isSubtitle = (para) => {
|
|
1463
|
+
if (!normalizedSubtitle) return false;
|
|
1464
|
+
const normalized = para.toLowerCase().trim();
|
|
1465
|
+
return normalized === normalizedSubtitle || normalized.includes(normalizedSubtitle) || normalizedSubtitle.includes(normalized);
|
|
1466
|
+
};
|
|
1395
1467
|
let situation = "";
|
|
1396
1468
|
let complication = "";
|
|
1397
1469
|
let question = "";
|
|
1398
1470
|
let answer = "";
|
|
1399
1471
|
for (const para of paragraphs.slice(0, 5)) {
|
|
1400
1472
|
if (para.startsWith("-") || para.startsWith("#") || para.startsWith("|")) continue;
|
|
1473
|
+
if (isSubtitle(para)) continue;
|
|
1401
1474
|
if (this.containsSignals(para.toLowerCase(), this.situationSignals)) {
|
|
1402
1475
|
situation = this.extractFirstSentence(para);
|
|
1403
1476
|
break;
|
|
@@ -1425,6 +1498,7 @@ var ContentAnalyzer = class {
|
|
|
1425
1498
|
if (!situation && paragraphs.length > 0) {
|
|
1426
1499
|
for (const para of paragraphs.slice(0, 5)) {
|
|
1427
1500
|
if (!para.startsWith("-") && !para.startsWith("#") && para.length > 50) {
|
|
1501
|
+
if (isSubtitle(para)) continue;
|
|
1428
1502
|
situation = this.extractFirstSentence(para);
|
|
1429
1503
|
break;
|
|
1430
1504
|
}
|
|
@@ -1871,7 +1945,8 @@ var ContentPatternClassifier = class {
|
|
|
1871
1945
|
if (!match || !match[1]) return null;
|
|
1872
1946
|
const value = match[1];
|
|
1873
1947
|
const afterNumber = content.slice(content.indexOf(value) + value.length);
|
|
1874
|
-
const
|
|
1948
|
+
const contextRaw = afterNumber.trim().split(/[\n\r]|-\s|[.!?]|(?=\d+%)|(?=\$\d)/)[0] || "";
|
|
1949
|
+
const context = contextRaw.trim().slice(0, 60);
|
|
1875
1950
|
return { value, context };
|
|
1876
1951
|
}
|
|
1877
1952
|
/**
|
|
@@ -2568,6 +2643,16 @@ var SlideFactory = class {
|
|
|
2568
2643
|
continue;
|
|
2569
2644
|
}
|
|
2570
2645
|
this.usedContent.add(contentKey);
|
|
2646
|
+
const normalizedHeader = section.header.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
2647
|
+
const normalizedTitle = analysis.title.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
2648
|
+
const normalizedSubtitle = (analysis.subtitle || "").toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
2649
|
+
const normalizedContent = (section.content || "").toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
2650
|
+
const headerMatchesTitle = normalizedHeader === normalizedTitle || normalizedTitle.includes(normalizedHeader) || normalizedHeader.includes(normalizedTitle);
|
|
2651
|
+
const contentMatchesSubtitle = normalizedContent && (normalizedContent === normalizedSubtitle || normalizedSubtitle.includes(normalizedContent) || normalizedContent.includes(normalizedSubtitle));
|
|
2652
|
+
if (headerMatchesTitle && (contentMatchesSubtitle || !section.content || section.content.length < 30)) {
|
|
2653
|
+
logger.warn(`Section "${section.header}" skipped - duplicates title slide`);
|
|
2654
|
+
continue;
|
|
2655
|
+
}
|
|
2571
2656
|
const headerLower = section.header.toLowerCase();
|
|
2572
2657
|
const isCTASection = headerLower.includes("call to action") || headerLower.includes("next step") || headerLower.includes("take action") || headerLower.includes("get started") || headerLower.includes("start today");
|
|
2573
2658
|
if (isCTASection) {
|
|
@@ -2747,47 +2832,69 @@ var SlideFactory = class {
|
|
|
2747
2832
|
const scqa = analysis.scqa;
|
|
2748
2833
|
const titles = this.config.scqaTitles;
|
|
2749
2834
|
const minBodyLength = 20;
|
|
2835
|
+
const normalizedTitle = analysis.title.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
2836
|
+
const normalizedSubtitle = (analysis.subtitle || "").toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
2837
|
+
const duplicatesHeadline = (body) => {
|
|
2838
|
+
if (!body) return true;
|
|
2839
|
+
const normalizedBody = body.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
2840
|
+
if (!normalizedBody || normalizedBody.length < 10) return false;
|
|
2841
|
+
const titleMatch = normalizedTitle.length > 5 && (normalizedBody === normalizedTitle || normalizedTitle.includes(normalizedBody) || normalizedBody.includes(normalizedTitle));
|
|
2842
|
+
const subtitleMatch = normalizedSubtitle.length > 5 && (normalizedBody === normalizedSubtitle || normalizedSubtitle.includes(normalizedBody) || normalizedBody.includes(normalizedSubtitle));
|
|
2843
|
+
return titleMatch || subtitleMatch;
|
|
2844
|
+
};
|
|
2750
2845
|
const situationBody = scqa?.situation ? this.truncateText(scqa.situation, this.config.rules.wordsPerSlide.max) : "";
|
|
2751
2846
|
if (situationBody.length >= minBodyLength && !this.usedContent.has("scqa-situation")) {
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2847
|
+
if (duplicatesHeadline(situationBody)) {
|
|
2848
|
+
logger.warn(`SCQA Situation skipped - duplicates title/subtitle: "${situationBody.slice(0, 50)}..."`);
|
|
2849
|
+
} else {
|
|
2850
|
+
this.usedContent.add("scqa-situation");
|
|
2851
|
+
slides.push({
|
|
2852
|
+
index: slides.length,
|
|
2853
|
+
type: "single-statement",
|
|
2854
|
+
data: {
|
|
2855
|
+
title: titles.situation,
|
|
2856
|
+
// FROM KB - not hardcoded 'Current Situation'
|
|
2857
|
+
body: situationBody
|
|
2858
|
+
},
|
|
2859
|
+
classes: ["situation-slide"]
|
|
2860
|
+
});
|
|
2861
|
+
}
|
|
2763
2862
|
}
|
|
2764
2863
|
const complicationBody = scqa?.complication ? this.truncateText(scqa.complication, this.config.rules.wordsPerSlide.max) : "";
|
|
2765
2864
|
if (complicationBody.length >= minBodyLength && !this.usedContent.has("scqa-complication")) {
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2865
|
+
if (duplicatesHeadline(complicationBody)) {
|
|
2866
|
+
logger.warn(`SCQA Complication skipped - duplicates title/subtitle`);
|
|
2867
|
+
} else {
|
|
2868
|
+
this.usedContent.add("scqa-complication");
|
|
2869
|
+
slides.push({
|
|
2870
|
+
index: slides.length,
|
|
2871
|
+
type: "single-statement",
|
|
2872
|
+
data: {
|
|
2873
|
+
title: titles.complication,
|
|
2874
|
+
// FROM KB - not hardcoded 'The Challenge'
|
|
2875
|
+
body: complicationBody
|
|
2876
|
+
},
|
|
2877
|
+
classes: ["complication-slide"]
|
|
2878
|
+
});
|
|
2879
|
+
}
|
|
2777
2880
|
}
|
|
2778
2881
|
const questionBody = scqa?.question ? this.truncateText(scqa.question, this.config.rules.wordsPerSlide.max) : "";
|
|
2779
2882
|
if (questionBody.length >= minBodyLength && !this.usedContent.has("scqa-question")) {
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2883
|
+
if (duplicatesHeadline(questionBody)) {
|
|
2884
|
+
logger.warn(`SCQA Question skipped - duplicates title/subtitle`);
|
|
2885
|
+
} else {
|
|
2886
|
+
this.usedContent.add("scqa-question");
|
|
2887
|
+
slides.push({
|
|
2888
|
+
index: slides.length,
|
|
2889
|
+
type: "single-statement",
|
|
2890
|
+
data: {
|
|
2891
|
+
title: titles.question,
|
|
2892
|
+
// FROM KB - not hardcoded 'The Question'
|
|
2893
|
+
body: questionBody
|
|
2894
|
+
},
|
|
2895
|
+
classes: ["question-slide"]
|
|
2896
|
+
});
|
|
2897
|
+
}
|
|
2791
2898
|
}
|
|
2792
2899
|
}
|
|
2793
2900
|
addSparklineSlides(slides, analysis) {
|
|
@@ -2840,14 +2947,41 @@ var SlideFactory = class {
|
|
|
2840
2947
|
// CONTENT SLIDES - ALL values from KB
|
|
2841
2948
|
// ===========================================================================
|
|
2842
2949
|
createBigNumberSlide(index, section, pattern) {
|
|
2843
|
-
|
|
2844
|
-
|
|
2950
|
+
let bigNumber = null;
|
|
2951
|
+
let sourceBullet = null;
|
|
2952
|
+
for (const bullet of section.bullets) {
|
|
2953
|
+
const extracted = this.classifier.extractBigNumber(bullet);
|
|
2954
|
+
if (extracted) {
|
|
2955
|
+
bigNumber = extracted;
|
|
2956
|
+
sourceBullet = bullet;
|
|
2957
|
+
break;
|
|
2958
|
+
}
|
|
2959
|
+
}
|
|
2960
|
+
if (!bigNumber) {
|
|
2961
|
+
bigNumber = this.classifier.extractBigNumber(section.header);
|
|
2962
|
+
}
|
|
2963
|
+
if (!bigNumber && section.content) {
|
|
2964
|
+
bigNumber = this.classifier.extractBigNumber(section.content);
|
|
2965
|
+
}
|
|
2845
2966
|
const actualValue = bigNumber?.value || pattern.bigNumberValue;
|
|
2846
2967
|
if (!actualValue) {
|
|
2847
2968
|
logger.warn(`No number found for big-number slide, falling back to single-statement`);
|
|
2848
2969
|
return this.createSingleStatementSlide(index, section);
|
|
2849
2970
|
}
|
|
2850
|
-
|
|
2971
|
+
let contextContent = bigNumber?.context || "";
|
|
2972
|
+
if (sourceBullet && !contextContent) {
|
|
2973
|
+
contextContent = sourceBullet.replace(actualValue, "").trim();
|
|
2974
|
+
}
|
|
2975
|
+
if (!contextContent) {
|
|
2976
|
+
contextContent = section.content || section.header;
|
|
2977
|
+
}
|
|
2978
|
+
let cleanTitle = section.header;
|
|
2979
|
+
if (actualValue && cleanTitle.includes(actualValue.replace(/[,$%]/g, ""))) {
|
|
2980
|
+
cleanTitle = cleanTitle.replace(new RegExp(`\\$?${actualValue.replace(/[,$%]/g, "").replace(/\./g, "\\.?")}[KMBkmb]?`, "gi"), "").replace(/\s+/g, " ").trim();
|
|
2981
|
+
}
|
|
2982
|
+
if (!cleanTitle || cleanTitle.length < 5) {
|
|
2983
|
+
cleanTitle = bigNumber?.context?.split(/[.!?]/)[0] || section.header;
|
|
2984
|
+
}
|
|
2851
2985
|
const craftedContext = this.craftExpertContent(
|
|
2852
2986
|
contextContent,
|
|
2853
2987
|
"big_number",
|
|
@@ -2857,7 +2991,7 @@ var SlideFactory = class {
|
|
|
2857
2991
|
index,
|
|
2858
2992
|
type: "big-number",
|
|
2859
2993
|
data: {
|
|
2860
|
-
title:
|
|
2994
|
+
title: cleanTitle,
|
|
2861
2995
|
keyMessage: actualValue,
|
|
2862
2996
|
body: craftedContext
|
|
2863
2997
|
},
|
|
@@ -2867,25 +3001,43 @@ var SlideFactory = class {
|
|
|
2867
3001
|
createComparisonSlide(index, section) {
|
|
2868
3002
|
const comparison = this.classifier.extractComparison(section);
|
|
2869
3003
|
const labels = this.config.defaults.comparison;
|
|
3004
|
+
const title = this.createTitle(section.header, section);
|
|
3005
|
+
const normalizedTitle = title.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
2870
3006
|
const leftFallback = labels.optionLabels[0] ?? "Option A";
|
|
2871
3007
|
const rightFallback = labels.optionLabels[1] ?? "Option B";
|
|
2872
|
-
|
|
2873
|
-
|
|
3008
|
+
let leftColumn = comparison?.left || section.bullets[0] || "";
|
|
3009
|
+
let rightColumn = comparison?.right || section.bullets[1] || "";
|
|
3010
|
+
const normalizedLeft = leftColumn.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
3011
|
+
const normalizedRight = rightColumn.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
3012
|
+
const leftIsDuplicate = normalizedLeft === normalizedTitle || normalizedTitle.includes(normalizedLeft);
|
|
3013
|
+
const rightIsDuplicate = normalizedRight === normalizedTitle || normalizedTitle.includes(normalizedRight);
|
|
3014
|
+
if (leftIsDuplicate || rightIsDuplicate || !leftColumn && !rightColumn) {
|
|
3015
|
+
if (section.bullets.length >= 2) {
|
|
3016
|
+
logger.warn(`Comparison slide "${title}" has duplicate content, using bullet points instead`);
|
|
3017
|
+
return this.createBulletSlide(index, section);
|
|
3018
|
+
}
|
|
3019
|
+
if (leftIsDuplicate && rightIsDuplicate) {
|
|
3020
|
+
logger.warn(`Skipping comparison slide "${title}" - content duplicates title`);
|
|
3021
|
+
return null;
|
|
3022
|
+
}
|
|
3023
|
+
if (leftIsDuplicate) leftColumn = leftFallback;
|
|
3024
|
+
if (rightIsDuplicate) rightColumn = rightFallback;
|
|
3025
|
+
}
|
|
2874
3026
|
return {
|
|
2875
3027
|
index,
|
|
2876
3028
|
type: "comparison",
|
|
2877
3029
|
data: {
|
|
2878
|
-
title
|
|
3030
|
+
title,
|
|
2879
3031
|
columns: [
|
|
2880
3032
|
{
|
|
2881
3033
|
title: labels.leftLabel,
|
|
2882
3034
|
// FROM KB - not hardcoded 'Before'
|
|
2883
|
-
content: this.truncateText(leftColumn, this.config.rules.wordsPerSlide.max)
|
|
3035
|
+
content: this.truncateText(leftColumn || leftFallback, this.config.rules.wordsPerSlide.max)
|
|
2884
3036
|
},
|
|
2885
3037
|
{
|
|
2886
3038
|
title: labels.rightLabel,
|
|
2887
3039
|
// FROM KB - not hardcoded 'After'
|
|
2888
|
-
content: this.truncateText(rightColumn, this.config.rules.wordsPerSlide.max)
|
|
3040
|
+
content: this.truncateText(rightColumn || rightFallback, this.config.rules.wordsPerSlide.max)
|
|
2889
3041
|
}
|
|
2890
3042
|
]
|
|
2891
3043
|
},
|
|
@@ -3156,10 +3308,21 @@ var SlideFactory = class {
|
|
|
3156
3308
|
* Used for section headers with strong conclusions.
|
|
3157
3309
|
*/
|
|
3158
3310
|
createTitleImpactSlide(index, section) {
|
|
3311
|
+
const title = this.cleanText(section.header);
|
|
3159
3312
|
const supportingText = section.content || section.bullets.slice(0, 2).join(". ");
|
|
3160
3313
|
const truncatedSupport = this.truncateText(supportingText, this.config.defaults.context.maxWords);
|
|
3314
|
+
const normalizedTitle = title.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
3315
|
+
const normalizedBody = truncatedSupport.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
3316
|
+
const bodyIsDuplicate = normalizedBody === normalizedTitle || normalizedTitle.includes(normalizedBody) || normalizedBody.includes(normalizedTitle) || truncatedSupport.length < 10;
|
|
3317
|
+
if (bodyIsDuplicate) {
|
|
3318
|
+
if (section.bullets.length >= 2 && this.config.mode === "business") {
|
|
3319
|
+
return this.createBulletSlide(index, section);
|
|
3320
|
+
}
|
|
3321
|
+
logger.warn(`Skipping title-impact slide "${title}" - no distinct body content`);
|
|
3322
|
+
return null;
|
|
3323
|
+
}
|
|
3161
3324
|
const data = {
|
|
3162
|
-
title
|
|
3325
|
+
title
|
|
3163
3326
|
};
|
|
3164
3327
|
if (truncatedSupport) {
|
|
3165
3328
|
data.body = truncatedSupport;
|
|
@@ -5575,12 +5738,15 @@ var VisualQualityEvaluator = class {
|
|
|
5575
5738
|
glanceTest += 1;
|
|
5576
5739
|
glanceNotes.push("Clean, minimal text - passes glance test easily");
|
|
5577
5740
|
}
|
|
5578
|
-
|
|
5741
|
+
const isAgendaSlide = slideType === "agenda" || (slideData.classes || []).includes("agenda-slide");
|
|
5742
|
+
const bulletLimit = isAgendaSlide ? 8 : 5;
|
|
5743
|
+
const bulletWarning = isAgendaSlide ? 6 : 3;
|
|
5744
|
+
if (slideData.bullets.length > bulletLimit) {
|
|
5579
5745
|
glanceTest -= 4;
|
|
5580
5746
|
glanceNotes.push("Too many bullets - cannot scan in 3 seconds");
|
|
5581
|
-
} else if (slideData.bullets.length >
|
|
5582
|
-
glanceTest -= 2;
|
|
5583
|
-
glanceNotes.push("Multiple bullets - needs careful structuring");
|
|
5747
|
+
} else if (slideData.bullets.length > bulletWarning) {
|
|
5748
|
+
glanceTest -= isAgendaSlide ? 1 : 2;
|
|
5749
|
+
glanceNotes.push(isAgendaSlide ? "Agenda with many items" : "Multiple bullets - needs careful structuring");
|
|
5584
5750
|
}
|
|
5585
5751
|
if (slideData.hasImage || slideData.hasChart) {
|
|
5586
5752
|
glanceTest += 1;
|
|
@@ -7675,13 +7841,63 @@ var PowerPointGenerator = class {
|
|
|
7675
7841
|
|
|
7676
7842
|
// src/generators/PDFGenerator.ts
|
|
7677
7843
|
var import_puppeteer = __toESM(require("puppeteer"));
|
|
7844
|
+
var cachedBrowser = null;
|
|
7845
|
+
var browserIdleTimeout = null;
|
|
7846
|
+
var BROWSER_IDLE_MS = 3e4;
|
|
7678
7847
|
var PDFGenerator = class {
|
|
7679
7848
|
defaultOptions = {
|
|
7680
7849
|
orientation: "landscape",
|
|
7681
7850
|
printBackground: true,
|
|
7682
7851
|
format: "Slide",
|
|
7683
|
-
scale: 1
|
|
7852
|
+
scale: 1,
|
|
7853
|
+
reuseBrowser: true
|
|
7684
7854
|
};
|
|
7855
|
+
/**
|
|
7856
|
+
* Get or create a browser instance
|
|
7857
|
+
*/
|
|
7858
|
+
async getBrowser(reuse) {
|
|
7859
|
+
if (browserIdleTimeout) {
|
|
7860
|
+
clearTimeout(browserIdleTimeout);
|
|
7861
|
+
browserIdleTimeout = null;
|
|
7862
|
+
}
|
|
7863
|
+
if (reuse && cachedBrowser && cachedBrowser.connected) {
|
|
7864
|
+
logger.progress("Using cached browser instance");
|
|
7865
|
+
return cachedBrowser;
|
|
7866
|
+
}
|
|
7867
|
+
if (cachedBrowser && !cachedBrowser.connected) {
|
|
7868
|
+
cachedBrowser = null;
|
|
7869
|
+
}
|
|
7870
|
+
logger.progress("Launching new browser instance...");
|
|
7871
|
+
const browser = await import_puppeteer.default.launch({
|
|
7872
|
+
headless: true,
|
|
7873
|
+
args: [
|
|
7874
|
+
"--no-sandbox",
|
|
7875
|
+
"--disable-setuid-sandbox",
|
|
7876
|
+
"--disable-dev-shm-usage",
|
|
7877
|
+
"--disable-gpu"
|
|
7878
|
+
]
|
|
7879
|
+
});
|
|
7880
|
+
if (reuse) {
|
|
7881
|
+
cachedBrowser = browser;
|
|
7882
|
+
}
|
|
7883
|
+
return browser;
|
|
7884
|
+
}
|
|
7885
|
+
/**
|
|
7886
|
+
* Schedule browser cleanup after idle period
|
|
7887
|
+
*/
|
|
7888
|
+
scheduleBrowserCleanup() {
|
|
7889
|
+
if (browserIdleTimeout) {
|
|
7890
|
+
clearTimeout(browserIdleTimeout);
|
|
7891
|
+
}
|
|
7892
|
+
browserIdleTimeout = setTimeout(async () => {
|
|
7893
|
+
if (cachedBrowser) {
|
|
7894
|
+
logger.progress("Closing idle browser instance");
|
|
7895
|
+
await cachedBrowser.close().catch(() => {
|
|
7896
|
+
});
|
|
7897
|
+
cachedBrowser = null;
|
|
7898
|
+
}
|
|
7899
|
+
}, BROWSER_IDLE_MS);
|
|
7900
|
+
}
|
|
7685
7901
|
/**
|
|
7686
7902
|
* Generate PDF from HTML presentation
|
|
7687
7903
|
* @param html - The HTML content of the presentation
|
|
@@ -7690,18 +7906,14 @@ var PDFGenerator = class {
|
|
|
7690
7906
|
*/
|
|
7691
7907
|
async generate(html, options = {}) {
|
|
7692
7908
|
const opts = { ...this.defaultOptions, ...options };
|
|
7909
|
+
const reuseBrowser = opts.reuseBrowser ?? true;
|
|
7693
7910
|
logger.progress("\u{1F4C4} Generating PDF...");
|
|
7694
|
-
let browser;
|
|
7911
|
+
let browser = null;
|
|
7912
|
+
let shouldCloseBrowser = !reuseBrowser;
|
|
7913
|
+
let page = null;
|
|
7695
7914
|
try {
|
|
7696
|
-
browser = await
|
|
7697
|
-
|
|
7698
|
-
args: [
|
|
7699
|
-
"--no-sandbox",
|
|
7700
|
-
"--disable-setuid-sandbox",
|
|
7701
|
-
"--disable-dev-shm-usage"
|
|
7702
|
-
]
|
|
7703
|
-
});
|
|
7704
|
-
const page = await browser.newPage();
|
|
7915
|
+
browser = await this.getBrowser(reuseBrowser);
|
|
7916
|
+
page = await browser.newPage();
|
|
7705
7917
|
const printHtml = this.preparePrintHtml(html);
|
|
7706
7918
|
await page.setViewport({
|
|
7707
7919
|
width: 1920,
|
|
@@ -7737,13 +7949,38 @@ var PDFGenerator = class {
|
|
|
7737
7949
|
} catch (error) {
|
|
7738
7950
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
7739
7951
|
logger.error(`PDF generation failed: ${errorMessage}`);
|
|
7952
|
+
shouldCloseBrowser = true;
|
|
7740
7953
|
throw new Error(`PDF generation failed: ${errorMessage}`);
|
|
7741
7954
|
} finally {
|
|
7742
|
-
if (
|
|
7743
|
-
await
|
|
7955
|
+
if (page) {
|
|
7956
|
+
await page.close().catch(() => {
|
|
7957
|
+
});
|
|
7958
|
+
}
|
|
7959
|
+
if (shouldCloseBrowser && browser) {
|
|
7960
|
+
await browser.close().catch(() => {
|
|
7961
|
+
});
|
|
7962
|
+
if (browser === cachedBrowser) {
|
|
7963
|
+
cachedBrowser = null;
|
|
7964
|
+
}
|
|
7965
|
+
} else if (reuseBrowser) {
|
|
7966
|
+
this.scheduleBrowserCleanup();
|
|
7744
7967
|
}
|
|
7745
7968
|
}
|
|
7746
7969
|
}
|
|
7970
|
+
/**
|
|
7971
|
+
* Manually close the cached browser (call before process exit)
|
|
7972
|
+
*/
|
|
7973
|
+
static async closeBrowser() {
|
|
7974
|
+
if (browserIdleTimeout) {
|
|
7975
|
+
clearTimeout(browserIdleTimeout);
|
|
7976
|
+
browserIdleTimeout = null;
|
|
7977
|
+
}
|
|
7978
|
+
if (cachedBrowser) {
|
|
7979
|
+
await cachedBrowser.close().catch(() => {
|
|
7980
|
+
});
|
|
7981
|
+
cachedBrowser = null;
|
|
7982
|
+
}
|
|
7983
|
+
}
|
|
7747
7984
|
/**
|
|
7748
7985
|
* Prepare HTML for print/PDF output
|
|
7749
7986
|
* Modifies the HTML to work better as a printed document
|
|
@@ -7757,12 +7994,24 @@ var PDFGenerator = class {
|
|
|
7757
7994
|
margin: 0;
|
|
7758
7995
|
}
|
|
7759
7996
|
|
|
7997
|
+
/* Font rendering optimization for print */
|
|
7998
|
+
* {
|
|
7999
|
+
-webkit-font-smoothing: antialiased;
|
|
8000
|
+
-moz-osx-font-smoothing: grayscale;
|
|
8001
|
+
text-rendering: optimizeLegibility;
|
|
8002
|
+
font-feature-settings: "liga" 1, "kern" 1;
|
|
8003
|
+
}
|
|
8004
|
+
|
|
7760
8005
|
@media print {
|
|
7761
8006
|
html, body {
|
|
7762
8007
|
margin: 0;
|
|
7763
8008
|
padding: 0;
|
|
7764
8009
|
width: 1920px;
|
|
7765
8010
|
height: 1080px;
|
|
8011
|
+
/* Print color optimization */
|
|
8012
|
+
color-adjust: exact;
|
|
8013
|
+
-webkit-print-color-adjust: exact;
|
|
8014
|
+
print-color-adjust: exact;
|
|
7766
8015
|
}
|
|
7767
8016
|
|
|
7768
8017
|
.reveal .slides {
|
|
@@ -7779,7 +8028,8 @@ var PDFGenerator = class {
|
|
|
7779
8028
|
width: 1920px !important;
|
|
7780
8029
|
height: 1080px !important;
|
|
7781
8030
|
margin: 0 !important;
|
|
7782
|
-
|
|
8031
|
+
/* Professional safe margins: 80px (4.2%) */
|
|
8032
|
+
padding: 80px !important;
|
|
7783
8033
|
box-sizing: border-box;
|
|
7784
8034
|
position: relative !important;
|
|
7785
8035
|
top: auto !important;
|
|
@@ -7803,7 +8053,7 @@ var PDFGenerator = class {
|
|
|
7803
8053
|
}
|
|
7804
8054
|
|
|
7805
8055
|
/* Disable animations for print */
|
|
7806
|
-
|
|
8056
|
+
*, *::before, *::after {
|
|
7807
8057
|
animation: none !important;
|
|
7808
8058
|
transition: none !important;
|
|
7809
8059
|
}
|
|
@@ -7819,6 +8069,30 @@ var PDFGenerator = class {
|
|
|
7819
8069
|
.reveal .navigate-down {
|
|
7820
8070
|
display: none !important;
|
|
7821
8071
|
}
|
|
8072
|
+
|
|
8073
|
+
/* Typography refinements for print */
|
|
8074
|
+
h1, h2, h3, h4 {
|
|
8075
|
+
orphans: 3;
|
|
8076
|
+
widows: 3;
|
|
8077
|
+
page-break-after: avoid;
|
|
8078
|
+
}
|
|
8079
|
+
|
|
8080
|
+
p, li {
|
|
8081
|
+
orphans: 2;
|
|
8082
|
+
widows: 2;
|
|
8083
|
+
}
|
|
8084
|
+
|
|
8085
|
+
/* Ensure links are readable in print */
|
|
8086
|
+
a {
|
|
8087
|
+
text-decoration: none;
|
|
8088
|
+
}
|
|
8089
|
+
|
|
8090
|
+
/* Prevent image overflow */
|
|
8091
|
+
img {
|
|
8092
|
+
max-width: 100%;
|
|
8093
|
+
height: auto;
|
|
8094
|
+
page-break-inside: avoid;
|
|
8095
|
+
}
|
|
7822
8096
|
}
|
|
7823
8097
|
|
|
7824
8098
|
/* Force print mode in Puppeteer */
|
|
@@ -7826,6 +8100,11 @@ var PDFGenerator = class {
|
|
|
7826
8100
|
page-break-after: always;
|
|
7827
8101
|
page-break-inside: avoid;
|
|
7828
8102
|
}
|
|
8103
|
+
|
|
8104
|
+
/* High contrast mode for business presentations */
|
|
8105
|
+
.reveal section {
|
|
8106
|
+
color-scheme: light;
|
|
8107
|
+
}
|
|
7829
8108
|
</style>
|
|
7830
8109
|
`;
|
|
7831
8110
|
if (html.includes("</head>")) {
|
|
@@ -8419,6 +8698,7 @@ var index_default = {
|
|
|
8419
8698
|
CompositeImageProvider,
|
|
8420
8699
|
ContentAnalyzer,
|
|
8421
8700
|
ContentPatternClassifier,
|
|
8701
|
+
KnowledgeBaseError,
|
|
8422
8702
|
KnowledgeGateway,
|
|
8423
8703
|
LocalImageProvider,
|
|
8424
8704
|
MermaidProvider,
|