claude-presentation-master 7.2.0 → 7.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/bin/cli.js +6 -0
- package/dist/index.d.mts +105 -11
- package/dist/index.d.ts +105 -11
- package/dist/index.js +612 -83
- package/dist/index.mjs +612 -83
- package/package.json +3 -2
package/dist/index.mjs
CHANGED
|
@@ -31,6 +31,75 @@ import { readFileSync } from "fs";
|
|
|
31
31
|
import { join, dirname } from "path";
|
|
32
32
|
import { fileURLToPath } from "url";
|
|
33
33
|
import * as yaml from "yaml";
|
|
34
|
+
|
|
35
|
+
// src/utils/Logger.ts
|
|
36
|
+
var Logger = class {
|
|
37
|
+
level;
|
|
38
|
+
prefix;
|
|
39
|
+
timestamps;
|
|
40
|
+
constructor(options = {}) {
|
|
41
|
+
this.level = options.level ?? 1 /* INFO */;
|
|
42
|
+
this.prefix = options.prefix ?? "";
|
|
43
|
+
this.timestamps = options.timestamps ?? false;
|
|
44
|
+
}
|
|
45
|
+
setLevel(level) {
|
|
46
|
+
this.level = level;
|
|
47
|
+
}
|
|
48
|
+
formatMessage(message) {
|
|
49
|
+
const parts = [];
|
|
50
|
+
if (this.timestamps) {
|
|
51
|
+
parts.push(`[${(/* @__PURE__ */ new Date()).toISOString()}]`);
|
|
52
|
+
}
|
|
53
|
+
if (this.prefix) {
|
|
54
|
+
parts.push(`[${this.prefix}]`);
|
|
55
|
+
}
|
|
56
|
+
parts.push(message);
|
|
57
|
+
return parts.join(" ");
|
|
58
|
+
}
|
|
59
|
+
debug(message, ...args) {
|
|
60
|
+
if (this.level <= 0 /* DEBUG */) {
|
|
61
|
+
console.debug(this.formatMessage(message), ...args);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
info(message, ...args) {
|
|
65
|
+
if (this.level <= 1 /* INFO */) {
|
|
66
|
+
console.info(this.formatMessage(message), ...args);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
warn(message, ...args) {
|
|
70
|
+
if (this.level <= 2 /* WARN */) {
|
|
71
|
+
console.warn(this.formatMessage(message), ...args);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
error(message, ...args) {
|
|
75
|
+
if (this.level <= 3 /* ERROR */) {
|
|
76
|
+
console.error(this.formatMessage(message), ...args);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// Progress messages (always shown unless silent)
|
|
80
|
+
progress(message) {
|
|
81
|
+
if (this.level < 4 /* SILENT */) {
|
|
82
|
+
console.info(message);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
// Success messages
|
|
86
|
+
success(message) {
|
|
87
|
+
if (this.level < 4 /* SILENT */) {
|
|
88
|
+
console.info(`\u2705 ${message}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// Step messages for workflow progress
|
|
92
|
+
step(message) {
|
|
93
|
+
if (this.level <= 1 /* INFO */) {
|
|
94
|
+
console.info(` \u2713 ${message}`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
var logger = new Logger({
|
|
99
|
+
level: process.env.LOG_LEVEL ? parseInt(process.env.LOG_LEVEL) : 1 /* INFO */
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// src/kb/KnowledgeGateway.ts
|
|
34
103
|
function getModuleDir() {
|
|
35
104
|
if (typeof import.meta !== "undefined" && import.meta.url) {
|
|
36
105
|
return dirname(fileURLToPath(import.meta.url));
|
|
@@ -41,7 +110,7 @@ function getModuleDir() {
|
|
|
41
110
|
return process.cwd();
|
|
42
111
|
}
|
|
43
112
|
var KnowledgeGateway = class {
|
|
44
|
-
kb;
|
|
113
|
+
kb = {};
|
|
45
114
|
loaded = false;
|
|
46
115
|
constructor() {
|
|
47
116
|
}
|
|
@@ -64,7 +133,7 @@ var KnowledgeGateway = class {
|
|
|
64
133
|
const content = readFileSync(path, "utf-8");
|
|
65
134
|
this.kb = yaml.parse(content);
|
|
66
135
|
this.loaded = true;
|
|
67
|
-
|
|
136
|
+
logger.step(`Knowledge base loaded from ${path}`);
|
|
68
137
|
return;
|
|
69
138
|
} catch {
|
|
70
139
|
}
|
|
@@ -255,7 +324,7 @@ var KnowledgeGateway = class {
|
|
|
255
324
|
*/
|
|
256
325
|
getChartGuidance(purpose) {
|
|
257
326
|
this.ensureLoaded();
|
|
258
|
-
return this.kb.chart_selection_guide.by_purpose[purpose];
|
|
327
|
+
return this.kb.chart_selection_guide.by_purpose?.[purpose];
|
|
259
328
|
}
|
|
260
329
|
/**
|
|
261
330
|
* Get charts to avoid
|
|
@@ -270,7 +339,7 @@ var KnowledgeGateway = class {
|
|
|
270
339
|
*/
|
|
271
340
|
getIBPitchBookStructure(type) {
|
|
272
341
|
this.ensureLoaded();
|
|
273
|
-
return this.kb.investment_banking?.pitch_book_types[type];
|
|
342
|
+
return this.kb.investment_banking?.pitch_book_types?.[type];
|
|
274
343
|
}
|
|
275
344
|
/**
|
|
276
345
|
* Check if knowledge base is loaded
|
|
@@ -820,22 +889,27 @@ var ContentAnalyzer = class {
|
|
|
820
889
|
async analyze(content, contentType) {
|
|
821
890
|
await this.initialize();
|
|
822
891
|
const text = this.parseContent(content, contentType);
|
|
823
|
-
const title = this.
|
|
892
|
+
const { title, subtitle } = this.extractTitleAndSubtitle(text);
|
|
893
|
+
logger.step(`Title: "${title}", Subtitle: "${subtitle || "(none)"}"`);
|
|
824
894
|
const detectedType = this.detectPresentationType(text);
|
|
825
|
-
|
|
895
|
+
logger.step(`Detected type: ${detectedType}`);
|
|
826
896
|
const sections = this.extractSections(text);
|
|
827
|
-
|
|
897
|
+
logger.step(`Found ${sections.length} sections`);
|
|
828
898
|
const scqa = this.extractSCQA(text);
|
|
829
899
|
const sparkline = this.extractSparkline(text);
|
|
830
900
|
const keyMessages = this.extractKeyMessages(text);
|
|
831
901
|
const dataPoints = this.extractDataPoints(text);
|
|
832
|
-
|
|
902
|
+
logger.step(`Found ${dataPoints.length} data points`);
|
|
833
903
|
const titles = sections.map((s) => s.header).filter((h) => h.length > 0);
|
|
834
904
|
const starMoments = this.extractStarMoments(text);
|
|
835
905
|
const estimatedSlideCount = Math.max(5, sections.length + 3);
|
|
906
|
+
const whatIsContent = scqa.situation ? [scqa.situation] : [];
|
|
907
|
+
const whatCouldBeContent = scqa.answer ? [scqa.answer] : keyMessages.slice(0, 2);
|
|
836
908
|
return {
|
|
837
909
|
detectedType,
|
|
838
910
|
title,
|
|
911
|
+
subtitle,
|
|
912
|
+
// NEW: Use explicit subtitle from content
|
|
839
913
|
sections,
|
|
840
914
|
keyMessages,
|
|
841
915
|
dataPoints,
|
|
@@ -846,8 +920,10 @@ var ContentAnalyzer = class {
|
|
|
846
920
|
answer: scqa.answer || ""
|
|
847
921
|
},
|
|
848
922
|
sparkline: {
|
|
849
|
-
whatIs:
|
|
850
|
-
|
|
923
|
+
whatIs: whatIsContent,
|
|
924
|
+
// FIX: Use situation, not CTA
|
|
925
|
+
whatCouldBe: whatCouldBeContent,
|
|
926
|
+
// FIX: Use answer/vision, not random messages
|
|
851
927
|
callToAdventure: sparkline.callToAction || ""
|
|
852
928
|
},
|
|
853
929
|
titles,
|
|
@@ -875,18 +951,46 @@ var ContentAnalyzer = class {
|
|
|
875
951
|
return text;
|
|
876
952
|
}
|
|
877
953
|
/**
|
|
878
|
-
* Extract the main title from content
|
|
954
|
+
* Extract the main title AND subtitle from content
|
|
955
|
+
* Subtitle is the line immediately after the H1 title (if not another header)
|
|
879
956
|
*/
|
|
880
|
-
|
|
881
|
-
const
|
|
882
|
-
|
|
883
|
-
|
|
957
|
+
extractTitleAndSubtitle(text) {
|
|
958
|
+
const lines = text.split("\n");
|
|
959
|
+
let title = "Presentation";
|
|
960
|
+
let subtitle = "";
|
|
961
|
+
let foundTitle = false;
|
|
962
|
+
let titleLineIndex = -1;
|
|
963
|
+
for (let i = 0; i < lines.length; i++) {
|
|
964
|
+
const rawLine = lines[i];
|
|
965
|
+
if (!rawLine) continue;
|
|
966
|
+
const line = rawLine.trim();
|
|
967
|
+
const h1Match = line.match(/^#\s+(.+)$/);
|
|
968
|
+
if (h1Match && h1Match[1] && !foundTitle) {
|
|
969
|
+
title = h1Match[1].replace(/\*\*/g, "").trim();
|
|
970
|
+
foundTitle = true;
|
|
971
|
+
titleLineIndex = i;
|
|
972
|
+
continue;
|
|
973
|
+
}
|
|
974
|
+
if (foundTitle && i > titleLineIndex && line.length > 0) {
|
|
975
|
+
if (line.startsWith("#")) break;
|
|
976
|
+
if (line.startsWith("-") || line.startsWith("*") || /^\d+\./.test(line)) break;
|
|
977
|
+
subtitle = line.replace(/\*\*/g, "").trim().slice(0, 100);
|
|
978
|
+
break;
|
|
979
|
+
}
|
|
884
980
|
}
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
981
|
+
if (!foundTitle) {
|
|
982
|
+
const firstLine = lines.find((l) => l.trim().length > 0);
|
|
983
|
+
if (firstLine) {
|
|
984
|
+
title = firstLine.replace(/^#+\s*/, "").replace(/\*\*/g, "").trim().slice(0, 80);
|
|
985
|
+
}
|
|
888
986
|
}
|
|
889
|
-
return
|
|
987
|
+
return { title, subtitle };
|
|
988
|
+
}
|
|
989
|
+
/**
|
|
990
|
+
* Extract the main title from content (legacy wrapper)
|
|
991
|
+
*/
|
|
992
|
+
extractTitle(text) {
|
|
993
|
+
return this.extractTitleAndSubtitle(text).title;
|
|
890
994
|
}
|
|
891
995
|
/**
|
|
892
996
|
* Detect presentation type from content signals
|
|
@@ -949,12 +1053,16 @@ var ContentAnalyzer = class {
|
|
|
949
1053
|
}
|
|
950
1054
|
const bulletMatch = trimmedLine.match(/^[-*+]\s+(.+)$/);
|
|
951
1055
|
if (bulletMatch && bulletMatch[1] && currentSection) {
|
|
952
|
-
|
|
1056
|
+
const bulletText = bulletMatch[1];
|
|
1057
|
+
currentSection.bullets.push(bulletText);
|
|
1058
|
+
this.extractMetricsFromText(bulletText, currentSection.metrics);
|
|
953
1059
|
continue;
|
|
954
1060
|
}
|
|
955
1061
|
const numberedMatch = trimmedLine.match(/^\d+\.\s+(.+)$/);
|
|
956
1062
|
if (numberedMatch && numberedMatch[1] && currentSection) {
|
|
957
|
-
|
|
1063
|
+
const itemText = numberedMatch[1];
|
|
1064
|
+
currentSection.bullets.push(itemText);
|
|
1065
|
+
this.extractMetricsFromText(itemText, currentSection.metrics);
|
|
958
1066
|
continue;
|
|
959
1067
|
}
|
|
960
1068
|
const metricMatch = trimmedLine.match(/\$?([\d,]+\.?\d*)[%]?\s*[-–—:]\s*(.+)/);
|
|
@@ -965,6 +1073,9 @@ var ContentAnalyzer = class {
|
|
|
965
1073
|
});
|
|
966
1074
|
continue;
|
|
967
1075
|
}
|
|
1076
|
+
if (currentSection && trimmedLine) {
|
|
1077
|
+
this.extractMetricsFromText(trimmedLine, currentSection.metrics);
|
|
1078
|
+
}
|
|
968
1079
|
if (trimmedLine.includes("|") && currentSection) {
|
|
969
1080
|
const cells = trimmedLine.split("|").map((c) => c.trim()).filter((c) => c && !c.match(/^-+$/));
|
|
970
1081
|
if (cells.length >= 2) {
|
|
@@ -1336,6 +1447,49 @@ var ContentAnalyzer = class {
|
|
|
1336
1447
|
const fallback = cleaned.slice(0, 150);
|
|
1337
1448
|
return fallback.length >= 20 ? fallback : "";
|
|
1338
1449
|
}
|
|
1450
|
+
/**
|
|
1451
|
+
* Extract metrics from natural language text
|
|
1452
|
+
* Handles formats like "40% productivity loss", "$2.5M investment"
|
|
1453
|
+
*/
|
|
1454
|
+
extractMetricsFromText(text, metrics) {
|
|
1455
|
+
const usedValues = new Set(metrics.map((m) => m.value));
|
|
1456
|
+
const moneyMatches = text.matchAll(/(\$[\d,.]+[MBK]?)\s+([a-zA-Z][a-zA-Z\s]{2,30})/g);
|
|
1457
|
+
for (const match of moneyMatches) {
|
|
1458
|
+
const value = match[1];
|
|
1459
|
+
const rawLabel = match[2]?.trim();
|
|
1460
|
+
if (value && rawLabel && !usedValues.has(value)) {
|
|
1461
|
+
usedValues.add(value);
|
|
1462
|
+
metrics.push({ value, label: this.cleanMetricLabel(rawLabel) });
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
const percentMatches = text.matchAll(/(\d+(?:\.\d+)?%)\s+([a-zA-Z][a-zA-Z\s]{2,30})/g);
|
|
1466
|
+
for (const match of percentMatches) {
|
|
1467
|
+
const value = match[1];
|
|
1468
|
+
const rawLabel = match[2]?.trim();
|
|
1469
|
+
if (value && rawLabel && !usedValues.has(value)) {
|
|
1470
|
+
usedValues.add(value);
|
|
1471
|
+
metrics.push({ value, label: this.cleanMetricLabel(rawLabel) });
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
const multMatches = text.matchAll(/(\d+[xX])\s+([a-zA-Z][a-zA-Z\s]{2,20})/g);
|
|
1475
|
+
for (const match of multMatches) {
|
|
1476
|
+
const value = match[1];
|
|
1477
|
+
const rawLabel = match[2]?.trim();
|
|
1478
|
+
if (value && rawLabel && !usedValues.has(value)) {
|
|
1479
|
+
usedValues.add(value);
|
|
1480
|
+
metrics.push({ value, label: this.cleanMetricLabel(rawLabel) });
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
const numMatches = text.matchAll(/(\d{3,}\+?)\s+([a-zA-Z][a-zA-Z\s]{2,20})/g);
|
|
1484
|
+
for (const match of numMatches) {
|
|
1485
|
+
const value = match[1];
|
|
1486
|
+
const rawLabel = match[2]?.trim();
|
|
1487
|
+
if (value && rawLabel && !usedValues.has(value)) {
|
|
1488
|
+
usedValues.add(value);
|
|
1489
|
+
metrics.push({ value, label: this.cleanMetricLabel(rawLabel) });
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1339
1493
|
};
|
|
1340
1494
|
|
|
1341
1495
|
// src/core/ContentPatternClassifier.ts
|
|
@@ -1520,8 +1674,8 @@ var SlideFactory = class {
|
|
|
1520
1674
|
this.usedContent = /* @__PURE__ */ new Set();
|
|
1521
1675
|
this.usedTitles = /* @__PURE__ */ new Set();
|
|
1522
1676
|
this.config = this.loadKBConfig(type);
|
|
1523
|
-
|
|
1524
|
-
|
|
1677
|
+
logger.step(`SlideFactory v7.1.0 initialized for ${type} (${this.config.mode} mode)`);
|
|
1678
|
+
logger.step(`KB Config loaded: ${this.config.allowedTypes.length} allowed types`);
|
|
1525
1679
|
}
|
|
1526
1680
|
/**
|
|
1527
1681
|
* Load ALL configuration from KB - ZERO hardcoded values after this.
|
|
@@ -1549,7 +1703,7 @@ var SlideFactory = class {
|
|
|
1549
1703
|
async createSlides(analysis) {
|
|
1550
1704
|
const slides = [];
|
|
1551
1705
|
const storyStructure = this.kb.getStoryStructure(this.presentationType);
|
|
1552
|
-
|
|
1706
|
+
logger.step(`Using ${storyStructure.framework} story framework`);
|
|
1553
1707
|
slides.push(this.createTitleSlide(0, analysis));
|
|
1554
1708
|
const minSectionsForAgenda = Math.max(3, this.config.rules.bulletsPerSlide.max - 2);
|
|
1555
1709
|
if (this.config.mode === "business" && analysis.sections.length >= minSectionsForAgenda) {
|
|
@@ -1587,7 +1741,7 @@ var SlideFactory = class {
|
|
|
1587
1741
|
const validatedSlides = this.validateAndFixSlides(slides);
|
|
1588
1742
|
this.checkRequiredElements(validatedSlides);
|
|
1589
1743
|
this.checkAntiPatterns(validatedSlides);
|
|
1590
|
-
|
|
1744
|
+
logger.step(`Created ${validatedSlides.length} validated slides`);
|
|
1591
1745
|
return validatedSlides;
|
|
1592
1746
|
}
|
|
1593
1747
|
// ===========================================================================
|
|
@@ -1628,7 +1782,7 @@ var SlideFactory = class {
|
|
|
1628
1782
|
case "technical":
|
|
1629
1783
|
return this.createCodeSlide(index, section);
|
|
1630
1784
|
default:
|
|
1631
|
-
|
|
1785
|
+
logger.warn(`Unknown slide type '${type}', using bullet-points`);
|
|
1632
1786
|
return this.createBulletSlide(index, section);
|
|
1633
1787
|
}
|
|
1634
1788
|
}
|
|
@@ -1639,9 +1793,10 @@ var SlideFactory = class {
|
|
|
1639
1793
|
const data = {
|
|
1640
1794
|
title: this.truncateText(analysis.title, this.config.rules.wordsPerSlide.max)
|
|
1641
1795
|
};
|
|
1642
|
-
|
|
1796
|
+
const subtitleSource = analysis.subtitle || analysis.keyMessages[0] || "";
|
|
1797
|
+
if (subtitleSource) {
|
|
1643
1798
|
data.subtitle = this.truncateText(
|
|
1644
|
-
|
|
1799
|
+
subtitleSource,
|
|
1645
1800
|
this.config.defaults.subtitle.maxWords
|
|
1646
1801
|
// FROM KB
|
|
1647
1802
|
);
|
|
@@ -1685,7 +1840,9 @@ var SlideFactory = class {
|
|
|
1685
1840
|
addSCQASlides(slides, analysis) {
|
|
1686
1841
|
const scqa = analysis.scqa;
|
|
1687
1842
|
const titles = this.config.scqaTitles;
|
|
1688
|
-
|
|
1843
|
+
const minBodyLength = 20;
|
|
1844
|
+
const situationBody = scqa?.situation ? this.truncateText(scqa.situation, this.config.rules.wordsPerSlide.max) : "";
|
|
1845
|
+
if (situationBody.length >= minBodyLength && !this.usedContent.has("scqa-situation")) {
|
|
1689
1846
|
this.usedContent.add("scqa-situation");
|
|
1690
1847
|
slides.push({
|
|
1691
1848
|
index: slides.length,
|
|
@@ -1693,12 +1850,13 @@ var SlideFactory = class {
|
|
|
1693
1850
|
data: {
|
|
1694
1851
|
title: titles.situation,
|
|
1695
1852
|
// FROM KB - not hardcoded 'Current Situation'
|
|
1696
|
-
body:
|
|
1853
|
+
body: situationBody
|
|
1697
1854
|
},
|
|
1698
1855
|
classes: ["situation-slide"]
|
|
1699
1856
|
});
|
|
1700
1857
|
}
|
|
1701
|
-
|
|
1858
|
+
const complicationBody = scqa?.complication ? this.truncateText(scqa.complication, this.config.rules.wordsPerSlide.max) : "";
|
|
1859
|
+
if (complicationBody.length >= minBodyLength && !this.usedContent.has("scqa-complication")) {
|
|
1702
1860
|
this.usedContent.add("scqa-complication");
|
|
1703
1861
|
slides.push({
|
|
1704
1862
|
index: slides.length,
|
|
@@ -1706,12 +1864,13 @@ var SlideFactory = class {
|
|
|
1706
1864
|
data: {
|
|
1707
1865
|
title: titles.complication,
|
|
1708
1866
|
// FROM KB - not hardcoded 'The Challenge'
|
|
1709
|
-
body:
|
|
1867
|
+
body: complicationBody
|
|
1710
1868
|
},
|
|
1711
1869
|
classes: ["complication-slide"]
|
|
1712
1870
|
});
|
|
1713
1871
|
}
|
|
1714
|
-
|
|
1872
|
+
const questionBody = scqa?.question ? this.truncateText(scqa.question, this.config.rules.wordsPerSlide.max) : "";
|
|
1873
|
+
if (questionBody.length >= minBodyLength && !this.usedContent.has("scqa-question")) {
|
|
1715
1874
|
this.usedContent.add("scqa-question");
|
|
1716
1875
|
slides.push({
|
|
1717
1876
|
index: slides.length,
|
|
@@ -1719,7 +1878,7 @@ var SlideFactory = class {
|
|
|
1719
1878
|
data: {
|
|
1720
1879
|
title: titles.question,
|
|
1721
1880
|
// FROM KB - not hardcoded 'The Question'
|
|
1722
|
-
body:
|
|
1881
|
+
body: questionBody
|
|
1723
1882
|
},
|
|
1724
1883
|
classes: ["question-slide"]
|
|
1725
1884
|
});
|
|
@@ -1729,7 +1888,8 @@ var SlideFactory = class {
|
|
|
1729
1888
|
const spark = analysis.sparkline;
|
|
1730
1889
|
const titles = this.config.sparklineTitles;
|
|
1731
1890
|
const whatIsFirst = spark?.whatIs?.[0];
|
|
1732
|
-
|
|
1891
|
+
const whatIsBody = whatIsFirst ? this.truncateText(whatIsFirst, this.config.rules.wordsPerSlide.max) : "";
|
|
1892
|
+
if (whatIsBody.length >= 20 && !this.usedContent.has("spark-what-is")) {
|
|
1733
1893
|
this.usedContent.add("spark-what-is");
|
|
1734
1894
|
slides.push({
|
|
1735
1895
|
index: slides.length,
|
|
@@ -1737,13 +1897,14 @@ var SlideFactory = class {
|
|
|
1737
1897
|
data: {
|
|
1738
1898
|
title: titles.whatIs,
|
|
1739
1899
|
// FROM KB - not hardcoded 'Where We Are Today'
|
|
1740
|
-
body:
|
|
1900
|
+
body: whatIsBody
|
|
1741
1901
|
},
|
|
1742
1902
|
classes: ["what-is-slide"]
|
|
1743
1903
|
});
|
|
1744
1904
|
}
|
|
1745
1905
|
const whatCouldBeFirst = spark?.whatCouldBe?.[0];
|
|
1746
|
-
|
|
1906
|
+
const whatCouldBeBody = whatCouldBeFirst ? this.truncateText(whatCouldBeFirst, this.config.rules.wordsPerSlide.max) : "";
|
|
1907
|
+
if (whatCouldBeBody.length >= 20 && !this.usedContent.has("spark-could-be")) {
|
|
1747
1908
|
this.usedContent.add("spark-could-be");
|
|
1748
1909
|
slides.push({
|
|
1749
1910
|
index: slides.length,
|
|
@@ -1751,7 +1912,7 @@ var SlideFactory = class {
|
|
|
1751
1912
|
data: {
|
|
1752
1913
|
title: titles.whatCouldBe,
|
|
1753
1914
|
// FROM KB - not hardcoded 'What Could Be'
|
|
1754
|
-
body:
|
|
1915
|
+
body: whatCouldBeBody
|
|
1755
1916
|
},
|
|
1756
1917
|
classes: ["what-could-be-slide"]
|
|
1757
1918
|
});
|
|
@@ -1761,15 +1922,19 @@ var SlideFactory = class {
|
|
|
1761
1922
|
// CONTENT SLIDES - ALL values from KB
|
|
1762
1923
|
// ===========================================================================
|
|
1763
1924
|
createBigNumberSlide(index, section, pattern) {
|
|
1764
|
-
const
|
|
1765
|
-
|
|
1766
|
-
|
|
1925
|
+
const fullContent = `${section.header} ${section.content} ${section.bullets.join(" ")}`;
|
|
1926
|
+
const bigNumber = this.classifier.extractBigNumber(fullContent);
|
|
1927
|
+
const actualValue = bigNumber?.value || pattern.bigNumberValue;
|
|
1928
|
+
if (!actualValue) {
|
|
1929
|
+
logger.warn(`No number found for big-number slide, falling back to single-statement`);
|
|
1930
|
+
return this.createSingleStatementSlide(index, section);
|
|
1931
|
+
}
|
|
1767
1932
|
return {
|
|
1768
1933
|
index,
|
|
1769
1934
|
type: "big-number",
|
|
1770
1935
|
data: {
|
|
1771
1936
|
title: this.createTitle(section.header, section),
|
|
1772
|
-
keyMessage:
|
|
1937
|
+
keyMessage: actualValue,
|
|
1773
1938
|
body: bigNumber?.context || this.truncateText(
|
|
1774
1939
|
section.content,
|
|
1775
1940
|
this.config.defaults.context.maxWords
|
|
@@ -1956,16 +2121,18 @@ var SlideFactory = class {
|
|
|
1956
2121
|
};
|
|
1957
2122
|
}
|
|
1958
2123
|
createSingleStatementSlide(index, section) {
|
|
2124
|
+
const bodyContent = section.content || section.bullets.join(" ") || "";
|
|
2125
|
+
const truncatedBody = this.truncateText(bodyContent, this.config.rules.wordsPerSlide.max);
|
|
2126
|
+
if (truncatedBody.length < 15 && section.bullets.length > 0) {
|
|
2127
|
+
return this.createBulletSlide(index, section);
|
|
2128
|
+
}
|
|
1959
2129
|
return {
|
|
1960
2130
|
index,
|
|
1961
2131
|
type: "single-statement",
|
|
1962
2132
|
data: {
|
|
1963
2133
|
title: this.createTitle(section.header, section),
|
|
1964
|
-
body:
|
|
1965
|
-
|
|
1966
|
-
this.config.rules.wordsPerSlide.max
|
|
1967
|
-
// FROM KB
|
|
1968
|
-
)
|
|
2134
|
+
body: truncatedBody || section.header
|
|
2135
|
+
// Fallback to header if no body
|
|
1969
2136
|
},
|
|
1970
2137
|
classes: ["single-statement-slide"]
|
|
1971
2138
|
};
|
|
@@ -2038,7 +2205,7 @@ var SlideFactory = class {
|
|
|
2038
2205
|
slide.data.bullets = slide.data.bullets.slice(0, validation.fixes.bulletCount);
|
|
2039
2206
|
}
|
|
2040
2207
|
if (validation.violations.length > 0) {
|
|
2041
|
-
|
|
2208
|
+
logger.warn(`Slide ${slide.index} (${slide.type}): Fixed ${Object.keys(validation.fixes).length} issues, ${validation.violations.length} remaining`);
|
|
2042
2209
|
}
|
|
2043
2210
|
if (validation.warnings.length > 0) {
|
|
2044
2211
|
slide.notes = (slide.notes || "") + "\nWarnings: " + validation.warnings.join(", ");
|
|
@@ -2078,7 +2245,7 @@ var SlideFactory = class {
|
|
|
2078
2245
|
}
|
|
2079
2246
|
}
|
|
2080
2247
|
if (missingElements.length > 0) {
|
|
2081
|
-
|
|
2248
|
+
logger.warn(`Missing required elements: ${missingElements.join(", ")}`);
|
|
2082
2249
|
}
|
|
2083
2250
|
}
|
|
2084
2251
|
/**
|
|
@@ -2109,7 +2276,7 @@ var SlideFactory = class {
|
|
|
2109
2276
|
}
|
|
2110
2277
|
}
|
|
2111
2278
|
if (violations.length > 0) {
|
|
2112
|
-
|
|
2279
|
+
logger.warn(`Anti-pattern violations: ${violations.join(", ")}`);
|
|
2113
2280
|
}
|
|
2114
2281
|
}
|
|
2115
2282
|
// ===========================================================================
|
|
@@ -2454,9 +2621,12 @@ var TemplateEngine = class {
|
|
|
2454
2621
|
this.templates.set("big-number", this.handlebars.compile(`
|
|
2455
2622
|
<section class="{{classes}}" data-slide-index="{{slideIndex}}" style="{{styles}}">
|
|
2456
2623
|
<div class="slide-content big-number-content">
|
|
2457
|
-
|
|
2458
|
-
{{
|
|
2459
|
-
|
|
2624
|
+
{{#if title}}
|
|
2625
|
+
<h2 class="title animate-fadeIn">{{title}}</h2>
|
|
2626
|
+
{{/if}}
|
|
2627
|
+
<div class="number animate-zoomIn">{{keyMessage}}</div>
|
|
2628
|
+
{{#if body}}
|
|
2629
|
+
<p class="number-context animate-fadeIn delay-300">{{body}}</p>
|
|
2460
2630
|
{{/if}}
|
|
2461
2631
|
{{> source}}
|
|
2462
2632
|
</div>
|
|
@@ -4840,6 +5010,12 @@ ${slides}
|
|
|
4840
5010
|
const highlight = palette.accent || "#e94560";
|
|
4841
5011
|
const text = palette.text || "#1a1a2e";
|
|
4842
5012
|
const background = palette.background || "#ffffff";
|
|
5013
|
+
const isDark = this.isDarkPalette(palette);
|
|
5014
|
+
const defaultBg = isDark ? background : "#ffffff";
|
|
5015
|
+
const defaultText = isDark ? text : "#1a1a2e";
|
|
5016
|
+
const headingColor = isDark ? primary : primary;
|
|
5017
|
+
const titleBg = isDark ? background : primary;
|
|
5018
|
+
const titleBgEnd = isDark ? this.lightenColor(background, 10) : this.darkenColor(primary, 15);
|
|
4843
5019
|
return `
|
|
4844
5020
|
/* Base Styles - KB-Driven Design System */
|
|
4845
5021
|
:root {
|
|
@@ -4866,7 +5042,13 @@ ${slides}
|
|
|
4866
5042
|
font-family: var(--font-body);
|
|
4867
5043
|
font-size: ${fontSize};
|
|
4868
5044
|
line-height: ${lineHeight};
|
|
4869
|
-
color:
|
|
5045
|
+
color: ${defaultText};
|
|
5046
|
+
background: ${defaultBg};
|
|
5047
|
+
}
|
|
5048
|
+
|
|
5049
|
+
/* Override reveal.js default backgrounds for dark mode */
|
|
5050
|
+
.reveal .slides section {
|
|
5051
|
+
background: ${defaultBg};
|
|
4870
5052
|
}
|
|
4871
5053
|
|
|
4872
5054
|
.reveal .slides {
|
|
@@ -4895,10 +5077,17 @@ ${slides}
|
|
|
4895
5077
|
font-family: var(--font-heading);
|
|
4896
5078
|
font-weight: 700;
|
|
4897
5079
|
letter-spacing: -0.02em;
|
|
4898
|
-
color:
|
|
5080
|
+
color: ${isDark ? primary : primary};
|
|
4899
5081
|
margin-bottom: 0.5em;
|
|
4900
5082
|
}
|
|
4901
5083
|
|
|
5084
|
+
/* Dark mode text visibility */
|
|
5085
|
+
${isDark ? `
|
|
5086
|
+
.reveal, .reveal p, .reveal li, .reveal span {
|
|
5087
|
+
color: ${text};
|
|
5088
|
+
}
|
|
5089
|
+
` : ""}
|
|
5090
|
+
|
|
4902
5091
|
.reveal h1 { font-size: 2.5em; }
|
|
4903
5092
|
.reveal h2 { font-size: 1.8em; }
|
|
4904
5093
|
.reveal h3 { font-size: 1.3em; }
|
|
@@ -5076,6 +5265,124 @@ ${slides}
|
|
|
5076
5265
|
font-weight: 600;
|
|
5077
5266
|
margin-top: 1em;
|
|
5078
5267
|
}
|
|
5268
|
+
|
|
5269
|
+
/* ================================================================
|
|
5270
|
+
SLIDE TYPE BACKGROUNDS - KB-Driven Visual Design
|
|
5271
|
+
Creates visual hierarchy and differentiation between slide types
|
|
5272
|
+
================================================================ */
|
|
5273
|
+
|
|
5274
|
+
/* Title slide: Bold background - makes strong first impression */
|
|
5275
|
+
.reveal .slide-title {
|
|
5276
|
+
background: linear-gradient(135deg, ${titleBg} 0%, ${titleBgEnd} 100%);
|
|
5277
|
+
}
|
|
5278
|
+
.reveal .slide-title h1,
|
|
5279
|
+
.reveal .slide-title h2,
|
|
5280
|
+
.reveal .slide-title p,
|
|
5281
|
+
.reveal .slide-title .subtitle {
|
|
5282
|
+
color: ${isDark ? primary : "#ffffff"};
|
|
5283
|
+
}
|
|
5284
|
+
|
|
5285
|
+
/* Section dividers: Subtle visual breaks */
|
|
5286
|
+
.reveal .slide-section-divider {
|
|
5287
|
+
background: ${isDark ? this.lightenColor(background, 8) : `linear-gradient(180deg, ${this.lightenColor(primary, 85)} 0%, ${this.lightenColor(primary, 92)} 100%)`};
|
|
5288
|
+
}
|
|
5289
|
+
|
|
5290
|
+
/* Big number slides: Clean with accent highlight - emphasizes the data */
|
|
5291
|
+
.reveal .slide-big-number {
|
|
5292
|
+
background: var(--color-background);
|
|
5293
|
+
border-left: 8px solid var(--color-highlight);
|
|
5294
|
+
}
|
|
5295
|
+
.reveal .slide-big-number .number {
|
|
5296
|
+
font-size: 5em;
|
|
5297
|
+
background: linear-gradient(135deg, var(--color-highlight) 0%, var(--color-accent) 100%);
|
|
5298
|
+
-webkit-background-clip: text;
|
|
5299
|
+
-webkit-text-fill-color: transparent;
|
|
5300
|
+
background-clip: text;
|
|
5301
|
+
}
|
|
5302
|
+
|
|
5303
|
+
/* Metrics grid: Light accent background - data-focused */
|
|
5304
|
+
.reveal .slide-metrics-grid {
|
|
5305
|
+
background: ${isDark ? this.lightenColor(background, 8) : this.lightenColor(accent, 90)};
|
|
5306
|
+
}
|
|
5307
|
+
${isDark ? `.reveal .slide-metrics-grid { color: ${text}; }` : ""}
|
|
5308
|
+
|
|
5309
|
+
/* CTA slide: Highlight color - drives action */
|
|
5310
|
+
.reveal .slide-cta {
|
|
5311
|
+
background: linear-gradient(135deg, var(--color-highlight) 0%, var(--color-accent) 100%);
|
|
5312
|
+
}
|
|
5313
|
+
.reveal .slide-cta h2,
|
|
5314
|
+
.reveal .slide-cta p {
|
|
5315
|
+
color: #ffffff;
|
|
5316
|
+
}
|
|
5317
|
+
.reveal .slide-cta .cta-button {
|
|
5318
|
+
background: #ffffff;
|
|
5319
|
+
color: var(--color-highlight);
|
|
5320
|
+
}
|
|
5321
|
+
|
|
5322
|
+
/* Thank you slide: Matches title for bookend effect */
|
|
5323
|
+
.reveal .slide-thank-you {
|
|
5324
|
+
background: linear-gradient(135deg, ${titleBg} 0%, ${titleBgEnd} 100%);
|
|
5325
|
+
}
|
|
5326
|
+
.reveal .slide-thank-you h2,
|
|
5327
|
+
.reveal .slide-thank-you p,
|
|
5328
|
+
.reveal .slide-thank-you .subtitle {
|
|
5329
|
+
color: ${isDark ? primary : "#ffffff"};
|
|
5330
|
+
}
|
|
5331
|
+
|
|
5332
|
+
/* Quote slides: Elegant subtle background */
|
|
5333
|
+
.reveal .slide-quote {
|
|
5334
|
+
background: ${isDark ? this.lightenColor(background, 5) : this.lightenColor(secondary, 92)};
|
|
5335
|
+
}
|
|
5336
|
+
.reveal .slide-quote blockquote {
|
|
5337
|
+
border-left-color: var(--color-accent);
|
|
5338
|
+
font-size: 1.3em;
|
|
5339
|
+
}
|
|
5340
|
+
${isDark ? `.reveal .slide-quote { color: ${text}; }` : ""}
|
|
5341
|
+
|
|
5342
|
+
/* Single statement: Clean, centered, impactful */
|
|
5343
|
+
.reveal .slide-single-statement {
|
|
5344
|
+
background: var(--color-background);
|
|
5345
|
+
}
|
|
5346
|
+
.reveal .slide-single-statement .statement,
|
|
5347
|
+
.reveal .slide-single-statement .big-idea-text {
|
|
5348
|
+
font-size: 2.2em;
|
|
5349
|
+
max-width: 80%;
|
|
5350
|
+
margin: 0 auto;
|
|
5351
|
+
}
|
|
5352
|
+
|
|
5353
|
+
/* Comparison slides: Split visual */
|
|
5354
|
+
.reveal .slide-comparison .columns {
|
|
5355
|
+
gap: 60px;
|
|
5356
|
+
}
|
|
5357
|
+
.reveal .slide-comparison .column:first-child {
|
|
5358
|
+
border-right: 2px solid ${this.lightenColor(secondary, 70)};
|
|
5359
|
+
padding-right: 30px;
|
|
5360
|
+
}
|
|
5361
|
+
|
|
5362
|
+
/* Timeline/Process: Visual flow */
|
|
5363
|
+
.reveal .slide-timeline .steps,
|
|
5364
|
+
.reveal .slide-process .steps {
|
|
5365
|
+
display: flex;
|
|
5366
|
+
gap: 20px;
|
|
5367
|
+
}
|
|
5368
|
+
.reveal .slide-timeline .step,
|
|
5369
|
+
.reveal .slide-process .step {
|
|
5370
|
+
flex: 1;
|
|
5371
|
+
padding: 20px;
|
|
5372
|
+
background: ${isDark ? this.lightenColor(background, 10) : this.lightenColor(secondary, 92)};
|
|
5373
|
+
border-radius: 8px;
|
|
5374
|
+
border-top: 4px solid var(--color-accent);
|
|
5375
|
+
${isDark ? `color: ${text};` : ""}
|
|
5376
|
+
}
|
|
5377
|
+
|
|
5378
|
+
/* Progress bar enhancement */
|
|
5379
|
+
.reveal .progress {
|
|
5380
|
+
background: ${this.lightenColor(primary, 80)};
|
|
5381
|
+
height: 4px;
|
|
5382
|
+
}
|
|
5383
|
+
.reveal .progress span {
|
|
5384
|
+
background: var(--color-highlight);
|
|
5385
|
+
}
|
|
5079
5386
|
`;
|
|
5080
5387
|
}
|
|
5081
5388
|
/**
|
|
@@ -5091,6 +5398,37 @@ ${slides}
|
|
|
5091
5398
|
b = Math.min(255, Math.floor(b + (255 - b) * (percent / 100)));
|
|
5092
5399
|
return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`;
|
|
5093
5400
|
}
|
|
5401
|
+
/**
|
|
5402
|
+
* Check if a palette is "dark mode" (dark background, light text)
|
|
5403
|
+
*/
|
|
5404
|
+
isDarkPalette(palette) {
|
|
5405
|
+
const bgLuminance = this.getLuminance(palette.background || "#ffffff");
|
|
5406
|
+
const textLuminance = this.getLuminance(palette.text || "#000000");
|
|
5407
|
+
return bgLuminance < textLuminance;
|
|
5408
|
+
}
|
|
5409
|
+
/**
|
|
5410
|
+
* Get relative luminance of a hex color (0-1, 0=black, 1=white)
|
|
5411
|
+
*/
|
|
5412
|
+
getLuminance(hex) {
|
|
5413
|
+
hex = hex.replace("#", "");
|
|
5414
|
+
const r = parseInt(hex.substring(0, 2), 16) / 255;
|
|
5415
|
+
const g = parseInt(hex.substring(2, 4), 16) / 255;
|
|
5416
|
+
const b = parseInt(hex.substring(4, 6), 16) / 255;
|
|
5417
|
+
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
5418
|
+
}
|
|
5419
|
+
/**
|
|
5420
|
+
* Darken a hex color
|
|
5421
|
+
*/
|
|
5422
|
+
darkenColor(hex, percent) {
|
|
5423
|
+
hex = hex.replace("#", "");
|
|
5424
|
+
let r = parseInt(hex.substring(0, 2), 16);
|
|
5425
|
+
let g = parseInt(hex.substring(2, 4), 16);
|
|
5426
|
+
let b = parseInt(hex.substring(4, 6), 16);
|
|
5427
|
+
r = Math.max(0, Math.floor(r * (1 - percent / 100)));
|
|
5428
|
+
g = Math.max(0, Math.floor(g * (1 - percent / 100)));
|
|
5429
|
+
b = Math.max(0, Math.floor(b * (1 - percent / 100)));
|
|
5430
|
+
return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`;
|
|
5431
|
+
}
|
|
5094
5432
|
/**
|
|
5095
5433
|
* Get theme-specific styles - KB-DRIVEN
|
|
5096
5434
|
*/
|
|
@@ -5307,15 +5645,15 @@ var IterativeQAEngine = class {
|
|
|
5307
5645
|
let autoFixSummary = "";
|
|
5308
5646
|
let totalFixesApplied = 0;
|
|
5309
5647
|
if (opts.verbose) {
|
|
5310
|
-
|
|
5311
|
-
|
|
5312
|
-
|
|
5313
|
-
|
|
5648
|
+
logger.progress("\n\u{1F504} Starting Iterative QA Process");
|
|
5649
|
+
logger.info(` Target Score: ${opts.minScore}/100`);
|
|
5650
|
+
logger.info(` Max Iterations: ${opts.maxIterations}`);
|
|
5651
|
+
logger.progress("");
|
|
5314
5652
|
}
|
|
5315
5653
|
for (let i = 0; i < opts.maxIterations; i++) {
|
|
5316
5654
|
const iterationNum = i + 1;
|
|
5317
5655
|
if (opts.verbose) {
|
|
5318
|
-
|
|
5656
|
+
logger.progress(`\u{1F4CA} Iteration ${iterationNum}/${opts.maxIterations}...`);
|
|
5319
5657
|
}
|
|
5320
5658
|
scoringResult = await this.scorer.score(currentSlides, currentHtml, opts.minScore);
|
|
5321
5659
|
iterations.push({
|
|
@@ -5334,17 +5672,17 @@ var IterativeQAEngine = class {
|
|
|
5334
5672
|
timestamp: /* @__PURE__ */ new Date()
|
|
5335
5673
|
});
|
|
5336
5674
|
if (opts.verbose) {
|
|
5337
|
-
|
|
5675
|
+
logger.info(` Score: ${scoringResult.overallScore}/100`);
|
|
5338
5676
|
}
|
|
5339
5677
|
if (scoringResult.passed) {
|
|
5340
5678
|
if (opts.verbose) {
|
|
5341
|
-
|
|
5679
|
+
logger.success(`PASSED - Score meets threshold (${opts.minScore})`);
|
|
5342
5680
|
}
|
|
5343
5681
|
break;
|
|
5344
5682
|
}
|
|
5345
5683
|
if (i < opts.maxIterations - 1) {
|
|
5346
5684
|
if (opts.verbose) {
|
|
5347
|
-
|
|
5685
|
+
logger.warn("Below threshold, applying auto-fixes...");
|
|
5348
5686
|
}
|
|
5349
5687
|
const fixResult = await this.fixer.fix(currentSlides, scoringResult);
|
|
5350
5688
|
if (fixResult.fixesApplied.length > 0) {
|
|
@@ -5356,12 +5694,12 @@ var IterativeQAEngine = class {
|
|
|
5356
5694
|
}
|
|
5357
5695
|
totalFixesApplied += fixResult.fixesApplied.length;
|
|
5358
5696
|
if (opts.verbose) {
|
|
5359
|
-
|
|
5697
|
+
logger.info(` \u{1F527} Applied ${fixResult.fixesApplied.length} fixes`);
|
|
5360
5698
|
}
|
|
5361
5699
|
autoFixSummary += fixResult.summary + "\n";
|
|
5362
5700
|
} else {
|
|
5363
5701
|
if (opts.verbose) {
|
|
5364
|
-
|
|
5702
|
+
logger.warn("No auto-fixes available, manual review needed");
|
|
5365
5703
|
}
|
|
5366
5704
|
break;
|
|
5367
5705
|
}
|
|
@@ -5377,7 +5715,7 @@ var IterativeQAEngine = class {
|
|
|
5377
5715
|
totalFixesApplied
|
|
5378
5716
|
);
|
|
5379
5717
|
if (opts.verbose) {
|
|
5380
|
-
|
|
5718
|
+
logger.info(report);
|
|
5381
5719
|
}
|
|
5382
5720
|
return {
|
|
5383
5721
|
finalScore: scoringResult.overallScore,
|
|
@@ -6179,6 +6517,172 @@ var PowerPointGenerator = class {
|
|
|
6179
6517
|
}
|
|
6180
6518
|
};
|
|
6181
6519
|
|
|
6520
|
+
// src/generators/PDFGenerator.ts
|
|
6521
|
+
import puppeteer from "puppeteer";
|
|
6522
|
+
var PDFGenerator = class {
|
|
6523
|
+
defaultOptions = {
|
|
6524
|
+
orientation: "landscape",
|
|
6525
|
+
printBackground: true,
|
|
6526
|
+
format: "Slide",
|
|
6527
|
+
scale: 1
|
|
6528
|
+
};
|
|
6529
|
+
/**
|
|
6530
|
+
* Generate PDF from HTML presentation
|
|
6531
|
+
* @param html - The HTML content of the presentation
|
|
6532
|
+
* @param options - PDF generation options
|
|
6533
|
+
* @returns Buffer containing the PDF file
|
|
6534
|
+
*/
|
|
6535
|
+
async generate(html, options = {}) {
|
|
6536
|
+
const opts = { ...this.defaultOptions, ...options };
|
|
6537
|
+
logger.progress("\u{1F4C4} Generating PDF...");
|
|
6538
|
+
let browser;
|
|
6539
|
+
try {
|
|
6540
|
+
browser = await puppeteer.launch({
|
|
6541
|
+
headless: true,
|
|
6542
|
+
args: [
|
|
6543
|
+
"--no-sandbox",
|
|
6544
|
+
"--disable-setuid-sandbox",
|
|
6545
|
+
"--disable-dev-shm-usage"
|
|
6546
|
+
]
|
|
6547
|
+
});
|
|
6548
|
+
const page = await browser.newPage();
|
|
6549
|
+
const printHtml = this.preparePrintHtml(html);
|
|
6550
|
+
await page.setViewport({
|
|
6551
|
+
width: 1920,
|
|
6552
|
+
height: 1080
|
|
6553
|
+
});
|
|
6554
|
+
await page.setContent(printHtml, {
|
|
6555
|
+
waitUntil: ["networkidle0", "domcontentloaded"]
|
|
6556
|
+
});
|
|
6557
|
+
await page.evaluate(() => {
|
|
6558
|
+
return new Promise((resolve) => {
|
|
6559
|
+
if (document.fonts && document.fonts.ready) {
|
|
6560
|
+
document.fonts.ready.then(() => resolve());
|
|
6561
|
+
} else {
|
|
6562
|
+
setTimeout(resolve, 1e3);
|
|
6563
|
+
}
|
|
6564
|
+
});
|
|
6565
|
+
});
|
|
6566
|
+
const pdfBuffer = await page.pdf({
|
|
6567
|
+
width: "1920px",
|
|
6568
|
+
height: "1080px",
|
|
6569
|
+
printBackground: opts.printBackground,
|
|
6570
|
+
scale: opts.scale,
|
|
6571
|
+
landscape: opts.orientation === "landscape",
|
|
6572
|
+
margin: {
|
|
6573
|
+
top: "0px",
|
|
6574
|
+
right: "0px",
|
|
6575
|
+
bottom: "0px",
|
|
6576
|
+
left: "0px"
|
|
6577
|
+
}
|
|
6578
|
+
});
|
|
6579
|
+
logger.step("PDF generated successfully");
|
|
6580
|
+
return Buffer.from(pdfBuffer);
|
|
6581
|
+
} catch (error) {
|
|
6582
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
6583
|
+
logger.error(`PDF generation failed: ${errorMessage}`);
|
|
6584
|
+
throw new Error(`PDF generation failed: ${errorMessage}`);
|
|
6585
|
+
} finally {
|
|
6586
|
+
if (browser) {
|
|
6587
|
+
await browser.close();
|
|
6588
|
+
}
|
|
6589
|
+
}
|
|
6590
|
+
}
|
|
6591
|
+
/**
|
|
6592
|
+
* Prepare HTML for print/PDF output
|
|
6593
|
+
* Modifies the HTML to work better as a printed document
|
|
6594
|
+
*/
|
|
6595
|
+
preparePrintHtml(html) {
|
|
6596
|
+
const printCss = `
|
|
6597
|
+
<style id="pdf-print-styles">
|
|
6598
|
+
/* Print-specific overrides */
|
|
6599
|
+
@page {
|
|
6600
|
+
size: 1920px 1080px;
|
|
6601
|
+
margin: 0;
|
|
6602
|
+
}
|
|
6603
|
+
|
|
6604
|
+
@media print {
|
|
6605
|
+
html, body {
|
|
6606
|
+
margin: 0;
|
|
6607
|
+
padding: 0;
|
|
6608
|
+
width: 1920px;
|
|
6609
|
+
height: 1080px;
|
|
6610
|
+
}
|
|
6611
|
+
|
|
6612
|
+
.reveal .slides {
|
|
6613
|
+
width: 100% !important;
|
|
6614
|
+
height: 100% !important;
|
|
6615
|
+
margin: 0 !important;
|
|
6616
|
+
padding: 0 !important;
|
|
6617
|
+
transform: none !important;
|
|
6618
|
+
}
|
|
6619
|
+
|
|
6620
|
+
.reveal .slides section {
|
|
6621
|
+
page-break-after: always;
|
|
6622
|
+
page-break-inside: avoid;
|
|
6623
|
+
width: 1920px !important;
|
|
6624
|
+
height: 1080px !important;
|
|
6625
|
+
margin: 0 !important;
|
|
6626
|
+
padding: 60px !important;
|
|
6627
|
+
box-sizing: border-box;
|
|
6628
|
+
position: relative !important;
|
|
6629
|
+
top: auto !important;
|
|
6630
|
+
left: auto !important;
|
|
6631
|
+
transform: none !important;
|
|
6632
|
+
display: flex !important;
|
|
6633
|
+
opacity: 1 !important;
|
|
6634
|
+
visibility: visible !important;
|
|
6635
|
+
}
|
|
6636
|
+
|
|
6637
|
+
.reveal .slides section:last-child {
|
|
6638
|
+
page-break-after: auto;
|
|
6639
|
+
}
|
|
6640
|
+
|
|
6641
|
+
/* Make all slides visible for printing */
|
|
6642
|
+
.reveal .slides section.future,
|
|
6643
|
+
.reveal .slides section.past {
|
|
6644
|
+
display: flex !important;
|
|
6645
|
+
opacity: 1 !important;
|
|
6646
|
+
visibility: visible !important;
|
|
6647
|
+
}
|
|
6648
|
+
|
|
6649
|
+
/* Disable animations for print */
|
|
6650
|
+
* {
|
|
6651
|
+
animation: none !important;
|
|
6652
|
+
transition: none !important;
|
|
6653
|
+
}
|
|
6654
|
+
|
|
6655
|
+
/* Hide Reveal.js controls */
|
|
6656
|
+
.reveal .controls,
|
|
6657
|
+
.reveal .progress,
|
|
6658
|
+
.reveal .slide-number,
|
|
6659
|
+
.reveal .speaker-notes,
|
|
6660
|
+
.reveal .navigate-left,
|
|
6661
|
+
.reveal .navigate-right,
|
|
6662
|
+
.reveal .navigate-up,
|
|
6663
|
+
.reveal .navigate-down {
|
|
6664
|
+
display: none !important;
|
|
6665
|
+
}
|
|
6666
|
+
}
|
|
6667
|
+
|
|
6668
|
+
/* Force print mode in Puppeteer */
|
|
6669
|
+
.reveal .slides section {
|
|
6670
|
+
page-break-after: always;
|
|
6671
|
+
page-break-inside: avoid;
|
|
6672
|
+
}
|
|
6673
|
+
</style>
|
|
6674
|
+
`;
|
|
6675
|
+
if (html.includes("</head>")) {
|
|
6676
|
+
return html.replace("</head>", `${printCss}</head>`);
|
|
6677
|
+
} else {
|
|
6678
|
+
return printCss + html;
|
|
6679
|
+
}
|
|
6680
|
+
}
|
|
6681
|
+
};
|
|
6682
|
+
function createPDFGenerator() {
|
|
6683
|
+
return new PDFGenerator();
|
|
6684
|
+
}
|
|
6685
|
+
|
|
6182
6686
|
// src/core/PresentationEngine.ts
|
|
6183
6687
|
var PresentationEngine = class {
|
|
6184
6688
|
contentAnalyzer;
|
|
@@ -6187,6 +6691,7 @@ var PresentationEngine = class {
|
|
|
6187
6691
|
qaEngine;
|
|
6188
6692
|
htmlGenerator;
|
|
6189
6693
|
pptxGenerator;
|
|
6694
|
+
pdfGenerator;
|
|
6190
6695
|
kb;
|
|
6191
6696
|
constructor() {
|
|
6192
6697
|
this.contentAnalyzer = new ContentAnalyzer();
|
|
@@ -6195,6 +6700,7 @@ var PresentationEngine = class {
|
|
|
6195
6700
|
this.qaEngine = new QAEngine();
|
|
6196
6701
|
this.htmlGenerator = new RevealJsGenerator();
|
|
6197
6702
|
this.pptxGenerator = new PowerPointGenerator();
|
|
6703
|
+
this.pdfGenerator = createPDFGenerator();
|
|
6198
6704
|
}
|
|
6199
6705
|
/**
|
|
6200
6706
|
* Generate a presentation from content.
|
|
@@ -6204,26 +6710,26 @@ var PresentationEngine = class {
|
|
|
6204
6710
|
*/
|
|
6205
6711
|
async generate(config) {
|
|
6206
6712
|
this.validateConfig(config);
|
|
6207
|
-
|
|
6713
|
+
logger.progress("\u{1F4DA} Loading knowledge base...");
|
|
6208
6714
|
this.kb = await getKnowledgeGateway();
|
|
6209
|
-
|
|
6715
|
+
logger.progress("\u{1F4DD} Analyzing content...");
|
|
6210
6716
|
const analysis = await this.contentAnalyzer.analyze(config.content, config.contentType);
|
|
6211
6717
|
const presentationType = config.presentationType || analysis.detectedType;
|
|
6212
|
-
|
|
6718
|
+
logger.step(`Presentation type: ${presentationType}`);
|
|
6213
6719
|
const slideFactory = createSlideFactory(this.kb, presentationType);
|
|
6214
|
-
|
|
6720
|
+
logger.progress("\u{1F3A8} Creating slides...");
|
|
6215
6721
|
const slides = await slideFactory.createSlides(analysis);
|
|
6216
|
-
|
|
6722
|
+
logger.progress("\u2705 Validating structure...");
|
|
6217
6723
|
const structureErrors = this.validateStructure(slides, config.mode);
|
|
6218
6724
|
if (structureErrors.length > 0) {
|
|
6219
6725
|
if (config.skipQA) {
|
|
6220
|
-
|
|
6221
|
-
structureErrors.forEach((e) =>
|
|
6726
|
+
logger.warn("Structure warnings (bypassed):");
|
|
6727
|
+
structureErrors.forEach((e) => logger.warn(` \u2022 ${e}`));
|
|
6222
6728
|
} else {
|
|
6223
6729
|
throw new ValidationError(structureErrors, "Slide structure validation failed");
|
|
6224
6730
|
}
|
|
6225
6731
|
}
|
|
6226
|
-
|
|
6732
|
+
logger.progress("\u{1F528} Generating outputs...");
|
|
6227
6733
|
const outputs = {};
|
|
6228
6734
|
if (config.format.includes("html")) {
|
|
6229
6735
|
outputs.html = await this.htmlGenerator.generate(slides, config);
|
|
@@ -6241,7 +6747,7 @@ var PresentationEngine = class {
|
|
|
6241
6747
|
const maxIterations = config.maxIterations ?? 5;
|
|
6242
6748
|
const useIterativeQA = config.useIterativeQA !== false;
|
|
6243
6749
|
if (useIterativeQA) {
|
|
6244
|
-
|
|
6750
|
+
logger.progress("\u{1F50D} Running Iterative QA (7-Dimension Scoring)...");
|
|
6245
6751
|
const iterativeEngine = createIterativeQAEngine(
|
|
6246
6752
|
config.mode,
|
|
6247
6753
|
presentationType,
|
|
@@ -6263,20 +6769,30 @@ var PresentationEngine = class {
|
|
|
6263
6769
|
throw new QAFailureError(score, threshold, qaResults);
|
|
6264
6770
|
}
|
|
6265
6771
|
} else {
|
|
6266
|
-
|
|
6772
|
+
logger.progress("\u{1F50D} Running QA validation (legacy mode)...");
|
|
6267
6773
|
qaResults = await this.qaEngine.validate(outputs.html, {
|
|
6268
6774
|
mode: config.mode,
|
|
6269
6775
|
strictMode: true
|
|
6270
6776
|
});
|
|
6271
6777
|
score = this.scoreCalculator.calculate(qaResults);
|
|
6272
|
-
|
|
6778
|
+
logger.info(`\u{1F4CA} QA Score: ${score}/100`);
|
|
6273
6779
|
if (score < threshold) {
|
|
6274
6780
|
throw new QAFailureError(score, threshold, qaResults);
|
|
6275
6781
|
}
|
|
6276
6782
|
}
|
|
6277
6783
|
} else {
|
|
6278
6784
|
qaResults = this.qaEngine.createEmptyResults();
|
|
6279
|
-
|
|
6785
|
+
logger.warn("QA validation skipped (NOT RECOMMENDED)");
|
|
6786
|
+
}
|
|
6787
|
+
if (outputs.html && (config.format.includes("pdf") || config.generatePdf !== false)) {
|
|
6788
|
+
try {
|
|
6789
|
+
logger.progress("\u{1F4C4} Generating PDF from validated HTML...");
|
|
6790
|
+
outputs.pdf = await this.pdfGenerator.generate(outputs.html);
|
|
6791
|
+
logger.step("PDF generated successfully");
|
|
6792
|
+
} catch (pdfError) {
|
|
6793
|
+
const errorMsg = pdfError instanceof Error ? pdfError.message : String(pdfError);
|
|
6794
|
+
logger.warn(`PDF generation failed (non-critical): ${errorMsg}`);
|
|
6795
|
+
}
|
|
6280
6796
|
}
|
|
6281
6797
|
const metadata = this.buildMetadata(config, analysis, finalSlides, iterativeResult);
|
|
6282
6798
|
return {
|
|
@@ -6364,15 +6880,28 @@ var PresentationEngine = class {
|
|
|
6364
6880
|
if (slides.length < 3) {
|
|
6365
6881
|
errors.push("Presentation must have at least 3 slides");
|
|
6366
6882
|
}
|
|
6883
|
+
const sparseByDesignTypes = [
|
|
6884
|
+
"title",
|
|
6885
|
+
"section-divider",
|
|
6886
|
+
"thank-you",
|
|
6887
|
+
"big-number",
|
|
6888
|
+
"single-statement",
|
|
6889
|
+
"cta",
|
|
6890
|
+
"agenda",
|
|
6891
|
+
"metrics-grid",
|
|
6892
|
+
"quote",
|
|
6893
|
+
"image-focus"
|
|
6894
|
+
];
|
|
6367
6895
|
slides.forEach((slide, index) => {
|
|
6368
6896
|
const wordCount = this.countWords(slide);
|
|
6369
6897
|
if (mode === "keynote") {
|
|
6370
|
-
if (wordCount > 25) {
|
|
6898
|
+
if (wordCount > 25 && !sparseByDesignTypes.includes(slide.type)) {
|
|
6371
6899
|
errors.push(`Slide ${index + 1}: ${wordCount} words exceeds keynote limit of 25`);
|
|
6372
6900
|
}
|
|
6373
6901
|
} else {
|
|
6374
|
-
|
|
6375
|
-
|
|
6902
|
+
const requiresMinWords = ["bullet-points", "two-column", "process-flow"];
|
|
6903
|
+
if (wordCount < 20 && requiresMinWords.includes(slide.type)) {
|
|
6904
|
+
logger.warn(`Slide ${index + 1}: ${wordCount} words is sparse for ${slide.type} slide`);
|
|
6376
6905
|
}
|
|
6377
6906
|
if (wordCount > 100) {
|
|
6378
6907
|
errors.push(`Slide ${index + 1}: ${wordCount} words exceeds business limit of 100`);
|
|
@@ -6466,11 +6995,11 @@ var SlideGenerator = class {
|
|
|
6466
6995
|
await this.initialize();
|
|
6467
6996
|
this.presentationType = type || analysis.detectedType;
|
|
6468
6997
|
this.mode = this.kb.getModeForType(this.presentationType);
|
|
6469
|
-
|
|
6998
|
+
logger.step(`Using ${this.mode} mode for ${this.presentationType}`);
|
|
6470
6999
|
this.factory = createSlideFactory(this.kb, this.presentationType);
|
|
6471
7000
|
const slides = await this.factory.createSlides(analysis);
|
|
6472
7001
|
const legacySlides = this.convertToLegacyFormat(slides);
|
|
6473
|
-
|
|
7002
|
+
logger.step(`Generated ${legacySlides.length} KB-validated slides`);
|
|
6474
7003
|
return legacySlides;
|
|
6475
7004
|
}
|
|
6476
7005
|
/**
|