claude-presentation-master 5.0.0 → 6.1.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 +2 -2
- package/dist/index.d.mts +375 -162
- package/dist/index.d.ts +375 -162
- package/dist/index.js +1039 -620
- package/dist/index.mjs +1036 -617
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -26,8 +26,280 @@ var TemplateNotFoundError = class extends Error {
|
|
|
26
26
|
}
|
|
27
27
|
};
|
|
28
28
|
|
|
29
|
+
// src/kb/KnowledgeGateway.ts
|
|
30
|
+
import { readFileSync } from "fs";
|
|
31
|
+
import { join, dirname } from "path";
|
|
32
|
+
import { fileURLToPath } from "url";
|
|
33
|
+
import * as yaml from "yaml";
|
|
34
|
+
function getModuleDir() {
|
|
35
|
+
if (typeof import.meta !== "undefined" && import.meta.url) {
|
|
36
|
+
return dirname(fileURLToPath(import.meta.url));
|
|
37
|
+
}
|
|
38
|
+
if (typeof __dirname !== "undefined") {
|
|
39
|
+
return __dirname;
|
|
40
|
+
}
|
|
41
|
+
return process.cwd();
|
|
42
|
+
}
|
|
43
|
+
var KnowledgeGateway = class {
|
|
44
|
+
kb;
|
|
45
|
+
loaded = false;
|
|
46
|
+
constructor() {
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Load the knowledge base from YAML file
|
|
50
|
+
*/
|
|
51
|
+
async load() {
|
|
52
|
+
if (this.loaded) return;
|
|
53
|
+
const moduleDir = getModuleDir();
|
|
54
|
+
const possiblePaths = [
|
|
55
|
+
join(moduleDir, "../../assets/presentation-knowledge.yaml"),
|
|
56
|
+
join(moduleDir, "../assets/presentation-knowledge.yaml"),
|
|
57
|
+
join(moduleDir, "../../../assets/presentation-knowledge.yaml"),
|
|
58
|
+
join(moduleDir, "assets/presentation-knowledge.yaml"),
|
|
59
|
+
join(process.cwd(), "assets/presentation-knowledge.yaml"),
|
|
60
|
+
join(process.cwd(), "node_modules/claude-presentation-master/assets/presentation-knowledge.yaml")
|
|
61
|
+
];
|
|
62
|
+
for (const path of possiblePaths) {
|
|
63
|
+
try {
|
|
64
|
+
const content = readFileSync(path, "utf-8");
|
|
65
|
+
this.kb = yaml.parse(content);
|
|
66
|
+
this.loaded = true;
|
|
67
|
+
console.log(` \u2713 Knowledge base loaded from ${path}`);
|
|
68
|
+
return;
|
|
69
|
+
} catch {
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
throw new Error(`Knowledge base not found. Tried: ${possiblePaths.join(", ")}`);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Get presentation mode configuration (keynote or business)
|
|
76
|
+
*/
|
|
77
|
+
getMode(mode) {
|
|
78
|
+
this.ensureLoaded();
|
|
79
|
+
return this.kb.presentation_modes[mode];
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Determine which mode is best for a given presentation type
|
|
83
|
+
*/
|
|
84
|
+
getModeForType(type) {
|
|
85
|
+
const keynoteTypes = ["ted_keynote", "sales_pitch", "all_hands"];
|
|
86
|
+
const businessTypes = ["consulting_deck", "investment_banking", "investor_pitch", "technical_presentation"];
|
|
87
|
+
if (keynoteTypes.includes(type)) return "keynote";
|
|
88
|
+
if (businessTypes.includes(type)) return "business";
|
|
89
|
+
return "keynote";
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Get word limits for the specified mode
|
|
93
|
+
*/
|
|
94
|
+
getWordLimits(mode) {
|
|
95
|
+
const modeConfig = this.getMode(mode);
|
|
96
|
+
const range = modeConfig.characteristics.words_per_slide;
|
|
97
|
+
if (mode === "keynote") {
|
|
98
|
+
return { min: 6, max: 15, ideal: 10 };
|
|
99
|
+
} else {
|
|
100
|
+
return { min: 40, max: 80, ideal: 60 };
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Get bullet point limits
|
|
105
|
+
*/
|
|
106
|
+
getBulletLimits(mode) {
|
|
107
|
+
if (mode === "keynote") {
|
|
108
|
+
return { maxBullets: 3, maxWordsPerBullet: 8 };
|
|
109
|
+
} else {
|
|
110
|
+
return { maxBullets: 5, maxWordsPerBullet: 20 };
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Get whitespace percentage requirement
|
|
115
|
+
*/
|
|
116
|
+
getWhitespaceRequirement(mode) {
|
|
117
|
+
if (mode === "keynote") return 0.4;
|
|
118
|
+
return 0.25;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Get slide templates for the specified mode
|
|
122
|
+
*/
|
|
123
|
+
getSlideTemplates(mode) {
|
|
124
|
+
this.ensureLoaded();
|
|
125
|
+
const key = mode === "keynote" ? "keynote_mode" : "business_mode";
|
|
126
|
+
return this.kb.slide_templates[key] || [];
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Get a specific slide template by name
|
|
130
|
+
*/
|
|
131
|
+
getSlideTemplate(mode, templateName) {
|
|
132
|
+
const templates = this.getSlideTemplates(mode);
|
|
133
|
+
return templates.find((t) => t.name.toLowerCase().includes(templateName.toLowerCase()));
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Get quality metrics for grading
|
|
137
|
+
*/
|
|
138
|
+
getQualityMetrics() {
|
|
139
|
+
this.ensureLoaded();
|
|
140
|
+
return this.kb.quality_metrics;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Get anti-patterns to avoid
|
|
144
|
+
*/
|
|
145
|
+
getAntiPatterns(mode) {
|
|
146
|
+
this.ensureLoaded();
|
|
147
|
+
const key = mode === "keynote" ? "keynote_mode" : "business_mode";
|
|
148
|
+
return this.kb.anti_patterns[key] || [];
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Get data visualization anti-patterns
|
|
152
|
+
*/
|
|
153
|
+
getDataVizAntiPatterns() {
|
|
154
|
+
this.ensureLoaded();
|
|
155
|
+
return this.kb.anti_patterns.data_visualization || [];
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Get accessibility anti-patterns
|
|
159
|
+
*/
|
|
160
|
+
getAccessibilityAntiPatterns() {
|
|
161
|
+
this.ensureLoaded();
|
|
162
|
+
return this.kb.anti_patterns.accessibility || [];
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Get color palette by name
|
|
166
|
+
*/
|
|
167
|
+
getColorPalette(name) {
|
|
168
|
+
this.ensureLoaded();
|
|
169
|
+
const palettes = this.kb.design_system.color_palettes;
|
|
170
|
+
return palettes[name];
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Get recommended color palette for presentation type
|
|
174
|
+
*/
|
|
175
|
+
getRecommendedPalette(type) {
|
|
176
|
+
this.ensureLoaded();
|
|
177
|
+
const palettes = this.kb.design_system.color_palettes;
|
|
178
|
+
switch (type) {
|
|
179
|
+
case "consulting_deck":
|
|
180
|
+
return palettes.consulting_classic;
|
|
181
|
+
case "investment_banking":
|
|
182
|
+
case "investor_pitch":
|
|
183
|
+
return palettes.executive_professional;
|
|
184
|
+
case "ted_keynote":
|
|
185
|
+
return palettes.dark_executive;
|
|
186
|
+
case "sales_pitch":
|
|
187
|
+
return palettes.modern_business;
|
|
188
|
+
case "technical_presentation":
|
|
189
|
+
return palettes.modern_business;
|
|
190
|
+
case "all_hands":
|
|
191
|
+
return palettes.strategy_growth;
|
|
192
|
+
default:
|
|
193
|
+
return palettes.consulting_classic;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Get typography guidelines for mode
|
|
198
|
+
*/
|
|
199
|
+
getTypography(mode) {
|
|
200
|
+
this.ensureLoaded();
|
|
201
|
+
const key = mode === "keynote" ? "keynote_mode" : "business_mode";
|
|
202
|
+
return this.kb.design_system.typography[key];
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Get story framework by name
|
|
206
|
+
*/
|
|
207
|
+
getStoryFramework(name) {
|
|
208
|
+
this.ensureLoaded();
|
|
209
|
+
return this.kb.story_frameworks[name];
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Get expert principles by name
|
|
213
|
+
*/
|
|
214
|
+
getExpertPrinciples(expertName) {
|
|
215
|
+
this.ensureLoaded();
|
|
216
|
+
if (this.kb.experts[expertName]) {
|
|
217
|
+
return this.kb.experts[expertName];
|
|
218
|
+
}
|
|
219
|
+
if (this.kb.data_visualization_experts) {
|
|
220
|
+
for (const key of Object.keys(this.kb.data_visualization_experts)) {
|
|
221
|
+
if (key.includes(expertName.toLowerCase())) {
|
|
222
|
+
return this.kb.data_visualization_experts[key];
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Get Duarte's glance test requirements
|
|
230
|
+
*/
|
|
231
|
+
getDuarteGlanceTest() {
|
|
232
|
+
this.ensureLoaded();
|
|
233
|
+
const duarte = this.kb.experts.nancy_duarte;
|
|
234
|
+
return {
|
|
235
|
+
wordLimit: duarte.core_principles.glance_test.word_limit || 25,
|
|
236
|
+
timeSeconds: 3
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Get Miller's Law constraints
|
|
241
|
+
*/
|
|
242
|
+
getMillersLaw() {
|
|
243
|
+
this.ensureLoaded();
|
|
244
|
+
return { minItems: 5, maxItems: 9 };
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Get cognitive load constraints
|
|
248
|
+
*/
|
|
249
|
+
getCognitiveLoadLimits() {
|
|
250
|
+
this.ensureLoaded();
|
|
251
|
+
return { maxItemsPerChunk: 7 };
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Get chart selection guidance
|
|
255
|
+
*/
|
|
256
|
+
getChartGuidance(purpose) {
|
|
257
|
+
this.ensureLoaded();
|
|
258
|
+
return this.kb.chart_selection_guide.by_purpose[purpose];
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Get charts to avoid
|
|
262
|
+
*/
|
|
263
|
+
getChartsToAvoid() {
|
|
264
|
+
this.ensureLoaded();
|
|
265
|
+
const knaflic = this.kb.data_visualization_experts.cole_nussbaumer_knaflic;
|
|
266
|
+
return knaflic.chart_guidance.avoid_these || [];
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Get investment banking pitch book structure
|
|
270
|
+
*/
|
|
271
|
+
getIBPitchBookStructure(type) {
|
|
272
|
+
this.ensureLoaded();
|
|
273
|
+
return this.kb.investment_banking?.pitch_book_types[type];
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Check if knowledge base is loaded
|
|
277
|
+
*/
|
|
278
|
+
ensureLoaded() {
|
|
279
|
+
if (!this.loaded) {
|
|
280
|
+
throw new Error("Knowledge base not loaded. Call load() first.");
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Get the full knowledge base (for advanced queries)
|
|
285
|
+
*/
|
|
286
|
+
getRawKB() {
|
|
287
|
+
this.ensureLoaded();
|
|
288
|
+
return this.kb;
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
var gatewayInstance = null;
|
|
292
|
+
async function getKnowledgeGateway() {
|
|
293
|
+
if (!gatewayInstance) {
|
|
294
|
+
gatewayInstance = new KnowledgeGateway();
|
|
295
|
+
await gatewayInstance.load();
|
|
296
|
+
}
|
|
297
|
+
return gatewayInstance;
|
|
298
|
+
}
|
|
299
|
+
|
|
29
300
|
// src/core/ContentAnalyzer.ts
|
|
30
301
|
var ContentAnalyzer = class {
|
|
302
|
+
kb;
|
|
31
303
|
// Signal words for SCQA detection
|
|
32
304
|
situationSignals = [
|
|
33
305
|
"currently",
|
|
@@ -39,7 +311,8 @@ var ContentAnalyzer = class {
|
|
|
39
311
|
"our",
|
|
40
312
|
"the market",
|
|
41
313
|
"industry",
|
|
42
|
-
"context"
|
|
314
|
+
"context",
|
|
315
|
+
"background"
|
|
43
316
|
];
|
|
44
317
|
complicationSignals = [
|
|
45
318
|
"however",
|
|
@@ -57,20 +330,10 @@ var ContentAnalyzer = class {
|
|
|
57
330
|
"yet",
|
|
58
331
|
"although",
|
|
59
332
|
"despite",
|
|
60
|
-
"while"
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
"
|
|
64
|
-
"what",
|
|
65
|
-
"why",
|
|
66
|
-
"when",
|
|
67
|
-
"where",
|
|
68
|
-
"which",
|
|
69
|
-
"should",
|
|
70
|
-
"could",
|
|
71
|
-
"can we",
|
|
72
|
-
"is it possible",
|
|
73
|
-
"?"
|
|
333
|
+
"while",
|
|
334
|
+
"crisis",
|
|
335
|
+
"gap",
|
|
336
|
+
"broken"
|
|
74
337
|
];
|
|
75
338
|
answerSignals = [
|
|
76
339
|
"therefore",
|
|
@@ -86,249 +349,389 @@ var ContentAnalyzer = class {
|
|
|
86
349
|
"we should",
|
|
87
350
|
"must",
|
|
88
351
|
"need to",
|
|
89
|
-
"the answer"
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
"currently",
|
|
94
|
-
"today",
|
|
95
|
-
"status quo",
|
|
96
|
-
"reality",
|
|
97
|
-
"actual",
|
|
98
|
-
"now",
|
|
99
|
-
"existing",
|
|
100
|
-
"present state",
|
|
101
|
-
"as-is",
|
|
102
|
-
"problem"
|
|
352
|
+
"the answer",
|
|
353
|
+
"introducing",
|
|
354
|
+
"presenting",
|
|
355
|
+
"platform"
|
|
103
356
|
];
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
"
|
|
107
|
-
"
|
|
108
|
-
"
|
|
109
|
-
"
|
|
110
|
-
"
|
|
111
|
-
"
|
|
112
|
-
"
|
|
113
|
-
"
|
|
114
|
-
"
|
|
115
|
-
"
|
|
116
|
-
"
|
|
117
|
-
"goal",
|
|
118
|
-
"aspiration"
|
|
357
|
+
// CTA signals - used to filter these OUT of answer detection
|
|
358
|
+
ctaSignals = [
|
|
359
|
+
"contact",
|
|
360
|
+
"call us",
|
|
361
|
+
"email",
|
|
362
|
+
"schedule",
|
|
363
|
+
"sign up",
|
|
364
|
+
"register",
|
|
365
|
+
"next steps",
|
|
366
|
+
"get started",
|
|
367
|
+
"reach out",
|
|
368
|
+
"visit",
|
|
369
|
+
"follow"
|
|
119
370
|
];
|
|
371
|
+
// Type detection signals
|
|
372
|
+
typeSignals = {
|
|
373
|
+
ted_keynote: ["inspire", "vision", "imagine", "dream", "transform", "story", "journey"],
|
|
374
|
+
sales_pitch: ["buy", "purchase", "pricing", "offer", "deal", "discount", "roi", "value"],
|
|
375
|
+
consulting_deck: ["recommend", "analysis", "assessment", "strategy", "findings", "options", "mece"],
|
|
376
|
+
investment_banking: ["valuation", "dcf", "multiple", "enterprise value", "ebitda", "ipo", "m&a"],
|
|
377
|
+
investor_pitch: ["investment", "funding", "series", "traction", "market size", "tam", "runway"],
|
|
378
|
+
technical_presentation: ["architecture", "api", "system", "implementation", "code", "database", "deploy"],
|
|
379
|
+
all_hands: ["team", "quarter", "update", "progress", "celebrate", "recognize", "company"]
|
|
380
|
+
};
|
|
381
|
+
async initialize() {
|
|
382
|
+
this.kb = await getKnowledgeGateway();
|
|
383
|
+
}
|
|
120
384
|
/**
|
|
121
385
|
* Analyze content and extract structural elements.
|
|
122
386
|
*/
|
|
123
387
|
async analyze(content, contentType) {
|
|
388
|
+
await this.initialize();
|
|
124
389
|
const text = this.parseContent(content, contentType);
|
|
125
|
-
const
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
const
|
|
131
|
-
const
|
|
132
|
-
const
|
|
390
|
+
const title = this.extractTitle(text);
|
|
391
|
+
const detectedType = this.detectPresentationType(text);
|
|
392
|
+
console.log(` \u2713 Detected type: ${detectedType}`);
|
|
393
|
+
const sections = this.extractSections(text);
|
|
394
|
+
console.log(` \u2713 Found ${sections.length} sections`);
|
|
395
|
+
const scqa = this.extractSCQA(text);
|
|
396
|
+
const sparkline = this.extractSparkline(text);
|
|
397
|
+
const keyMessages = this.extractKeyMessages(text);
|
|
398
|
+
const dataPoints = this.extractDataPoints(text);
|
|
399
|
+
console.log(` \u2713 Found ${dataPoints.length} data points`);
|
|
400
|
+
const titles = sections.map((s) => s.header).filter((h) => h.length > 0);
|
|
401
|
+
const starMoments = this.extractStarMoments(text);
|
|
402
|
+
const estimatedSlideCount = Math.max(5, sections.length + 3);
|
|
133
403
|
return {
|
|
134
|
-
|
|
135
|
-
|
|
404
|
+
detectedType,
|
|
405
|
+
title,
|
|
406
|
+
sections,
|
|
136
407
|
keyMessages,
|
|
408
|
+
dataPoints,
|
|
409
|
+
scqa: {
|
|
410
|
+
situation: scqa.situation || "",
|
|
411
|
+
complication: scqa.complication || "",
|
|
412
|
+
question: scqa.question || "",
|
|
413
|
+
answer: scqa.answer || ""
|
|
414
|
+
},
|
|
415
|
+
sparkline: {
|
|
416
|
+
whatIs: sparkline.callToAction ? [sparkline.callToAction] : [],
|
|
417
|
+
whatCouldBe: keyMessages,
|
|
418
|
+
callToAdventure: sparkline.callToAction || ""
|
|
419
|
+
},
|
|
137
420
|
titles,
|
|
138
421
|
starMoments,
|
|
139
422
|
estimatedSlideCount
|
|
140
423
|
};
|
|
141
424
|
}
|
|
142
425
|
/**
|
|
143
|
-
* Parse content based on
|
|
426
|
+
* Parse content based on type
|
|
144
427
|
*/
|
|
145
428
|
parseContent(content, contentType) {
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
return this.parseMarkdown(content);
|
|
149
|
-
case "json":
|
|
150
|
-
return this.parseJSON(content);
|
|
151
|
-
case "yaml":
|
|
152
|
-
return this.parseYAML(content);
|
|
153
|
-
case "text":
|
|
154
|
-
default:
|
|
155
|
-
return content;
|
|
429
|
+
if (contentType === "markdown" || content.includes("#") || content.includes("- ")) {
|
|
430
|
+
return content;
|
|
156
431
|
}
|
|
432
|
+
return content;
|
|
157
433
|
}
|
|
158
434
|
/**
|
|
159
|
-
*
|
|
435
|
+
* Extract the main title from content
|
|
160
436
|
*/
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
text = text.replace(/^\d+\.\s+(.+)$/gm, "[NUMBERED] $1");
|
|
166
|
-
text = text.replace(/\*\*(.+?)\*\*/g, "[EMPHASIS] $1 [/EMPHASIS]");
|
|
167
|
-
text = text.replace(/\*(.+?)\*/g, "$1");
|
|
168
|
-
text = text.replace(/```[\s\S]*?```/g, "[CODE BLOCK]");
|
|
169
|
-
text = text.replace(/`(.+?)`/g, "$1");
|
|
170
|
-
text = text.replace(/\[(.+?)\]\(.+?\)/g, "$1");
|
|
171
|
-
text = text.replace(/!\[.*?\]\(.+?\)/g, "[IMAGE]");
|
|
172
|
-
return text.trim();
|
|
173
|
-
}
|
|
174
|
-
/**
|
|
175
|
-
* Parse JSON content.
|
|
176
|
-
*/
|
|
177
|
-
parseJSON(content) {
|
|
178
|
-
try {
|
|
179
|
-
const data = JSON.parse(content);
|
|
180
|
-
return this.flattenObject(data);
|
|
181
|
-
} catch {
|
|
182
|
-
return content;
|
|
437
|
+
extractTitle(text) {
|
|
438
|
+
const h1Match = text.match(/^#\s+(.+)$/m);
|
|
439
|
+
if (h1Match && h1Match[1]) {
|
|
440
|
+
return h1Match[1].replace(/\*\*/g, "").trim();
|
|
183
441
|
}
|
|
442
|
+
const lines = text.split("\n").filter((l) => l.trim().length > 0);
|
|
443
|
+
if (lines.length > 0 && lines[0]) {
|
|
444
|
+
return lines[0].replace(/^#+\s*/, "").replace(/\*\*/g, "").trim().slice(0, 80);
|
|
445
|
+
}
|
|
446
|
+
return "Presentation";
|
|
184
447
|
}
|
|
185
448
|
/**
|
|
186
|
-
*
|
|
449
|
+
* Detect presentation type from content signals
|
|
187
450
|
*/
|
|
188
|
-
|
|
189
|
-
const
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
451
|
+
detectPresentationType(text) {
|
|
452
|
+
const lowerText = text.toLowerCase();
|
|
453
|
+
const scores = {
|
|
454
|
+
ted_keynote: 0,
|
|
455
|
+
sales_pitch: 0,
|
|
456
|
+
consulting_deck: 0,
|
|
457
|
+
investment_banking: 0,
|
|
458
|
+
investor_pitch: 0,
|
|
459
|
+
technical_presentation: 0,
|
|
460
|
+
all_hands: 0
|
|
461
|
+
};
|
|
462
|
+
for (const [type, signals] of Object.entries(this.typeSignals)) {
|
|
463
|
+
for (const signal of signals) {
|
|
464
|
+
if (lowerText.includes(signal)) {
|
|
465
|
+
scores[type]++;
|
|
466
|
+
}
|
|
195
467
|
}
|
|
196
468
|
}
|
|
197
|
-
|
|
469
|
+
let maxScore = 0;
|
|
470
|
+
let detectedType = "consulting_deck";
|
|
471
|
+
for (const [type, score] of Object.entries(scores)) {
|
|
472
|
+
if (score > maxScore) {
|
|
473
|
+
maxScore = score;
|
|
474
|
+
detectedType = type;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
return detectedType;
|
|
198
478
|
}
|
|
199
479
|
/**
|
|
200
|
-
*
|
|
480
|
+
* Extract sections from content (headers, bullets, content)
|
|
201
481
|
*/
|
|
202
|
-
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
482
|
+
extractSections(text) {
|
|
483
|
+
const sections = [];
|
|
484
|
+
const lines = text.split("\n");
|
|
485
|
+
let currentSection = null;
|
|
486
|
+
let contentLines = [];
|
|
487
|
+
for (const line of lines) {
|
|
488
|
+
const trimmedLine = line.trim();
|
|
489
|
+
const headerMatch = trimmedLine.match(/^(#{1,6})\s+(.+)$/);
|
|
490
|
+
if (headerMatch) {
|
|
491
|
+
if (currentSection) {
|
|
492
|
+
currentSection.content = contentLines.join("\n").trim();
|
|
493
|
+
if (currentSection.header || currentSection.content || currentSection.bullets.length > 0) {
|
|
494
|
+
sections.push(currentSection);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
currentSection = {
|
|
498
|
+
header: (headerMatch[2] || "").replace(/\*\*/g, "").trim(),
|
|
499
|
+
level: (headerMatch[1] || "").length,
|
|
500
|
+
content: "",
|
|
501
|
+
bullets: [],
|
|
502
|
+
metrics: []
|
|
503
|
+
};
|
|
504
|
+
contentLines = [];
|
|
505
|
+
continue;
|
|
506
|
+
}
|
|
507
|
+
const bulletMatch = trimmedLine.match(/^[-*+]\s+(.+)$/);
|
|
508
|
+
if (bulletMatch && bulletMatch[1] && currentSection) {
|
|
509
|
+
currentSection.bullets.push(bulletMatch[1]);
|
|
510
|
+
continue;
|
|
511
|
+
}
|
|
512
|
+
const numberedMatch = trimmedLine.match(/^\d+\.\s+(.+)$/);
|
|
513
|
+
if (numberedMatch && numberedMatch[1] && currentSection) {
|
|
514
|
+
currentSection.bullets.push(numberedMatch[1]);
|
|
515
|
+
continue;
|
|
516
|
+
}
|
|
517
|
+
const metricMatch = trimmedLine.match(/\$?([\d,]+\.?\d*)[%]?\s*[-–—:]\s*(.+)/);
|
|
518
|
+
if (metricMatch && metricMatch[1] && metricMatch[2] && currentSection) {
|
|
519
|
+
currentSection.metrics.push({
|
|
520
|
+
value: metricMatch[1],
|
|
521
|
+
label: metricMatch[2].slice(0, 50)
|
|
522
|
+
});
|
|
523
|
+
continue;
|
|
524
|
+
}
|
|
525
|
+
if (trimmedLine.includes("|") && currentSection) {
|
|
526
|
+
const cells = trimmedLine.split("|").map((c) => c.trim()).filter((c) => c && !c.match(/^-+$/));
|
|
527
|
+
if (cells.length >= 2) {
|
|
528
|
+
const value = cells.find((c) => c.match(/^\$?[\d,]+\.?\d*[%]?$/));
|
|
529
|
+
const label = cells.find((c) => !c.match(/^\$?[\d,]+\.?\d*[%]?$/) && c.length > 2);
|
|
530
|
+
if (value && label) {
|
|
531
|
+
currentSection.metrics.push({ value, label: label.slice(0, 50) });
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
continue;
|
|
210
535
|
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
const newPrefix = prefix ? `${prefix}.${key}` : key;
|
|
214
|
-
parts.push(this.flattenObject(value, newPrefix));
|
|
536
|
+
if (trimmedLine && currentSection) {
|
|
537
|
+
contentLines.push(trimmedLine);
|
|
215
538
|
}
|
|
216
|
-
} else if (obj !== null && obj !== void 0) {
|
|
217
|
-
parts.push(String(obj));
|
|
218
539
|
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
return
|
|
226
|
-
}
|
|
227
|
-
/**
|
|
228
|
-
* Split text into sentences.
|
|
229
|
-
*/
|
|
230
|
-
splitIntoSentences(text) {
|
|
231
|
-
const cleaned = text.replace(/Mr\./g, "Mr").replace(/Mrs\./g, "Mrs").replace(/Dr\./g, "Dr").replace(/vs\./g, "vs").replace(/etc\./g, "etc").replace(/e\.g\./g, "eg").replace(/i\.e\./g, "ie");
|
|
232
|
-
return cleaned.split(/[.!?]+/).map((s) => s.trim()).filter((s) => s.length > 10);
|
|
540
|
+
if (currentSection) {
|
|
541
|
+
currentSection.content = contentLines.join("\n").trim();
|
|
542
|
+
if (currentSection.header || currentSection.content || currentSection.bullets.length > 0) {
|
|
543
|
+
sections.push(currentSection);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
return sections;
|
|
233
547
|
}
|
|
234
548
|
/**
|
|
235
|
-
* Extract SCQA structure (Barbara Minto
|
|
549
|
+
* Extract SCQA structure (Barbara Minto)
|
|
236
550
|
*/
|
|
237
|
-
extractSCQA(
|
|
551
|
+
extractSCQA(text) {
|
|
552
|
+
const paragraphs = text.split(/\n\n+/).filter((p) => p.trim());
|
|
238
553
|
let situation = "";
|
|
239
554
|
let complication = "";
|
|
240
555
|
let question = "";
|
|
241
556
|
let answer = "";
|
|
242
557
|
for (const para of paragraphs.slice(0, 3)) {
|
|
243
|
-
if (this.containsSignals(para, this.situationSignals)) {
|
|
244
|
-
situation = this.
|
|
558
|
+
if (this.containsSignals(para.toLowerCase(), this.situationSignals)) {
|
|
559
|
+
situation = this.extractFirstSentence(para);
|
|
245
560
|
break;
|
|
246
561
|
}
|
|
247
562
|
}
|
|
248
563
|
for (const para of paragraphs) {
|
|
249
|
-
if (this.containsSignals(para, this.complicationSignals)) {
|
|
250
|
-
complication = this.
|
|
564
|
+
if (this.containsSignals(para.toLowerCase(), this.complicationSignals)) {
|
|
565
|
+
complication = this.extractFirstSentence(para);
|
|
251
566
|
break;
|
|
252
567
|
}
|
|
253
568
|
}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
569
|
+
const middleStart = Math.floor(paragraphs.length * 0.2);
|
|
570
|
+
const middleEnd = Math.floor(paragraphs.length * 0.8);
|
|
571
|
+
for (const para of paragraphs.slice(middleStart, middleEnd)) {
|
|
572
|
+
const lowerPara = para.toLowerCase();
|
|
573
|
+
if (this.containsSignals(lowerPara, this.answerSignals) && !this.containsSignals(lowerPara, this.ctaSignals)) {
|
|
574
|
+
answer = this.extractFirstSentence(para);
|
|
257
575
|
break;
|
|
258
576
|
}
|
|
259
577
|
}
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
answer = this.extractRelevantSentence(para, this.answerSignals);
|
|
263
|
-
break;
|
|
264
|
-
}
|
|
578
|
+
if (!situation && paragraphs.length > 0 && paragraphs[0]) {
|
|
579
|
+
situation = this.extractFirstSentence(paragraphs[0]);
|
|
265
580
|
}
|
|
266
|
-
if (!
|
|
267
|
-
|
|
581
|
+
if (!answer && paragraphs.length > 2) {
|
|
582
|
+
for (const para of paragraphs.slice(1, Math.floor(paragraphs.length * 0.5))) {
|
|
583
|
+
const lowerPara = para.toLowerCase();
|
|
584
|
+
if (lowerPara.includes("recommend") || lowerPara.includes("strategy") || lowerPara.includes("solution") || lowerPara.includes("approach")) {
|
|
585
|
+
answer = this.extractFirstSentence(para);
|
|
586
|
+
break;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
268
589
|
}
|
|
269
|
-
if (
|
|
270
|
-
|
|
271
|
-
answer = lastPara ? this.truncateToSentence(lastPara, 150) : "";
|
|
590
|
+
if (complication && !question) {
|
|
591
|
+
question = `How do we address: ${complication.slice(0, 80)}?`;
|
|
272
592
|
}
|
|
273
593
|
return { situation, complication, question, answer };
|
|
274
594
|
}
|
|
275
595
|
/**
|
|
276
|
-
* Extract
|
|
596
|
+
* Extract STAR moments (Something They'll Always Remember)
|
|
597
|
+
* Per Nancy Duarte: These should be memorable, COMPLETE thoughts with impact
|
|
598
|
+
*
|
|
599
|
+
* CRITICAL REQUIREMENTS:
|
|
600
|
+
* - Must be complete sentences with subject + verb structure
|
|
601
|
+
* - Must have 5+ words minimum (no fragments!)
|
|
602
|
+
* - Must be 30+ characters
|
|
603
|
+
* - Must contain a verb
|
|
604
|
+
* - NOT fragments like "significant growth" or "cloud-first strategy"
|
|
605
|
+
* - NOT headers or topic labels
|
|
277
606
|
*/
|
|
278
|
-
|
|
279
|
-
const
|
|
280
|
-
const
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
607
|
+
extractStarMoments(text) {
|
|
608
|
+
const starMoments = [];
|
|
609
|
+
const usedPhrases = /* @__PURE__ */ new Set();
|
|
610
|
+
const fragmentPatterns = [
|
|
611
|
+
/^[a-z]+-[a-z]+\s+(strategy|approach|solution|platform|architecture)$/i,
|
|
612
|
+
// "cloud-first strategy"
|
|
613
|
+
/^(significant|substantial|dramatic|rapid|strong)\s+(growth|increase|decline|change)$/i,
|
|
614
|
+
// "significant growth"
|
|
615
|
+
/^(digital|cloud|data|ai)\s+(transformation|strategy|platform|solution)$/i,
|
|
616
|
+
// "digital transformation"
|
|
617
|
+
/^(our|your|the|a)\s+\w+\s*$/i,
|
|
618
|
+
// "our solution", "the platform"
|
|
619
|
+
/^[a-z]+\s+[a-z]+$/i
|
|
620
|
+
// Any two-word phrase
|
|
621
|
+
];
|
|
622
|
+
const verbPatterns = /\b(is|are|was|were|will|can|could|should|must|has|have|had|does|do|provides?|enables?|allows?|offers?|delivers?|creates?|achieves?|exceeds?|results?|generates?|grows?|increases?|decreases?|transforms?|improves?|reduces?)\b/i;
|
|
623
|
+
const boldMatches = text.match(/\*\*([^*]+)\*\*/g);
|
|
624
|
+
if (boldMatches) {
|
|
625
|
+
for (const match of boldMatches) {
|
|
626
|
+
const cleaned = match.replace(/\*\*/g, "").trim();
|
|
627
|
+
const lowerCleaned = cleaned.toLowerCase();
|
|
628
|
+
const wordCount = cleaned.split(/\s+/).length;
|
|
629
|
+
const normalized = cleaned.toLowerCase().slice(0, 30);
|
|
630
|
+
const hasVerb = verbPatterns.test(cleaned);
|
|
631
|
+
const isLongEnough = wordCount >= 5 && cleaned.length >= 30;
|
|
632
|
+
const isNotFragment = !fragmentPatterns.some((p) => p.test(cleaned));
|
|
633
|
+
const isNotHeader = !/^(The |Our |Your |Next |Key |Overview|Introduction|Conclusion|Summary|Background)/i.test(cleaned);
|
|
634
|
+
const isNotCTA = !this.containsSignals(lowerCleaned, this.ctaSignals);
|
|
635
|
+
const hasMetricContext = /\d+[%xX]|\$[\d,]+/.test(cleaned) && wordCount >= 5;
|
|
636
|
+
if ((hasVerb || hasMetricContext) && isLongEnough && isNotFragment && isNotHeader && isNotCTA && wordCount <= 25 && !usedPhrases.has(normalized)) {
|
|
637
|
+
starMoments.push(cleaned);
|
|
638
|
+
usedPhrases.add(normalized);
|
|
639
|
+
if (starMoments.length >= 3) break;
|
|
640
|
+
}
|
|
286
641
|
}
|
|
287
|
-
|
|
288
|
-
|
|
642
|
+
}
|
|
643
|
+
if (starMoments.length < 3) {
|
|
644
|
+
const cleanedText = text.replace(/^#+\s+.+$/gm, "").replace(/\*\*/g, "").replace(/\|/g, " ").replace(/-{3,}/g, "").replace(/\n{2,}/g, "\n").trim();
|
|
645
|
+
const sentences = cleanedText.match(/[^.!?]+[.!?]/g) || [];
|
|
646
|
+
for (const sentence of sentences) {
|
|
647
|
+
const cleaned = sentence.trim();
|
|
648
|
+
const normalized = cleaned.toLowerCase().slice(0, 30);
|
|
649
|
+
const wordCount = cleaned.split(/\s+/).length;
|
|
650
|
+
const hasHighImpact = /(\d{2,3}%|\d+x|billion|million|\$[\d,]+\s*(million|billion)?)/i.test(cleaned);
|
|
651
|
+
const hasVerb = verbPatterns.test(cleaned);
|
|
652
|
+
if (hasHighImpact && hasVerb && wordCount >= 6 && wordCount <= 25 && cleaned.length >= 40 && cleaned.length <= 200 && !usedPhrases.has(normalized) && !this.containsSignals(cleaned.toLowerCase(), this.ctaSignals)) {
|
|
653
|
+
starMoments.push(cleaned);
|
|
654
|
+
usedPhrases.add(normalized);
|
|
655
|
+
if (starMoments.length >= 3) break;
|
|
656
|
+
}
|
|
289
657
|
}
|
|
290
658
|
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
659
|
+
return starMoments.slice(0, 3);
|
|
660
|
+
}
|
|
661
|
+
/**
|
|
662
|
+
* Extract Sparkline elements (Nancy Duarte)
|
|
663
|
+
*/
|
|
664
|
+
extractSparkline(text) {
|
|
665
|
+
const paragraphs = text.split(/\n\n+/).filter((p) => p.trim());
|
|
666
|
+
let callToAction = "";
|
|
667
|
+
const ctaSignals = ["next steps", "call to action", "take action", "start now", "join us", "contact"];
|
|
668
|
+
for (const para of paragraphs.slice(-3)) {
|
|
669
|
+
if (this.containsSignals(para.toLowerCase(), ctaSignals)) {
|
|
670
|
+
callToAction = this.extractFirstSentence(para);
|
|
294
671
|
break;
|
|
295
672
|
}
|
|
296
673
|
}
|
|
297
|
-
return {
|
|
674
|
+
return { callToAction };
|
|
298
675
|
}
|
|
299
676
|
/**
|
|
300
|
-
* Extract key messages
|
|
677
|
+
* Extract key messages - the actual insights from the content
|
|
678
|
+
* Per Carmine Gallo: Rule of Three - max 3 key messages
|
|
679
|
+
* Per Barbara Minto: Messages should be actionable conclusions, not topics
|
|
301
680
|
*/
|
|
302
|
-
extractKeyMessages(text
|
|
681
|
+
extractKeyMessages(text) {
|
|
303
682
|
const messages = [];
|
|
304
|
-
const
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
683
|
+
const usedPhrases = /* @__PURE__ */ new Set();
|
|
684
|
+
let cleanText = text.replace(/^#+\s+.+$/gm, "").replace(/\[.+?\]/g, "").replace(/\*\*/g, "");
|
|
685
|
+
cleanText = cleanText.replace(/(\d)\.(\d)/g, "$1<DECIMAL>$2");
|
|
686
|
+
const rawSentences = cleanText.match(/[^.!?]+[.!?]/g) || [];
|
|
687
|
+
const sentences = rawSentences.map((s) => s.replace(/<DECIMAL>/g, "."));
|
|
688
|
+
const insightSignals = [
|
|
689
|
+
"we recommend",
|
|
690
|
+
"we suggest",
|
|
691
|
+
"our strategy",
|
|
692
|
+
"the solution",
|
|
693
|
+
"key finding",
|
|
694
|
+
"result",
|
|
695
|
+
"achieve",
|
|
696
|
+
"improve",
|
|
697
|
+
"increase",
|
|
698
|
+
"decrease",
|
|
699
|
+
"expect",
|
|
700
|
+
"roi",
|
|
701
|
+
"benefit",
|
|
702
|
+
"opportunity",
|
|
703
|
+
"transform"
|
|
704
|
+
];
|
|
705
|
+
for (const sentence of sentences) {
|
|
706
|
+
const cleaned = sentence.trim();
|
|
707
|
+
const normalized = cleaned.toLowerCase().slice(0, 30);
|
|
708
|
+
if (cleaned.length > 30 && cleaned.length < 150 && !usedPhrases.has(normalized) && !this.containsSignals(cleaned.toLowerCase(), this.ctaSignals)) {
|
|
709
|
+
if (this.containsSignals(cleaned.toLowerCase(), insightSignals)) {
|
|
710
|
+
messages.push(cleaned);
|
|
711
|
+
usedPhrases.add(normalized);
|
|
712
|
+
if (messages.length >= 3) break;
|
|
319
713
|
}
|
|
320
714
|
}
|
|
321
715
|
}
|
|
322
716
|
if (messages.length < 3) {
|
|
323
|
-
const
|
|
324
|
-
|
|
325
|
-
|
|
717
|
+
for (const sentence of sentences) {
|
|
718
|
+
const cleaned = sentence.trim();
|
|
719
|
+
const normalized = cleaned.toLowerCase().slice(0, 30);
|
|
720
|
+
if (cleaned.length > 25 && cleaned.length < 150 && !usedPhrases.has(normalized) && /\d+[%xX]|\$[\d,]+/.test(cleaned)) {
|
|
721
|
+
messages.push(cleaned);
|
|
722
|
+
usedPhrases.add(normalized);
|
|
723
|
+
if (messages.length >= 3) break;
|
|
724
|
+
}
|
|
326
725
|
}
|
|
327
726
|
}
|
|
328
|
-
if (messages.length <
|
|
727
|
+
if (messages.length < 2) {
|
|
329
728
|
for (const sentence of sentences) {
|
|
330
|
-
|
|
331
|
-
|
|
729
|
+
const cleaned = sentence.trim();
|
|
730
|
+
const wordCount = cleaned.split(/\s+/).length;
|
|
731
|
+
const normalized = cleaned.toLowerCase().slice(0, 30);
|
|
732
|
+
if (wordCount >= 6 && wordCount <= 25 && !usedPhrases.has(normalized) && /\b(is|are|will|can|should|must|has|have|provides?|enables?|allows?)\b/.test(cleaned.toLowerCase())) {
|
|
733
|
+
messages.push(cleaned);
|
|
734
|
+
usedPhrases.add(normalized);
|
|
332
735
|
if (messages.length >= 3) break;
|
|
333
736
|
}
|
|
334
737
|
}
|
|
@@ -336,195 +739,253 @@ var ContentAnalyzer = class {
|
|
|
336
739
|
return messages.slice(0, 3);
|
|
337
740
|
}
|
|
338
741
|
/**
|
|
339
|
-
*
|
|
742
|
+
* Extract data points (metrics with values)
|
|
743
|
+
* IMPROVED: Smarter label extraction that understands markdown tables
|
|
340
744
|
*/
|
|
341
|
-
|
|
342
|
-
const
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
745
|
+
extractDataPoints(text) {
|
|
746
|
+
const dataPoints = [];
|
|
747
|
+
const usedValues = /* @__PURE__ */ new Set();
|
|
748
|
+
const tableLines = text.split("\n").filter((line) => line.includes("|"));
|
|
749
|
+
if (tableLines.length >= 3) {
|
|
750
|
+
const dataRows = tableLines.filter((line) => !line.match(/^[\s|:-]+$/));
|
|
751
|
+
if (dataRows.length >= 2) {
|
|
752
|
+
for (const row of dataRows.slice(1)) {
|
|
753
|
+
const cells = row.split("|").map((c) => c.trim()).filter((c) => c.length > 0);
|
|
754
|
+
if (cells.length >= 2) {
|
|
755
|
+
const valueCell = cells.find((c) => /^\$?[\d,]+\.?\d*[MBK%]?$/.test(c.replace(/[,$]/g, "")));
|
|
756
|
+
const labelCell = cells[0];
|
|
757
|
+
if (valueCell && labelCell && !usedValues.has(valueCell)) {
|
|
758
|
+
usedValues.add(valueCell);
|
|
759
|
+
dataPoints.push({ value: valueCell, label: labelCell.slice(0, 40) });
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
}
|
|
347
763
|
}
|
|
348
764
|
}
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
765
|
+
const lines = text.split("\n");
|
|
766
|
+
for (const line of lines) {
|
|
767
|
+
if (line.includes("|")) continue;
|
|
768
|
+
const percentMatch = line.match(/(\w+(?:\s+\w+){0,4})\s+(?:by\s+)?(\d+(?:\.\d+)?%)/i);
|
|
769
|
+
if (percentMatch && percentMatch[2] && percentMatch[1] && !usedValues.has(percentMatch[2])) {
|
|
770
|
+
usedValues.add(percentMatch[2]);
|
|
771
|
+
dataPoints.push({ value: percentMatch[2], label: percentMatch[1].slice(0, 40) });
|
|
772
|
+
}
|
|
773
|
+
const dollarMatch = line.match(/\$(\d+(?:\.\d+)?)\s*(million|billion|M|B|K)?/i);
|
|
774
|
+
if (dollarMatch && dataPoints.length < 6) {
|
|
775
|
+
const fullValue = "$" + dollarMatch[1] + (dollarMatch[2] ? " " + dollarMatch[2] : "");
|
|
776
|
+
if (!usedValues.has(fullValue)) {
|
|
777
|
+
usedValues.add(fullValue);
|
|
778
|
+
const label = this.extractLabelFromLine(line, fullValue);
|
|
779
|
+
dataPoints.push({ value: fullValue, label });
|
|
356
780
|
}
|
|
357
781
|
}
|
|
358
782
|
}
|
|
359
|
-
return
|
|
783
|
+
return dataPoints.slice(0, 4);
|
|
360
784
|
}
|
|
361
785
|
/**
|
|
362
|
-
*
|
|
786
|
+
* Extract a meaningful label from a line containing a metric
|
|
363
787
|
*/
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
const
|
|
367
|
-
const
|
|
368
|
-
|
|
369
|
-
return this.capitalizeFirst(title);
|
|
370
|
-
}
|
|
371
|
-
if (title.toLowerCase().includes("should")) {
|
|
372
|
-
title = title.replace(/we should|you should|should/gi, "").trim();
|
|
373
|
-
return this.capitalizeFirst(title);
|
|
374
|
-
}
|
|
375
|
-
if (title.toLowerCase().includes("need to")) {
|
|
376
|
-
title = title.replace(/we need to|you need to|need to/gi, "").trim();
|
|
377
|
-
return this.capitalizeFirst(title);
|
|
378
|
-
}
|
|
379
|
-
if (title.length < 50) {
|
|
380
|
-
return title;
|
|
381
|
-
}
|
|
382
|
-
return this.truncateToWords(title, 8);
|
|
788
|
+
extractLabelFromLine(line, value) {
|
|
789
|
+
const cleaned = line.replace(/\*\*/g, "").replace(/\|/g, " ").trim();
|
|
790
|
+
const beforeValue = cleaned.split(value)[0] || "";
|
|
791
|
+
const words = beforeValue.split(/\s+/).filter((w) => w.length > 2);
|
|
792
|
+
return words.slice(-4).join(" ").slice(0, 40) || "Value";
|
|
383
793
|
}
|
|
384
794
|
/**
|
|
385
|
-
*
|
|
795
|
+
* Check if text contains any of the signals
|
|
386
796
|
*/
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
const starSignals = [
|
|
390
|
-
"surprising",
|
|
391
|
-
"amazing",
|
|
392
|
-
"incredible",
|
|
393
|
-
"remarkable",
|
|
394
|
-
"stunning",
|
|
395
|
-
"imagine",
|
|
396
|
-
"what if",
|
|
397
|
-
"breakthrough",
|
|
398
|
-
"revolutionary",
|
|
399
|
-
"never before",
|
|
400
|
-
"first time",
|
|
401
|
-
"unprecedented",
|
|
402
|
-
"game-changing",
|
|
403
|
-
"dramatic"
|
|
404
|
-
];
|
|
405
|
-
for (const para of paragraphs) {
|
|
406
|
-
if (this.containsSignals(para.toLowerCase(), starSignals)) {
|
|
407
|
-
const moment = this.truncateToSentence(para, 120);
|
|
408
|
-
if (moment.length > 20) {
|
|
409
|
-
starMoments.push(moment);
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
const statPattern = /\d+[%xX]|\$[\d,]+(?:\s*(?:million|billion|trillion))?|\d+(?:\s*(?:million|billion|trillion))/g;
|
|
414
|
-
for (const para of paragraphs) {
|
|
415
|
-
if (statPattern.test(para) && starMoments.length < 5) {
|
|
416
|
-
const moment = this.truncateToSentence(para, 100);
|
|
417
|
-
if (!starMoments.includes(moment)) {
|
|
418
|
-
starMoments.push(moment);
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
return starMoments.slice(0, 5);
|
|
797
|
+
containsSignals(text, signals) {
|
|
798
|
+
return signals.some((signal) => text.includes(signal));
|
|
423
799
|
}
|
|
424
800
|
/**
|
|
425
|
-
*
|
|
801
|
+
* Extract first meaningful sentence from text
|
|
802
|
+
* CRITICAL: Must strip headers, CTA text, and fragments
|
|
426
803
|
*/
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
const
|
|
431
|
-
const wordBasedEstimate = Math.ceil(wordCount / 35);
|
|
432
|
-
const estimate = Math.max(
|
|
433
|
-
5,
|
|
434
|
-
// Minimum 5 slides
|
|
435
|
-
Math.ceil((wordBasedEstimate + headerCount + bulletGroups) / 2)
|
|
436
|
-
);
|
|
437
|
-
return Math.min(estimate, 30);
|
|
438
|
-
}
|
|
439
|
-
// === Helper Methods ===
|
|
440
|
-
containsSignals(text, signals) {
|
|
441
|
-
const lowerText = text.toLowerCase();
|
|
442
|
-
return signals.some((signal) => lowerText.includes(signal));
|
|
443
|
-
}
|
|
444
|
-
extractRelevantSentence(paragraph, signals) {
|
|
445
|
-
const sentences = paragraph.split(/[.!?]+/);
|
|
804
|
+
extractFirstSentence(text) {
|
|
805
|
+
let cleaned = text.replace(/^#+\s+.+$/gm, "").replace(/\*\*/g, "").replace(/\|/g, " ").replace(/-{3,}/g, "").replace(/\n{2,}/g, "\n").trim();
|
|
806
|
+
cleaned = cleaned.replace(/^(Overview|Introduction|The Problem|Our Solution|Next Steps|Conclusion|Summary|Background|Context|Recommendation)\s*:?\s*/i, "").trim();
|
|
807
|
+
const sentences = cleaned.match(/[^.!?]+[.!?]/g) || [];
|
|
446
808
|
for (const sentence of sentences) {
|
|
447
|
-
|
|
448
|
-
|
|
809
|
+
const trimmed = sentence.trim();
|
|
810
|
+
if (trimmed.length >= 20 && /\b(is|are|was|were|will|can|should|must|has|have|had|provides?|enables?|allows?|offers?|delivers?|creates?|includes?|involves?|requires?|experiencing)\b/i.test(trimmed) && !this.containsSignals(trimmed.toLowerCase(), this.ctaSignals)) {
|
|
811
|
+
return trimmed.slice(0, 150);
|
|
449
812
|
}
|
|
450
813
|
}
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
truncateToSentence(text, maxLength) {
|
|
454
|
-
if (text.length <= maxLength) {
|
|
455
|
-
return text.trim();
|
|
456
|
-
}
|
|
457
|
-
const truncated = text.slice(0, maxLength);
|
|
458
|
-
const lastPeriod = truncated.lastIndexOf(".");
|
|
459
|
-
const lastQuestion = truncated.lastIndexOf("?");
|
|
460
|
-
const lastExclaim = truncated.lastIndexOf("!");
|
|
461
|
-
const lastBoundary = Math.max(lastPeriod, lastQuestion, lastExclaim);
|
|
462
|
-
if (lastBoundary > maxLength * 0.5) {
|
|
463
|
-
return text.slice(0, lastBoundary + 1).trim();
|
|
464
|
-
}
|
|
465
|
-
return truncated.trim() + "...";
|
|
466
|
-
}
|
|
467
|
-
truncateToWords(text, maxWords) {
|
|
468
|
-
const words = text.split(/\s+/);
|
|
469
|
-
if (words.length <= maxWords) {
|
|
470
|
-
return text;
|
|
471
|
-
}
|
|
472
|
-
return words.slice(0, maxWords).join(" ");
|
|
473
|
-
}
|
|
474
|
-
capitalizeFirst(text) {
|
|
475
|
-
if (!text) return "";
|
|
476
|
-
return text.charAt(0).toUpperCase() + text.slice(1);
|
|
814
|
+
const fallback = cleaned.slice(0, 150);
|
|
815
|
+
return fallback.length >= 20 ? fallback : "";
|
|
477
816
|
}
|
|
478
817
|
};
|
|
479
818
|
|
|
480
819
|
// src/core/SlideFactory.ts
|
|
481
820
|
var SlideFactory = class {
|
|
482
821
|
templates;
|
|
822
|
+
usedContent = /* @__PURE__ */ new Set();
|
|
483
823
|
constructor() {
|
|
484
824
|
this.templates = this.initializeTemplates();
|
|
485
825
|
}
|
|
826
|
+
/**
|
|
827
|
+
* Check if content has already been used (deduplication)
|
|
828
|
+
*/
|
|
829
|
+
isContentUsed(content) {
|
|
830
|
+
if (!content) return true;
|
|
831
|
+
const normalized = content.toLowerCase().replace(/[^a-z0-9]/g, "").slice(0, 50);
|
|
832
|
+
if (this.usedContent.has(normalized)) {
|
|
833
|
+
return true;
|
|
834
|
+
}
|
|
835
|
+
this.usedContent.add(normalized);
|
|
836
|
+
return false;
|
|
837
|
+
}
|
|
486
838
|
/**
|
|
487
839
|
* Create slides from analyzed content.
|
|
840
|
+
*
|
|
841
|
+
* ARCHITECTURE (per KB expert methodologies):
|
|
842
|
+
* 1. Title slide - always first
|
|
843
|
+
* 2. Agenda slide - business mode only with 3+ sections
|
|
844
|
+
* 3. SCQA slides - Situation, Complication (per Minto)
|
|
845
|
+
* 4. Content slides - from sections with bullets/content
|
|
846
|
+
* 5. Metrics slide - if data points exist
|
|
847
|
+
* 6. Solution slide - SCQA answer
|
|
848
|
+
* 7. STAR moments - only if high-quality (per Duarte)
|
|
849
|
+
* 8. CTA slide - if call to action exists
|
|
850
|
+
* 9. Thank you slide - always last
|
|
488
851
|
*/
|
|
489
852
|
async createSlides(analysis, mode) {
|
|
490
853
|
const slides = [];
|
|
491
854
|
let slideIndex = 0;
|
|
855
|
+
this.usedContent.clear();
|
|
492
856
|
slides.push(this.createTitleSlide(slideIndex++, analysis));
|
|
493
|
-
|
|
857
|
+
this.isContentUsed(analysis.titles[0] ?? "");
|
|
858
|
+
const substantiveSections = analysis.sections.filter(
|
|
859
|
+
(s) => s.level === 2 && (s.bullets.length > 0 || s.content.length > 50)
|
|
860
|
+
);
|
|
861
|
+
if (mode === "business" && substantiveSections.length >= 3) {
|
|
494
862
|
slides.push(this.createAgendaSlide(slideIndex++, analysis));
|
|
495
863
|
}
|
|
496
|
-
if (analysis.scqa.situation) {
|
|
864
|
+
if (analysis.scqa.situation && analysis.scqa.situation.length > 30 && !this.isContentUsed(analysis.scqa.situation)) {
|
|
497
865
|
slides.push(this.createContextSlide(slideIndex++, analysis, mode));
|
|
498
866
|
}
|
|
499
|
-
if (analysis.scqa.complication) {
|
|
867
|
+
if (analysis.scqa.complication && analysis.scqa.complication.length > 30 && !this.isContentUsed(analysis.scqa.complication)) {
|
|
500
868
|
slides.push(this.createProblemSlide(slideIndex++, analysis, mode));
|
|
501
869
|
}
|
|
870
|
+
for (const section of substantiveSections.slice(0, 4)) {
|
|
871
|
+
const headerUsed = this.isContentUsed(section.header);
|
|
872
|
+
const contentUsed = section.content.length > 30 && this.isContentUsed(section.content.slice(0, 80));
|
|
873
|
+
if (!headerUsed && !contentUsed) {
|
|
874
|
+
if (section.bullets.length > 0) {
|
|
875
|
+
slides.push(this.createSectionBulletSlide(slideIndex++, section, mode));
|
|
876
|
+
} else if (section.content.length > 50) {
|
|
877
|
+
slides.push(this.createSectionContentSlide(slideIndex++, section, mode));
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
}
|
|
502
881
|
for (const message of analysis.keyMessages) {
|
|
503
|
-
|
|
882
|
+
const wordCount = message.split(/\s+/).length;
|
|
883
|
+
if (wordCount >= 6 && !/^(The |Our |Your |Overview|Introduction|Conclusion)/i.test(message) && !this.isContentUsed(message)) {
|
|
884
|
+
slides.push(this.createMessageSlide(slideIndex++, message, mode));
|
|
885
|
+
}
|
|
504
886
|
}
|
|
505
|
-
|
|
506
|
-
slides.push(this.
|
|
887
|
+
if (analysis.dataPoints.length >= 2) {
|
|
888
|
+
slides.push(this.createMetricsSlide(slideIndex++, analysis.dataPoints));
|
|
507
889
|
}
|
|
508
|
-
if (analysis.scqa.answer) {
|
|
890
|
+
if (analysis.scqa.answer && analysis.scqa.answer.length > 30 && !this.isContentUsed(analysis.scqa.answer)) {
|
|
509
891
|
slides.push(this.createSolutionSlide(slideIndex++, analysis, mode));
|
|
510
892
|
}
|
|
511
|
-
|
|
893
|
+
const verbPattern = /\b(is|are|was|were|will|can|should|must|has|have|provides?|enables?|allows?|achieves?|exceeds?|results?|generates?|delivers?|creates?)\b/i;
|
|
894
|
+
for (const starMoment of analysis.starMoments.slice(0, 2)) {
|
|
895
|
+
const wordCount = starMoment.split(/\s+/).length;
|
|
896
|
+
const hasVerb = verbPattern.test(starMoment);
|
|
897
|
+
if (wordCount >= 6 && starMoment.length >= 40 && hasVerb && !this.isContentUsed(starMoment)) {
|
|
898
|
+
slides.push(this.createStarMomentSlide(slideIndex++, starMoment, mode));
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
if (analysis.sparkline.callToAdventure && analysis.sparkline.callToAdventure.length > 20 && !this.isContentUsed(analysis.sparkline.callToAdventure)) {
|
|
512
902
|
slides.push(this.createCTASlide(slideIndex++, analysis, mode));
|
|
513
903
|
}
|
|
514
904
|
slides.push(this.createThankYouSlide(slideIndex++));
|
|
515
905
|
return slides;
|
|
516
906
|
}
|
|
907
|
+
/**
|
|
908
|
+
* Create a slide from a section with bullets
|
|
909
|
+
*/
|
|
910
|
+
createSectionBulletSlide(index, section, mode) {
|
|
911
|
+
const bullets = section.bullets.slice(0, mode === "keynote" ? 3 : 5);
|
|
912
|
+
return {
|
|
913
|
+
index,
|
|
914
|
+
type: "bullet-points",
|
|
915
|
+
data: {
|
|
916
|
+
title: this.truncate(section.header, 60),
|
|
917
|
+
bullets: bullets.map((b) => this.cleanText(b).slice(0, 80))
|
|
918
|
+
},
|
|
919
|
+
classes: ["slide-bullet-points"]
|
|
920
|
+
};
|
|
921
|
+
}
|
|
922
|
+
/**
|
|
923
|
+
* Create a slide from a section with body content
|
|
924
|
+
*/
|
|
925
|
+
createSectionContentSlide(index, section, mode) {
|
|
926
|
+
if (mode === "keynote") {
|
|
927
|
+
return {
|
|
928
|
+
index,
|
|
929
|
+
type: "single-statement",
|
|
930
|
+
data: {
|
|
931
|
+
title: this.truncate(this.extractFirstSentence(section.content), 80),
|
|
932
|
+
keyMessage: section.header
|
|
933
|
+
},
|
|
934
|
+
classes: ["slide-single-statement"]
|
|
935
|
+
};
|
|
936
|
+
}
|
|
937
|
+
return {
|
|
938
|
+
index,
|
|
939
|
+
type: "two-column",
|
|
940
|
+
data: {
|
|
941
|
+
title: this.truncate(section.header, 60),
|
|
942
|
+
body: this.truncate(section.content, 200)
|
|
943
|
+
},
|
|
944
|
+
classes: ["slide-two-column"]
|
|
945
|
+
};
|
|
946
|
+
}
|
|
947
|
+
/**
|
|
948
|
+
* Extract first sentence from text
|
|
949
|
+
*/
|
|
950
|
+
extractFirstSentence(text) {
|
|
951
|
+
const cleaned = this.cleanText(text);
|
|
952
|
+
const match = cleaned.match(/^[^.!?]+[.!?]/);
|
|
953
|
+
return match ? match[0].trim() : cleaned.slice(0, 100);
|
|
954
|
+
}
|
|
955
|
+
/**
|
|
956
|
+
* Create a metrics slide from data points
|
|
957
|
+
*/
|
|
958
|
+
createMetricsSlide(index, dataPoints) {
|
|
959
|
+
const metrics = dataPoints.slice(0, 4).map((dp) => ({
|
|
960
|
+
value: dp.value,
|
|
961
|
+
label: this.cleanText(dp.label).slice(0, 40)
|
|
962
|
+
}));
|
|
963
|
+
return {
|
|
964
|
+
index,
|
|
965
|
+
type: "metrics-grid",
|
|
966
|
+
data: {
|
|
967
|
+
title: "Key Metrics",
|
|
968
|
+
metrics
|
|
969
|
+
},
|
|
970
|
+
classes: ["slide-metrics-grid"]
|
|
971
|
+
};
|
|
972
|
+
}
|
|
517
973
|
/**
|
|
518
974
|
* Create a title slide.
|
|
519
975
|
*/
|
|
520
976
|
createTitleSlide(index, analysis) {
|
|
521
|
-
|
|
977
|
+
let subtitle = "";
|
|
978
|
+
if (analysis.scqa.answer && analysis.scqa.answer.length > 10) {
|
|
979
|
+
subtitle = analysis.scqa.answer;
|
|
980
|
+
} else if (analysis.starMoments.length > 0 && analysis.starMoments[0]) {
|
|
981
|
+
subtitle = analysis.starMoments[0];
|
|
982
|
+
}
|
|
522
983
|
return {
|
|
523
984
|
index,
|
|
524
985
|
type: "title",
|
|
525
986
|
data: {
|
|
526
987
|
title: analysis.titles[0] ?? "Presentation",
|
|
527
|
-
subtitle: this.truncate(subtitle,
|
|
988
|
+
subtitle: this.truncate(subtitle, 80),
|
|
528
989
|
keyMessage: analysis.scqa.answer
|
|
529
990
|
},
|
|
530
991
|
classes: ["slide-title"]
|
|
@@ -805,23 +1266,43 @@ var SlideFactory = class {
|
|
|
805
1266
|
}
|
|
806
1267
|
// === Helper Methods ===
|
|
807
1268
|
/**
|
|
808
|
-
* Clean text by removing all content markers.
|
|
1269
|
+
* Clean text by removing all markdown and content markers.
|
|
1270
|
+
* CRITICAL: Must strip all formatting to prevent garbage in slides
|
|
809
1271
|
*/
|
|
810
1272
|
cleanText(text) {
|
|
811
1273
|
if (!text) return "";
|
|
812
|
-
return text.replace(/\[HEADER\]\s*/g, "").replace(/\[BULLET\]\s*/g, "").replace(/\[NUMBERED\]\s*/g, "").replace(/\[EMPHASIS\]/g, "").replace(/\[\/EMPHASIS\]/g, "").replace(/\[CODE BLOCK\]/g, "").replace(/\[IMAGE\]/g, "").replace(/\s+/g, " ").trim();
|
|
1274
|
+
return text.replace(/^#+\s+/gm, "").replace(/\*\*([^*]+)\*\*/g, "$1").replace(/\*([^*]+)\*/g, "$1").replace(/__([^_]+)__/g, "$1").replace(/_([^_]+)_/g, "$1").replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/\|/g, " ").replace(/-{3,}/g, "").replace(/\[HEADER\]\s*/g, "").replace(/\[BULLET\]\s*/g, "").replace(/\[NUMBERED\]\s*/g, "").replace(/\[EMPHASIS\]/g, "").replace(/\[\/EMPHASIS\]/g, "").replace(/\[CODE BLOCK\]/g, "").replace(/\[IMAGE\]/g, "").replace(/\n{2,}/g, " ").replace(/\s+/g, " ").trim();
|
|
813
1275
|
}
|
|
814
1276
|
/**
|
|
815
|
-
* Truncate text to max length at
|
|
1277
|
+
* Truncate text to max length at sentence boundary when possible.
|
|
1278
|
+
* CRITICAL: Never cut mid-number (99.5% should not become 99.)
|
|
816
1279
|
*/
|
|
817
1280
|
truncate(text, maxLength) {
|
|
818
1281
|
const cleanedText = this.cleanText(text);
|
|
819
1282
|
if (!cleanedText || cleanedText.length <= maxLength) {
|
|
820
1283
|
return cleanedText;
|
|
821
1284
|
}
|
|
1285
|
+
const sentences = cleanedText.match(/[^.!?]+[.!?]/g);
|
|
1286
|
+
if (sentences) {
|
|
1287
|
+
let result = "";
|
|
1288
|
+
for (const sentence of sentences) {
|
|
1289
|
+
if ((result + sentence).length <= maxLength) {
|
|
1290
|
+
result += sentence;
|
|
1291
|
+
} else {
|
|
1292
|
+
break;
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
if (result.length > maxLength * 0.5) {
|
|
1296
|
+
return result.trim();
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
822
1299
|
const truncated = cleanedText.slice(0, maxLength);
|
|
823
|
-
|
|
824
|
-
|
|
1300
|
+
let lastSpace = truncated.lastIndexOf(" ");
|
|
1301
|
+
const afterCut = cleanedText.slice(lastSpace + 1, maxLength + 10);
|
|
1302
|
+
if (/^[\d.,%$]+/.test(afterCut)) {
|
|
1303
|
+
lastSpace = truncated.slice(0, lastSpace).lastIndexOf(" ");
|
|
1304
|
+
}
|
|
1305
|
+
if (lastSpace > maxLength * 0.5) {
|
|
825
1306
|
return truncated.slice(0, lastSpace) + "...";
|
|
826
1307
|
}
|
|
827
1308
|
return truncated + "...";
|
|
@@ -852,10 +1333,12 @@ var SlideFactory = class {
|
|
|
852
1333
|
return sentences.slice(0, 5).map((s) => s.trim());
|
|
853
1334
|
}
|
|
854
1335
|
/**
|
|
855
|
-
* Remove a statistic from text.
|
|
1336
|
+
* Remove a statistic from text and clean thoroughly.
|
|
856
1337
|
*/
|
|
857
1338
|
removeStatistic(text, stat) {
|
|
858
|
-
|
|
1339
|
+
const cleaned = this.cleanText(text).replace(stat, "").replace(/^\s*[-–—:,]\s*/, "").trim();
|
|
1340
|
+
const firstSentence = cleaned.match(/^[^.!?]+[.!?]?/);
|
|
1341
|
+
return firstSentence ? firstSentence[0].slice(0, 80) : cleaned.slice(0, 80);
|
|
859
1342
|
}
|
|
860
1343
|
};
|
|
861
1344
|
|
|
@@ -1755,19 +2238,28 @@ var ScoreCalculator = class {
|
|
|
1755
2238
|
import { chromium } from "playwright";
|
|
1756
2239
|
var QAEngine = class {
|
|
1757
2240
|
browser = null;
|
|
2241
|
+
kb;
|
|
1758
2242
|
/**
|
|
1759
|
-
*
|
|
2243
|
+
* Initialize KnowledgeGateway
|
|
2244
|
+
*/
|
|
2245
|
+
async initialize() {
|
|
2246
|
+
this.kb = await getKnowledgeGateway();
|
|
2247
|
+
}
|
|
2248
|
+
/**
|
|
2249
|
+
* Validate a presentation using KB-driven quality metrics
|
|
1760
2250
|
*/
|
|
1761
2251
|
async validate(presentation, options) {
|
|
2252
|
+
await this.initialize();
|
|
1762
2253
|
const html = typeof presentation === "string" ? presentation : presentation.toString("utf-8");
|
|
1763
2254
|
const mode = options?.mode ?? "keynote";
|
|
2255
|
+
const qualityMetrics = this.kb.getQualityMetrics();
|
|
1764
2256
|
await this.initBrowser();
|
|
1765
2257
|
try {
|
|
1766
2258
|
const [visualResults, contentResults, expertResults, accessibilityResults] = await Promise.all([
|
|
1767
|
-
this.runVisualTests(html, mode),
|
|
1768
|
-
this.runContentTests(html, mode),
|
|
2259
|
+
this.runVisualTests(html, mode, qualityMetrics),
|
|
2260
|
+
this.runContentTests(html, mode, qualityMetrics),
|
|
1769
2261
|
this.runExpertTests(html, mode),
|
|
1770
|
-
this.runAccessibilityTests(html)
|
|
2262
|
+
this.runAccessibilityTests(html, qualityMetrics)
|
|
1771
2263
|
]);
|
|
1772
2264
|
const issues = this.collectIssues(visualResults, contentResults, expertResults, accessibilityResults);
|
|
1773
2265
|
const errorCount = issues.filter((i) => i.severity === "error").length;
|
|
@@ -1785,18 +2277,18 @@ var QAEngine = class {
|
|
|
1785
2277
|
}
|
|
1786
2278
|
}
|
|
1787
2279
|
/**
|
|
1788
|
-
* Calculate overall QA score
|
|
2280
|
+
* Calculate overall QA score using KB-driven weights
|
|
1789
2281
|
*/
|
|
1790
2282
|
calculateScore(results) {
|
|
1791
2283
|
const weights = {
|
|
1792
2284
|
visual: 0.35,
|
|
1793
|
-
// 35%
|
|
2285
|
+
// 35% - Design quality
|
|
1794
2286
|
content: 0.3,
|
|
1795
|
-
// 30%
|
|
2287
|
+
// 30% - Content quality
|
|
1796
2288
|
expert: 0.25,
|
|
1797
|
-
// 25%
|
|
2289
|
+
// 25% - Expert methodology
|
|
1798
2290
|
accessibility: 0.1
|
|
1799
|
-
// 10%
|
|
2291
|
+
// 10% - Accessibility
|
|
1800
2292
|
};
|
|
1801
2293
|
const visualScore = this.calculateVisualScore(results.visual);
|
|
1802
2294
|
const contentScore = this.calculateContentScore(results.content);
|
|
@@ -1843,13 +2335,15 @@ var QAEngine = class {
|
|
|
1843
2335
|
};
|
|
1844
2336
|
}
|
|
1845
2337
|
// ===========================================================================
|
|
1846
|
-
// VISUAL TESTS
|
|
2338
|
+
// VISUAL TESTS - KB-DRIVEN
|
|
1847
2339
|
// ===========================================================================
|
|
1848
|
-
async runVisualTests(html, mode) {
|
|
2340
|
+
async runVisualTests(html, mode, qualityMetrics) {
|
|
1849
2341
|
const page = await this.browser.newPage();
|
|
1850
2342
|
await page.setViewportSize({ width: 1280, height: 720 });
|
|
1851
2343
|
await page.setContent(html);
|
|
1852
2344
|
await page.waitForTimeout(1e3);
|
|
2345
|
+
const modeMetrics = mode === "keynote" ? qualityMetrics.keynote_mode : qualityMetrics.business_mode;
|
|
2346
|
+
const minWhitespace = this.kb.getWhitespaceRequirement(mode) * 100;
|
|
1853
2347
|
const slideCount = await page.evaluate(() => {
|
|
1854
2348
|
return window.Reveal?.getTotalSlides?.() ?? document.querySelectorAll(".slides > section").length;
|
|
1855
2349
|
});
|
|
@@ -1915,9 +2409,8 @@ var QAEngine = class {
|
|
|
1915
2409
|
}, { slideIndex: i });
|
|
1916
2410
|
if (slideAnalysis) {
|
|
1917
2411
|
const issues = [];
|
|
1918
|
-
const minWhitespace = mode === "keynote" ? 40 : 25;
|
|
1919
2412
|
if (slideAnalysis.whitespace < minWhitespace) {
|
|
1920
|
-
issues.push(`Whitespace ${slideAnalysis.whitespace}% below ${minWhitespace}% minimum`);
|
|
2413
|
+
issues.push(`Whitespace ${slideAnalysis.whitespace}% below ${minWhitespace}% minimum (KB-defined)`);
|
|
1921
2414
|
}
|
|
1922
2415
|
if (slideAnalysis.whitespace > 80) {
|
|
1923
2416
|
issues.push(`Whitespace ${slideAnalysis.whitespace}% - slide appears sparse`);
|
|
@@ -1972,34 +2465,35 @@ var QAEngine = class {
|
|
|
1972
2465
|
};
|
|
1973
2466
|
}
|
|
1974
2467
|
// ===========================================================================
|
|
1975
|
-
// CONTENT TESTS
|
|
2468
|
+
// CONTENT TESTS - KB-DRIVEN
|
|
1976
2469
|
// ===========================================================================
|
|
1977
|
-
async runContentTests(html, mode) {
|
|
2470
|
+
async runContentTests(html, mode, qualityMetrics) {
|
|
1978
2471
|
const page = await this.browser.newPage();
|
|
1979
2472
|
await page.setContent(html);
|
|
1980
2473
|
await page.waitForTimeout(500);
|
|
1981
|
-
const
|
|
2474
|
+
const wordLimits = this.kb.getWordLimits(mode);
|
|
2475
|
+
const glanceTest = this.kb.getDuarteGlanceTest();
|
|
2476
|
+
const results = await page.evaluate((params) => {
|
|
2477
|
+
const { targetMode, maxWords, minWords, glanceWordLimit } = params;
|
|
1982
2478
|
const slides = document.querySelectorAll(".slides > section");
|
|
1983
2479
|
const perSlide = [];
|
|
1984
|
-
const
|
|
2480
|
+
const glanceTestResults = [];
|
|
1985
2481
|
const signalNoise = [];
|
|
1986
2482
|
const oneIdea = [];
|
|
1987
2483
|
slides.forEach((slide, index) => {
|
|
1988
2484
|
const text = slide.innerText || "";
|
|
1989
2485
|
const words = text.split(/\s+/).filter((w) => w.length > 0);
|
|
1990
2486
|
const wordCount = words.length;
|
|
1991
|
-
const maxWords = targetMode === "keynote" ? 25 : 80;
|
|
1992
|
-
const minWords = targetMode === "business" ? 20 : 0;
|
|
1993
2487
|
const withinLimit = wordCount <= maxWords && wordCount >= minWords;
|
|
1994
2488
|
const title = slide.querySelector("h2")?.textContent || "";
|
|
1995
2489
|
const hasVerb = /\b(is|are|was|were|has|have|had|will|can|could|should|would|may|might|must|exceeded|increased|decreased|grew|fell|drove|caused|enabled|prevented|achieved|failed|creates?|generates?|delivers?|provides?|shows?|demonstrates?)\b/i.test(title);
|
|
1996
2490
|
const hasInsight = title.length > 30 && hasVerb;
|
|
1997
2491
|
const issues = [];
|
|
1998
2492
|
if (!withinLimit) {
|
|
1999
|
-
issues.push(`Word count ${wordCount} outside ${minWords}-${maxWords} range`);
|
|
2493
|
+
issues.push(`Word count ${wordCount} outside ${minWords}-${maxWords} range (KB-defined)`);
|
|
2000
2494
|
}
|
|
2001
2495
|
if (targetMode === "business" && !hasInsight && index > 0) {
|
|
2002
|
-
issues.push("Title is not action-oriented (missing insight)");
|
|
2496
|
+
issues.push("Title is not action-oriented (missing insight per Minto)");
|
|
2003
2497
|
}
|
|
2004
2498
|
perSlide.push({
|
|
2005
2499
|
slideIndex: index,
|
|
@@ -2012,12 +2506,12 @@ var QAEngine = class {
|
|
|
2012
2506
|
const keyMessage = prominentElement?.textContent?.trim() || "";
|
|
2013
2507
|
const keyWordCount = keyMessage.split(/\s+/).filter((w) => w.length > 0).length;
|
|
2014
2508
|
const readingTime = keyWordCount / 4.2;
|
|
2015
|
-
|
|
2509
|
+
glanceTestResults.push({
|
|
2016
2510
|
slideIndex: index,
|
|
2017
2511
|
keyMessage,
|
|
2018
2512
|
wordCount: keyWordCount,
|
|
2019
2513
|
readingTime: Math.round(readingTime * 10) / 10,
|
|
2020
|
-
passed: readingTime <= 3 && keyWordCount <=
|
|
2514
|
+
passed: readingTime <= 3 && keyWordCount <= glanceWordLimit,
|
|
2021
2515
|
recommendation: readingTime > 3 ? `Shorten to ${Math.floor(3 * 4.2)} words or less` : void 0
|
|
2022
2516
|
});
|
|
2023
2517
|
const elements = slide.querySelectorAll("h1, h2, h3, p, li, img");
|
|
@@ -2044,35 +2538,143 @@ var QAEngine = class {
|
|
|
2044
2538
|
conflictingIdeas: ideaCount > 2 ? ["Multiple competing ideas detected"] : void 0
|
|
2045
2539
|
});
|
|
2046
2540
|
});
|
|
2047
|
-
return { perSlide, glanceTest, signalNoise, oneIdea };
|
|
2048
|
-
},
|
|
2541
|
+
return { perSlide, glanceTest: glanceTestResults, signalNoise, oneIdea };
|
|
2542
|
+
}, {
|
|
2543
|
+
targetMode: mode,
|
|
2544
|
+
maxWords: wordLimits.max,
|
|
2545
|
+
minWords: mode === "business" ? 20 : 0,
|
|
2546
|
+
glanceWordLimit: glanceTest.wordLimit
|
|
2547
|
+
});
|
|
2049
2548
|
await page.close();
|
|
2050
2549
|
return results;
|
|
2051
2550
|
}
|
|
2052
2551
|
// ===========================================================================
|
|
2053
|
-
// EXPERT TESTS
|
|
2552
|
+
// EXPERT TESTS - KB-DRIVEN
|
|
2054
2553
|
// ===========================================================================
|
|
2055
2554
|
async runExpertTests(html, mode) {
|
|
2555
|
+
const duartePrinciples = this.kb.getExpertPrinciples("nancy_duarte");
|
|
2556
|
+
const reynoldsPrinciples = this.kb.getExpertPrinciples("garr_reynolds");
|
|
2557
|
+
const galloPrinciples = this.kb.getExpertPrinciples("carmine_gallo");
|
|
2558
|
+
const andersonPrinciples = this.kb.getExpertPrinciples("chris_anderson");
|
|
2056
2559
|
return {
|
|
2057
|
-
duarte: this.
|
|
2058
|
-
reynolds: this.
|
|
2059
|
-
gallo: this.
|
|
2060
|
-
anderson: this.
|
|
2560
|
+
duarte: this.validateDuartePrinciples(html, duartePrinciples),
|
|
2561
|
+
reynolds: this.validateReynoldsPrinciples(html, reynoldsPrinciples),
|
|
2562
|
+
gallo: this.validateGalloPrinciples(html, galloPrinciples),
|
|
2563
|
+
anderson: this.validateAndersonPrinciples(html, andersonPrinciples)
|
|
2061
2564
|
};
|
|
2062
2565
|
}
|
|
2063
|
-
|
|
2566
|
+
validateDuartePrinciples(html, principles) {
|
|
2567
|
+
const violations = [];
|
|
2568
|
+
let score = 100;
|
|
2569
|
+
const slideMatches = html.match(/<section[^>]*>[\s\S]*?<\/section>/gi) || [];
|
|
2570
|
+
slideMatches.forEach((slide, index) => {
|
|
2571
|
+
const h1Match = slide.match(/<h1[^>]*>([\s\S]*?)<\/h1>/i);
|
|
2572
|
+
const h2Match = slide.match(/<h2[^>]*>([\s\S]*?)<\/h2>/i);
|
|
2573
|
+
const keyMessage = (h1Match?.[1] || h2Match?.[1] || "").replace(/<[^>]+>/g, "").trim();
|
|
2574
|
+
const wordCount = keyMessage.split(/\s+/).filter((w) => w.length > 0).length;
|
|
2575
|
+
if (wordCount > 25) {
|
|
2576
|
+
violations.push(`Slide ${index + 1}: Key message exceeds 25 words (Duarte Glance Test)`);
|
|
2577
|
+
score -= 5;
|
|
2578
|
+
}
|
|
2579
|
+
});
|
|
2580
|
+
const hasStarMoment = html.includes("callout") || html.includes("highlight") || html.includes("big-idea");
|
|
2581
|
+
if (!hasStarMoment && slideMatches.length > 5) {
|
|
2582
|
+
violations.push("Missing STAR moment (Something They'll Always Remember)");
|
|
2583
|
+
score -= 10;
|
|
2584
|
+
}
|
|
2064
2585
|
return {
|
|
2065
|
-
expertName:
|
|
2066
|
-
principlesChecked:
|
|
2586
|
+
expertName: "Nancy Duarte",
|
|
2587
|
+
principlesChecked: ["Glance Test", "STAR Moment", "Sparkline Structure"],
|
|
2067
2588
|
passed: score >= 80,
|
|
2068
|
-
score,
|
|
2069
|
-
violations
|
|
2589
|
+
score: Math.max(0, score),
|
|
2590
|
+
violations
|
|
2591
|
+
};
|
|
2592
|
+
}
|
|
2593
|
+
validateReynoldsPrinciples(html, principles) {
|
|
2594
|
+
const violations = [];
|
|
2595
|
+
let score = 100;
|
|
2596
|
+
const decorativeElements = (html.match(/class="[^"]*decorative[^"]*"/gi) || []).length;
|
|
2597
|
+
const randomImages = (html.match(/picsum\.photos/gi) || []).length;
|
|
2598
|
+
if (randomImages > 0) {
|
|
2599
|
+
violations.push(`Found ${randomImages} random stock images (Reynolds: only use purposeful images)`);
|
|
2600
|
+
score -= randomImages * 10;
|
|
2601
|
+
}
|
|
2602
|
+
if (decorativeElements > 3) {
|
|
2603
|
+
violations.push(`Too many decorative elements (${decorativeElements}) - reduces signal-to-noise ratio`);
|
|
2604
|
+
score -= 10;
|
|
2605
|
+
}
|
|
2606
|
+
const columnLayouts = (html.match(/class="[^"]*columns[^"]*"/gi) || []).length;
|
|
2607
|
+
const slideCount = (html.match(/<section/gi) || []).length;
|
|
2608
|
+
if (columnLayouts > slideCount * 0.5) {
|
|
2609
|
+
violations.push("Overuse of column layouts - simplify per Reynolds");
|
|
2610
|
+
score -= 10;
|
|
2611
|
+
}
|
|
2612
|
+
return {
|
|
2613
|
+
expertName: "Garr Reynolds",
|
|
2614
|
+
principlesChecked: ["Signal-to-Noise", "Simplicity", "Picture Superiority"],
|
|
2615
|
+
passed: score >= 80,
|
|
2616
|
+
score: Math.max(0, score),
|
|
2617
|
+
violations
|
|
2618
|
+
};
|
|
2619
|
+
}
|
|
2620
|
+
validateGalloPrinciples(html, principles) {
|
|
2621
|
+
const violations = [];
|
|
2622
|
+
let score = 100;
|
|
2623
|
+
const bulletLists = html.match(/<ul[^>]*>[\s\S]*?<\/ul>/gi) || [];
|
|
2624
|
+
bulletLists.forEach((list, index) => {
|
|
2625
|
+
const bullets = (list.match(/<li/gi) || []).length;
|
|
2626
|
+
if (bullets > 5) {
|
|
2627
|
+
violations.push(`Bullet list ${index + 1} has ${bullets} items (Gallo: max 3-5)`);
|
|
2628
|
+
score -= 5;
|
|
2629
|
+
}
|
|
2630
|
+
});
|
|
2631
|
+
const hasQuote = html.includes("blockquote") || html.includes("quote");
|
|
2632
|
+
const hasStory = html.toLowerCase().includes("story") || html.toLowerCase().includes("journey");
|
|
2633
|
+
if (!hasQuote && !hasStory) {
|
|
2634
|
+
violations.push("Missing emotional connection elements (quotes, stories)");
|
|
2635
|
+
score -= 10;
|
|
2636
|
+
}
|
|
2637
|
+
return {
|
|
2638
|
+
expertName: "Carmine Gallo",
|
|
2639
|
+
principlesChecked: ["Rule of Three", "Emotional Connection", "Headline Power"],
|
|
2640
|
+
passed: score >= 80,
|
|
2641
|
+
score: Math.max(0, score),
|
|
2642
|
+
violations
|
|
2643
|
+
};
|
|
2644
|
+
}
|
|
2645
|
+
validateAndersonPrinciples(html, principles) {
|
|
2646
|
+
const violations = [];
|
|
2647
|
+
let score = 100;
|
|
2648
|
+
const slideMatches = html.match(/<section[^>]*>[\s\S]*?<\/section>/gi) || [];
|
|
2649
|
+
slideMatches.forEach((slide, index) => {
|
|
2650
|
+
const headings = (slide.match(/<h[12][^>]*>/gi) || []).length;
|
|
2651
|
+
if (headings > 2) {
|
|
2652
|
+
violations.push(`Slide ${index + 1}: Multiple ideas detected (Anderson: one idea per slide)`);
|
|
2653
|
+
score -= 5;
|
|
2654
|
+
}
|
|
2655
|
+
});
|
|
2656
|
+
const jargonPatterns = ["synergy", "leverage", "paradigm", "holistic", "streamline"];
|
|
2657
|
+
let jargonCount = 0;
|
|
2658
|
+
jargonPatterns.forEach((pattern) => {
|
|
2659
|
+
const matches = html.toLowerCase().match(new RegExp(pattern, "gi")) || [];
|
|
2660
|
+
jargonCount += matches.length;
|
|
2661
|
+
});
|
|
2662
|
+
if (jargonCount > 5) {
|
|
2663
|
+
violations.push(`Excessive jargon detected (${jargonCount} instances) - simplify language`);
|
|
2664
|
+
score -= 10;
|
|
2665
|
+
}
|
|
2666
|
+
return {
|
|
2667
|
+
expertName: "Chris Anderson",
|
|
2668
|
+
principlesChecked: ["One Idea", "Clarity", "Throughline"],
|
|
2669
|
+
passed: score >= 80,
|
|
2670
|
+
score: Math.max(0, score),
|
|
2671
|
+
violations
|
|
2070
2672
|
};
|
|
2071
2673
|
}
|
|
2072
2674
|
// ===========================================================================
|
|
2073
|
-
// ACCESSIBILITY TESTS
|
|
2675
|
+
// ACCESSIBILITY TESTS - KB-DRIVEN
|
|
2074
2676
|
// ===========================================================================
|
|
2075
|
-
async runAccessibilityTests(html) {
|
|
2677
|
+
async runAccessibilityTests(html, qualityMetrics) {
|
|
2076
2678
|
const page = await this.browser.newPage();
|
|
2077
2679
|
await page.setContent(html);
|
|
2078
2680
|
await page.waitForTimeout(500);
|
|
@@ -2122,7 +2724,7 @@ var QAEngine = class {
|
|
|
2122
2724
|
};
|
|
2123
2725
|
}
|
|
2124
2726
|
// ===========================================================================
|
|
2125
|
-
// SCORING
|
|
2727
|
+
// SCORING - KB-DRIVEN
|
|
2126
2728
|
// ===========================================================================
|
|
2127
2729
|
calculateVisualScore(results) {
|
|
2128
2730
|
let score = 100;
|
|
@@ -2246,6 +2848,7 @@ var QAEngine = class {
|
|
|
2246
2848
|
// src/generators/html/RevealJsGenerator.ts
|
|
2247
2849
|
var RevealJsGenerator = class {
|
|
2248
2850
|
templateEngine;
|
|
2851
|
+
kb;
|
|
2249
2852
|
defaultRevealConfig = {
|
|
2250
2853
|
revealVersion: "5.0.4",
|
|
2251
2854
|
hash: true,
|
|
@@ -2264,20 +2867,32 @@ var RevealJsGenerator = class {
|
|
|
2264
2867
|
constructor() {
|
|
2265
2868
|
this.templateEngine = new TemplateEngine();
|
|
2266
2869
|
}
|
|
2870
|
+
/**
|
|
2871
|
+
* Initialize KnowledgeGateway
|
|
2872
|
+
*/
|
|
2873
|
+
async initialize() {
|
|
2874
|
+
this.kb = await getKnowledgeGateway();
|
|
2875
|
+
}
|
|
2267
2876
|
/**
|
|
2268
2877
|
* Generate complete Reveal.js HTML presentation.
|
|
2269
2878
|
*/
|
|
2270
2879
|
async generate(slides, config) {
|
|
2880
|
+
await this.initialize();
|
|
2271
2881
|
const templateConfig = {};
|
|
2272
2882
|
if (config.theme) templateConfig.theme = config.theme;
|
|
2273
2883
|
if (config.customTemplates) templateConfig.customTemplates = config.customTemplates;
|
|
2274
2884
|
const slideHtml = this.templateEngine.renderAll(slides, templateConfig);
|
|
2885
|
+
const presentationType = this.detectPresentationType(config);
|
|
2886
|
+
const palette = this.kb.getRecommendedPalette(presentationType);
|
|
2887
|
+
const typography = this.kb.getTypography(config.mode);
|
|
2275
2888
|
const docConfig = {
|
|
2276
2889
|
title: config.title,
|
|
2277
2890
|
slides: slideHtml.join("\n"),
|
|
2278
2891
|
theme: config.theme ?? "default",
|
|
2279
2892
|
revealConfig: this.defaultRevealConfig,
|
|
2280
|
-
mode: config.mode
|
|
2893
|
+
mode: config.mode,
|
|
2894
|
+
palette,
|
|
2895
|
+
typography
|
|
2281
2896
|
};
|
|
2282
2897
|
if (config.author) docConfig.author = config.author;
|
|
2283
2898
|
if (config.subject) docConfig.subject = config.subject;
|
|
@@ -2288,11 +2903,23 @@ var RevealJsGenerator = class {
|
|
|
2288
2903
|
}
|
|
2289
2904
|
return html;
|
|
2290
2905
|
}
|
|
2906
|
+
/**
|
|
2907
|
+
* Detect presentation type from config
|
|
2908
|
+
*/
|
|
2909
|
+
detectPresentationType(config) {
|
|
2910
|
+
if (config.presentationType) {
|
|
2911
|
+
return config.presentationType;
|
|
2912
|
+
}
|
|
2913
|
+
if (config.mode === "keynote") {
|
|
2914
|
+
return "ted_keynote";
|
|
2915
|
+
}
|
|
2916
|
+
return "consulting_deck";
|
|
2917
|
+
}
|
|
2291
2918
|
/**
|
|
2292
2919
|
* Build the complete HTML document.
|
|
2293
2920
|
*/
|
|
2294
2921
|
buildDocument(options) {
|
|
2295
|
-
const { title, author, subject, slides, theme, customCSS, revealConfig, mode } = options;
|
|
2922
|
+
const { title, author, subject, slides, theme, customCSS, revealConfig, mode, palette, typography } = options;
|
|
2296
2923
|
return `<!DOCTYPE html>
|
|
2297
2924
|
<html lang="en">
|
|
2298
2925
|
<head>
|
|
@@ -2300,7 +2927,7 @@ var RevealJsGenerator = class {
|
|
|
2300
2927
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
2301
2928
|
<meta name="author" content="${this.escapeHtml(author ?? "Claude Presentation Master")}">
|
|
2302
2929
|
<meta name="description" content="${this.escapeHtml(subject ?? "")}">
|
|
2303
|
-
<meta name="generator" content="Claude Presentation Master
|
|
2930
|
+
<meta name="generator" content="Claude Presentation Master v6.0.0">
|
|
2304
2931
|
<title>${this.escapeHtml(title)}</title>
|
|
2305
2932
|
|
|
2306
2933
|
<!-- Reveal.js CSS -->
|
|
@@ -2314,10 +2941,10 @@ var RevealJsGenerator = class {
|
|
|
2314
2941
|
<!-- Mermaid for diagrams -->
|
|
2315
2942
|
<script src="https://cdn.jsdelivr.net/npm/mermaid@10.6.1/dist/mermaid.min.js"></script>
|
|
2316
2943
|
|
|
2317
|
-
<!-- Presentation Engine CSS -->
|
|
2944
|
+
<!-- Presentation Engine CSS - KB-Driven Design System -->
|
|
2318
2945
|
<style>
|
|
2319
|
-
${this.getBaseStyles(mode)}
|
|
2320
|
-
${this.getThemeStyles(theme)}
|
|
2946
|
+
${this.getBaseStyles(mode, palette, typography)}
|
|
2947
|
+
${this.getThemeStyles(theme, palette)}
|
|
2321
2948
|
${this.getAnimationStyles()}
|
|
2322
2949
|
${customCSS ?? ""}
|
|
2323
2950
|
</style>
|
|
@@ -2369,26 +2996,35 @@ ${slides}
|
|
|
2369
2996
|
</html>`;
|
|
2370
2997
|
}
|
|
2371
2998
|
/**
|
|
2372
|
-
* Get base styles for slides
|
|
2999
|
+
* Get base styles for slides - KB-DRIVEN
|
|
2373
3000
|
*/
|
|
2374
|
-
getBaseStyles(mode) {
|
|
3001
|
+
getBaseStyles(mode, palette, typography) {
|
|
2375
3002
|
const fontSize = mode === "keynote" ? "2.5em" : "1.8em";
|
|
2376
3003
|
const lineHeight = mode === "keynote" ? "1.4" : "1.5";
|
|
3004
|
+
const primary = palette.primary || "#1a1a2e";
|
|
3005
|
+
const secondary = palette.secondary || "#16213e";
|
|
3006
|
+
const accent = palette.accent || "#0f3460";
|
|
3007
|
+
const highlight = palette.accent || "#e94560";
|
|
3008
|
+
const text = palette.text || "#1a1a2e";
|
|
3009
|
+
const background = palette.background || "#ffffff";
|
|
2377
3010
|
return `
|
|
2378
|
-
/* Base Styles */
|
|
3011
|
+
/* Base Styles - KB-Driven Design System */
|
|
2379
3012
|
:root {
|
|
3013
|
+
/* Typography from KB */
|
|
2380
3014
|
--font-heading: 'Source Sans Pro', 'Helvetica Neue', -apple-system, BlinkMacSystemFont, sans-serif;
|
|
2381
3015
|
--font-body: 'Source Sans Pro', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
2382
3016
|
--font-mono: 'SF Mono', 'Fira Code', 'JetBrains Mono', monospace;
|
|
2383
3017
|
|
|
2384
|
-
|
|
2385
|
-
--color-
|
|
2386
|
-
--color-
|
|
2387
|
-
--color-
|
|
2388
|
-
--color-
|
|
2389
|
-
--color-text
|
|
2390
|
-
--color-
|
|
3018
|
+
/* Colors from KB Palette: ${palette.name} */
|
|
3019
|
+
--color-primary: ${primary};
|
|
3020
|
+
--color-secondary: ${secondary};
|
|
3021
|
+
--color-accent: ${accent};
|
|
3022
|
+
--color-highlight: ${highlight};
|
|
3023
|
+
--color-text: ${text};
|
|
3024
|
+
--color-text-light: ${this.lightenColor(text, 30)};
|
|
3025
|
+
--color-background: ${background};
|
|
2391
3026
|
|
|
3027
|
+
/* Layout */
|
|
2392
3028
|
--slide-padding: 60px;
|
|
2393
3029
|
--content-max-width: 1200px;
|
|
2394
3030
|
}
|
|
@@ -2504,7 +3140,7 @@ ${slides}
|
|
|
2504
3140
|
font-size: 0.8em;
|
|
2505
3141
|
}
|
|
2506
3142
|
|
|
2507
|
-
/* Images */
|
|
3143
|
+
/* Images - NO RANDOM PICSUM - Only user-provided or none */
|
|
2508
3144
|
.reveal img {
|
|
2509
3145
|
max-width: 100%;
|
|
2510
3146
|
height: auto;
|
|
@@ -2610,9 +3246,25 @@ ${slides}
|
|
|
2610
3246
|
`;
|
|
2611
3247
|
}
|
|
2612
3248
|
/**
|
|
2613
|
-
*
|
|
3249
|
+
* Lighten a hex color
|
|
3250
|
+
*/
|
|
3251
|
+
lightenColor(hex, percent) {
|
|
3252
|
+
hex = hex.replace("#", "");
|
|
3253
|
+
let r = parseInt(hex.substring(0, 2), 16);
|
|
3254
|
+
let g = parseInt(hex.substring(2, 4), 16);
|
|
3255
|
+
let b = parseInt(hex.substring(4, 6), 16);
|
|
3256
|
+
r = Math.min(255, Math.floor(r + (255 - r) * (percent / 100)));
|
|
3257
|
+
g = Math.min(255, Math.floor(g + (255 - g) * (percent / 100)));
|
|
3258
|
+
b = Math.min(255, Math.floor(b + (255 - b) * (percent / 100)));
|
|
3259
|
+
return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`;
|
|
3260
|
+
}
|
|
3261
|
+
/**
|
|
3262
|
+
* Get theme-specific styles - KB-DRIVEN
|
|
2614
3263
|
*/
|
|
2615
|
-
getThemeStyles(theme) {
|
|
3264
|
+
getThemeStyles(theme, palette) {
|
|
3265
|
+
if (theme === "default") {
|
|
3266
|
+
return "";
|
|
3267
|
+
}
|
|
2616
3268
|
const themes = {
|
|
2617
3269
|
"default": "",
|
|
2618
3270
|
"light-corporate": `
|
|
@@ -2626,10 +3278,10 @@ ${slides}
|
|
|
2626
3278
|
`,
|
|
2627
3279
|
"modern-tech": `
|
|
2628
3280
|
:root {
|
|
2629
|
-
--color-primary: #1a1a2e;
|
|
2630
|
-
--color-secondary: #16213e;
|
|
2631
|
-
--color-accent: #0f3460;
|
|
2632
|
-
--color-highlight: #e94560;
|
|
3281
|
+
--color-primary: ${palette.primary || "#1a1a2e"};
|
|
3282
|
+
--color-secondary: ${palette.secondary || "#16213e"};
|
|
3283
|
+
--color-accent: ${palette.accent || "#0f3460"};
|
|
3284
|
+
--color-highlight: ${palette.accent || "#e94560"};
|
|
2633
3285
|
--color-background: #f8f9fa;
|
|
2634
3286
|
}
|
|
2635
3287
|
`,
|
|
@@ -3875,239 +4527,6 @@ function createDefaultImageProvider(options) {
|
|
|
3875
4527
|
return new CompositeImageProvider(providers);
|
|
3876
4528
|
}
|
|
3877
4529
|
|
|
3878
|
-
// src/knowledge/KnowledgeBase.ts
|
|
3879
|
-
import { readFileSync } from "fs";
|
|
3880
|
-
import { join, dirname } from "path";
|
|
3881
|
-
import { fileURLToPath } from "url";
|
|
3882
|
-
import * as yaml from "yaml";
|
|
3883
|
-
function getModuleDir() {
|
|
3884
|
-
if (typeof import.meta !== "undefined" && import.meta.url) {
|
|
3885
|
-
return dirname(fileURLToPath(import.meta.url));
|
|
3886
|
-
}
|
|
3887
|
-
if (typeof __dirname !== "undefined") {
|
|
3888
|
-
return __dirname;
|
|
3889
|
-
}
|
|
3890
|
-
return process.cwd();
|
|
3891
|
-
}
|
|
3892
|
-
var moduleDir = getModuleDir();
|
|
3893
|
-
var KnowledgeBase = class {
|
|
3894
|
-
data = null;
|
|
3895
|
-
loaded = false;
|
|
3896
|
-
/**
|
|
3897
|
-
* Load the knowledge base from the bundled YAML file.
|
|
3898
|
-
*/
|
|
3899
|
-
async load() {
|
|
3900
|
-
if (this.loaded) return;
|
|
3901
|
-
try {
|
|
3902
|
-
const possiblePaths = [
|
|
3903
|
-
// From dist/: go up to package root
|
|
3904
|
-
join(moduleDir, "../../assets/presentation-knowledge.yaml"),
|
|
3905
|
-
join(moduleDir, "../assets/presentation-knowledge.yaml"),
|
|
3906
|
-
join(moduleDir, "../../../assets/presentation-knowledge.yaml"),
|
|
3907
|
-
// From bundle in dist/index.js: go up one level
|
|
3908
|
-
join(moduleDir, "assets/presentation-knowledge.yaml"),
|
|
3909
|
-
// CWD-based paths for development or local installs
|
|
3910
|
-
join(process.cwd(), "assets/presentation-knowledge.yaml"),
|
|
3911
|
-
join(process.cwd(), "node_modules/claude-presentation-master/assets/presentation-knowledge.yaml")
|
|
3912
|
-
];
|
|
3913
|
-
let assetPath = "";
|
|
3914
|
-
for (const p of possiblePaths) {
|
|
3915
|
-
try {
|
|
3916
|
-
readFileSync(p);
|
|
3917
|
-
assetPath = p;
|
|
3918
|
-
break;
|
|
3919
|
-
} catch {
|
|
3920
|
-
continue;
|
|
3921
|
-
}
|
|
3922
|
-
}
|
|
3923
|
-
if (!assetPath) {
|
|
3924
|
-
throw new Error("Could not locate knowledge base file");
|
|
3925
|
-
}
|
|
3926
|
-
const content = readFileSync(assetPath, "utf-8");
|
|
3927
|
-
this.data = yaml.parse(content);
|
|
3928
|
-
this.loaded = true;
|
|
3929
|
-
console.log(`\u{1F4DA} Knowledge base loaded: v${this.data.version}`);
|
|
3930
|
-
} catch (error) {
|
|
3931
|
-
console.warn("\u26A0\uFE0F Could not load knowledge base, using defaults");
|
|
3932
|
-
this.data = this.getDefaultData();
|
|
3933
|
-
this.loaded = true;
|
|
3934
|
-
}
|
|
3935
|
-
}
|
|
3936
|
-
/**
|
|
3937
|
-
* Get expert methodology by name.
|
|
3938
|
-
*/
|
|
3939
|
-
getExpert(name) {
|
|
3940
|
-
this.ensureLoaded();
|
|
3941
|
-
return this.data?.experts?.[name];
|
|
3942
|
-
}
|
|
3943
|
-
/**
|
|
3944
|
-
* Get all expert names.
|
|
3945
|
-
*/
|
|
3946
|
-
getExpertNames() {
|
|
3947
|
-
this.ensureLoaded();
|
|
3948
|
-
return Object.keys(this.data?.experts ?? {});
|
|
3949
|
-
}
|
|
3950
|
-
/**
|
|
3951
|
-
* Get framework recommendation for audience.
|
|
3952
|
-
*/
|
|
3953
|
-
getFrameworkForAudience(audience) {
|
|
3954
|
-
this.ensureLoaded();
|
|
3955
|
-
return this.data?.frameworkSelector?.byAudience?.[audience];
|
|
3956
|
-
}
|
|
3957
|
-
/**
|
|
3958
|
-
* Get framework recommendation for goal.
|
|
3959
|
-
*/
|
|
3960
|
-
getFrameworkForGoal(goal) {
|
|
3961
|
-
this.ensureLoaded();
|
|
3962
|
-
return this.data?.frameworkSelector?.byGoal?.[goal];
|
|
3963
|
-
}
|
|
3964
|
-
/**
|
|
3965
|
-
* Get QA scoring rubric.
|
|
3966
|
-
*/
|
|
3967
|
-
getScoringRubric() {
|
|
3968
|
-
this.ensureLoaded();
|
|
3969
|
-
return this.data?.automatedQA?.scoringRubric;
|
|
3970
|
-
}
|
|
3971
|
-
/**
|
|
3972
|
-
* Get mode configuration (keynote or business).
|
|
3973
|
-
*/
|
|
3974
|
-
getModeConfig(mode) {
|
|
3975
|
-
this.ensureLoaded();
|
|
3976
|
-
return this.data?.modes?.[mode];
|
|
3977
|
-
}
|
|
3978
|
-
/**
|
|
3979
|
-
* Get slide type configuration.
|
|
3980
|
-
*/
|
|
3981
|
-
getSlideType(type) {
|
|
3982
|
-
this.ensureLoaded();
|
|
3983
|
-
return this.data?.slideTypes?.[type];
|
|
3984
|
-
}
|
|
3985
|
-
/**
|
|
3986
|
-
* Get the knowledge base version.
|
|
3987
|
-
*/
|
|
3988
|
-
getVersion() {
|
|
3989
|
-
this.ensureLoaded();
|
|
3990
|
-
return this.data?.version ?? "unknown";
|
|
3991
|
-
}
|
|
3992
|
-
/**
|
|
3993
|
-
* Validate a slide against expert principles.
|
|
3994
|
-
*/
|
|
3995
|
-
validateAgainstExpert(expertName, slideData) {
|
|
3996
|
-
const expert = this.getExpert(expertName);
|
|
3997
|
-
if (!expert) {
|
|
3998
|
-
return { passed: true, violations: [] };
|
|
3999
|
-
}
|
|
4000
|
-
const violations = [];
|
|
4001
|
-
if (expert.wordLimits) {
|
|
4002
|
-
if (expert.wordLimits.max && slideData.wordCount > expert.wordLimits.max) {
|
|
4003
|
-
violations.push(`Exceeds ${expertName} word limit of ${expert.wordLimits.max}`);
|
|
4004
|
-
}
|
|
4005
|
-
if (expert.wordLimits.min && slideData.wordCount < expert.wordLimits.min) {
|
|
4006
|
-
violations.push(`Below ${expertName} minimum of ${expert.wordLimits.min} words`);
|
|
4007
|
-
}
|
|
4008
|
-
}
|
|
4009
|
-
return {
|
|
4010
|
-
passed: violations.length === 0,
|
|
4011
|
-
violations
|
|
4012
|
-
};
|
|
4013
|
-
}
|
|
4014
|
-
/**
|
|
4015
|
-
* Ensure knowledge base is loaded.
|
|
4016
|
-
*/
|
|
4017
|
-
ensureLoaded() {
|
|
4018
|
-
if (!this.loaded) {
|
|
4019
|
-
this.data = this.getDefaultData();
|
|
4020
|
-
this.loaded = true;
|
|
4021
|
-
}
|
|
4022
|
-
}
|
|
4023
|
-
/**
|
|
4024
|
-
* Get default data if YAML can't be loaded.
|
|
4025
|
-
*/
|
|
4026
|
-
getDefaultData() {
|
|
4027
|
-
return {
|
|
4028
|
-
version: "1.0.0-fallback",
|
|
4029
|
-
lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4030
|
-
experts: {
|
|
4031
|
-
"Nancy Duarte": {
|
|
4032
|
-
name: "Nancy Duarte",
|
|
4033
|
-
principles: [
|
|
4034
|
-
{ name: "Glance Test", description: "Message clear in 3 seconds" },
|
|
4035
|
-
{ name: "STAR Moment", description: "Something They'll Always Remember" },
|
|
4036
|
-
{ name: "Sparkline", description: "Contrast What Is vs What Could Be" }
|
|
4037
|
-
]
|
|
4038
|
-
},
|
|
4039
|
-
"Garr Reynolds": {
|
|
4040
|
-
name: "Garr Reynolds",
|
|
4041
|
-
principles: [
|
|
4042
|
-
{ name: "Signal-to-Noise", description: "Maximize signal, minimize noise" },
|
|
4043
|
-
{ name: "Simplicity", description: "Amplify through simplification" }
|
|
4044
|
-
]
|
|
4045
|
-
},
|
|
4046
|
-
"Carmine Gallo": {
|
|
4047
|
-
name: "Carmine Gallo",
|
|
4048
|
-
principles: [
|
|
4049
|
-
{ name: "Rule of Three", description: "Maximum 3 key messages" },
|
|
4050
|
-
{ name: "Emotional Connection", description: "Connect emotionally first" }
|
|
4051
|
-
]
|
|
4052
|
-
},
|
|
4053
|
-
"Chris Anderson": {
|
|
4054
|
-
name: "Chris Anderson",
|
|
4055
|
-
principles: [
|
|
4056
|
-
{ name: "One Idea", description: "One powerful idea per talk" },
|
|
4057
|
-
{ name: "Dead Laptop Test", description: "Present without slides" }
|
|
4058
|
-
]
|
|
4059
|
-
}
|
|
4060
|
-
},
|
|
4061
|
-
frameworkSelector: {
|
|
4062
|
-
byAudience: {
|
|
4063
|
-
"board": {
|
|
4064
|
-
primaryFramework: "Barbara Minto",
|
|
4065
|
-
slideTypes: ["executive_summary", "data_insight"]
|
|
4066
|
-
},
|
|
4067
|
-
"sales": {
|
|
4068
|
-
primaryFramework: "Nancy Duarte",
|
|
4069
|
-
slideTypes: ["big_idea", "social_proof"]
|
|
4070
|
-
}
|
|
4071
|
-
},
|
|
4072
|
-
byGoal: {
|
|
4073
|
-
"persuade": {
|
|
4074
|
-
primaryFramework: "Nancy Duarte",
|
|
4075
|
-
slideTypes: ["big_idea", "star_moment"]
|
|
4076
|
-
},
|
|
4077
|
-
"inform": {
|
|
4078
|
-
primaryFramework: "Barbara Minto",
|
|
4079
|
-
slideTypes: ["bullet_points", "data_insight"]
|
|
4080
|
-
}
|
|
4081
|
-
}
|
|
4082
|
-
},
|
|
4083
|
-
automatedQA: {
|
|
4084
|
-
scoringRubric: {
|
|
4085
|
-
totalPoints: 100,
|
|
4086
|
-
passingThreshold: 95,
|
|
4087
|
-
categories: {
|
|
4088
|
-
visual: { weight: 35, checks: {} },
|
|
4089
|
-
content: { weight: 30, checks: {} },
|
|
4090
|
-
expert: { weight: 25, checks: {} },
|
|
4091
|
-
accessibility: { weight: 10, checks: {} }
|
|
4092
|
-
}
|
|
4093
|
-
}
|
|
4094
|
-
},
|
|
4095
|
-
slideTypes: {},
|
|
4096
|
-
modes: {
|
|
4097
|
-
keynote: { maxWords: 25, minWhitespace: 35 },
|
|
4098
|
-
business: { maxWords: 80, minWhitespace: 25 }
|
|
4099
|
-
}
|
|
4100
|
-
};
|
|
4101
|
-
}
|
|
4102
|
-
};
|
|
4103
|
-
var knowledgeBaseInstance = null;
|
|
4104
|
-
function getKnowledgeBase() {
|
|
4105
|
-
if (!knowledgeBaseInstance) {
|
|
4106
|
-
knowledgeBaseInstance = new KnowledgeBase();
|
|
4107
|
-
}
|
|
4108
|
-
return knowledgeBaseInstance;
|
|
4109
|
-
}
|
|
4110
|
-
|
|
4111
4530
|
// src/index.ts
|
|
4112
4531
|
async function generate(config) {
|
|
4113
4532
|
const engine = new PresentationEngine();
|
|
@@ -4122,7 +4541,7 @@ async function validate(presentation, options) {
|
|
|
4122
4541
|
score
|
|
4123
4542
|
};
|
|
4124
4543
|
}
|
|
4125
|
-
var VERSION = "
|
|
4544
|
+
var VERSION = "6.0.0";
|
|
4126
4545
|
var index_default = {
|
|
4127
4546
|
generate,
|
|
4128
4547
|
validate,
|
|
@@ -4135,7 +4554,7 @@ export {
|
|
|
4135
4554
|
CompositeChartProvider,
|
|
4136
4555
|
CompositeImageProvider,
|
|
4137
4556
|
ContentAnalyzer,
|
|
4138
|
-
|
|
4557
|
+
KnowledgeGateway,
|
|
4139
4558
|
LocalImageProvider,
|
|
4140
4559
|
MermaidProvider,
|
|
4141
4560
|
PlaceholderImageProvider,
|
|
@@ -4156,7 +4575,7 @@ export {
|
|
|
4156
4575
|
createDefaultImageProvider,
|
|
4157
4576
|
index_default as default,
|
|
4158
4577
|
generate,
|
|
4159
|
-
|
|
4578
|
+
getKnowledgeGateway,
|
|
4160
4579
|
validate
|
|
4161
4580
|
};
|
|
4162
4581
|
/**
|