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.js
CHANGED
|
@@ -34,7 +34,7 @@ __export(index_exports, {
|
|
|
34
34
|
CompositeChartProvider: () => CompositeChartProvider,
|
|
35
35
|
CompositeImageProvider: () => CompositeImageProvider,
|
|
36
36
|
ContentAnalyzer: () => ContentAnalyzer,
|
|
37
|
-
|
|
37
|
+
KnowledgeGateway: () => KnowledgeGateway,
|
|
38
38
|
LocalImageProvider: () => LocalImageProvider,
|
|
39
39
|
MermaidProvider: () => MermaidProvider,
|
|
40
40
|
PlaceholderImageProvider: () => PlaceholderImageProvider,
|
|
@@ -55,7 +55,7 @@ __export(index_exports, {
|
|
|
55
55
|
createDefaultImageProvider: () => createDefaultImageProvider,
|
|
56
56
|
default: () => index_default,
|
|
57
57
|
generate: () => generate,
|
|
58
|
-
|
|
58
|
+
getKnowledgeGateway: () => getKnowledgeGateway,
|
|
59
59
|
validate: () => validate
|
|
60
60
|
});
|
|
61
61
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -88,8 +88,281 @@ var TemplateNotFoundError = class extends Error {
|
|
|
88
88
|
}
|
|
89
89
|
};
|
|
90
90
|
|
|
91
|
+
// src/kb/KnowledgeGateway.ts
|
|
92
|
+
var import_fs = require("fs");
|
|
93
|
+
var import_path = require("path");
|
|
94
|
+
var import_url = require("url");
|
|
95
|
+
var yaml = __toESM(require("yaml"));
|
|
96
|
+
var import_meta = {};
|
|
97
|
+
function getModuleDir() {
|
|
98
|
+
if (typeof import_meta !== "undefined" && import_meta.url) {
|
|
99
|
+
return (0, import_path.dirname)((0, import_url.fileURLToPath)(import_meta.url));
|
|
100
|
+
}
|
|
101
|
+
if (typeof __dirname !== "undefined") {
|
|
102
|
+
return __dirname;
|
|
103
|
+
}
|
|
104
|
+
return process.cwd();
|
|
105
|
+
}
|
|
106
|
+
var KnowledgeGateway = class {
|
|
107
|
+
kb;
|
|
108
|
+
loaded = false;
|
|
109
|
+
constructor() {
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Load the knowledge base from YAML file
|
|
113
|
+
*/
|
|
114
|
+
async load() {
|
|
115
|
+
if (this.loaded) return;
|
|
116
|
+
const moduleDir = getModuleDir();
|
|
117
|
+
const possiblePaths = [
|
|
118
|
+
(0, import_path.join)(moduleDir, "../../assets/presentation-knowledge.yaml"),
|
|
119
|
+
(0, import_path.join)(moduleDir, "../assets/presentation-knowledge.yaml"),
|
|
120
|
+
(0, import_path.join)(moduleDir, "../../../assets/presentation-knowledge.yaml"),
|
|
121
|
+
(0, import_path.join)(moduleDir, "assets/presentation-knowledge.yaml"),
|
|
122
|
+
(0, import_path.join)(process.cwd(), "assets/presentation-knowledge.yaml"),
|
|
123
|
+
(0, import_path.join)(process.cwd(), "node_modules/claude-presentation-master/assets/presentation-knowledge.yaml")
|
|
124
|
+
];
|
|
125
|
+
for (const path of possiblePaths) {
|
|
126
|
+
try {
|
|
127
|
+
const content = (0, import_fs.readFileSync)(path, "utf-8");
|
|
128
|
+
this.kb = yaml.parse(content);
|
|
129
|
+
this.loaded = true;
|
|
130
|
+
console.log(` \u2713 Knowledge base loaded from ${path}`);
|
|
131
|
+
return;
|
|
132
|
+
} catch {
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
throw new Error(`Knowledge base not found. Tried: ${possiblePaths.join(", ")}`);
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Get presentation mode configuration (keynote or business)
|
|
139
|
+
*/
|
|
140
|
+
getMode(mode) {
|
|
141
|
+
this.ensureLoaded();
|
|
142
|
+
return this.kb.presentation_modes[mode];
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Determine which mode is best for a given presentation type
|
|
146
|
+
*/
|
|
147
|
+
getModeForType(type) {
|
|
148
|
+
const keynoteTypes = ["ted_keynote", "sales_pitch", "all_hands"];
|
|
149
|
+
const businessTypes = ["consulting_deck", "investment_banking", "investor_pitch", "technical_presentation"];
|
|
150
|
+
if (keynoteTypes.includes(type)) return "keynote";
|
|
151
|
+
if (businessTypes.includes(type)) return "business";
|
|
152
|
+
return "keynote";
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Get word limits for the specified mode
|
|
156
|
+
*/
|
|
157
|
+
getWordLimits(mode) {
|
|
158
|
+
const modeConfig = this.getMode(mode);
|
|
159
|
+
const range = modeConfig.characteristics.words_per_slide;
|
|
160
|
+
if (mode === "keynote") {
|
|
161
|
+
return { min: 6, max: 15, ideal: 10 };
|
|
162
|
+
} else {
|
|
163
|
+
return { min: 40, max: 80, ideal: 60 };
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Get bullet point limits
|
|
168
|
+
*/
|
|
169
|
+
getBulletLimits(mode) {
|
|
170
|
+
if (mode === "keynote") {
|
|
171
|
+
return { maxBullets: 3, maxWordsPerBullet: 8 };
|
|
172
|
+
} else {
|
|
173
|
+
return { maxBullets: 5, maxWordsPerBullet: 20 };
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Get whitespace percentage requirement
|
|
178
|
+
*/
|
|
179
|
+
getWhitespaceRequirement(mode) {
|
|
180
|
+
if (mode === "keynote") return 0.4;
|
|
181
|
+
return 0.25;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Get slide templates for the specified mode
|
|
185
|
+
*/
|
|
186
|
+
getSlideTemplates(mode) {
|
|
187
|
+
this.ensureLoaded();
|
|
188
|
+
const key = mode === "keynote" ? "keynote_mode" : "business_mode";
|
|
189
|
+
return this.kb.slide_templates[key] || [];
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Get a specific slide template by name
|
|
193
|
+
*/
|
|
194
|
+
getSlideTemplate(mode, templateName) {
|
|
195
|
+
const templates = this.getSlideTemplates(mode);
|
|
196
|
+
return templates.find((t) => t.name.toLowerCase().includes(templateName.toLowerCase()));
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Get quality metrics for grading
|
|
200
|
+
*/
|
|
201
|
+
getQualityMetrics() {
|
|
202
|
+
this.ensureLoaded();
|
|
203
|
+
return this.kb.quality_metrics;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Get anti-patterns to avoid
|
|
207
|
+
*/
|
|
208
|
+
getAntiPatterns(mode) {
|
|
209
|
+
this.ensureLoaded();
|
|
210
|
+
const key = mode === "keynote" ? "keynote_mode" : "business_mode";
|
|
211
|
+
return this.kb.anti_patterns[key] || [];
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Get data visualization anti-patterns
|
|
215
|
+
*/
|
|
216
|
+
getDataVizAntiPatterns() {
|
|
217
|
+
this.ensureLoaded();
|
|
218
|
+
return this.kb.anti_patterns.data_visualization || [];
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Get accessibility anti-patterns
|
|
222
|
+
*/
|
|
223
|
+
getAccessibilityAntiPatterns() {
|
|
224
|
+
this.ensureLoaded();
|
|
225
|
+
return this.kb.anti_patterns.accessibility || [];
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Get color palette by name
|
|
229
|
+
*/
|
|
230
|
+
getColorPalette(name) {
|
|
231
|
+
this.ensureLoaded();
|
|
232
|
+
const palettes = this.kb.design_system.color_palettes;
|
|
233
|
+
return palettes[name];
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Get recommended color palette for presentation type
|
|
237
|
+
*/
|
|
238
|
+
getRecommendedPalette(type) {
|
|
239
|
+
this.ensureLoaded();
|
|
240
|
+
const palettes = this.kb.design_system.color_palettes;
|
|
241
|
+
switch (type) {
|
|
242
|
+
case "consulting_deck":
|
|
243
|
+
return palettes.consulting_classic;
|
|
244
|
+
case "investment_banking":
|
|
245
|
+
case "investor_pitch":
|
|
246
|
+
return palettes.executive_professional;
|
|
247
|
+
case "ted_keynote":
|
|
248
|
+
return palettes.dark_executive;
|
|
249
|
+
case "sales_pitch":
|
|
250
|
+
return palettes.modern_business;
|
|
251
|
+
case "technical_presentation":
|
|
252
|
+
return palettes.modern_business;
|
|
253
|
+
case "all_hands":
|
|
254
|
+
return palettes.strategy_growth;
|
|
255
|
+
default:
|
|
256
|
+
return palettes.consulting_classic;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Get typography guidelines for mode
|
|
261
|
+
*/
|
|
262
|
+
getTypography(mode) {
|
|
263
|
+
this.ensureLoaded();
|
|
264
|
+
const key = mode === "keynote" ? "keynote_mode" : "business_mode";
|
|
265
|
+
return this.kb.design_system.typography[key];
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Get story framework by name
|
|
269
|
+
*/
|
|
270
|
+
getStoryFramework(name) {
|
|
271
|
+
this.ensureLoaded();
|
|
272
|
+
return this.kb.story_frameworks[name];
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Get expert principles by name
|
|
276
|
+
*/
|
|
277
|
+
getExpertPrinciples(expertName) {
|
|
278
|
+
this.ensureLoaded();
|
|
279
|
+
if (this.kb.experts[expertName]) {
|
|
280
|
+
return this.kb.experts[expertName];
|
|
281
|
+
}
|
|
282
|
+
if (this.kb.data_visualization_experts) {
|
|
283
|
+
for (const key of Object.keys(this.kb.data_visualization_experts)) {
|
|
284
|
+
if (key.includes(expertName.toLowerCase())) {
|
|
285
|
+
return this.kb.data_visualization_experts[key];
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return null;
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Get Duarte's glance test requirements
|
|
293
|
+
*/
|
|
294
|
+
getDuarteGlanceTest() {
|
|
295
|
+
this.ensureLoaded();
|
|
296
|
+
const duarte = this.kb.experts.nancy_duarte;
|
|
297
|
+
return {
|
|
298
|
+
wordLimit: duarte.core_principles.glance_test.word_limit || 25,
|
|
299
|
+
timeSeconds: 3
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Get Miller's Law constraints
|
|
304
|
+
*/
|
|
305
|
+
getMillersLaw() {
|
|
306
|
+
this.ensureLoaded();
|
|
307
|
+
return { minItems: 5, maxItems: 9 };
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Get cognitive load constraints
|
|
311
|
+
*/
|
|
312
|
+
getCognitiveLoadLimits() {
|
|
313
|
+
this.ensureLoaded();
|
|
314
|
+
return { maxItemsPerChunk: 7 };
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Get chart selection guidance
|
|
318
|
+
*/
|
|
319
|
+
getChartGuidance(purpose) {
|
|
320
|
+
this.ensureLoaded();
|
|
321
|
+
return this.kb.chart_selection_guide.by_purpose[purpose];
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Get charts to avoid
|
|
325
|
+
*/
|
|
326
|
+
getChartsToAvoid() {
|
|
327
|
+
this.ensureLoaded();
|
|
328
|
+
const knaflic = this.kb.data_visualization_experts.cole_nussbaumer_knaflic;
|
|
329
|
+
return knaflic.chart_guidance.avoid_these || [];
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Get investment banking pitch book structure
|
|
333
|
+
*/
|
|
334
|
+
getIBPitchBookStructure(type) {
|
|
335
|
+
this.ensureLoaded();
|
|
336
|
+
return this.kb.investment_banking?.pitch_book_types[type];
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Check if knowledge base is loaded
|
|
340
|
+
*/
|
|
341
|
+
ensureLoaded() {
|
|
342
|
+
if (!this.loaded) {
|
|
343
|
+
throw new Error("Knowledge base not loaded. Call load() first.");
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Get the full knowledge base (for advanced queries)
|
|
348
|
+
*/
|
|
349
|
+
getRawKB() {
|
|
350
|
+
this.ensureLoaded();
|
|
351
|
+
return this.kb;
|
|
352
|
+
}
|
|
353
|
+
};
|
|
354
|
+
var gatewayInstance = null;
|
|
355
|
+
async function getKnowledgeGateway() {
|
|
356
|
+
if (!gatewayInstance) {
|
|
357
|
+
gatewayInstance = new KnowledgeGateway();
|
|
358
|
+
await gatewayInstance.load();
|
|
359
|
+
}
|
|
360
|
+
return gatewayInstance;
|
|
361
|
+
}
|
|
362
|
+
|
|
91
363
|
// src/core/ContentAnalyzer.ts
|
|
92
364
|
var ContentAnalyzer = class {
|
|
365
|
+
kb;
|
|
93
366
|
// Signal words for SCQA detection
|
|
94
367
|
situationSignals = [
|
|
95
368
|
"currently",
|
|
@@ -101,7 +374,8 @@ var ContentAnalyzer = class {
|
|
|
101
374
|
"our",
|
|
102
375
|
"the market",
|
|
103
376
|
"industry",
|
|
104
|
-
"context"
|
|
377
|
+
"context",
|
|
378
|
+
"background"
|
|
105
379
|
];
|
|
106
380
|
complicationSignals = [
|
|
107
381
|
"however",
|
|
@@ -119,20 +393,10 @@ var ContentAnalyzer = class {
|
|
|
119
393
|
"yet",
|
|
120
394
|
"although",
|
|
121
395
|
"despite",
|
|
122
|
-
"while"
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
"
|
|
126
|
-
"what",
|
|
127
|
-
"why",
|
|
128
|
-
"when",
|
|
129
|
-
"where",
|
|
130
|
-
"which",
|
|
131
|
-
"should",
|
|
132
|
-
"could",
|
|
133
|
-
"can we",
|
|
134
|
-
"is it possible",
|
|
135
|
-
"?"
|
|
396
|
+
"while",
|
|
397
|
+
"crisis",
|
|
398
|
+
"gap",
|
|
399
|
+
"broken"
|
|
136
400
|
];
|
|
137
401
|
answerSignals = [
|
|
138
402
|
"therefore",
|
|
@@ -148,249 +412,389 @@ var ContentAnalyzer = class {
|
|
|
148
412
|
"we should",
|
|
149
413
|
"must",
|
|
150
414
|
"need to",
|
|
151
|
-
"the answer"
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
"currently",
|
|
156
|
-
"today",
|
|
157
|
-
"status quo",
|
|
158
|
-
"reality",
|
|
159
|
-
"actual",
|
|
160
|
-
"now",
|
|
161
|
-
"existing",
|
|
162
|
-
"present state",
|
|
163
|
-
"as-is",
|
|
164
|
-
"problem"
|
|
415
|
+
"the answer",
|
|
416
|
+
"introducing",
|
|
417
|
+
"presenting",
|
|
418
|
+
"platform"
|
|
165
419
|
];
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
"
|
|
169
|
-
"
|
|
170
|
-
"
|
|
171
|
-
"
|
|
172
|
-
"
|
|
173
|
-
"
|
|
174
|
-
"
|
|
175
|
-
"
|
|
176
|
-
"
|
|
177
|
-
"
|
|
178
|
-
"
|
|
179
|
-
"goal",
|
|
180
|
-
"aspiration"
|
|
420
|
+
// CTA signals - used to filter these OUT of answer detection
|
|
421
|
+
ctaSignals = [
|
|
422
|
+
"contact",
|
|
423
|
+
"call us",
|
|
424
|
+
"email",
|
|
425
|
+
"schedule",
|
|
426
|
+
"sign up",
|
|
427
|
+
"register",
|
|
428
|
+
"next steps",
|
|
429
|
+
"get started",
|
|
430
|
+
"reach out",
|
|
431
|
+
"visit",
|
|
432
|
+
"follow"
|
|
181
433
|
];
|
|
434
|
+
// Type detection signals
|
|
435
|
+
typeSignals = {
|
|
436
|
+
ted_keynote: ["inspire", "vision", "imagine", "dream", "transform", "story", "journey"],
|
|
437
|
+
sales_pitch: ["buy", "purchase", "pricing", "offer", "deal", "discount", "roi", "value"],
|
|
438
|
+
consulting_deck: ["recommend", "analysis", "assessment", "strategy", "findings", "options", "mece"],
|
|
439
|
+
investment_banking: ["valuation", "dcf", "multiple", "enterprise value", "ebitda", "ipo", "m&a"],
|
|
440
|
+
investor_pitch: ["investment", "funding", "series", "traction", "market size", "tam", "runway"],
|
|
441
|
+
technical_presentation: ["architecture", "api", "system", "implementation", "code", "database", "deploy"],
|
|
442
|
+
all_hands: ["team", "quarter", "update", "progress", "celebrate", "recognize", "company"]
|
|
443
|
+
};
|
|
444
|
+
async initialize() {
|
|
445
|
+
this.kb = await getKnowledgeGateway();
|
|
446
|
+
}
|
|
182
447
|
/**
|
|
183
448
|
* Analyze content and extract structural elements.
|
|
184
449
|
*/
|
|
185
450
|
async analyze(content, contentType) {
|
|
451
|
+
await this.initialize();
|
|
186
452
|
const text = this.parseContent(content, contentType);
|
|
187
|
-
const
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
const
|
|
193
|
-
const
|
|
194
|
-
const
|
|
453
|
+
const title = this.extractTitle(text);
|
|
454
|
+
const detectedType = this.detectPresentationType(text);
|
|
455
|
+
console.log(` \u2713 Detected type: ${detectedType}`);
|
|
456
|
+
const sections = this.extractSections(text);
|
|
457
|
+
console.log(` \u2713 Found ${sections.length} sections`);
|
|
458
|
+
const scqa = this.extractSCQA(text);
|
|
459
|
+
const sparkline = this.extractSparkline(text);
|
|
460
|
+
const keyMessages = this.extractKeyMessages(text);
|
|
461
|
+
const dataPoints = this.extractDataPoints(text);
|
|
462
|
+
console.log(` \u2713 Found ${dataPoints.length} data points`);
|
|
463
|
+
const titles = sections.map((s) => s.header).filter((h) => h.length > 0);
|
|
464
|
+
const starMoments = this.extractStarMoments(text);
|
|
465
|
+
const estimatedSlideCount = Math.max(5, sections.length + 3);
|
|
195
466
|
return {
|
|
196
|
-
|
|
197
|
-
|
|
467
|
+
detectedType,
|
|
468
|
+
title,
|
|
469
|
+
sections,
|
|
198
470
|
keyMessages,
|
|
471
|
+
dataPoints,
|
|
472
|
+
scqa: {
|
|
473
|
+
situation: scqa.situation || "",
|
|
474
|
+
complication: scqa.complication || "",
|
|
475
|
+
question: scqa.question || "",
|
|
476
|
+
answer: scqa.answer || ""
|
|
477
|
+
},
|
|
478
|
+
sparkline: {
|
|
479
|
+
whatIs: sparkline.callToAction ? [sparkline.callToAction] : [],
|
|
480
|
+
whatCouldBe: keyMessages,
|
|
481
|
+
callToAdventure: sparkline.callToAction || ""
|
|
482
|
+
},
|
|
199
483
|
titles,
|
|
200
484
|
starMoments,
|
|
201
485
|
estimatedSlideCount
|
|
202
486
|
};
|
|
203
487
|
}
|
|
204
488
|
/**
|
|
205
|
-
* Parse content based on
|
|
489
|
+
* Parse content based on type
|
|
206
490
|
*/
|
|
207
491
|
parseContent(content, contentType) {
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
return this.parseMarkdown(content);
|
|
211
|
-
case "json":
|
|
212
|
-
return this.parseJSON(content);
|
|
213
|
-
case "yaml":
|
|
214
|
-
return this.parseYAML(content);
|
|
215
|
-
case "text":
|
|
216
|
-
default:
|
|
217
|
-
return content;
|
|
492
|
+
if (contentType === "markdown" || content.includes("#") || content.includes("- ")) {
|
|
493
|
+
return content;
|
|
218
494
|
}
|
|
495
|
+
return content;
|
|
219
496
|
}
|
|
220
497
|
/**
|
|
221
|
-
*
|
|
222
|
-
*/
|
|
223
|
-
parseMarkdown(content) {
|
|
224
|
-
let text = content;
|
|
225
|
-
text = text.replace(/^#{1,6}\s+(.+)$/gm, "\n[HEADER] $1\n");
|
|
226
|
-
text = text.replace(/^[-*+]\s+(.+)$/gm, "[BULLET] $1");
|
|
227
|
-
text = text.replace(/^\d+\.\s+(.+)$/gm, "[NUMBERED] $1");
|
|
228
|
-
text = text.replace(/\*\*(.+?)\*\*/g, "[EMPHASIS] $1 [/EMPHASIS]");
|
|
229
|
-
text = text.replace(/\*(.+?)\*/g, "$1");
|
|
230
|
-
text = text.replace(/```[\s\S]*?```/g, "[CODE BLOCK]");
|
|
231
|
-
text = text.replace(/`(.+?)`/g, "$1");
|
|
232
|
-
text = text.replace(/\[(.+?)\]\(.+?\)/g, "$1");
|
|
233
|
-
text = text.replace(/!\[.*?\]\(.+?\)/g, "[IMAGE]");
|
|
234
|
-
return text.trim();
|
|
235
|
-
}
|
|
236
|
-
/**
|
|
237
|
-
* Parse JSON content.
|
|
498
|
+
* Extract the main title from content
|
|
238
499
|
*/
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
return
|
|
243
|
-
}
|
|
244
|
-
|
|
500
|
+
extractTitle(text) {
|
|
501
|
+
const h1Match = text.match(/^#\s+(.+)$/m);
|
|
502
|
+
if (h1Match && h1Match[1]) {
|
|
503
|
+
return h1Match[1].replace(/\*\*/g, "").trim();
|
|
504
|
+
}
|
|
505
|
+
const lines = text.split("\n").filter((l) => l.trim().length > 0);
|
|
506
|
+
if (lines.length > 0 && lines[0]) {
|
|
507
|
+
return lines[0].replace(/^#+\s*/, "").replace(/\*\*/g, "").trim().slice(0, 80);
|
|
245
508
|
}
|
|
509
|
+
return "Presentation";
|
|
246
510
|
}
|
|
247
511
|
/**
|
|
248
|
-
*
|
|
512
|
+
* Detect presentation type from content signals
|
|
249
513
|
*/
|
|
250
|
-
|
|
251
|
-
const
|
|
252
|
-
const
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
514
|
+
detectPresentationType(text) {
|
|
515
|
+
const lowerText = text.toLowerCase();
|
|
516
|
+
const scores = {
|
|
517
|
+
ted_keynote: 0,
|
|
518
|
+
sales_pitch: 0,
|
|
519
|
+
consulting_deck: 0,
|
|
520
|
+
investment_banking: 0,
|
|
521
|
+
investor_pitch: 0,
|
|
522
|
+
technical_presentation: 0,
|
|
523
|
+
all_hands: 0
|
|
524
|
+
};
|
|
525
|
+
for (const [type, signals] of Object.entries(this.typeSignals)) {
|
|
526
|
+
for (const signal of signals) {
|
|
527
|
+
if (lowerText.includes(signal)) {
|
|
528
|
+
scores[type]++;
|
|
529
|
+
}
|
|
257
530
|
}
|
|
258
531
|
}
|
|
259
|
-
|
|
532
|
+
let maxScore = 0;
|
|
533
|
+
let detectedType = "consulting_deck";
|
|
534
|
+
for (const [type, score] of Object.entries(scores)) {
|
|
535
|
+
if (score > maxScore) {
|
|
536
|
+
maxScore = score;
|
|
537
|
+
detectedType = type;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
return detectedType;
|
|
260
541
|
}
|
|
261
542
|
/**
|
|
262
|
-
*
|
|
543
|
+
* Extract sections from content (headers, bullets, content)
|
|
263
544
|
*/
|
|
264
|
-
|
|
265
|
-
const
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
545
|
+
extractSections(text) {
|
|
546
|
+
const sections = [];
|
|
547
|
+
const lines = text.split("\n");
|
|
548
|
+
let currentSection = null;
|
|
549
|
+
let contentLines = [];
|
|
550
|
+
for (const line of lines) {
|
|
551
|
+
const trimmedLine = line.trim();
|
|
552
|
+
const headerMatch = trimmedLine.match(/^(#{1,6})\s+(.+)$/);
|
|
553
|
+
if (headerMatch) {
|
|
554
|
+
if (currentSection) {
|
|
555
|
+
currentSection.content = contentLines.join("\n").trim();
|
|
556
|
+
if (currentSection.header || currentSection.content || currentSection.bullets.length > 0) {
|
|
557
|
+
sections.push(currentSection);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
currentSection = {
|
|
561
|
+
header: (headerMatch[2] || "").replace(/\*\*/g, "").trim(),
|
|
562
|
+
level: (headerMatch[1] || "").length,
|
|
563
|
+
content: "",
|
|
564
|
+
bullets: [],
|
|
565
|
+
metrics: []
|
|
566
|
+
};
|
|
567
|
+
contentLines = [];
|
|
568
|
+
continue;
|
|
569
|
+
}
|
|
570
|
+
const bulletMatch = trimmedLine.match(/^[-*+]\s+(.+)$/);
|
|
571
|
+
if (bulletMatch && bulletMatch[1] && currentSection) {
|
|
572
|
+
currentSection.bullets.push(bulletMatch[1]);
|
|
573
|
+
continue;
|
|
574
|
+
}
|
|
575
|
+
const numberedMatch = trimmedLine.match(/^\d+\.\s+(.+)$/);
|
|
576
|
+
if (numberedMatch && numberedMatch[1] && currentSection) {
|
|
577
|
+
currentSection.bullets.push(numberedMatch[1]);
|
|
578
|
+
continue;
|
|
579
|
+
}
|
|
580
|
+
const metricMatch = trimmedLine.match(/\$?([\d,]+\.?\d*)[%]?\s*[-–—:]\s*(.+)/);
|
|
581
|
+
if (metricMatch && metricMatch[1] && metricMatch[2] && currentSection) {
|
|
582
|
+
currentSection.metrics.push({
|
|
583
|
+
value: metricMatch[1],
|
|
584
|
+
label: metricMatch[2].slice(0, 50)
|
|
585
|
+
});
|
|
586
|
+
continue;
|
|
272
587
|
}
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
588
|
+
if (trimmedLine.includes("|") && currentSection) {
|
|
589
|
+
const cells = trimmedLine.split("|").map((c) => c.trim()).filter((c) => c && !c.match(/^-+$/));
|
|
590
|
+
if (cells.length >= 2) {
|
|
591
|
+
const value = cells.find((c) => c.match(/^\$?[\d,]+\.?\d*[%]?$/));
|
|
592
|
+
const label = cells.find((c) => !c.match(/^\$?[\d,]+\.?\d*[%]?$/) && c.length > 2);
|
|
593
|
+
if (value && label) {
|
|
594
|
+
currentSection.metrics.push({ value, label: label.slice(0, 50) });
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
continue;
|
|
598
|
+
}
|
|
599
|
+
if (trimmedLine && currentSection) {
|
|
600
|
+
contentLines.push(trimmedLine);
|
|
277
601
|
}
|
|
278
|
-
} else if (obj !== null && obj !== void 0) {
|
|
279
|
-
parts.push(String(obj));
|
|
280
602
|
}
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
return
|
|
288
|
-
}
|
|
289
|
-
/**
|
|
290
|
-
* Split text into sentences.
|
|
291
|
-
*/
|
|
292
|
-
splitIntoSentences(text) {
|
|
293
|
-
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");
|
|
294
|
-
return cleaned.split(/[.!?]+/).map((s) => s.trim()).filter((s) => s.length > 10);
|
|
603
|
+
if (currentSection) {
|
|
604
|
+
currentSection.content = contentLines.join("\n").trim();
|
|
605
|
+
if (currentSection.header || currentSection.content || currentSection.bullets.length > 0) {
|
|
606
|
+
sections.push(currentSection);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
return sections;
|
|
295
610
|
}
|
|
296
611
|
/**
|
|
297
|
-
* Extract SCQA structure (Barbara Minto
|
|
612
|
+
* Extract SCQA structure (Barbara Minto)
|
|
298
613
|
*/
|
|
299
|
-
extractSCQA(
|
|
614
|
+
extractSCQA(text) {
|
|
615
|
+
const paragraphs = text.split(/\n\n+/).filter((p) => p.trim());
|
|
300
616
|
let situation = "";
|
|
301
617
|
let complication = "";
|
|
302
618
|
let question = "";
|
|
303
619
|
let answer = "";
|
|
304
620
|
for (const para of paragraphs.slice(0, 3)) {
|
|
305
|
-
if (this.containsSignals(para, this.situationSignals)) {
|
|
306
|
-
situation = this.
|
|
621
|
+
if (this.containsSignals(para.toLowerCase(), this.situationSignals)) {
|
|
622
|
+
situation = this.extractFirstSentence(para);
|
|
307
623
|
break;
|
|
308
624
|
}
|
|
309
625
|
}
|
|
310
626
|
for (const para of paragraphs) {
|
|
311
|
-
if (this.containsSignals(para, this.complicationSignals)) {
|
|
312
|
-
complication = this.
|
|
627
|
+
if (this.containsSignals(para.toLowerCase(), this.complicationSignals)) {
|
|
628
|
+
complication = this.extractFirstSentence(para);
|
|
313
629
|
break;
|
|
314
630
|
}
|
|
315
631
|
}
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
632
|
+
const middleStart = Math.floor(paragraphs.length * 0.2);
|
|
633
|
+
const middleEnd = Math.floor(paragraphs.length * 0.8);
|
|
634
|
+
for (const para of paragraphs.slice(middleStart, middleEnd)) {
|
|
635
|
+
const lowerPara = para.toLowerCase();
|
|
636
|
+
if (this.containsSignals(lowerPara, this.answerSignals) && !this.containsSignals(lowerPara, this.ctaSignals)) {
|
|
637
|
+
answer = this.extractFirstSentence(para);
|
|
319
638
|
break;
|
|
320
639
|
}
|
|
321
640
|
}
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
answer = this.extractRelevantSentence(para, this.answerSignals);
|
|
325
|
-
break;
|
|
326
|
-
}
|
|
641
|
+
if (!situation && paragraphs.length > 0 && paragraphs[0]) {
|
|
642
|
+
situation = this.extractFirstSentence(paragraphs[0]);
|
|
327
643
|
}
|
|
328
|
-
if (!
|
|
329
|
-
|
|
644
|
+
if (!answer && paragraphs.length > 2) {
|
|
645
|
+
for (const para of paragraphs.slice(1, Math.floor(paragraphs.length * 0.5))) {
|
|
646
|
+
const lowerPara = para.toLowerCase();
|
|
647
|
+
if (lowerPara.includes("recommend") || lowerPara.includes("strategy") || lowerPara.includes("solution") || lowerPara.includes("approach")) {
|
|
648
|
+
answer = this.extractFirstSentence(para);
|
|
649
|
+
break;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
330
652
|
}
|
|
331
|
-
if (
|
|
332
|
-
|
|
333
|
-
answer = lastPara ? this.truncateToSentence(lastPara, 150) : "";
|
|
653
|
+
if (complication && !question) {
|
|
654
|
+
question = `How do we address: ${complication.slice(0, 80)}?`;
|
|
334
655
|
}
|
|
335
656
|
return { situation, complication, question, answer };
|
|
336
657
|
}
|
|
337
658
|
/**
|
|
338
|
-
* Extract
|
|
659
|
+
* Extract STAR moments (Something They'll Always Remember)
|
|
660
|
+
* Per Nancy Duarte: These should be memorable, COMPLETE thoughts with impact
|
|
661
|
+
*
|
|
662
|
+
* CRITICAL REQUIREMENTS:
|
|
663
|
+
* - Must be complete sentences with subject + verb structure
|
|
664
|
+
* - Must have 5+ words minimum (no fragments!)
|
|
665
|
+
* - Must be 30+ characters
|
|
666
|
+
* - Must contain a verb
|
|
667
|
+
* - NOT fragments like "significant growth" or "cloud-first strategy"
|
|
668
|
+
* - NOT headers or topic labels
|
|
339
669
|
*/
|
|
340
|
-
|
|
341
|
-
const
|
|
342
|
-
const
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
670
|
+
extractStarMoments(text) {
|
|
671
|
+
const starMoments = [];
|
|
672
|
+
const usedPhrases = /* @__PURE__ */ new Set();
|
|
673
|
+
const fragmentPatterns = [
|
|
674
|
+
/^[a-z]+-[a-z]+\s+(strategy|approach|solution|platform|architecture)$/i,
|
|
675
|
+
// "cloud-first strategy"
|
|
676
|
+
/^(significant|substantial|dramatic|rapid|strong)\s+(growth|increase|decline|change)$/i,
|
|
677
|
+
// "significant growth"
|
|
678
|
+
/^(digital|cloud|data|ai)\s+(transformation|strategy|platform|solution)$/i,
|
|
679
|
+
// "digital transformation"
|
|
680
|
+
/^(our|your|the|a)\s+\w+\s*$/i,
|
|
681
|
+
// "our solution", "the platform"
|
|
682
|
+
/^[a-z]+\s+[a-z]+$/i
|
|
683
|
+
// Any two-word phrase
|
|
684
|
+
];
|
|
685
|
+
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;
|
|
686
|
+
const boldMatches = text.match(/\*\*([^*]+)\*\*/g);
|
|
687
|
+
if (boldMatches) {
|
|
688
|
+
for (const match of boldMatches) {
|
|
689
|
+
const cleaned = match.replace(/\*\*/g, "").trim();
|
|
690
|
+
const lowerCleaned = cleaned.toLowerCase();
|
|
691
|
+
const wordCount = cleaned.split(/\s+/).length;
|
|
692
|
+
const normalized = cleaned.toLowerCase().slice(0, 30);
|
|
693
|
+
const hasVerb = verbPatterns.test(cleaned);
|
|
694
|
+
const isLongEnough = wordCount >= 5 && cleaned.length >= 30;
|
|
695
|
+
const isNotFragment = !fragmentPatterns.some((p) => p.test(cleaned));
|
|
696
|
+
const isNotHeader = !/^(The |Our |Your |Next |Key |Overview|Introduction|Conclusion|Summary|Background)/i.test(cleaned);
|
|
697
|
+
const isNotCTA = !this.containsSignals(lowerCleaned, this.ctaSignals);
|
|
698
|
+
const hasMetricContext = /\d+[%xX]|\$[\d,]+/.test(cleaned) && wordCount >= 5;
|
|
699
|
+
if ((hasVerb || hasMetricContext) && isLongEnough && isNotFragment && isNotHeader && isNotCTA && wordCount <= 25 && !usedPhrases.has(normalized)) {
|
|
700
|
+
starMoments.push(cleaned);
|
|
701
|
+
usedPhrases.add(normalized);
|
|
702
|
+
if (starMoments.length >= 3) break;
|
|
703
|
+
}
|
|
348
704
|
}
|
|
349
|
-
|
|
350
|
-
|
|
705
|
+
}
|
|
706
|
+
if (starMoments.length < 3) {
|
|
707
|
+
const cleanedText = text.replace(/^#+\s+.+$/gm, "").replace(/\*\*/g, "").replace(/\|/g, " ").replace(/-{3,}/g, "").replace(/\n{2,}/g, "\n").trim();
|
|
708
|
+
const sentences = cleanedText.match(/[^.!?]+[.!?]/g) || [];
|
|
709
|
+
for (const sentence of sentences) {
|
|
710
|
+
const cleaned = sentence.trim();
|
|
711
|
+
const normalized = cleaned.toLowerCase().slice(0, 30);
|
|
712
|
+
const wordCount = cleaned.split(/\s+/).length;
|
|
713
|
+
const hasHighImpact = /(\d{2,3}%|\d+x|billion|million|\$[\d,]+\s*(million|billion)?)/i.test(cleaned);
|
|
714
|
+
const hasVerb = verbPatterns.test(cleaned);
|
|
715
|
+
if (hasHighImpact && hasVerb && wordCount >= 6 && wordCount <= 25 && cleaned.length >= 40 && cleaned.length <= 200 && !usedPhrases.has(normalized) && !this.containsSignals(cleaned.toLowerCase(), this.ctaSignals)) {
|
|
716
|
+
starMoments.push(cleaned);
|
|
717
|
+
usedPhrases.add(normalized);
|
|
718
|
+
if (starMoments.length >= 3) break;
|
|
719
|
+
}
|
|
351
720
|
}
|
|
352
721
|
}
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
722
|
+
return starMoments.slice(0, 3);
|
|
723
|
+
}
|
|
724
|
+
/**
|
|
725
|
+
* Extract Sparkline elements (Nancy Duarte)
|
|
726
|
+
*/
|
|
727
|
+
extractSparkline(text) {
|
|
728
|
+
const paragraphs = text.split(/\n\n+/).filter((p) => p.trim());
|
|
729
|
+
let callToAction = "";
|
|
730
|
+
const ctaSignals = ["next steps", "call to action", "take action", "start now", "join us", "contact"];
|
|
731
|
+
for (const para of paragraphs.slice(-3)) {
|
|
732
|
+
if (this.containsSignals(para.toLowerCase(), ctaSignals)) {
|
|
733
|
+
callToAction = this.extractFirstSentence(para);
|
|
356
734
|
break;
|
|
357
735
|
}
|
|
358
736
|
}
|
|
359
|
-
return {
|
|
737
|
+
return { callToAction };
|
|
360
738
|
}
|
|
361
739
|
/**
|
|
362
|
-
* Extract key messages
|
|
740
|
+
* Extract key messages - the actual insights from the content
|
|
741
|
+
* Per Carmine Gallo: Rule of Three - max 3 key messages
|
|
742
|
+
* Per Barbara Minto: Messages should be actionable conclusions, not topics
|
|
363
743
|
*/
|
|
364
|
-
extractKeyMessages(text
|
|
744
|
+
extractKeyMessages(text) {
|
|
365
745
|
const messages = [];
|
|
366
|
-
const
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
746
|
+
const usedPhrases = /* @__PURE__ */ new Set();
|
|
747
|
+
let cleanText = text.replace(/^#+\s+.+$/gm, "").replace(/\[.+?\]/g, "").replace(/\*\*/g, "");
|
|
748
|
+
cleanText = cleanText.replace(/(\d)\.(\d)/g, "$1<DECIMAL>$2");
|
|
749
|
+
const rawSentences = cleanText.match(/[^.!?]+[.!?]/g) || [];
|
|
750
|
+
const sentences = rawSentences.map((s) => s.replace(/<DECIMAL>/g, "."));
|
|
751
|
+
const insightSignals = [
|
|
752
|
+
"we recommend",
|
|
753
|
+
"we suggest",
|
|
754
|
+
"our strategy",
|
|
755
|
+
"the solution",
|
|
756
|
+
"key finding",
|
|
757
|
+
"result",
|
|
758
|
+
"achieve",
|
|
759
|
+
"improve",
|
|
760
|
+
"increase",
|
|
761
|
+
"decrease",
|
|
762
|
+
"expect",
|
|
763
|
+
"roi",
|
|
764
|
+
"benefit",
|
|
765
|
+
"opportunity",
|
|
766
|
+
"transform"
|
|
767
|
+
];
|
|
768
|
+
for (const sentence of sentences) {
|
|
769
|
+
const cleaned = sentence.trim();
|
|
770
|
+
const normalized = cleaned.toLowerCase().slice(0, 30);
|
|
771
|
+
if (cleaned.length > 30 && cleaned.length < 150 && !usedPhrases.has(normalized) && !this.containsSignals(cleaned.toLowerCase(), this.ctaSignals)) {
|
|
772
|
+
if (this.containsSignals(cleaned.toLowerCase(), insightSignals)) {
|
|
773
|
+
messages.push(cleaned);
|
|
774
|
+
usedPhrases.add(normalized);
|
|
775
|
+
if (messages.length >= 3) break;
|
|
381
776
|
}
|
|
382
777
|
}
|
|
383
778
|
}
|
|
384
779
|
if (messages.length < 3) {
|
|
385
|
-
const
|
|
386
|
-
|
|
387
|
-
|
|
780
|
+
for (const sentence of sentences) {
|
|
781
|
+
const cleaned = sentence.trim();
|
|
782
|
+
const normalized = cleaned.toLowerCase().slice(0, 30);
|
|
783
|
+
if (cleaned.length > 25 && cleaned.length < 150 && !usedPhrases.has(normalized) && /\d+[%xX]|\$[\d,]+/.test(cleaned)) {
|
|
784
|
+
messages.push(cleaned);
|
|
785
|
+
usedPhrases.add(normalized);
|
|
786
|
+
if (messages.length >= 3) break;
|
|
787
|
+
}
|
|
388
788
|
}
|
|
389
789
|
}
|
|
390
|
-
if (messages.length <
|
|
790
|
+
if (messages.length < 2) {
|
|
391
791
|
for (const sentence of sentences) {
|
|
392
|
-
|
|
393
|
-
|
|
792
|
+
const cleaned = sentence.trim();
|
|
793
|
+
const wordCount = cleaned.split(/\s+/).length;
|
|
794
|
+
const normalized = cleaned.toLowerCase().slice(0, 30);
|
|
795
|
+
if (wordCount >= 6 && wordCount <= 25 && !usedPhrases.has(normalized) && /\b(is|are|will|can|should|must|has|have|provides?|enables?|allows?)\b/.test(cleaned.toLowerCase())) {
|
|
796
|
+
messages.push(cleaned);
|
|
797
|
+
usedPhrases.add(normalized);
|
|
394
798
|
if (messages.length >= 3) break;
|
|
395
799
|
}
|
|
396
800
|
}
|
|
@@ -398,195 +802,253 @@ var ContentAnalyzer = class {
|
|
|
398
802
|
return messages.slice(0, 3);
|
|
399
803
|
}
|
|
400
804
|
/**
|
|
401
|
-
*
|
|
805
|
+
* Extract data points (metrics with values)
|
|
806
|
+
* IMPROVED: Smarter label extraction that understands markdown tables
|
|
402
807
|
*/
|
|
403
|
-
|
|
404
|
-
const
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
808
|
+
extractDataPoints(text) {
|
|
809
|
+
const dataPoints = [];
|
|
810
|
+
const usedValues = /* @__PURE__ */ new Set();
|
|
811
|
+
const tableLines = text.split("\n").filter((line) => line.includes("|"));
|
|
812
|
+
if (tableLines.length >= 3) {
|
|
813
|
+
const dataRows = tableLines.filter((line) => !line.match(/^[\s|:-]+$/));
|
|
814
|
+
if (dataRows.length >= 2) {
|
|
815
|
+
for (const row of dataRows.slice(1)) {
|
|
816
|
+
const cells = row.split("|").map((c) => c.trim()).filter((c) => c.length > 0);
|
|
817
|
+
if (cells.length >= 2) {
|
|
818
|
+
const valueCell = cells.find((c) => /^\$?[\d,]+\.?\d*[MBK%]?$/.test(c.replace(/[,$]/g, "")));
|
|
819
|
+
const labelCell = cells[0];
|
|
820
|
+
if (valueCell && labelCell && !usedValues.has(valueCell)) {
|
|
821
|
+
usedValues.add(valueCell);
|
|
822
|
+
dataPoints.push({ value: valueCell, label: labelCell.slice(0, 40) });
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
}
|
|
409
826
|
}
|
|
410
827
|
}
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
828
|
+
const lines = text.split("\n");
|
|
829
|
+
for (const line of lines) {
|
|
830
|
+
if (line.includes("|")) continue;
|
|
831
|
+
const percentMatch = line.match(/(\w+(?:\s+\w+){0,4})\s+(?:by\s+)?(\d+(?:\.\d+)?%)/i);
|
|
832
|
+
if (percentMatch && percentMatch[2] && percentMatch[1] && !usedValues.has(percentMatch[2])) {
|
|
833
|
+
usedValues.add(percentMatch[2]);
|
|
834
|
+
dataPoints.push({ value: percentMatch[2], label: percentMatch[1].slice(0, 40) });
|
|
835
|
+
}
|
|
836
|
+
const dollarMatch = line.match(/\$(\d+(?:\.\d+)?)\s*(million|billion|M|B|K)?/i);
|
|
837
|
+
if (dollarMatch && dataPoints.length < 6) {
|
|
838
|
+
const fullValue = "$" + dollarMatch[1] + (dollarMatch[2] ? " " + dollarMatch[2] : "");
|
|
839
|
+
if (!usedValues.has(fullValue)) {
|
|
840
|
+
usedValues.add(fullValue);
|
|
841
|
+
const label = this.extractLabelFromLine(line, fullValue);
|
|
842
|
+
dataPoints.push({ value: fullValue, label });
|
|
418
843
|
}
|
|
419
844
|
}
|
|
420
845
|
}
|
|
421
|
-
return
|
|
846
|
+
return dataPoints.slice(0, 4);
|
|
422
847
|
}
|
|
423
848
|
/**
|
|
424
|
-
*
|
|
849
|
+
* Extract a meaningful label from a line containing a metric
|
|
425
850
|
*/
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
const
|
|
429
|
-
const
|
|
430
|
-
|
|
431
|
-
return this.capitalizeFirst(title);
|
|
432
|
-
}
|
|
433
|
-
if (title.toLowerCase().includes("should")) {
|
|
434
|
-
title = title.replace(/we should|you should|should/gi, "").trim();
|
|
435
|
-
return this.capitalizeFirst(title);
|
|
436
|
-
}
|
|
437
|
-
if (title.toLowerCase().includes("need to")) {
|
|
438
|
-
title = title.replace(/we need to|you need to|need to/gi, "").trim();
|
|
439
|
-
return this.capitalizeFirst(title);
|
|
440
|
-
}
|
|
441
|
-
if (title.length < 50) {
|
|
442
|
-
return title;
|
|
443
|
-
}
|
|
444
|
-
return this.truncateToWords(title, 8);
|
|
851
|
+
extractLabelFromLine(line, value) {
|
|
852
|
+
const cleaned = line.replace(/\*\*/g, "").replace(/\|/g, " ").trim();
|
|
853
|
+
const beforeValue = cleaned.split(value)[0] || "";
|
|
854
|
+
const words = beforeValue.split(/\s+/).filter((w) => w.length > 2);
|
|
855
|
+
return words.slice(-4).join(" ").slice(0, 40) || "Value";
|
|
445
856
|
}
|
|
446
857
|
/**
|
|
447
|
-
*
|
|
858
|
+
* Check if text contains any of the signals
|
|
448
859
|
*/
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
const starSignals = [
|
|
452
|
-
"surprising",
|
|
453
|
-
"amazing",
|
|
454
|
-
"incredible",
|
|
455
|
-
"remarkable",
|
|
456
|
-
"stunning",
|
|
457
|
-
"imagine",
|
|
458
|
-
"what if",
|
|
459
|
-
"breakthrough",
|
|
460
|
-
"revolutionary",
|
|
461
|
-
"never before",
|
|
462
|
-
"first time",
|
|
463
|
-
"unprecedented",
|
|
464
|
-
"game-changing",
|
|
465
|
-
"dramatic"
|
|
466
|
-
];
|
|
467
|
-
for (const para of paragraphs) {
|
|
468
|
-
if (this.containsSignals(para.toLowerCase(), starSignals)) {
|
|
469
|
-
const moment = this.truncateToSentence(para, 120);
|
|
470
|
-
if (moment.length > 20) {
|
|
471
|
-
starMoments.push(moment);
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
const statPattern = /\d+[%xX]|\$[\d,]+(?:\s*(?:million|billion|trillion))?|\d+(?:\s*(?:million|billion|trillion))/g;
|
|
476
|
-
for (const para of paragraphs) {
|
|
477
|
-
if (statPattern.test(para) && starMoments.length < 5) {
|
|
478
|
-
const moment = this.truncateToSentence(para, 100);
|
|
479
|
-
if (!starMoments.includes(moment)) {
|
|
480
|
-
starMoments.push(moment);
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
return starMoments.slice(0, 5);
|
|
860
|
+
containsSignals(text, signals) {
|
|
861
|
+
return signals.some((signal) => text.includes(signal));
|
|
485
862
|
}
|
|
486
863
|
/**
|
|
487
|
-
*
|
|
864
|
+
* Extract first meaningful sentence from text
|
|
865
|
+
* CRITICAL: Must strip headers, CTA text, and fragments
|
|
488
866
|
*/
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
const
|
|
493
|
-
const wordBasedEstimate = Math.ceil(wordCount / 35);
|
|
494
|
-
const estimate = Math.max(
|
|
495
|
-
5,
|
|
496
|
-
// Minimum 5 slides
|
|
497
|
-
Math.ceil((wordBasedEstimate + headerCount + bulletGroups) / 2)
|
|
498
|
-
);
|
|
499
|
-
return Math.min(estimate, 30);
|
|
500
|
-
}
|
|
501
|
-
// === Helper Methods ===
|
|
502
|
-
containsSignals(text, signals) {
|
|
503
|
-
const lowerText = text.toLowerCase();
|
|
504
|
-
return signals.some((signal) => lowerText.includes(signal));
|
|
505
|
-
}
|
|
506
|
-
extractRelevantSentence(paragraph, signals) {
|
|
507
|
-
const sentences = paragraph.split(/[.!?]+/);
|
|
867
|
+
extractFirstSentence(text) {
|
|
868
|
+
let cleaned = text.replace(/^#+\s+.+$/gm, "").replace(/\*\*/g, "").replace(/\|/g, " ").replace(/-{3,}/g, "").replace(/\n{2,}/g, "\n").trim();
|
|
869
|
+
cleaned = cleaned.replace(/^(Overview|Introduction|The Problem|Our Solution|Next Steps|Conclusion|Summary|Background|Context|Recommendation)\s*:?\s*/i, "").trim();
|
|
870
|
+
const sentences = cleaned.match(/[^.!?]+[.!?]/g) || [];
|
|
508
871
|
for (const sentence of sentences) {
|
|
509
|
-
|
|
510
|
-
|
|
872
|
+
const trimmed = sentence.trim();
|
|
873
|
+
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)) {
|
|
874
|
+
return trimmed.slice(0, 150);
|
|
511
875
|
}
|
|
512
876
|
}
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
truncateToSentence(text, maxLength) {
|
|
516
|
-
if (text.length <= maxLength) {
|
|
517
|
-
return text.trim();
|
|
518
|
-
}
|
|
519
|
-
const truncated = text.slice(0, maxLength);
|
|
520
|
-
const lastPeriod = truncated.lastIndexOf(".");
|
|
521
|
-
const lastQuestion = truncated.lastIndexOf("?");
|
|
522
|
-
const lastExclaim = truncated.lastIndexOf("!");
|
|
523
|
-
const lastBoundary = Math.max(lastPeriod, lastQuestion, lastExclaim);
|
|
524
|
-
if (lastBoundary > maxLength * 0.5) {
|
|
525
|
-
return text.slice(0, lastBoundary + 1).trim();
|
|
526
|
-
}
|
|
527
|
-
return truncated.trim() + "...";
|
|
528
|
-
}
|
|
529
|
-
truncateToWords(text, maxWords) {
|
|
530
|
-
const words = text.split(/\s+/);
|
|
531
|
-
if (words.length <= maxWords) {
|
|
532
|
-
return text;
|
|
533
|
-
}
|
|
534
|
-
return words.slice(0, maxWords).join(" ");
|
|
535
|
-
}
|
|
536
|
-
capitalizeFirst(text) {
|
|
537
|
-
if (!text) return "";
|
|
538
|
-
return text.charAt(0).toUpperCase() + text.slice(1);
|
|
877
|
+
const fallback = cleaned.slice(0, 150);
|
|
878
|
+
return fallback.length >= 20 ? fallback : "";
|
|
539
879
|
}
|
|
540
880
|
};
|
|
541
881
|
|
|
542
882
|
// src/core/SlideFactory.ts
|
|
543
883
|
var SlideFactory = class {
|
|
544
884
|
templates;
|
|
885
|
+
usedContent = /* @__PURE__ */ new Set();
|
|
545
886
|
constructor() {
|
|
546
887
|
this.templates = this.initializeTemplates();
|
|
547
888
|
}
|
|
889
|
+
/**
|
|
890
|
+
* Check if content has already been used (deduplication)
|
|
891
|
+
*/
|
|
892
|
+
isContentUsed(content) {
|
|
893
|
+
if (!content) return true;
|
|
894
|
+
const normalized = content.toLowerCase().replace(/[^a-z0-9]/g, "").slice(0, 50);
|
|
895
|
+
if (this.usedContent.has(normalized)) {
|
|
896
|
+
return true;
|
|
897
|
+
}
|
|
898
|
+
this.usedContent.add(normalized);
|
|
899
|
+
return false;
|
|
900
|
+
}
|
|
548
901
|
/**
|
|
549
902
|
* Create slides from analyzed content.
|
|
903
|
+
*
|
|
904
|
+
* ARCHITECTURE (per KB expert methodologies):
|
|
905
|
+
* 1. Title slide - always first
|
|
906
|
+
* 2. Agenda slide - business mode only with 3+ sections
|
|
907
|
+
* 3. SCQA slides - Situation, Complication (per Minto)
|
|
908
|
+
* 4. Content slides - from sections with bullets/content
|
|
909
|
+
* 5. Metrics slide - if data points exist
|
|
910
|
+
* 6. Solution slide - SCQA answer
|
|
911
|
+
* 7. STAR moments - only if high-quality (per Duarte)
|
|
912
|
+
* 8. CTA slide - if call to action exists
|
|
913
|
+
* 9. Thank you slide - always last
|
|
550
914
|
*/
|
|
551
915
|
async createSlides(analysis, mode) {
|
|
552
916
|
const slides = [];
|
|
553
917
|
let slideIndex = 0;
|
|
918
|
+
this.usedContent.clear();
|
|
554
919
|
slides.push(this.createTitleSlide(slideIndex++, analysis));
|
|
555
|
-
|
|
920
|
+
this.isContentUsed(analysis.titles[0] ?? "");
|
|
921
|
+
const substantiveSections = analysis.sections.filter(
|
|
922
|
+
(s) => s.level === 2 && (s.bullets.length > 0 || s.content.length > 50)
|
|
923
|
+
);
|
|
924
|
+
if (mode === "business" && substantiveSections.length >= 3) {
|
|
556
925
|
slides.push(this.createAgendaSlide(slideIndex++, analysis));
|
|
557
926
|
}
|
|
558
|
-
if (analysis.scqa.situation) {
|
|
927
|
+
if (analysis.scqa.situation && analysis.scqa.situation.length > 30 && !this.isContentUsed(analysis.scqa.situation)) {
|
|
559
928
|
slides.push(this.createContextSlide(slideIndex++, analysis, mode));
|
|
560
929
|
}
|
|
561
|
-
if (analysis.scqa.complication) {
|
|
930
|
+
if (analysis.scqa.complication && analysis.scqa.complication.length > 30 && !this.isContentUsed(analysis.scqa.complication)) {
|
|
562
931
|
slides.push(this.createProblemSlide(slideIndex++, analysis, mode));
|
|
563
932
|
}
|
|
933
|
+
for (const section of substantiveSections.slice(0, 4)) {
|
|
934
|
+
const headerUsed = this.isContentUsed(section.header);
|
|
935
|
+
const contentUsed = section.content.length > 30 && this.isContentUsed(section.content.slice(0, 80));
|
|
936
|
+
if (!headerUsed && !contentUsed) {
|
|
937
|
+
if (section.bullets.length > 0) {
|
|
938
|
+
slides.push(this.createSectionBulletSlide(slideIndex++, section, mode));
|
|
939
|
+
} else if (section.content.length > 50) {
|
|
940
|
+
slides.push(this.createSectionContentSlide(slideIndex++, section, mode));
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
}
|
|
564
944
|
for (const message of analysis.keyMessages) {
|
|
565
|
-
|
|
945
|
+
const wordCount = message.split(/\s+/).length;
|
|
946
|
+
if (wordCount >= 6 && !/^(The |Our |Your |Overview|Introduction|Conclusion)/i.test(message) && !this.isContentUsed(message)) {
|
|
947
|
+
slides.push(this.createMessageSlide(slideIndex++, message, mode));
|
|
948
|
+
}
|
|
566
949
|
}
|
|
567
|
-
|
|
568
|
-
slides.push(this.
|
|
950
|
+
if (analysis.dataPoints.length >= 2) {
|
|
951
|
+
slides.push(this.createMetricsSlide(slideIndex++, analysis.dataPoints));
|
|
569
952
|
}
|
|
570
|
-
if (analysis.scqa.answer) {
|
|
953
|
+
if (analysis.scqa.answer && analysis.scqa.answer.length > 30 && !this.isContentUsed(analysis.scqa.answer)) {
|
|
571
954
|
slides.push(this.createSolutionSlide(slideIndex++, analysis, mode));
|
|
572
955
|
}
|
|
573
|
-
|
|
956
|
+
const verbPattern = /\b(is|are|was|were|will|can|should|must|has|have|provides?|enables?|allows?|achieves?|exceeds?|results?|generates?|delivers?|creates?)\b/i;
|
|
957
|
+
for (const starMoment of analysis.starMoments.slice(0, 2)) {
|
|
958
|
+
const wordCount = starMoment.split(/\s+/).length;
|
|
959
|
+
const hasVerb = verbPattern.test(starMoment);
|
|
960
|
+
if (wordCount >= 6 && starMoment.length >= 40 && hasVerb && !this.isContentUsed(starMoment)) {
|
|
961
|
+
slides.push(this.createStarMomentSlide(slideIndex++, starMoment, mode));
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
if (analysis.sparkline.callToAdventure && analysis.sparkline.callToAdventure.length > 20 && !this.isContentUsed(analysis.sparkline.callToAdventure)) {
|
|
574
965
|
slides.push(this.createCTASlide(slideIndex++, analysis, mode));
|
|
575
966
|
}
|
|
576
967
|
slides.push(this.createThankYouSlide(slideIndex++));
|
|
577
968
|
return slides;
|
|
578
969
|
}
|
|
970
|
+
/**
|
|
971
|
+
* Create a slide from a section with bullets
|
|
972
|
+
*/
|
|
973
|
+
createSectionBulletSlide(index, section, mode) {
|
|
974
|
+
const bullets = section.bullets.slice(0, mode === "keynote" ? 3 : 5);
|
|
975
|
+
return {
|
|
976
|
+
index,
|
|
977
|
+
type: "bullet-points",
|
|
978
|
+
data: {
|
|
979
|
+
title: this.truncate(section.header, 60),
|
|
980
|
+
bullets: bullets.map((b) => this.cleanText(b).slice(0, 80))
|
|
981
|
+
},
|
|
982
|
+
classes: ["slide-bullet-points"]
|
|
983
|
+
};
|
|
984
|
+
}
|
|
985
|
+
/**
|
|
986
|
+
* Create a slide from a section with body content
|
|
987
|
+
*/
|
|
988
|
+
createSectionContentSlide(index, section, mode) {
|
|
989
|
+
if (mode === "keynote") {
|
|
990
|
+
return {
|
|
991
|
+
index,
|
|
992
|
+
type: "single-statement",
|
|
993
|
+
data: {
|
|
994
|
+
title: this.truncate(this.extractFirstSentence(section.content), 80),
|
|
995
|
+
keyMessage: section.header
|
|
996
|
+
},
|
|
997
|
+
classes: ["slide-single-statement"]
|
|
998
|
+
};
|
|
999
|
+
}
|
|
1000
|
+
return {
|
|
1001
|
+
index,
|
|
1002
|
+
type: "two-column",
|
|
1003
|
+
data: {
|
|
1004
|
+
title: this.truncate(section.header, 60),
|
|
1005
|
+
body: this.truncate(section.content, 200)
|
|
1006
|
+
},
|
|
1007
|
+
classes: ["slide-two-column"]
|
|
1008
|
+
};
|
|
1009
|
+
}
|
|
1010
|
+
/**
|
|
1011
|
+
* Extract first sentence from text
|
|
1012
|
+
*/
|
|
1013
|
+
extractFirstSentence(text) {
|
|
1014
|
+
const cleaned = this.cleanText(text);
|
|
1015
|
+
const match = cleaned.match(/^[^.!?]+[.!?]/);
|
|
1016
|
+
return match ? match[0].trim() : cleaned.slice(0, 100);
|
|
1017
|
+
}
|
|
1018
|
+
/**
|
|
1019
|
+
* Create a metrics slide from data points
|
|
1020
|
+
*/
|
|
1021
|
+
createMetricsSlide(index, dataPoints) {
|
|
1022
|
+
const metrics = dataPoints.slice(0, 4).map((dp) => ({
|
|
1023
|
+
value: dp.value,
|
|
1024
|
+
label: this.cleanText(dp.label).slice(0, 40)
|
|
1025
|
+
}));
|
|
1026
|
+
return {
|
|
1027
|
+
index,
|
|
1028
|
+
type: "metrics-grid",
|
|
1029
|
+
data: {
|
|
1030
|
+
title: "Key Metrics",
|
|
1031
|
+
metrics
|
|
1032
|
+
},
|
|
1033
|
+
classes: ["slide-metrics-grid"]
|
|
1034
|
+
};
|
|
1035
|
+
}
|
|
579
1036
|
/**
|
|
580
1037
|
* Create a title slide.
|
|
581
1038
|
*/
|
|
582
1039
|
createTitleSlide(index, analysis) {
|
|
583
|
-
|
|
1040
|
+
let subtitle = "";
|
|
1041
|
+
if (analysis.scqa.answer && analysis.scqa.answer.length > 10) {
|
|
1042
|
+
subtitle = analysis.scqa.answer;
|
|
1043
|
+
} else if (analysis.starMoments.length > 0 && analysis.starMoments[0]) {
|
|
1044
|
+
subtitle = analysis.starMoments[0];
|
|
1045
|
+
}
|
|
584
1046
|
return {
|
|
585
1047
|
index,
|
|
586
1048
|
type: "title",
|
|
587
1049
|
data: {
|
|
588
1050
|
title: analysis.titles[0] ?? "Presentation",
|
|
589
|
-
subtitle: this.truncate(subtitle,
|
|
1051
|
+
subtitle: this.truncate(subtitle, 80),
|
|
590
1052
|
keyMessage: analysis.scqa.answer
|
|
591
1053
|
},
|
|
592
1054
|
classes: ["slide-title"]
|
|
@@ -867,23 +1329,43 @@ var SlideFactory = class {
|
|
|
867
1329
|
}
|
|
868
1330
|
// === Helper Methods ===
|
|
869
1331
|
/**
|
|
870
|
-
* Clean text by removing all content markers.
|
|
1332
|
+
* Clean text by removing all markdown and content markers.
|
|
1333
|
+
* CRITICAL: Must strip all formatting to prevent garbage in slides
|
|
871
1334
|
*/
|
|
872
1335
|
cleanText(text) {
|
|
873
1336
|
if (!text) return "";
|
|
874
|
-
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();
|
|
1337
|
+
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();
|
|
875
1338
|
}
|
|
876
1339
|
/**
|
|
877
|
-
* Truncate text to max length at
|
|
1340
|
+
* Truncate text to max length at sentence boundary when possible.
|
|
1341
|
+
* CRITICAL: Never cut mid-number (99.5% should not become 99.)
|
|
878
1342
|
*/
|
|
879
1343
|
truncate(text, maxLength) {
|
|
880
1344
|
const cleanedText = this.cleanText(text);
|
|
881
1345
|
if (!cleanedText || cleanedText.length <= maxLength) {
|
|
882
1346
|
return cleanedText;
|
|
883
1347
|
}
|
|
1348
|
+
const sentences = cleanedText.match(/[^.!?]+[.!?]/g);
|
|
1349
|
+
if (sentences) {
|
|
1350
|
+
let result = "";
|
|
1351
|
+
for (const sentence of sentences) {
|
|
1352
|
+
if ((result + sentence).length <= maxLength) {
|
|
1353
|
+
result += sentence;
|
|
1354
|
+
} else {
|
|
1355
|
+
break;
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
if (result.length > maxLength * 0.5) {
|
|
1359
|
+
return result.trim();
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
884
1362
|
const truncated = cleanedText.slice(0, maxLength);
|
|
885
|
-
|
|
886
|
-
|
|
1363
|
+
let lastSpace = truncated.lastIndexOf(" ");
|
|
1364
|
+
const afterCut = cleanedText.slice(lastSpace + 1, maxLength + 10);
|
|
1365
|
+
if (/^[\d.,%$]+/.test(afterCut)) {
|
|
1366
|
+
lastSpace = truncated.slice(0, lastSpace).lastIndexOf(" ");
|
|
1367
|
+
}
|
|
1368
|
+
if (lastSpace > maxLength * 0.5) {
|
|
887
1369
|
return truncated.slice(0, lastSpace) + "...";
|
|
888
1370
|
}
|
|
889
1371
|
return truncated + "...";
|
|
@@ -914,10 +1396,12 @@ var SlideFactory = class {
|
|
|
914
1396
|
return sentences.slice(0, 5).map((s) => s.trim());
|
|
915
1397
|
}
|
|
916
1398
|
/**
|
|
917
|
-
* Remove a statistic from text.
|
|
1399
|
+
* Remove a statistic from text and clean thoroughly.
|
|
918
1400
|
*/
|
|
919
1401
|
removeStatistic(text, stat) {
|
|
920
|
-
|
|
1402
|
+
const cleaned = this.cleanText(text).replace(stat, "").replace(/^\s*[-–—:,]\s*/, "").trim();
|
|
1403
|
+
const firstSentence = cleaned.match(/^[^.!?]+[.!?]?/);
|
|
1404
|
+
return firstSentence ? firstSentence[0].slice(0, 80) : cleaned.slice(0, 80);
|
|
921
1405
|
}
|
|
922
1406
|
};
|
|
923
1407
|
|
|
@@ -1817,19 +2301,28 @@ var ScoreCalculator = class {
|
|
|
1817
2301
|
var import_playwright = require("playwright");
|
|
1818
2302
|
var QAEngine = class {
|
|
1819
2303
|
browser = null;
|
|
2304
|
+
kb;
|
|
1820
2305
|
/**
|
|
1821
|
-
*
|
|
2306
|
+
* Initialize KnowledgeGateway
|
|
2307
|
+
*/
|
|
2308
|
+
async initialize() {
|
|
2309
|
+
this.kb = await getKnowledgeGateway();
|
|
2310
|
+
}
|
|
2311
|
+
/**
|
|
2312
|
+
* Validate a presentation using KB-driven quality metrics
|
|
1822
2313
|
*/
|
|
1823
2314
|
async validate(presentation, options) {
|
|
2315
|
+
await this.initialize();
|
|
1824
2316
|
const html = typeof presentation === "string" ? presentation : presentation.toString("utf-8");
|
|
1825
2317
|
const mode = options?.mode ?? "keynote";
|
|
2318
|
+
const qualityMetrics = this.kb.getQualityMetrics();
|
|
1826
2319
|
await this.initBrowser();
|
|
1827
2320
|
try {
|
|
1828
2321
|
const [visualResults, contentResults, expertResults, accessibilityResults] = await Promise.all([
|
|
1829
|
-
this.runVisualTests(html, mode),
|
|
1830
|
-
this.runContentTests(html, mode),
|
|
2322
|
+
this.runVisualTests(html, mode, qualityMetrics),
|
|
2323
|
+
this.runContentTests(html, mode, qualityMetrics),
|
|
1831
2324
|
this.runExpertTests(html, mode),
|
|
1832
|
-
this.runAccessibilityTests(html)
|
|
2325
|
+
this.runAccessibilityTests(html, qualityMetrics)
|
|
1833
2326
|
]);
|
|
1834
2327
|
const issues = this.collectIssues(visualResults, contentResults, expertResults, accessibilityResults);
|
|
1835
2328
|
const errorCount = issues.filter((i) => i.severity === "error").length;
|
|
@@ -1847,18 +2340,18 @@ var QAEngine = class {
|
|
|
1847
2340
|
}
|
|
1848
2341
|
}
|
|
1849
2342
|
/**
|
|
1850
|
-
* Calculate overall QA score
|
|
2343
|
+
* Calculate overall QA score using KB-driven weights
|
|
1851
2344
|
*/
|
|
1852
2345
|
calculateScore(results) {
|
|
1853
2346
|
const weights = {
|
|
1854
2347
|
visual: 0.35,
|
|
1855
|
-
// 35%
|
|
2348
|
+
// 35% - Design quality
|
|
1856
2349
|
content: 0.3,
|
|
1857
|
-
// 30%
|
|
2350
|
+
// 30% - Content quality
|
|
1858
2351
|
expert: 0.25,
|
|
1859
|
-
// 25%
|
|
2352
|
+
// 25% - Expert methodology
|
|
1860
2353
|
accessibility: 0.1
|
|
1861
|
-
// 10%
|
|
2354
|
+
// 10% - Accessibility
|
|
1862
2355
|
};
|
|
1863
2356
|
const visualScore = this.calculateVisualScore(results.visual);
|
|
1864
2357
|
const contentScore = this.calculateContentScore(results.content);
|
|
@@ -1905,13 +2398,15 @@ var QAEngine = class {
|
|
|
1905
2398
|
};
|
|
1906
2399
|
}
|
|
1907
2400
|
// ===========================================================================
|
|
1908
|
-
// VISUAL TESTS
|
|
2401
|
+
// VISUAL TESTS - KB-DRIVEN
|
|
1909
2402
|
// ===========================================================================
|
|
1910
|
-
async runVisualTests(html, mode) {
|
|
2403
|
+
async runVisualTests(html, mode, qualityMetrics) {
|
|
1911
2404
|
const page = await this.browser.newPage();
|
|
1912
2405
|
await page.setViewportSize({ width: 1280, height: 720 });
|
|
1913
2406
|
await page.setContent(html);
|
|
1914
2407
|
await page.waitForTimeout(1e3);
|
|
2408
|
+
const modeMetrics = mode === "keynote" ? qualityMetrics.keynote_mode : qualityMetrics.business_mode;
|
|
2409
|
+
const minWhitespace = this.kb.getWhitespaceRequirement(mode) * 100;
|
|
1915
2410
|
const slideCount = await page.evaluate(() => {
|
|
1916
2411
|
return window.Reveal?.getTotalSlides?.() ?? document.querySelectorAll(".slides > section").length;
|
|
1917
2412
|
});
|
|
@@ -1977,9 +2472,8 @@ var QAEngine = class {
|
|
|
1977
2472
|
}, { slideIndex: i });
|
|
1978
2473
|
if (slideAnalysis) {
|
|
1979
2474
|
const issues = [];
|
|
1980
|
-
const minWhitespace = mode === "keynote" ? 40 : 25;
|
|
1981
2475
|
if (slideAnalysis.whitespace < minWhitespace) {
|
|
1982
|
-
issues.push(`Whitespace ${slideAnalysis.whitespace}% below ${minWhitespace}% minimum`);
|
|
2476
|
+
issues.push(`Whitespace ${slideAnalysis.whitespace}% below ${minWhitespace}% minimum (KB-defined)`);
|
|
1983
2477
|
}
|
|
1984
2478
|
if (slideAnalysis.whitespace > 80) {
|
|
1985
2479
|
issues.push(`Whitespace ${slideAnalysis.whitespace}% - slide appears sparse`);
|
|
@@ -2034,34 +2528,35 @@ var QAEngine = class {
|
|
|
2034
2528
|
};
|
|
2035
2529
|
}
|
|
2036
2530
|
// ===========================================================================
|
|
2037
|
-
// CONTENT TESTS
|
|
2531
|
+
// CONTENT TESTS - KB-DRIVEN
|
|
2038
2532
|
// ===========================================================================
|
|
2039
|
-
async runContentTests(html, mode) {
|
|
2533
|
+
async runContentTests(html, mode, qualityMetrics) {
|
|
2040
2534
|
const page = await this.browser.newPage();
|
|
2041
2535
|
await page.setContent(html);
|
|
2042
2536
|
await page.waitForTimeout(500);
|
|
2043
|
-
const
|
|
2537
|
+
const wordLimits = this.kb.getWordLimits(mode);
|
|
2538
|
+
const glanceTest = this.kb.getDuarteGlanceTest();
|
|
2539
|
+
const results = await page.evaluate((params) => {
|
|
2540
|
+
const { targetMode, maxWords, minWords, glanceWordLimit } = params;
|
|
2044
2541
|
const slides = document.querySelectorAll(".slides > section");
|
|
2045
2542
|
const perSlide = [];
|
|
2046
|
-
const
|
|
2543
|
+
const glanceTestResults = [];
|
|
2047
2544
|
const signalNoise = [];
|
|
2048
2545
|
const oneIdea = [];
|
|
2049
2546
|
slides.forEach((slide, index) => {
|
|
2050
2547
|
const text = slide.innerText || "";
|
|
2051
2548
|
const words = text.split(/\s+/).filter((w) => w.length > 0);
|
|
2052
2549
|
const wordCount = words.length;
|
|
2053
|
-
const maxWords = targetMode === "keynote" ? 25 : 80;
|
|
2054
|
-
const minWords = targetMode === "business" ? 20 : 0;
|
|
2055
2550
|
const withinLimit = wordCount <= maxWords && wordCount >= minWords;
|
|
2056
2551
|
const title = slide.querySelector("h2")?.textContent || "";
|
|
2057
2552
|
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);
|
|
2058
2553
|
const hasInsight = title.length > 30 && hasVerb;
|
|
2059
2554
|
const issues = [];
|
|
2060
2555
|
if (!withinLimit) {
|
|
2061
|
-
issues.push(`Word count ${wordCount} outside ${minWords}-${maxWords} range`);
|
|
2556
|
+
issues.push(`Word count ${wordCount} outside ${minWords}-${maxWords} range (KB-defined)`);
|
|
2062
2557
|
}
|
|
2063
2558
|
if (targetMode === "business" && !hasInsight && index > 0) {
|
|
2064
|
-
issues.push("Title is not action-oriented (missing insight)");
|
|
2559
|
+
issues.push("Title is not action-oriented (missing insight per Minto)");
|
|
2065
2560
|
}
|
|
2066
2561
|
perSlide.push({
|
|
2067
2562
|
slideIndex: index,
|
|
@@ -2074,12 +2569,12 @@ var QAEngine = class {
|
|
|
2074
2569
|
const keyMessage = prominentElement?.textContent?.trim() || "";
|
|
2075
2570
|
const keyWordCount = keyMessage.split(/\s+/).filter((w) => w.length > 0).length;
|
|
2076
2571
|
const readingTime = keyWordCount / 4.2;
|
|
2077
|
-
|
|
2572
|
+
glanceTestResults.push({
|
|
2078
2573
|
slideIndex: index,
|
|
2079
2574
|
keyMessage,
|
|
2080
2575
|
wordCount: keyWordCount,
|
|
2081
2576
|
readingTime: Math.round(readingTime * 10) / 10,
|
|
2082
|
-
passed: readingTime <= 3 && keyWordCount <=
|
|
2577
|
+
passed: readingTime <= 3 && keyWordCount <= glanceWordLimit,
|
|
2083
2578
|
recommendation: readingTime > 3 ? `Shorten to ${Math.floor(3 * 4.2)} words or less` : void 0
|
|
2084
2579
|
});
|
|
2085
2580
|
const elements = slide.querySelectorAll("h1, h2, h3, p, li, img");
|
|
@@ -2106,35 +2601,143 @@ var QAEngine = class {
|
|
|
2106
2601
|
conflictingIdeas: ideaCount > 2 ? ["Multiple competing ideas detected"] : void 0
|
|
2107
2602
|
});
|
|
2108
2603
|
});
|
|
2109
|
-
return { perSlide, glanceTest, signalNoise, oneIdea };
|
|
2110
|
-
},
|
|
2604
|
+
return { perSlide, glanceTest: glanceTestResults, signalNoise, oneIdea };
|
|
2605
|
+
}, {
|
|
2606
|
+
targetMode: mode,
|
|
2607
|
+
maxWords: wordLimits.max,
|
|
2608
|
+
minWords: mode === "business" ? 20 : 0,
|
|
2609
|
+
glanceWordLimit: glanceTest.wordLimit
|
|
2610
|
+
});
|
|
2111
2611
|
await page.close();
|
|
2112
2612
|
return results;
|
|
2113
2613
|
}
|
|
2114
2614
|
// ===========================================================================
|
|
2115
|
-
// EXPERT TESTS
|
|
2615
|
+
// EXPERT TESTS - KB-DRIVEN
|
|
2116
2616
|
// ===========================================================================
|
|
2117
2617
|
async runExpertTests(html, mode) {
|
|
2618
|
+
const duartePrinciples = this.kb.getExpertPrinciples("nancy_duarte");
|
|
2619
|
+
const reynoldsPrinciples = this.kb.getExpertPrinciples("garr_reynolds");
|
|
2620
|
+
const galloPrinciples = this.kb.getExpertPrinciples("carmine_gallo");
|
|
2621
|
+
const andersonPrinciples = this.kb.getExpertPrinciples("chris_anderson");
|
|
2118
2622
|
return {
|
|
2119
|
-
duarte: this.
|
|
2120
|
-
reynolds: this.
|
|
2121
|
-
gallo: this.
|
|
2122
|
-
anderson: this.
|
|
2623
|
+
duarte: this.validateDuartePrinciples(html, duartePrinciples),
|
|
2624
|
+
reynolds: this.validateReynoldsPrinciples(html, reynoldsPrinciples),
|
|
2625
|
+
gallo: this.validateGalloPrinciples(html, galloPrinciples),
|
|
2626
|
+
anderson: this.validateAndersonPrinciples(html, andersonPrinciples)
|
|
2123
2627
|
};
|
|
2124
2628
|
}
|
|
2125
|
-
|
|
2629
|
+
validateDuartePrinciples(html, principles) {
|
|
2630
|
+
const violations = [];
|
|
2631
|
+
let score = 100;
|
|
2632
|
+
const slideMatches = html.match(/<section[^>]*>[\s\S]*?<\/section>/gi) || [];
|
|
2633
|
+
slideMatches.forEach((slide, index) => {
|
|
2634
|
+
const h1Match = slide.match(/<h1[^>]*>([\s\S]*?)<\/h1>/i);
|
|
2635
|
+
const h2Match = slide.match(/<h2[^>]*>([\s\S]*?)<\/h2>/i);
|
|
2636
|
+
const keyMessage = (h1Match?.[1] || h2Match?.[1] || "").replace(/<[^>]+>/g, "").trim();
|
|
2637
|
+
const wordCount = keyMessage.split(/\s+/).filter((w) => w.length > 0).length;
|
|
2638
|
+
if (wordCount > 25) {
|
|
2639
|
+
violations.push(`Slide ${index + 1}: Key message exceeds 25 words (Duarte Glance Test)`);
|
|
2640
|
+
score -= 5;
|
|
2641
|
+
}
|
|
2642
|
+
});
|
|
2643
|
+
const hasStarMoment = html.includes("callout") || html.includes("highlight") || html.includes("big-idea");
|
|
2644
|
+
if (!hasStarMoment && slideMatches.length > 5) {
|
|
2645
|
+
violations.push("Missing STAR moment (Something They'll Always Remember)");
|
|
2646
|
+
score -= 10;
|
|
2647
|
+
}
|
|
2126
2648
|
return {
|
|
2127
|
-
expertName:
|
|
2128
|
-
principlesChecked:
|
|
2649
|
+
expertName: "Nancy Duarte",
|
|
2650
|
+
principlesChecked: ["Glance Test", "STAR Moment", "Sparkline Structure"],
|
|
2129
2651
|
passed: score >= 80,
|
|
2130
|
-
score,
|
|
2131
|
-
violations
|
|
2652
|
+
score: Math.max(0, score),
|
|
2653
|
+
violations
|
|
2654
|
+
};
|
|
2655
|
+
}
|
|
2656
|
+
validateReynoldsPrinciples(html, principles) {
|
|
2657
|
+
const violations = [];
|
|
2658
|
+
let score = 100;
|
|
2659
|
+
const decorativeElements = (html.match(/class="[^"]*decorative[^"]*"/gi) || []).length;
|
|
2660
|
+
const randomImages = (html.match(/picsum\.photos/gi) || []).length;
|
|
2661
|
+
if (randomImages > 0) {
|
|
2662
|
+
violations.push(`Found ${randomImages} random stock images (Reynolds: only use purposeful images)`);
|
|
2663
|
+
score -= randomImages * 10;
|
|
2664
|
+
}
|
|
2665
|
+
if (decorativeElements > 3) {
|
|
2666
|
+
violations.push(`Too many decorative elements (${decorativeElements}) - reduces signal-to-noise ratio`);
|
|
2667
|
+
score -= 10;
|
|
2668
|
+
}
|
|
2669
|
+
const columnLayouts = (html.match(/class="[^"]*columns[^"]*"/gi) || []).length;
|
|
2670
|
+
const slideCount = (html.match(/<section/gi) || []).length;
|
|
2671
|
+
if (columnLayouts > slideCount * 0.5) {
|
|
2672
|
+
violations.push("Overuse of column layouts - simplify per Reynolds");
|
|
2673
|
+
score -= 10;
|
|
2674
|
+
}
|
|
2675
|
+
return {
|
|
2676
|
+
expertName: "Garr Reynolds",
|
|
2677
|
+
principlesChecked: ["Signal-to-Noise", "Simplicity", "Picture Superiority"],
|
|
2678
|
+
passed: score >= 80,
|
|
2679
|
+
score: Math.max(0, score),
|
|
2680
|
+
violations
|
|
2681
|
+
};
|
|
2682
|
+
}
|
|
2683
|
+
validateGalloPrinciples(html, principles) {
|
|
2684
|
+
const violations = [];
|
|
2685
|
+
let score = 100;
|
|
2686
|
+
const bulletLists = html.match(/<ul[^>]*>[\s\S]*?<\/ul>/gi) || [];
|
|
2687
|
+
bulletLists.forEach((list, index) => {
|
|
2688
|
+
const bullets = (list.match(/<li/gi) || []).length;
|
|
2689
|
+
if (bullets > 5) {
|
|
2690
|
+
violations.push(`Bullet list ${index + 1} has ${bullets} items (Gallo: max 3-5)`);
|
|
2691
|
+
score -= 5;
|
|
2692
|
+
}
|
|
2693
|
+
});
|
|
2694
|
+
const hasQuote = html.includes("blockquote") || html.includes("quote");
|
|
2695
|
+
const hasStory = html.toLowerCase().includes("story") || html.toLowerCase().includes("journey");
|
|
2696
|
+
if (!hasQuote && !hasStory) {
|
|
2697
|
+
violations.push("Missing emotional connection elements (quotes, stories)");
|
|
2698
|
+
score -= 10;
|
|
2699
|
+
}
|
|
2700
|
+
return {
|
|
2701
|
+
expertName: "Carmine Gallo",
|
|
2702
|
+
principlesChecked: ["Rule of Three", "Emotional Connection", "Headline Power"],
|
|
2703
|
+
passed: score >= 80,
|
|
2704
|
+
score: Math.max(0, score),
|
|
2705
|
+
violations
|
|
2706
|
+
};
|
|
2707
|
+
}
|
|
2708
|
+
validateAndersonPrinciples(html, principles) {
|
|
2709
|
+
const violations = [];
|
|
2710
|
+
let score = 100;
|
|
2711
|
+
const slideMatches = html.match(/<section[^>]*>[\s\S]*?<\/section>/gi) || [];
|
|
2712
|
+
slideMatches.forEach((slide, index) => {
|
|
2713
|
+
const headings = (slide.match(/<h[12][^>]*>/gi) || []).length;
|
|
2714
|
+
if (headings > 2) {
|
|
2715
|
+
violations.push(`Slide ${index + 1}: Multiple ideas detected (Anderson: one idea per slide)`);
|
|
2716
|
+
score -= 5;
|
|
2717
|
+
}
|
|
2718
|
+
});
|
|
2719
|
+
const jargonPatterns = ["synergy", "leverage", "paradigm", "holistic", "streamline"];
|
|
2720
|
+
let jargonCount = 0;
|
|
2721
|
+
jargonPatterns.forEach((pattern) => {
|
|
2722
|
+
const matches = html.toLowerCase().match(new RegExp(pattern, "gi")) || [];
|
|
2723
|
+
jargonCount += matches.length;
|
|
2724
|
+
});
|
|
2725
|
+
if (jargonCount > 5) {
|
|
2726
|
+
violations.push(`Excessive jargon detected (${jargonCount} instances) - simplify language`);
|
|
2727
|
+
score -= 10;
|
|
2728
|
+
}
|
|
2729
|
+
return {
|
|
2730
|
+
expertName: "Chris Anderson",
|
|
2731
|
+
principlesChecked: ["One Idea", "Clarity", "Throughline"],
|
|
2732
|
+
passed: score >= 80,
|
|
2733
|
+
score: Math.max(0, score),
|
|
2734
|
+
violations
|
|
2132
2735
|
};
|
|
2133
2736
|
}
|
|
2134
2737
|
// ===========================================================================
|
|
2135
|
-
// ACCESSIBILITY TESTS
|
|
2738
|
+
// ACCESSIBILITY TESTS - KB-DRIVEN
|
|
2136
2739
|
// ===========================================================================
|
|
2137
|
-
async runAccessibilityTests(html) {
|
|
2740
|
+
async runAccessibilityTests(html, qualityMetrics) {
|
|
2138
2741
|
const page = await this.browser.newPage();
|
|
2139
2742
|
await page.setContent(html);
|
|
2140
2743
|
await page.waitForTimeout(500);
|
|
@@ -2184,7 +2787,7 @@ var QAEngine = class {
|
|
|
2184
2787
|
};
|
|
2185
2788
|
}
|
|
2186
2789
|
// ===========================================================================
|
|
2187
|
-
// SCORING
|
|
2790
|
+
// SCORING - KB-DRIVEN
|
|
2188
2791
|
// ===========================================================================
|
|
2189
2792
|
calculateVisualScore(results) {
|
|
2190
2793
|
let score = 100;
|
|
@@ -2308,6 +2911,7 @@ var QAEngine = class {
|
|
|
2308
2911
|
// src/generators/html/RevealJsGenerator.ts
|
|
2309
2912
|
var RevealJsGenerator = class {
|
|
2310
2913
|
templateEngine;
|
|
2914
|
+
kb;
|
|
2311
2915
|
defaultRevealConfig = {
|
|
2312
2916
|
revealVersion: "5.0.4",
|
|
2313
2917
|
hash: true,
|
|
@@ -2326,20 +2930,32 @@ var RevealJsGenerator = class {
|
|
|
2326
2930
|
constructor() {
|
|
2327
2931
|
this.templateEngine = new TemplateEngine();
|
|
2328
2932
|
}
|
|
2933
|
+
/**
|
|
2934
|
+
* Initialize KnowledgeGateway
|
|
2935
|
+
*/
|
|
2936
|
+
async initialize() {
|
|
2937
|
+
this.kb = await getKnowledgeGateway();
|
|
2938
|
+
}
|
|
2329
2939
|
/**
|
|
2330
2940
|
* Generate complete Reveal.js HTML presentation.
|
|
2331
2941
|
*/
|
|
2332
2942
|
async generate(slides, config) {
|
|
2943
|
+
await this.initialize();
|
|
2333
2944
|
const templateConfig = {};
|
|
2334
2945
|
if (config.theme) templateConfig.theme = config.theme;
|
|
2335
2946
|
if (config.customTemplates) templateConfig.customTemplates = config.customTemplates;
|
|
2336
2947
|
const slideHtml = this.templateEngine.renderAll(slides, templateConfig);
|
|
2948
|
+
const presentationType = this.detectPresentationType(config);
|
|
2949
|
+
const palette = this.kb.getRecommendedPalette(presentationType);
|
|
2950
|
+
const typography = this.kb.getTypography(config.mode);
|
|
2337
2951
|
const docConfig = {
|
|
2338
2952
|
title: config.title,
|
|
2339
2953
|
slides: slideHtml.join("\n"),
|
|
2340
2954
|
theme: config.theme ?? "default",
|
|
2341
2955
|
revealConfig: this.defaultRevealConfig,
|
|
2342
|
-
mode: config.mode
|
|
2956
|
+
mode: config.mode,
|
|
2957
|
+
palette,
|
|
2958
|
+
typography
|
|
2343
2959
|
};
|
|
2344
2960
|
if (config.author) docConfig.author = config.author;
|
|
2345
2961
|
if (config.subject) docConfig.subject = config.subject;
|
|
@@ -2350,11 +2966,23 @@ var RevealJsGenerator = class {
|
|
|
2350
2966
|
}
|
|
2351
2967
|
return html;
|
|
2352
2968
|
}
|
|
2969
|
+
/**
|
|
2970
|
+
* Detect presentation type from config
|
|
2971
|
+
*/
|
|
2972
|
+
detectPresentationType(config) {
|
|
2973
|
+
if (config.presentationType) {
|
|
2974
|
+
return config.presentationType;
|
|
2975
|
+
}
|
|
2976
|
+
if (config.mode === "keynote") {
|
|
2977
|
+
return "ted_keynote";
|
|
2978
|
+
}
|
|
2979
|
+
return "consulting_deck";
|
|
2980
|
+
}
|
|
2353
2981
|
/**
|
|
2354
2982
|
* Build the complete HTML document.
|
|
2355
2983
|
*/
|
|
2356
2984
|
buildDocument(options) {
|
|
2357
|
-
const { title, author, subject, slides, theme, customCSS, revealConfig, mode } = options;
|
|
2985
|
+
const { title, author, subject, slides, theme, customCSS, revealConfig, mode, palette, typography } = options;
|
|
2358
2986
|
return `<!DOCTYPE html>
|
|
2359
2987
|
<html lang="en">
|
|
2360
2988
|
<head>
|
|
@@ -2362,7 +2990,7 @@ var RevealJsGenerator = class {
|
|
|
2362
2990
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
2363
2991
|
<meta name="author" content="${this.escapeHtml(author ?? "Claude Presentation Master")}">
|
|
2364
2992
|
<meta name="description" content="${this.escapeHtml(subject ?? "")}">
|
|
2365
|
-
<meta name="generator" content="Claude Presentation Master
|
|
2993
|
+
<meta name="generator" content="Claude Presentation Master v6.0.0">
|
|
2366
2994
|
<title>${this.escapeHtml(title)}</title>
|
|
2367
2995
|
|
|
2368
2996
|
<!-- Reveal.js CSS -->
|
|
@@ -2376,10 +3004,10 @@ var RevealJsGenerator = class {
|
|
|
2376
3004
|
<!-- Mermaid for diagrams -->
|
|
2377
3005
|
<script src="https://cdn.jsdelivr.net/npm/mermaid@10.6.1/dist/mermaid.min.js"></script>
|
|
2378
3006
|
|
|
2379
|
-
<!-- Presentation Engine CSS -->
|
|
3007
|
+
<!-- Presentation Engine CSS - KB-Driven Design System -->
|
|
2380
3008
|
<style>
|
|
2381
|
-
${this.getBaseStyles(mode)}
|
|
2382
|
-
${this.getThemeStyles(theme)}
|
|
3009
|
+
${this.getBaseStyles(mode, palette, typography)}
|
|
3010
|
+
${this.getThemeStyles(theme, palette)}
|
|
2383
3011
|
${this.getAnimationStyles()}
|
|
2384
3012
|
${customCSS ?? ""}
|
|
2385
3013
|
</style>
|
|
@@ -2431,26 +3059,35 @@ ${slides}
|
|
|
2431
3059
|
</html>`;
|
|
2432
3060
|
}
|
|
2433
3061
|
/**
|
|
2434
|
-
* Get base styles for slides
|
|
3062
|
+
* Get base styles for slides - KB-DRIVEN
|
|
2435
3063
|
*/
|
|
2436
|
-
getBaseStyles(mode) {
|
|
3064
|
+
getBaseStyles(mode, palette, typography) {
|
|
2437
3065
|
const fontSize = mode === "keynote" ? "2.5em" : "1.8em";
|
|
2438
3066
|
const lineHeight = mode === "keynote" ? "1.4" : "1.5";
|
|
3067
|
+
const primary = palette.primary || "#1a1a2e";
|
|
3068
|
+
const secondary = palette.secondary || "#16213e";
|
|
3069
|
+
const accent = palette.accent || "#0f3460";
|
|
3070
|
+
const highlight = palette.accent || "#e94560";
|
|
3071
|
+
const text = palette.text || "#1a1a2e";
|
|
3072
|
+
const background = palette.background || "#ffffff";
|
|
2439
3073
|
return `
|
|
2440
|
-
/* Base Styles */
|
|
3074
|
+
/* Base Styles - KB-Driven Design System */
|
|
2441
3075
|
:root {
|
|
3076
|
+
/* Typography from KB */
|
|
2442
3077
|
--font-heading: 'Source Sans Pro', 'Helvetica Neue', -apple-system, BlinkMacSystemFont, sans-serif;
|
|
2443
3078
|
--font-body: 'Source Sans Pro', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
2444
3079
|
--font-mono: 'SF Mono', 'Fira Code', 'JetBrains Mono', monospace;
|
|
2445
3080
|
|
|
2446
|
-
|
|
2447
|
-
--color-
|
|
2448
|
-
--color-
|
|
2449
|
-
--color-
|
|
2450
|
-
--color-
|
|
2451
|
-
--color-text
|
|
2452
|
-
--color-
|
|
3081
|
+
/* Colors from KB Palette: ${palette.name} */
|
|
3082
|
+
--color-primary: ${primary};
|
|
3083
|
+
--color-secondary: ${secondary};
|
|
3084
|
+
--color-accent: ${accent};
|
|
3085
|
+
--color-highlight: ${highlight};
|
|
3086
|
+
--color-text: ${text};
|
|
3087
|
+
--color-text-light: ${this.lightenColor(text, 30)};
|
|
3088
|
+
--color-background: ${background};
|
|
2453
3089
|
|
|
3090
|
+
/* Layout */
|
|
2454
3091
|
--slide-padding: 60px;
|
|
2455
3092
|
--content-max-width: 1200px;
|
|
2456
3093
|
}
|
|
@@ -2566,7 +3203,7 @@ ${slides}
|
|
|
2566
3203
|
font-size: 0.8em;
|
|
2567
3204
|
}
|
|
2568
3205
|
|
|
2569
|
-
/* Images */
|
|
3206
|
+
/* Images - NO RANDOM PICSUM - Only user-provided or none */
|
|
2570
3207
|
.reveal img {
|
|
2571
3208
|
max-width: 100%;
|
|
2572
3209
|
height: auto;
|
|
@@ -2672,9 +3309,25 @@ ${slides}
|
|
|
2672
3309
|
`;
|
|
2673
3310
|
}
|
|
2674
3311
|
/**
|
|
2675
|
-
*
|
|
3312
|
+
* Lighten a hex color
|
|
2676
3313
|
*/
|
|
2677
|
-
|
|
3314
|
+
lightenColor(hex, percent) {
|
|
3315
|
+
hex = hex.replace("#", "");
|
|
3316
|
+
let r = parseInt(hex.substring(0, 2), 16);
|
|
3317
|
+
let g = parseInt(hex.substring(2, 4), 16);
|
|
3318
|
+
let b = parseInt(hex.substring(4, 6), 16);
|
|
3319
|
+
r = Math.min(255, Math.floor(r + (255 - r) * (percent / 100)));
|
|
3320
|
+
g = Math.min(255, Math.floor(g + (255 - g) * (percent / 100)));
|
|
3321
|
+
b = Math.min(255, Math.floor(b + (255 - b) * (percent / 100)));
|
|
3322
|
+
return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`;
|
|
3323
|
+
}
|
|
3324
|
+
/**
|
|
3325
|
+
* Get theme-specific styles - KB-DRIVEN
|
|
3326
|
+
*/
|
|
3327
|
+
getThemeStyles(theme, palette) {
|
|
3328
|
+
if (theme === "default") {
|
|
3329
|
+
return "";
|
|
3330
|
+
}
|
|
2678
3331
|
const themes = {
|
|
2679
3332
|
"default": "",
|
|
2680
3333
|
"light-corporate": `
|
|
@@ -2688,10 +3341,10 @@ ${slides}
|
|
|
2688
3341
|
`,
|
|
2689
3342
|
"modern-tech": `
|
|
2690
3343
|
:root {
|
|
2691
|
-
--color-primary: #1a1a2e;
|
|
2692
|
-
--color-secondary: #16213e;
|
|
2693
|
-
--color-accent: #0f3460;
|
|
2694
|
-
--color-highlight: #e94560;
|
|
3344
|
+
--color-primary: ${palette.primary || "#1a1a2e"};
|
|
3345
|
+
--color-secondary: ${palette.secondary || "#16213e"};
|
|
3346
|
+
--color-accent: ${palette.accent || "#0f3460"};
|
|
3347
|
+
--color-highlight: ${palette.accent || "#e94560"};
|
|
2695
3348
|
--color-background: #f8f9fa;
|
|
2696
3349
|
}
|
|
2697
3350
|
`,
|
|
@@ -3937,240 +4590,6 @@ function createDefaultImageProvider(options) {
|
|
|
3937
4590
|
return new CompositeImageProvider(providers);
|
|
3938
4591
|
}
|
|
3939
4592
|
|
|
3940
|
-
// src/knowledge/KnowledgeBase.ts
|
|
3941
|
-
var import_fs = require("fs");
|
|
3942
|
-
var import_path = require("path");
|
|
3943
|
-
var import_url = require("url");
|
|
3944
|
-
var yaml = __toESM(require("yaml"));
|
|
3945
|
-
var import_meta = {};
|
|
3946
|
-
function getModuleDir() {
|
|
3947
|
-
if (typeof import_meta !== "undefined" && import_meta.url) {
|
|
3948
|
-
return (0, import_path.dirname)((0, import_url.fileURLToPath)(import_meta.url));
|
|
3949
|
-
}
|
|
3950
|
-
if (typeof __dirname !== "undefined") {
|
|
3951
|
-
return __dirname;
|
|
3952
|
-
}
|
|
3953
|
-
return process.cwd();
|
|
3954
|
-
}
|
|
3955
|
-
var moduleDir = getModuleDir();
|
|
3956
|
-
var KnowledgeBase = class {
|
|
3957
|
-
data = null;
|
|
3958
|
-
loaded = false;
|
|
3959
|
-
/**
|
|
3960
|
-
* Load the knowledge base from the bundled YAML file.
|
|
3961
|
-
*/
|
|
3962
|
-
async load() {
|
|
3963
|
-
if (this.loaded) return;
|
|
3964
|
-
try {
|
|
3965
|
-
const possiblePaths = [
|
|
3966
|
-
// From dist/: go up to package root
|
|
3967
|
-
(0, import_path.join)(moduleDir, "../../assets/presentation-knowledge.yaml"),
|
|
3968
|
-
(0, import_path.join)(moduleDir, "../assets/presentation-knowledge.yaml"),
|
|
3969
|
-
(0, import_path.join)(moduleDir, "../../../assets/presentation-knowledge.yaml"),
|
|
3970
|
-
// From bundle in dist/index.js: go up one level
|
|
3971
|
-
(0, import_path.join)(moduleDir, "assets/presentation-knowledge.yaml"),
|
|
3972
|
-
// CWD-based paths for development or local installs
|
|
3973
|
-
(0, import_path.join)(process.cwd(), "assets/presentation-knowledge.yaml"),
|
|
3974
|
-
(0, import_path.join)(process.cwd(), "node_modules/claude-presentation-master/assets/presentation-knowledge.yaml")
|
|
3975
|
-
];
|
|
3976
|
-
let assetPath = "";
|
|
3977
|
-
for (const p of possiblePaths) {
|
|
3978
|
-
try {
|
|
3979
|
-
(0, import_fs.readFileSync)(p);
|
|
3980
|
-
assetPath = p;
|
|
3981
|
-
break;
|
|
3982
|
-
} catch {
|
|
3983
|
-
continue;
|
|
3984
|
-
}
|
|
3985
|
-
}
|
|
3986
|
-
if (!assetPath) {
|
|
3987
|
-
throw new Error("Could not locate knowledge base file");
|
|
3988
|
-
}
|
|
3989
|
-
const content = (0, import_fs.readFileSync)(assetPath, "utf-8");
|
|
3990
|
-
this.data = yaml.parse(content);
|
|
3991
|
-
this.loaded = true;
|
|
3992
|
-
console.log(`\u{1F4DA} Knowledge base loaded: v${this.data.version}`);
|
|
3993
|
-
} catch (error) {
|
|
3994
|
-
console.warn("\u26A0\uFE0F Could not load knowledge base, using defaults");
|
|
3995
|
-
this.data = this.getDefaultData();
|
|
3996
|
-
this.loaded = true;
|
|
3997
|
-
}
|
|
3998
|
-
}
|
|
3999
|
-
/**
|
|
4000
|
-
* Get expert methodology by name.
|
|
4001
|
-
*/
|
|
4002
|
-
getExpert(name) {
|
|
4003
|
-
this.ensureLoaded();
|
|
4004
|
-
return this.data?.experts?.[name];
|
|
4005
|
-
}
|
|
4006
|
-
/**
|
|
4007
|
-
* Get all expert names.
|
|
4008
|
-
*/
|
|
4009
|
-
getExpertNames() {
|
|
4010
|
-
this.ensureLoaded();
|
|
4011
|
-
return Object.keys(this.data?.experts ?? {});
|
|
4012
|
-
}
|
|
4013
|
-
/**
|
|
4014
|
-
* Get framework recommendation for audience.
|
|
4015
|
-
*/
|
|
4016
|
-
getFrameworkForAudience(audience) {
|
|
4017
|
-
this.ensureLoaded();
|
|
4018
|
-
return this.data?.frameworkSelector?.byAudience?.[audience];
|
|
4019
|
-
}
|
|
4020
|
-
/**
|
|
4021
|
-
* Get framework recommendation for goal.
|
|
4022
|
-
*/
|
|
4023
|
-
getFrameworkForGoal(goal) {
|
|
4024
|
-
this.ensureLoaded();
|
|
4025
|
-
return this.data?.frameworkSelector?.byGoal?.[goal];
|
|
4026
|
-
}
|
|
4027
|
-
/**
|
|
4028
|
-
* Get QA scoring rubric.
|
|
4029
|
-
*/
|
|
4030
|
-
getScoringRubric() {
|
|
4031
|
-
this.ensureLoaded();
|
|
4032
|
-
return this.data?.automatedQA?.scoringRubric;
|
|
4033
|
-
}
|
|
4034
|
-
/**
|
|
4035
|
-
* Get mode configuration (keynote or business).
|
|
4036
|
-
*/
|
|
4037
|
-
getModeConfig(mode) {
|
|
4038
|
-
this.ensureLoaded();
|
|
4039
|
-
return this.data?.modes?.[mode];
|
|
4040
|
-
}
|
|
4041
|
-
/**
|
|
4042
|
-
* Get slide type configuration.
|
|
4043
|
-
*/
|
|
4044
|
-
getSlideType(type) {
|
|
4045
|
-
this.ensureLoaded();
|
|
4046
|
-
return this.data?.slideTypes?.[type];
|
|
4047
|
-
}
|
|
4048
|
-
/**
|
|
4049
|
-
* Get the knowledge base version.
|
|
4050
|
-
*/
|
|
4051
|
-
getVersion() {
|
|
4052
|
-
this.ensureLoaded();
|
|
4053
|
-
return this.data?.version ?? "unknown";
|
|
4054
|
-
}
|
|
4055
|
-
/**
|
|
4056
|
-
* Validate a slide against expert principles.
|
|
4057
|
-
*/
|
|
4058
|
-
validateAgainstExpert(expertName, slideData) {
|
|
4059
|
-
const expert = this.getExpert(expertName);
|
|
4060
|
-
if (!expert) {
|
|
4061
|
-
return { passed: true, violations: [] };
|
|
4062
|
-
}
|
|
4063
|
-
const violations = [];
|
|
4064
|
-
if (expert.wordLimits) {
|
|
4065
|
-
if (expert.wordLimits.max && slideData.wordCount > expert.wordLimits.max) {
|
|
4066
|
-
violations.push(`Exceeds ${expertName} word limit of ${expert.wordLimits.max}`);
|
|
4067
|
-
}
|
|
4068
|
-
if (expert.wordLimits.min && slideData.wordCount < expert.wordLimits.min) {
|
|
4069
|
-
violations.push(`Below ${expertName} minimum of ${expert.wordLimits.min} words`);
|
|
4070
|
-
}
|
|
4071
|
-
}
|
|
4072
|
-
return {
|
|
4073
|
-
passed: violations.length === 0,
|
|
4074
|
-
violations
|
|
4075
|
-
};
|
|
4076
|
-
}
|
|
4077
|
-
/**
|
|
4078
|
-
* Ensure knowledge base is loaded.
|
|
4079
|
-
*/
|
|
4080
|
-
ensureLoaded() {
|
|
4081
|
-
if (!this.loaded) {
|
|
4082
|
-
this.data = this.getDefaultData();
|
|
4083
|
-
this.loaded = true;
|
|
4084
|
-
}
|
|
4085
|
-
}
|
|
4086
|
-
/**
|
|
4087
|
-
* Get default data if YAML can't be loaded.
|
|
4088
|
-
*/
|
|
4089
|
-
getDefaultData() {
|
|
4090
|
-
return {
|
|
4091
|
-
version: "1.0.0-fallback",
|
|
4092
|
-
lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4093
|
-
experts: {
|
|
4094
|
-
"Nancy Duarte": {
|
|
4095
|
-
name: "Nancy Duarte",
|
|
4096
|
-
principles: [
|
|
4097
|
-
{ name: "Glance Test", description: "Message clear in 3 seconds" },
|
|
4098
|
-
{ name: "STAR Moment", description: "Something They'll Always Remember" },
|
|
4099
|
-
{ name: "Sparkline", description: "Contrast What Is vs What Could Be" }
|
|
4100
|
-
]
|
|
4101
|
-
},
|
|
4102
|
-
"Garr Reynolds": {
|
|
4103
|
-
name: "Garr Reynolds",
|
|
4104
|
-
principles: [
|
|
4105
|
-
{ name: "Signal-to-Noise", description: "Maximize signal, minimize noise" },
|
|
4106
|
-
{ name: "Simplicity", description: "Amplify through simplification" }
|
|
4107
|
-
]
|
|
4108
|
-
},
|
|
4109
|
-
"Carmine Gallo": {
|
|
4110
|
-
name: "Carmine Gallo",
|
|
4111
|
-
principles: [
|
|
4112
|
-
{ name: "Rule of Three", description: "Maximum 3 key messages" },
|
|
4113
|
-
{ name: "Emotional Connection", description: "Connect emotionally first" }
|
|
4114
|
-
]
|
|
4115
|
-
},
|
|
4116
|
-
"Chris Anderson": {
|
|
4117
|
-
name: "Chris Anderson",
|
|
4118
|
-
principles: [
|
|
4119
|
-
{ name: "One Idea", description: "One powerful idea per talk" },
|
|
4120
|
-
{ name: "Dead Laptop Test", description: "Present without slides" }
|
|
4121
|
-
]
|
|
4122
|
-
}
|
|
4123
|
-
},
|
|
4124
|
-
frameworkSelector: {
|
|
4125
|
-
byAudience: {
|
|
4126
|
-
"board": {
|
|
4127
|
-
primaryFramework: "Barbara Minto",
|
|
4128
|
-
slideTypes: ["executive_summary", "data_insight"]
|
|
4129
|
-
},
|
|
4130
|
-
"sales": {
|
|
4131
|
-
primaryFramework: "Nancy Duarte",
|
|
4132
|
-
slideTypes: ["big_idea", "social_proof"]
|
|
4133
|
-
}
|
|
4134
|
-
},
|
|
4135
|
-
byGoal: {
|
|
4136
|
-
"persuade": {
|
|
4137
|
-
primaryFramework: "Nancy Duarte",
|
|
4138
|
-
slideTypes: ["big_idea", "star_moment"]
|
|
4139
|
-
},
|
|
4140
|
-
"inform": {
|
|
4141
|
-
primaryFramework: "Barbara Minto",
|
|
4142
|
-
slideTypes: ["bullet_points", "data_insight"]
|
|
4143
|
-
}
|
|
4144
|
-
}
|
|
4145
|
-
},
|
|
4146
|
-
automatedQA: {
|
|
4147
|
-
scoringRubric: {
|
|
4148
|
-
totalPoints: 100,
|
|
4149
|
-
passingThreshold: 95,
|
|
4150
|
-
categories: {
|
|
4151
|
-
visual: { weight: 35, checks: {} },
|
|
4152
|
-
content: { weight: 30, checks: {} },
|
|
4153
|
-
expert: { weight: 25, checks: {} },
|
|
4154
|
-
accessibility: { weight: 10, checks: {} }
|
|
4155
|
-
}
|
|
4156
|
-
}
|
|
4157
|
-
},
|
|
4158
|
-
slideTypes: {},
|
|
4159
|
-
modes: {
|
|
4160
|
-
keynote: { maxWords: 25, minWhitespace: 35 },
|
|
4161
|
-
business: { maxWords: 80, minWhitespace: 25 }
|
|
4162
|
-
}
|
|
4163
|
-
};
|
|
4164
|
-
}
|
|
4165
|
-
};
|
|
4166
|
-
var knowledgeBaseInstance = null;
|
|
4167
|
-
function getKnowledgeBase() {
|
|
4168
|
-
if (!knowledgeBaseInstance) {
|
|
4169
|
-
knowledgeBaseInstance = new KnowledgeBase();
|
|
4170
|
-
}
|
|
4171
|
-
return knowledgeBaseInstance;
|
|
4172
|
-
}
|
|
4173
|
-
|
|
4174
4593
|
// src/index.ts
|
|
4175
4594
|
async function generate(config) {
|
|
4176
4595
|
const engine = new PresentationEngine();
|
|
@@ -4185,7 +4604,7 @@ async function validate(presentation, options) {
|
|
|
4185
4604
|
score
|
|
4186
4605
|
};
|
|
4187
4606
|
}
|
|
4188
|
-
var VERSION = "
|
|
4607
|
+
var VERSION = "6.0.0";
|
|
4189
4608
|
var index_default = {
|
|
4190
4609
|
generate,
|
|
4191
4610
|
validate,
|
|
@@ -4199,7 +4618,7 @@ var index_default = {
|
|
|
4199
4618
|
CompositeChartProvider,
|
|
4200
4619
|
CompositeImageProvider,
|
|
4201
4620
|
ContentAnalyzer,
|
|
4202
|
-
|
|
4621
|
+
KnowledgeGateway,
|
|
4203
4622
|
LocalImageProvider,
|
|
4204
4623
|
MermaidProvider,
|
|
4205
4624
|
PlaceholderImageProvider,
|
|
@@ -4219,7 +4638,7 @@ var index_default = {
|
|
|
4219
4638
|
createDefaultChartProvider,
|
|
4220
4639
|
createDefaultImageProvider,
|
|
4221
4640
|
generate,
|
|
4222
|
-
|
|
4641
|
+
getKnowledgeGateway,
|
|
4223
4642
|
validate
|
|
4224
4643
|
});
|
|
4225
4644
|
/**
|