claude-presentation-master 7.3.0 → 8.0.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/README.md +57 -11
- package/assets/presentation-knowledge.yaml +90 -0
- package/bin/cli.js +51 -0
- package/dist/index.d.mts +227 -285
- package/dist/index.d.ts +227 -285
- package/dist/index.js +2176 -1378
- package/dist/index.mjs +2174 -1373
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -26,6 +26,11 @@ var TemplateNotFoundError = class extends Error {
|
|
|
26
26
|
}
|
|
27
27
|
};
|
|
28
28
|
|
|
29
|
+
// src/core/PresentationEngine.ts
|
|
30
|
+
import * as fs2 from "fs";
|
|
31
|
+
import * as path2 from "path";
|
|
32
|
+
import * as os from "os";
|
|
33
|
+
|
|
29
34
|
// src/kb/KnowledgeGateway.ts
|
|
30
35
|
import { readFileSync } from "fs";
|
|
31
36
|
import { join, dirname } from "path";
|
|
@@ -128,12 +133,12 @@ var KnowledgeGateway = class {
|
|
|
128
133
|
join(process.cwd(), "assets/presentation-knowledge.yaml"),
|
|
129
134
|
join(process.cwd(), "node_modules/claude-presentation-master/assets/presentation-knowledge.yaml")
|
|
130
135
|
];
|
|
131
|
-
for (const
|
|
136
|
+
for (const path3 of possiblePaths) {
|
|
132
137
|
try {
|
|
133
|
-
const content = readFileSync(
|
|
138
|
+
const content = readFileSync(path3, "utf-8");
|
|
134
139
|
this.kb = yaml.parse(content);
|
|
135
140
|
this.loaded = true;
|
|
136
|
-
logger.step(`Knowledge base loaded from ${
|
|
141
|
+
logger.step(`Knowledge base loaded from ${path3}`);
|
|
137
142
|
return;
|
|
138
143
|
} catch {
|
|
139
144
|
}
|
|
@@ -158,33 +163,54 @@ var KnowledgeGateway = class {
|
|
|
158
163
|
return "keynote";
|
|
159
164
|
}
|
|
160
165
|
/**
|
|
161
|
-
* Get word limits for the specified mode
|
|
166
|
+
* Get word limits for the specified mode.
|
|
167
|
+
* READS FROM KB - NO HARDCODED VALUES.
|
|
162
168
|
*/
|
|
163
169
|
getWordLimits(mode) {
|
|
170
|
+
this.ensureLoaded();
|
|
164
171
|
const modeConfig = this.getMode(mode);
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
return { min: 40, max: 80, ideal: 60 };
|
|
172
|
+
const rangeStr = modeConfig.characteristics.words_per_slide;
|
|
173
|
+
const match = rangeStr.match(/(\d+)-(\d+)/);
|
|
174
|
+
if (!match || !match[1] || !match[2]) {
|
|
175
|
+
throw new Error(`[KB ERROR] Invalid words_per_slide format in KB for ${mode}: "${rangeStr}". Expected "min-max" format.`);
|
|
170
176
|
}
|
|
177
|
+
const min = parseInt(match[1], 10);
|
|
178
|
+
const max = parseInt(match[2], 10);
|
|
179
|
+
const ideal = Math.round((min + max) / 2);
|
|
180
|
+
logger.debug(`[KB] getWordLimits(${mode}): min=${min}, max=${max}, ideal=${ideal} (from KB: "${rangeStr}")`);
|
|
181
|
+
return { min, max, ideal };
|
|
171
182
|
}
|
|
172
183
|
/**
|
|
173
|
-
* Get bullet point limits
|
|
184
|
+
* Get bullet point limits.
|
|
185
|
+
* READS FROM KB - NO HARDCODED VALUES.
|
|
174
186
|
*/
|
|
175
187
|
getBulletLimits(mode) {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
188
|
+
this.ensureLoaded();
|
|
189
|
+
const type = mode === "keynote" ? "ted_keynote" : "consulting_deck";
|
|
190
|
+
const typeConfig = this.kb.presentation_types?.[type];
|
|
191
|
+
if (typeConfig?.validation_rules?.bullets_per_slide) {
|
|
192
|
+
const maxBullets = typeConfig.validation_rules.bullets_per_slide.max;
|
|
193
|
+
const wordLimits = this.getWordLimits(mode);
|
|
194
|
+
const maxWordsPerBullet = maxBullets > 0 ? Math.floor(wordLimits.max / maxBullets) : 0;
|
|
195
|
+
logger.debug(`[KB] getBulletLimits(${mode}): maxBullets=${maxBullets}, maxWordsPerBullet=${maxWordsPerBullet} (from KB presentation_types.${type})`);
|
|
196
|
+
return { maxBullets, maxWordsPerBullet };
|
|
180
197
|
}
|
|
198
|
+
throw new Error(`[KB ERROR] Missing bullets_per_slide in KB for presentation_types.${type}.validation_rules`);
|
|
181
199
|
}
|
|
182
200
|
/**
|
|
183
|
-
* Get whitespace percentage requirement
|
|
201
|
+
* Get whitespace percentage requirement.
|
|
202
|
+
* READS FROM KB - NO HARDCODED VALUES.
|
|
184
203
|
*/
|
|
185
204
|
getWhitespaceRequirement(mode) {
|
|
186
|
-
|
|
187
|
-
|
|
205
|
+
this.ensureLoaded();
|
|
206
|
+
const type = mode === "keynote" ? "ted_keynote" : "consulting_deck";
|
|
207
|
+
const typeConfig = this.kb.presentation_types?.[type];
|
|
208
|
+
if (typeConfig?.validation_rules?.whitespace?.min) {
|
|
209
|
+
const whitespace = typeConfig.validation_rules.whitespace.min / 100;
|
|
210
|
+
logger.debug(`[KB] getWhitespaceRequirement(${mode}): ${whitespace} (from KB presentation_types.${type})`);
|
|
211
|
+
return whitespace;
|
|
212
|
+
}
|
|
213
|
+
throw new Error(`[KB ERROR] Missing whitespace.min in KB for presentation_types.${type}.validation_rules`);
|
|
188
214
|
}
|
|
189
215
|
/**
|
|
190
216
|
* Get slide templates for the specified mode
|
|
@@ -295,29 +321,54 @@ var KnowledgeGateway = class {
|
|
|
295
321
|
return null;
|
|
296
322
|
}
|
|
297
323
|
/**
|
|
298
|
-
* Get Duarte's glance test requirements
|
|
324
|
+
* Get Duarte's glance test requirements.
|
|
325
|
+
* READS FROM KB - NO HARDCODED VALUES.
|
|
299
326
|
*/
|
|
300
327
|
getDuarteGlanceTest() {
|
|
301
328
|
this.ensureLoaded();
|
|
302
|
-
const duarte = this.kb.experts
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
329
|
+
const duarte = this.kb.experts?.nancy_duarte;
|
|
330
|
+
if (!duarte?.core_principles?.glance_test?.word_limit) {
|
|
331
|
+
throw new Error("[KB ERROR] Missing experts.nancy_duarte.core_principles.glance_test.word_limit in KB");
|
|
332
|
+
}
|
|
333
|
+
if (!duarte?.core_principles?.glance_test?.time_limit) {
|
|
334
|
+
throw new Error("[KB ERROR] Missing experts.nancy_duarte.core_principles.glance_test.time_limit in KB");
|
|
335
|
+
}
|
|
336
|
+
const wordLimit = duarte.core_principles.glance_test.word_limit;
|
|
337
|
+
const timeSeconds = parseInt(duarte.core_principles.glance_test.time_limit.replace(/\D/g, ""), 10);
|
|
338
|
+
logger.debug(`[KB] getDuarteGlanceTest(): wordLimit=${wordLimit}, timeSeconds=${timeSeconds} (from KB experts.nancy_duarte)`);
|
|
339
|
+
return { wordLimit, timeSeconds };
|
|
307
340
|
}
|
|
308
341
|
/**
|
|
309
|
-
* Get Miller's Law constraints
|
|
342
|
+
* Get Miller's Law constraints.
|
|
343
|
+
* READS FROM KB - NO HARDCODED VALUES.
|
|
310
344
|
*/
|
|
311
345
|
getMillersLaw() {
|
|
312
346
|
this.ensureLoaded();
|
|
313
|
-
|
|
347
|
+
const millersLaw = this.kb.cognitive_science?.millers_law;
|
|
348
|
+
if (!millersLaw?.principle) {
|
|
349
|
+
throw new Error("[KB ERROR] Missing cognitive_science.millers_law.principle in KB");
|
|
350
|
+
}
|
|
351
|
+
const match = millersLaw.principle.match(/(\d+)\s*[±+-]\s*(\d+)/);
|
|
352
|
+
if (!match) {
|
|
353
|
+
throw new Error(`[KB ERROR] Cannot parse Miller's Law from KB: "${millersLaw.principle}". Expected format: "7 \xB1 2"`);
|
|
354
|
+
}
|
|
355
|
+
const center = parseInt(match[1], 10);
|
|
356
|
+
const range = parseInt(match[2], 10);
|
|
357
|
+
const minItems = center - range;
|
|
358
|
+
const maxItems = center + range;
|
|
359
|
+
logger.debug(`[KB] getMillersLaw(): minItems=${minItems}, maxItems=${maxItems} (from KB: "${millersLaw.principle}")`);
|
|
360
|
+
return { minItems, maxItems };
|
|
314
361
|
}
|
|
315
362
|
/**
|
|
316
|
-
* Get cognitive load constraints
|
|
363
|
+
* Get cognitive load constraints.
|
|
364
|
+
* READS FROM KB - NO HARDCODED VALUES.
|
|
317
365
|
*/
|
|
318
366
|
getCognitiveLoadLimits() {
|
|
319
367
|
this.ensureLoaded();
|
|
320
|
-
|
|
368
|
+
const millers = this.getMillersLaw();
|
|
369
|
+
const maxItemsPerChunk = millers.maxItems - 2;
|
|
370
|
+
logger.debug(`[KB] getCognitiveLoadLimits(): maxItemsPerChunk=${maxItemsPerChunk} (derived from Miller's Law)`);
|
|
371
|
+
return { maxItemsPerChunk };
|
|
321
372
|
}
|
|
322
373
|
/**
|
|
323
374
|
* Get chart selection guidance
|
|
@@ -381,42 +432,40 @@ var KnowledgeGateway = class {
|
|
|
381
432
|
this.ensureLoaded();
|
|
382
433
|
const typeConfig = this.kb.presentation_types?.[type];
|
|
383
434
|
if (!typeConfig?.validation_rules) {
|
|
384
|
-
|
|
385
|
-
return mode === "keynote" ? {
|
|
386
|
-
wordsPerSlide: { min: 1, max: 25, ideal: 10 },
|
|
387
|
-
whitespace: { min: 40, ideal: 50 },
|
|
388
|
-
bulletsPerSlide: { max: 3 },
|
|
389
|
-
actionTitlesRequired: false,
|
|
390
|
-
sourcesRequired: false
|
|
391
|
-
} : {
|
|
392
|
-
wordsPerSlide: { min: 40, max: 80, ideal: 60 },
|
|
393
|
-
whitespace: { min: 25, ideal: 30 },
|
|
394
|
-
bulletsPerSlide: { max: 5 },
|
|
395
|
-
actionTitlesRequired: true,
|
|
396
|
-
sourcesRequired: true
|
|
397
|
-
};
|
|
435
|
+
throw new Error(`[KB ERROR] Missing presentation_types.${type}.validation_rules in KB. Cannot proceed without KB rules.`);
|
|
398
436
|
}
|
|
399
437
|
const rules = typeConfig.validation_rules;
|
|
400
|
-
|
|
438
|
+
if (rules.words_per_slide?.max === void 0) {
|
|
439
|
+
throw new Error(`[KB ERROR] Missing presentation_types.${type}.validation_rules.words_per_slide.max in KB`);
|
|
440
|
+
}
|
|
441
|
+
if (rules.whitespace?.min === void 0) {
|
|
442
|
+
throw new Error(`[KB ERROR] Missing presentation_types.${type}.validation_rules.whitespace.min in KB`);
|
|
443
|
+
}
|
|
444
|
+
if (rules.bullets_per_slide?.max === void 0) {
|
|
445
|
+
throw new Error(`[KB ERROR] Missing presentation_types.${type}.validation_rules.bullets_per_slide.max in KB`);
|
|
446
|
+
}
|
|
447
|
+
const result = {
|
|
401
448
|
wordsPerSlide: {
|
|
402
|
-
min: rules.words_per_slide
|
|
403
|
-
max: rules.words_per_slide
|
|
404
|
-
ideal: rules.words_per_slide
|
|
449
|
+
min: rules.words_per_slide.min,
|
|
450
|
+
max: rules.words_per_slide.max,
|
|
451
|
+
ideal: rules.words_per_slide.ideal
|
|
405
452
|
},
|
|
406
453
|
whitespace: {
|
|
407
|
-
min: rules.whitespace
|
|
408
|
-
ideal: rules.whitespace
|
|
454
|
+
min: rules.whitespace.min,
|
|
455
|
+
ideal: rules.whitespace.ideal
|
|
409
456
|
},
|
|
410
457
|
bulletsPerSlide: {
|
|
411
|
-
max: rules.bullets_per_slide
|
|
458
|
+
max: rules.bullets_per_slide.max
|
|
412
459
|
},
|
|
413
|
-
actionTitlesRequired: rules.action_titles_required
|
|
414
|
-
sourcesRequired: rules.sources_required
|
|
460
|
+
actionTitlesRequired: rules.action_titles_required,
|
|
461
|
+
sourcesRequired: rules.sources_required,
|
|
415
462
|
calloutsRequired: rules.callouts_required,
|
|
416
463
|
denseDataAllowed: rules.dense_data_allowed,
|
|
417
464
|
codeBlocksAllowed: rules.code_blocks_allowed,
|
|
418
465
|
diagramsRequired: rules.diagrams_required
|
|
419
466
|
};
|
|
467
|
+
logger.debug(`[KB] getValidationRules(${type}): words=${result.wordsPerSlide.min}-${result.wordsPerSlide.max}, bullets=${result.bulletsPerSlide.max}, whitespace=${result.whitespace.min}%`);
|
|
468
|
+
return result;
|
|
420
469
|
}
|
|
421
470
|
/**
|
|
422
471
|
* Get required elements that MUST be in the deck for this type.
|
|
@@ -526,39 +575,146 @@ var KnowledgeGateway = class {
|
|
|
526
575
|
* Map a content pattern to the best allowed slide type.
|
|
527
576
|
* CRITICAL: This is how SlideFactory decides which slide type to use.
|
|
528
577
|
*/
|
|
578
|
+
/**
|
|
579
|
+
* Map semantic slide types to structural types that SlideFactory can handle.
|
|
580
|
+
* This bridges the gap between KB's rich semantic types and code's structural types.
|
|
581
|
+
*/
|
|
582
|
+
normalizeToStructuralType(semanticType) {
|
|
583
|
+
const normalizedType = semanticType.toLowerCase().replace(/_/g, "-");
|
|
584
|
+
const structuralTypes = /* @__PURE__ */ new Set([
|
|
585
|
+
"title",
|
|
586
|
+
"agenda",
|
|
587
|
+
"thank-you",
|
|
588
|
+
"cta",
|
|
589
|
+
"single-statement",
|
|
590
|
+
"big-number",
|
|
591
|
+
"quote",
|
|
592
|
+
"bullet-points",
|
|
593
|
+
"two-column",
|
|
594
|
+
"three-column",
|
|
595
|
+
"three-points",
|
|
596
|
+
"comparison",
|
|
597
|
+
"timeline",
|
|
598
|
+
"process",
|
|
599
|
+
"metrics-grid",
|
|
600
|
+
"data-insight",
|
|
601
|
+
"full-image",
|
|
602
|
+
"screenshot",
|
|
603
|
+
"code-snippet",
|
|
604
|
+
"title-impact"
|
|
605
|
+
]);
|
|
606
|
+
if (structuralTypes.has(normalizedType)) {
|
|
607
|
+
return normalizedType;
|
|
608
|
+
}
|
|
609
|
+
const semanticToStructural = {
|
|
610
|
+
// TED Keynote semantic types
|
|
611
|
+
"star-moment": "single-statement",
|
|
612
|
+
"call-to-action": "cta",
|
|
613
|
+
// Sales pitch semantic types
|
|
614
|
+
"problem-statement": "single-statement",
|
|
615
|
+
"solution-overview": "single-statement",
|
|
616
|
+
"social-proof": "quote",
|
|
617
|
+
"testimonial": "quote",
|
|
618
|
+
"pricing": "metrics-grid",
|
|
619
|
+
"demo-screenshot": "screenshot",
|
|
620
|
+
// Consulting deck semantic types
|
|
621
|
+
"executive-summary-scr": "bullet-points",
|
|
622
|
+
"executive-summary": "bullet-points",
|
|
623
|
+
"mece-breakdown": "three-column",
|
|
624
|
+
"process-timeline": "timeline",
|
|
625
|
+
"recommendation": "single-statement",
|
|
626
|
+
"risks-mitigation": "two-column",
|
|
627
|
+
"next-steps": "bullet-points",
|
|
628
|
+
"detailed-findings": "bullet-points",
|
|
629
|
+
// Investment banking semantic types
|
|
630
|
+
"situation-overview": "bullet-points",
|
|
631
|
+
"credentials": "metrics-grid",
|
|
632
|
+
"valuation-summary": "metrics-grid",
|
|
633
|
+
"football-field": "comparison",
|
|
634
|
+
"comparable-companies": "metrics-grid",
|
|
635
|
+
"precedent-transactions": "metrics-grid",
|
|
636
|
+
"dcf-summary": "metrics-grid",
|
|
637
|
+
"waterfall-bridge": "timeline",
|
|
638
|
+
"sources-uses": "two-column",
|
|
639
|
+
"risk-factors": "bullet-points",
|
|
640
|
+
// Investor pitch semantic types
|
|
641
|
+
"problem": "single-statement",
|
|
642
|
+
"solution": "single-statement",
|
|
643
|
+
"market-size": "big-number",
|
|
644
|
+
"product": "three-points",
|
|
645
|
+
"business-model": "three-column",
|
|
646
|
+
"traction": "metrics-grid",
|
|
647
|
+
"competition": "comparison",
|
|
648
|
+
"team": "three-column",
|
|
649
|
+
"financials": "metrics-grid",
|
|
650
|
+
"ask": "single-statement",
|
|
651
|
+
// Technical presentation semantic types
|
|
652
|
+
"architecture-diagram": "full-image",
|
|
653
|
+
"data-flow": "full-image",
|
|
654
|
+
"metrics": "metrics-grid",
|
|
655
|
+
"tradeoffs": "comparison",
|
|
656
|
+
// All hands semantic types
|
|
657
|
+
"celebration": "single-statement",
|
|
658
|
+
"announcement": "single-statement",
|
|
659
|
+
"milestones": "timeline",
|
|
660
|
+
"team-recognition": "three-column",
|
|
661
|
+
"roadmap": "timeline"
|
|
662
|
+
};
|
|
663
|
+
return semanticToStructural[normalizedType] || "bullet-points";
|
|
664
|
+
}
|
|
529
665
|
mapContentPatternToSlideType(pattern, allowedTypes) {
|
|
530
666
|
const patternToTypes = {
|
|
531
|
-
big_number: ["
|
|
532
|
-
comparison: ["comparison", "
|
|
533
|
-
timeline: ["timeline", "
|
|
534
|
-
process: ["process", "
|
|
535
|
-
metrics: ["
|
|
536
|
-
quote: ["quote", "
|
|
537
|
-
code: ["
|
|
538
|
-
bullets: ["
|
|
539
|
-
prose: ["
|
|
667
|
+
big_number: ["big-number", "metrics-grid", "single-statement"],
|
|
668
|
+
comparison: ["comparison", "two-column", "three-column"],
|
|
669
|
+
timeline: ["timeline", "process", "three-points", "bullet-points"],
|
|
670
|
+
process: ["process", "timeline", "three-column", "three-points", "bullet-points"],
|
|
671
|
+
metrics: ["metrics-grid", "big-number", "three-points"],
|
|
672
|
+
quote: ["quote", "single-statement"],
|
|
673
|
+
code: ["code-snippet", "two-column"],
|
|
674
|
+
bullets: ["bullet-points", "three-points", "three-column", "two-column", "process"],
|
|
675
|
+
prose: ["single-statement", "title-impact", "two-column"]
|
|
540
676
|
};
|
|
677
|
+
if (pattern.primaryPattern === "process" || pattern.primaryPattern === "bullets") {
|
|
678
|
+
if (pattern.bulletCount === 3) {
|
|
679
|
+
const threePointTypes = ["three-points", "three-column"];
|
|
680
|
+
for (const t of threePointTypes) {
|
|
681
|
+
if (this.isTypeAllowed(t, allowedTypes)) return t;
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
if (pattern.bulletCount && pattern.bulletCount >= 2 && pattern.bulletCount <= 6) {
|
|
685
|
+
const processTypes = ["timeline", "process", "bullet-points"];
|
|
686
|
+
for (const t of processTypes) {
|
|
687
|
+
if (this.isTypeAllowed(t, allowedTypes)) return t;
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
}
|
|
541
691
|
const preferredTypes = patternToTypes[pattern.primaryPattern] || patternToTypes.prose || [];
|
|
542
692
|
for (const preferred of preferredTypes) {
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
}
|
|
549
|
-
const contentTypes = [
|
|
550
|
-
"bullet_points",
|
|
551
|
-
"bullet-points",
|
|
552
|
-
"two_column",
|
|
553
|
-
"two-column",
|
|
554
|
-
"three_column",
|
|
555
|
-
"three-column",
|
|
556
|
-
"data_insight"
|
|
557
|
-
];
|
|
693
|
+
if (this.isTypeAllowed(preferred, allowedTypes)) {
|
|
694
|
+
return preferred;
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
const contentTypes = ["bullet-points", "three-points", "single-statement"];
|
|
558
698
|
for (const ct of contentTypes) {
|
|
559
|
-
if (
|
|
699
|
+
if (this.isTypeAllowed(ct, allowedTypes)) return ct;
|
|
700
|
+
}
|
|
701
|
+
return this.normalizeToStructuralType(allowedTypes[0] ?? "bullet-points");
|
|
702
|
+
}
|
|
703
|
+
/**
|
|
704
|
+
* Check if a structural type is allowed (directly or via semantic mapping)
|
|
705
|
+
*/
|
|
706
|
+
isTypeAllowed(structuralType, allowedTypes) {
|
|
707
|
+
const normalized = structuralType.toLowerCase().replace(/_/g, "-");
|
|
708
|
+
const underscored = normalized.replace(/-/g, "_");
|
|
709
|
+
if (allowedTypes.includes(normalized) || allowedTypes.includes(underscored)) {
|
|
710
|
+
return true;
|
|
711
|
+
}
|
|
712
|
+
for (const allowed of allowedTypes) {
|
|
713
|
+
if (this.normalizeToStructuralType(allowed) === normalized) {
|
|
714
|
+
return true;
|
|
715
|
+
}
|
|
560
716
|
}
|
|
561
|
-
return
|
|
717
|
+
return false;
|
|
562
718
|
}
|
|
563
719
|
/**
|
|
564
720
|
* Get a specific slide template by name from the KB.
|
|
@@ -619,7 +775,8 @@ var KnowledgeGateway = class {
|
|
|
619
775
|
optionLabels: ["Option A", "Option B", "Option C", "Option D"]
|
|
620
776
|
},
|
|
621
777
|
column: { labelTemplate: "Point {n}" },
|
|
622
|
-
|
|
778
|
+
// Subtitle needs more room for complete phrases - at least 10 words for keynote
|
|
779
|
+
subtitle: { maxWords: mode === "keynote" ? 12 : Math.min(20, Math.floor(maxWords / 2)) },
|
|
623
780
|
context: { maxWords: Math.min(30, Math.floor(maxWords / 2)) },
|
|
624
781
|
step: { maxWords: Math.min(20, Math.floor(maxWords / 4)) },
|
|
625
782
|
columnContent: { maxWords: Math.min(25, Math.floor(maxWords / 3)) }
|
|
@@ -974,7 +1131,14 @@ var ContentAnalyzer = class {
|
|
|
974
1131
|
if (foundTitle && i > titleLineIndex && line.length > 0) {
|
|
975
1132
|
if (line.startsWith("#")) break;
|
|
976
1133
|
if (line.startsWith("-") || line.startsWith("*") || /^\d+\./.test(line)) break;
|
|
977
|
-
|
|
1134
|
+
const cleanLine = line.replace(/\*\*/g, "").trim();
|
|
1135
|
+
if (cleanLine.length <= 120) {
|
|
1136
|
+
subtitle = cleanLine;
|
|
1137
|
+
} else {
|
|
1138
|
+
const truncated = cleanLine.slice(0, 120);
|
|
1139
|
+
const lastSpace = truncated.lastIndexOf(" ");
|
|
1140
|
+
subtitle = lastSpace > 80 ? truncated.slice(0, lastSpace) : truncated;
|
|
1141
|
+
}
|
|
978
1142
|
break;
|
|
979
1143
|
}
|
|
980
1144
|
}
|
|
@@ -1097,7 +1261,60 @@ var ContentAnalyzer = class {
|
|
|
1097
1261
|
sections.push(currentSection);
|
|
1098
1262
|
}
|
|
1099
1263
|
}
|
|
1100
|
-
return sections;
|
|
1264
|
+
return this.combineParentChildSections(sections);
|
|
1265
|
+
}
|
|
1266
|
+
/**
|
|
1267
|
+
* Combine parent sections with their child sub-sections.
|
|
1268
|
+
* Detects patterns like "Three Pillars" + 3 child sections and merges them.
|
|
1269
|
+
*/
|
|
1270
|
+
combineParentChildSections(sections) {
|
|
1271
|
+
const result = [];
|
|
1272
|
+
let i = 0;
|
|
1273
|
+
while (i < sections.length) {
|
|
1274
|
+
const current = sections[i];
|
|
1275
|
+
if (current && current.level === 2 && (!current.content || current.content.length < 20) && current.bullets.length === 0) {
|
|
1276
|
+
const children = [];
|
|
1277
|
+
let j = i + 1;
|
|
1278
|
+
while (j < sections.length && children.length < 4) {
|
|
1279
|
+
const child = sections[j];
|
|
1280
|
+
if (!child || child.level !== 3) break;
|
|
1281
|
+
children.push(child);
|
|
1282
|
+
j++;
|
|
1283
|
+
}
|
|
1284
|
+
if (children.length >= 2 && children.length <= 4) {
|
|
1285
|
+
const combined = {
|
|
1286
|
+
header: current.header,
|
|
1287
|
+
level: current.level,
|
|
1288
|
+
content: "",
|
|
1289
|
+
// Will use bullets instead
|
|
1290
|
+
bullets: children.map((child) => {
|
|
1291
|
+
const title = child.header.replace(/^\d+\.\s*/, "").trim();
|
|
1292
|
+
if (child.content) {
|
|
1293
|
+
return `**${title}**: ${child.content.split("\n")[0] || ""}`;
|
|
1294
|
+
}
|
|
1295
|
+
return title;
|
|
1296
|
+
}),
|
|
1297
|
+
metrics: [
|
|
1298
|
+
...current.metrics,
|
|
1299
|
+
...children.flatMap((c) => c.metrics)
|
|
1300
|
+
],
|
|
1301
|
+
// Add sub-sections as a property for advanced slide types
|
|
1302
|
+
subSections: children.map((child) => ({
|
|
1303
|
+
title: child.header.replace(/^\d+\.\s*/, "").trim(),
|
|
1304
|
+
content: child.content
|
|
1305
|
+
}))
|
|
1306
|
+
};
|
|
1307
|
+
result.push(combined);
|
|
1308
|
+
i = j;
|
|
1309
|
+
continue;
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
if (current) {
|
|
1313
|
+
result.push(current);
|
|
1314
|
+
}
|
|
1315
|
+
i++;
|
|
1316
|
+
}
|
|
1317
|
+
return result;
|
|
1101
1318
|
}
|
|
1102
1319
|
/**
|
|
1103
1320
|
* Extract SCQA structure (Barbara Minto)
|
|
@@ -1613,6 +1830,11 @@ var ContentPatternClassifier = class {
|
|
|
1613
1830
|
}
|
|
1614
1831
|
/**
|
|
1615
1832
|
* Extract timeline/process steps from content
|
|
1833
|
+
* Handles multiple formats:
|
|
1834
|
+
* - "Step 1: Do something" → label: "Step 1", description: "Do something"
|
|
1835
|
+
* - "1. First item" → label: "1", description: "First item"
|
|
1836
|
+
* - "Title - Description text" → label: "Title", description: "Description text"
|
|
1837
|
+
* - "Plain text" → label: "1", description: "Plain text"
|
|
1616
1838
|
*/
|
|
1617
1839
|
extractSteps(section) {
|
|
1618
1840
|
const steps = [];
|
|
@@ -1623,12 +1845,28 @@ var ContentPatternClassifier = class {
|
|
|
1623
1845
|
label: stepMatch[1].trim(),
|
|
1624
1846
|
description: stepMatch[2].trim()
|
|
1625
1847
|
});
|
|
1626
|
-
|
|
1848
|
+
continue;
|
|
1849
|
+
}
|
|
1850
|
+
const dashMatch = bullet.match(/^([^-]+)\s+-\s+(.+)$/);
|
|
1851
|
+
if (dashMatch && dashMatch[1] && dashMatch[2]) {
|
|
1852
|
+
steps.push({
|
|
1853
|
+
label: dashMatch[1].trim(),
|
|
1854
|
+
description: dashMatch[2].trim()
|
|
1855
|
+
});
|
|
1856
|
+
continue;
|
|
1857
|
+
}
|
|
1858
|
+
const colonMatch = bullet.match(/^([^:]+):\s*(.+)$/);
|
|
1859
|
+
if (colonMatch && colonMatch[1] && colonMatch[2] && colonMatch[1].length < 40) {
|
|
1627
1860
|
steps.push({
|
|
1628
|
-
label:
|
|
1629
|
-
description:
|
|
1861
|
+
label: colonMatch[1].trim(),
|
|
1862
|
+
description: colonMatch[2].trim()
|
|
1630
1863
|
});
|
|
1864
|
+
continue;
|
|
1631
1865
|
}
|
|
1866
|
+
steps.push({
|
|
1867
|
+
label: `${steps.length + 1}`,
|
|
1868
|
+
description: bullet
|
|
1869
|
+
});
|
|
1632
1870
|
}
|
|
1633
1871
|
return steps;
|
|
1634
1872
|
}
|
|
@@ -1659,11 +1897,538 @@ var ContentPatternClassifier = class {
|
|
|
1659
1897
|
}
|
|
1660
1898
|
};
|
|
1661
1899
|
|
|
1900
|
+
// src/kb/ExpertGuidanceEngine.ts
|
|
1901
|
+
var ExpertGuidanceEngine = class {
|
|
1902
|
+
kb;
|
|
1903
|
+
presentationType;
|
|
1904
|
+
constructor(kb, presentationType) {
|
|
1905
|
+
this.kb = kb;
|
|
1906
|
+
this.presentationType = presentationType;
|
|
1907
|
+
}
|
|
1908
|
+
/**
|
|
1909
|
+
* Get the narrative arc for this presentation type.
|
|
1910
|
+
* Returns the story structure with required elements and their purposes.
|
|
1911
|
+
*/
|
|
1912
|
+
getNarrativeArc() {
|
|
1913
|
+
const mode = this.kb.getModeForType(this.presentationType);
|
|
1914
|
+
if (mode === "keynote") {
|
|
1915
|
+
return {
|
|
1916
|
+
framework: "Duarte Sparkline",
|
|
1917
|
+
expert: "Nancy Duarte",
|
|
1918
|
+
description: "Alternate between What Is (current reality) and What Could Be (possibility), building emotional tension to a climax, then resolve with New Bliss",
|
|
1919
|
+
elements: [
|
|
1920
|
+
{
|
|
1921
|
+
id: "opening_hook",
|
|
1922
|
+
name: "Opening Hook",
|
|
1923
|
+
purpose: "Grab attention in first 10 seconds with something unexpected",
|
|
1924
|
+
expertSource: 'Chris Anderson: "You have one chance to hook them"',
|
|
1925
|
+
designPrinciples: [
|
|
1926
|
+
"Start with a story, question, or surprising fact",
|
|
1927
|
+
"Create curiosity gap - make them NEED to know more",
|
|
1928
|
+
"Touch heart before head (Gallo)"
|
|
1929
|
+
],
|
|
1930
|
+
required: true
|
|
1931
|
+
},
|
|
1932
|
+
{
|
|
1933
|
+
id: "what_is",
|
|
1934
|
+
name: "What Is (Current Reality)",
|
|
1935
|
+
purpose: "Establish the current state that audience recognizes",
|
|
1936
|
+
expertSource: 'Nancy Duarte: "Ground them in familiar reality"',
|
|
1937
|
+
designPrinciples: [
|
|
1938
|
+
"Use concrete, recognizable situations",
|
|
1939
|
+
"Create emotional resonance with current pain",
|
|
1940
|
+
"Keep it brief - setup for contrast"
|
|
1941
|
+
],
|
|
1942
|
+
required: true
|
|
1943
|
+
},
|
|
1944
|
+
{
|
|
1945
|
+
id: "what_could_be",
|
|
1946
|
+
name: "What Could Be (Vision)",
|
|
1947
|
+
purpose: "Paint the picture of the transformed future",
|
|
1948
|
+
expertSource: 'Nancy Duarte: "Make them FEEL the possibility"',
|
|
1949
|
+
designPrinciples: [
|
|
1950
|
+
"Be specific and vivid about the future state",
|
|
1951
|
+
"Connect emotionally - how will they FEEL?",
|
|
1952
|
+
"Create contrast with What Is"
|
|
1953
|
+
],
|
|
1954
|
+
required: true
|
|
1955
|
+
},
|
|
1956
|
+
{
|
|
1957
|
+
id: "star_moment",
|
|
1958
|
+
name: "STAR Moment",
|
|
1959
|
+
purpose: "Something They'll Always Remember - the emotional climax",
|
|
1960
|
+
expertSource: 'Nancy Duarte: "One dramatic memorable moment"',
|
|
1961
|
+
designPrinciples: [
|
|
1962
|
+
"Make it unexpected and emotionally impactful",
|
|
1963
|
+
"Use a dramatic statistic, story, or demonstration",
|
|
1964
|
+
"This is what they'll tell others about",
|
|
1965
|
+
"It should give them chills or make them gasp"
|
|
1966
|
+
],
|
|
1967
|
+
required: true
|
|
1968
|
+
},
|
|
1969
|
+
{
|
|
1970
|
+
id: "call_to_action",
|
|
1971
|
+
name: "Call to Action",
|
|
1972
|
+
purpose: "Clear next step that audience can take",
|
|
1973
|
+
expertSource: 'Chris Anderson: "Give them something to DO"',
|
|
1974
|
+
designPrinciples: [
|
|
1975
|
+
"Be specific and actionable",
|
|
1976
|
+
"Make it achievable - first step, not whole journey",
|
|
1977
|
+
"Connect action to the vision you painted"
|
|
1978
|
+
],
|
|
1979
|
+
required: true
|
|
1980
|
+
},
|
|
1981
|
+
{
|
|
1982
|
+
id: "new_bliss",
|
|
1983
|
+
name: "New Bliss (Resolution)",
|
|
1984
|
+
purpose: "Leave them with the transformed future firmly in mind",
|
|
1985
|
+
expertSource: 'Nancy Duarte: "End with the new world, not the old"',
|
|
1986
|
+
designPrinciples: [
|
|
1987
|
+
"Reinforce the vision one final time",
|
|
1988
|
+
"Make them want to be part of it",
|
|
1989
|
+
"End on emotional high, not logistics"
|
|
1990
|
+
],
|
|
1991
|
+
required: false
|
|
1992
|
+
}
|
|
1993
|
+
]
|
|
1994
|
+
};
|
|
1995
|
+
} else {
|
|
1996
|
+
return {
|
|
1997
|
+
framework: "Minto Pyramid (SCQA)",
|
|
1998
|
+
expert: "Barbara Minto",
|
|
1999
|
+
description: "Lead with the answer/recommendation, then support with evidence in MECE structure",
|
|
2000
|
+
elements: [
|
|
2001
|
+
{
|
|
2002
|
+
id: "situation",
|
|
2003
|
+
name: "Situation",
|
|
2004
|
+
purpose: "Context the audience already knows and agrees with",
|
|
2005
|
+
expertSource: 'Barbara Minto: "Start from common ground"',
|
|
2006
|
+
designPrinciples: [
|
|
2007
|
+
"Facts everyone agrees on",
|
|
2008
|
+
"Recent and relevant context",
|
|
2009
|
+
"Brief - not the star of the show"
|
|
2010
|
+
],
|
|
2011
|
+
required: true
|
|
2012
|
+
},
|
|
2013
|
+
{
|
|
2014
|
+
id: "complication",
|
|
2015
|
+
name: "Complication",
|
|
2016
|
+
purpose: "The problem, threat, or opportunity that requires action",
|
|
2017
|
+
expertSource: 'Barbara Minto: "Why are we here?"',
|
|
2018
|
+
designPrinciples: [
|
|
2019
|
+
"Clear articulation of the challenge",
|
|
2020
|
+
"Why the status quo is unacceptable",
|
|
2021
|
+
"Creates urgency for the answer"
|
|
2022
|
+
],
|
|
2023
|
+
required: true
|
|
2024
|
+
},
|
|
2025
|
+
{
|
|
2026
|
+
id: "answer",
|
|
2027
|
+
name: "Answer/Recommendation",
|
|
2028
|
+
purpose: "The core recommendation - upfront, not buried",
|
|
2029
|
+
expertSource: 'Barbara Minto: "Answer first, evidence second"',
|
|
2030
|
+
designPrinciples: [
|
|
2031
|
+
"Clear, actionable recommendation",
|
|
2032
|
+
"Use an action title that states the conclusion",
|
|
2033
|
+
"Executive should understand even if they stop here"
|
|
2034
|
+
],
|
|
2035
|
+
required: true
|
|
2036
|
+
},
|
|
2037
|
+
{
|
|
2038
|
+
id: "evidence",
|
|
2039
|
+
name: "Supporting Evidence",
|
|
2040
|
+
purpose: "MECE support for the recommendation",
|
|
2041
|
+
expertSource: 'Barbara Minto: "Mutually Exclusive, Collectively Exhaustive"',
|
|
2042
|
+
designPrinciples: [
|
|
2043
|
+
"Group into 3-5 supporting pillars",
|
|
2044
|
+
"Each pillar has sub-evidence",
|
|
2045
|
+
"No gaps, no overlaps (MECE)",
|
|
2046
|
+
"Use action titles throughout"
|
|
2047
|
+
],
|
|
2048
|
+
required: true
|
|
2049
|
+
},
|
|
2050
|
+
{
|
|
2051
|
+
id: "next_steps",
|
|
2052
|
+
name: "Next Steps",
|
|
2053
|
+
purpose: "Clear actions with owners and timelines",
|
|
2054
|
+
expertSource: 'Analyst Academy: "End with who does what by when"',
|
|
2055
|
+
designPrinciples: [
|
|
2056
|
+
'Specific actions, not vague "follow up"',
|
|
2057
|
+
"Named owners where possible",
|
|
2058
|
+
"Realistic timelines"
|
|
2059
|
+
],
|
|
2060
|
+
required: true
|
|
2061
|
+
}
|
|
2062
|
+
]
|
|
2063
|
+
};
|
|
2064
|
+
}
|
|
2065
|
+
}
|
|
2066
|
+
/**
|
|
2067
|
+
* Get expert guidance for designing a specific type of slide.
|
|
2068
|
+
* This returns PRINCIPLES for excellence, not constraints.
|
|
2069
|
+
*/
|
|
2070
|
+
getSlideDesignGuidance(slideType) {
|
|
2071
|
+
const mode = this.kb.getModeForType(this.presentationType);
|
|
2072
|
+
switch (slideType) {
|
|
2073
|
+
case "title":
|
|
2074
|
+
case "title_impact":
|
|
2075
|
+
return {
|
|
2076
|
+
purpose: "Create immediate emotional connection and establish the one idea worth spreading",
|
|
2077
|
+
principles: [
|
|
2078
|
+
{
|
|
2079
|
+
expert: "Chris Anderson",
|
|
2080
|
+
principle: "One Idea Worth Spreading",
|
|
2081
|
+
application: "The title should encapsulate a single powerful idea that the audience will remember",
|
|
2082
|
+
technique: "Ask: If they remember ONE thing, what should it be? That's your title."
|
|
2083
|
+
},
|
|
2084
|
+
{
|
|
2085
|
+
expert: "Carmine Gallo",
|
|
2086
|
+
principle: "Headline Test",
|
|
2087
|
+
application: "Title should work as a Twitter headline - compelling in under 280 characters",
|
|
2088
|
+
technique: "Write 10 versions, pick the one that makes you want to hear more"
|
|
2089
|
+
},
|
|
2090
|
+
{
|
|
2091
|
+
expert: "Nancy Duarte",
|
|
2092
|
+
principle: "Glance Test",
|
|
2093
|
+
application: "Audience should grasp the essence in 3 seconds",
|
|
2094
|
+
technique: "Show it to someone for 3 seconds, ask what they remember"
|
|
2095
|
+
}
|
|
2096
|
+
],
|
|
2097
|
+
whatMakesItGreat: [
|
|
2098
|
+
"Creates curiosity - makes audience WANT to hear more",
|
|
2099
|
+
"Emotionally resonant - connects to something they care about",
|
|
2100
|
+
"Unexpected angle - not the obvious framing",
|
|
2101
|
+
"Memorable - they could repeat it to someone else"
|
|
2102
|
+
],
|
|
2103
|
+
antiPatterns: [
|
|
2104
|
+
'Generic topic labels ("Q4 Update")',
|
|
2105
|
+
"Long sentences that require reading",
|
|
2106
|
+
"Jargon or acronyms",
|
|
2107
|
+
"Multiple ideas competing for attention"
|
|
2108
|
+
],
|
|
2109
|
+
questions: [
|
|
2110
|
+
"Would someone share this title with a colleague?",
|
|
2111
|
+
"Does it create a curiosity gap?",
|
|
2112
|
+
"Can you understand the value in 3 seconds?",
|
|
2113
|
+
"Is there emotional resonance?"
|
|
2114
|
+
]
|
|
2115
|
+
};
|
|
2116
|
+
case "single_statement":
|
|
2117
|
+
case "big_idea":
|
|
2118
|
+
return {
|
|
2119
|
+
purpose: "Communicate ONE powerful insight that shifts perspective",
|
|
2120
|
+
principles: [
|
|
2121
|
+
{
|
|
2122
|
+
expert: "Garr Reynolds",
|
|
2123
|
+
principle: "Signal to Noise",
|
|
2124
|
+
application: "Everything on the slide should serve the one message. Eliminate ruthlessly.",
|
|
2125
|
+
technique: "For each element, ask: Does this AMPLIFY the message or distract from it?"
|
|
2126
|
+
},
|
|
2127
|
+
{
|
|
2128
|
+
expert: "Garr Reynolds",
|
|
2129
|
+
principle: "Amplification Through Simplification",
|
|
2130
|
+
application: "Empty space makes the remaining content more powerful",
|
|
2131
|
+
technique: "Try removing 50% of what's there. Is it clearer?"
|
|
2132
|
+
},
|
|
2133
|
+
{
|
|
2134
|
+
expert: "Nancy Duarte",
|
|
2135
|
+
principle: "Audience Paradox",
|
|
2136
|
+
application: "They will read OR listen, never both. Design for one.",
|
|
2137
|
+
technique: "If presenting live, the statement should be short enough to absorb while listening"
|
|
2138
|
+
}
|
|
2139
|
+
],
|
|
2140
|
+
whatMakesItGreat: [
|
|
2141
|
+
"One clear insight that shifts thinking",
|
|
2142
|
+
"Ample whitespace that emphasizes the message",
|
|
2143
|
+
"Words are carefully chosen - each one earns its place",
|
|
2144
|
+
"Can be absorbed in a glance"
|
|
2145
|
+
],
|
|
2146
|
+
antiPatterns: [
|
|
2147
|
+
"Cramming multiple points onto one slide",
|
|
2148
|
+
'Adding "supporting" text that dilutes the message',
|
|
2149
|
+
"Decorative elements that don't serve the idea",
|
|
2150
|
+
"Topic labels instead of insights"
|
|
2151
|
+
],
|
|
2152
|
+
questions: [
|
|
2153
|
+
"What is the ONE thing this slide should communicate?",
|
|
2154
|
+
"If I remove this word/element, is the message clearer?",
|
|
2155
|
+
"Will this shift how they think about the topic?"
|
|
2156
|
+
]
|
|
2157
|
+
};
|
|
2158
|
+
case "star_moment":
|
|
2159
|
+
return {
|
|
2160
|
+
purpose: "Create the moment they'll remember forever and tell others about",
|
|
2161
|
+
principles: [
|
|
2162
|
+
{
|
|
2163
|
+
expert: "Nancy Duarte",
|
|
2164
|
+
principle: "STAR Moment",
|
|
2165
|
+
application: "Something They'll Always Remember - this is the emotional climax",
|
|
2166
|
+
technique: "What would make the audience gasp, laugh, or get chills?"
|
|
2167
|
+
},
|
|
2168
|
+
{
|
|
2169
|
+
expert: "Chip & Dan Heath",
|
|
2170
|
+
principle: "Unexpected",
|
|
2171
|
+
application: "Break a pattern to get attention. Violate expectations.",
|
|
2172
|
+
technique: "What assumption can you shatter? What surprise can you deliver?"
|
|
2173
|
+
}
|
|
2174
|
+
],
|
|
2175
|
+
whatMakesItGreat: [
|
|
2176
|
+
"Creates visceral emotional response",
|
|
2177
|
+
"Breaks expectations - genuinely surprising",
|
|
2178
|
+
"Connects logically to the core message",
|
|
2179
|
+
"Memorable enough to retell"
|
|
2180
|
+
],
|
|
2181
|
+
antiPatterns: [
|
|
2182
|
+
"Playing it safe with expected information",
|
|
2183
|
+
"Burying the dramatic element in other content",
|
|
2184
|
+
"Gimmicks that don't connect to the message"
|
|
2185
|
+
],
|
|
2186
|
+
questions: [
|
|
2187
|
+
"Will this give them chills or make them gasp?",
|
|
2188
|
+
"Would they tell someone about this tomorrow?",
|
|
2189
|
+
"Does it support or distract from the core message?"
|
|
2190
|
+
]
|
|
2191
|
+
};
|
|
2192
|
+
case "big_number":
|
|
2193
|
+
case "data_insight":
|
|
2194
|
+
return {
|
|
2195
|
+
purpose: "Make data memorable by providing context that reveals meaning",
|
|
2196
|
+
principles: [
|
|
2197
|
+
{
|
|
2198
|
+
expert: "Edward Tufte",
|
|
2199
|
+
principle: "Data-Ink Ratio",
|
|
2200
|
+
application: "Maximize ink used for data, minimize everything else",
|
|
2201
|
+
technique: "Remove all decoration. Is the data still clear? Good. Now make it beautiful."
|
|
2202
|
+
},
|
|
2203
|
+
{
|
|
2204
|
+
expert: "Lea Pica",
|
|
2205
|
+
principle: "One Insight Per Chart",
|
|
2206
|
+
application: "Don't let data speak for itself - tell them exactly what to see",
|
|
2207
|
+
technique: "What's the ONE conclusion? Make that the headline."
|
|
2208
|
+
},
|
|
2209
|
+
{
|
|
2210
|
+
expert: "Cole Knaflic",
|
|
2211
|
+
principle: "Context Creates Meaning",
|
|
2212
|
+
application: "Numbers mean nothing without context",
|
|
2213
|
+
technique: "Compare to something they understand - time, money, scale"
|
|
2214
|
+
}
|
|
2215
|
+
],
|
|
2216
|
+
whatMakesItGreat: [
|
|
2217
|
+
"The number is HUGE and simple",
|
|
2218
|
+
"Context makes the number meaningful",
|
|
2219
|
+
"Audience immediately understands significance",
|
|
2220
|
+
"One clear insight, not a data dump"
|
|
2221
|
+
],
|
|
2222
|
+
antiPatterns: [
|
|
2223
|
+
"Multiple competing numbers",
|
|
2224
|
+
'Data without context ("$5M" - is that good?)',
|
|
2225
|
+
"Complex charts when simple would work",
|
|
2226
|
+
"Decorative elements that distract from data"
|
|
2227
|
+
],
|
|
2228
|
+
questions: [
|
|
2229
|
+
"What is the ONE insight this data reveals?",
|
|
2230
|
+
"What context makes this number meaningful?",
|
|
2231
|
+
"Can someone understand the significance in 3 seconds?"
|
|
2232
|
+
]
|
|
2233
|
+
};
|
|
2234
|
+
case "three_points":
|
|
2235
|
+
case "three_column":
|
|
2236
|
+
return {
|
|
2237
|
+
purpose: "Present key messages in memorable groups of three",
|
|
2238
|
+
principles: [
|
|
2239
|
+
{
|
|
2240
|
+
expert: "Carmine Gallo",
|
|
2241
|
+
principle: "Rule of Three",
|
|
2242
|
+
application: "Three is the magic number for memory. Not two, not four.",
|
|
2243
|
+
technique: "Can you group your content into exactly three main points?"
|
|
2244
|
+
},
|
|
2245
|
+
{
|
|
2246
|
+
expert: "Barbara Minto",
|
|
2247
|
+
principle: "MECE",
|
|
2248
|
+
application: "Points should be Mutually Exclusive (no overlap) and Collectively Exhaustive (no gaps)",
|
|
2249
|
+
technique: "Do these three points cover everything without repeating?"
|
|
2250
|
+
}
|
|
2251
|
+
],
|
|
2252
|
+
whatMakesItGreat: [
|
|
2253
|
+
"Exactly three points - no more, no less",
|
|
2254
|
+
"Each point is substantively different",
|
|
2255
|
+
"Together they tell a complete story",
|
|
2256
|
+
"Each point is memorable on its own"
|
|
2257
|
+
],
|
|
2258
|
+
antiPatterns: [
|
|
2259
|
+
"Forcing content into three when it should be two or four",
|
|
2260
|
+
"Overlapping points that aren't distinct",
|
|
2261
|
+
"Generic labels instead of insights",
|
|
2262
|
+
"Uneven importance across points"
|
|
2263
|
+
],
|
|
2264
|
+
questions: [
|
|
2265
|
+
"Are these three truly distinct and non-overlapping?",
|
|
2266
|
+
"Do they together cover the complete picture?",
|
|
2267
|
+
"Is each point valuable on its own?"
|
|
2268
|
+
]
|
|
2269
|
+
};
|
|
2270
|
+
case "call_to_action":
|
|
2271
|
+
case "cta":
|
|
2272
|
+
return {
|
|
2273
|
+
purpose: "Give the audience a clear, compelling next step they can take",
|
|
2274
|
+
principles: [
|
|
2275
|
+
{
|
|
2276
|
+
expert: "Chris Anderson",
|
|
2277
|
+
principle: "Gift Giving",
|
|
2278
|
+
application: "Your job is to give them something valuable they can USE",
|
|
2279
|
+
technique: "What can they DO tomorrow because of this presentation?"
|
|
2280
|
+
},
|
|
2281
|
+
{
|
|
2282
|
+
expert: "Robert Cialdini",
|
|
2283
|
+
principle: "Commitment",
|
|
2284
|
+
application: "Small commitments lead to larger ones",
|
|
2285
|
+
technique: "What's the smallest first step that gets them moving?"
|
|
2286
|
+
}
|
|
2287
|
+
],
|
|
2288
|
+
whatMakesItGreat: [
|
|
2289
|
+
'Specific and actionable - not vague "learn more"',
|
|
2290
|
+
"Achievable first step, not overwhelming",
|
|
2291
|
+
"Clearly connected to the value proposition",
|
|
2292
|
+
"Creates urgency without being pushy"
|
|
2293
|
+
],
|
|
2294
|
+
antiPatterns: [
|
|
2295
|
+
'Vague calls to action ("contact us")',
|
|
2296
|
+
"Multiple competing CTAs",
|
|
2297
|
+
"Actions that feel like too much work",
|
|
2298
|
+
"Disconnected from the presentation content"
|
|
2299
|
+
],
|
|
2300
|
+
questions: [
|
|
2301
|
+
"Can they do this tomorrow?",
|
|
2302
|
+
"Is this the logical first step?",
|
|
2303
|
+
"Does it connect to the value you promised?"
|
|
2304
|
+
]
|
|
2305
|
+
};
|
|
2306
|
+
default:
|
|
2307
|
+
return {
|
|
2308
|
+
purpose: "Communicate one clear message that advances the narrative",
|
|
2309
|
+
principles: [
|
|
2310
|
+
{
|
|
2311
|
+
expert: "Garr Reynolds",
|
|
2312
|
+
principle: "Signal to Noise",
|
|
2313
|
+
application: "Maximize signal (valuable content), minimize noise (distractions)",
|
|
2314
|
+
technique: "For every element, ask: Does this serve the message?"
|
|
2315
|
+
},
|
|
2316
|
+
{
|
|
2317
|
+
expert: "Nancy Duarte",
|
|
2318
|
+
principle: "Glance Test",
|
|
2319
|
+
application: "Key message should be clear in 3 seconds",
|
|
2320
|
+
technique: "What would someone remember after a 3-second glance?"
|
|
2321
|
+
}
|
|
2322
|
+
],
|
|
2323
|
+
whatMakesItGreat: [
|
|
2324
|
+
"One clear message per slide",
|
|
2325
|
+
"Visual hierarchy guides the eye",
|
|
2326
|
+
"Every element serves a purpose",
|
|
2327
|
+
"Can be understood at a glance"
|
|
2328
|
+
],
|
|
2329
|
+
antiPatterns: [
|
|
2330
|
+
"Multiple competing messages",
|
|
2331
|
+
"Decorative elements that distract",
|
|
2332
|
+
"Text-heavy slides that require reading",
|
|
2333
|
+
"Unclear what's most important"
|
|
2334
|
+
],
|
|
2335
|
+
questions: [
|
|
2336
|
+
"What is the ONE message?",
|
|
2337
|
+
"What can be removed without losing meaning?",
|
|
2338
|
+
"Does visual hierarchy match content hierarchy?"
|
|
2339
|
+
]
|
|
2340
|
+
};
|
|
2341
|
+
}
|
|
2342
|
+
}
|
|
2343
|
+
/**
|
|
2344
|
+
* Get guidance for extracting the core message from content.
|
|
2345
|
+
* This helps identify WHAT should be on the slide, not just how to fit content.
|
|
2346
|
+
*/
|
|
2347
|
+
getCoreMessageGuidance() {
|
|
2348
|
+
return [
|
|
2349
|
+
{
|
|
2350
|
+
expert: "Chris Anderson",
|
|
2351
|
+
principle: "One Idea Worth Spreading",
|
|
2352
|
+
application: "Find the single most valuable insight in this content",
|
|
2353
|
+
technique: "If they could only remember ONE thing, what should it be?"
|
|
2354
|
+
},
|
|
2355
|
+
{
|
|
2356
|
+
expert: "Carmine Gallo",
|
|
2357
|
+
principle: "Twitter Test",
|
|
2358
|
+
application: "Can you express the key insight in a tweet?",
|
|
2359
|
+
technique: "Write the insight in under 280 characters. If you can't, you don't understand it well enough."
|
|
2360
|
+
},
|
|
2361
|
+
{
|
|
2362
|
+
expert: "Barbara Minto",
|
|
2363
|
+
principle: "So What?",
|
|
2364
|
+
application: 'Keep asking "so what?" until you reach the insight',
|
|
2365
|
+
technique: 'For every statement, ask "so what?" The answer is closer to the real message.'
|
|
2366
|
+
}
|
|
2367
|
+
];
|
|
2368
|
+
}
|
|
2369
|
+
/**
|
|
2370
|
+
* Evaluate whether a slide meets expert standards.
|
|
2371
|
+
* Returns guidance on how to improve, not just pass/fail.
|
|
2372
|
+
*/
|
|
2373
|
+
evaluateSlideExcellence(slide) {
|
|
2374
|
+
const guidance = this.getSlideDesignGuidance(slide.type);
|
|
2375
|
+
const strengths = [];
|
|
2376
|
+
const improvements = [];
|
|
2377
|
+
const expertFeedback = [];
|
|
2378
|
+
let score = 50;
|
|
2379
|
+
for (const quality of guidance.whatMakesItGreat) {
|
|
2380
|
+
}
|
|
2381
|
+
for (const antiPattern of guidance.antiPatterns) {
|
|
2382
|
+
}
|
|
2383
|
+
for (const question of guidance.questions) {
|
|
2384
|
+
expertFeedback.push(`Ask: ${question}`);
|
|
2385
|
+
}
|
|
2386
|
+
if (slide.title) {
|
|
2387
|
+
if (slide.title.length <= 60) {
|
|
2388
|
+
strengths.push("Title passes Duarte Glance Test (under 60 chars)");
|
|
2389
|
+
score += 10;
|
|
2390
|
+
} else {
|
|
2391
|
+
improvements.push("Shorten title - Duarte says 3 seconds to comprehend");
|
|
2392
|
+
}
|
|
2393
|
+
if (!/^(update|overview|introduction|summary)$/i.test(slide.title)) {
|
|
2394
|
+
strengths.push("Title is specific, not generic (Gallo Headline Test)");
|
|
2395
|
+
score += 5;
|
|
2396
|
+
} else {
|
|
2397
|
+
improvements.push("Replace generic title with insight - Gallo says it should work as a headline");
|
|
2398
|
+
score -= 10;
|
|
2399
|
+
}
|
|
2400
|
+
if (!slide.title.includes(" and ") && !slide.title.includes(" & ")) {
|
|
2401
|
+
strengths.push("Title focuses on one idea (Chris Anderson)");
|
|
2402
|
+
score += 5;
|
|
2403
|
+
} else {
|
|
2404
|
+
improvements.push("Split into two slides - Anderson says one idea per slide");
|
|
2405
|
+
}
|
|
2406
|
+
}
|
|
2407
|
+
if (slide.bullets && slide.bullets.length > 5) {
|
|
2408
|
+
improvements.push("Reduce bullets to 3-5 - Reynolds says maximize signal, eliminate noise");
|
|
2409
|
+
score -= 10;
|
|
2410
|
+
}
|
|
2411
|
+
return {
|
|
2412
|
+
score: Math.max(0, Math.min(100, score)),
|
|
2413
|
+
strengths,
|
|
2414
|
+
improvements,
|
|
2415
|
+
expertFeedback
|
|
2416
|
+
};
|
|
2417
|
+
}
|
|
2418
|
+
};
|
|
2419
|
+
function createExpertGuidanceEngine(kb, presentationType) {
|
|
2420
|
+
return new ExpertGuidanceEngine(kb, presentationType);
|
|
2421
|
+
}
|
|
2422
|
+
|
|
1662
2423
|
// src/core/SlideFactory.ts
|
|
1663
2424
|
var SlideFactory = class {
|
|
1664
2425
|
kb;
|
|
1665
2426
|
presentationType;
|
|
1666
2427
|
classifier;
|
|
2428
|
+
expertEngine;
|
|
2429
|
+
// NEW: Expert guidance for world-class slides
|
|
2430
|
+
narrativeArc;
|
|
2431
|
+
// NEW: Story structure from experts
|
|
1667
2432
|
config;
|
|
1668
2433
|
usedContent;
|
|
1669
2434
|
usedTitles;
|
|
@@ -1674,7 +2439,10 @@ var SlideFactory = class {
|
|
|
1674
2439
|
this.usedContent = /* @__PURE__ */ new Set();
|
|
1675
2440
|
this.usedTitles = /* @__PURE__ */ new Set();
|
|
1676
2441
|
this.config = this.loadKBConfig(type);
|
|
1677
|
-
|
|
2442
|
+
this.expertEngine = createExpertGuidanceEngine(kb, type);
|
|
2443
|
+
this.narrativeArc = this.expertEngine.getNarrativeArc();
|
|
2444
|
+
logger.step(`SlideFactory v8.0.0 initialized for ${type} (${this.config.mode} mode)`);
|
|
2445
|
+
logger.step(`Using ${this.narrativeArc.framework} narrative (${this.narrativeArc.expert})`);
|
|
1678
2446
|
logger.step(`KB Config loaded: ${this.config.allowedTypes.length} allowed types`);
|
|
1679
2447
|
}
|
|
1680
2448
|
/**
|
|
@@ -1714,15 +2482,56 @@ var SlideFactory = class {
|
|
|
1714
2482
|
} else if (storyStructure.framework === "sparkline") {
|
|
1715
2483
|
this.addSparklineSlides(slides, analysis);
|
|
1716
2484
|
}
|
|
2485
|
+
let hasCTASection = false;
|
|
1717
2486
|
for (const section of analysis.sections) {
|
|
2487
|
+
logger.debug(`[TRACE] Processing section: "${section.header}"`);
|
|
2488
|
+
logger.debug(`[TRACE] Level: ${section.level}, Content: ${section.content?.length ?? 0} chars`);
|
|
2489
|
+
logger.debug(`[TRACE] Bullets: ${section.bullets?.length ?? 0} items`);
|
|
2490
|
+
if (section.bullets?.length > 0) {
|
|
2491
|
+
section.bullets.slice(0, 3).forEach((b, i) => {
|
|
2492
|
+
logger.debug(`[TRACE] Bullet ${i}: "${b.slice(0, 50)}..."`);
|
|
2493
|
+
});
|
|
2494
|
+
}
|
|
1718
2495
|
const contentKey = this.normalizeKey(section.header);
|
|
1719
|
-
if (this.usedContent.has(contentKey))
|
|
2496
|
+
if (this.usedContent.has(contentKey)) {
|
|
2497
|
+
logger.debug(`[TRACE] SKIPPED: Already used content key "${contentKey}"`);
|
|
2498
|
+
continue;
|
|
2499
|
+
}
|
|
1720
2500
|
this.usedContent.add(contentKey);
|
|
2501
|
+
const headerLower = section.header.toLowerCase();
|
|
2502
|
+
const isCTASection = headerLower.includes("call to action") || headerLower.includes("next step") || headerLower.includes("take action") || headerLower.includes("get started") || headerLower.includes("start today");
|
|
2503
|
+
if (isCTASection) {
|
|
2504
|
+
hasCTASection = true;
|
|
2505
|
+
const ctaDefaults = this.config.defaults.cta;
|
|
2506
|
+
const ctaContent = section.bullets.length > 0 ? section.bullets.slice(0, 4).join(". ") : section.content || ctaDefaults.fallback;
|
|
2507
|
+
const craftedCTA = this.craftExpertContent(
|
|
2508
|
+
ctaContent,
|
|
2509
|
+
"call_to_action",
|
|
2510
|
+
this.config.rules.wordsPerSlide.max * 3
|
|
2511
|
+
// Allow more words for CTA steps
|
|
2512
|
+
);
|
|
2513
|
+
slides.push({
|
|
2514
|
+
index: slides.length,
|
|
2515
|
+
type: "cta",
|
|
2516
|
+
data: {
|
|
2517
|
+
title: section.header,
|
|
2518
|
+
body: craftedCTA,
|
|
2519
|
+
keyMessage: ctaDefaults.message
|
|
2520
|
+
},
|
|
2521
|
+
classes: ["cta-slide"]
|
|
2522
|
+
});
|
|
2523
|
+
continue;
|
|
2524
|
+
}
|
|
1721
2525
|
const pattern = this.classifier.classify(section);
|
|
2526
|
+
logger.debug(`[TRACE] Pattern: ${pattern.primaryPattern}, bulletCount: ${pattern.bulletCount}`);
|
|
1722
2527
|
const slideType = this.kb.mapContentPatternToSlideType(pattern, this.config.allowedTypes);
|
|
2528
|
+
logger.debug(`[TRACE] KB returned slide type: "${slideType}"`);
|
|
1723
2529
|
const slide = this.createSlideByType(slides.length, slideType, section, pattern);
|
|
1724
|
-
if (slide) {
|
|
2530
|
+
if (slide && slide.type && slide.data) {
|
|
2531
|
+
logger.debug(`[TRACE] Created slide type: "${slide.type}" with ${slide.data.bullets?.length ?? 0} bullets`);
|
|
1725
2532
|
slides.push(slide);
|
|
2533
|
+
} else {
|
|
2534
|
+
logger.debug(`[TRACE] SKIPPED: Slide filtered out (no valid content)`);
|
|
1726
2535
|
}
|
|
1727
2536
|
}
|
|
1728
2537
|
const minDataPointsForMetrics = 2;
|
|
@@ -1734,7 +2543,7 @@ var SlideFactory = class {
|
|
|
1734
2543
|
slides.push(this.createMetricsGridSlide(slides.length, analysis.dataPoints));
|
|
1735
2544
|
}
|
|
1736
2545
|
}
|
|
1737
|
-
if (analysis.sparkline?.callToAdventure || analysis.scqa?.answer) {
|
|
2546
|
+
if (!hasCTASection && (analysis.sparkline?.callToAdventure || analysis.scqa?.answer)) {
|
|
1738
2547
|
slides.push(this.createCTASlide(slides.length, analysis));
|
|
1739
2548
|
}
|
|
1740
2549
|
slides.push(this.createThankYouSlide(slides.length));
|
|
@@ -1750,34 +2559,55 @@ var SlideFactory = class {
|
|
|
1750
2559
|
createSlideByType(index, type, section, pattern) {
|
|
1751
2560
|
const normalizedType = type.toLowerCase().replace(/_/g, "-");
|
|
1752
2561
|
switch (normalizedType) {
|
|
2562
|
+
// Big number slides - show prominent metrics
|
|
1753
2563
|
case "big-number":
|
|
1754
|
-
case "data-insight":
|
|
1755
2564
|
return this.createBigNumberSlide(index, section, pattern);
|
|
2565
|
+
// Data insight - for findings that may or may not have numbers
|
|
2566
|
+
case "data-insight":
|
|
2567
|
+
return this.createDataInsightSlide(index, section, pattern);
|
|
2568
|
+
// Comparison slides
|
|
1756
2569
|
case "comparison":
|
|
1757
2570
|
case "options-comparison":
|
|
1758
2571
|
return this.createComparisonSlide(index, section);
|
|
2572
|
+
// Timeline and roadmap slides
|
|
1759
2573
|
case "timeline":
|
|
1760
2574
|
case "process-timeline":
|
|
1761
2575
|
case "roadmap":
|
|
1762
2576
|
return this.createTimelineSlide(index, section);
|
|
2577
|
+
// Process flow slides (with steps)
|
|
1763
2578
|
case "process":
|
|
2579
|
+
case "mece-breakdown":
|
|
1764
2580
|
return this.createProcessSlide(index, section);
|
|
2581
|
+
// Three points slide (for exactly 3 items)
|
|
2582
|
+
case "three-points":
|
|
2583
|
+
return this.createThreePointsSlide(index, section);
|
|
2584
|
+
// Column layouts
|
|
1765
2585
|
case "three-column":
|
|
1766
2586
|
return this.createThreeColumnSlide(index, section);
|
|
1767
2587
|
case "two-column":
|
|
1768
2588
|
return this.createTwoColumnSlide(index, section);
|
|
2589
|
+
// Quote and testimonial slides
|
|
1769
2590
|
case "quote":
|
|
1770
2591
|
case "testimonial":
|
|
1771
2592
|
case "social-proof":
|
|
1772
2593
|
return this.createQuoteSlide(index, section);
|
|
2594
|
+
// Metrics grid for multiple KPIs
|
|
1773
2595
|
case "metrics-grid":
|
|
1774
2596
|
return this.createMetricsGridSlide(index, section.metrics);
|
|
2597
|
+
// Standard bullet point slides
|
|
1775
2598
|
case "bullet-points":
|
|
1776
|
-
case "detailed-findings":
|
|
1777
2599
|
return this.createBulletSlide(index, section);
|
|
2600
|
+
// Title impact - emphasized single statement
|
|
2601
|
+
case "title-impact":
|
|
2602
|
+
return this.createTitleImpactSlide(index, section);
|
|
2603
|
+
// Single statement slides
|
|
1778
2604
|
case "single-statement":
|
|
1779
2605
|
case "big-idea":
|
|
1780
2606
|
return this.createSingleStatementSlide(index, section);
|
|
2607
|
+
// Detailed findings - bullets with context
|
|
2608
|
+
case "detailed-findings":
|
|
2609
|
+
return this.createDetailedFindingsSlide(index, section);
|
|
2610
|
+
// Code and technical slides
|
|
1781
2611
|
case "code-snippet":
|
|
1782
2612
|
case "technical":
|
|
1783
2613
|
return this.createCodeSlide(index, section);
|
|
@@ -1790,15 +2620,21 @@ var SlideFactory = class {
|
|
|
1790
2620
|
// STRUCTURAL SLIDES (title, agenda, thank you) - ALL from KB
|
|
1791
2621
|
// ===========================================================================
|
|
1792
2622
|
createTitleSlide(index, analysis) {
|
|
2623
|
+
const craftedTitle = this.craftExpertContent(
|
|
2624
|
+
analysis.title,
|
|
2625
|
+
"title",
|
|
2626
|
+
this.config.rules.wordsPerSlide.max
|
|
2627
|
+
);
|
|
1793
2628
|
const data = {
|
|
1794
|
-
title:
|
|
2629
|
+
title: craftedTitle
|
|
1795
2630
|
};
|
|
1796
2631
|
const subtitleSource = analysis.subtitle || analysis.keyMessages[0] || "";
|
|
1797
2632
|
if (subtitleSource) {
|
|
1798
|
-
data.subtitle = this.
|
|
2633
|
+
data.subtitle = this.craftExpertContent(
|
|
1799
2634
|
subtitleSource,
|
|
2635
|
+
"title",
|
|
2636
|
+
// Use title guidance for subtitle too
|
|
1800
2637
|
this.config.defaults.subtitle.maxWords
|
|
1801
|
-
// FROM KB
|
|
1802
2638
|
);
|
|
1803
2639
|
}
|
|
1804
2640
|
return {
|
|
@@ -1887,31 +2723,43 @@ var SlideFactory = class {
|
|
|
1887
2723
|
addSparklineSlides(slides, analysis) {
|
|
1888
2724
|
const spark = analysis.sparkline;
|
|
1889
2725
|
const titles = this.config.sparklineTitles;
|
|
2726
|
+
const maxWords = this.config.rules.wordsPerSlide.max;
|
|
1890
2727
|
const whatIsFirst = spark?.whatIs?.[0];
|
|
1891
|
-
const whatIsBody = whatIsFirst ? this.
|
|
1892
|
-
|
|
2728
|
+
const whatIsBody = whatIsFirst ? this.craftExpertContent(whatIsFirst, "single_statement", maxWords) : "";
|
|
2729
|
+
const whatCouldBeFirst = spark?.whatCouldBe?.[0];
|
|
2730
|
+
const whatCouldBeBody = whatCouldBeFirst ? this.craftExpertContent(whatCouldBeFirst, "single_statement", maxWords) : "";
|
|
2731
|
+
const normalizedTitle = analysis.title.toLowerCase().slice(0, 50);
|
|
2732
|
+
const normalizedSubtitle = (analysis.subtitle || "").toLowerCase().slice(0, 50);
|
|
2733
|
+
const normalizedWhatIs = whatIsBody.toLowerCase().slice(0, 50);
|
|
2734
|
+
const normalizedWhatCouldBe = whatCouldBeBody.toLowerCase().slice(0, 50);
|
|
2735
|
+
const whatIsDuplicatesTitle = normalizedWhatIs === normalizedTitle || normalizedWhatIs.includes(normalizedTitle.slice(0, 30)) || normalizedTitle.includes(normalizedWhatIs.slice(0, 30));
|
|
2736
|
+
const whatIsDuplicatesSubtitle = normalizedWhatIs === normalizedSubtitle || normalizedWhatIs.includes(normalizedSubtitle.slice(0, 30)) || normalizedSubtitle.includes(normalizedWhatIs.slice(0, 30));
|
|
2737
|
+
const isDuplicatePair = normalizedWhatIs === normalizedWhatCouldBe;
|
|
2738
|
+
if (whatIsBody.length >= 30 && !isDuplicatePair && !whatIsDuplicatesTitle && !whatIsDuplicatesSubtitle && !this.usedContent.has("spark-what-is")) {
|
|
1893
2739
|
this.usedContent.add("spark-what-is");
|
|
2740
|
+
this.usedContent.add(normalizedWhatIs);
|
|
1894
2741
|
slides.push({
|
|
1895
2742
|
index: slides.length,
|
|
1896
2743
|
type: "single-statement",
|
|
1897
2744
|
data: {
|
|
1898
2745
|
title: titles.whatIs,
|
|
1899
|
-
// FROM KB
|
|
2746
|
+
// FROM KB
|
|
1900
2747
|
body: whatIsBody
|
|
1901
2748
|
},
|
|
1902
2749
|
classes: ["what-is-slide"]
|
|
1903
2750
|
});
|
|
1904
2751
|
}
|
|
1905
|
-
const
|
|
1906
|
-
const
|
|
1907
|
-
if (whatCouldBeBody.length >=
|
|
2752
|
+
const whatCouldBeDuplicatesTitle = normalizedWhatCouldBe === normalizedTitle || normalizedWhatCouldBe.includes(normalizedTitle.slice(0, 30)) || normalizedTitle.includes(normalizedWhatCouldBe.slice(0, 30));
|
|
2753
|
+
const whatCouldBeDuplicatesSubtitle = normalizedWhatCouldBe === normalizedSubtitle || normalizedWhatCouldBe.includes(normalizedSubtitle.slice(0, 30)) || normalizedSubtitle.includes(normalizedWhatCouldBe.slice(0, 30));
|
|
2754
|
+
if (whatCouldBeBody.length >= 30 && !isDuplicatePair && !whatCouldBeDuplicatesTitle && !whatCouldBeDuplicatesSubtitle && !this.usedContent.has("spark-could-be")) {
|
|
1908
2755
|
this.usedContent.add("spark-could-be");
|
|
2756
|
+
this.usedContent.add(normalizedWhatCouldBe);
|
|
1909
2757
|
slides.push({
|
|
1910
2758
|
index: slides.length,
|
|
1911
2759
|
type: "single-statement",
|
|
1912
2760
|
data: {
|
|
1913
2761
|
title: titles.whatCouldBe,
|
|
1914
|
-
// FROM KB
|
|
2762
|
+
// FROM KB
|
|
1915
2763
|
body: whatCouldBeBody
|
|
1916
2764
|
},
|
|
1917
2765
|
classes: ["what-could-be-slide"]
|
|
@@ -1929,17 +2777,19 @@ var SlideFactory = class {
|
|
|
1929
2777
|
logger.warn(`No number found for big-number slide, falling back to single-statement`);
|
|
1930
2778
|
return this.createSingleStatementSlide(index, section);
|
|
1931
2779
|
}
|
|
2780
|
+
const contextContent = bigNumber?.context || section.content;
|
|
2781
|
+
const craftedContext = this.craftExpertContent(
|
|
2782
|
+
contextContent,
|
|
2783
|
+
"big_number",
|
|
2784
|
+
this.config.defaults.context.maxWords
|
|
2785
|
+
);
|
|
1932
2786
|
return {
|
|
1933
2787
|
index,
|
|
1934
2788
|
type: "big-number",
|
|
1935
2789
|
data: {
|
|
1936
2790
|
title: this.createTitle(section.header, section),
|
|
1937
2791
|
keyMessage: actualValue,
|
|
1938
|
-
body:
|
|
1939
|
-
section.content,
|
|
1940
|
-
this.config.defaults.context.maxWords
|
|
1941
|
-
// FROM KB - not hardcoded 30
|
|
1942
|
-
)
|
|
2792
|
+
body: craftedContext
|
|
1943
2793
|
},
|
|
1944
2794
|
classes: ["big-number-slide"]
|
|
1945
2795
|
};
|
|
@@ -1986,7 +2836,8 @@ var SlideFactory = class {
|
|
|
1986
2836
|
data: {
|
|
1987
2837
|
title: this.createTitle(section.header, section),
|
|
1988
2838
|
steps: steps.slice(0, maxSteps).map((step) => ({
|
|
1989
|
-
label: step.label,
|
|
2839
|
+
label: this.cleanText(step.label),
|
|
2840
|
+
// Clean markdown from labels
|
|
1990
2841
|
description: this.truncateText(
|
|
1991
2842
|
step.description,
|
|
1992
2843
|
this.config.defaults.step.maxWords
|
|
@@ -2007,7 +2858,8 @@ var SlideFactory = class {
|
|
|
2007
2858
|
title: this.createTitle(section.header, section),
|
|
2008
2859
|
steps: steps.slice(0, maxSteps).map((step, i) => ({
|
|
2009
2860
|
number: i + 1,
|
|
2010
|
-
title: step.label,
|
|
2861
|
+
title: this.cleanText(step.label),
|
|
2862
|
+
// Clean markdown from labels
|
|
2011
2863
|
description: this.truncateText(
|
|
2012
2864
|
step.description,
|
|
2013
2865
|
this.config.defaults.step.maxWords
|
|
@@ -2042,6 +2894,69 @@ var SlideFactory = class {
|
|
|
2042
2894
|
classes: ["three-column-slide"]
|
|
2043
2895
|
};
|
|
2044
2896
|
}
|
|
2897
|
+
/**
|
|
2898
|
+
* Create a three-points slide for exactly 3 key points.
|
|
2899
|
+
* Used for content that fits the "rule of three" (Carmine Gallo).
|
|
2900
|
+
*/
|
|
2901
|
+
createThreePointsSlide(index, section) {
|
|
2902
|
+
const points = [];
|
|
2903
|
+
if (section.bullets.length >= 3) {
|
|
2904
|
+
for (let i = 0; i < 3; i++) {
|
|
2905
|
+
const bullet = section.bullets[i] || "";
|
|
2906
|
+
const extracted = this.classifier.extractSteps({ ...section, bullets: [bullet] });
|
|
2907
|
+
if (extracted.length > 0 && extracted[0] && extracted[0].description) {
|
|
2908
|
+
points.push({
|
|
2909
|
+
number: i + 1,
|
|
2910
|
+
title: this.cleanText(extracted[0].label) || `Step ${i + 1}`,
|
|
2911
|
+
description: this.truncateText(extracted[0].description, 20)
|
|
2912
|
+
});
|
|
2913
|
+
} else {
|
|
2914
|
+
const firstWords = this.cleanText(bullet).split(/\s+/).slice(0, 3).join(" ");
|
|
2915
|
+
points.push({
|
|
2916
|
+
number: i + 1,
|
|
2917
|
+
title: firstWords || `Step ${i + 1}`,
|
|
2918
|
+
description: this.truncateText(bullet, 20)
|
|
2919
|
+
});
|
|
2920
|
+
}
|
|
2921
|
+
}
|
|
2922
|
+
} else if (section.bullets.length > 0) {
|
|
2923
|
+
for (let i = 0; i < 3; i++) {
|
|
2924
|
+
const bullet = section.bullets[i % section.bullets.length] || "";
|
|
2925
|
+
const firstWords = this.cleanText(bullet).split(/\s+/).slice(0, 3).join(" ");
|
|
2926
|
+
points.push({
|
|
2927
|
+
number: i + 1,
|
|
2928
|
+
title: firstWords || `Step ${i + 1}`,
|
|
2929
|
+
description: this.truncateText(bullet, 20)
|
|
2930
|
+
});
|
|
2931
|
+
}
|
|
2932
|
+
} else {
|
|
2933
|
+
const sentences = section.content.split(/[.!?]+/).filter((s) => s.trim().length > 0).slice(0, 3);
|
|
2934
|
+
sentences.forEach((s, i) => {
|
|
2935
|
+
const firstWords = this.cleanText(s.trim()).split(/\s+/).slice(0, 3).join(" ");
|
|
2936
|
+
points.push({
|
|
2937
|
+
number: i + 1,
|
|
2938
|
+
title: firstWords || `Step ${i + 1}`,
|
|
2939
|
+
description: this.truncateText(s.trim(), 20)
|
|
2940
|
+
});
|
|
2941
|
+
});
|
|
2942
|
+
}
|
|
2943
|
+
return {
|
|
2944
|
+
index,
|
|
2945
|
+
type: "three-column",
|
|
2946
|
+
// Uses three-column template
|
|
2947
|
+
data: {
|
|
2948
|
+
title: this.createTitle(section.header, section),
|
|
2949
|
+
columns: points.map((p) => ({
|
|
2950
|
+
title: String(p.number),
|
|
2951
|
+
// Use the sequential number
|
|
2952
|
+
content: p.description,
|
|
2953
|
+
subtitle: p.title
|
|
2954
|
+
// Use extracted title as subtitle
|
|
2955
|
+
}))
|
|
2956
|
+
},
|
|
2957
|
+
classes: ["three-points-slide", "three-column-slide"]
|
|
2958
|
+
};
|
|
2959
|
+
}
|
|
2045
2960
|
createTwoColumnSlide(index, section) {
|
|
2046
2961
|
const midpoint = Math.ceil(section.bullets.length / 2);
|
|
2047
2962
|
const leftBullets = section.bullets.slice(0, midpoint);
|
|
@@ -2104,10 +3019,22 @@ var SlideFactory = class {
|
|
|
2104
3019
|
};
|
|
2105
3020
|
}
|
|
2106
3021
|
createBulletSlide(index, section) {
|
|
3022
|
+
logger.debug(`[TRACE] createBulletSlide for "${section.header}"`);
|
|
3023
|
+
logger.debug(`[TRACE] Input bullets: ${section.bullets?.length ?? 0}`);
|
|
2107
3024
|
const maxBullets = this.config.rules.bulletsPerSlide.max;
|
|
2108
3025
|
const wordsPerBullet = Math.floor(this.config.rules.wordsPerSlide.max / maxBullets);
|
|
2109
|
-
|
|
3026
|
+
logger.debug(`[TRACE] maxBullets: ${maxBullets}, wordsPerBullet: ${wordsPerBullet}`);
|
|
3027
|
+
const slicedBullets = section.bullets.slice(0, maxBullets);
|
|
3028
|
+
logger.debug(`[TRACE] After slice: ${slicedBullets.length} bullets`);
|
|
3029
|
+
const cleanedBullets = slicedBullets.map((b, i) => {
|
|
3030
|
+
const cleaned = this.cleanText(b);
|
|
3031
|
+
const truncated = this.truncateText(cleaned, wordsPerBullet);
|
|
3032
|
+
logger.debug(`[TRACE] Bullet ${i}: "${b.slice(0, 30)}..." \u2192 "${truncated.slice(0, 30)}..."`);
|
|
3033
|
+
return truncated;
|
|
3034
|
+
}).filter((b) => b.length > 0);
|
|
3035
|
+
logger.debug(`[TRACE] Final cleaned bullets: ${cleanedBullets.length}`);
|
|
2110
3036
|
if (cleanedBullets.length === 0 && section.content) {
|
|
3037
|
+
logger.debug(`[TRACE] No bullets, falling back to single-statement`);
|
|
2111
3038
|
return this.createSingleStatementSlide(index, section);
|
|
2112
3039
|
}
|
|
2113
3040
|
return {
|
|
@@ -2121,54 +3048,144 @@ var SlideFactory = class {
|
|
|
2121
3048
|
};
|
|
2122
3049
|
}
|
|
2123
3050
|
createSingleStatementSlide(index, section) {
|
|
3051
|
+
const title = this.createTitle(section.header, section);
|
|
3052
|
+
const titleWords = title.split(/\s+/).length;
|
|
3053
|
+
const maxTotalWords = this.config.rules.wordsPerSlide.max;
|
|
3054
|
+
const maxBodyWords = Math.max(3, maxTotalWords - titleWords);
|
|
2124
3055
|
const bodyContent = section.content || section.bullets.join(" ") || "";
|
|
2125
|
-
const
|
|
2126
|
-
|
|
2127
|
-
|
|
3056
|
+
const craftedBody = this.craftExpertContent(
|
|
3057
|
+
bodyContent,
|
|
3058
|
+
"single_statement",
|
|
3059
|
+
// Tell expert engine what type of slide this is
|
|
3060
|
+
maxBodyWords
|
|
3061
|
+
);
|
|
3062
|
+
const normalizedBody = craftedBody.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
3063
|
+
const normalizedTitle = title.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
3064
|
+
const bodyIsDuplicate = normalizedBody === normalizedTitle || craftedBody.length < 10;
|
|
3065
|
+
if (bodyIsDuplicate) {
|
|
3066
|
+
if (section.bullets.length >= 2 && this.config.mode === "business") {
|
|
3067
|
+
return this.createBulletSlide(index, section);
|
|
3068
|
+
}
|
|
3069
|
+
if (section.metrics.length >= 2) {
|
|
3070
|
+
return this.createMetricsGridSlide(index, section.metrics);
|
|
3071
|
+
}
|
|
3072
|
+
logger.warn(`Skipping slide "${title}" - no distinct body content`);
|
|
3073
|
+
return null;
|
|
2128
3074
|
}
|
|
2129
3075
|
return {
|
|
2130
3076
|
index,
|
|
2131
3077
|
type: "single-statement",
|
|
2132
3078
|
data: {
|
|
2133
|
-
title
|
|
2134
|
-
body:
|
|
2135
|
-
// Fallback to header if no body
|
|
3079
|
+
title,
|
|
3080
|
+
body: craftedBody
|
|
2136
3081
|
},
|
|
2137
3082
|
classes: ["single-statement-slide"]
|
|
2138
3083
|
};
|
|
2139
3084
|
}
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
3085
|
+
/**
|
|
3086
|
+
* Create a title impact slide - emphasized statement with key takeaway.
|
|
3087
|
+
* Used for section headers with strong conclusions.
|
|
3088
|
+
*/
|
|
3089
|
+
createTitleImpactSlide(index, section) {
|
|
3090
|
+
const supportingText = section.content || section.bullets.slice(0, 2).join(". ");
|
|
3091
|
+
const truncatedSupport = this.truncateText(supportingText, this.config.defaults.context.maxWords);
|
|
3092
|
+
const data = {
|
|
3093
|
+
title: this.cleanText(section.header)
|
|
3094
|
+
};
|
|
3095
|
+
if (truncatedSupport) {
|
|
3096
|
+
data.body = truncatedSupport;
|
|
3097
|
+
}
|
|
2143
3098
|
return {
|
|
2144
3099
|
index,
|
|
2145
|
-
type: "
|
|
2146
|
-
//
|
|
2147
|
-
data
|
|
2148
|
-
|
|
2149
|
-
leftColumn: {
|
|
2150
|
-
body: this.config.defaults.code.label
|
|
2151
|
-
// FROM KB - not hardcoded 'Code Example'
|
|
2152
|
-
},
|
|
2153
|
-
rightColumn: {
|
|
2154
|
-
body: code.slice(0, this.config.defaults.code.maxChars)
|
|
2155
|
-
// FROM KB - not hardcoded 500
|
|
2156
|
-
}
|
|
2157
|
-
},
|
|
2158
|
-
classes: ["code-slide"]
|
|
3100
|
+
type: "single-statement",
|
|
3101
|
+
// Uses single-statement template
|
|
3102
|
+
data,
|
|
3103
|
+
classes: ["title-impact-slide", "single-statement-slide"]
|
|
2159
3104
|
};
|
|
2160
3105
|
}
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
3106
|
+
/**
|
|
3107
|
+
* Create a data insight slide - for presenting key findings with context.
|
|
3108
|
+
* Similar to big-number but works even without a prominent metric.
|
|
3109
|
+
*/
|
|
3110
|
+
createDataInsightSlide(index, section, pattern) {
|
|
3111
|
+
const fullContent = `${section.header} ${section.content} ${section.bullets.join(" ")}`;
|
|
3112
|
+
const bigNumber = this.classifier.extractBigNumber(fullContent);
|
|
3113
|
+
if (bigNumber?.value || pattern.bigNumberValue) {
|
|
3114
|
+
return this.createBigNumberSlide(index, section, pattern);
|
|
3115
|
+
}
|
|
3116
|
+
const keyInsight = section.content || section.bullets[0] || section.header;
|
|
2164
3117
|
return {
|
|
2165
3118
|
index,
|
|
2166
|
-
type: "
|
|
3119
|
+
type: "single-statement",
|
|
2167
3120
|
data: {
|
|
2168
|
-
title:
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
3121
|
+
title: this.createTitle(section.header, section),
|
|
3122
|
+
body: this.truncateText(keyInsight, this.config.rules.wordsPerSlide.max)
|
|
3123
|
+
},
|
|
3124
|
+
classes: ["data-insight-slide", "single-statement-slide"]
|
|
3125
|
+
};
|
|
3126
|
+
}
|
|
3127
|
+
/**
|
|
3128
|
+
* Create a detailed findings slide - bullet points with introductory context.
|
|
3129
|
+
*/
|
|
3130
|
+
createDetailedFindingsSlide(index, section) {
|
|
3131
|
+
const introText = section.content ? this.truncateText(section.content, this.config.defaults.context.maxWords) : "";
|
|
3132
|
+
const maxBullets = this.config.rules.bulletsPerSlide.max;
|
|
3133
|
+
const wordsPerBullet = Math.floor(this.config.rules.wordsPerSlide.max / maxBullets);
|
|
3134
|
+
const cleanedBullets = section.bullets.slice(0, maxBullets).map((b) => this.truncateText(this.cleanText(b), wordsPerBullet)).filter((b) => b.length > 0);
|
|
3135
|
+
if (cleanedBullets.length === 0) {
|
|
3136
|
+
return this.createSingleStatementSlide(index, section);
|
|
3137
|
+
}
|
|
3138
|
+
const data = {
|
|
3139
|
+
title: this.createTitle(section.header, section),
|
|
3140
|
+
bullets: cleanedBullets
|
|
3141
|
+
};
|
|
3142
|
+
if (introText) {
|
|
3143
|
+
data.body = introText;
|
|
3144
|
+
}
|
|
3145
|
+
return {
|
|
3146
|
+
index,
|
|
3147
|
+
type: "bullet-points",
|
|
3148
|
+
data,
|
|
3149
|
+
classes: ["detailed-findings-slide", "bullet-points-slide"]
|
|
3150
|
+
};
|
|
3151
|
+
}
|
|
3152
|
+
createCodeSlide(index, section) {
|
|
3153
|
+
const codeMatch = section.content.match(/```[\s\S]*?```|`[^`]+`/);
|
|
3154
|
+
const code = codeMatch ? codeMatch[0].replace(/```/g, "").trim() : section.content;
|
|
3155
|
+
return {
|
|
3156
|
+
index,
|
|
3157
|
+
type: "two-column",
|
|
3158
|
+
// Code slides use two-column layout
|
|
3159
|
+
data: {
|
|
3160
|
+
title: this.createTitle(section.header, section),
|
|
3161
|
+
leftColumn: {
|
|
3162
|
+
body: this.config.defaults.code.label
|
|
3163
|
+
// FROM KB - not hardcoded 'Code Example'
|
|
3164
|
+
},
|
|
3165
|
+
rightColumn: {
|
|
3166
|
+
body: code.slice(0, this.config.defaults.code.maxChars)
|
|
3167
|
+
// FROM KB - not hardcoded 500
|
|
3168
|
+
}
|
|
3169
|
+
},
|
|
3170
|
+
classes: ["code-slide"]
|
|
3171
|
+
};
|
|
3172
|
+
}
|
|
3173
|
+
createCTASlide(index, analysis) {
|
|
3174
|
+
const ctaDefaults = this.config.defaults.cta;
|
|
3175
|
+
const ctaText = analysis.sparkline?.callToAdventure || analysis.scqa?.answer || ctaDefaults.fallback;
|
|
3176
|
+
const craftedCTA = this.craftExpertContent(
|
|
3177
|
+
ctaText,
|
|
3178
|
+
"call_to_action",
|
|
3179
|
+
this.config.rules.wordsPerSlide.max
|
|
3180
|
+
);
|
|
3181
|
+
return {
|
|
3182
|
+
index,
|
|
3183
|
+
type: "cta",
|
|
3184
|
+
data: {
|
|
3185
|
+
title: ctaDefaults.title,
|
|
3186
|
+
// FROM KB - not hardcoded 'Next Steps'
|
|
3187
|
+
body: craftedCTA,
|
|
3188
|
+
keyMessage: ctaDefaults.message
|
|
2172
3189
|
// FROM KB - not hardcoded 'Ready to Begin?'
|
|
2173
3190
|
},
|
|
2174
3191
|
classes: ["cta-slide"]
|
|
@@ -2280,6 +3297,215 @@ var SlideFactory = class {
|
|
|
2280
3297
|
}
|
|
2281
3298
|
}
|
|
2282
3299
|
// ===========================================================================
|
|
3300
|
+
// EXPERT-DRIVEN CONTENT CRAFTING (v8.0.0)
|
|
3301
|
+
// Instead of truncating content, we CRAFT it using expert principles
|
|
3302
|
+
// ===========================================================================
|
|
3303
|
+
/**
|
|
3304
|
+
* Craft content using expert principles - NOT truncation.
|
|
3305
|
+
*
|
|
3306
|
+
* The old approach: "Take 100 words, chop to 15, add ellipsis"
|
|
3307
|
+
* The new approach: "What would Nancy Duarte say is the ONE thing to remember?"
|
|
3308
|
+
*
|
|
3309
|
+
* @param content - The raw content to craft from
|
|
3310
|
+
* @param slideType - The type of slide being created
|
|
3311
|
+
* @param maxWords - Word budget from KB
|
|
3312
|
+
* @returns Expertly crafted content that serves the slide's purpose
|
|
3313
|
+
*/
|
|
3314
|
+
craftExpertContent(content, slideType, maxWords) {
|
|
3315
|
+
if (!content || content.trim().length === 0) return "";
|
|
3316
|
+
const guidance = this.expertEngine.getSlideDesignGuidance(slideType);
|
|
3317
|
+
switch (slideType) {
|
|
3318
|
+
case "title":
|
|
3319
|
+
case "title_impact":
|
|
3320
|
+
return this.extractCoreIdea(content, maxWords);
|
|
3321
|
+
case "star_moment":
|
|
3322
|
+
return this.extractDramaticElement(content, maxWords);
|
|
3323
|
+
case "big_number":
|
|
3324
|
+
case "data_insight":
|
|
3325
|
+
return this.extractDataInsight(content, maxWords);
|
|
3326
|
+
case "single_statement":
|
|
3327
|
+
case "big_idea":
|
|
3328
|
+
return this.extractSingleInsight(content, maxWords);
|
|
3329
|
+
case "call_to_action":
|
|
3330
|
+
case "cta":
|
|
3331
|
+
return this.extractActionableStep(content, maxWords);
|
|
3332
|
+
default:
|
|
3333
|
+
return this.extractCoreIdea(content, maxWords);
|
|
3334
|
+
}
|
|
3335
|
+
}
|
|
3336
|
+
/**
|
|
3337
|
+
* Chris Anderson's "One Idea Worth Spreading"
|
|
3338
|
+
* Extract the single most valuable insight from content.
|
|
3339
|
+
*/
|
|
3340
|
+
extractCoreIdea(content, maxWords) {
|
|
3341
|
+
const cleaned = this.cleanText(content);
|
|
3342
|
+
const sentences = cleaned.match(/[^.!?]+[.!?]?/g) || [cleaned];
|
|
3343
|
+
let bestSentence = sentences[0] || cleaned;
|
|
3344
|
+
let bestScore = 0;
|
|
3345
|
+
for (const sentence of sentences) {
|
|
3346
|
+
let score = 0;
|
|
3347
|
+
const lowerSentence = sentence.toLowerCase();
|
|
3348
|
+
for (const marker of this.config.insightMarkers) {
|
|
3349
|
+
if (lowerSentence.includes(marker.toLowerCase())) {
|
|
3350
|
+
score += 10;
|
|
3351
|
+
}
|
|
3352
|
+
}
|
|
3353
|
+
if (/\d+%|\$[\d,]+|\d+x|\d+\+/.test(sentence)) {
|
|
3354
|
+
score += 5;
|
|
3355
|
+
}
|
|
3356
|
+
if (/transform|revolutionize|breakthrough|critical|essential|must|never|always/i.test(sentence)) {
|
|
3357
|
+
score += 3;
|
|
3358
|
+
}
|
|
3359
|
+
if (/overview|introduction|agenda|summary|the following/i.test(sentence)) {
|
|
3360
|
+
score -= 10;
|
|
3361
|
+
}
|
|
3362
|
+
if (score > bestScore) {
|
|
3363
|
+
bestScore = score;
|
|
3364
|
+
bestSentence = sentence.trim();
|
|
3365
|
+
}
|
|
3366
|
+
}
|
|
3367
|
+
const words = bestSentence.split(/\s+/);
|
|
3368
|
+
if (words.length <= maxWords) {
|
|
3369
|
+
return bestSentence;
|
|
3370
|
+
}
|
|
3371
|
+
return this.extractKeyPhrase(bestSentence, maxWords);
|
|
3372
|
+
}
|
|
3373
|
+
/**
|
|
3374
|
+
* Extract the key phrase from a sentence - the part that matters most.
|
|
3375
|
+
*/
|
|
3376
|
+
extractKeyPhrase(sentence, maxWords) {
|
|
3377
|
+
const patterns = [
|
|
3378
|
+
/(?:the key (?:is|to)|critical|essential|must|should)\s+(.+)/i,
|
|
3379
|
+
/(.+?)\s+(?:is critical|is essential|matters most)/i,
|
|
3380
|
+
/^(.+?)\s*[-–—:]\s*.+$/
|
|
3381
|
+
// Before a colon/dash is often the key
|
|
3382
|
+
];
|
|
3383
|
+
for (const pattern of patterns) {
|
|
3384
|
+
const match = sentence.match(pattern);
|
|
3385
|
+
if (match && match[1]) {
|
|
3386
|
+
const extracted = match[1].trim();
|
|
3387
|
+
if (extracted.split(/\s+/).length <= maxWords) {
|
|
3388
|
+
return extracted;
|
|
3389
|
+
}
|
|
3390
|
+
}
|
|
3391
|
+
}
|
|
3392
|
+
const words = sentence.split(/\s+/);
|
|
3393
|
+
let result = words.slice(0, maxWords).join(" ");
|
|
3394
|
+
const lastPunctuation = result.lastIndexOf(",");
|
|
3395
|
+
if (lastPunctuation > result.length / 2) {
|
|
3396
|
+
result = result.slice(0, lastPunctuation);
|
|
3397
|
+
}
|
|
3398
|
+
return result;
|
|
3399
|
+
}
|
|
3400
|
+
/**
|
|
3401
|
+
* Nancy Duarte's "STAR Moment" - Something They'll Always Remember
|
|
3402
|
+
* Find the most dramatic, unexpected element.
|
|
3403
|
+
*/
|
|
3404
|
+
extractDramaticElement(content, maxWords) {
|
|
3405
|
+
const cleaned = this.cleanText(content);
|
|
3406
|
+
const sentences = cleaned.match(/[^.!?]+[.!?]?/g) || [cleaned];
|
|
3407
|
+
let mostDramatic = sentences[0] || cleaned;
|
|
3408
|
+
let highestDrama = 0;
|
|
3409
|
+
for (const sentence of sentences) {
|
|
3410
|
+
let drama = 0;
|
|
3411
|
+
const numbers = sentence.match(/\d+/g);
|
|
3412
|
+
if (numbers) {
|
|
3413
|
+
for (const num of numbers) {
|
|
3414
|
+
const value = parseInt(num);
|
|
3415
|
+
if (value >= 1e3) drama += 15;
|
|
3416
|
+
else if (value >= 100) drama += 10;
|
|
3417
|
+
else if (value >= 50) drama += 5;
|
|
3418
|
+
}
|
|
3419
|
+
}
|
|
3420
|
+
if (/\d+%/.test(sentence)) drama += 10;
|
|
3421
|
+
if (/\$[\d,]+[MBK]?/i.test(sentence)) drama += 12;
|
|
3422
|
+
if (/never|always|revolutionary|transforming|critical|dramatic|shocking|surprising/i.test(sentence)) {
|
|
3423
|
+
drama += 8;
|
|
3424
|
+
}
|
|
3425
|
+
if (/but|however|yet|instead|versus|vs/i.test(sentence)) {
|
|
3426
|
+
drama += 5;
|
|
3427
|
+
}
|
|
3428
|
+
if (drama > highestDrama) {
|
|
3429
|
+
highestDrama = drama;
|
|
3430
|
+
mostDramatic = sentence.trim();
|
|
3431
|
+
}
|
|
3432
|
+
}
|
|
3433
|
+
const words = mostDramatic.split(/\s+/);
|
|
3434
|
+
if (words.length <= maxWords) {
|
|
3435
|
+
return mostDramatic;
|
|
3436
|
+
}
|
|
3437
|
+
return this.extractKeyPhrase(mostDramatic, maxWords);
|
|
3438
|
+
}
|
|
3439
|
+
/**
|
|
3440
|
+
* Edward Tufte / Cole Knaflic - Extract data insight with context
|
|
3441
|
+
*/
|
|
3442
|
+
extractDataInsight(content, maxWords) {
|
|
3443
|
+
const cleaned = this.cleanText(content);
|
|
3444
|
+
const sentences = cleaned.match(/[^.!?]+[.!?]?/g) || [cleaned];
|
|
3445
|
+
const datasentences = sentences.filter(
|
|
3446
|
+
(s) => /\d+%|\$[\d,]+|\d+x|\d+ (million|billion|thousand)/i.test(s)
|
|
3447
|
+
);
|
|
3448
|
+
if (datasentences.length > 0 && datasentences[0]) {
|
|
3449
|
+
const best = datasentences[0].trim();
|
|
3450
|
+
const words = best.split(/\s+/);
|
|
3451
|
+
if (words.length <= maxWords) {
|
|
3452
|
+
return best;
|
|
3453
|
+
}
|
|
3454
|
+
return this.extractKeyPhrase(best, maxWords);
|
|
3455
|
+
}
|
|
3456
|
+
return this.extractCoreIdea(content, maxWords);
|
|
3457
|
+
}
|
|
3458
|
+
/**
|
|
3459
|
+
* Garr Reynolds - Signal to Noise
|
|
3460
|
+
* Extract the ONE insight, eliminate everything else.
|
|
3461
|
+
*/
|
|
3462
|
+
extractSingleInsight(content, maxWords) {
|
|
3463
|
+
const cleaned = this.cleanText(content);
|
|
3464
|
+
const sentences = cleaned.match(/[^.!?]+[.!?]?/g) || [cleaned];
|
|
3465
|
+
const conclusionPatterns = [
|
|
3466
|
+
/(?:therefore|thus|hence|consequently|as a result|this means|the bottom line|in conclusion)/i,
|
|
3467
|
+
/(?:we (?:must|should|need to)|it's (?:critical|essential|important) to)/i,
|
|
3468
|
+
/(?:the key (?:is|takeaway)|what this means)/i
|
|
3469
|
+
];
|
|
3470
|
+
for (const sentence of sentences) {
|
|
3471
|
+
for (const pattern of conclusionPatterns) {
|
|
3472
|
+
if (pattern.test(sentence)) {
|
|
3473
|
+
const words = sentence.trim().split(/\s+/);
|
|
3474
|
+
if (words.length <= maxWords) {
|
|
3475
|
+
return sentence.trim();
|
|
3476
|
+
}
|
|
3477
|
+
return this.extractKeyPhrase(sentence, maxWords);
|
|
3478
|
+
}
|
|
3479
|
+
}
|
|
3480
|
+
}
|
|
3481
|
+
return this.extractCoreIdea(content, maxWords);
|
|
3482
|
+
}
|
|
3483
|
+
/**
|
|
3484
|
+
* Chris Anderson - Gift Giving
|
|
3485
|
+
* What can they DO tomorrow?
|
|
3486
|
+
*/
|
|
3487
|
+
extractActionableStep(content, maxWords) {
|
|
3488
|
+
const cleaned = this.cleanText(content);
|
|
3489
|
+
const sentences = cleaned.match(/[^.!?]+[.!?]?/g) || [cleaned];
|
|
3490
|
+
const actionPatterns = [
|
|
3491
|
+
/(?:start|begin|try|implement|apply|use|adopt|build|create|develop)/i,
|
|
3492
|
+
/(?:step \d|first,|next,|then,|finally)/i,
|
|
3493
|
+
/(?:you (?:can|should|must|need to))/i
|
|
3494
|
+
];
|
|
3495
|
+
for (const sentence of sentences) {
|
|
3496
|
+
for (const pattern of actionPatterns) {
|
|
3497
|
+
if (pattern.test(sentence)) {
|
|
3498
|
+
const words = sentence.trim().split(/\s+/);
|
|
3499
|
+
if (words.length <= maxWords) {
|
|
3500
|
+
return sentence.trim();
|
|
3501
|
+
}
|
|
3502
|
+
return this.extractKeyPhrase(sentence, maxWords);
|
|
3503
|
+
}
|
|
3504
|
+
}
|
|
3505
|
+
}
|
|
3506
|
+
return this.extractCoreIdea(content, maxWords);
|
|
3507
|
+
}
|
|
3508
|
+
// ===========================================================================
|
|
2283
3509
|
// HELPER METHODS - All use KB configuration
|
|
2284
3510
|
// ===========================================================================
|
|
2285
3511
|
/**
|
|
@@ -2506,6 +3732,9 @@ var TemplateEngine = class {
|
|
|
2506
3732
|
this.handlebars.registerHelper("animDelay", function(index, baseMs = 100) {
|
|
2507
3733
|
return `animation-delay: ${index * baseMs}ms`;
|
|
2508
3734
|
});
|
|
3735
|
+
this.handlebars.registerHelper("add", function(a, b) {
|
|
3736
|
+
return a + b;
|
|
3737
|
+
});
|
|
2509
3738
|
this.handlebars.registerHelper("safeHTML", function(text) {
|
|
2510
3739
|
return new Handlebars.SafeString(text);
|
|
2511
3740
|
});
|
|
@@ -2613,7 +3842,12 @@ var TemplateEngine = class {
|
|
|
2613
3842
|
this.templates.set("single-statement", this.handlebars.compile(`
|
|
2614
3843
|
<section class="{{classes}}" data-slide-index="{{slideIndex}}" style="{{styles}}">
|
|
2615
3844
|
<div class="slide-content statement-content">
|
|
3845
|
+
{{#if body}}
|
|
3846
|
+
<h2 class="title animate-fadeIn">{{title}}</h2>
|
|
3847
|
+
<p class="statement animate-fadeIn delay-200">{{markdown body}}</p>
|
|
3848
|
+
{{else}}
|
|
2616
3849
|
<p class="statement animate-fadeIn">{{title}}</p>
|
|
3850
|
+
{{/if}}
|
|
2617
3851
|
</div>
|
|
2618
3852
|
{{> speakerNotes}}
|
|
2619
3853
|
</section>
|
|
@@ -2679,14 +3913,30 @@ var TemplateEngine = class {
|
|
|
2679
3913
|
<h2 class="title animate-fadeIn">{{title}}</h2>
|
|
2680
3914
|
<div class="columns two-columns">
|
|
2681
3915
|
<div class="column column-left animate-slideRight">
|
|
2682
|
-
{{#if
|
|
2683
|
-
|
|
3916
|
+
{{#if leftColumn.title}}<h3>{{leftColumn.title}}</h3>{{/if}}
|
|
3917
|
+
{{#if leftColumn.body}}
|
|
3918
|
+
<p class="body">{{markdown leftColumn.body}}</p>
|
|
2684
3919
|
{{/if}}
|
|
2685
|
-
{{#if
|
|
2686
|
-
|
|
3920
|
+
{{#if leftColumn.bullets}}
|
|
3921
|
+
<ul class="bullets stagger-children">
|
|
3922
|
+
{{#each leftColumn.bullets}}
|
|
3923
|
+
<li class="bullet animate-fadeIn" style="{{animDelay @index 150}}">{{markdown this}}</li>
|
|
3924
|
+
{{/each}}
|
|
3925
|
+
</ul>
|
|
2687
3926
|
{{/if}}
|
|
2688
3927
|
</div>
|
|
2689
3928
|
<div class="column column-right animate-slideLeft delay-200">
|
|
3929
|
+
{{#if rightColumn.title}}<h3>{{rightColumn.title}}</h3>{{/if}}
|
|
3930
|
+
{{#if rightColumn.body}}
|
|
3931
|
+
<p class="body">{{markdown rightColumn.body}}</p>
|
|
3932
|
+
{{/if}}
|
|
3933
|
+
{{#if rightColumn.bullets}}
|
|
3934
|
+
<ul class="bullets stagger-children">
|
|
3935
|
+
{{#each rightColumn.bullets}}
|
|
3936
|
+
<li class="bullet animate-fadeIn" style="{{animDelay @index 150}}">{{markdown this}}</li>
|
|
3937
|
+
{{/each}}
|
|
3938
|
+
</ul>
|
|
3939
|
+
{{/if}}
|
|
2690
3940
|
{{#if hasImage}}
|
|
2691
3941
|
{{> imageWithCaption images.[0]}}
|
|
2692
3942
|
{{else if metrics}}
|
|
@@ -2707,11 +3957,13 @@ var TemplateEngine = class {
|
|
|
2707
3957
|
{{#each columns}}
|
|
2708
3958
|
<div class="column animate-fadeIn" style="{{animDelay @index 200}}">
|
|
2709
3959
|
{{#if title}}<h3 class="column-title">{{title}}</h3>{{/if}}
|
|
3960
|
+
{{#if content}}<p class="column-body">{{markdown content}}</p>{{/if}}
|
|
2710
3961
|
{{#if body}}<p class="column-body">{{markdown body}}</p>{{/if}}
|
|
2711
3962
|
{{#if icon}}<div class="column-icon">{{icon}}</div>{{/if}}
|
|
2712
3963
|
</div>
|
|
2713
3964
|
{{/each}}
|
|
2714
3965
|
</div>
|
|
3966
|
+
{{> source}}
|
|
2715
3967
|
</div>
|
|
2716
3968
|
{{> speakerNotes}}
|
|
2717
3969
|
</section>
|
|
@@ -2720,25 +3972,24 @@ var TemplateEngine = class {
|
|
|
2720
3972
|
<section class="{{classes}}" data-slide-index="{{slideIndex}}" style="{{styles}}">
|
|
2721
3973
|
<div class="slide-content">
|
|
2722
3974
|
<h2 class="title animate-fadeIn">{{title}}</h2>
|
|
2723
|
-
<div class="comparison-container">
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
<
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
</div>
|
|
2732
|
-
<div class="comparison-divider"></div>
|
|
2733
|
-
<div class="comparison-right animate-slideLeft delay-200">
|
|
2734
|
-
<h3>{{rightTitle}}</h3>
|
|
3975
|
+
<div class="comparison-container columns two-columns">
|
|
3976
|
+
{{#each columns}}
|
|
3977
|
+
<div class="column comparison-{{#if @first}}left animate-slideRight{{else}}right animate-slideLeft delay-200{{/if}}">
|
|
3978
|
+
<h3 class="column-title">{{title}}</h3>
|
|
3979
|
+
{{#if content}}
|
|
3980
|
+
<p class="column-content">{{markdown content}}</p>
|
|
3981
|
+
{{/if}}
|
|
3982
|
+
{{#if bullets}}
|
|
2735
3983
|
<ul>
|
|
2736
|
-
{{#each
|
|
3984
|
+
{{#each bullets}}
|
|
2737
3985
|
<li>{{this}}</li>
|
|
2738
3986
|
{{/each}}
|
|
2739
3987
|
</ul>
|
|
3988
|
+
{{/if}}
|
|
2740
3989
|
</div>
|
|
3990
|
+
{{/each}}
|
|
2741
3991
|
</div>
|
|
3992
|
+
{{> source}}
|
|
2742
3993
|
</div>
|
|
2743
3994
|
{{> speakerNotes}}
|
|
2744
3995
|
</section>
|
|
@@ -2802,18 +4053,18 @@ var TemplateEngine = class {
|
|
|
2802
4053
|
<section class="{{classes}}" data-slide-index="{{slideIndex}}" style="{{styles}}">
|
|
2803
4054
|
<div class="slide-content">
|
|
2804
4055
|
<h2 class="title animate-fadeIn">{{title}}</h2>
|
|
2805
|
-
<div class="timeline stagger-children">
|
|
2806
|
-
{{#each
|
|
2807
|
-
<div class="timeline-item animate-fadeIn" style="{{animDelay @index 200}}">
|
|
2808
|
-
<div class="
|
|
4056
|
+
<div class="timeline steps stagger-children">
|
|
4057
|
+
{{#each steps}}
|
|
4058
|
+
<div class="step timeline-item animate-fadeIn" style="{{animDelay @index 200}}">
|
|
4059
|
+
<div class="step-number">{{add @index 1}}</div>
|
|
2809
4060
|
<div class="timeline-content">
|
|
2810
|
-
<div class="
|
|
2811
|
-
<div class="
|
|
2812
|
-
{{#if description}}<div class="timeline-desc">{{description}}</div>{{/if}}
|
|
4061
|
+
<div class="step-title">{{label}}</div>
|
|
4062
|
+
{{#if description}}<div class="step-desc">{{description}}</div>{{/if}}
|
|
2813
4063
|
</div>
|
|
2814
4064
|
</div>
|
|
2815
4065
|
{{/each}}
|
|
2816
4066
|
</div>
|
|
4067
|
+
{{> source}}
|
|
2817
4068
|
</div>
|
|
2818
4069
|
{{> speakerNotes}}
|
|
2819
4070
|
</section>
|
|
@@ -3903,949 +5154,554 @@ var QAEngine = class {
|
|
|
3903
5154
|
}
|
|
3904
5155
|
};
|
|
3905
5156
|
|
|
3906
|
-
// src/qa/
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
};
|
|
3916
|
-
var SevenDimensionScorer = class {
|
|
3917
|
-
kb;
|
|
3918
|
-
mode;
|
|
3919
|
-
presentationType;
|
|
3920
|
-
constructor(mode, presentationType) {
|
|
3921
|
-
this.mode = mode;
|
|
3922
|
-
this.presentationType = presentationType;
|
|
3923
|
-
}
|
|
3924
|
-
/**
|
|
3925
|
-
* Score a presentation across all 7 dimensions.
|
|
3926
|
-
*/
|
|
3927
|
-
async score(slides, html, threshold = 95) {
|
|
3928
|
-
this.kb = await getKnowledgeGateway();
|
|
3929
|
-
const layout = await this.scoreLayout(slides, html);
|
|
3930
|
-
const contrast = await this.scoreContrast(html);
|
|
3931
|
-
const graphics = await this.scoreGraphics(slides);
|
|
3932
|
-
const content = await this.scoreContent(slides);
|
|
3933
|
-
const clarity = await this.scoreClarity(slides);
|
|
3934
|
-
const effectiveness = await this.scoreEffectiveness(slides);
|
|
3935
|
-
const consistency = await this.scoreConsistency(slides, html);
|
|
3936
|
-
const overallScore = Math.round(
|
|
3937
|
-
layout.score * DIMENSION_WEIGHTS.layout + contrast.score * DIMENSION_WEIGHTS.contrast + graphics.score * DIMENSION_WEIGHTS.graphics + content.score * DIMENSION_WEIGHTS.content + clarity.score * DIMENSION_WEIGHTS.clarity + effectiveness.score * DIMENSION_WEIGHTS.effectiveness + consistency.score * DIMENSION_WEIGHTS.consistency
|
|
3938
|
-
);
|
|
3939
|
-
const issues = [
|
|
3940
|
-
...layout.issues,
|
|
3941
|
-
...contrast.issues,
|
|
3942
|
-
...graphics.issues,
|
|
3943
|
-
...content.issues,
|
|
3944
|
-
...clarity.issues,
|
|
3945
|
-
...effectiveness.issues,
|
|
3946
|
-
...consistency.issues
|
|
3947
|
-
];
|
|
3948
|
-
return {
|
|
3949
|
-
overallScore,
|
|
3950
|
-
dimensions: {
|
|
3951
|
-
layout,
|
|
3952
|
-
contrast,
|
|
3953
|
-
graphics,
|
|
3954
|
-
content,
|
|
3955
|
-
clarity,
|
|
3956
|
-
effectiveness,
|
|
3957
|
-
consistency
|
|
3958
|
-
},
|
|
3959
|
-
issues,
|
|
3960
|
-
passed: overallScore >= threshold,
|
|
3961
|
-
threshold
|
|
3962
|
-
};
|
|
3963
|
-
}
|
|
3964
|
-
/**
|
|
3965
|
-
* Score layout dimension (whitespace, visual balance, structure)
|
|
3966
|
-
*/
|
|
3967
|
-
async scoreLayout(slides, html) {
|
|
3968
|
-
const issues = [];
|
|
3969
|
-
let totalScore = 0;
|
|
3970
|
-
let checks = 0;
|
|
3971
|
-
const minWhitespace = this.mode === "keynote" ? 0.45 : 0.35;
|
|
3972
|
-
const maxWhitespace = 0.6;
|
|
3973
|
-
const slideSections = html.match(/<section[^>]*>[\s\S]*?<\/section>/gi) || [];
|
|
3974
|
-
for (let i = 0; i < slideSections.length; i++) {
|
|
3975
|
-
const section = slideSections[i];
|
|
3976
|
-
if (!section) continue;
|
|
3977
|
-
const textContent = section.replace(/<[^>]+>/g, "").trim();
|
|
3978
|
-
const totalArea = 1920 * 1080;
|
|
3979
|
-
const estimatedTextArea = textContent.length * 100;
|
|
3980
|
-
const whitespaceRatio = 1 - Math.min(estimatedTextArea / totalArea, 1);
|
|
3981
|
-
if (whitespaceRatio < minWhitespace) {
|
|
3982
|
-
issues.push({
|
|
3983
|
-
slideIndex: i,
|
|
3984
|
-
dimension: "layout",
|
|
3985
|
-
severity: "error",
|
|
3986
|
-
message: `Slide ${i + 1}: Insufficient whitespace (${Math.round(whitespaceRatio * 100)}% < ${Math.round(minWhitespace * 100)}%)`,
|
|
3987
|
-
currentValue: whitespaceRatio,
|
|
3988
|
-
expectedValue: minWhitespace,
|
|
3989
|
-
autoFixable: true,
|
|
3990
|
-
fixSuggestion: "Reduce content or increase margins"
|
|
3991
|
-
});
|
|
3992
|
-
totalScore += 50;
|
|
3993
|
-
} else if (whitespaceRatio > maxWhitespace) {
|
|
3994
|
-
issues.push({
|
|
3995
|
-
slideIndex: i,
|
|
3996
|
-
dimension: "layout",
|
|
3997
|
-
severity: "warning",
|
|
3998
|
-
message: `Slide ${i + 1}: Too much whitespace (${Math.round(whitespaceRatio * 100)}% > ${Math.round(maxWhitespace * 100)}%)`,
|
|
3999
|
-
currentValue: whitespaceRatio,
|
|
4000
|
-
expectedValue: maxWhitespace,
|
|
4001
|
-
autoFixable: false,
|
|
4002
|
-
fixSuggestion: "Add more content or reduce margins"
|
|
4003
|
-
});
|
|
4004
|
-
totalScore += 80;
|
|
4005
|
-
} else {
|
|
4006
|
-
totalScore += 100;
|
|
4007
|
-
}
|
|
4008
|
-
checks++;
|
|
4009
|
-
}
|
|
4010
|
-
for (let i = 0; i < slides.length; i++) {
|
|
4011
|
-
const slide = slides[i];
|
|
4012
|
-
if (!slide) continue;
|
|
4013
|
-
if (!["thank-you", "section-divider"].includes(slide.type)) {
|
|
4014
|
-
if (!slide.data.title || slide.data.title.trim().length === 0) {
|
|
4015
|
-
issues.push({
|
|
4016
|
-
slideIndex: i,
|
|
4017
|
-
dimension: "layout",
|
|
4018
|
-
severity: "warning",
|
|
4019
|
-
message: `Slide ${i + 1}: Missing title`,
|
|
4020
|
-
autoFixable: false,
|
|
4021
|
-
fixSuggestion: "Add a clear slide title"
|
|
4022
|
-
});
|
|
4023
|
-
totalScore += 70;
|
|
4024
|
-
} else {
|
|
4025
|
-
totalScore += 100;
|
|
4026
|
-
}
|
|
4027
|
-
checks++;
|
|
4028
|
-
}
|
|
4029
|
-
}
|
|
4030
|
-
const score = checks > 0 ? Math.round(totalScore / checks) : 100;
|
|
4031
|
-
return {
|
|
4032
|
-
name: "Layout",
|
|
4033
|
-
score,
|
|
4034
|
-
weight: DIMENSION_WEIGHTS.layout,
|
|
4035
|
-
issues,
|
|
4036
|
-
details: {
|
|
4037
|
-
slidesAnalyzed: slides.length,
|
|
4038
|
-
whitespaceTarget: `${Math.round(minWhitespace * 100)}-${Math.round(maxWhitespace * 100)}%`
|
|
4039
|
-
}
|
|
4040
|
-
};
|
|
4041
|
-
}
|
|
4042
|
-
/**
|
|
4043
|
-
* Score contrast dimension (WCAG compliance, readability)
|
|
4044
|
-
*/
|
|
4045
|
-
async scoreContrast(html) {
|
|
4046
|
-
const issues = [];
|
|
4047
|
-
let score = 100;
|
|
4048
|
-
const minContrastRatio = 4.5;
|
|
4049
|
-
const colorMatches = html.match(/color:\s*([^;]+);/gi) || [];
|
|
4050
|
-
const bgColorMatches = html.match(/background(-color)?:\s*([^;]+);/gi) || [];
|
|
4051
|
-
const hasGoodContrast = html.includes("color: #fff") || html.includes("color: white") || html.includes("color: #18181B") || html.includes("color: #F5F5F4");
|
|
4052
|
-
const hasDarkBackground = html.includes("background-color: #18181B") || html.includes("background: #18181B") || html.includes("background-color: rgb(24, 24, 27)");
|
|
4053
|
-
if (html.includes("color: gray") || html.includes("color: #999") || html.includes("color: #888")) {
|
|
4054
|
-
issues.push({
|
|
4055
|
-
slideIndex: -1,
|
|
4056
|
-
dimension: "contrast",
|
|
4057
|
-
severity: "error",
|
|
4058
|
-
message: "Low contrast text color detected (gray text)",
|
|
4059
|
-
currentValue: "gray",
|
|
4060
|
-
expectedValue: "High contrast color",
|
|
4061
|
-
autoFixable: true,
|
|
4062
|
-
fixSuggestion: "Use white (#fff) or dark (#18181B) text depending on background"
|
|
4063
|
-
});
|
|
4064
|
-
score -= 20;
|
|
4065
|
-
}
|
|
4066
|
-
if (!hasGoodContrast && !hasDarkBackground) {
|
|
4067
|
-
issues.push({
|
|
4068
|
-
slideIndex: -1,
|
|
4069
|
-
dimension: "contrast",
|
|
4070
|
-
severity: "warning",
|
|
4071
|
-
message: "Could not verify WCAG-compliant contrast ratios",
|
|
4072
|
-
autoFixable: false,
|
|
4073
|
-
fixSuggestion: "Ensure text has 4.5:1 contrast ratio against background"
|
|
4074
|
-
});
|
|
4075
|
-
score -= 10;
|
|
4076
|
-
}
|
|
4077
|
-
const hasSmallFont = html.match(/font-size:\s*(1[0-3]|[0-9])px/i) !== null;
|
|
4078
|
-
if (hasSmallFont) {
|
|
4079
|
-
issues.push({
|
|
4080
|
-
slideIndex: -1,
|
|
4081
|
-
dimension: "contrast",
|
|
4082
|
-
severity: "error",
|
|
4083
|
-
message: "Font size too small for presentation (< 14px)",
|
|
4084
|
-
currentValue: "Small font",
|
|
4085
|
-
expectedValue: "18px minimum for body text",
|
|
4086
|
-
autoFixable: true,
|
|
4087
|
-
fixSuggestion: "Increase font size to minimum 18px"
|
|
4088
|
-
});
|
|
4089
|
-
score -= 15;
|
|
4090
|
-
}
|
|
4091
|
-
return {
|
|
4092
|
-
name: "Contrast",
|
|
4093
|
-
score: Math.max(0, score),
|
|
4094
|
-
weight: DIMENSION_WEIGHTS.contrast,
|
|
4095
|
-
issues,
|
|
4096
|
-
details: {
|
|
4097
|
-
wcagLevel: "AA",
|
|
4098
|
-
minContrastRatio,
|
|
4099
|
-
colorDefinitions: colorMatches.length,
|
|
4100
|
-
backgroundDefinitions: bgColorMatches.length
|
|
4101
|
-
}
|
|
4102
|
-
};
|
|
4103
|
-
}
|
|
4104
|
-
/**
|
|
4105
|
-
* Score graphics dimension (images, placement, relevance)
|
|
4106
|
-
*/
|
|
4107
|
-
async scoreGraphics(slides) {
|
|
4108
|
-
const issues = [];
|
|
4109
|
-
let score = 100;
|
|
4110
|
-
let slidesWithImages = 0;
|
|
4111
|
-
let totalImageSlides = 0;
|
|
4112
|
-
for (let i = 0; i < slides.length; i++) {
|
|
4113
|
-
const slide = slides[i];
|
|
4114
|
-
if (!slide) continue;
|
|
4115
|
-
const shouldHaveImage = ["hero", "image", "feature"].includes(slide.type);
|
|
4116
|
-
if (shouldHaveImage) {
|
|
4117
|
-
totalImageSlides++;
|
|
4118
|
-
if (slide.data.image || slide.data.backgroundImage) {
|
|
4119
|
-
slidesWithImages++;
|
|
4120
|
-
} else {
|
|
4121
|
-
issues.push({
|
|
4122
|
-
slideIndex: i,
|
|
4123
|
-
dimension: "graphics",
|
|
4124
|
-
severity: "warning",
|
|
4125
|
-
message: `Slide ${i + 1} (${slide.type}): Missing expected image`,
|
|
4126
|
-
autoFixable: false,
|
|
4127
|
-
fixSuggestion: "Add a relevant image or change slide type"
|
|
4128
|
-
});
|
|
4129
|
-
score -= 5;
|
|
4130
|
-
}
|
|
4131
|
-
}
|
|
4132
|
-
}
|
|
4133
|
-
return {
|
|
4134
|
-
name: "Graphics",
|
|
4135
|
-
score: Math.max(0, score),
|
|
4136
|
-
weight: DIMENSION_WEIGHTS.graphics,
|
|
4137
|
-
issues,
|
|
4138
|
-
details: {
|
|
4139
|
-
slidesWithImages,
|
|
4140
|
-
expectedImageSlides: totalImageSlides,
|
|
4141
|
-
imageRatio: totalImageSlides > 0 ? slidesWithImages / totalImageSlides : 1
|
|
4142
|
-
}
|
|
4143
|
-
};
|
|
4144
|
-
}
|
|
4145
|
-
/**
|
|
4146
|
-
* Score content dimension (word limits, bullet counts, structure)
|
|
4147
|
-
* This is critical - must use KB rules exactly.
|
|
4148
|
-
*/
|
|
4149
|
-
async scoreContent(slides) {
|
|
4150
|
-
const issues = [];
|
|
4151
|
-
let totalScore = 0;
|
|
4152
|
-
let checks = 0;
|
|
4153
|
-
const wordLimits = this.kb.getWordLimits(this.mode);
|
|
4154
|
-
const bulletLimits = this.kb.getBulletLimits(this.mode);
|
|
4155
|
-
const defaultMaxWords = this.mode === "keynote" ? 25 : 80;
|
|
4156
|
-
const defaultMinWords = this.mode === "keynote" ? 3 : 15;
|
|
4157
|
-
const defaultMaxBullets = 5;
|
|
4158
|
-
for (let i = 0; i < slides.length; i++) {
|
|
4159
|
-
const slide = slides[i];
|
|
4160
|
-
if (!slide) continue;
|
|
4161
|
-
const wordCount = this.countWords(slide);
|
|
4162
|
-
const slideType = slide.type;
|
|
4163
|
-
const maxWords = wordLimits[slideType] ?? defaultMaxWords;
|
|
4164
|
-
const minWords = defaultMinWords;
|
|
4165
|
-
if (wordCount > maxWords) {
|
|
4166
|
-
const severity = wordCount > maxWords * 1.5 ? "error" : "warning";
|
|
4167
|
-
issues.push({
|
|
4168
|
-
slideIndex: i,
|
|
4169
|
-
dimension: "content",
|
|
4170
|
-
severity,
|
|
4171
|
-
message: `Slide ${i + 1}: Word count ${wordCount} exceeds limit of ${maxWords} for ${this.mode} mode`,
|
|
4172
|
-
currentValue: wordCount,
|
|
4173
|
-
expectedValue: maxWords,
|
|
4174
|
-
autoFixable: true,
|
|
4175
|
-
fixSuggestion: "Condense text to key points only"
|
|
4176
|
-
});
|
|
4177
|
-
totalScore += severity === "error" ? 40 : 70;
|
|
4178
|
-
} else if (wordCount < minWords && !["title", "section-divider", "thank-you"].includes(slide.type)) {
|
|
4179
|
-
issues.push({
|
|
4180
|
-
slideIndex: i,
|
|
4181
|
-
dimension: "content",
|
|
4182
|
-
severity: "warning",
|
|
4183
|
-
message: `Slide ${i + 1}: Word count ${wordCount} may be too sparse (min: ${minWords})`,
|
|
4184
|
-
currentValue: wordCount,
|
|
4185
|
-
expectedValue: minWords,
|
|
4186
|
-
autoFixable: false,
|
|
4187
|
-
fixSuggestion: "Add supporting content"
|
|
4188
|
-
});
|
|
4189
|
-
totalScore += 80;
|
|
4190
|
-
} else {
|
|
4191
|
-
totalScore += 100;
|
|
4192
|
-
}
|
|
4193
|
-
checks++;
|
|
4194
|
-
if (slide.data.bullets && Array.isArray(slide.data.bullets)) {
|
|
4195
|
-
const bulletCount = slide.data.bullets.length;
|
|
4196
|
-
const maxBullets = bulletLimits[slideType] ?? defaultMaxBullets;
|
|
4197
|
-
if (bulletCount > maxBullets) {
|
|
4198
|
-
issues.push({
|
|
4199
|
-
slideIndex: i,
|
|
4200
|
-
dimension: "content",
|
|
4201
|
-
severity: "error",
|
|
4202
|
-
message: `Slide ${i + 1}: ${bulletCount} bullets exceeds limit of ${maxBullets}`,
|
|
4203
|
-
currentValue: bulletCount,
|
|
4204
|
-
expectedValue: maxBullets,
|
|
4205
|
-
autoFixable: true,
|
|
4206
|
-
fixSuggestion: "Reduce to top 3-5 key points"
|
|
4207
|
-
});
|
|
4208
|
-
totalScore += 50;
|
|
4209
|
-
} else {
|
|
4210
|
-
totalScore += 100;
|
|
4211
|
-
}
|
|
4212
|
-
checks++;
|
|
4213
|
-
}
|
|
4214
|
-
}
|
|
4215
|
-
const score = checks > 0 ? Math.round(totalScore / checks) : 100;
|
|
4216
|
-
return {
|
|
4217
|
-
name: "Content",
|
|
4218
|
-
score,
|
|
4219
|
-
weight: DIMENSION_WEIGHTS.content,
|
|
4220
|
-
issues,
|
|
4221
|
-
details: {
|
|
4222
|
-
mode: this.mode,
|
|
4223
|
-
wordLimits,
|
|
4224
|
-
bulletLimits,
|
|
4225
|
-
totalSlides: slides.length
|
|
4226
|
-
}
|
|
4227
|
-
};
|
|
4228
|
-
}
|
|
4229
|
-
/**
|
|
4230
|
-
* Score clarity dimension (message focus, information density)
|
|
4231
|
-
*/
|
|
4232
|
-
async scoreClarity(slides) {
|
|
4233
|
-
const issues = [];
|
|
4234
|
-
let totalScore = 0;
|
|
4235
|
-
let checks = 0;
|
|
4236
|
-
for (let i = 0; i < slides.length; i++) {
|
|
4237
|
-
const slide = slides[i];
|
|
4238
|
-
if (!slide) continue;
|
|
4239
|
-
if (slide.data.keyMessage) {
|
|
4240
|
-
const keyMessageWords = slide.data.keyMessage.split(/\s+/).length;
|
|
4241
|
-
const maxKeyMessageWords = this.mode === "keynote" ? 15 : 25;
|
|
4242
|
-
if (keyMessageWords > maxKeyMessageWords) {
|
|
4243
|
-
issues.push({
|
|
4244
|
-
slideIndex: i,
|
|
4245
|
-
dimension: "clarity",
|
|
4246
|
-
severity: "warning",
|
|
4247
|
-
message: `Slide ${i + 1}: Key message too long (${keyMessageWords} words > ${maxKeyMessageWords})`,
|
|
4248
|
-
currentValue: keyMessageWords,
|
|
4249
|
-
expectedValue: maxKeyMessageWords,
|
|
4250
|
-
autoFixable: true,
|
|
4251
|
-
fixSuggestion: "Shorten key message to one impactful sentence"
|
|
4252
|
-
});
|
|
4253
|
-
totalScore += 70;
|
|
4254
|
-
} else {
|
|
4255
|
-
totalScore += 100;
|
|
4256
|
-
}
|
|
4257
|
-
checks++;
|
|
4258
|
-
}
|
|
4259
|
-
if (slide.data.title) {
|
|
4260
|
-
const title = slide.data.title;
|
|
4261
|
-
const titleWords = title.split(/\s+/).length;
|
|
4262
|
-
if (titleWords > 10) {
|
|
4263
|
-
issues.push({
|
|
4264
|
-
slideIndex: i,
|
|
4265
|
-
dimension: "clarity",
|
|
4266
|
-
severity: "warning",
|
|
4267
|
-
message: `Slide ${i + 1}: Title too long (${titleWords} words)`,
|
|
4268
|
-
currentValue: titleWords,
|
|
4269
|
-
expectedValue: "2-8 words",
|
|
4270
|
-
autoFixable: true,
|
|
4271
|
-
fixSuggestion: "Use action-oriented, concise title"
|
|
4272
|
-
});
|
|
4273
|
-
totalScore += 75;
|
|
4274
|
-
} else {
|
|
4275
|
-
totalScore += 100;
|
|
4276
|
-
}
|
|
4277
|
-
checks++;
|
|
4278
|
-
}
|
|
4279
|
-
const elementCount = (slide.data.title ? 1 : 0) + (slide.data.subtitle ? 1 : 0) + (slide.data.body ? 1 : 0) + (slide.data.bullets?.length ?? 0) + (slide.data.keyMessage ? 1 : 0) + (slide.data.image ? 1 : 0);
|
|
4280
|
-
const maxElements = this.mode === "keynote" ? 4 : 6;
|
|
4281
|
-
if (elementCount > maxElements) {
|
|
4282
|
-
issues.push({
|
|
4283
|
-
slideIndex: i,
|
|
4284
|
-
dimension: "clarity",
|
|
4285
|
-
severity: "warning",
|
|
4286
|
-
message: `Slide ${i + 1}: Too many elements (${elementCount} > ${maxElements})`,
|
|
4287
|
-
currentValue: elementCount,
|
|
4288
|
-
expectedValue: maxElements,
|
|
4289
|
-
autoFixable: true,
|
|
4290
|
-
fixSuggestion: "Split into multiple slides for clarity"
|
|
4291
|
-
});
|
|
4292
|
-
totalScore += 70;
|
|
4293
|
-
} else {
|
|
4294
|
-
totalScore += 100;
|
|
4295
|
-
}
|
|
4296
|
-
checks++;
|
|
4297
|
-
}
|
|
4298
|
-
const score = checks > 0 ? Math.round(totalScore / checks) : 100;
|
|
4299
|
-
return {
|
|
4300
|
-
name: "Clarity",
|
|
4301
|
-
score,
|
|
4302
|
-
weight: DIMENSION_WEIGHTS.clarity,
|
|
4303
|
-
issues,
|
|
4304
|
-
details: {
|
|
4305
|
-
slidesAnalyzed: slides.length,
|
|
4306
|
-
mode: this.mode
|
|
4307
|
-
}
|
|
4308
|
-
};
|
|
4309
|
-
}
|
|
4310
|
-
/**
|
|
4311
|
-
* Score effectiveness dimension (expert methodology compliance)
|
|
4312
|
-
*/
|
|
4313
|
-
async scoreEffectiveness(slides) {
|
|
4314
|
-
const issues = [];
|
|
4315
|
-
let score = 100;
|
|
4316
|
-
if (slides.length >= 3) {
|
|
4317
|
-
const firstSlide = slides[0];
|
|
4318
|
-
const lastSlide = slides[slides.length - 1];
|
|
4319
|
-
if (firstSlide && !["title", "hero"].includes(firstSlide.type)) {
|
|
4320
|
-
issues.push({
|
|
4321
|
-
slideIndex: 0,
|
|
4322
|
-
dimension: "effectiveness",
|
|
4323
|
-
severity: "warning",
|
|
4324
|
-
message: "Presentation should start with a title or hero slide",
|
|
4325
|
-
currentValue: firstSlide.type,
|
|
4326
|
-
expectedValue: "title or hero",
|
|
4327
|
-
autoFixable: false,
|
|
4328
|
-
fixSuggestion: "Add a compelling opening slide"
|
|
4329
|
-
});
|
|
4330
|
-
score -= 10;
|
|
4331
|
-
}
|
|
4332
|
-
if (lastSlide && !["thank-you", "cta", "closing"].includes(lastSlide.type)) {
|
|
4333
|
-
issues.push({
|
|
4334
|
-
slideIndex: slides.length - 1,
|
|
4335
|
-
dimension: "effectiveness",
|
|
4336
|
-
severity: "warning",
|
|
4337
|
-
message: "Presentation should end with a closing or CTA slide",
|
|
4338
|
-
currentValue: lastSlide.type,
|
|
4339
|
-
expectedValue: "thank-you, cta, or closing",
|
|
4340
|
-
autoFixable: false,
|
|
4341
|
-
fixSuggestion: "Add a clear call-to-action or closing"
|
|
4342
|
-
});
|
|
4343
|
-
score -= 10;
|
|
4344
|
-
}
|
|
4345
|
-
}
|
|
4346
|
-
const keyMessages = slides.filter((s) => s.data.keyMessage);
|
|
4347
|
-
if (keyMessages.length > 0 && keyMessages.length !== 3 && keyMessages.length > 4) {
|
|
4348
|
-
issues.push({
|
|
4349
|
-
slideIndex: -1,
|
|
4350
|
-
dimension: "effectiveness",
|
|
4351
|
-
severity: "info",
|
|
4352
|
-
message: `Consider using Rule of Three: ${keyMessages.length} key messages found`,
|
|
4353
|
-
currentValue: keyMessages.length,
|
|
4354
|
-
expectedValue: 3,
|
|
4355
|
-
autoFixable: false,
|
|
4356
|
-
fixSuggestion: "Group messages into 3 main themes"
|
|
4357
|
-
});
|
|
4358
|
-
score -= 5;
|
|
4359
|
-
}
|
|
4360
|
-
const hasScqaElements = slides.some(
|
|
4361
|
-
(s) => s.data.scqaType || s.data.title && s.data.title.toLowerCase().includes("challenge") || s.data.title && s.data.title.toLowerCase().includes("solution") || s.data.title && s.data.title.toLowerCase().includes("recommendation")
|
|
4362
|
-
);
|
|
4363
|
-
if (!hasScqaElements && this.presentationType === "consulting_deck") {
|
|
4364
|
-
issues.push({
|
|
4365
|
-
slideIndex: -1,
|
|
4366
|
-
dimension: "effectiveness",
|
|
4367
|
-
severity: "warning",
|
|
4368
|
-
message: "Consulting deck should follow SCQA structure (Situation, Complication, Question, Answer)",
|
|
4369
|
-
autoFixable: false,
|
|
4370
|
-
fixSuggestion: "Organize content using Barbara Minto Pyramid Principle"
|
|
4371
|
-
});
|
|
4372
|
-
score -= 10;
|
|
4373
|
-
}
|
|
4374
|
-
const firstSlideType = slides[0]?.type;
|
|
4375
|
-
const lastSlideType = slides[slides.length - 1]?.type;
|
|
4376
|
-
return {
|
|
4377
|
-
name: "Effectiveness",
|
|
4378
|
-
score: Math.max(0, score),
|
|
4379
|
-
weight: DIMENSION_WEIGHTS.effectiveness,
|
|
4380
|
-
issues,
|
|
4381
|
-
details: {
|
|
4382
|
-
presentationType: this.presentationType,
|
|
4383
|
-
slideCount: slides.length,
|
|
4384
|
-
hasOpeningSlide: firstSlideType ? ["title", "hero"].includes(firstSlideType) : false,
|
|
4385
|
-
hasClosingSlide: lastSlideType ? ["thank-you", "cta", "closing"].includes(lastSlideType) : false
|
|
4386
|
-
}
|
|
4387
|
-
};
|
|
5157
|
+
// src/qa/VisualQualityEvaluator.ts
|
|
5158
|
+
import * as path from "path";
|
|
5159
|
+
import * as fs from "fs";
|
|
5160
|
+
var VisualQualityEvaluator = class {
|
|
5161
|
+
browser = null;
|
|
5162
|
+
page = null;
|
|
5163
|
+
screenshotDir;
|
|
5164
|
+
constructor(screenshotDir = "/tmp/presentation-qa") {
|
|
5165
|
+
this.screenshotDir = screenshotDir;
|
|
4388
5166
|
}
|
|
4389
5167
|
/**
|
|
4390
|
-
*
|
|
5168
|
+
* Evaluate a presentation's visual quality.
|
|
5169
|
+
* This opens the HTML in a real browser and evaluates each slide.
|
|
4391
5170
|
*/
|
|
4392
|
-
async
|
|
4393
|
-
|
|
4394
|
-
|
|
4395
|
-
const hasCssVariables = html.includes("var(--") || html.includes(":root");
|
|
4396
|
-
if (!hasCssVariables) {
|
|
4397
|
-
issues.push({
|
|
4398
|
-
slideIndex: -1,
|
|
4399
|
-
dimension: "consistency",
|
|
4400
|
-
severity: "warning",
|
|
4401
|
-
message: "Presentation lacks CSS variables for consistent styling",
|
|
4402
|
-
autoFixable: true,
|
|
4403
|
-
fixSuggestion: "Use CSS variables for colors, fonts, and spacing"
|
|
4404
|
-
});
|
|
4405
|
-
score -= 10;
|
|
5171
|
+
async evaluate(htmlPath) {
|
|
5172
|
+
if (!fs.existsSync(this.screenshotDir)) {
|
|
5173
|
+
fs.mkdirSync(this.screenshotDir, { recursive: true });
|
|
4406
5174
|
}
|
|
4407
|
-
|
|
4408
|
-
|
|
4409
|
-
|
|
4410
|
-
|
|
4411
|
-
|
|
4412
|
-
|
|
4413
|
-
|
|
4414
|
-
|
|
4415
|
-
|
|
5175
|
+
try {
|
|
5176
|
+
await this.launchBrowser();
|
|
5177
|
+
const absolutePath = path.resolve(htmlPath);
|
|
5178
|
+
await this.page.goto(`file://${absolutePath}`, { waitUntil: "networkidle" });
|
|
5179
|
+
await this.page.waitForSelector(".reveal", { timeout: 5e3 });
|
|
5180
|
+
await this.page.waitForTimeout(1e3);
|
|
5181
|
+
const slideCount = await this.getSlideCount();
|
|
5182
|
+
console.log(`Evaluating ${slideCount} slides...`);
|
|
5183
|
+
const slideScores = [];
|
|
5184
|
+
for (let i = 0; i < slideCount; i++) {
|
|
5185
|
+
const score = await this.evaluateSlide(i);
|
|
5186
|
+
slideScores.push(score);
|
|
4416
5187
|
}
|
|
5188
|
+
const narrativeFlow = this.evaluateNarrativeFlow(slideScores);
|
|
5189
|
+
const visualConsistency = this.evaluateVisualConsistency(slideScores);
|
|
5190
|
+
const contentQuality = this.evaluateContentQuality(slideScores);
|
|
5191
|
+
const executiveReadiness = this.evaluateExecutiveReadiness(slideScores);
|
|
5192
|
+
const overallScore = Math.round(
|
|
5193
|
+
narrativeFlow.score + visualConsistency.score + contentQuality.score + executiveReadiness.score
|
|
5194
|
+
);
|
|
5195
|
+
const verdict = this.determineVerdict(overallScore);
|
|
5196
|
+
const { topIssues, topStrengths } = this.extractTopIssuesAndStrengths(slideScores);
|
|
5197
|
+
return {
|
|
5198
|
+
overallScore,
|
|
5199
|
+
narrativeFlow,
|
|
5200
|
+
visualConsistency,
|
|
5201
|
+
contentQuality,
|
|
5202
|
+
executiveReadiness,
|
|
5203
|
+
slideScores,
|
|
5204
|
+
verdict,
|
|
5205
|
+
verdictExplanation: this.explainVerdict(verdict, overallScore),
|
|
5206
|
+
topIssues,
|
|
5207
|
+
topStrengths
|
|
5208
|
+
};
|
|
5209
|
+
} finally {
|
|
5210
|
+
await this.closeBrowser();
|
|
4417
5211
|
}
|
|
4418
|
-
if (titlePatterns.size > 1) {
|
|
4419
|
-
issues.push({
|
|
4420
|
-
slideIndex: -1,
|
|
4421
|
-
dimension: "consistency",
|
|
4422
|
-
severity: "warning",
|
|
4423
|
-
message: `Inconsistent title casing: ${Array.from(titlePatterns).join(", ")}`,
|
|
4424
|
-
autoFixable: true,
|
|
4425
|
-
fixSuggestion: "Use consistent title case throughout"
|
|
4426
|
-
});
|
|
4427
|
-
score -= 10;
|
|
4428
|
-
}
|
|
4429
|
-
const fontMatches = html.match(/font-family:\s*([^;]+);/gi) || [];
|
|
4430
|
-
const uniqueFonts = new Set(fontMatches.map((f) => f.toLowerCase()));
|
|
4431
|
-
if (uniqueFonts.size > 3) {
|
|
4432
|
-
issues.push({
|
|
4433
|
-
slideIndex: -1,
|
|
4434
|
-
dimension: "consistency",
|
|
4435
|
-
severity: "warning",
|
|
4436
|
-
message: `Too many font families (${uniqueFonts.size} > 3)`,
|
|
4437
|
-
autoFixable: true,
|
|
4438
|
-
fixSuggestion: "Use 2-3 complementary fonts max"
|
|
4439
|
-
});
|
|
4440
|
-
score -= 10;
|
|
4441
|
-
}
|
|
4442
|
-
return {
|
|
4443
|
-
name: "Consistency",
|
|
4444
|
-
score: Math.max(0, score),
|
|
4445
|
-
weight: DIMENSION_WEIGHTS.consistency,
|
|
4446
|
-
issues,
|
|
4447
|
-
details: {
|
|
4448
|
-
hasCssVariables,
|
|
4449
|
-
titlePatterns: Array.from(titlePatterns),
|
|
4450
|
-
fontFamilyCount: uniqueFonts.size
|
|
4451
|
-
}
|
|
4452
|
-
};
|
|
4453
5212
|
}
|
|
4454
|
-
|
|
4455
|
-
|
|
4456
|
-
|
|
4457
|
-
|
|
4458
|
-
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
return text.split(/\s+/).filter((w) => w.length > 0).length;
|
|
5213
|
+
// ===========================================================================
|
|
5214
|
+
// BROWSER MANAGEMENT
|
|
5215
|
+
// ===========================================================================
|
|
5216
|
+
async launchBrowser() {
|
|
5217
|
+
const { chromium: chromium2 } = await import("playwright");
|
|
5218
|
+
this.browser = await chromium2.launch({ headless: true });
|
|
5219
|
+
const context = await this.browser.newContext({
|
|
5220
|
+
viewport: { width: 1920, height: 1080 }
|
|
5221
|
+
});
|
|
5222
|
+
this.page = await context.newPage();
|
|
4465
5223
|
}
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
|
|
4469
|
-
|
|
4470
|
-
|
|
4471
|
-
lines.push("\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557");
|
|
4472
|
-
lines.push("\u2551 7-DIMENSION QUALITY ASSESSMENT \u2551");
|
|
4473
|
-
lines.push("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D");
|
|
4474
|
-
lines.push("");
|
|
4475
|
-
lines.push(`Overall Score: ${result.overallScore}/100 ${result.passed ? "\u2705 PASSED" : "\u274C FAILED"}`);
|
|
4476
|
-
lines.push(`Threshold: ${result.threshold}/100`);
|
|
4477
|
-
lines.push("");
|
|
4478
|
-
lines.push("Dimension Breakdown:");
|
|
4479
|
-
lines.push("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
4480
|
-
const dims = result.dimensions;
|
|
4481
|
-
const formatDim = (name, d) => {
|
|
4482
|
-
const bar = "\u2588".repeat(Math.floor(d.score / 10)) + "\u2591".repeat(10 - Math.floor(d.score / 10));
|
|
4483
|
-
const status = d.score >= 95 ? "\u2705" : d.score >= 80 ? "\u26A0\uFE0F" : "\u274C";
|
|
4484
|
-
return `${status} ${name.padEnd(14)} ${bar} ${d.score.toString().padStart(3)}/100 (${(d.weight * 100).toFixed(0)}%)`;
|
|
4485
|
-
};
|
|
4486
|
-
lines.push(formatDim("Layout", dims.layout));
|
|
4487
|
-
lines.push(formatDim("Contrast", dims.contrast));
|
|
4488
|
-
lines.push(formatDim("Graphics", dims.graphics));
|
|
4489
|
-
lines.push(formatDim("Content", dims.content));
|
|
4490
|
-
lines.push(formatDim("Clarity", dims.clarity));
|
|
4491
|
-
lines.push(formatDim("Effectiveness", dims.effectiveness));
|
|
4492
|
-
lines.push(formatDim("Consistency", dims.consistency));
|
|
4493
|
-
lines.push("");
|
|
4494
|
-
const errors = result.issues.filter((i) => i.severity === "error");
|
|
4495
|
-
const warnings = result.issues.filter((i) => i.severity === "warning");
|
|
4496
|
-
if (errors.length > 0) {
|
|
4497
|
-
lines.push("\u274C Errors:");
|
|
4498
|
-
errors.forEach((e) => lines.push(` \u2022 ${e.message}`));
|
|
4499
|
-
lines.push("");
|
|
4500
|
-
}
|
|
4501
|
-
if (warnings.length > 0) {
|
|
4502
|
-
lines.push("\u26A0\uFE0F Warnings:");
|
|
4503
|
-
warnings.slice(0, 10).forEach((w) => lines.push(` \u2022 ${w.message}`));
|
|
4504
|
-
if (warnings.length > 10) {
|
|
4505
|
-
lines.push(` ... and ${warnings.length - 10} more warnings`);
|
|
4506
|
-
}
|
|
4507
|
-
lines.push("");
|
|
4508
|
-
}
|
|
4509
|
-
const autoFixable = result.issues.filter((i) => i.autoFixable);
|
|
4510
|
-
if (autoFixable.length > 0) {
|
|
4511
|
-
lines.push(`\u{1F527} ${autoFixable.length} issues can be auto-fixed`);
|
|
5224
|
+
async closeBrowser() {
|
|
5225
|
+
if (this.browser) {
|
|
5226
|
+
await this.browser.close();
|
|
5227
|
+
this.browser = null;
|
|
5228
|
+
this.page = null;
|
|
4512
5229
|
}
|
|
4513
|
-
return lines.join("\n");
|
|
4514
5230
|
}
|
|
4515
|
-
|
|
4516
|
-
|
|
4517
|
-
|
|
4518
|
-
|
|
4519
|
-
|
|
4520
|
-
mode;
|
|
4521
|
-
presentationType;
|
|
4522
|
-
constructor(mode, presentationType) {
|
|
4523
|
-
this.mode = mode;
|
|
4524
|
-
this.presentationType = presentationType;
|
|
5231
|
+
async getSlideCount() {
|
|
5232
|
+
return await this.page.evaluate(() => {
|
|
5233
|
+
const slides = document.querySelectorAll(".slides > section");
|
|
5234
|
+
return slides.length;
|
|
5235
|
+
});
|
|
4525
5236
|
}
|
|
4526
|
-
|
|
4527
|
-
|
|
4528
|
-
|
|
4529
|
-
async
|
|
4530
|
-
this.
|
|
4531
|
-
|
|
4532
|
-
|
|
4533
|
-
|
|
4534
|
-
|
|
4535
|
-
|
|
4536
|
-
|
|
4537
|
-
|
|
4538
|
-
fixesApplied.push(result);
|
|
4539
|
-
} else {
|
|
4540
|
-
fixesSkipped.push(result);
|
|
5237
|
+
// ===========================================================================
|
|
5238
|
+
// SLIDE-LEVEL EVALUATION
|
|
5239
|
+
// ===========================================================================
|
|
5240
|
+
async evaluateSlide(slideIndex) {
|
|
5241
|
+
await this.page.evaluate((index) => {
|
|
5242
|
+
window.Reveal?.slide(index, 0);
|
|
5243
|
+
}, slideIndex);
|
|
5244
|
+
await this.page.waitForTimeout(600);
|
|
5245
|
+
await this.page.evaluate(() => {
|
|
5246
|
+
const currentSlide = document.querySelector(".present");
|
|
5247
|
+
if (currentSlide) {
|
|
5248
|
+
currentSlide.getAnimations().forEach((anim) => anim.finish());
|
|
4541
5249
|
}
|
|
5250
|
+
});
|
|
5251
|
+
await this.page.waitForTimeout(100);
|
|
5252
|
+
const screenshotPath = path.join(this.screenshotDir, `slide-${slideIndex}.png`);
|
|
5253
|
+
await this.page.screenshot({ path: screenshotPath, fullPage: false });
|
|
5254
|
+
const slideData = await this.page.evaluate(() => {
|
|
5255
|
+
const currentSlide = document.querySelector(".present");
|
|
5256
|
+
if (!currentSlide) return null;
|
|
5257
|
+
const title = currentSlide.querySelector("h1, h2, .title")?.textContent?.trim() || "";
|
|
5258
|
+
const body = currentSlide.querySelector(".body, p:not(.subtitle)")?.textContent?.trim() || "";
|
|
5259
|
+
const bullets = Array.from(currentSlide.querySelectorAll("li")).map((li) => li.textContent?.trim() || "");
|
|
5260
|
+
const hasImage = !!currentSlide.querySelector("img");
|
|
5261
|
+
const hasChart = !!currentSlide.querySelector(".chart, svg, canvas");
|
|
5262
|
+
const classList = Array.from(currentSlide.classList);
|
|
5263
|
+
const backgroundColor = window.getComputedStyle(currentSlide).backgroundColor;
|
|
5264
|
+
const titleEl = currentSlide.querySelector("h1, h2, .title");
|
|
5265
|
+
const titleStyles = titleEl ? window.getComputedStyle(titleEl) : null;
|
|
5266
|
+
return {
|
|
5267
|
+
title,
|
|
5268
|
+
body,
|
|
5269
|
+
bullets,
|
|
5270
|
+
hasImage,
|
|
5271
|
+
hasChart,
|
|
5272
|
+
classList,
|
|
5273
|
+
backgroundColor,
|
|
5274
|
+
titleFontSize: titleStyles?.fontSize || "",
|
|
5275
|
+
titleColor: titleStyles?.color || "",
|
|
5276
|
+
contentLength: (title + body + bullets.join(" ")).length
|
|
5277
|
+
};
|
|
5278
|
+
});
|
|
5279
|
+
return this.scoreSlide(slideIndex, slideData, screenshotPath);
|
|
5280
|
+
}
|
|
5281
|
+
scoreSlide(slideIndex, slideData, screenshotPath) {
|
|
5282
|
+
if (!slideData) {
|
|
5283
|
+
return {
|
|
5284
|
+
slideIndex,
|
|
5285
|
+
slideType: "unknown",
|
|
5286
|
+
visualImpact: 0,
|
|
5287
|
+
visualImpactNotes: "Could not analyze slide",
|
|
5288
|
+
contentClarity: 0,
|
|
5289
|
+
contentClarityNotes: "Could not analyze slide",
|
|
5290
|
+
professionalPolish: 0,
|
|
5291
|
+
professionalPolishNotes: "Could not analyze slide",
|
|
5292
|
+
themeCoherence: 0,
|
|
5293
|
+
themeCoherenceNotes: "Could not analyze slide",
|
|
5294
|
+
totalScore: 0,
|
|
5295
|
+
screenshotPath
|
|
5296
|
+
};
|
|
5297
|
+
}
|
|
5298
|
+
const slideType = this.inferSlideType(slideData);
|
|
5299
|
+
let visualImpact = 5;
|
|
5300
|
+
const visualNotes = [];
|
|
5301
|
+
if (slideData.hasImage) {
|
|
5302
|
+
visualImpact += 2;
|
|
5303
|
+
visualNotes.push("Has imagery");
|
|
4542
5304
|
}
|
|
4543
|
-
|
|
5305
|
+
if (slideData.hasChart) {
|
|
5306
|
+
visualImpact += 2;
|
|
5307
|
+
visualNotes.push("Has data visualization");
|
|
5308
|
+
}
|
|
5309
|
+
const highImpactTypes = [
|
|
5310
|
+
"big-number",
|
|
5311
|
+
"big_number",
|
|
5312
|
+
"metrics-grid",
|
|
5313
|
+
"metrics_grid",
|
|
5314
|
+
"three-column",
|
|
5315
|
+
"three_column",
|
|
5316
|
+
"three-points",
|
|
5317
|
+
"three_points",
|
|
5318
|
+
"title-impact",
|
|
5319
|
+
"title_impact",
|
|
5320
|
+
"cta",
|
|
5321
|
+
"call-to-action",
|
|
5322
|
+
"comparison",
|
|
5323
|
+
"timeline",
|
|
5324
|
+
"process",
|
|
5325
|
+
"quote",
|
|
5326
|
+
"testimonial"
|
|
5327
|
+
];
|
|
5328
|
+
if (highImpactTypes.some((t) => slideType.includes(t.replace(/_/g, "-")) || slideType.includes(t.replace(/-/g, "_")))) {
|
|
5329
|
+
visualImpact += 2;
|
|
5330
|
+
visualNotes.push("High-impact slide type");
|
|
5331
|
+
}
|
|
5332
|
+
if (slideType === "single-statement" && slideData.body.length < 50) {
|
|
5333
|
+
visualImpact += 2;
|
|
5334
|
+
visualNotes.push("Clean single statement");
|
|
5335
|
+
}
|
|
5336
|
+
if (slideType === "title" && slideData.title.length > 0 && slideData.title.length < 80) {
|
|
5337
|
+
visualImpact += 1;
|
|
5338
|
+
visualNotes.push("Strong title");
|
|
5339
|
+
}
|
|
5340
|
+
if (slideData.contentLength > 300) {
|
|
5341
|
+
visualImpact -= 3;
|
|
5342
|
+
visualNotes.push("Too much text - overwhelming");
|
|
5343
|
+
}
|
|
5344
|
+
if (slideData.bullets.length > 5) {
|
|
5345
|
+
visualImpact -= 2;
|
|
5346
|
+
visualNotes.push("Too many bullets");
|
|
5347
|
+
}
|
|
5348
|
+
visualImpact = Math.max(0, Math.min(10, visualImpact));
|
|
5349
|
+
let contentClarity = 7;
|
|
5350
|
+
const clarityNotes = [];
|
|
5351
|
+
if (slideData.title.length > 80) {
|
|
5352
|
+
contentClarity -= 2;
|
|
5353
|
+
clarityNotes.push("Title too long");
|
|
5354
|
+
}
|
|
5355
|
+
if (slideData.title.length === 0) {
|
|
5356
|
+
contentClarity -= 3;
|
|
5357
|
+
clarityNotes.push("No title");
|
|
5358
|
+
}
|
|
5359
|
+
if (slideData.body && slideData.body.length > 0 && slideData.body.length < 200) {
|
|
5360
|
+
contentClarity += 1;
|
|
5361
|
+
clarityNotes.push("Good content length");
|
|
5362
|
+
}
|
|
5363
|
+
const avgBulletLength = slideData.bullets.length > 0 ? slideData.bullets.reduce((sum, b) => sum + b.length, 0) / slideData.bullets.length : 0;
|
|
5364
|
+
if (avgBulletLength > 100) {
|
|
5365
|
+
contentClarity -= 2;
|
|
5366
|
+
clarityNotes.push("Bullets too long - not scannable");
|
|
5367
|
+
}
|
|
5368
|
+
contentClarity = Math.max(0, Math.min(10, contentClarity));
|
|
5369
|
+
let professionalPolish = 6;
|
|
5370
|
+
const polishNotes = [];
|
|
5371
|
+
const titleFontSize = parseFloat(slideData.titleFontSize || "0");
|
|
5372
|
+
if (titleFontSize >= 40) {
|
|
5373
|
+
professionalPolish += 2;
|
|
5374
|
+
polishNotes.push("Strong title typography");
|
|
5375
|
+
} else if (titleFontSize < 24) {
|
|
5376
|
+
professionalPolish -= 1;
|
|
5377
|
+
polishNotes.push("Title could be more prominent");
|
|
5378
|
+
}
|
|
5379
|
+
if (slideData.contentLength > 10 && slideData.contentLength < 200) {
|
|
5380
|
+
professionalPolish += 1;
|
|
5381
|
+
polishNotes.push("Well-balanced content");
|
|
5382
|
+
}
|
|
5383
|
+
const polishedTypes = [
|
|
5384
|
+
"big-number",
|
|
5385
|
+
"metrics-grid",
|
|
5386
|
+
"three-column",
|
|
5387
|
+
"three-points",
|
|
5388
|
+
"comparison",
|
|
5389
|
+
"timeline",
|
|
5390
|
+
"process",
|
|
5391
|
+
"cta",
|
|
5392
|
+
"title",
|
|
5393
|
+
"thank-you"
|
|
5394
|
+
];
|
|
5395
|
+
if (polishedTypes.some((t) => slideType.includes(t))) {
|
|
5396
|
+
professionalPolish += 1;
|
|
5397
|
+
polishNotes.push("Well-structured layout");
|
|
5398
|
+
}
|
|
5399
|
+
professionalPolish = Math.max(0, Math.min(10, professionalPolish));
|
|
5400
|
+
let themeCoherence = 7;
|
|
5401
|
+
const coherenceNotes = [];
|
|
5402
|
+
if (slideData.classList.some((c) => c.includes("slide-"))) {
|
|
5403
|
+
themeCoherence += 1;
|
|
5404
|
+
coherenceNotes.push("Has slide type class");
|
|
5405
|
+
}
|
|
5406
|
+
themeCoherence = Math.max(0, Math.min(10, themeCoherence));
|
|
5407
|
+
const totalScore = visualImpact + contentClarity + professionalPolish + themeCoherence;
|
|
4544
5408
|
return {
|
|
4545
|
-
|
|
4546
|
-
|
|
4547
|
-
|
|
4548
|
-
|
|
5409
|
+
slideIndex,
|
|
5410
|
+
slideType,
|
|
5411
|
+
visualImpact,
|
|
5412
|
+
visualImpactNotes: visualNotes.join("; ") || "Standard",
|
|
5413
|
+
contentClarity,
|
|
5414
|
+
contentClarityNotes: clarityNotes.join("; ") || "Good",
|
|
5415
|
+
professionalPolish,
|
|
5416
|
+
professionalPolishNotes: polishNotes.join("; ") || "Acceptable",
|
|
5417
|
+
themeCoherence,
|
|
5418
|
+
themeCoherenceNotes: coherenceNotes.join("; ") || "Consistent",
|
|
5419
|
+
totalScore,
|
|
5420
|
+
screenshotPath
|
|
4549
5421
|
};
|
|
4550
5422
|
}
|
|
4551
|
-
|
|
4552
|
-
|
|
4553
|
-
|
|
4554
|
-
|
|
4555
|
-
|
|
4556
|
-
|
|
4557
|
-
|
|
4558
|
-
|
|
4559
|
-
|
|
4560
|
-
|
|
4561
|
-
|
|
4562
|
-
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
|
|
4567
|
-
|
|
4568
|
-
|
|
4569
|
-
|
|
4570
|
-
|
|
4571
|
-
|
|
4572
|
-
|
|
4573
|
-
|
|
4574
|
-
|
|
4575
|
-
result.description = `No auto-fix available for ${issue.dimension}`;
|
|
4576
|
-
return result;
|
|
4577
|
-
}
|
|
5423
|
+
inferSlideType(slideData) {
|
|
5424
|
+
const classList = slideData.classList || [];
|
|
5425
|
+
for (const cls of classList) {
|
|
5426
|
+
if (cls.includes("metrics-grid")) return "metrics-grid";
|
|
5427
|
+
if (cls.includes("three-column")) return "three-column";
|
|
5428
|
+
if (cls.includes("three-points")) return "three-points";
|
|
5429
|
+
if (cls.includes("two-column")) return "two-column";
|
|
5430
|
+
if (cls.includes("big-number")) return "big-number";
|
|
5431
|
+
if (cls.includes("comparison")) return "comparison";
|
|
5432
|
+
if (cls.includes("timeline")) return "timeline";
|
|
5433
|
+
if (cls.includes("process")) return "process";
|
|
5434
|
+
if (cls.includes("quote")) return "quote";
|
|
5435
|
+
if (cls.includes("testimonial")) return "testimonial";
|
|
5436
|
+
if (cls.includes("cta")) return "cta";
|
|
5437
|
+
if (cls.includes("thank-you")) return "thank-you";
|
|
5438
|
+
if (cls.includes("title")) return "title";
|
|
5439
|
+
if (cls.includes("bullet")) return "bullet-points";
|
|
5440
|
+
if (cls.includes("single-statement")) return "single-statement";
|
|
5441
|
+
if (cls.includes("agenda")) return "agenda";
|
|
5442
|
+
}
|
|
5443
|
+
if (!slideData.body && slideData.bullets.length > 0) return "bullet-points";
|
|
5444
|
+
if (slideData.body && !slideData.bullets.length) return "content";
|
|
5445
|
+
if (slideData.hasChart) return "data-visualization";
|
|
5446
|
+
return "standard";
|
|
4578
5447
|
}
|
|
4579
|
-
|
|
4580
|
-
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
|
|
4585
|
-
|
|
4586
|
-
const
|
|
4587
|
-
if (!
|
|
4588
|
-
|
|
4589
|
-
|
|
4590
|
-
|
|
4591
|
-
|
|
4592
|
-
|
|
4593
|
-
|
|
4594
|
-
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
|
|
4598
|
-
|
|
4599
|
-
|
|
4600
|
-
|
|
4601
|
-
|
|
4602
|
-
|
|
4603
|
-
|
|
4604
|
-
|
|
4605
|
-
|
|
5448
|
+
// ===========================================================================
|
|
5449
|
+
// PRESENTATION-LEVEL EVALUATION
|
|
5450
|
+
// ===========================================================================
|
|
5451
|
+
evaluateNarrativeFlow(slideScores) {
|
|
5452
|
+
let score = 25;
|
|
5453
|
+
const notes = [];
|
|
5454
|
+
const firstSlide = slideScores[0];
|
|
5455
|
+
const hasStrongOpening = Boolean(firstSlide && (firstSlide.slideType === "title" || firstSlide.visualImpact >= 7));
|
|
5456
|
+
if (!hasStrongOpening) {
|
|
5457
|
+
score -= 5;
|
|
5458
|
+
notes.push("Opening could be stronger");
|
|
5459
|
+
}
|
|
5460
|
+
const middleSlides = slideScores.slice(1, -1);
|
|
5461
|
+
const highImpactMiddle = middleSlides.filter((s) => s.visualImpact >= 7).length;
|
|
5462
|
+
const hasCompellingMiddle = highImpactMiddle >= middleSlides.length * 0.3;
|
|
5463
|
+
if (!hasCompellingMiddle) {
|
|
5464
|
+
score -= 7;
|
|
5465
|
+
notes.push("Middle section needs more visual variety");
|
|
5466
|
+
}
|
|
5467
|
+
const lastSlide = slideScores[slideScores.length - 1];
|
|
5468
|
+
const secondLastSlide = slideScores[slideScores.length - 2];
|
|
5469
|
+
const closingTypes = ["cta", "thank-you", "call-to-action"];
|
|
5470
|
+
const hasMemorableClose = Boolean(
|
|
5471
|
+
lastSlide && (closingTypes.includes(lastSlide.slideType) || lastSlide.visualImpact >= 7) || secondLastSlide && closingTypes.includes(secondLastSlide.slideType)
|
|
5472
|
+
);
|
|
5473
|
+
if (!hasMemorableClose) {
|
|
5474
|
+
score -= 5;
|
|
5475
|
+
notes.push("Ending should be more memorable");
|
|
4606
5476
|
}
|
|
4607
|
-
|
|
4608
|
-
|
|
4609
|
-
|
|
4610
|
-
|
|
4611
|
-
|
|
4612
|
-
result.newValue = slide.data.bullets.length;
|
|
4613
|
-
result.applied = true;
|
|
4614
|
-
result.description = `Reduced bullets from ${result.originalValue} to ${result.newValue}`;
|
|
4615
|
-
}
|
|
5477
|
+
const slideTypes = new Set(slideScores.map((s) => s.slideType));
|
|
5478
|
+
const storyArcComplete = slideTypes.size >= 3;
|
|
5479
|
+
if (!storyArcComplete) {
|
|
5480
|
+
score -= 5;
|
|
5481
|
+
notes.push("Needs more variety in slide types");
|
|
4616
5482
|
}
|
|
4617
|
-
return
|
|
5483
|
+
return {
|
|
5484
|
+
score: Math.max(0, score),
|
|
5485
|
+
hasStrongOpening,
|
|
5486
|
+
hasCompellingMiddle,
|
|
5487
|
+
hasMemorableClose,
|
|
5488
|
+
storyArcComplete,
|
|
5489
|
+
notes: notes.join(". ") || "Good narrative structure"
|
|
5490
|
+
};
|
|
4618
5491
|
}
|
|
4619
|
-
|
|
4620
|
-
|
|
4621
|
-
|
|
4622
|
-
|
|
4623
|
-
|
|
4624
|
-
|
|
5492
|
+
evaluateVisualConsistency(slideScores) {
|
|
5493
|
+
let score = 25;
|
|
5494
|
+
const notes = [];
|
|
5495
|
+
const visualScores = slideScores.map((s) => s.visualImpact);
|
|
5496
|
+
const avgVisual = visualScores.reduce((a, b) => a + b, 0) / visualScores.length;
|
|
5497
|
+
const variance = visualScores.reduce((sum, s) => sum + Math.pow(s - avgVisual, 2), 0) / visualScores.length;
|
|
5498
|
+
const colorPaletteConsistent = variance < 4;
|
|
5499
|
+
if (!colorPaletteConsistent) {
|
|
5500
|
+
score -= 5;
|
|
5501
|
+
notes.push("Visual quality varies too much between slides");
|
|
4625
5502
|
}
|
|
4626
|
-
const
|
|
4627
|
-
|
|
4628
|
-
|
|
4629
|
-
|
|
4630
|
-
|
|
4631
|
-
|
|
4632
|
-
slide.data.keyMessage = this.condenseText(slide.data.keyMessage, maxWords);
|
|
4633
|
-
result.newValue = slide.data.keyMessage;
|
|
4634
|
-
result.applied = true;
|
|
4635
|
-
result.description = `Shortened key message to ${maxWords} words`;
|
|
4636
|
-
}
|
|
5503
|
+
const polishScores = slideScores.map((s) => s.professionalPolish);
|
|
5504
|
+
const avgPolish = polishScores.reduce((a, b) => a + b, 0) / polishScores.length;
|
|
5505
|
+
const typographyConsistent = avgPolish >= 6;
|
|
5506
|
+
if (!typographyConsistent) {
|
|
5507
|
+
score -= 5;
|
|
5508
|
+
notes.push("Typography could be more polished");
|
|
4637
5509
|
}
|
|
4638
|
-
|
|
4639
|
-
|
|
4640
|
-
|
|
4641
|
-
|
|
4642
|
-
|
|
4643
|
-
|
|
4644
|
-
slide.data.title = words.slice(0, 6).join(" ");
|
|
4645
|
-
}
|
|
4646
|
-
result.newValue = slide.data.title;
|
|
4647
|
-
result.applied = true;
|
|
4648
|
-
result.description = `Shortened title from ${originalLength} to ${slide.data.title.split(/\s+/).length} words`;
|
|
4649
|
-
}
|
|
5510
|
+
const coherenceScores = slideScores.map((s) => s.themeCoherence);
|
|
5511
|
+
const avgCoherence = coherenceScores.reduce((a, b) => a + b, 0) / coherenceScores.length;
|
|
5512
|
+
const layoutPatternsConsistent = avgCoherence >= 6;
|
|
5513
|
+
if (!layoutPatternsConsistent) {
|
|
5514
|
+
score -= 5;
|
|
5515
|
+
notes.push("Layout patterns should be more consistent");
|
|
4650
5516
|
}
|
|
4651
|
-
|
|
4652
|
-
|
|
4653
|
-
|
|
4654
|
-
|
|
4655
|
-
result.applied = true;
|
|
4656
|
-
result.description = "Removed subtitle to reduce element count";
|
|
4657
|
-
} else if (slide.data.body && slide.data.bullets && slide.data.bullets.length > 0) {
|
|
4658
|
-
delete slide.data.body;
|
|
4659
|
-
result.applied = true;
|
|
4660
|
-
result.description = "Removed body text, keeping bullets";
|
|
4661
|
-
}
|
|
4662
|
-
result.newValue = this.countElements(slide);
|
|
5517
|
+
const professionalLook = avgPolish >= 7 && avgCoherence >= 7;
|
|
5518
|
+
if (!professionalLook) {
|
|
5519
|
+
score -= 5;
|
|
5520
|
+
notes.push("Overall polish could be improved");
|
|
4663
5521
|
}
|
|
4664
|
-
return
|
|
5522
|
+
return {
|
|
5523
|
+
score: Math.max(0, score),
|
|
5524
|
+
colorPaletteConsistent,
|
|
5525
|
+
typographyConsistent,
|
|
5526
|
+
layoutPatternsConsistent,
|
|
5527
|
+
professionalLook,
|
|
5528
|
+
notes: notes.join(". ") || "Consistent visual design"
|
|
5529
|
+
};
|
|
4665
5530
|
}
|
|
4666
|
-
|
|
4667
|
-
|
|
4668
|
-
|
|
4669
|
-
|
|
4670
|
-
|
|
4671
|
-
|
|
5531
|
+
evaluateContentQuality(slideScores) {
|
|
5532
|
+
let score = 25;
|
|
5533
|
+
const notes = [];
|
|
5534
|
+
const clarityScores = slideScores.map((s) => s.contentClarity);
|
|
5535
|
+
const avgClarity = clarityScores.reduce((a, b) => a + b, 0) / clarityScores.length;
|
|
5536
|
+
const messagesAreClear = avgClarity >= 7;
|
|
5537
|
+
if (!messagesAreClear) {
|
|
5538
|
+
score -= 7;
|
|
5539
|
+
notes.push("Messages could be clearer");
|
|
5540
|
+
}
|
|
5541
|
+
const lowClarity = slideScores.filter((s) => s.contentClarity < 5).length;
|
|
5542
|
+
const appropriateDepth = lowClarity < slideScores.length * 0.2;
|
|
5543
|
+
if (!appropriateDepth) {
|
|
5544
|
+
score -= 5;
|
|
5545
|
+
notes.push("Some slides have content issues");
|
|
4672
5546
|
}
|
|
4673
|
-
const
|
|
4674
|
-
|
|
4675
|
-
|
|
4676
|
-
|
|
4677
|
-
|
|
4678
|
-
|
|
4679
|
-
|
|
4680
|
-
if (slide.data.body) {
|
|
4681
|
-
slide.data.body = this.condenseText(slide.data.body, Math.floor(targetWords * 0.5));
|
|
4682
|
-
}
|
|
4683
|
-
if (slide.data.bullets && slide.data.bullets.length > 0) {
|
|
4684
|
-
const wordsPerBullet = Math.floor(targetWords / (slide.data.bullets.length * 2));
|
|
4685
|
-
slide.data.bullets = slide.data.bullets.map((b) => this.condenseText(b, wordsPerBullet));
|
|
4686
|
-
}
|
|
4687
|
-
result.newValue = this.countWords(slide);
|
|
4688
|
-
result.applied = true;
|
|
4689
|
-
result.description = `Reduced content from ${result.originalValue} to ${result.newValue} words for better whitespace`;
|
|
5547
|
+
const overloadedSlides = slideScores.filter(
|
|
5548
|
+
(s) => s.contentClarityNotes.includes("too long") || s.visualImpactNotes.includes("Too much")
|
|
5549
|
+
).length;
|
|
5550
|
+
const noOverload = overloadedSlides === 0;
|
|
5551
|
+
if (!noOverload) {
|
|
5552
|
+
score -= 5;
|
|
5553
|
+
notes.push(`${overloadedSlides} slides have too much content`);
|
|
4690
5554
|
}
|
|
4691
|
-
|
|
4692
|
-
|
|
4693
|
-
|
|
4694
|
-
|
|
4695
|
-
|
|
4696
|
-
|
|
4697
|
-
|
|
4698
|
-
let fixedCount = 0;
|
|
4699
|
-
for (const slide of slides) {
|
|
4700
|
-
if (slide.data.title) {
|
|
4701
|
-
const original = slide.data.title;
|
|
4702
|
-
slide.data.title = this.toTitleCase(slide.data.title);
|
|
4703
|
-
if (slide.data.title !== original) fixedCount++;
|
|
4704
|
-
}
|
|
4705
|
-
}
|
|
4706
|
-
result.originalValue = "Mixed casing";
|
|
4707
|
-
result.newValue = "Title Case";
|
|
4708
|
-
result.applied = fixedCount > 0;
|
|
4709
|
-
result.description = `Applied Title Case to ${fixedCount} slide titles`;
|
|
5555
|
+
const insightSlides = slideScores.filter(
|
|
5556
|
+
(s) => s.visualImpact >= 7 && s.contentClarity >= 7
|
|
5557
|
+
).length;
|
|
5558
|
+
const actionableInsights = insightSlides >= slideScores.length * 0.3;
|
|
5559
|
+
if (!actionableInsights) {
|
|
5560
|
+
score -= 5;
|
|
5561
|
+
notes.push("Need more high-impact insight slides");
|
|
4710
5562
|
}
|
|
4711
|
-
return
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
|
|
4715
|
-
|
|
4716
|
-
|
|
4717
|
-
|
|
4718
|
-
|
|
4719
|
-
result.applied = false;
|
|
4720
|
-
return result;
|
|
5563
|
+
return {
|
|
5564
|
+
score: Math.max(0, score),
|
|
5565
|
+
messagesAreClear,
|
|
5566
|
+
appropriateDepth,
|
|
5567
|
+
noOverload,
|
|
5568
|
+
actionableInsights,
|
|
5569
|
+
notes: notes.join(". ") || "Strong content quality"
|
|
5570
|
+
};
|
|
4721
5571
|
}
|
|
4722
|
-
|
|
4723
|
-
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
|
|
4727
|
-
const
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
|
|
4731
|
-
|
|
4732
|
-
"
|
|
4733
|
-
|
|
4734
|
-
|
|
4735
|
-
|
|
4736
|
-
|
|
4737
|
-
"
|
|
4738
|
-
|
|
4739
|
-
|
|
4740
|
-
|
|
4741
|
-
|
|
4742
|
-
"
|
|
4743
|
-
"would",
|
|
4744
|
-
"could",
|
|
4745
|
-
"should",
|
|
4746
|
-
"might"
|
|
4747
|
-
]);
|
|
4748
|
-
let filtered = words.filter((w) => !fillerWords.has(w.toLowerCase()));
|
|
4749
|
-
if (filtered.length <= maxWords) {
|
|
4750
|
-
return filtered.join(" ");
|
|
4751
|
-
}
|
|
4752
|
-
const punctuation = [".", ",", ";", ":", "-"];
|
|
4753
|
-
let breakPoint = maxWords;
|
|
4754
|
-
for (let i = maxWords - 1; i >= maxWords - 5 && i >= 0; i--) {
|
|
4755
|
-
const word = filtered[i];
|
|
4756
|
-
if (word && punctuation.some((p) => word.endsWith(p))) {
|
|
4757
|
-
breakPoint = i + 1;
|
|
4758
|
-
break;
|
|
4759
|
-
}
|
|
5572
|
+
evaluateExecutiveReadiness(slideScores) {
|
|
5573
|
+
let score = 25;
|
|
5574
|
+
const notes = [];
|
|
5575
|
+
const avgVisual = slideScores.reduce((sum, s) => sum + s.visualImpact, 0) / slideScores.length;
|
|
5576
|
+
const avgPolish = slideScores.reduce((sum, s) => sum + s.professionalPolish, 0) / slideScores.length;
|
|
5577
|
+
const avgClarity = slideScores.reduce((sum, s) => sum + s.contentClarity, 0) / slideScores.length;
|
|
5578
|
+
const avgTotal = slideScores.reduce((sum, s) => sum + s.totalScore, 0) / slideScores.length;
|
|
5579
|
+
const wouldImpress = avgTotal >= 26;
|
|
5580
|
+
if (!wouldImpress) {
|
|
5581
|
+
score -= 7;
|
|
5582
|
+
notes.push("Needs more visual impact to impress");
|
|
5583
|
+
}
|
|
5584
|
+
const readyForBoardroom = avgPolish >= 6 && avgClarity >= 6;
|
|
5585
|
+
if (!readyForBoardroom) {
|
|
5586
|
+
score -= 7;
|
|
5587
|
+
notes.push("Needs more polish for executive audience");
|
|
5588
|
+
}
|
|
5589
|
+
const compelling = avgVisual >= 6;
|
|
5590
|
+
if (!compelling) {
|
|
5591
|
+
score -= 5;
|
|
5592
|
+
notes.push("Could be more visually compelling");
|
|
4760
5593
|
}
|
|
4761
|
-
|
|
4762
|
-
|
|
4763
|
-
if (!
|
|
4764
|
-
|
|
5594
|
+
const excellentSlides = slideScores.filter((s) => s.totalScore >= 30).length;
|
|
5595
|
+
const shareworthy = excellentSlides >= slideScores.length * 0.4;
|
|
5596
|
+
if (!shareworthy) {
|
|
5597
|
+
score -= 5;
|
|
5598
|
+
notes.push("Less than 40% of slides are excellent");
|
|
4765
5599
|
}
|
|
4766
|
-
return
|
|
5600
|
+
return {
|
|
5601
|
+
score: Math.max(0, score),
|
|
5602
|
+
wouldImpress,
|
|
5603
|
+
readyForBoardroom,
|
|
5604
|
+
compelling,
|
|
5605
|
+
shareworthy,
|
|
5606
|
+
notes: notes.join(". ") || "Executive-ready presentation"
|
|
5607
|
+
};
|
|
4767
5608
|
}
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
|
|
4771
|
-
|
|
4772
|
-
|
|
4773
|
-
|
|
4774
|
-
|
|
4775
|
-
|
|
4776
|
-
|
|
4777
|
-
|
|
4778
|
-
|
|
4779
|
-
|
|
4780
|
-
"
|
|
4781
|
-
"
|
|
4782
|
-
"
|
|
4783
|
-
"
|
|
4784
|
-
"
|
|
4785
|
-
|
|
4786
|
-
|
|
4787
|
-
|
|
4788
|
-
|
|
4789
|
-
|
|
4790
|
-
|
|
4791
|
-
|
|
5609
|
+
// ===========================================================================
|
|
5610
|
+
// VERDICT & SUMMARY
|
|
5611
|
+
// ===========================================================================
|
|
5612
|
+
determineVerdict(score) {
|
|
5613
|
+
if (score >= 90) return "world-class";
|
|
5614
|
+
if (score >= 80) return "professional";
|
|
5615
|
+
if (score >= 65) return "acceptable";
|
|
5616
|
+
if (score >= 50) return "needs-work";
|
|
5617
|
+
return "poor";
|
|
5618
|
+
}
|
|
5619
|
+
explainVerdict(verdict, score) {
|
|
5620
|
+
const explanations = {
|
|
5621
|
+
"world-class": `Score: ${score}/100. This presentation would impress any audience. Ready for TED, boardrooms, and high-stakes pitches.`,
|
|
5622
|
+
"professional": `Score: ${score}/100. Solid professional presentation. Ready for most business contexts with minor polish.`,
|
|
5623
|
+
"acceptable": `Score: ${score}/100. Meets basic standards but lacks the polish and impact of world-class work.`,
|
|
5624
|
+
"needs-work": `Score: ${score}/100. Significant improvements needed in visual design, content clarity, or structure.`,
|
|
5625
|
+
"poor": `Score: ${score}/100. Fundamental issues with content, design, or structure. Major rework required.`
|
|
5626
|
+
};
|
|
5627
|
+
return explanations[verdict];
|
|
5628
|
+
}
|
|
5629
|
+
extractTopIssuesAndStrengths(slideScores) {
|
|
5630
|
+
const issues = [];
|
|
5631
|
+
const strengths = [];
|
|
5632
|
+
for (const slide of slideScores) {
|
|
5633
|
+
if (slide.visualImpact < 5) {
|
|
5634
|
+
issues.push(`Slide ${slide.slideIndex}: Low visual impact - ${slide.visualImpactNotes}`);
|
|
4792
5635
|
}
|
|
4793
|
-
if (
|
|
4794
|
-
|
|
5636
|
+
if (slide.contentClarity < 5) {
|
|
5637
|
+
issues.push(`Slide ${slide.slideIndex}: Clarity issue - ${slide.contentClarityNotes}`);
|
|
4795
5638
|
}
|
|
4796
|
-
|
|
4797
|
-
|
|
4798
|
-
}
|
|
4799
|
-
/**
|
|
4800
|
-
* Count words in a slide.
|
|
4801
|
-
*/
|
|
4802
|
-
countWords(slide) {
|
|
4803
|
-
let text = "";
|
|
4804
|
-
if (slide.data.title) text += slide.data.title + " ";
|
|
4805
|
-
if (slide.data.subtitle) text += slide.data.subtitle + " ";
|
|
4806
|
-
if (slide.data.body) text += slide.data.body + " ";
|
|
4807
|
-
if (slide.data.bullets) text += slide.data.bullets.join(" ") + " ";
|
|
4808
|
-
if (slide.data.keyMessage) text += slide.data.keyMessage + " ";
|
|
4809
|
-
return text.split(/\s+/).filter((w) => w.length > 0).length;
|
|
4810
|
-
}
|
|
4811
|
-
/**
|
|
4812
|
-
* Count elements in a slide.
|
|
4813
|
-
*/
|
|
4814
|
-
countElements(slide) {
|
|
4815
|
-
return (slide.data.title ? 1 : 0) + (slide.data.subtitle ? 1 : 0) + (slide.data.body ? 1 : 0) + (slide.data.bullets?.length || 0) + (slide.data.keyMessage ? 1 : 0) + (slide.data.image ? 1 : 0);
|
|
4816
|
-
}
|
|
4817
|
-
/**
|
|
4818
|
-
* Generate a summary of fixes applied.
|
|
4819
|
-
*/
|
|
4820
|
-
generateSummary(applied, skipped) {
|
|
4821
|
-
const lines = [];
|
|
4822
|
-
lines.push(`
|
|
4823
|
-
\u{1F527} Auto-Fix Summary`);
|
|
4824
|
-
lines.push(`\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`);
|
|
4825
|
-
lines.push(`Fixes Applied: ${applied.length}`);
|
|
4826
|
-
lines.push(`Fixes Skipped: ${skipped.length}`);
|
|
4827
|
-
lines.push("");
|
|
4828
|
-
if (applied.length > 0) {
|
|
4829
|
-
lines.push("\u2705 Applied Fixes:");
|
|
4830
|
-
for (const fix of applied) {
|
|
4831
|
-
lines.push(` \u2022 ${fix.description}`);
|
|
5639
|
+
if (slide.totalScore >= 35) {
|
|
5640
|
+
strengths.push(`Slide ${slide.slideIndex}: Excellent overall (${slide.slideType})`);
|
|
4832
5641
|
}
|
|
5642
|
+
}
|
|
5643
|
+
return {
|
|
5644
|
+
topIssues: issues.slice(0, 5),
|
|
5645
|
+
topStrengths: strengths.slice(0, 3)
|
|
5646
|
+
};
|
|
5647
|
+
}
|
|
5648
|
+
// ===========================================================================
|
|
5649
|
+
// FORMATTED REPORT
|
|
5650
|
+
// ===========================================================================
|
|
5651
|
+
generateReport(result) {
|
|
5652
|
+
const lines = [
|
|
5653
|
+
"\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557",
|
|
5654
|
+
"\u2551 VISUAL QUALITY EVALUATION REPORT \u2551",
|
|
5655
|
+
"\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563",
|
|
5656
|
+
"",
|
|
5657
|
+
` VERDICT: ${result.verdict.toUpperCase()}`,
|
|
5658
|
+
` ${result.verdictExplanation}`,
|
|
5659
|
+
"",
|
|
5660
|
+
"\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510",
|
|
5661
|
+
"\u2502 DIMENSION SCORES \u2502",
|
|
5662
|
+
"\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524",
|
|
5663
|
+
`\u2502 Narrative Flow: ${this.scoreBar(result.narrativeFlow.score, 25)} ${result.narrativeFlow.score}/25`,
|
|
5664
|
+
`\u2502 Visual Consistency: ${this.scoreBar(result.visualConsistency.score, 25)} ${result.visualConsistency.score}/25`,
|
|
5665
|
+
`\u2502 Content Quality: ${this.scoreBar(result.contentQuality.score, 25)} ${result.contentQuality.score}/25`,
|
|
5666
|
+
`\u2502 Executive Readiness: ${this.scoreBar(result.executiveReadiness.score, 25)} ${result.executiveReadiness.score}/25`,
|
|
5667
|
+
"\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524",
|
|
5668
|
+
`\u2502 OVERALL SCORE: ${this.scoreBar(result.overallScore, 100)} ${result.overallScore}/100`,
|
|
5669
|
+
"\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518",
|
|
5670
|
+
""
|
|
5671
|
+
];
|
|
5672
|
+
if (result.topStrengths.length > 0) {
|
|
5673
|
+
lines.push("\u2713 TOP STRENGTHS:");
|
|
5674
|
+
result.topStrengths.forEach((s) => lines.push(` \u2022 ${s}`));
|
|
4833
5675
|
lines.push("");
|
|
4834
5676
|
}
|
|
4835
|
-
if (
|
|
4836
|
-
lines.push("\
|
|
4837
|
-
|
|
4838
|
-
|
|
4839
|
-
|
|
4840
|
-
|
|
4841
|
-
|
|
4842
|
-
|
|
5677
|
+
if (result.topIssues.length > 0) {
|
|
5678
|
+
lines.push("\u2717 TOP ISSUES:");
|
|
5679
|
+
result.topIssues.forEach((i) => lines.push(` \u2022 ${i}`));
|
|
5680
|
+
lines.push("");
|
|
5681
|
+
}
|
|
5682
|
+
lines.push("\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510");
|
|
5683
|
+
lines.push("\u2502 SLIDE-BY-SLIDE BREAKDOWN \u2502");
|
|
5684
|
+
lines.push("\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524");
|
|
5685
|
+
for (const slide of result.slideScores) {
|
|
5686
|
+
const scoreColor = slide.totalScore >= 30 ? "\u2713" : slide.totalScore >= 20 ? "\u25D0" : "\u2717";
|
|
5687
|
+
lines.push(`\u2502 ${scoreColor} Slide ${slide.slideIndex.toString().padStart(2)} (${slide.slideType.padEnd(18)}): ${slide.totalScore}/40`);
|
|
5688
|
+
lines.push(`\u2502 Visual: ${slide.visualImpact}/10 Clarity: ${slide.contentClarity}/10 Polish: ${slide.professionalPolish}/10 Theme: ${slide.themeCoherence}/10`);
|
|
4843
5689
|
}
|
|
5690
|
+
lines.push("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518");
|
|
5691
|
+
lines.push("");
|
|
5692
|
+
lines.push("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D");
|
|
4844
5693
|
return lines.join("\n");
|
|
4845
5694
|
}
|
|
5695
|
+
scoreBar(score, max) {
|
|
5696
|
+
const percentage = score / max;
|
|
5697
|
+
const filled = Math.round(percentage * 20);
|
|
5698
|
+
const empty = 20 - filled;
|
|
5699
|
+
return "\u2588".repeat(filled) + "\u2591".repeat(empty);
|
|
5700
|
+
}
|
|
4846
5701
|
};
|
|
4847
|
-
function
|
|
4848
|
-
|
|
5702
|
+
async function evaluatePresentation(htmlPath, screenshotDir) {
|
|
5703
|
+
const evaluator = new VisualQualityEvaluator(screenshotDir);
|
|
5704
|
+
return evaluator.evaluate(htmlPath);
|
|
4849
5705
|
}
|
|
4850
5706
|
|
|
4851
5707
|
// src/generators/html/RevealJsGenerator.ts
|
|
@@ -5139,7 +5995,7 @@ ${slides}
|
|
|
5139
5995
|
.reveal .number {
|
|
5140
5996
|
font-size: 4em;
|
|
5141
5997
|
font-weight: 800;
|
|
5142
|
-
color: var(--color-
|
|
5998
|
+
color: var(--color-primary);
|
|
5143
5999
|
text-align: center;
|
|
5144
6000
|
}
|
|
5145
6001
|
|
|
@@ -5190,12 +6046,12 @@ ${slides}
|
|
|
5190
6046
|
.reveal .metric-value {
|
|
5191
6047
|
font-size: 2em;
|
|
5192
6048
|
font-weight: 700;
|
|
5193
|
-
color: var(--color-
|
|
6049
|
+
color: var(--color-primary);
|
|
5194
6050
|
}
|
|
5195
6051
|
|
|
5196
6052
|
.reveal .metric-label {
|
|
5197
6053
|
font-size: 0.8em;
|
|
5198
|
-
color: var(--color-text
|
|
6054
|
+
color: var(--color-text);
|
|
5199
6055
|
}
|
|
5200
6056
|
|
|
5201
6057
|
.reveal .metric-change {
|
|
@@ -5272,27 +6128,31 @@ ${slides}
|
|
|
5272
6128
|
================================================================ */
|
|
5273
6129
|
|
|
5274
6130
|
/* Title slide: Bold background - makes strong first impression */
|
|
5275
|
-
.
|
|
5276
|
-
|
|
5277
|
-
|
|
5278
|
-
|
|
5279
|
-
|
|
5280
|
-
.reveal .slide-title
|
|
5281
|
-
.reveal .slide-title
|
|
5282
|
-
|
|
6131
|
+
/* Using .slides section.slide-title for higher specificity than .reveal .slides section */
|
|
6132
|
+
.reveal .slides section.slide-title {
|
|
6133
|
+
background-color: ${titleBg} !important;
|
|
6134
|
+
background-image: linear-gradient(135deg, ${titleBg} 0%, ${titleBgEnd} 100%);
|
|
6135
|
+
}
|
|
6136
|
+
.reveal .slides section.slide-title h1,
|
|
6137
|
+
.reveal .slides section.slide-title h2,
|
|
6138
|
+
.reveal .slides section.slide-title p,
|
|
6139
|
+
.reveal .slides section.slide-title .subtitle {
|
|
6140
|
+
color: ${isDark ? "#ffffff" : "#ffffff"} !important;
|
|
6141
|
+
-webkit-text-fill-color: ${isDark ? "#ffffff" : "#ffffff"} !important;
|
|
6142
|
+
background: none !important;
|
|
5283
6143
|
}
|
|
5284
6144
|
|
|
5285
6145
|
/* Section dividers: Subtle visual breaks */
|
|
5286
|
-
.reveal .slide-section-divider {
|
|
6146
|
+
.reveal .slides section.slide-section-divider {
|
|
5287
6147
|
background: ${isDark ? this.lightenColor(background, 8) : `linear-gradient(180deg, ${this.lightenColor(primary, 85)} 0%, ${this.lightenColor(primary, 92)} 100%)`};
|
|
5288
6148
|
}
|
|
5289
6149
|
|
|
5290
6150
|
/* Big number slides: Clean with accent highlight - emphasizes the data */
|
|
5291
|
-
.reveal .slide-big-number {
|
|
6151
|
+
.reveal .slides section.slide-big-number {
|
|
5292
6152
|
background: var(--color-background);
|
|
5293
6153
|
border-left: 8px solid var(--color-highlight);
|
|
5294
6154
|
}
|
|
5295
|
-
.reveal .slide-big-number .number {
|
|
6155
|
+
.reveal .slides section.slide-big-number .number {
|
|
5296
6156
|
font-size: 5em;
|
|
5297
6157
|
background: linear-gradient(135deg, var(--color-highlight) 0%, var(--color-accent) 100%);
|
|
5298
6158
|
-webkit-background-clip: text;
|
|
@@ -5301,78 +6161,198 @@ ${slides}
|
|
|
5301
6161
|
}
|
|
5302
6162
|
|
|
5303
6163
|
/* Metrics grid: Light accent background - data-focused */
|
|
5304
|
-
.reveal .slide-metrics-grid {
|
|
6164
|
+
.reveal .slides section.slide-metrics-grid {
|
|
5305
6165
|
background: ${isDark ? this.lightenColor(background, 8) : this.lightenColor(accent, 90)};
|
|
5306
6166
|
}
|
|
5307
|
-
${isDark ? `.reveal .slide-metrics-grid { color: ${text}; }` : ""}
|
|
6167
|
+
${isDark ? `.reveal .slides section.slide-metrics-grid { color: ${text}; }` : ""}
|
|
5308
6168
|
|
|
5309
|
-
/* CTA slide: Highlight color - drives action */
|
|
5310
|
-
.reveal .slide-cta {
|
|
5311
|
-
background:
|
|
5312
|
-
|
|
5313
|
-
|
|
5314
|
-
.reveal .slide-cta
|
|
5315
|
-
|
|
5316
|
-
|
|
5317
|
-
|
|
6169
|
+
/* CTA slide: Highlight color - drives action (darkened for 7:1 contrast with white) */
|
|
6170
|
+
.reveal .slides section.slide-cta {
|
|
6171
|
+
background-color: ${this.darkenColor(highlight, 30)} !important;
|
|
6172
|
+
background-image: linear-gradient(135deg, ${this.darkenColor(highlight, 25)} 0%, ${this.darkenColor(accent, 30)} 100%);
|
|
6173
|
+
}
|
|
6174
|
+
.reveal .slides section.slide-cta h2,
|
|
6175
|
+
.reveal .slides section.slide-cta p {
|
|
6176
|
+
color: #ffffff !important;
|
|
6177
|
+
-webkit-text-fill-color: #ffffff !important;
|
|
6178
|
+
background: none !important;
|
|
6179
|
+
}
|
|
6180
|
+
.reveal .slides section.slide-cta .cta-button {
|
|
5318
6181
|
background: #ffffff;
|
|
5319
6182
|
color: var(--color-highlight);
|
|
5320
6183
|
}
|
|
5321
6184
|
|
|
5322
6185
|
/* Thank you slide: Matches title for bookend effect */
|
|
5323
|
-
.reveal .slide-thank-you {
|
|
5324
|
-
background:
|
|
6186
|
+
.reveal .slides section.slide-thank-you {
|
|
6187
|
+
background-color: ${titleBg} !important;
|
|
6188
|
+
background-image: linear-gradient(135deg, ${titleBg} 0%, ${titleBgEnd} 100%);
|
|
5325
6189
|
}
|
|
5326
|
-
.reveal .slide-thank-you h2,
|
|
5327
|
-
.reveal .slide-thank-you p,
|
|
5328
|
-
.reveal .slide-thank-you .subtitle {
|
|
5329
|
-
color:
|
|
6190
|
+
.reveal .slides section.slide-thank-you h2,
|
|
6191
|
+
.reveal .slides section.slide-thank-you p,
|
|
6192
|
+
.reveal .slides section.slide-thank-you .subtitle {
|
|
6193
|
+
color: #ffffff !important;
|
|
6194
|
+
-webkit-text-fill-color: #ffffff !important;
|
|
6195
|
+
background: none !important;
|
|
5330
6196
|
}
|
|
5331
6197
|
|
|
5332
6198
|
/* Quote slides: Elegant subtle background */
|
|
5333
|
-
.reveal .slide-quote {
|
|
6199
|
+
.reveal .slides section.slide-quote {
|
|
5334
6200
|
background: ${isDark ? this.lightenColor(background, 5) : this.lightenColor(secondary, 92)};
|
|
5335
6201
|
}
|
|
5336
|
-
.reveal .slide-quote blockquote {
|
|
6202
|
+
.reveal .slides section.slide-quote blockquote {
|
|
5337
6203
|
border-left-color: var(--color-accent);
|
|
5338
6204
|
font-size: 1.3em;
|
|
5339
6205
|
}
|
|
5340
|
-
${isDark ? `.reveal .slide-quote { color: ${text}; }` : ""}
|
|
6206
|
+
${isDark ? `.reveal .slides section.slide-quote { color: ${text}; }` : ""}
|
|
5341
6207
|
|
|
5342
6208
|
/* Single statement: Clean, centered, impactful */
|
|
5343
|
-
.reveal .slide-single-statement {
|
|
6209
|
+
.reveal .slides section.slide-single-statement {
|
|
5344
6210
|
background: var(--color-background);
|
|
5345
6211
|
}
|
|
5346
|
-
.reveal .slide-single-statement .statement,
|
|
5347
|
-
.reveal .slide-single-statement .big-idea-text {
|
|
6212
|
+
.reveal .slides section.slide-single-statement .statement,
|
|
6213
|
+
.reveal .slides section.slide-single-statement .big-idea-text {
|
|
5348
6214
|
font-size: 2.2em;
|
|
5349
6215
|
max-width: 80%;
|
|
5350
6216
|
margin: 0 auto;
|
|
5351
6217
|
}
|
|
5352
6218
|
|
|
5353
6219
|
/* Comparison slides: Split visual */
|
|
5354
|
-
.reveal .slide-comparison .columns
|
|
6220
|
+
.reveal .slide-comparison .columns,
|
|
6221
|
+
.reveal .slide-comparison .comparison-container {
|
|
5355
6222
|
gap: 60px;
|
|
5356
6223
|
}
|
|
5357
|
-
.reveal .slide-comparison .column:first-child
|
|
6224
|
+
.reveal .slide-comparison .column:first-child,
|
|
6225
|
+
.reveal .slide-comparison .comparison-left {
|
|
5358
6226
|
border-right: 2px solid ${this.lightenColor(secondary, 70)};
|
|
5359
6227
|
padding-right: 30px;
|
|
5360
6228
|
}
|
|
6229
|
+
.reveal .column-title {
|
|
6230
|
+
font-size: 1.3em;
|
|
6231
|
+
font-weight: 600;
|
|
6232
|
+
margin-bottom: 0.5em;
|
|
6233
|
+
color: var(--color-primary);
|
|
6234
|
+
}
|
|
6235
|
+
.reveal .column-content,
|
|
6236
|
+
.reveal .column-body {
|
|
6237
|
+
line-height: 1.6;
|
|
6238
|
+
}
|
|
5361
6239
|
|
|
5362
|
-
/* Timeline/Process: Visual flow */
|
|
6240
|
+
/* Timeline/Process: Visual flow - ENHANCED */
|
|
6241
|
+
.reveal .slide-timeline .slide-content,
|
|
6242
|
+
.reveal .slide-process .slide-content {
|
|
6243
|
+
justify-content: center;
|
|
6244
|
+
}
|
|
5363
6245
|
.reveal .slide-timeline .steps,
|
|
5364
|
-
.reveal .slide-
|
|
6246
|
+
.reveal .slide-timeline .timeline,
|
|
6247
|
+
.reveal .slide-process .steps,
|
|
6248
|
+
.reveal .slide-process .process-steps {
|
|
5365
6249
|
display: flex;
|
|
5366
|
-
gap:
|
|
6250
|
+
gap: 24px;
|
|
6251
|
+
flex-wrap: wrap;
|
|
6252
|
+
justify-content: center;
|
|
6253
|
+
align-items: stretch;
|
|
6254
|
+
margin-top: 30px;
|
|
6255
|
+
padding: 0 20px;
|
|
5367
6256
|
}
|
|
5368
6257
|
.reveal .slide-timeline .step,
|
|
5369
|
-
.reveal .slide-
|
|
5370
|
-
|
|
5371
|
-
|
|
5372
|
-
|
|
5373
|
-
|
|
5374
|
-
|
|
6258
|
+
.reveal .slide-timeline .timeline-item,
|
|
6259
|
+
.reveal .slide-process .step,
|
|
6260
|
+
.reveal .slide-process .process-step {
|
|
6261
|
+
flex: 1 1 200px;
|
|
6262
|
+
min-width: 180px;
|
|
6263
|
+
max-width: 280px;
|
|
6264
|
+
padding: 28px 24px;
|
|
6265
|
+
background: ${isDark ? this.lightenColor(background, 10) : "#ffffff"};
|
|
6266
|
+
border-radius: 12px;
|
|
6267
|
+
border-top: 5px solid var(--color-accent);
|
|
6268
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
|
5375
6269
|
${isDark ? `color: ${text};` : ""}
|
|
6270
|
+
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
|
6271
|
+
}
|
|
6272
|
+
.reveal .slide-timeline .step:hover,
|
|
6273
|
+
.reveal .slide-timeline .timeline-item:hover,
|
|
6274
|
+
.reveal .slide-process .step:hover,
|
|
6275
|
+
.reveal .slide-process .process-step:hover {
|
|
6276
|
+
transform: translateY(-4px);
|
|
6277
|
+
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.12);
|
|
6278
|
+
}
|
|
6279
|
+
.reveal .step-number {
|
|
6280
|
+
display: inline-flex;
|
|
6281
|
+
align-items: center;
|
|
6282
|
+
justify-content: center;
|
|
6283
|
+
width: 40px;
|
|
6284
|
+
height: 40px;
|
|
6285
|
+
background: var(--color-accent);
|
|
6286
|
+
color: #ffffff;
|
|
6287
|
+
border-radius: 50%;
|
|
6288
|
+
font-size: 1.2em;
|
|
6289
|
+
font-weight: 700;
|
|
6290
|
+
margin-bottom: 16px;
|
|
6291
|
+
}
|
|
6292
|
+
.reveal .step-title {
|
|
6293
|
+
font-size: 1.3em;
|
|
6294
|
+
font-weight: 600;
|
|
6295
|
+
margin-bottom: 8px;
|
|
6296
|
+
color: var(--color-primary);
|
|
6297
|
+
}
|
|
6298
|
+
.reveal .step-desc {
|
|
6299
|
+
font-size: 0.95em;
|
|
6300
|
+
line-height: 1.5;
|
|
6301
|
+
color: var(--color-text-light);
|
|
6302
|
+
}
|
|
6303
|
+
.reveal .step-arrow {
|
|
6304
|
+
display: flex;
|
|
6305
|
+
align-items: center;
|
|
6306
|
+
font-size: 1.5em;
|
|
6307
|
+
color: var(--color-accent);
|
|
6308
|
+
}
|
|
6309
|
+
|
|
6310
|
+
/* Improved slide layout - PROFESSIONAL */
|
|
6311
|
+
.reveal .slides section {
|
|
6312
|
+
overflow: hidden;
|
|
6313
|
+
}
|
|
6314
|
+
.reveal .slides section .slide-content {
|
|
6315
|
+
overflow-y: auto;
|
|
6316
|
+
overflow-x: hidden;
|
|
6317
|
+
max-height: calc(100vh - 120px);
|
|
6318
|
+
}
|
|
6319
|
+
|
|
6320
|
+
/* Three column professional styling */
|
|
6321
|
+
.reveal .three-columns .column {
|
|
6322
|
+
padding: 24px;
|
|
6323
|
+
background: ${isDark ? this.lightenColor(background, 8) : "#f8f9fa"};
|
|
6324
|
+
border-radius: 12px;
|
|
6325
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
|
6326
|
+
}
|
|
6327
|
+
.reveal .three-columns .column-title {
|
|
6328
|
+
font-size: 1.3em;
|
|
6329
|
+
color: var(--color-primary);
|
|
6330
|
+
border-bottom: 2px solid var(--color-accent);
|
|
6331
|
+
padding-bottom: 8px;
|
|
6332
|
+
margin-bottom: 12px;
|
|
6333
|
+
}
|
|
6334
|
+
|
|
6335
|
+
/* Bullets professional styling */
|
|
6336
|
+
.reveal .bullets {
|
|
6337
|
+
list-style: none;
|
|
6338
|
+
margin: 0;
|
|
6339
|
+
padding: 0;
|
|
6340
|
+
}
|
|
6341
|
+
.reveal .bullets li {
|
|
6342
|
+
position: relative;
|
|
6343
|
+
padding-left: 28px;
|
|
6344
|
+
margin-bottom: 16px;
|
|
6345
|
+
line-height: 1.5;
|
|
6346
|
+
}
|
|
6347
|
+
.reveal .bullets li::before {
|
|
6348
|
+
content: "";
|
|
6349
|
+
position: absolute;
|
|
6350
|
+
left: 0;
|
|
6351
|
+
top: 8px;
|
|
6352
|
+
width: 8px;
|
|
6353
|
+
height: 8px;
|
|
6354
|
+
background: var(--color-accent);
|
|
6355
|
+
border-radius: 50%;
|
|
5376
6356
|
}
|
|
5377
6357
|
|
|
5378
6358
|
/* Progress bar enhancement */
|
|
@@ -5610,182 +6590,6 @@ ${slides}
|
|
|
5610
6590
|
}
|
|
5611
6591
|
};
|
|
5612
6592
|
|
|
5613
|
-
// src/qa/IterativeQAEngine.ts
|
|
5614
|
-
var DEFAULT_OPTIONS = {
|
|
5615
|
-
minScore: 95,
|
|
5616
|
-
maxIterations: 5,
|
|
5617
|
-
verbose: true
|
|
5618
|
-
};
|
|
5619
|
-
var IterativeQAEngine = class {
|
|
5620
|
-
kb;
|
|
5621
|
-
scorer;
|
|
5622
|
-
fixer;
|
|
5623
|
-
generator;
|
|
5624
|
-
mode;
|
|
5625
|
-
presentationType;
|
|
5626
|
-
config;
|
|
5627
|
-
constructor(mode, presentationType, config) {
|
|
5628
|
-
this.mode = mode;
|
|
5629
|
-
this.presentationType = presentationType;
|
|
5630
|
-
this.config = config;
|
|
5631
|
-
this.scorer = new SevenDimensionScorer(mode, presentationType);
|
|
5632
|
-
this.fixer = new AutoFixEngine(mode, presentationType);
|
|
5633
|
-
this.generator = new RevealJsGenerator();
|
|
5634
|
-
}
|
|
5635
|
-
/**
|
|
5636
|
-
* Run the iterative QA process.
|
|
5637
|
-
*/
|
|
5638
|
-
async run(initialSlides, initialHtml, options = {}) {
|
|
5639
|
-
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
5640
|
-
this.kb = await getKnowledgeGateway();
|
|
5641
|
-
const iterations = [];
|
|
5642
|
-
let currentSlides = initialSlides;
|
|
5643
|
-
let currentHtml = initialHtml;
|
|
5644
|
-
let scoringResult;
|
|
5645
|
-
let autoFixSummary = "";
|
|
5646
|
-
let totalFixesApplied = 0;
|
|
5647
|
-
if (opts.verbose) {
|
|
5648
|
-
logger.progress("\n\u{1F504} Starting Iterative QA Process");
|
|
5649
|
-
logger.info(` Target Score: ${opts.minScore}/100`);
|
|
5650
|
-
logger.info(` Max Iterations: ${opts.maxIterations}`);
|
|
5651
|
-
logger.progress("");
|
|
5652
|
-
}
|
|
5653
|
-
for (let i = 0; i < opts.maxIterations; i++) {
|
|
5654
|
-
const iterationNum = i + 1;
|
|
5655
|
-
if (opts.verbose) {
|
|
5656
|
-
logger.progress(`\u{1F4CA} Iteration ${iterationNum}/${opts.maxIterations}...`);
|
|
5657
|
-
}
|
|
5658
|
-
scoringResult = await this.scorer.score(currentSlides, currentHtml, opts.minScore);
|
|
5659
|
-
iterations.push({
|
|
5660
|
-
iteration: iterationNum,
|
|
5661
|
-
score: scoringResult.overallScore,
|
|
5662
|
-
dimensionScores: {
|
|
5663
|
-
layout: scoringResult.dimensions.layout.score,
|
|
5664
|
-
contrast: scoringResult.dimensions.contrast.score,
|
|
5665
|
-
graphics: scoringResult.dimensions.graphics.score,
|
|
5666
|
-
content: scoringResult.dimensions.content.score,
|
|
5667
|
-
clarity: scoringResult.dimensions.clarity.score,
|
|
5668
|
-
effectiveness: scoringResult.dimensions.effectiveness.score,
|
|
5669
|
-
consistency: scoringResult.dimensions.consistency.score
|
|
5670
|
-
},
|
|
5671
|
-
fixesApplied: 0,
|
|
5672
|
-
timestamp: /* @__PURE__ */ new Date()
|
|
5673
|
-
});
|
|
5674
|
-
if (opts.verbose) {
|
|
5675
|
-
logger.info(` Score: ${scoringResult.overallScore}/100`);
|
|
5676
|
-
}
|
|
5677
|
-
if (scoringResult.passed) {
|
|
5678
|
-
if (opts.verbose) {
|
|
5679
|
-
logger.success(`PASSED - Score meets threshold (${opts.minScore})`);
|
|
5680
|
-
}
|
|
5681
|
-
break;
|
|
5682
|
-
}
|
|
5683
|
-
if (i < opts.maxIterations - 1) {
|
|
5684
|
-
if (opts.verbose) {
|
|
5685
|
-
logger.warn("Below threshold, applying auto-fixes...");
|
|
5686
|
-
}
|
|
5687
|
-
const fixResult = await this.fixer.fix(currentSlides, scoringResult);
|
|
5688
|
-
if (fixResult.fixesApplied.length > 0) {
|
|
5689
|
-
currentSlides = fixResult.slidesFixed;
|
|
5690
|
-
currentHtml = await this.generator.generate(currentSlides, this.config);
|
|
5691
|
-
const lastIteration = iterations[iterations.length - 1];
|
|
5692
|
-
if (lastIteration) {
|
|
5693
|
-
lastIteration.fixesApplied = fixResult.fixesApplied.length;
|
|
5694
|
-
}
|
|
5695
|
-
totalFixesApplied += fixResult.fixesApplied.length;
|
|
5696
|
-
if (opts.verbose) {
|
|
5697
|
-
logger.info(` \u{1F527} Applied ${fixResult.fixesApplied.length} fixes`);
|
|
5698
|
-
}
|
|
5699
|
-
autoFixSummary += fixResult.summary + "\n";
|
|
5700
|
-
} else {
|
|
5701
|
-
if (opts.verbose) {
|
|
5702
|
-
logger.warn("No auto-fixes available, manual review needed");
|
|
5703
|
-
}
|
|
5704
|
-
break;
|
|
5705
|
-
}
|
|
5706
|
-
}
|
|
5707
|
-
}
|
|
5708
|
-
if (!scoringResult.passed) {
|
|
5709
|
-
scoringResult = await this.scorer.score(currentSlides, currentHtml, opts.minScore);
|
|
5710
|
-
}
|
|
5711
|
-
const report = this.generateReport(
|
|
5712
|
-
scoringResult,
|
|
5713
|
-
iterations,
|
|
5714
|
-
opts,
|
|
5715
|
-
totalFixesApplied
|
|
5716
|
-
);
|
|
5717
|
-
if (opts.verbose) {
|
|
5718
|
-
logger.info(report);
|
|
5719
|
-
}
|
|
5720
|
-
return {
|
|
5721
|
-
finalScore: scoringResult.overallScore,
|
|
5722
|
-
passed: scoringResult.passed,
|
|
5723
|
-
threshold: opts.minScore,
|
|
5724
|
-
iterations,
|
|
5725
|
-
totalIterations: iterations.length,
|
|
5726
|
-
maxIterations: opts.maxIterations,
|
|
5727
|
-
slides: currentSlides,
|
|
5728
|
-
html: currentHtml,
|
|
5729
|
-
finalScoring: scoringResult,
|
|
5730
|
-
autoFixSummary,
|
|
5731
|
-
report
|
|
5732
|
-
};
|
|
5733
|
-
}
|
|
5734
|
-
/**
|
|
5735
|
-
* Generate a comprehensive report.
|
|
5736
|
-
*/
|
|
5737
|
-
generateReport(finalScoring, iterations, options, totalFixesApplied) {
|
|
5738
|
-
const lines = [];
|
|
5739
|
-
lines.push("");
|
|
5740
|
-
lines.push("\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557");
|
|
5741
|
-
lines.push("\u2551 ITERATIVE QA FINAL REPORT \u2551");
|
|
5742
|
-
lines.push("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D");
|
|
5743
|
-
lines.push("");
|
|
5744
|
-
const passStatus = finalScoring.passed ? "\u2705 PASSED" : "\u274C FAILED";
|
|
5745
|
-
lines.push(`Final Score: ${finalScoring.overallScore}/100 ${passStatus}`);
|
|
5746
|
-
lines.push(`Threshold: ${options.minScore}/100`);
|
|
5747
|
-
lines.push(`Iterations: ${iterations.length}/${options.maxIterations}`);
|
|
5748
|
-
lines.push(`Total Fixes Applied: ${totalFixesApplied}`);
|
|
5749
|
-
lines.push("");
|
|
5750
|
-
if (iterations.length > 1) {
|
|
5751
|
-
lines.push("Score Progression:");
|
|
5752
|
-
lines.push("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
5753
|
-
for (const iter of iterations) {
|
|
5754
|
-
const bar = "\u2588".repeat(Math.floor(iter.score / 10)) + "\u2591".repeat(10 - Math.floor(iter.score / 10));
|
|
5755
|
-
lines.push(` Iter ${iter.iteration}: ${bar} ${iter.score}/100 (+${iter.fixesApplied} fixes)`);
|
|
5756
|
-
}
|
|
5757
|
-
lines.push("");
|
|
5758
|
-
}
|
|
5759
|
-
lines.push(this.scorer.formatReport(finalScoring));
|
|
5760
|
-
lines.push("");
|
|
5761
|
-
lines.push("\u{1F4DA} Knowledge Base Compliance:");
|
|
5762
|
-
lines.push("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
5763
|
-
lines.push(` Mode: ${this.mode}`);
|
|
5764
|
-
lines.push(` Presentation Type: ${this.presentationType}`);
|
|
5765
|
-
lines.push(` Word Limits: ${this.mode === "keynote" ? "6-25" : "40-80"} per slide`);
|
|
5766
|
-
lines.push(` Expert Frameworks: Duarte, Reynolds, Gallo, Anderson`);
|
|
5767
|
-
lines.push("");
|
|
5768
|
-
if (!finalScoring.passed) {
|
|
5769
|
-
lines.push("\u{1F4CB} Recommendations for Manual Review:");
|
|
5770
|
-
lines.push("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
5771
|
-
const manualIssues = finalScoring.issues.filter((i) => !i.autoFixable);
|
|
5772
|
-
for (const issue of manualIssues.slice(0, 5)) {
|
|
5773
|
-
lines.push(` \u2022 ${issue.message}`);
|
|
5774
|
-
if (issue.fixSuggestion) {
|
|
5775
|
-
lines.push(` \u2192 ${issue.fixSuggestion}`);
|
|
5776
|
-
}
|
|
5777
|
-
}
|
|
5778
|
-
if (manualIssues.length > 5) {
|
|
5779
|
-
lines.push(` ... and ${manualIssues.length - 5} more issues`);
|
|
5780
|
-
}
|
|
5781
|
-
}
|
|
5782
|
-
return lines.join("\n");
|
|
5783
|
-
}
|
|
5784
|
-
};
|
|
5785
|
-
function createIterativeQAEngine(mode, presentationType, config) {
|
|
5786
|
-
return new IterativeQAEngine(mode, presentationType, config);
|
|
5787
|
-
}
|
|
5788
|
-
|
|
5789
6593
|
// src/generators/pptx/PowerPointGenerator.ts
|
|
5790
6594
|
import PptxGenJS from "pptxgenjs";
|
|
5791
6595
|
|
|
@@ -6555,11 +7359,11 @@ var PDFGenerator = class {
|
|
|
6555
7359
|
waitUntil: ["networkidle0", "domcontentloaded"]
|
|
6556
7360
|
});
|
|
6557
7361
|
await page.evaluate(() => {
|
|
6558
|
-
return new Promise((
|
|
7362
|
+
return new Promise((resolve2) => {
|
|
6559
7363
|
if (document.fonts && document.fonts.ready) {
|
|
6560
|
-
document.fonts.ready.then(() =>
|
|
7364
|
+
document.fonts.ready.then(() => resolve2());
|
|
6561
7365
|
} else {
|
|
6562
|
-
setTimeout(
|
|
7366
|
+
setTimeout(resolve2, 1e3);
|
|
6563
7367
|
}
|
|
6564
7368
|
});
|
|
6565
7369
|
});
|
|
@@ -6739,46 +7543,38 @@ var PresentationEngine = class {
|
|
|
6739
7543
|
}
|
|
6740
7544
|
let qaResults;
|
|
6741
7545
|
let score = 100;
|
|
6742
|
-
let
|
|
6743
|
-
|
|
6744
|
-
|
|
7546
|
+
let visualQAResult = null;
|
|
7547
|
+
const finalSlides = slides;
|
|
7548
|
+
const finalHtml = outputs.html;
|
|
6745
7549
|
if (!config.skipQA && outputs.html) {
|
|
6746
|
-
const threshold = config.qaThreshold ??
|
|
6747
|
-
|
|
6748
|
-
const
|
|
6749
|
-
|
|
6750
|
-
|
|
6751
|
-
|
|
6752
|
-
|
|
6753
|
-
|
|
6754
|
-
|
|
6755
|
-
);
|
|
6756
|
-
|
|
6757
|
-
|
|
6758
|
-
|
|
6759
|
-
|
|
6760
|
-
|
|
6761
|
-
score = iterativeResult.finalScore;
|
|
6762
|
-
finalSlides = iterativeResult.slides;
|
|
6763
|
-
finalHtml = iterativeResult.html;
|
|
6764
|
-
if (outputs.html) {
|
|
6765
|
-
outputs.html = finalHtml;
|
|
6766
|
-
}
|
|
6767
|
-
qaResults = this.buildQAResultsFrom7Dimension(iterativeResult);
|
|
6768
|
-
if (!iterativeResult.passed) {
|
|
6769
|
-
throw new QAFailureError(score, threshold, qaResults);
|
|
6770
|
-
}
|
|
6771
|
-
} else {
|
|
6772
|
-
logger.progress("\u{1F50D} Running QA validation (legacy mode)...");
|
|
6773
|
-
qaResults = await this.qaEngine.validate(outputs.html, {
|
|
6774
|
-
mode: config.mode,
|
|
6775
|
-
strictMode: true
|
|
6776
|
-
});
|
|
6777
|
-
score = this.scoreCalculator.calculate(qaResults);
|
|
6778
|
-
logger.info(`\u{1F4CA} QA Score: ${score}/100`);
|
|
7550
|
+
const threshold = config.qaThreshold ?? 80;
|
|
7551
|
+
logger.progress("\u{1F50D} Running Visual Quality Evaluation (Playwright)...");
|
|
7552
|
+
const tempDir = fs2.mkdtempSync(path2.join(os.tmpdir(), "presentation-qa-"));
|
|
7553
|
+
const tempHtmlPath = path2.join(tempDir, "presentation.html");
|
|
7554
|
+
fs2.writeFileSync(tempHtmlPath, outputs.html);
|
|
7555
|
+
try {
|
|
7556
|
+
const visualEvaluator = new VisualQualityEvaluator(path2.join(tempDir, "screenshots"));
|
|
7557
|
+
visualQAResult = await visualEvaluator.evaluate(tempHtmlPath);
|
|
7558
|
+
score = visualQAResult.overallScore;
|
|
7559
|
+
logger.info(`\u{1F4CA} Visual QA Score: ${score}/100 (${visualQAResult.verdict.toUpperCase()})`);
|
|
7560
|
+
logger.info(` Narrative Flow: ${visualQAResult.narrativeFlow.score}/25`);
|
|
7561
|
+
logger.info(` Visual Consistency: ${visualQAResult.visualConsistency.score}/25`);
|
|
7562
|
+
logger.info(` Content Quality: ${visualQAResult.contentQuality.score}/25`);
|
|
7563
|
+
logger.info(` Executive Readiness: ${visualQAResult.executiveReadiness.score}/25`);
|
|
7564
|
+
qaResults = this.buildQAResultsFromVisual(visualQAResult);
|
|
6779
7565
|
if (score < threshold) {
|
|
7566
|
+
logger.warn(`\u26A0\uFE0F Score ${score} below threshold ${threshold}`);
|
|
7567
|
+
if (visualQAResult.topIssues.length > 0) {
|
|
7568
|
+
logger.warn("Top issues:");
|
|
7569
|
+
visualQAResult.topIssues.forEach((issue) => logger.warn(` \u2022 ${issue}`));
|
|
7570
|
+
}
|
|
6780
7571
|
throw new QAFailureError(score, threshold, qaResults);
|
|
6781
7572
|
}
|
|
7573
|
+
} finally {
|
|
7574
|
+
try {
|
|
7575
|
+
fs2.rmSync(tempDir, { recursive: true, force: true });
|
|
7576
|
+
} catch {
|
|
7577
|
+
}
|
|
6782
7578
|
}
|
|
6783
7579
|
} else {
|
|
6784
7580
|
qaResults = this.qaEngine.createEmptyResults();
|
|
@@ -6794,7 +7590,7 @@ var PresentationEngine = class {
|
|
|
6794
7590
|
logger.warn(`PDF generation failed (non-critical): ${errorMsg}`);
|
|
6795
7591
|
}
|
|
6796
7592
|
}
|
|
6797
|
-
const metadata = this.buildMetadata(config, analysis, finalSlides,
|
|
7593
|
+
const metadata = this.buildMetadata(config, analysis, finalSlides, visualQAResult);
|
|
6798
7594
|
return {
|
|
6799
7595
|
outputs,
|
|
6800
7596
|
qaResults,
|
|
@@ -6803,43 +7599,57 @@ var PresentationEngine = class {
|
|
|
6803
7599
|
};
|
|
6804
7600
|
}
|
|
6805
7601
|
/**
|
|
6806
|
-
* Build QA results structure from
|
|
7602
|
+
* Build QA results structure from visual quality evaluation.
|
|
6807
7603
|
*/
|
|
6808
|
-
|
|
6809
|
-
const
|
|
7604
|
+
buildQAResultsFromVisual(visualResult) {
|
|
7605
|
+
const passed = visualResult.overallScore >= 80;
|
|
6810
7606
|
return {
|
|
6811
|
-
passed
|
|
6812
|
-
score:
|
|
7607
|
+
passed,
|
|
7608
|
+
score: visualResult.overallScore,
|
|
6813
7609
|
visual: {
|
|
6814
|
-
whitespacePercentage:
|
|
6815
|
-
|
|
6816
|
-
|
|
7610
|
+
whitespacePercentage: visualResult.visualConsistency.score * 4,
|
|
7611
|
+
// Scale 25 to 100
|
|
7612
|
+
layoutBalance: visualResult.visualConsistency.professionalLook ? 1 : 0.7,
|
|
7613
|
+
colorContrast: visualResult.visualConsistency.colorPaletteConsistent ? 0.95 : 0.8
|
|
6817
7614
|
},
|
|
6818
7615
|
content: {
|
|
6819
|
-
perSlide:
|
|
6820
|
-
|
|
7616
|
+
perSlide: visualResult.slideScores.map((slide) => ({
|
|
7617
|
+
slideIndex: slide.slideIndex,
|
|
7618
|
+
wordCount: 0,
|
|
7619
|
+
// Not tracked by visual QA
|
|
7620
|
+
bulletCount: 0,
|
|
7621
|
+
withinLimit: true,
|
|
7622
|
+
hasActionTitle: slide.contentClarity >= 7,
|
|
7623
|
+
issues: slide.contentClarity < 7 ? [slide.contentClarityNotes] : []
|
|
7624
|
+
})),
|
|
7625
|
+
issues: visualResult.topIssues.map((issue) => ({
|
|
7626
|
+
severity: "warning",
|
|
7627
|
+
message: issue,
|
|
7628
|
+
dimension: "content"
|
|
7629
|
+
}))
|
|
6821
7630
|
},
|
|
6822
7631
|
accessibility: {
|
|
6823
|
-
wcagLevel:
|
|
6824
|
-
issues:
|
|
7632
|
+
wcagLevel: visualResult.visualConsistency.professionalLook ? "AA" : "A",
|
|
7633
|
+
issues: []
|
|
6825
7634
|
},
|
|
6826
|
-
issues:
|
|
6827
|
-
severity:
|
|
6828
|
-
message: issue
|
|
6829
|
-
slideIndex:
|
|
6830
|
-
dimension:
|
|
7635
|
+
issues: visualResult.topIssues.map((issue, i) => ({
|
|
7636
|
+
severity: "warning",
|
|
7637
|
+
message: issue,
|
|
7638
|
+
slideIndex: i,
|
|
7639
|
+
dimension: "visual"
|
|
6831
7640
|
})),
|
|
6832
7641
|
dimensions: {
|
|
6833
|
-
layout:
|
|
6834
|
-
contrast:
|
|
6835
|
-
graphics:
|
|
6836
|
-
content:
|
|
6837
|
-
clarity:
|
|
6838
|
-
effectiveness:
|
|
6839
|
-
consistency:
|
|
7642
|
+
layout: visualResult.narrativeFlow.score * 4,
|
|
7643
|
+
contrast: visualResult.visualConsistency.colorPaletteConsistent ? 100 : 80,
|
|
7644
|
+
graphics: visualResult.executiveReadiness.score * 4,
|
|
7645
|
+
content: visualResult.contentQuality.score * 4,
|
|
7646
|
+
clarity: visualResult.contentQuality.messagesAreClear ? 100 : 75,
|
|
7647
|
+
effectiveness: visualResult.executiveReadiness.wouldImpress ? 100 : 60,
|
|
7648
|
+
consistency: visualResult.visualConsistency.score * 4
|
|
6840
7649
|
},
|
|
6841
|
-
iterations:
|
|
6842
|
-
|
|
7650
|
+
iterations: [],
|
|
7651
|
+
// Visual QA is single-pass, no iteration history
|
|
7652
|
+
report: visualResult.verdictExplanation
|
|
6843
7653
|
};
|
|
6844
7654
|
}
|
|
6845
7655
|
/**
|
|
@@ -6925,7 +7735,7 @@ var PresentationEngine = class {
|
|
|
6925
7735
|
/**
|
|
6926
7736
|
* Build presentation metadata.
|
|
6927
7737
|
*/
|
|
6928
|
-
buildMetadata(config, analysis, slides,
|
|
7738
|
+
buildMetadata(config, analysis, slides, visualResult) {
|
|
6929
7739
|
const wordCounts = slides.map((s) => this.countWords(s));
|
|
6930
7740
|
const totalWords = wordCounts.reduce((sum, count) => sum + count, 0);
|
|
6931
7741
|
const avgWordsPerSlide = Math.round(totalWords / slides.length);
|
|
@@ -6943,10 +7753,9 @@ var PresentationEngine = class {
|
|
|
6943
7753
|
frameworks: this.detectFrameworks(analysis),
|
|
6944
7754
|
presentationType: config.presentationType || analysis.detectedType
|
|
6945
7755
|
};
|
|
6946
|
-
if (
|
|
6947
|
-
metadata.qaIterations =
|
|
6948
|
-
metadata.qaMaxIterations =
|
|
6949
|
-
metadata.dimensionScores = iterativeResult.finalScoring.dimensions;
|
|
7756
|
+
if (visualResult) {
|
|
7757
|
+
metadata.qaIterations = 1;
|
|
7758
|
+
metadata.qaMaxIterations = 1;
|
|
6950
7759
|
}
|
|
6951
7760
|
return metadata;
|
|
6952
7761
|
}
|
|
@@ -7188,7 +7997,7 @@ var UnsplashImageProvider = class {
|
|
|
7188
7997
|
};
|
|
7189
7998
|
}
|
|
7190
7999
|
delay(ms) {
|
|
7191
|
-
return new Promise((
|
|
8000
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
7192
8001
|
}
|
|
7193
8002
|
};
|
|
7194
8003
|
var CompositeImageProvider = class {
|
|
@@ -7239,16 +8048,11 @@ async function generate(config) {
|
|
|
7239
8048
|
const engine = new PresentationEngine();
|
|
7240
8049
|
return engine.generate(config);
|
|
7241
8050
|
}
|
|
7242
|
-
async function validate(
|
|
7243
|
-
const
|
|
7244
|
-
|
|
7245
|
-
const score = qaEngine.calculateScore(results);
|
|
7246
|
-
return {
|
|
7247
|
-
...results,
|
|
7248
|
-
score
|
|
7249
|
-
};
|
|
8051
|
+
async function validate(htmlPath, options) {
|
|
8052
|
+
const evaluator = new VisualQualityEvaluator(options?.screenshotDir);
|
|
8053
|
+
return evaluator.evaluate(htmlPath);
|
|
7250
8054
|
}
|
|
7251
|
-
var VERSION = "
|
|
8055
|
+
var VERSION = "9.0.0";
|
|
7252
8056
|
var index_default = {
|
|
7253
8057
|
generate,
|
|
7254
8058
|
validate,
|
|
@@ -7257,13 +8061,11 @@ var index_default = {
|
|
|
7257
8061
|
VERSION
|
|
7258
8062
|
};
|
|
7259
8063
|
export {
|
|
7260
|
-
AutoFixEngine,
|
|
7261
8064
|
ChartJsProvider,
|
|
7262
8065
|
CompositeChartProvider,
|
|
7263
8066
|
CompositeImageProvider,
|
|
7264
8067
|
ContentAnalyzer,
|
|
7265
8068
|
ContentPatternClassifier,
|
|
7266
|
-
IterativeQAEngine,
|
|
7267
8069
|
KnowledgeGateway,
|
|
7268
8070
|
LocalImageProvider,
|
|
7269
8071
|
MermaidProvider,
|
|
@@ -7275,7 +8077,6 @@ export {
|
|
|
7275
8077
|
QuickChartProvider,
|
|
7276
8078
|
RevealJsGenerator,
|
|
7277
8079
|
ScoreCalculator,
|
|
7278
|
-
SevenDimensionScorer,
|
|
7279
8080
|
SlideFactory,
|
|
7280
8081
|
SlideGenerator,
|
|
7281
8082
|
TemplateEngine,
|
|
@@ -7283,12 +8084,12 @@ export {
|
|
|
7283
8084
|
UnsplashImageProvider,
|
|
7284
8085
|
VERSION,
|
|
7285
8086
|
ValidationError,
|
|
7286
|
-
|
|
8087
|
+
VisualQualityEvaluator,
|
|
7287
8088
|
createDefaultChartProvider,
|
|
7288
8089
|
createDefaultImageProvider,
|
|
7289
|
-
createIterativeQAEngine,
|
|
7290
8090
|
createSlideFactory,
|
|
7291
8091
|
index_default as default,
|
|
8092
|
+
evaluatePresentation,
|
|
7292
8093
|
generate,
|
|
7293
8094
|
getKnowledgeGateway,
|
|
7294
8095
|
initSlideGenerator,
|