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.js
CHANGED
|
@@ -30,13 +30,11 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/index.ts
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
|
-
AutoFixEngine: () => AutoFixEngine,
|
|
34
33
|
ChartJsProvider: () => ChartJsProvider,
|
|
35
34
|
CompositeChartProvider: () => CompositeChartProvider,
|
|
36
35
|
CompositeImageProvider: () => CompositeImageProvider,
|
|
37
36
|
ContentAnalyzer: () => ContentAnalyzer,
|
|
38
37
|
ContentPatternClassifier: () => ContentPatternClassifier,
|
|
39
|
-
IterativeQAEngine: () => IterativeQAEngine,
|
|
40
38
|
KnowledgeGateway: () => KnowledgeGateway,
|
|
41
39
|
LocalImageProvider: () => LocalImageProvider,
|
|
42
40
|
MermaidProvider: () => MermaidProvider,
|
|
@@ -48,7 +46,6 @@ __export(index_exports, {
|
|
|
48
46
|
QuickChartProvider: () => QuickChartProvider,
|
|
49
47
|
RevealJsGenerator: () => RevealJsGenerator,
|
|
50
48
|
ScoreCalculator: () => ScoreCalculator,
|
|
51
|
-
SevenDimensionScorer: () => SevenDimensionScorer,
|
|
52
49
|
SlideFactory: () => SlideFactory,
|
|
53
50
|
SlideGenerator: () => SlideGenerator,
|
|
54
51
|
TemplateEngine: () => TemplateEngine,
|
|
@@ -56,12 +53,12 @@ __export(index_exports, {
|
|
|
56
53
|
UnsplashImageProvider: () => UnsplashImageProvider,
|
|
57
54
|
VERSION: () => VERSION,
|
|
58
55
|
ValidationError: () => ValidationError,
|
|
59
|
-
|
|
56
|
+
VisualQualityEvaluator: () => VisualQualityEvaluator,
|
|
60
57
|
createDefaultChartProvider: () => createDefaultChartProvider,
|
|
61
58
|
createDefaultImageProvider: () => createDefaultImageProvider,
|
|
62
|
-
createIterativeQAEngine: () => createIterativeQAEngine,
|
|
63
59
|
createSlideFactory: () => createSlideFactory,
|
|
64
60
|
default: () => index_default,
|
|
61
|
+
evaluatePresentation: () => evaluatePresentation,
|
|
65
62
|
generate: () => generate,
|
|
66
63
|
getKnowledgeGateway: () => getKnowledgeGateway,
|
|
67
64
|
initSlideGenerator: () => initSlideGenerator,
|
|
@@ -97,6 +94,11 @@ var TemplateNotFoundError = class extends Error {
|
|
|
97
94
|
}
|
|
98
95
|
};
|
|
99
96
|
|
|
97
|
+
// src/core/PresentationEngine.ts
|
|
98
|
+
var fs2 = __toESM(require("fs"));
|
|
99
|
+
var path2 = __toESM(require("path"));
|
|
100
|
+
var os = __toESM(require("os"));
|
|
101
|
+
|
|
100
102
|
// src/kb/KnowledgeGateway.ts
|
|
101
103
|
var import_fs = require("fs");
|
|
102
104
|
var import_path = require("path");
|
|
@@ -200,12 +202,12 @@ var KnowledgeGateway = class {
|
|
|
200
202
|
(0, import_path.join)(process.cwd(), "assets/presentation-knowledge.yaml"),
|
|
201
203
|
(0, import_path.join)(process.cwd(), "node_modules/claude-presentation-master/assets/presentation-knowledge.yaml")
|
|
202
204
|
];
|
|
203
|
-
for (const
|
|
205
|
+
for (const path3 of possiblePaths) {
|
|
204
206
|
try {
|
|
205
|
-
const content = (0, import_fs.readFileSync)(
|
|
207
|
+
const content = (0, import_fs.readFileSync)(path3, "utf-8");
|
|
206
208
|
this.kb = yaml.parse(content);
|
|
207
209
|
this.loaded = true;
|
|
208
|
-
logger.step(`Knowledge base loaded from ${
|
|
210
|
+
logger.step(`Knowledge base loaded from ${path3}`);
|
|
209
211
|
return;
|
|
210
212
|
} catch {
|
|
211
213
|
}
|
|
@@ -230,33 +232,54 @@ var KnowledgeGateway = class {
|
|
|
230
232
|
return "keynote";
|
|
231
233
|
}
|
|
232
234
|
/**
|
|
233
|
-
* Get word limits for the specified mode
|
|
235
|
+
* Get word limits for the specified mode.
|
|
236
|
+
* READS FROM KB - NO HARDCODED VALUES.
|
|
234
237
|
*/
|
|
235
238
|
getWordLimits(mode) {
|
|
239
|
+
this.ensureLoaded();
|
|
236
240
|
const modeConfig = this.getMode(mode);
|
|
237
|
-
const
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
return { min: 40, max: 80, ideal: 60 };
|
|
241
|
+
const rangeStr = modeConfig.characteristics.words_per_slide;
|
|
242
|
+
const match = rangeStr.match(/(\d+)-(\d+)/);
|
|
243
|
+
if (!match || !match[1] || !match[2]) {
|
|
244
|
+
throw new Error(`[KB ERROR] Invalid words_per_slide format in KB for ${mode}: "${rangeStr}". Expected "min-max" format.`);
|
|
242
245
|
}
|
|
246
|
+
const min = parseInt(match[1], 10);
|
|
247
|
+
const max = parseInt(match[2], 10);
|
|
248
|
+
const ideal = Math.round((min + max) / 2);
|
|
249
|
+
logger.debug(`[KB] getWordLimits(${mode}): min=${min}, max=${max}, ideal=${ideal} (from KB: "${rangeStr}")`);
|
|
250
|
+
return { min, max, ideal };
|
|
243
251
|
}
|
|
244
252
|
/**
|
|
245
|
-
* Get bullet point limits
|
|
253
|
+
* Get bullet point limits.
|
|
254
|
+
* READS FROM KB - NO HARDCODED VALUES.
|
|
246
255
|
*/
|
|
247
256
|
getBulletLimits(mode) {
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
257
|
+
this.ensureLoaded();
|
|
258
|
+
const type = mode === "keynote" ? "ted_keynote" : "consulting_deck";
|
|
259
|
+
const typeConfig = this.kb.presentation_types?.[type];
|
|
260
|
+
if (typeConfig?.validation_rules?.bullets_per_slide) {
|
|
261
|
+
const maxBullets = typeConfig.validation_rules.bullets_per_slide.max;
|
|
262
|
+
const wordLimits = this.getWordLimits(mode);
|
|
263
|
+
const maxWordsPerBullet = maxBullets > 0 ? Math.floor(wordLimits.max / maxBullets) : 0;
|
|
264
|
+
logger.debug(`[KB] getBulletLimits(${mode}): maxBullets=${maxBullets}, maxWordsPerBullet=${maxWordsPerBullet} (from KB presentation_types.${type})`);
|
|
265
|
+
return { maxBullets, maxWordsPerBullet };
|
|
252
266
|
}
|
|
267
|
+
throw new Error(`[KB ERROR] Missing bullets_per_slide in KB for presentation_types.${type}.validation_rules`);
|
|
253
268
|
}
|
|
254
269
|
/**
|
|
255
|
-
* Get whitespace percentage requirement
|
|
270
|
+
* Get whitespace percentage requirement.
|
|
271
|
+
* READS FROM KB - NO HARDCODED VALUES.
|
|
256
272
|
*/
|
|
257
273
|
getWhitespaceRequirement(mode) {
|
|
258
|
-
|
|
259
|
-
|
|
274
|
+
this.ensureLoaded();
|
|
275
|
+
const type = mode === "keynote" ? "ted_keynote" : "consulting_deck";
|
|
276
|
+
const typeConfig = this.kb.presentation_types?.[type];
|
|
277
|
+
if (typeConfig?.validation_rules?.whitespace?.min) {
|
|
278
|
+
const whitespace = typeConfig.validation_rules.whitespace.min / 100;
|
|
279
|
+
logger.debug(`[KB] getWhitespaceRequirement(${mode}): ${whitespace} (from KB presentation_types.${type})`);
|
|
280
|
+
return whitespace;
|
|
281
|
+
}
|
|
282
|
+
throw new Error(`[KB ERROR] Missing whitespace.min in KB for presentation_types.${type}.validation_rules`);
|
|
260
283
|
}
|
|
261
284
|
/**
|
|
262
285
|
* Get slide templates for the specified mode
|
|
@@ -367,29 +390,54 @@ var KnowledgeGateway = class {
|
|
|
367
390
|
return null;
|
|
368
391
|
}
|
|
369
392
|
/**
|
|
370
|
-
* Get Duarte's glance test requirements
|
|
393
|
+
* Get Duarte's glance test requirements.
|
|
394
|
+
* READS FROM KB - NO HARDCODED VALUES.
|
|
371
395
|
*/
|
|
372
396
|
getDuarteGlanceTest() {
|
|
373
397
|
this.ensureLoaded();
|
|
374
|
-
const duarte = this.kb.experts
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
398
|
+
const duarte = this.kb.experts?.nancy_duarte;
|
|
399
|
+
if (!duarte?.core_principles?.glance_test?.word_limit) {
|
|
400
|
+
throw new Error("[KB ERROR] Missing experts.nancy_duarte.core_principles.glance_test.word_limit in KB");
|
|
401
|
+
}
|
|
402
|
+
if (!duarte?.core_principles?.glance_test?.time_limit) {
|
|
403
|
+
throw new Error("[KB ERROR] Missing experts.nancy_duarte.core_principles.glance_test.time_limit in KB");
|
|
404
|
+
}
|
|
405
|
+
const wordLimit = duarte.core_principles.glance_test.word_limit;
|
|
406
|
+
const timeSeconds = parseInt(duarte.core_principles.glance_test.time_limit.replace(/\D/g, ""), 10);
|
|
407
|
+
logger.debug(`[KB] getDuarteGlanceTest(): wordLimit=${wordLimit}, timeSeconds=${timeSeconds} (from KB experts.nancy_duarte)`);
|
|
408
|
+
return { wordLimit, timeSeconds };
|
|
379
409
|
}
|
|
380
410
|
/**
|
|
381
|
-
* Get Miller's Law constraints
|
|
411
|
+
* Get Miller's Law constraints.
|
|
412
|
+
* READS FROM KB - NO HARDCODED VALUES.
|
|
382
413
|
*/
|
|
383
414
|
getMillersLaw() {
|
|
384
415
|
this.ensureLoaded();
|
|
385
|
-
|
|
416
|
+
const millersLaw = this.kb.cognitive_science?.millers_law;
|
|
417
|
+
if (!millersLaw?.principle) {
|
|
418
|
+
throw new Error("[KB ERROR] Missing cognitive_science.millers_law.principle in KB");
|
|
419
|
+
}
|
|
420
|
+
const match = millersLaw.principle.match(/(\d+)\s*[±+-]\s*(\d+)/);
|
|
421
|
+
if (!match) {
|
|
422
|
+
throw new Error(`[KB ERROR] Cannot parse Miller's Law from KB: "${millersLaw.principle}". Expected format: "7 \xB1 2"`);
|
|
423
|
+
}
|
|
424
|
+
const center = parseInt(match[1], 10);
|
|
425
|
+
const range = parseInt(match[2], 10);
|
|
426
|
+
const minItems = center - range;
|
|
427
|
+
const maxItems = center + range;
|
|
428
|
+
logger.debug(`[KB] getMillersLaw(): minItems=${minItems}, maxItems=${maxItems} (from KB: "${millersLaw.principle}")`);
|
|
429
|
+
return { minItems, maxItems };
|
|
386
430
|
}
|
|
387
431
|
/**
|
|
388
|
-
* Get cognitive load constraints
|
|
432
|
+
* Get cognitive load constraints.
|
|
433
|
+
* READS FROM KB - NO HARDCODED VALUES.
|
|
389
434
|
*/
|
|
390
435
|
getCognitiveLoadLimits() {
|
|
391
436
|
this.ensureLoaded();
|
|
392
|
-
|
|
437
|
+
const millers = this.getMillersLaw();
|
|
438
|
+
const maxItemsPerChunk = millers.maxItems - 2;
|
|
439
|
+
logger.debug(`[KB] getCognitiveLoadLimits(): maxItemsPerChunk=${maxItemsPerChunk} (derived from Miller's Law)`);
|
|
440
|
+
return { maxItemsPerChunk };
|
|
393
441
|
}
|
|
394
442
|
/**
|
|
395
443
|
* Get chart selection guidance
|
|
@@ -453,42 +501,40 @@ var KnowledgeGateway = class {
|
|
|
453
501
|
this.ensureLoaded();
|
|
454
502
|
const typeConfig = this.kb.presentation_types?.[type];
|
|
455
503
|
if (!typeConfig?.validation_rules) {
|
|
456
|
-
|
|
457
|
-
return mode === "keynote" ? {
|
|
458
|
-
wordsPerSlide: { min: 1, max: 25, ideal: 10 },
|
|
459
|
-
whitespace: { min: 40, ideal: 50 },
|
|
460
|
-
bulletsPerSlide: { max: 3 },
|
|
461
|
-
actionTitlesRequired: false,
|
|
462
|
-
sourcesRequired: false
|
|
463
|
-
} : {
|
|
464
|
-
wordsPerSlide: { min: 40, max: 80, ideal: 60 },
|
|
465
|
-
whitespace: { min: 25, ideal: 30 },
|
|
466
|
-
bulletsPerSlide: { max: 5 },
|
|
467
|
-
actionTitlesRequired: true,
|
|
468
|
-
sourcesRequired: true
|
|
469
|
-
};
|
|
504
|
+
throw new Error(`[KB ERROR] Missing presentation_types.${type}.validation_rules in KB. Cannot proceed without KB rules.`);
|
|
470
505
|
}
|
|
471
506
|
const rules = typeConfig.validation_rules;
|
|
472
|
-
|
|
507
|
+
if (rules.words_per_slide?.max === void 0) {
|
|
508
|
+
throw new Error(`[KB ERROR] Missing presentation_types.${type}.validation_rules.words_per_slide.max in KB`);
|
|
509
|
+
}
|
|
510
|
+
if (rules.whitespace?.min === void 0) {
|
|
511
|
+
throw new Error(`[KB ERROR] Missing presentation_types.${type}.validation_rules.whitespace.min in KB`);
|
|
512
|
+
}
|
|
513
|
+
if (rules.bullets_per_slide?.max === void 0) {
|
|
514
|
+
throw new Error(`[KB ERROR] Missing presentation_types.${type}.validation_rules.bullets_per_slide.max in KB`);
|
|
515
|
+
}
|
|
516
|
+
const result = {
|
|
473
517
|
wordsPerSlide: {
|
|
474
|
-
min: rules.words_per_slide
|
|
475
|
-
max: rules.words_per_slide
|
|
476
|
-
ideal: rules.words_per_slide
|
|
518
|
+
min: rules.words_per_slide.min,
|
|
519
|
+
max: rules.words_per_slide.max,
|
|
520
|
+
ideal: rules.words_per_slide.ideal
|
|
477
521
|
},
|
|
478
522
|
whitespace: {
|
|
479
|
-
min: rules.whitespace
|
|
480
|
-
ideal: rules.whitespace
|
|
523
|
+
min: rules.whitespace.min,
|
|
524
|
+
ideal: rules.whitespace.ideal
|
|
481
525
|
},
|
|
482
526
|
bulletsPerSlide: {
|
|
483
|
-
max: rules.bullets_per_slide
|
|
527
|
+
max: rules.bullets_per_slide.max
|
|
484
528
|
},
|
|
485
|
-
actionTitlesRequired: rules.action_titles_required
|
|
486
|
-
sourcesRequired: rules.sources_required
|
|
529
|
+
actionTitlesRequired: rules.action_titles_required,
|
|
530
|
+
sourcesRequired: rules.sources_required,
|
|
487
531
|
calloutsRequired: rules.callouts_required,
|
|
488
532
|
denseDataAllowed: rules.dense_data_allowed,
|
|
489
533
|
codeBlocksAllowed: rules.code_blocks_allowed,
|
|
490
534
|
diagramsRequired: rules.diagrams_required
|
|
491
535
|
};
|
|
536
|
+
logger.debug(`[KB] getValidationRules(${type}): words=${result.wordsPerSlide.min}-${result.wordsPerSlide.max}, bullets=${result.bulletsPerSlide.max}, whitespace=${result.whitespace.min}%`);
|
|
537
|
+
return result;
|
|
492
538
|
}
|
|
493
539
|
/**
|
|
494
540
|
* Get required elements that MUST be in the deck for this type.
|
|
@@ -598,39 +644,146 @@ var KnowledgeGateway = class {
|
|
|
598
644
|
* Map a content pattern to the best allowed slide type.
|
|
599
645
|
* CRITICAL: This is how SlideFactory decides which slide type to use.
|
|
600
646
|
*/
|
|
647
|
+
/**
|
|
648
|
+
* Map semantic slide types to structural types that SlideFactory can handle.
|
|
649
|
+
* This bridges the gap between KB's rich semantic types and code's structural types.
|
|
650
|
+
*/
|
|
651
|
+
normalizeToStructuralType(semanticType) {
|
|
652
|
+
const normalizedType = semanticType.toLowerCase().replace(/_/g, "-");
|
|
653
|
+
const structuralTypes = /* @__PURE__ */ new Set([
|
|
654
|
+
"title",
|
|
655
|
+
"agenda",
|
|
656
|
+
"thank-you",
|
|
657
|
+
"cta",
|
|
658
|
+
"single-statement",
|
|
659
|
+
"big-number",
|
|
660
|
+
"quote",
|
|
661
|
+
"bullet-points",
|
|
662
|
+
"two-column",
|
|
663
|
+
"three-column",
|
|
664
|
+
"three-points",
|
|
665
|
+
"comparison",
|
|
666
|
+
"timeline",
|
|
667
|
+
"process",
|
|
668
|
+
"metrics-grid",
|
|
669
|
+
"data-insight",
|
|
670
|
+
"full-image",
|
|
671
|
+
"screenshot",
|
|
672
|
+
"code-snippet",
|
|
673
|
+
"title-impact"
|
|
674
|
+
]);
|
|
675
|
+
if (structuralTypes.has(normalizedType)) {
|
|
676
|
+
return normalizedType;
|
|
677
|
+
}
|
|
678
|
+
const semanticToStructural = {
|
|
679
|
+
// TED Keynote semantic types
|
|
680
|
+
"star-moment": "single-statement",
|
|
681
|
+
"call-to-action": "cta",
|
|
682
|
+
// Sales pitch semantic types
|
|
683
|
+
"problem-statement": "single-statement",
|
|
684
|
+
"solution-overview": "single-statement",
|
|
685
|
+
"social-proof": "quote",
|
|
686
|
+
"testimonial": "quote",
|
|
687
|
+
"pricing": "metrics-grid",
|
|
688
|
+
"demo-screenshot": "screenshot",
|
|
689
|
+
// Consulting deck semantic types
|
|
690
|
+
"executive-summary-scr": "bullet-points",
|
|
691
|
+
"executive-summary": "bullet-points",
|
|
692
|
+
"mece-breakdown": "three-column",
|
|
693
|
+
"process-timeline": "timeline",
|
|
694
|
+
"recommendation": "single-statement",
|
|
695
|
+
"risks-mitigation": "two-column",
|
|
696
|
+
"next-steps": "bullet-points",
|
|
697
|
+
"detailed-findings": "bullet-points",
|
|
698
|
+
// Investment banking semantic types
|
|
699
|
+
"situation-overview": "bullet-points",
|
|
700
|
+
"credentials": "metrics-grid",
|
|
701
|
+
"valuation-summary": "metrics-grid",
|
|
702
|
+
"football-field": "comparison",
|
|
703
|
+
"comparable-companies": "metrics-grid",
|
|
704
|
+
"precedent-transactions": "metrics-grid",
|
|
705
|
+
"dcf-summary": "metrics-grid",
|
|
706
|
+
"waterfall-bridge": "timeline",
|
|
707
|
+
"sources-uses": "two-column",
|
|
708
|
+
"risk-factors": "bullet-points",
|
|
709
|
+
// Investor pitch semantic types
|
|
710
|
+
"problem": "single-statement",
|
|
711
|
+
"solution": "single-statement",
|
|
712
|
+
"market-size": "big-number",
|
|
713
|
+
"product": "three-points",
|
|
714
|
+
"business-model": "three-column",
|
|
715
|
+
"traction": "metrics-grid",
|
|
716
|
+
"competition": "comparison",
|
|
717
|
+
"team": "three-column",
|
|
718
|
+
"financials": "metrics-grid",
|
|
719
|
+
"ask": "single-statement",
|
|
720
|
+
// Technical presentation semantic types
|
|
721
|
+
"architecture-diagram": "full-image",
|
|
722
|
+
"data-flow": "full-image",
|
|
723
|
+
"metrics": "metrics-grid",
|
|
724
|
+
"tradeoffs": "comparison",
|
|
725
|
+
// All hands semantic types
|
|
726
|
+
"celebration": "single-statement",
|
|
727
|
+
"announcement": "single-statement",
|
|
728
|
+
"milestones": "timeline",
|
|
729
|
+
"team-recognition": "three-column",
|
|
730
|
+
"roadmap": "timeline"
|
|
731
|
+
};
|
|
732
|
+
return semanticToStructural[normalizedType] || "bullet-points";
|
|
733
|
+
}
|
|
601
734
|
mapContentPatternToSlideType(pattern, allowedTypes) {
|
|
602
735
|
const patternToTypes = {
|
|
603
|
-
big_number: ["
|
|
604
|
-
comparison: ["comparison", "
|
|
605
|
-
timeline: ["timeline", "
|
|
606
|
-
process: ["process", "
|
|
607
|
-
metrics: ["
|
|
608
|
-
quote: ["quote", "
|
|
609
|
-
code: ["
|
|
610
|
-
bullets: ["
|
|
611
|
-
prose: ["
|
|
736
|
+
big_number: ["big-number", "metrics-grid", "single-statement"],
|
|
737
|
+
comparison: ["comparison", "two-column", "three-column"],
|
|
738
|
+
timeline: ["timeline", "process", "three-points", "bullet-points"],
|
|
739
|
+
process: ["process", "timeline", "three-column", "three-points", "bullet-points"],
|
|
740
|
+
metrics: ["metrics-grid", "big-number", "three-points"],
|
|
741
|
+
quote: ["quote", "single-statement"],
|
|
742
|
+
code: ["code-snippet", "two-column"],
|
|
743
|
+
bullets: ["bullet-points", "three-points", "three-column", "two-column", "process"],
|
|
744
|
+
prose: ["single-statement", "title-impact", "two-column"]
|
|
612
745
|
};
|
|
746
|
+
if (pattern.primaryPattern === "process" || pattern.primaryPattern === "bullets") {
|
|
747
|
+
if (pattern.bulletCount === 3) {
|
|
748
|
+
const threePointTypes = ["three-points", "three-column"];
|
|
749
|
+
for (const t of threePointTypes) {
|
|
750
|
+
if (this.isTypeAllowed(t, allowedTypes)) return t;
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
if (pattern.bulletCount && pattern.bulletCount >= 2 && pattern.bulletCount <= 6) {
|
|
754
|
+
const processTypes = ["timeline", "process", "bullet-points"];
|
|
755
|
+
for (const t of processTypes) {
|
|
756
|
+
if (this.isTypeAllowed(t, allowedTypes)) return t;
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
}
|
|
613
760
|
const preferredTypes = patternToTypes[pattern.primaryPattern] || patternToTypes.prose || [];
|
|
614
761
|
for (const preferred of preferredTypes) {
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
}
|
|
621
|
-
const contentTypes = [
|
|
622
|
-
"bullet_points",
|
|
623
|
-
"bullet-points",
|
|
624
|
-
"two_column",
|
|
625
|
-
"two-column",
|
|
626
|
-
"three_column",
|
|
627
|
-
"three-column",
|
|
628
|
-
"data_insight"
|
|
629
|
-
];
|
|
762
|
+
if (this.isTypeAllowed(preferred, allowedTypes)) {
|
|
763
|
+
return preferred;
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
const contentTypes = ["bullet-points", "three-points", "single-statement"];
|
|
630
767
|
for (const ct of contentTypes) {
|
|
631
|
-
if (
|
|
768
|
+
if (this.isTypeAllowed(ct, allowedTypes)) return ct;
|
|
769
|
+
}
|
|
770
|
+
return this.normalizeToStructuralType(allowedTypes[0] ?? "bullet-points");
|
|
771
|
+
}
|
|
772
|
+
/**
|
|
773
|
+
* Check if a structural type is allowed (directly or via semantic mapping)
|
|
774
|
+
*/
|
|
775
|
+
isTypeAllowed(structuralType, allowedTypes) {
|
|
776
|
+
const normalized = structuralType.toLowerCase().replace(/_/g, "-");
|
|
777
|
+
const underscored = normalized.replace(/-/g, "_");
|
|
778
|
+
if (allowedTypes.includes(normalized) || allowedTypes.includes(underscored)) {
|
|
779
|
+
return true;
|
|
780
|
+
}
|
|
781
|
+
for (const allowed of allowedTypes) {
|
|
782
|
+
if (this.normalizeToStructuralType(allowed) === normalized) {
|
|
783
|
+
return true;
|
|
784
|
+
}
|
|
632
785
|
}
|
|
633
|
-
return
|
|
786
|
+
return false;
|
|
634
787
|
}
|
|
635
788
|
/**
|
|
636
789
|
* Get a specific slide template by name from the KB.
|
|
@@ -691,7 +844,8 @@ var KnowledgeGateway = class {
|
|
|
691
844
|
optionLabels: ["Option A", "Option B", "Option C", "Option D"]
|
|
692
845
|
},
|
|
693
846
|
column: { labelTemplate: "Point {n}" },
|
|
694
|
-
|
|
847
|
+
// Subtitle needs more room for complete phrases - at least 10 words for keynote
|
|
848
|
+
subtitle: { maxWords: mode === "keynote" ? 12 : Math.min(20, Math.floor(maxWords / 2)) },
|
|
695
849
|
context: { maxWords: Math.min(30, Math.floor(maxWords / 2)) },
|
|
696
850
|
step: { maxWords: Math.min(20, Math.floor(maxWords / 4)) },
|
|
697
851
|
columnContent: { maxWords: Math.min(25, Math.floor(maxWords / 3)) }
|
|
@@ -1046,7 +1200,14 @@ var ContentAnalyzer = class {
|
|
|
1046
1200
|
if (foundTitle && i > titleLineIndex && line.length > 0) {
|
|
1047
1201
|
if (line.startsWith("#")) break;
|
|
1048
1202
|
if (line.startsWith("-") || line.startsWith("*") || /^\d+\./.test(line)) break;
|
|
1049
|
-
|
|
1203
|
+
const cleanLine = line.replace(/\*\*/g, "").trim();
|
|
1204
|
+
if (cleanLine.length <= 120) {
|
|
1205
|
+
subtitle = cleanLine;
|
|
1206
|
+
} else {
|
|
1207
|
+
const truncated = cleanLine.slice(0, 120);
|
|
1208
|
+
const lastSpace = truncated.lastIndexOf(" ");
|
|
1209
|
+
subtitle = lastSpace > 80 ? truncated.slice(0, lastSpace) : truncated;
|
|
1210
|
+
}
|
|
1050
1211
|
break;
|
|
1051
1212
|
}
|
|
1052
1213
|
}
|
|
@@ -1169,7 +1330,60 @@ var ContentAnalyzer = class {
|
|
|
1169
1330
|
sections.push(currentSection);
|
|
1170
1331
|
}
|
|
1171
1332
|
}
|
|
1172
|
-
return sections;
|
|
1333
|
+
return this.combineParentChildSections(sections);
|
|
1334
|
+
}
|
|
1335
|
+
/**
|
|
1336
|
+
* Combine parent sections with their child sub-sections.
|
|
1337
|
+
* Detects patterns like "Three Pillars" + 3 child sections and merges them.
|
|
1338
|
+
*/
|
|
1339
|
+
combineParentChildSections(sections) {
|
|
1340
|
+
const result = [];
|
|
1341
|
+
let i = 0;
|
|
1342
|
+
while (i < sections.length) {
|
|
1343
|
+
const current = sections[i];
|
|
1344
|
+
if (current && current.level === 2 && (!current.content || current.content.length < 20) && current.bullets.length === 0) {
|
|
1345
|
+
const children = [];
|
|
1346
|
+
let j = i + 1;
|
|
1347
|
+
while (j < sections.length && children.length < 4) {
|
|
1348
|
+
const child = sections[j];
|
|
1349
|
+
if (!child || child.level !== 3) break;
|
|
1350
|
+
children.push(child);
|
|
1351
|
+
j++;
|
|
1352
|
+
}
|
|
1353
|
+
if (children.length >= 2 && children.length <= 4) {
|
|
1354
|
+
const combined = {
|
|
1355
|
+
header: current.header,
|
|
1356
|
+
level: current.level,
|
|
1357
|
+
content: "",
|
|
1358
|
+
// Will use bullets instead
|
|
1359
|
+
bullets: children.map((child) => {
|
|
1360
|
+
const title = child.header.replace(/^\d+\.\s*/, "").trim();
|
|
1361
|
+
if (child.content) {
|
|
1362
|
+
return `**${title}**: ${child.content.split("\n")[0] || ""}`;
|
|
1363
|
+
}
|
|
1364
|
+
return title;
|
|
1365
|
+
}),
|
|
1366
|
+
metrics: [
|
|
1367
|
+
...current.metrics,
|
|
1368
|
+
...children.flatMap((c) => c.metrics)
|
|
1369
|
+
],
|
|
1370
|
+
// Add sub-sections as a property for advanced slide types
|
|
1371
|
+
subSections: children.map((child) => ({
|
|
1372
|
+
title: child.header.replace(/^\d+\.\s*/, "").trim(),
|
|
1373
|
+
content: child.content
|
|
1374
|
+
}))
|
|
1375
|
+
};
|
|
1376
|
+
result.push(combined);
|
|
1377
|
+
i = j;
|
|
1378
|
+
continue;
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
if (current) {
|
|
1382
|
+
result.push(current);
|
|
1383
|
+
}
|
|
1384
|
+
i++;
|
|
1385
|
+
}
|
|
1386
|
+
return result;
|
|
1173
1387
|
}
|
|
1174
1388
|
/**
|
|
1175
1389
|
* Extract SCQA structure (Barbara Minto)
|
|
@@ -1685,6 +1899,11 @@ var ContentPatternClassifier = class {
|
|
|
1685
1899
|
}
|
|
1686
1900
|
/**
|
|
1687
1901
|
* Extract timeline/process steps from content
|
|
1902
|
+
* Handles multiple formats:
|
|
1903
|
+
* - "Step 1: Do something" → label: "Step 1", description: "Do something"
|
|
1904
|
+
* - "1. First item" → label: "1", description: "First item"
|
|
1905
|
+
* - "Title - Description text" → label: "Title", description: "Description text"
|
|
1906
|
+
* - "Plain text" → label: "1", description: "Plain text"
|
|
1688
1907
|
*/
|
|
1689
1908
|
extractSteps(section) {
|
|
1690
1909
|
const steps = [];
|
|
@@ -1695,12 +1914,28 @@ var ContentPatternClassifier = class {
|
|
|
1695
1914
|
label: stepMatch[1].trim(),
|
|
1696
1915
|
description: stepMatch[2].trim()
|
|
1697
1916
|
});
|
|
1698
|
-
|
|
1917
|
+
continue;
|
|
1918
|
+
}
|
|
1919
|
+
const dashMatch = bullet.match(/^([^-]+)\s+-\s+(.+)$/);
|
|
1920
|
+
if (dashMatch && dashMatch[1] && dashMatch[2]) {
|
|
1921
|
+
steps.push({
|
|
1922
|
+
label: dashMatch[1].trim(),
|
|
1923
|
+
description: dashMatch[2].trim()
|
|
1924
|
+
});
|
|
1925
|
+
continue;
|
|
1926
|
+
}
|
|
1927
|
+
const colonMatch = bullet.match(/^([^:]+):\s*(.+)$/);
|
|
1928
|
+
if (colonMatch && colonMatch[1] && colonMatch[2] && colonMatch[1].length < 40) {
|
|
1699
1929
|
steps.push({
|
|
1700
|
-
label:
|
|
1701
|
-
description:
|
|
1930
|
+
label: colonMatch[1].trim(),
|
|
1931
|
+
description: colonMatch[2].trim()
|
|
1702
1932
|
});
|
|
1933
|
+
continue;
|
|
1703
1934
|
}
|
|
1935
|
+
steps.push({
|
|
1936
|
+
label: `${steps.length + 1}`,
|
|
1937
|
+
description: bullet
|
|
1938
|
+
});
|
|
1704
1939
|
}
|
|
1705
1940
|
return steps;
|
|
1706
1941
|
}
|
|
@@ -1731,11 +1966,538 @@ var ContentPatternClassifier = class {
|
|
|
1731
1966
|
}
|
|
1732
1967
|
};
|
|
1733
1968
|
|
|
1969
|
+
// src/kb/ExpertGuidanceEngine.ts
|
|
1970
|
+
var ExpertGuidanceEngine = class {
|
|
1971
|
+
kb;
|
|
1972
|
+
presentationType;
|
|
1973
|
+
constructor(kb, presentationType) {
|
|
1974
|
+
this.kb = kb;
|
|
1975
|
+
this.presentationType = presentationType;
|
|
1976
|
+
}
|
|
1977
|
+
/**
|
|
1978
|
+
* Get the narrative arc for this presentation type.
|
|
1979
|
+
* Returns the story structure with required elements and their purposes.
|
|
1980
|
+
*/
|
|
1981
|
+
getNarrativeArc() {
|
|
1982
|
+
const mode = this.kb.getModeForType(this.presentationType);
|
|
1983
|
+
if (mode === "keynote") {
|
|
1984
|
+
return {
|
|
1985
|
+
framework: "Duarte Sparkline",
|
|
1986
|
+
expert: "Nancy Duarte",
|
|
1987
|
+
description: "Alternate between What Is (current reality) and What Could Be (possibility), building emotional tension to a climax, then resolve with New Bliss",
|
|
1988
|
+
elements: [
|
|
1989
|
+
{
|
|
1990
|
+
id: "opening_hook",
|
|
1991
|
+
name: "Opening Hook",
|
|
1992
|
+
purpose: "Grab attention in first 10 seconds with something unexpected",
|
|
1993
|
+
expertSource: 'Chris Anderson: "You have one chance to hook them"',
|
|
1994
|
+
designPrinciples: [
|
|
1995
|
+
"Start with a story, question, or surprising fact",
|
|
1996
|
+
"Create curiosity gap - make them NEED to know more",
|
|
1997
|
+
"Touch heart before head (Gallo)"
|
|
1998
|
+
],
|
|
1999
|
+
required: true
|
|
2000
|
+
},
|
|
2001
|
+
{
|
|
2002
|
+
id: "what_is",
|
|
2003
|
+
name: "What Is (Current Reality)",
|
|
2004
|
+
purpose: "Establish the current state that audience recognizes",
|
|
2005
|
+
expertSource: 'Nancy Duarte: "Ground them in familiar reality"',
|
|
2006
|
+
designPrinciples: [
|
|
2007
|
+
"Use concrete, recognizable situations",
|
|
2008
|
+
"Create emotional resonance with current pain",
|
|
2009
|
+
"Keep it brief - setup for contrast"
|
|
2010
|
+
],
|
|
2011
|
+
required: true
|
|
2012
|
+
},
|
|
2013
|
+
{
|
|
2014
|
+
id: "what_could_be",
|
|
2015
|
+
name: "What Could Be (Vision)",
|
|
2016
|
+
purpose: "Paint the picture of the transformed future",
|
|
2017
|
+
expertSource: 'Nancy Duarte: "Make them FEEL the possibility"',
|
|
2018
|
+
designPrinciples: [
|
|
2019
|
+
"Be specific and vivid about the future state",
|
|
2020
|
+
"Connect emotionally - how will they FEEL?",
|
|
2021
|
+
"Create contrast with What Is"
|
|
2022
|
+
],
|
|
2023
|
+
required: true
|
|
2024
|
+
},
|
|
2025
|
+
{
|
|
2026
|
+
id: "star_moment",
|
|
2027
|
+
name: "STAR Moment",
|
|
2028
|
+
purpose: "Something They'll Always Remember - the emotional climax",
|
|
2029
|
+
expertSource: 'Nancy Duarte: "One dramatic memorable moment"',
|
|
2030
|
+
designPrinciples: [
|
|
2031
|
+
"Make it unexpected and emotionally impactful",
|
|
2032
|
+
"Use a dramatic statistic, story, or demonstration",
|
|
2033
|
+
"This is what they'll tell others about",
|
|
2034
|
+
"It should give them chills or make them gasp"
|
|
2035
|
+
],
|
|
2036
|
+
required: true
|
|
2037
|
+
},
|
|
2038
|
+
{
|
|
2039
|
+
id: "call_to_action",
|
|
2040
|
+
name: "Call to Action",
|
|
2041
|
+
purpose: "Clear next step that audience can take",
|
|
2042
|
+
expertSource: 'Chris Anderson: "Give them something to DO"',
|
|
2043
|
+
designPrinciples: [
|
|
2044
|
+
"Be specific and actionable",
|
|
2045
|
+
"Make it achievable - first step, not whole journey",
|
|
2046
|
+
"Connect action to the vision you painted"
|
|
2047
|
+
],
|
|
2048
|
+
required: true
|
|
2049
|
+
},
|
|
2050
|
+
{
|
|
2051
|
+
id: "new_bliss",
|
|
2052
|
+
name: "New Bliss (Resolution)",
|
|
2053
|
+
purpose: "Leave them with the transformed future firmly in mind",
|
|
2054
|
+
expertSource: 'Nancy Duarte: "End with the new world, not the old"',
|
|
2055
|
+
designPrinciples: [
|
|
2056
|
+
"Reinforce the vision one final time",
|
|
2057
|
+
"Make them want to be part of it",
|
|
2058
|
+
"End on emotional high, not logistics"
|
|
2059
|
+
],
|
|
2060
|
+
required: false
|
|
2061
|
+
}
|
|
2062
|
+
]
|
|
2063
|
+
};
|
|
2064
|
+
} else {
|
|
2065
|
+
return {
|
|
2066
|
+
framework: "Minto Pyramid (SCQA)",
|
|
2067
|
+
expert: "Barbara Minto",
|
|
2068
|
+
description: "Lead with the answer/recommendation, then support with evidence in MECE structure",
|
|
2069
|
+
elements: [
|
|
2070
|
+
{
|
|
2071
|
+
id: "situation",
|
|
2072
|
+
name: "Situation",
|
|
2073
|
+
purpose: "Context the audience already knows and agrees with",
|
|
2074
|
+
expertSource: 'Barbara Minto: "Start from common ground"',
|
|
2075
|
+
designPrinciples: [
|
|
2076
|
+
"Facts everyone agrees on",
|
|
2077
|
+
"Recent and relevant context",
|
|
2078
|
+
"Brief - not the star of the show"
|
|
2079
|
+
],
|
|
2080
|
+
required: true
|
|
2081
|
+
},
|
|
2082
|
+
{
|
|
2083
|
+
id: "complication",
|
|
2084
|
+
name: "Complication",
|
|
2085
|
+
purpose: "The problem, threat, or opportunity that requires action",
|
|
2086
|
+
expertSource: 'Barbara Minto: "Why are we here?"',
|
|
2087
|
+
designPrinciples: [
|
|
2088
|
+
"Clear articulation of the challenge",
|
|
2089
|
+
"Why the status quo is unacceptable",
|
|
2090
|
+
"Creates urgency for the answer"
|
|
2091
|
+
],
|
|
2092
|
+
required: true
|
|
2093
|
+
},
|
|
2094
|
+
{
|
|
2095
|
+
id: "answer",
|
|
2096
|
+
name: "Answer/Recommendation",
|
|
2097
|
+
purpose: "The core recommendation - upfront, not buried",
|
|
2098
|
+
expertSource: 'Barbara Minto: "Answer first, evidence second"',
|
|
2099
|
+
designPrinciples: [
|
|
2100
|
+
"Clear, actionable recommendation",
|
|
2101
|
+
"Use an action title that states the conclusion",
|
|
2102
|
+
"Executive should understand even if they stop here"
|
|
2103
|
+
],
|
|
2104
|
+
required: true
|
|
2105
|
+
},
|
|
2106
|
+
{
|
|
2107
|
+
id: "evidence",
|
|
2108
|
+
name: "Supporting Evidence",
|
|
2109
|
+
purpose: "MECE support for the recommendation",
|
|
2110
|
+
expertSource: 'Barbara Minto: "Mutually Exclusive, Collectively Exhaustive"',
|
|
2111
|
+
designPrinciples: [
|
|
2112
|
+
"Group into 3-5 supporting pillars",
|
|
2113
|
+
"Each pillar has sub-evidence",
|
|
2114
|
+
"No gaps, no overlaps (MECE)",
|
|
2115
|
+
"Use action titles throughout"
|
|
2116
|
+
],
|
|
2117
|
+
required: true
|
|
2118
|
+
},
|
|
2119
|
+
{
|
|
2120
|
+
id: "next_steps",
|
|
2121
|
+
name: "Next Steps",
|
|
2122
|
+
purpose: "Clear actions with owners and timelines",
|
|
2123
|
+
expertSource: 'Analyst Academy: "End with who does what by when"',
|
|
2124
|
+
designPrinciples: [
|
|
2125
|
+
'Specific actions, not vague "follow up"',
|
|
2126
|
+
"Named owners where possible",
|
|
2127
|
+
"Realistic timelines"
|
|
2128
|
+
],
|
|
2129
|
+
required: true
|
|
2130
|
+
}
|
|
2131
|
+
]
|
|
2132
|
+
};
|
|
2133
|
+
}
|
|
2134
|
+
}
|
|
2135
|
+
/**
|
|
2136
|
+
* Get expert guidance for designing a specific type of slide.
|
|
2137
|
+
* This returns PRINCIPLES for excellence, not constraints.
|
|
2138
|
+
*/
|
|
2139
|
+
getSlideDesignGuidance(slideType) {
|
|
2140
|
+
const mode = this.kb.getModeForType(this.presentationType);
|
|
2141
|
+
switch (slideType) {
|
|
2142
|
+
case "title":
|
|
2143
|
+
case "title_impact":
|
|
2144
|
+
return {
|
|
2145
|
+
purpose: "Create immediate emotional connection and establish the one idea worth spreading",
|
|
2146
|
+
principles: [
|
|
2147
|
+
{
|
|
2148
|
+
expert: "Chris Anderson",
|
|
2149
|
+
principle: "One Idea Worth Spreading",
|
|
2150
|
+
application: "The title should encapsulate a single powerful idea that the audience will remember",
|
|
2151
|
+
technique: "Ask: If they remember ONE thing, what should it be? That's your title."
|
|
2152
|
+
},
|
|
2153
|
+
{
|
|
2154
|
+
expert: "Carmine Gallo",
|
|
2155
|
+
principle: "Headline Test",
|
|
2156
|
+
application: "Title should work as a Twitter headline - compelling in under 280 characters",
|
|
2157
|
+
technique: "Write 10 versions, pick the one that makes you want to hear more"
|
|
2158
|
+
},
|
|
2159
|
+
{
|
|
2160
|
+
expert: "Nancy Duarte",
|
|
2161
|
+
principle: "Glance Test",
|
|
2162
|
+
application: "Audience should grasp the essence in 3 seconds",
|
|
2163
|
+
technique: "Show it to someone for 3 seconds, ask what they remember"
|
|
2164
|
+
}
|
|
2165
|
+
],
|
|
2166
|
+
whatMakesItGreat: [
|
|
2167
|
+
"Creates curiosity - makes audience WANT to hear more",
|
|
2168
|
+
"Emotionally resonant - connects to something they care about",
|
|
2169
|
+
"Unexpected angle - not the obvious framing",
|
|
2170
|
+
"Memorable - they could repeat it to someone else"
|
|
2171
|
+
],
|
|
2172
|
+
antiPatterns: [
|
|
2173
|
+
'Generic topic labels ("Q4 Update")',
|
|
2174
|
+
"Long sentences that require reading",
|
|
2175
|
+
"Jargon or acronyms",
|
|
2176
|
+
"Multiple ideas competing for attention"
|
|
2177
|
+
],
|
|
2178
|
+
questions: [
|
|
2179
|
+
"Would someone share this title with a colleague?",
|
|
2180
|
+
"Does it create a curiosity gap?",
|
|
2181
|
+
"Can you understand the value in 3 seconds?",
|
|
2182
|
+
"Is there emotional resonance?"
|
|
2183
|
+
]
|
|
2184
|
+
};
|
|
2185
|
+
case "single_statement":
|
|
2186
|
+
case "big_idea":
|
|
2187
|
+
return {
|
|
2188
|
+
purpose: "Communicate ONE powerful insight that shifts perspective",
|
|
2189
|
+
principles: [
|
|
2190
|
+
{
|
|
2191
|
+
expert: "Garr Reynolds",
|
|
2192
|
+
principle: "Signal to Noise",
|
|
2193
|
+
application: "Everything on the slide should serve the one message. Eliminate ruthlessly.",
|
|
2194
|
+
technique: "For each element, ask: Does this AMPLIFY the message or distract from it?"
|
|
2195
|
+
},
|
|
2196
|
+
{
|
|
2197
|
+
expert: "Garr Reynolds",
|
|
2198
|
+
principle: "Amplification Through Simplification",
|
|
2199
|
+
application: "Empty space makes the remaining content more powerful",
|
|
2200
|
+
technique: "Try removing 50% of what's there. Is it clearer?"
|
|
2201
|
+
},
|
|
2202
|
+
{
|
|
2203
|
+
expert: "Nancy Duarte",
|
|
2204
|
+
principle: "Audience Paradox",
|
|
2205
|
+
application: "They will read OR listen, never both. Design for one.",
|
|
2206
|
+
technique: "If presenting live, the statement should be short enough to absorb while listening"
|
|
2207
|
+
}
|
|
2208
|
+
],
|
|
2209
|
+
whatMakesItGreat: [
|
|
2210
|
+
"One clear insight that shifts thinking",
|
|
2211
|
+
"Ample whitespace that emphasizes the message",
|
|
2212
|
+
"Words are carefully chosen - each one earns its place",
|
|
2213
|
+
"Can be absorbed in a glance"
|
|
2214
|
+
],
|
|
2215
|
+
antiPatterns: [
|
|
2216
|
+
"Cramming multiple points onto one slide",
|
|
2217
|
+
'Adding "supporting" text that dilutes the message',
|
|
2218
|
+
"Decorative elements that don't serve the idea",
|
|
2219
|
+
"Topic labels instead of insights"
|
|
2220
|
+
],
|
|
2221
|
+
questions: [
|
|
2222
|
+
"What is the ONE thing this slide should communicate?",
|
|
2223
|
+
"If I remove this word/element, is the message clearer?",
|
|
2224
|
+
"Will this shift how they think about the topic?"
|
|
2225
|
+
]
|
|
2226
|
+
};
|
|
2227
|
+
case "star_moment":
|
|
2228
|
+
return {
|
|
2229
|
+
purpose: "Create the moment they'll remember forever and tell others about",
|
|
2230
|
+
principles: [
|
|
2231
|
+
{
|
|
2232
|
+
expert: "Nancy Duarte",
|
|
2233
|
+
principle: "STAR Moment",
|
|
2234
|
+
application: "Something They'll Always Remember - this is the emotional climax",
|
|
2235
|
+
technique: "What would make the audience gasp, laugh, or get chills?"
|
|
2236
|
+
},
|
|
2237
|
+
{
|
|
2238
|
+
expert: "Chip & Dan Heath",
|
|
2239
|
+
principle: "Unexpected",
|
|
2240
|
+
application: "Break a pattern to get attention. Violate expectations.",
|
|
2241
|
+
technique: "What assumption can you shatter? What surprise can you deliver?"
|
|
2242
|
+
}
|
|
2243
|
+
],
|
|
2244
|
+
whatMakesItGreat: [
|
|
2245
|
+
"Creates visceral emotional response",
|
|
2246
|
+
"Breaks expectations - genuinely surprising",
|
|
2247
|
+
"Connects logically to the core message",
|
|
2248
|
+
"Memorable enough to retell"
|
|
2249
|
+
],
|
|
2250
|
+
antiPatterns: [
|
|
2251
|
+
"Playing it safe with expected information",
|
|
2252
|
+
"Burying the dramatic element in other content",
|
|
2253
|
+
"Gimmicks that don't connect to the message"
|
|
2254
|
+
],
|
|
2255
|
+
questions: [
|
|
2256
|
+
"Will this give them chills or make them gasp?",
|
|
2257
|
+
"Would they tell someone about this tomorrow?",
|
|
2258
|
+
"Does it support or distract from the core message?"
|
|
2259
|
+
]
|
|
2260
|
+
};
|
|
2261
|
+
case "big_number":
|
|
2262
|
+
case "data_insight":
|
|
2263
|
+
return {
|
|
2264
|
+
purpose: "Make data memorable by providing context that reveals meaning",
|
|
2265
|
+
principles: [
|
|
2266
|
+
{
|
|
2267
|
+
expert: "Edward Tufte",
|
|
2268
|
+
principle: "Data-Ink Ratio",
|
|
2269
|
+
application: "Maximize ink used for data, minimize everything else",
|
|
2270
|
+
technique: "Remove all decoration. Is the data still clear? Good. Now make it beautiful."
|
|
2271
|
+
},
|
|
2272
|
+
{
|
|
2273
|
+
expert: "Lea Pica",
|
|
2274
|
+
principle: "One Insight Per Chart",
|
|
2275
|
+
application: "Don't let data speak for itself - tell them exactly what to see",
|
|
2276
|
+
technique: "What's the ONE conclusion? Make that the headline."
|
|
2277
|
+
},
|
|
2278
|
+
{
|
|
2279
|
+
expert: "Cole Knaflic",
|
|
2280
|
+
principle: "Context Creates Meaning",
|
|
2281
|
+
application: "Numbers mean nothing without context",
|
|
2282
|
+
technique: "Compare to something they understand - time, money, scale"
|
|
2283
|
+
}
|
|
2284
|
+
],
|
|
2285
|
+
whatMakesItGreat: [
|
|
2286
|
+
"The number is HUGE and simple",
|
|
2287
|
+
"Context makes the number meaningful",
|
|
2288
|
+
"Audience immediately understands significance",
|
|
2289
|
+
"One clear insight, not a data dump"
|
|
2290
|
+
],
|
|
2291
|
+
antiPatterns: [
|
|
2292
|
+
"Multiple competing numbers",
|
|
2293
|
+
'Data without context ("$5M" - is that good?)',
|
|
2294
|
+
"Complex charts when simple would work",
|
|
2295
|
+
"Decorative elements that distract from data"
|
|
2296
|
+
],
|
|
2297
|
+
questions: [
|
|
2298
|
+
"What is the ONE insight this data reveals?",
|
|
2299
|
+
"What context makes this number meaningful?",
|
|
2300
|
+
"Can someone understand the significance in 3 seconds?"
|
|
2301
|
+
]
|
|
2302
|
+
};
|
|
2303
|
+
case "three_points":
|
|
2304
|
+
case "three_column":
|
|
2305
|
+
return {
|
|
2306
|
+
purpose: "Present key messages in memorable groups of three",
|
|
2307
|
+
principles: [
|
|
2308
|
+
{
|
|
2309
|
+
expert: "Carmine Gallo",
|
|
2310
|
+
principle: "Rule of Three",
|
|
2311
|
+
application: "Three is the magic number for memory. Not two, not four.",
|
|
2312
|
+
technique: "Can you group your content into exactly three main points?"
|
|
2313
|
+
},
|
|
2314
|
+
{
|
|
2315
|
+
expert: "Barbara Minto",
|
|
2316
|
+
principle: "MECE",
|
|
2317
|
+
application: "Points should be Mutually Exclusive (no overlap) and Collectively Exhaustive (no gaps)",
|
|
2318
|
+
technique: "Do these three points cover everything without repeating?"
|
|
2319
|
+
}
|
|
2320
|
+
],
|
|
2321
|
+
whatMakesItGreat: [
|
|
2322
|
+
"Exactly three points - no more, no less",
|
|
2323
|
+
"Each point is substantively different",
|
|
2324
|
+
"Together they tell a complete story",
|
|
2325
|
+
"Each point is memorable on its own"
|
|
2326
|
+
],
|
|
2327
|
+
antiPatterns: [
|
|
2328
|
+
"Forcing content into three when it should be two or four",
|
|
2329
|
+
"Overlapping points that aren't distinct",
|
|
2330
|
+
"Generic labels instead of insights",
|
|
2331
|
+
"Uneven importance across points"
|
|
2332
|
+
],
|
|
2333
|
+
questions: [
|
|
2334
|
+
"Are these three truly distinct and non-overlapping?",
|
|
2335
|
+
"Do they together cover the complete picture?",
|
|
2336
|
+
"Is each point valuable on its own?"
|
|
2337
|
+
]
|
|
2338
|
+
};
|
|
2339
|
+
case "call_to_action":
|
|
2340
|
+
case "cta":
|
|
2341
|
+
return {
|
|
2342
|
+
purpose: "Give the audience a clear, compelling next step they can take",
|
|
2343
|
+
principles: [
|
|
2344
|
+
{
|
|
2345
|
+
expert: "Chris Anderson",
|
|
2346
|
+
principle: "Gift Giving",
|
|
2347
|
+
application: "Your job is to give them something valuable they can USE",
|
|
2348
|
+
technique: "What can they DO tomorrow because of this presentation?"
|
|
2349
|
+
},
|
|
2350
|
+
{
|
|
2351
|
+
expert: "Robert Cialdini",
|
|
2352
|
+
principle: "Commitment",
|
|
2353
|
+
application: "Small commitments lead to larger ones",
|
|
2354
|
+
technique: "What's the smallest first step that gets them moving?"
|
|
2355
|
+
}
|
|
2356
|
+
],
|
|
2357
|
+
whatMakesItGreat: [
|
|
2358
|
+
'Specific and actionable - not vague "learn more"',
|
|
2359
|
+
"Achievable first step, not overwhelming",
|
|
2360
|
+
"Clearly connected to the value proposition",
|
|
2361
|
+
"Creates urgency without being pushy"
|
|
2362
|
+
],
|
|
2363
|
+
antiPatterns: [
|
|
2364
|
+
'Vague calls to action ("contact us")',
|
|
2365
|
+
"Multiple competing CTAs",
|
|
2366
|
+
"Actions that feel like too much work",
|
|
2367
|
+
"Disconnected from the presentation content"
|
|
2368
|
+
],
|
|
2369
|
+
questions: [
|
|
2370
|
+
"Can they do this tomorrow?",
|
|
2371
|
+
"Is this the logical first step?",
|
|
2372
|
+
"Does it connect to the value you promised?"
|
|
2373
|
+
]
|
|
2374
|
+
};
|
|
2375
|
+
default:
|
|
2376
|
+
return {
|
|
2377
|
+
purpose: "Communicate one clear message that advances the narrative",
|
|
2378
|
+
principles: [
|
|
2379
|
+
{
|
|
2380
|
+
expert: "Garr Reynolds",
|
|
2381
|
+
principle: "Signal to Noise",
|
|
2382
|
+
application: "Maximize signal (valuable content), minimize noise (distractions)",
|
|
2383
|
+
technique: "For every element, ask: Does this serve the message?"
|
|
2384
|
+
},
|
|
2385
|
+
{
|
|
2386
|
+
expert: "Nancy Duarte",
|
|
2387
|
+
principle: "Glance Test",
|
|
2388
|
+
application: "Key message should be clear in 3 seconds",
|
|
2389
|
+
technique: "What would someone remember after a 3-second glance?"
|
|
2390
|
+
}
|
|
2391
|
+
],
|
|
2392
|
+
whatMakesItGreat: [
|
|
2393
|
+
"One clear message per slide",
|
|
2394
|
+
"Visual hierarchy guides the eye",
|
|
2395
|
+
"Every element serves a purpose",
|
|
2396
|
+
"Can be understood at a glance"
|
|
2397
|
+
],
|
|
2398
|
+
antiPatterns: [
|
|
2399
|
+
"Multiple competing messages",
|
|
2400
|
+
"Decorative elements that distract",
|
|
2401
|
+
"Text-heavy slides that require reading",
|
|
2402
|
+
"Unclear what's most important"
|
|
2403
|
+
],
|
|
2404
|
+
questions: [
|
|
2405
|
+
"What is the ONE message?",
|
|
2406
|
+
"What can be removed without losing meaning?",
|
|
2407
|
+
"Does visual hierarchy match content hierarchy?"
|
|
2408
|
+
]
|
|
2409
|
+
};
|
|
2410
|
+
}
|
|
2411
|
+
}
|
|
2412
|
+
/**
|
|
2413
|
+
* Get guidance for extracting the core message from content.
|
|
2414
|
+
* This helps identify WHAT should be on the slide, not just how to fit content.
|
|
2415
|
+
*/
|
|
2416
|
+
getCoreMessageGuidance() {
|
|
2417
|
+
return [
|
|
2418
|
+
{
|
|
2419
|
+
expert: "Chris Anderson",
|
|
2420
|
+
principle: "One Idea Worth Spreading",
|
|
2421
|
+
application: "Find the single most valuable insight in this content",
|
|
2422
|
+
technique: "If they could only remember ONE thing, what should it be?"
|
|
2423
|
+
},
|
|
2424
|
+
{
|
|
2425
|
+
expert: "Carmine Gallo",
|
|
2426
|
+
principle: "Twitter Test",
|
|
2427
|
+
application: "Can you express the key insight in a tweet?",
|
|
2428
|
+
technique: "Write the insight in under 280 characters. If you can't, you don't understand it well enough."
|
|
2429
|
+
},
|
|
2430
|
+
{
|
|
2431
|
+
expert: "Barbara Minto",
|
|
2432
|
+
principle: "So What?",
|
|
2433
|
+
application: 'Keep asking "so what?" until you reach the insight',
|
|
2434
|
+
technique: 'For every statement, ask "so what?" The answer is closer to the real message.'
|
|
2435
|
+
}
|
|
2436
|
+
];
|
|
2437
|
+
}
|
|
2438
|
+
/**
|
|
2439
|
+
* Evaluate whether a slide meets expert standards.
|
|
2440
|
+
* Returns guidance on how to improve, not just pass/fail.
|
|
2441
|
+
*/
|
|
2442
|
+
evaluateSlideExcellence(slide) {
|
|
2443
|
+
const guidance = this.getSlideDesignGuidance(slide.type);
|
|
2444
|
+
const strengths = [];
|
|
2445
|
+
const improvements = [];
|
|
2446
|
+
const expertFeedback = [];
|
|
2447
|
+
let score = 50;
|
|
2448
|
+
for (const quality of guidance.whatMakesItGreat) {
|
|
2449
|
+
}
|
|
2450
|
+
for (const antiPattern of guidance.antiPatterns) {
|
|
2451
|
+
}
|
|
2452
|
+
for (const question of guidance.questions) {
|
|
2453
|
+
expertFeedback.push(`Ask: ${question}`);
|
|
2454
|
+
}
|
|
2455
|
+
if (slide.title) {
|
|
2456
|
+
if (slide.title.length <= 60) {
|
|
2457
|
+
strengths.push("Title passes Duarte Glance Test (under 60 chars)");
|
|
2458
|
+
score += 10;
|
|
2459
|
+
} else {
|
|
2460
|
+
improvements.push("Shorten title - Duarte says 3 seconds to comprehend");
|
|
2461
|
+
}
|
|
2462
|
+
if (!/^(update|overview|introduction|summary)$/i.test(slide.title)) {
|
|
2463
|
+
strengths.push("Title is specific, not generic (Gallo Headline Test)");
|
|
2464
|
+
score += 5;
|
|
2465
|
+
} else {
|
|
2466
|
+
improvements.push("Replace generic title with insight - Gallo says it should work as a headline");
|
|
2467
|
+
score -= 10;
|
|
2468
|
+
}
|
|
2469
|
+
if (!slide.title.includes(" and ") && !slide.title.includes(" & ")) {
|
|
2470
|
+
strengths.push("Title focuses on one idea (Chris Anderson)");
|
|
2471
|
+
score += 5;
|
|
2472
|
+
} else {
|
|
2473
|
+
improvements.push("Split into two slides - Anderson says one idea per slide");
|
|
2474
|
+
}
|
|
2475
|
+
}
|
|
2476
|
+
if (slide.bullets && slide.bullets.length > 5) {
|
|
2477
|
+
improvements.push("Reduce bullets to 3-5 - Reynolds says maximize signal, eliminate noise");
|
|
2478
|
+
score -= 10;
|
|
2479
|
+
}
|
|
2480
|
+
return {
|
|
2481
|
+
score: Math.max(0, Math.min(100, score)),
|
|
2482
|
+
strengths,
|
|
2483
|
+
improvements,
|
|
2484
|
+
expertFeedback
|
|
2485
|
+
};
|
|
2486
|
+
}
|
|
2487
|
+
};
|
|
2488
|
+
function createExpertGuidanceEngine(kb, presentationType) {
|
|
2489
|
+
return new ExpertGuidanceEngine(kb, presentationType);
|
|
2490
|
+
}
|
|
2491
|
+
|
|
1734
2492
|
// src/core/SlideFactory.ts
|
|
1735
2493
|
var SlideFactory = class {
|
|
1736
2494
|
kb;
|
|
1737
2495
|
presentationType;
|
|
1738
2496
|
classifier;
|
|
2497
|
+
expertEngine;
|
|
2498
|
+
// NEW: Expert guidance for world-class slides
|
|
2499
|
+
narrativeArc;
|
|
2500
|
+
// NEW: Story structure from experts
|
|
1739
2501
|
config;
|
|
1740
2502
|
usedContent;
|
|
1741
2503
|
usedTitles;
|
|
@@ -1746,7 +2508,10 @@ var SlideFactory = class {
|
|
|
1746
2508
|
this.usedContent = /* @__PURE__ */ new Set();
|
|
1747
2509
|
this.usedTitles = /* @__PURE__ */ new Set();
|
|
1748
2510
|
this.config = this.loadKBConfig(type);
|
|
1749
|
-
|
|
2511
|
+
this.expertEngine = createExpertGuidanceEngine(kb, type);
|
|
2512
|
+
this.narrativeArc = this.expertEngine.getNarrativeArc();
|
|
2513
|
+
logger.step(`SlideFactory v8.0.0 initialized for ${type} (${this.config.mode} mode)`);
|
|
2514
|
+
logger.step(`Using ${this.narrativeArc.framework} narrative (${this.narrativeArc.expert})`);
|
|
1750
2515
|
logger.step(`KB Config loaded: ${this.config.allowedTypes.length} allowed types`);
|
|
1751
2516
|
}
|
|
1752
2517
|
/**
|
|
@@ -1786,15 +2551,56 @@ var SlideFactory = class {
|
|
|
1786
2551
|
} else if (storyStructure.framework === "sparkline") {
|
|
1787
2552
|
this.addSparklineSlides(slides, analysis);
|
|
1788
2553
|
}
|
|
2554
|
+
let hasCTASection = false;
|
|
1789
2555
|
for (const section of analysis.sections) {
|
|
2556
|
+
logger.debug(`[TRACE] Processing section: "${section.header}"`);
|
|
2557
|
+
logger.debug(`[TRACE] Level: ${section.level}, Content: ${section.content?.length ?? 0} chars`);
|
|
2558
|
+
logger.debug(`[TRACE] Bullets: ${section.bullets?.length ?? 0} items`);
|
|
2559
|
+
if (section.bullets?.length > 0) {
|
|
2560
|
+
section.bullets.slice(0, 3).forEach((b, i) => {
|
|
2561
|
+
logger.debug(`[TRACE] Bullet ${i}: "${b.slice(0, 50)}..."`);
|
|
2562
|
+
});
|
|
2563
|
+
}
|
|
1790
2564
|
const contentKey = this.normalizeKey(section.header);
|
|
1791
|
-
if (this.usedContent.has(contentKey))
|
|
2565
|
+
if (this.usedContent.has(contentKey)) {
|
|
2566
|
+
logger.debug(`[TRACE] SKIPPED: Already used content key "${contentKey}"`);
|
|
2567
|
+
continue;
|
|
2568
|
+
}
|
|
1792
2569
|
this.usedContent.add(contentKey);
|
|
2570
|
+
const headerLower = section.header.toLowerCase();
|
|
2571
|
+
const isCTASection = headerLower.includes("call to action") || headerLower.includes("next step") || headerLower.includes("take action") || headerLower.includes("get started") || headerLower.includes("start today");
|
|
2572
|
+
if (isCTASection) {
|
|
2573
|
+
hasCTASection = true;
|
|
2574
|
+
const ctaDefaults = this.config.defaults.cta;
|
|
2575
|
+
const ctaContent = section.bullets.length > 0 ? section.bullets.slice(0, 4).join(". ") : section.content || ctaDefaults.fallback;
|
|
2576
|
+
const craftedCTA = this.craftExpertContent(
|
|
2577
|
+
ctaContent,
|
|
2578
|
+
"call_to_action",
|
|
2579
|
+
this.config.rules.wordsPerSlide.max * 3
|
|
2580
|
+
// Allow more words for CTA steps
|
|
2581
|
+
);
|
|
2582
|
+
slides.push({
|
|
2583
|
+
index: slides.length,
|
|
2584
|
+
type: "cta",
|
|
2585
|
+
data: {
|
|
2586
|
+
title: section.header,
|
|
2587
|
+
body: craftedCTA,
|
|
2588
|
+
keyMessage: ctaDefaults.message
|
|
2589
|
+
},
|
|
2590
|
+
classes: ["cta-slide"]
|
|
2591
|
+
});
|
|
2592
|
+
continue;
|
|
2593
|
+
}
|
|
1793
2594
|
const pattern = this.classifier.classify(section);
|
|
2595
|
+
logger.debug(`[TRACE] Pattern: ${pattern.primaryPattern}, bulletCount: ${pattern.bulletCount}`);
|
|
1794
2596
|
const slideType = this.kb.mapContentPatternToSlideType(pattern, this.config.allowedTypes);
|
|
2597
|
+
logger.debug(`[TRACE] KB returned slide type: "${slideType}"`);
|
|
1795
2598
|
const slide = this.createSlideByType(slides.length, slideType, section, pattern);
|
|
1796
|
-
if (slide) {
|
|
2599
|
+
if (slide && slide.type && slide.data) {
|
|
2600
|
+
logger.debug(`[TRACE] Created slide type: "${slide.type}" with ${slide.data.bullets?.length ?? 0} bullets`);
|
|
1797
2601
|
slides.push(slide);
|
|
2602
|
+
} else {
|
|
2603
|
+
logger.debug(`[TRACE] SKIPPED: Slide filtered out (no valid content)`);
|
|
1798
2604
|
}
|
|
1799
2605
|
}
|
|
1800
2606
|
const minDataPointsForMetrics = 2;
|
|
@@ -1806,7 +2612,7 @@ var SlideFactory = class {
|
|
|
1806
2612
|
slides.push(this.createMetricsGridSlide(slides.length, analysis.dataPoints));
|
|
1807
2613
|
}
|
|
1808
2614
|
}
|
|
1809
|
-
if (analysis.sparkline?.callToAdventure || analysis.scqa?.answer) {
|
|
2615
|
+
if (!hasCTASection && (analysis.sparkline?.callToAdventure || analysis.scqa?.answer)) {
|
|
1810
2616
|
slides.push(this.createCTASlide(slides.length, analysis));
|
|
1811
2617
|
}
|
|
1812
2618
|
slides.push(this.createThankYouSlide(slides.length));
|
|
@@ -1822,34 +2628,55 @@ var SlideFactory = class {
|
|
|
1822
2628
|
createSlideByType(index, type, section, pattern) {
|
|
1823
2629
|
const normalizedType = type.toLowerCase().replace(/_/g, "-");
|
|
1824
2630
|
switch (normalizedType) {
|
|
2631
|
+
// Big number slides - show prominent metrics
|
|
1825
2632
|
case "big-number":
|
|
1826
|
-
case "data-insight":
|
|
1827
2633
|
return this.createBigNumberSlide(index, section, pattern);
|
|
2634
|
+
// Data insight - for findings that may or may not have numbers
|
|
2635
|
+
case "data-insight":
|
|
2636
|
+
return this.createDataInsightSlide(index, section, pattern);
|
|
2637
|
+
// Comparison slides
|
|
1828
2638
|
case "comparison":
|
|
1829
2639
|
case "options-comparison":
|
|
1830
2640
|
return this.createComparisonSlide(index, section);
|
|
2641
|
+
// Timeline and roadmap slides
|
|
1831
2642
|
case "timeline":
|
|
1832
2643
|
case "process-timeline":
|
|
1833
2644
|
case "roadmap":
|
|
1834
2645
|
return this.createTimelineSlide(index, section);
|
|
2646
|
+
// Process flow slides (with steps)
|
|
1835
2647
|
case "process":
|
|
2648
|
+
case "mece-breakdown":
|
|
1836
2649
|
return this.createProcessSlide(index, section);
|
|
2650
|
+
// Three points slide (for exactly 3 items)
|
|
2651
|
+
case "three-points":
|
|
2652
|
+
return this.createThreePointsSlide(index, section);
|
|
2653
|
+
// Column layouts
|
|
1837
2654
|
case "three-column":
|
|
1838
2655
|
return this.createThreeColumnSlide(index, section);
|
|
1839
2656
|
case "two-column":
|
|
1840
2657
|
return this.createTwoColumnSlide(index, section);
|
|
2658
|
+
// Quote and testimonial slides
|
|
1841
2659
|
case "quote":
|
|
1842
2660
|
case "testimonial":
|
|
1843
2661
|
case "social-proof":
|
|
1844
2662
|
return this.createQuoteSlide(index, section);
|
|
2663
|
+
// Metrics grid for multiple KPIs
|
|
1845
2664
|
case "metrics-grid":
|
|
1846
2665
|
return this.createMetricsGridSlide(index, section.metrics);
|
|
2666
|
+
// Standard bullet point slides
|
|
1847
2667
|
case "bullet-points":
|
|
1848
|
-
case "detailed-findings":
|
|
1849
2668
|
return this.createBulletSlide(index, section);
|
|
2669
|
+
// Title impact - emphasized single statement
|
|
2670
|
+
case "title-impact":
|
|
2671
|
+
return this.createTitleImpactSlide(index, section);
|
|
2672
|
+
// Single statement slides
|
|
1850
2673
|
case "single-statement":
|
|
1851
2674
|
case "big-idea":
|
|
1852
2675
|
return this.createSingleStatementSlide(index, section);
|
|
2676
|
+
// Detailed findings - bullets with context
|
|
2677
|
+
case "detailed-findings":
|
|
2678
|
+
return this.createDetailedFindingsSlide(index, section);
|
|
2679
|
+
// Code and technical slides
|
|
1853
2680
|
case "code-snippet":
|
|
1854
2681
|
case "technical":
|
|
1855
2682
|
return this.createCodeSlide(index, section);
|
|
@@ -1862,15 +2689,21 @@ var SlideFactory = class {
|
|
|
1862
2689
|
// STRUCTURAL SLIDES (title, agenda, thank you) - ALL from KB
|
|
1863
2690
|
// ===========================================================================
|
|
1864
2691
|
createTitleSlide(index, analysis) {
|
|
2692
|
+
const craftedTitle = this.craftExpertContent(
|
|
2693
|
+
analysis.title,
|
|
2694
|
+
"title",
|
|
2695
|
+
this.config.rules.wordsPerSlide.max
|
|
2696
|
+
);
|
|
1865
2697
|
const data = {
|
|
1866
|
-
title:
|
|
2698
|
+
title: craftedTitle
|
|
1867
2699
|
};
|
|
1868
2700
|
const subtitleSource = analysis.subtitle || analysis.keyMessages[0] || "";
|
|
1869
2701
|
if (subtitleSource) {
|
|
1870
|
-
data.subtitle = this.
|
|
2702
|
+
data.subtitle = this.craftExpertContent(
|
|
1871
2703
|
subtitleSource,
|
|
2704
|
+
"title",
|
|
2705
|
+
// Use title guidance for subtitle too
|
|
1872
2706
|
this.config.defaults.subtitle.maxWords
|
|
1873
|
-
// FROM KB
|
|
1874
2707
|
);
|
|
1875
2708
|
}
|
|
1876
2709
|
return {
|
|
@@ -1959,31 +2792,43 @@ var SlideFactory = class {
|
|
|
1959
2792
|
addSparklineSlides(slides, analysis) {
|
|
1960
2793
|
const spark = analysis.sparkline;
|
|
1961
2794
|
const titles = this.config.sparklineTitles;
|
|
2795
|
+
const maxWords = this.config.rules.wordsPerSlide.max;
|
|
1962
2796
|
const whatIsFirst = spark?.whatIs?.[0];
|
|
1963
|
-
const whatIsBody = whatIsFirst ? this.
|
|
1964
|
-
|
|
2797
|
+
const whatIsBody = whatIsFirst ? this.craftExpertContent(whatIsFirst, "single_statement", maxWords) : "";
|
|
2798
|
+
const whatCouldBeFirst = spark?.whatCouldBe?.[0];
|
|
2799
|
+
const whatCouldBeBody = whatCouldBeFirst ? this.craftExpertContent(whatCouldBeFirst, "single_statement", maxWords) : "";
|
|
2800
|
+
const normalizedTitle = analysis.title.toLowerCase().slice(0, 50);
|
|
2801
|
+
const normalizedSubtitle = (analysis.subtitle || "").toLowerCase().slice(0, 50);
|
|
2802
|
+
const normalizedWhatIs = whatIsBody.toLowerCase().slice(0, 50);
|
|
2803
|
+
const normalizedWhatCouldBe = whatCouldBeBody.toLowerCase().slice(0, 50);
|
|
2804
|
+
const whatIsDuplicatesTitle = normalizedWhatIs === normalizedTitle || normalizedWhatIs.includes(normalizedTitle.slice(0, 30)) || normalizedTitle.includes(normalizedWhatIs.slice(0, 30));
|
|
2805
|
+
const whatIsDuplicatesSubtitle = normalizedWhatIs === normalizedSubtitle || normalizedWhatIs.includes(normalizedSubtitle.slice(0, 30)) || normalizedSubtitle.includes(normalizedWhatIs.slice(0, 30));
|
|
2806
|
+
const isDuplicatePair = normalizedWhatIs === normalizedWhatCouldBe;
|
|
2807
|
+
if (whatIsBody.length >= 30 && !isDuplicatePair && !whatIsDuplicatesTitle && !whatIsDuplicatesSubtitle && !this.usedContent.has("spark-what-is")) {
|
|
1965
2808
|
this.usedContent.add("spark-what-is");
|
|
2809
|
+
this.usedContent.add(normalizedWhatIs);
|
|
1966
2810
|
slides.push({
|
|
1967
2811
|
index: slides.length,
|
|
1968
2812
|
type: "single-statement",
|
|
1969
2813
|
data: {
|
|
1970
2814
|
title: titles.whatIs,
|
|
1971
|
-
// FROM KB
|
|
2815
|
+
// FROM KB
|
|
1972
2816
|
body: whatIsBody
|
|
1973
2817
|
},
|
|
1974
2818
|
classes: ["what-is-slide"]
|
|
1975
2819
|
});
|
|
1976
2820
|
}
|
|
1977
|
-
const
|
|
1978
|
-
const
|
|
1979
|
-
if (whatCouldBeBody.length >=
|
|
2821
|
+
const whatCouldBeDuplicatesTitle = normalizedWhatCouldBe === normalizedTitle || normalizedWhatCouldBe.includes(normalizedTitle.slice(0, 30)) || normalizedTitle.includes(normalizedWhatCouldBe.slice(0, 30));
|
|
2822
|
+
const whatCouldBeDuplicatesSubtitle = normalizedWhatCouldBe === normalizedSubtitle || normalizedWhatCouldBe.includes(normalizedSubtitle.slice(0, 30)) || normalizedSubtitle.includes(normalizedWhatCouldBe.slice(0, 30));
|
|
2823
|
+
if (whatCouldBeBody.length >= 30 && !isDuplicatePair && !whatCouldBeDuplicatesTitle && !whatCouldBeDuplicatesSubtitle && !this.usedContent.has("spark-could-be")) {
|
|
1980
2824
|
this.usedContent.add("spark-could-be");
|
|
2825
|
+
this.usedContent.add(normalizedWhatCouldBe);
|
|
1981
2826
|
slides.push({
|
|
1982
2827
|
index: slides.length,
|
|
1983
2828
|
type: "single-statement",
|
|
1984
2829
|
data: {
|
|
1985
2830
|
title: titles.whatCouldBe,
|
|
1986
|
-
// FROM KB
|
|
2831
|
+
// FROM KB
|
|
1987
2832
|
body: whatCouldBeBody
|
|
1988
2833
|
},
|
|
1989
2834
|
classes: ["what-could-be-slide"]
|
|
@@ -2001,17 +2846,19 @@ var SlideFactory = class {
|
|
|
2001
2846
|
logger.warn(`No number found for big-number slide, falling back to single-statement`);
|
|
2002
2847
|
return this.createSingleStatementSlide(index, section);
|
|
2003
2848
|
}
|
|
2849
|
+
const contextContent = bigNumber?.context || section.content;
|
|
2850
|
+
const craftedContext = this.craftExpertContent(
|
|
2851
|
+
contextContent,
|
|
2852
|
+
"big_number",
|
|
2853
|
+
this.config.defaults.context.maxWords
|
|
2854
|
+
);
|
|
2004
2855
|
return {
|
|
2005
2856
|
index,
|
|
2006
2857
|
type: "big-number",
|
|
2007
2858
|
data: {
|
|
2008
2859
|
title: this.createTitle(section.header, section),
|
|
2009
2860
|
keyMessage: actualValue,
|
|
2010
|
-
body:
|
|
2011
|
-
section.content,
|
|
2012
|
-
this.config.defaults.context.maxWords
|
|
2013
|
-
// FROM KB - not hardcoded 30
|
|
2014
|
-
)
|
|
2861
|
+
body: craftedContext
|
|
2015
2862
|
},
|
|
2016
2863
|
classes: ["big-number-slide"]
|
|
2017
2864
|
};
|
|
@@ -2058,7 +2905,8 @@ var SlideFactory = class {
|
|
|
2058
2905
|
data: {
|
|
2059
2906
|
title: this.createTitle(section.header, section),
|
|
2060
2907
|
steps: steps.slice(0, maxSteps).map((step) => ({
|
|
2061
|
-
label: step.label,
|
|
2908
|
+
label: this.cleanText(step.label),
|
|
2909
|
+
// Clean markdown from labels
|
|
2062
2910
|
description: this.truncateText(
|
|
2063
2911
|
step.description,
|
|
2064
2912
|
this.config.defaults.step.maxWords
|
|
@@ -2079,7 +2927,8 @@ var SlideFactory = class {
|
|
|
2079
2927
|
title: this.createTitle(section.header, section),
|
|
2080
2928
|
steps: steps.slice(0, maxSteps).map((step, i) => ({
|
|
2081
2929
|
number: i + 1,
|
|
2082
|
-
title: step.label,
|
|
2930
|
+
title: this.cleanText(step.label),
|
|
2931
|
+
// Clean markdown from labels
|
|
2083
2932
|
description: this.truncateText(
|
|
2084
2933
|
step.description,
|
|
2085
2934
|
this.config.defaults.step.maxWords
|
|
@@ -2114,6 +2963,69 @@ var SlideFactory = class {
|
|
|
2114
2963
|
classes: ["three-column-slide"]
|
|
2115
2964
|
};
|
|
2116
2965
|
}
|
|
2966
|
+
/**
|
|
2967
|
+
* Create a three-points slide for exactly 3 key points.
|
|
2968
|
+
* Used for content that fits the "rule of three" (Carmine Gallo).
|
|
2969
|
+
*/
|
|
2970
|
+
createThreePointsSlide(index, section) {
|
|
2971
|
+
const points = [];
|
|
2972
|
+
if (section.bullets.length >= 3) {
|
|
2973
|
+
for (let i = 0; i < 3; i++) {
|
|
2974
|
+
const bullet = section.bullets[i] || "";
|
|
2975
|
+
const extracted = this.classifier.extractSteps({ ...section, bullets: [bullet] });
|
|
2976
|
+
if (extracted.length > 0 && extracted[0] && extracted[0].description) {
|
|
2977
|
+
points.push({
|
|
2978
|
+
number: i + 1,
|
|
2979
|
+
title: this.cleanText(extracted[0].label) || `Step ${i + 1}`,
|
|
2980
|
+
description: this.truncateText(extracted[0].description, 20)
|
|
2981
|
+
});
|
|
2982
|
+
} else {
|
|
2983
|
+
const firstWords = this.cleanText(bullet).split(/\s+/).slice(0, 3).join(" ");
|
|
2984
|
+
points.push({
|
|
2985
|
+
number: i + 1,
|
|
2986
|
+
title: firstWords || `Step ${i + 1}`,
|
|
2987
|
+
description: this.truncateText(bullet, 20)
|
|
2988
|
+
});
|
|
2989
|
+
}
|
|
2990
|
+
}
|
|
2991
|
+
} else if (section.bullets.length > 0) {
|
|
2992
|
+
for (let i = 0; i < 3; i++) {
|
|
2993
|
+
const bullet = section.bullets[i % section.bullets.length] || "";
|
|
2994
|
+
const firstWords = this.cleanText(bullet).split(/\s+/).slice(0, 3).join(" ");
|
|
2995
|
+
points.push({
|
|
2996
|
+
number: i + 1,
|
|
2997
|
+
title: firstWords || `Step ${i + 1}`,
|
|
2998
|
+
description: this.truncateText(bullet, 20)
|
|
2999
|
+
});
|
|
3000
|
+
}
|
|
3001
|
+
} else {
|
|
3002
|
+
const sentences = section.content.split(/[.!?]+/).filter((s) => s.trim().length > 0).slice(0, 3);
|
|
3003
|
+
sentences.forEach((s, i) => {
|
|
3004
|
+
const firstWords = this.cleanText(s.trim()).split(/\s+/).slice(0, 3).join(" ");
|
|
3005
|
+
points.push({
|
|
3006
|
+
number: i + 1,
|
|
3007
|
+
title: firstWords || `Step ${i + 1}`,
|
|
3008
|
+
description: this.truncateText(s.trim(), 20)
|
|
3009
|
+
});
|
|
3010
|
+
});
|
|
3011
|
+
}
|
|
3012
|
+
return {
|
|
3013
|
+
index,
|
|
3014
|
+
type: "three-column",
|
|
3015
|
+
// Uses three-column template
|
|
3016
|
+
data: {
|
|
3017
|
+
title: this.createTitle(section.header, section),
|
|
3018
|
+
columns: points.map((p) => ({
|
|
3019
|
+
title: String(p.number),
|
|
3020
|
+
// Use the sequential number
|
|
3021
|
+
content: p.description,
|
|
3022
|
+
subtitle: p.title
|
|
3023
|
+
// Use extracted title as subtitle
|
|
3024
|
+
}))
|
|
3025
|
+
},
|
|
3026
|
+
classes: ["three-points-slide", "three-column-slide"]
|
|
3027
|
+
};
|
|
3028
|
+
}
|
|
2117
3029
|
createTwoColumnSlide(index, section) {
|
|
2118
3030
|
const midpoint = Math.ceil(section.bullets.length / 2);
|
|
2119
3031
|
const leftBullets = section.bullets.slice(0, midpoint);
|
|
@@ -2176,10 +3088,22 @@ var SlideFactory = class {
|
|
|
2176
3088
|
};
|
|
2177
3089
|
}
|
|
2178
3090
|
createBulletSlide(index, section) {
|
|
3091
|
+
logger.debug(`[TRACE] createBulletSlide for "${section.header}"`);
|
|
3092
|
+
logger.debug(`[TRACE] Input bullets: ${section.bullets?.length ?? 0}`);
|
|
2179
3093
|
const maxBullets = this.config.rules.bulletsPerSlide.max;
|
|
2180
3094
|
const wordsPerBullet = Math.floor(this.config.rules.wordsPerSlide.max / maxBullets);
|
|
2181
|
-
|
|
3095
|
+
logger.debug(`[TRACE] maxBullets: ${maxBullets}, wordsPerBullet: ${wordsPerBullet}`);
|
|
3096
|
+
const slicedBullets = section.bullets.slice(0, maxBullets);
|
|
3097
|
+
logger.debug(`[TRACE] After slice: ${slicedBullets.length} bullets`);
|
|
3098
|
+
const cleanedBullets = slicedBullets.map((b, i) => {
|
|
3099
|
+
const cleaned = this.cleanText(b);
|
|
3100
|
+
const truncated = this.truncateText(cleaned, wordsPerBullet);
|
|
3101
|
+
logger.debug(`[TRACE] Bullet ${i}: "${b.slice(0, 30)}..." \u2192 "${truncated.slice(0, 30)}..."`);
|
|
3102
|
+
return truncated;
|
|
3103
|
+
}).filter((b) => b.length > 0);
|
|
3104
|
+
logger.debug(`[TRACE] Final cleaned bullets: ${cleanedBullets.length}`);
|
|
2182
3105
|
if (cleanedBullets.length === 0 && section.content) {
|
|
3106
|
+
logger.debug(`[TRACE] No bullets, falling back to single-statement`);
|
|
2183
3107
|
return this.createSingleStatementSlide(index, section);
|
|
2184
3108
|
}
|
|
2185
3109
|
return {
|
|
@@ -2193,54 +3117,144 @@ var SlideFactory = class {
|
|
|
2193
3117
|
};
|
|
2194
3118
|
}
|
|
2195
3119
|
createSingleStatementSlide(index, section) {
|
|
3120
|
+
const title = this.createTitle(section.header, section);
|
|
3121
|
+
const titleWords = title.split(/\s+/).length;
|
|
3122
|
+
const maxTotalWords = this.config.rules.wordsPerSlide.max;
|
|
3123
|
+
const maxBodyWords = Math.max(3, maxTotalWords - titleWords);
|
|
2196
3124
|
const bodyContent = section.content || section.bullets.join(" ") || "";
|
|
2197
|
-
const
|
|
2198
|
-
|
|
2199
|
-
|
|
3125
|
+
const craftedBody = this.craftExpertContent(
|
|
3126
|
+
bodyContent,
|
|
3127
|
+
"single_statement",
|
|
3128
|
+
// Tell expert engine what type of slide this is
|
|
3129
|
+
maxBodyWords
|
|
3130
|
+
);
|
|
3131
|
+
const normalizedBody = craftedBody.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
3132
|
+
const normalizedTitle = title.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
3133
|
+
const bodyIsDuplicate = normalizedBody === normalizedTitle || craftedBody.length < 10;
|
|
3134
|
+
if (bodyIsDuplicate) {
|
|
3135
|
+
if (section.bullets.length >= 2 && this.config.mode === "business") {
|
|
3136
|
+
return this.createBulletSlide(index, section);
|
|
3137
|
+
}
|
|
3138
|
+
if (section.metrics.length >= 2) {
|
|
3139
|
+
return this.createMetricsGridSlide(index, section.metrics);
|
|
3140
|
+
}
|
|
3141
|
+
logger.warn(`Skipping slide "${title}" - no distinct body content`);
|
|
3142
|
+
return null;
|
|
2200
3143
|
}
|
|
2201
3144
|
return {
|
|
2202
3145
|
index,
|
|
2203
3146
|
type: "single-statement",
|
|
2204
3147
|
data: {
|
|
2205
|
-
title
|
|
2206
|
-
body:
|
|
2207
|
-
// Fallback to header if no body
|
|
3148
|
+
title,
|
|
3149
|
+
body: craftedBody
|
|
2208
3150
|
},
|
|
2209
3151
|
classes: ["single-statement-slide"]
|
|
2210
3152
|
};
|
|
2211
3153
|
}
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
3154
|
+
/**
|
|
3155
|
+
* Create a title impact slide - emphasized statement with key takeaway.
|
|
3156
|
+
* Used for section headers with strong conclusions.
|
|
3157
|
+
*/
|
|
3158
|
+
createTitleImpactSlide(index, section) {
|
|
3159
|
+
const supportingText = section.content || section.bullets.slice(0, 2).join(". ");
|
|
3160
|
+
const truncatedSupport = this.truncateText(supportingText, this.config.defaults.context.maxWords);
|
|
3161
|
+
const data = {
|
|
3162
|
+
title: this.cleanText(section.header)
|
|
3163
|
+
};
|
|
3164
|
+
if (truncatedSupport) {
|
|
3165
|
+
data.body = truncatedSupport;
|
|
3166
|
+
}
|
|
2215
3167
|
return {
|
|
2216
3168
|
index,
|
|
2217
|
-
type: "
|
|
2218
|
-
//
|
|
2219
|
-
data
|
|
2220
|
-
|
|
2221
|
-
leftColumn: {
|
|
2222
|
-
body: this.config.defaults.code.label
|
|
2223
|
-
// FROM KB - not hardcoded 'Code Example'
|
|
2224
|
-
},
|
|
2225
|
-
rightColumn: {
|
|
2226
|
-
body: code.slice(0, this.config.defaults.code.maxChars)
|
|
2227
|
-
// FROM KB - not hardcoded 500
|
|
2228
|
-
}
|
|
2229
|
-
},
|
|
2230
|
-
classes: ["code-slide"]
|
|
3169
|
+
type: "single-statement",
|
|
3170
|
+
// Uses single-statement template
|
|
3171
|
+
data,
|
|
3172
|
+
classes: ["title-impact-slide", "single-statement-slide"]
|
|
2231
3173
|
};
|
|
2232
3174
|
}
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
3175
|
+
/**
|
|
3176
|
+
* Create a data insight slide - for presenting key findings with context.
|
|
3177
|
+
* Similar to big-number but works even without a prominent metric.
|
|
3178
|
+
*/
|
|
3179
|
+
createDataInsightSlide(index, section, pattern) {
|
|
3180
|
+
const fullContent = `${section.header} ${section.content} ${section.bullets.join(" ")}`;
|
|
3181
|
+
const bigNumber = this.classifier.extractBigNumber(fullContent);
|
|
3182
|
+
if (bigNumber?.value || pattern.bigNumberValue) {
|
|
3183
|
+
return this.createBigNumberSlide(index, section, pattern);
|
|
3184
|
+
}
|
|
3185
|
+
const keyInsight = section.content || section.bullets[0] || section.header;
|
|
2236
3186
|
return {
|
|
2237
3187
|
index,
|
|
2238
|
-
type: "
|
|
3188
|
+
type: "single-statement",
|
|
2239
3189
|
data: {
|
|
2240
|
-
title:
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
3190
|
+
title: this.createTitle(section.header, section),
|
|
3191
|
+
body: this.truncateText(keyInsight, this.config.rules.wordsPerSlide.max)
|
|
3192
|
+
},
|
|
3193
|
+
classes: ["data-insight-slide", "single-statement-slide"]
|
|
3194
|
+
};
|
|
3195
|
+
}
|
|
3196
|
+
/**
|
|
3197
|
+
* Create a detailed findings slide - bullet points with introductory context.
|
|
3198
|
+
*/
|
|
3199
|
+
createDetailedFindingsSlide(index, section) {
|
|
3200
|
+
const introText = section.content ? this.truncateText(section.content, this.config.defaults.context.maxWords) : "";
|
|
3201
|
+
const maxBullets = this.config.rules.bulletsPerSlide.max;
|
|
3202
|
+
const wordsPerBullet = Math.floor(this.config.rules.wordsPerSlide.max / maxBullets);
|
|
3203
|
+
const cleanedBullets = section.bullets.slice(0, maxBullets).map((b) => this.truncateText(this.cleanText(b), wordsPerBullet)).filter((b) => b.length > 0);
|
|
3204
|
+
if (cleanedBullets.length === 0) {
|
|
3205
|
+
return this.createSingleStatementSlide(index, section);
|
|
3206
|
+
}
|
|
3207
|
+
const data = {
|
|
3208
|
+
title: this.createTitle(section.header, section),
|
|
3209
|
+
bullets: cleanedBullets
|
|
3210
|
+
};
|
|
3211
|
+
if (introText) {
|
|
3212
|
+
data.body = introText;
|
|
3213
|
+
}
|
|
3214
|
+
return {
|
|
3215
|
+
index,
|
|
3216
|
+
type: "bullet-points",
|
|
3217
|
+
data,
|
|
3218
|
+
classes: ["detailed-findings-slide", "bullet-points-slide"]
|
|
3219
|
+
};
|
|
3220
|
+
}
|
|
3221
|
+
createCodeSlide(index, section) {
|
|
3222
|
+
const codeMatch = section.content.match(/```[\s\S]*?```|`[^`]+`/);
|
|
3223
|
+
const code = codeMatch ? codeMatch[0].replace(/```/g, "").trim() : section.content;
|
|
3224
|
+
return {
|
|
3225
|
+
index,
|
|
3226
|
+
type: "two-column",
|
|
3227
|
+
// Code slides use two-column layout
|
|
3228
|
+
data: {
|
|
3229
|
+
title: this.createTitle(section.header, section),
|
|
3230
|
+
leftColumn: {
|
|
3231
|
+
body: this.config.defaults.code.label
|
|
3232
|
+
// FROM KB - not hardcoded 'Code Example'
|
|
3233
|
+
},
|
|
3234
|
+
rightColumn: {
|
|
3235
|
+
body: code.slice(0, this.config.defaults.code.maxChars)
|
|
3236
|
+
// FROM KB - not hardcoded 500
|
|
3237
|
+
}
|
|
3238
|
+
},
|
|
3239
|
+
classes: ["code-slide"]
|
|
3240
|
+
};
|
|
3241
|
+
}
|
|
3242
|
+
createCTASlide(index, analysis) {
|
|
3243
|
+
const ctaDefaults = this.config.defaults.cta;
|
|
3244
|
+
const ctaText = analysis.sparkline?.callToAdventure || analysis.scqa?.answer || ctaDefaults.fallback;
|
|
3245
|
+
const craftedCTA = this.craftExpertContent(
|
|
3246
|
+
ctaText,
|
|
3247
|
+
"call_to_action",
|
|
3248
|
+
this.config.rules.wordsPerSlide.max
|
|
3249
|
+
);
|
|
3250
|
+
return {
|
|
3251
|
+
index,
|
|
3252
|
+
type: "cta",
|
|
3253
|
+
data: {
|
|
3254
|
+
title: ctaDefaults.title,
|
|
3255
|
+
// FROM KB - not hardcoded 'Next Steps'
|
|
3256
|
+
body: craftedCTA,
|
|
3257
|
+
keyMessage: ctaDefaults.message
|
|
2244
3258
|
// FROM KB - not hardcoded 'Ready to Begin?'
|
|
2245
3259
|
},
|
|
2246
3260
|
classes: ["cta-slide"]
|
|
@@ -2352,6 +3366,215 @@ var SlideFactory = class {
|
|
|
2352
3366
|
}
|
|
2353
3367
|
}
|
|
2354
3368
|
// ===========================================================================
|
|
3369
|
+
// EXPERT-DRIVEN CONTENT CRAFTING (v8.0.0)
|
|
3370
|
+
// Instead of truncating content, we CRAFT it using expert principles
|
|
3371
|
+
// ===========================================================================
|
|
3372
|
+
/**
|
|
3373
|
+
* Craft content using expert principles - NOT truncation.
|
|
3374
|
+
*
|
|
3375
|
+
* The old approach: "Take 100 words, chop to 15, add ellipsis"
|
|
3376
|
+
* The new approach: "What would Nancy Duarte say is the ONE thing to remember?"
|
|
3377
|
+
*
|
|
3378
|
+
* @param content - The raw content to craft from
|
|
3379
|
+
* @param slideType - The type of slide being created
|
|
3380
|
+
* @param maxWords - Word budget from KB
|
|
3381
|
+
* @returns Expertly crafted content that serves the slide's purpose
|
|
3382
|
+
*/
|
|
3383
|
+
craftExpertContent(content, slideType, maxWords) {
|
|
3384
|
+
if (!content || content.trim().length === 0) return "";
|
|
3385
|
+
const guidance = this.expertEngine.getSlideDesignGuidance(slideType);
|
|
3386
|
+
switch (slideType) {
|
|
3387
|
+
case "title":
|
|
3388
|
+
case "title_impact":
|
|
3389
|
+
return this.extractCoreIdea(content, maxWords);
|
|
3390
|
+
case "star_moment":
|
|
3391
|
+
return this.extractDramaticElement(content, maxWords);
|
|
3392
|
+
case "big_number":
|
|
3393
|
+
case "data_insight":
|
|
3394
|
+
return this.extractDataInsight(content, maxWords);
|
|
3395
|
+
case "single_statement":
|
|
3396
|
+
case "big_idea":
|
|
3397
|
+
return this.extractSingleInsight(content, maxWords);
|
|
3398
|
+
case "call_to_action":
|
|
3399
|
+
case "cta":
|
|
3400
|
+
return this.extractActionableStep(content, maxWords);
|
|
3401
|
+
default:
|
|
3402
|
+
return this.extractCoreIdea(content, maxWords);
|
|
3403
|
+
}
|
|
3404
|
+
}
|
|
3405
|
+
/**
|
|
3406
|
+
* Chris Anderson's "One Idea Worth Spreading"
|
|
3407
|
+
* Extract the single most valuable insight from content.
|
|
3408
|
+
*/
|
|
3409
|
+
extractCoreIdea(content, maxWords) {
|
|
3410
|
+
const cleaned = this.cleanText(content);
|
|
3411
|
+
const sentences = cleaned.match(/[^.!?]+[.!?]?/g) || [cleaned];
|
|
3412
|
+
let bestSentence = sentences[0] || cleaned;
|
|
3413
|
+
let bestScore = 0;
|
|
3414
|
+
for (const sentence of sentences) {
|
|
3415
|
+
let score = 0;
|
|
3416
|
+
const lowerSentence = sentence.toLowerCase();
|
|
3417
|
+
for (const marker of this.config.insightMarkers) {
|
|
3418
|
+
if (lowerSentence.includes(marker.toLowerCase())) {
|
|
3419
|
+
score += 10;
|
|
3420
|
+
}
|
|
3421
|
+
}
|
|
3422
|
+
if (/\d+%|\$[\d,]+|\d+x|\d+\+/.test(sentence)) {
|
|
3423
|
+
score += 5;
|
|
3424
|
+
}
|
|
3425
|
+
if (/transform|revolutionize|breakthrough|critical|essential|must|never|always/i.test(sentence)) {
|
|
3426
|
+
score += 3;
|
|
3427
|
+
}
|
|
3428
|
+
if (/overview|introduction|agenda|summary|the following/i.test(sentence)) {
|
|
3429
|
+
score -= 10;
|
|
3430
|
+
}
|
|
3431
|
+
if (score > bestScore) {
|
|
3432
|
+
bestScore = score;
|
|
3433
|
+
bestSentence = sentence.trim();
|
|
3434
|
+
}
|
|
3435
|
+
}
|
|
3436
|
+
const words = bestSentence.split(/\s+/);
|
|
3437
|
+
if (words.length <= maxWords) {
|
|
3438
|
+
return bestSentence;
|
|
3439
|
+
}
|
|
3440
|
+
return this.extractKeyPhrase(bestSentence, maxWords);
|
|
3441
|
+
}
|
|
3442
|
+
/**
|
|
3443
|
+
* Extract the key phrase from a sentence - the part that matters most.
|
|
3444
|
+
*/
|
|
3445
|
+
extractKeyPhrase(sentence, maxWords) {
|
|
3446
|
+
const patterns = [
|
|
3447
|
+
/(?:the key (?:is|to)|critical|essential|must|should)\s+(.+)/i,
|
|
3448
|
+
/(.+?)\s+(?:is critical|is essential|matters most)/i,
|
|
3449
|
+
/^(.+?)\s*[-–—:]\s*.+$/
|
|
3450
|
+
// Before a colon/dash is often the key
|
|
3451
|
+
];
|
|
3452
|
+
for (const pattern of patterns) {
|
|
3453
|
+
const match = sentence.match(pattern);
|
|
3454
|
+
if (match && match[1]) {
|
|
3455
|
+
const extracted = match[1].trim();
|
|
3456
|
+
if (extracted.split(/\s+/).length <= maxWords) {
|
|
3457
|
+
return extracted;
|
|
3458
|
+
}
|
|
3459
|
+
}
|
|
3460
|
+
}
|
|
3461
|
+
const words = sentence.split(/\s+/);
|
|
3462
|
+
let result = words.slice(0, maxWords).join(" ");
|
|
3463
|
+
const lastPunctuation = result.lastIndexOf(",");
|
|
3464
|
+
if (lastPunctuation > result.length / 2) {
|
|
3465
|
+
result = result.slice(0, lastPunctuation);
|
|
3466
|
+
}
|
|
3467
|
+
return result;
|
|
3468
|
+
}
|
|
3469
|
+
/**
|
|
3470
|
+
* Nancy Duarte's "STAR Moment" - Something They'll Always Remember
|
|
3471
|
+
* Find the most dramatic, unexpected element.
|
|
3472
|
+
*/
|
|
3473
|
+
extractDramaticElement(content, maxWords) {
|
|
3474
|
+
const cleaned = this.cleanText(content);
|
|
3475
|
+
const sentences = cleaned.match(/[^.!?]+[.!?]?/g) || [cleaned];
|
|
3476
|
+
let mostDramatic = sentences[0] || cleaned;
|
|
3477
|
+
let highestDrama = 0;
|
|
3478
|
+
for (const sentence of sentences) {
|
|
3479
|
+
let drama = 0;
|
|
3480
|
+
const numbers = sentence.match(/\d+/g);
|
|
3481
|
+
if (numbers) {
|
|
3482
|
+
for (const num of numbers) {
|
|
3483
|
+
const value = parseInt(num);
|
|
3484
|
+
if (value >= 1e3) drama += 15;
|
|
3485
|
+
else if (value >= 100) drama += 10;
|
|
3486
|
+
else if (value >= 50) drama += 5;
|
|
3487
|
+
}
|
|
3488
|
+
}
|
|
3489
|
+
if (/\d+%/.test(sentence)) drama += 10;
|
|
3490
|
+
if (/\$[\d,]+[MBK]?/i.test(sentence)) drama += 12;
|
|
3491
|
+
if (/never|always|revolutionary|transforming|critical|dramatic|shocking|surprising/i.test(sentence)) {
|
|
3492
|
+
drama += 8;
|
|
3493
|
+
}
|
|
3494
|
+
if (/but|however|yet|instead|versus|vs/i.test(sentence)) {
|
|
3495
|
+
drama += 5;
|
|
3496
|
+
}
|
|
3497
|
+
if (drama > highestDrama) {
|
|
3498
|
+
highestDrama = drama;
|
|
3499
|
+
mostDramatic = sentence.trim();
|
|
3500
|
+
}
|
|
3501
|
+
}
|
|
3502
|
+
const words = mostDramatic.split(/\s+/);
|
|
3503
|
+
if (words.length <= maxWords) {
|
|
3504
|
+
return mostDramatic;
|
|
3505
|
+
}
|
|
3506
|
+
return this.extractKeyPhrase(mostDramatic, maxWords);
|
|
3507
|
+
}
|
|
3508
|
+
/**
|
|
3509
|
+
* Edward Tufte / Cole Knaflic - Extract data insight with context
|
|
3510
|
+
*/
|
|
3511
|
+
extractDataInsight(content, maxWords) {
|
|
3512
|
+
const cleaned = this.cleanText(content);
|
|
3513
|
+
const sentences = cleaned.match(/[^.!?]+[.!?]?/g) || [cleaned];
|
|
3514
|
+
const datasentences = sentences.filter(
|
|
3515
|
+
(s) => /\d+%|\$[\d,]+|\d+x|\d+ (million|billion|thousand)/i.test(s)
|
|
3516
|
+
);
|
|
3517
|
+
if (datasentences.length > 0 && datasentences[0]) {
|
|
3518
|
+
const best = datasentences[0].trim();
|
|
3519
|
+
const words = best.split(/\s+/);
|
|
3520
|
+
if (words.length <= maxWords) {
|
|
3521
|
+
return best;
|
|
3522
|
+
}
|
|
3523
|
+
return this.extractKeyPhrase(best, maxWords);
|
|
3524
|
+
}
|
|
3525
|
+
return this.extractCoreIdea(content, maxWords);
|
|
3526
|
+
}
|
|
3527
|
+
/**
|
|
3528
|
+
* Garr Reynolds - Signal to Noise
|
|
3529
|
+
* Extract the ONE insight, eliminate everything else.
|
|
3530
|
+
*/
|
|
3531
|
+
extractSingleInsight(content, maxWords) {
|
|
3532
|
+
const cleaned = this.cleanText(content);
|
|
3533
|
+
const sentences = cleaned.match(/[^.!?]+[.!?]?/g) || [cleaned];
|
|
3534
|
+
const conclusionPatterns = [
|
|
3535
|
+
/(?:therefore|thus|hence|consequently|as a result|this means|the bottom line|in conclusion)/i,
|
|
3536
|
+
/(?:we (?:must|should|need to)|it's (?:critical|essential|important) to)/i,
|
|
3537
|
+
/(?:the key (?:is|takeaway)|what this means)/i
|
|
3538
|
+
];
|
|
3539
|
+
for (const sentence of sentences) {
|
|
3540
|
+
for (const pattern of conclusionPatterns) {
|
|
3541
|
+
if (pattern.test(sentence)) {
|
|
3542
|
+
const words = sentence.trim().split(/\s+/);
|
|
3543
|
+
if (words.length <= maxWords) {
|
|
3544
|
+
return sentence.trim();
|
|
3545
|
+
}
|
|
3546
|
+
return this.extractKeyPhrase(sentence, maxWords);
|
|
3547
|
+
}
|
|
3548
|
+
}
|
|
3549
|
+
}
|
|
3550
|
+
return this.extractCoreIdea(content, maxWords);
|
|
3551
|
+
}
|
|
3552
|
+
/**
|
|
3553
|
+
* Chris Anderson - Gift Giving
|
|
3554
|
+
* What can they DO tomorrow?
|
|
3555
|
+
*/
|
|
3556
|
+
extractActionableStep(content, maxWords) {
|
|
3557
|
+
const cleaned = this.cleanText(content);
|
|
3558
|
+
const sentences = cleaned.match(/[^.!?]+[.!?]?/g) || [cleaned];
|
|
3559
|
+
const actionPatterns = [
|
|
3560
|
+
/(?:start|begin|try|implement|apply|use|adopt|build|create|develop)/i,
|
|
3561
|
+
/(?:step \d|first,|next,|then,|finally)/i,
|
|
3562
|
+
/(?:you (?:can|should|must|need to))/i
|
|
3563
|
+
];
|
|
3564
|
+
for (const sentence of sentences) {
|
|
3565
|
+
for (const pattern of actionPatterns) {
|
|
3566
|
+
if (pattern.test(sentence)) {
|
|
3567
|
+
const words = sentence.trim().split(/\s+/);
|
|
3568
|
+
if (words.length <= maxWords) {
|
|
3569
|
+
return sentence.trim();
|
|
3570
|
+
}
|
|
3571
|
+
return this.extractKeyPhrase(sentence, maxWords);
|
|
3572
|
+
}
|
|
3573
|
+
}
|
|
3574
|
+
}
|
|
3575
|
+
return this.extractCoreIdea(content, maxWords);
|
|
3576
|
+
}
|
|
3577
|
+
// ===========================================================================
|
|
2355
3578
|
// HELPER METHODS - All use KB configuration
|
|
2356
3579
|
// ===========================================================================
|
|
2357
3580
|
/**
|
|
@@ -2578,6 +3801,9 @@ var TemplateEngine = class {
|
|
|
2578
3801
|
this.handlebars.registerHelper("animDelay", function(index, baseMs = 100) {
|
|
2579
3802
|
return `animation-delay: ${index * baseMs}ms`;
|
|
2580
3803
|
});
|
|
3804
|
+
this.handlebars.registerHelper("add", function(a, b) {
|
|
3805
|
+
return a + b;
|
|
3806
|
+
});
|
|
2581
3807
|
this.handlebars.registerHelper("safeHTML", function(text) {
|
|
2582
3808
|
return new import_handlebars.default.SafeString(text);
|
|
2583
3809
|
});
|
|
@@ -2685,7 +3911,12 @@ var TemplateEngine = class {
|
|
|
2685
3911
|
this.templates.set("single-statement", this.handlebars.compile(`
|
|
2686
3912
|
<section class="{{classes}}" data-slide-index="{{slideIndex}}" style="{{styles}}">
|
|
2687
3913
|
<div class="slide-content statement-content">
|
|
3914
|
+
{{#if body}}
|
|
3915
|
+
<h2 class="title animate-fadeIn">{{title}}</h2>
|
|
3916
|
+
<p class="statement animate-fadeIn delay-200">{{markdown body}}</p>
|
|
3917
|
+
{{else}}
|
|
2688
3918
|
<p class="statement animate-fadeIn">{{title}}</p>
|
|
3919
|
+
{{/if}}
|
|
2689
3920
|
</div>
|
|
2690
3921
|
{{> speakerNotes}}
|
|
2691
3922
|
</section>
|
|
@@ -2751,14 +3982,30 @@ var TemplateEngine = class {
|
|
|
2751
3982
|
<h2 class="title animate-fadeIn">{{title}}</h2>
|
|
2752
3983
|
<div class="columns two-columns">
|
|
2753
3984
|
<div class="column column-left animate-slideRight">
|
|
2754
|
-
{{#if
|
|
2755
|
-
|
|
3985
|
+
{{#if leftColumn.title}}<h3>{{leftColumn.title}}</h3>{{/if}}
|
|
3986
|
+
{{#if leftColumn.body}}
|
|
3987
|
+
<p class="body">{{markdown leftColumn.body}}</p>
|
|
2756
3988
|
{{/if}}
|
|
2757
|
-
{{#if
|
|
2758
|
-
|
|
3989
|
+
{{#if leftColumn.bullets}}
|
|
3990
|
+
<ul class="bullets stagger-children">
|
|
3991
|
+
{{#each leftColumn.bullets}}
|
|
3992
|
+
<li class="bullet animate-fadeIn" style="{{animDelay @index 150}}">{{markdown this}}</li>
|
|
3993
|
+
{{/each}}
|
|
3994
|
+
</ul>
|
|
2759
3995
|
{{/if}}
|
|
2760
3996
|
</div>
|
|
2761
3997
|
<div class="column column-right animate-slideLeft delay-200">
|
|
3998
|
+
{{#if rightColumn.title}}<h3>{{rightColumn.title}}</h3>{{/if}}
|
|
3999
|
+
{{#if rightColumn.body}}
|
|
4000
|
+
<p class="body">{{markdown rightColumn.body}}</p>
|
|
4001
|
+
{{/if}}
|
|
4002
|
+
{{#if rightColumn.bullets}}
|
|
4003
|
+
<ul class="bullets stagger-children">
|
|
4004
|
+
{{#each rightColumn.bullets}}
|
|
4005
|
+
<li class="bullet animate-fadeIn" style="{{animDelay @index 150}}">{{markdown this}}</li>
|
|
4006
|
+
{{/each}}
|
|
4007
|
+
</ul>
|
|
4008
|
+
{{/if}}
|
|
2762
4009
|
{{#if hasImage}}
|
|
2763
4010
|
{{> imageWithCaption images.[0]}}
|
|
2764
4011
|
{{else if metrics}}
|
|
@@ -2779,11 +4026,13 @@ var TemplateEngine = class {
|
|
|
2779
4026
|
{{#each columns}}
|
|
2780
4027
|
<div class="column animate-fadeIn" style="{{animDelay @index 200}}">
|
|
2781
4028
|
{{#if title}}<h3 class="column-title">{{title}}</h3>{{/if}}
|
|
4029
|
+
{{#if content}}<p class="column-body">{{markdown content}}</p>{{/if}}
|
|
2782
4030
|
{{#if body}}<p class="column-body">{{markdown body}}</p>{{/if}}
|
|
2783
4031
|
{{#if icon}}<div class="column-icon">{{icon}}</div>{{/if}}
|
|
2784
4032
|
</div>
|
|
2785
4033
|
{{/each}}
|
|
2786
4034
|
</div>
|
|
4035
|
+
{{> source}}
|
|
2787
4036
|
</div>
|
|
2788
4037
|
{{> speakerNotes}}
|
|
2789
4038
|
</section>
|
|
@@ -2792,25 +4041,24 @@ var TemplateEngine = class {
|
|
|
2792
4041
|
<section class="{{classes}}" data-slide-index="{{slideIndex}}" style="{{styles}}">
|
|
2793
4042
|
<div class="slide-content">
|
|
2794
4043
|
<h2 class="title animate-fadeIn">{{title}}</h2>
|
|
2795
|
-
<div class="comparison-container">
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
<
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
</div>
|
|
2804
|
-
<div class="comparison-divider"></div>
|
|
2805
|
-
<div class="comparison-right animate-slideLeft delay-200">
|
|
2806
|
-
<h3>{{rightTitle}}</h3>
|
|
4044
|
+
<div class="comparison-container columns two-columns">
|
|
4045
|
+
{{#each columns}}
|
|
4046
|
+
<div class="column comparison-{{#if @first}}left animate-slideRight{{else}}right animate-slideLeft delay-200{{/if}}">
|
|
4047
|
+
<h3 class="column-title">{{title}}</h3>
|
|
4048
|
+
{{#if content}}
|
|
4049
|
+
<p class="column-content">{{markdown content}}</p>
|
|
4050
|
+
{{/if}}
|
|
4051
|
+
{{#if bullets}}
|
|
2807
4052
|
<ul>
|
|
2808
|
-
{{#each
|
|
4053
|
+
{{#each bullets}}
|
|
2809
4054
|
<li>{{this}}</li>
|
|
2810
4055
|
{{/each}}
|
|
2811
4056
|
</ul>
|
|
4057
|
+
{{/if}}
|
|
2812
4058
|
</div>
|
|
4059
|
+
{{/each}}
|
|
2813
4060
|
</div>
|
|
4061
|
+
{{> source}}
|
|
2814
4062
|
</div>
|
|
2815
4063
|
{{> speakerNotes}}
|
|
2816
4064
|
</section>
|
|
@@ -2874,18 +4122,18 @@ var TemplateEngine = class {
|
|
|
2874
4122
|
<section class="{{classes}}" data-slide-index="{{slideIndex}}" style="{{styles}}">
|
|
2875
4123
|
<div class="slide-content">
|
|
2876
4124
|
<h2 class="title animate-fadeIn">{{title}}</h2>
|
|
2877
|
-
<div class="timeline stagger-children">
|
|
2878
|
-
{{#each
|
|
2879
|
-
<div class="timeline-item animate-fadeIn" style="{{animDelay @index 200}}">
|
|
2880
|
-
<div class="
|
|
4125
|
+
<div class="timeline steps stagger-children">
|
|
4126
|
+
{{#each steps}}
|
|
4127
|
+
<div class="step timeline-item animate-fadeIn" style="{{animDelay @index 200}}">
|
|
4128
|
+
<div class="step-number">{{add @index 1}}</div>
|
|
2881
4129
|
<div class="timeline-content">
|
|
2882
|
-
<div class="
|
|
2883
|
-
<div class="
|
|
2884
|
-
{{#if description}}<div class="timeline-desc">{{description}}</div>{{/if}}
|
|
4130
|
+
<div class="step-title">{{label}}</div>
|
|
4131
|
+
{{#if description}}<div class="step-desc">{{description}}</div>{{/if}}
|
|
2885
4132
|
</div>
|
|
2886
4133
|
</div>
|
|
2887
4134
|
{{/each}}
|
|
2888
4135
|
</div>
|
|
4136
|
+
{{> source}}
|
|
2889
4137
|
</div>
|
|
2890
4138
|
{{> speakerNotes}}
|
|
2891
4139
|
</section>
|
|
@@ -3975,949 +5223,554 @@ var QAEngine = class {
|
|
|
3975
5223
|
}
|
|
3976
5224
|
};
|
|
3977
5225
|
|
|
3978
|
-
// src/qa/
|
|
3979
|
-
var
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
3985
|
-
|
|
3986
|
-
|
|
3987
|
-
};
|
|
3988
|
-
var SevenDimensionScorer = class {
|
|
3989
|
-
kb;
|
|
3990
|
-
mode;
|
|
3991
|
-
presentationType;
|
|
3992
|
-
constructor(mode, presentationType) {
|
|
3993
|
-
this.mode = mode;
|
|
3994
|
-
this.presentationType = presentationType;
|
|
3995
|
-
}
|
|
3996
|
-
/**
|
|
3997
|
-
* Score a presentation across all 7 dimensions.
|
|
3998
|
-
*/
|
|
3999
|
-
async score(slides, html, threshold = 95) {
|
|
4000
|
-
this.kb = await getKnowledgeGateway();
|
|
4001
|
-
const layout = await this.scoreLayout(slides, html);
|
|
4002
|
-
const contrast = await this.scoreContrast(html);
|
|
4003
|
-
const graphics = await this.scoreGraphics(slides);
|
|
4004
|
-
const content = await this.scoreContent(slides);
|
|
4005
|
-
const clarity = await this.scoreClarity(slides);
|
|
4006
|
-
const effectiveness = await this.scoreEffectiveness(slides);
|
|
4007
|
-
const consistency = await this.scoreConsistency(slides, html);
|
|
4008
|
-
const overallScore = Math.round(
|
|
4009
|
-
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
|
|
4010
|
-
);
|
|
4011
|
-
const issues = [
|
|
4012
|
-
...layout.issues,
|
|
4013
|
-
...contrast.issues,
|
|
4014
|
-
...graphics.issues,
|
|
4015
|
-
...content.issues,
|
|
4016
|
-
...clarity.issues,
|
|
4017
|
-
...effectiveness.issues,
|
|
4018
|
-
...consistency.issues
|
|
4019
|
-
];
|
|
4020
|
-
return {
|
|
4021
|
-
overallScore,
|
|
4022
|
-
dimensions: {
|
|
4023
|
-
layout,
|
|
4024
|
-
contrast,
|
|
4025
|
-
graphics,
|
|
4026
|
-
content,
|
|
4027
|
-
clarity,
|
|
4028
|
-
effectiveness,
|
|
4029
|
-
consistency
|
|
4030
|
-
},
|
|
4031
|
-
issues,
|
|
4032
|
-
passed: overallScore >= threshold,
|
|
4033
|
-
threshold
|
|
4034
|
-
};
|
|
4035
|
-
}
|
|
4036
|
-
/**
|
|
4037
|
-
* Score layout dimension (whitespace, visual balance, structure)
|
|
4038
|
-
*/
|
|
4039
|
-
async scoreLayout(slides, html) {
|
|
4040
|
-
const issues = [];
|
|
4041
|
-
let totalScore = 0;
|
|
4042
|
-
let checks = 0;
|
|
4043
|
-
const minWhitespace = this.mode === "keynote" ? 0.45 : 0.35;
|
|
4044
|
-
const maxWhitespace = 0.6;
|
|
4045
|
-
const slideSections = html.match(/<section[^>]*>[\s\S]*?<\/section>/gi) || [];
|
|
4046
|
-
for (let i = 0; i < slideSections.length; i++) {
|
|
4047
|
-
const section = slideSections[i];
|
|
4048
|
-
if (!section) continue;
|
|
4049
|
-
const textContent = section.replace(/<[^>]+>/g, "").trim();
|
|
4050
|
-
const totalArea = 1920 * 1080;
|
|
4051
|
-
const estimatedTextArea = textContent.length * 100;
|
|
4052
|
-
const whitespaceRatio = 1 - Math.min(estimatedTextArea / totalArea, 1);
|
|
4053
|
-
if (whitespaceRatio < minWhitespace) {
|
|
4054
|
-
issues.push({
|
|
4055
|
-
slideIndex: i,
|
|
4056
|
-
dimension: "layout",
|
|
4057
|
-
severity: "error",
|
|
4058
|
-
message: `Slide ${i + 1}: Insufficient whitespace (${Math.round(whitespaceRatio * 100)}% < ${Math.round(minWhitespace * 100)}%)`,
|
|
4059
|
-
currentValue: whitespaceRatio,
|
|
4060
|
-
expectedValue: minWhitespace,
|
|
4061
|
-
autoFixable: true,
|
|
4062
|
-
fixSuggestion: "Reduce content or increase margins"
|
|
4063
|
-
});
|
|
4064
|
-
totalScore += 50;
|
|
4065
|
-
} else if (whitespaceRatio > maxWhitespace) {
|
|
4066
|
-
issues.push({
|
|
4067
|
-
slideIndex: i,
|
|
4068
|
-
dimension: "layout",
|
|
4069
|
-
severity: "warning",
|
|
4070
|
-
message: `Slide ${i + 1}: Too much whitespace (${Math.round(whitespaceRatio * 100)}% > ${Math.round(maxWhitespace * 100)}%)`,
|
|
4071
|
-
currentValue: whitespaceRatio,
|
|
4072
|
-
expectedValue: maxWhitespace,
|
|
4073
|
-
autoFixable: false,
|
|
4074
|
-
fixSuggestion: "Add more content or reduce margins"
|
|
4075
|
-
});
|
|
4076
|
-
totalScore += 80;
|
|
4077
|
-
} else {
|
|
4078
|
-
totalScore += 100;
|
|
4079
|
-
}
|
|
4080
|
-
checks++;
|
|
4081
|
-
}
|
|
4082
|
-
for (let i = 0; i < slides.length; i++) {
|
|
4083
|
-
const slide = slides[i];
|
|
4084
|
-
if (!slide) continue;
|
|
4085
|
-
if (!["thank-you", "section-divider"].includes(slide.type)) {
|
|
4086
|
-
if (!slide.data.title || slide.data.title.trim().length === 0) {
|
|
4087
|
-
issues.push({
|
|
4088
|
-
slideIndex: i,
|
|
4089
|
-
dimension: "layout",
|
|
4090
|
-
severity: "warning",
|
|
4091
|
-
message: `Slide ${i + 1}: Missing title`,
|
|
4092
|
-
autoFixable: false,
|
|
4093
|
-
fixSuggestion: "Add a clear slide title"
|
|
4094
|
-
});
|
|
4095
|
-
totalScore += 70;
|
|
4096
|
-
} else {
|
|
4097
|
-
totalScore += 100;
|
|
4098
|
-
}
|
|
4099
|
-
checks++;
|
|
4100
|
-
}
|
|
4101
|
-
}
|
|
4102
|
-
const score = checks > 0 ? Math.round(totalScore / checks) : 100;
|
|
4103
|
-
return {
|
|
4104
|
-
name: "Layout",
|
|
4105
|
-
score,
|
|
4106
|
-
weight: DIMENSION_WEIGHTS.layout,
|
|
4107
|
-
issues,
|
|
4108
|
-
details: {
|
|
4109
|
-
slidesAnalyzed: slides.length,
|
|
4110
|
-
whitespaceTarget: `${Math.round(minWhitespace * 100)}-${Math.round(maxWhitespace * 100)}%`
|
|
4111
|
-
}
|
|
4112
|
-
};
|
|
4113
|
-
}
|
|
4114
|
-
/**
|
|
4115
|
-
* Score contrast dimension (WCAG compliance, readability)
|
|
4116
|
-
*/
|
|
4117
|
-
async scoreContrast(html) {
|
|
4118
|
-
const issues = [];
|
|
4119
|
-
let score = 100;
|
|
4120
|
-
const minContrastRatio = 4.5;
|
|
4121
|
-
const colorMatches = html.match(/color:\s*([^;]+);/gi) || [];
|
|
4122
|
-
const bgColorMatches = html.match(/background(-color)?:\s*([^;]+);/gi) || [];
|
|
4123
|
-
const hasGoodContrast = html.includes("color: #fff") || html.includes("color: white") || html.includes("color: #18181B") || html.includes("color: #F5F5F4");
|
|
4124
|
-
const hasDarkBackground = html.includes("background-color: #18181B") || html.includes("background: #18181B") || html.includes("background-color: rgb(24, 24, 27)");
|
|
4125
|
-
if (html.includes("color: gray") || html.includes("color: #999") || html.includes("color: #888")) {
|
|
4126
|
-
issues.push({
|
|
4127
|
-
slideIndex: -1,
|
|
4128
|
-
dimension: "contrast",
|
|
4129
|
-
severity: "error",
|
|
4130
|
-
message: "Low contrast text color detected (gray text)",
|
|
4131
|
-
currentValue: "gray",
|
|
4132
|
-
expectedValue: "High contrast color",
|
|
4133
|
-
autoFixable: true,
|
|
4134
|
-
fixSuggestion: "Use white (#fff) or dark (#18181B) text depending on background"
|
|
4135
|
-
});
|
|
4136
|
-
score -= 20;
|
|
4137
|
-
}
|
|
4138
|
-
if (!hasGoodContrast && !hasDarkBackground) {
|
|
4139
|
-
issues.push({
|
|
4140
|
-
slideIndex: -1,
|
|
4141
|
-
dimension: "contrast",
|
|
4142
|
-
severity: "warning",
|
|
4143
|
-
message: "Could not verify WCAG-compliant contrast ratios",
|
|
4144
|
-
autoFixable: false,
|
|
4145
|
-
fixSuggestion: "Ensure text has 4.5:1 contrast ratio against background"
|
|
4146
|
-
});
|
|
4147
|
-
score -= 10;
|
|
4148
|
-
}
|
|
4149
|
-
const hasSmallFont = html.match(/font-size:\s*(1[0-3]|[0-9])px/i) !== null;
|
|
4150
|
-
if (hasSmallFont) {
|
|
4151
|
-
issues.push({
|
|
4152
|
-
slideIndex: -1,
|
|
4153
|
-
dimension: "contrast",
|
|
4154
|
-
severity: "error",
|
|
4155
|
-
message: "Font size too small for presentation (< 14px)",
|
|
4156
|
-
currentValue: "Small font",
|
|
4157
|
-
expectedValue: "18px minimum for body text",
|
|
4158
|
-
autoFixable: true,
|
|
4159
|
-
fixSuggestion: "Increase font size to minimum 18px"
|
|
4160
|
-
});
|
|
4161
|
-
score -= 15;
|
|
4162
|
-
}
|
|
4163
|
-
return {
|
|
4164
|
-
name: "Contrast",
|
|
4165
|
-
score: Math.max(0, score),
|
|
4166
|
-
weight: DIMENSION_WEIGHTS.contrast,
|
|
4167
|
-
issues,
|
|
4168
|
-
details: {
|
|
4169
|
-
wcagLevel: "AA",
|
|
4170
|
-
minContrastRatio,
|
|
4171
|
-
colorDefinitions: colorMatches.length,
|
|
4172
|
-
backgroundDefinitions: bgColorMatches.length
|
|
4173
|
-
}
|
|
4174
|
-
};
|
|
4175
|
-
}
|
|
4176
|
-
/**
|
|
4177
|
-
* Score graphics dimension (images, placement, relevance)
|
|
4178
|
-
*/
|
|
4179
|
-
async scoreGraphics(slides) {
|
|
4180
|
-
const issues = [];
|
|
4181
|
-
let score = 100;
|
|
4182
|
-
let slidesWithImages = 0;
|
|
4183
|
-
let totalImageSlides = 0;
|
|
4184
|
-
for (let i = 0; i < slides.length; i++) {
|
|
4185
|
-
const slide = slides[i];
|
|
4186
|
-
if (!slide) continue;
|
|
4187
|
-
const shouldHaveImage = ["hero", "image", "feature"].includes(slide.type);
|
|
4188
|
-
if (shouldHaveImage) {
|
|
4189
|
-
totalImageSlides++;
|
|
4190
|
-
if (slide.data.image || slide.data.backgroundImage) {
|
|
4191
|
-
slidesWithImages++;
|
|
4192
|
-
} else {
|
|
4193
|
-
issues.push({
|
|
4194
|
-
slideIndex: i,
|
|
4195
|
-
dimension: "graphics",
|
|
4196
|
-
severity: "warning",
|
|
4197
|
-
message: `Slide ${i + 1} (${slide.type}): Missing expected image`,
|
|
4198
|
-
autoFixable: false,
|
|
4199
|
-
fixSuggestion: "Add a relevant image or change slide type"
|
|
4200
|
-
});
|
|
4201
|
-
score -= 5;
|
|
4202
|
-
}
|
|
4203
|
-
}
|
|
4204
|
-
}
|
|
4205
|
-
return {
|
|
4206
|
-
name: "Graphics",
|
|
4207
|
-
score: Math.max(0, score),
|
|
4208
|
-
weight: DIMENSION_WEIGHTS.graphics,
|
|
4209
|
-
issues,
|
|
4210
|
-
details: {
|
|
4211
|
-
slidesWithImages,
|
|
4212
|
-
expectedImageSlides: totalImageSlides,
|
|
4213
|
-
imageRatio: totalImageSlides > 0 ? slidesWithImages / totalImageSlides : 1
|
|
4214
|
-
}
|
|
4215
|
-
};
|
|
4216
|
-
}
|
|
4217
|
-
/**
|
|
4218
|
-
* Score content dimension (word limits, bullet counts, structure)
|
|
4219
|
-
* This is critical - must use KB rules exactly.
|
|
4220
|
-
*/
|
|
4221
|
-
async scoreContent(slides) {
|
|
4222
|
-
const issues = [];
|
|
4223
|
-
let totalScore = 0;
|
|
4224
|
-
let checks = 0;
|
|
4225
|
-
const wordLimits = this.kb.getWordLimits(this.mode);
|
|
4226
|
-
const bulletLimits = this.kb.getBulletLimits(this.mode);
|
|
4227
|
-
const defaultMaxWords = this.mode === "keynote" ? 25 : 80;
|
|
4228
|
-
const defaultMinWords = this.mode === "keynote" ? 3 : 15;
|
|
4229
|
-
const defaultMaxBullets = 5;
|
|
4230
|
-
for (let i = 0; i < slides.length; i++) {
|
|
4231
|
-
const slide = slides[i];
|
|
4232
|
-
if (!slide) continue;
|
|
4233
|
-
const wordCount = this.countWords(slide);
|
|
4234
|
-
const slideType = slide.type;
|
|
4235
|
-
const maxWords = wordLimits[slideType] ?? defaultMaxWords;
|
|
4236
|
-
const minWords = defaultMinWords;
|
|
4237
|
-
if (wordCount > maxWords) {
|
|
4238
|
-
const severity = wordCount > maxWords * 1.5 ? "error" : "warning";
|
|
4239
|
-
issues.push({
|
|
4240
|
-
slideIndex: i,
|
|
4241
|
-
dimension: "content",
|
|
4242
|
-
severity,
|
|
4243
|
-
message: `Slide ${i + 1}: Word count ${wordCount} exceeds limit of ${maxWords} for ${this.mode} mode`,
|
|
4244
|
-
currentValue: wordCount,
|
|
4245
|
-
expectedValue: maxWords,
|
|
4246
|
-
autoFixable: true,
|
|
4247
|
-
fixSuggestion: "Condense text to key points only"
|
|
4248
|
-
});
|
|
4249
|
-
totalScore += severity === "error" ? 40 : 70;
|
|
4250
|
-
} else if (wordCount < minWords && !["title", "section-divider", "thank-you"].includes(slide.type)) {
|
|
4251
|
-
issues.push({
|
|
4252
|
-
slideIndex: i,
|
|
4253
|
-
dimension: "content",
|
|
4254
|
-
severity: "warning",
|
|
4255
|
-
message: `Slide ${i + 1}: Word count ${wordCount} may be too sparse (min: ${minWords})`,
|
|
4256
|
-
currentValue: wordCount,
|
|
4257
|
-
expectedValue: minWords,
|
|
4258
|
-
autoFixable: false,
|
|
4259
|
-
fixSuggestion: "Add supporting content"
|
|
4260
|
-
});
|
|
4261
|
-
totalScore += 80;
|
|
4262
|
-
} else {
|
|
4263
|
-
totalScore += 100;
|
|
4264
|
-
}
|
|
4265
|
-
checks++;
|
|
4266
|
-
if (slide.data.bullets && Array.isArray(slide.data.bullets)) {
|
|
4267
|
-
const bulletCount = slide.data.bullets.length;
|
|
4268
|
-
const maxBullets = bulletLimits[slideType] ?? defaultMaxBullets;
|
|
4269
|
-
if (bulletCount > maxBullets) {
|
|
4270
|
-
issues.push({
|
|
4271
|
-
slideIndex: i,
|
|
4272
|
-
dimension: "content",
|
|
4273
|
-
severity: "error",
|
|
4274
|
-
message: `Slide ${i + 1}: ${bulletCount} bullets exceeds limit of ${maxBullets}`,
|
|
4275
|
-
currentValue: bulletCount,
|
|
4276
|
-
expectedValue: maxBullets,
|
|
4277
|
-
autoFixable: true,
|
|
4278
|
-
fixSuggestion: "Reduce to top 3-5 key points"
|
|
4279
|
-
});
|
|
4280
|
-
totalScore += 50;
|
|
4281
|
-
} else {
|
|
4282
|
-
totalScore += 100;
|
|
4283
|
-
}
|
|
4284
|
-
checks++;
|
|
4285
|
-
}
|
|
4286
|
-
}
|
|
4287
|
-
const score = checks > 0 ? Math.round(totalScore / checks) : 100;
|
|
4288
|
-
return {
|
|
4289
|
-
name: "Content",
|
|
4290
|
-
score,
|
|
4291
|
-
weight: DIMENSION_WEIGHTS.content,
|
|
4292
|
-
issues,
|
|
4293
|
-
details: {
|
|
4294
|
-
mode: this.mode,
|
|
4295
|
-
wordLimits,
|
|
4296
|
-
bulletLimits,
|
|
4297
|
-
totalSlides: slides.length
|
|
4298
|
-
}
|
|
4299
|
-
};
|
|
4300
|
-
}
|
|
4301
|
-
/**
|
|
4302
|
-
* Score clarity dimension (message focus, information density)
|
|
4303
|
-
*/
|
|
4304
|
-
async scoreClarity(slides) {
|
|
4305
|
-
const issues = [];
|
|
4306
|
-
let totalScore = 0;
|
|
4307
|
-
let checks = 0;
|
|
4308
|
-
for (let i = 0; i < slides.length; i++) {
|
|
4309
|
-
const slide = slides[i];
|
|
4310
|
-
if (!slide) continue;
|
|
4311
|
-
if (slide.data.keyMessage) {
|
|
4312
|
-
const keyMessageWords = slide.data.keyMessage.split(/\s+/).length;
|
|
4313
|
-
const maxKeyMessageWords = this.mode === "keynote" ? 15 : 25;
|
|
4314
|
-
if (keyMessageWords > maxKeyMessageWords) {
|
|
4315
|
-
issues.push({
|
|
4316
|
-
slideIndex: i,
|
|
4317
|
-
dimension: "clarity",
|
|
4318
|
-
severity: "warning",
|
|
4319
|
-
message: `Slide ${i + 1}: Key message too long (${keyMessageWords} words > ${maxKeyMessageWords})`,
|
|
4320
|
-
currentValue: keyMessageWords,
|
|
4321
|
-
expectedValue: maxKeyMessageWords,
|
|
4322
|
-
autoFixable: true,
|
|
4323
|
-
fixSuggestion: "Shorten key message to one impactful sentence"
|
|
4324
|
-
});
|
|
4325
|
-
totalScore += 70;
|
|
4326
|
-
} else {
|
|
4327
|
-
totalScore += 100;
|
|
4328
|
-
}
|
|
4329
|
-
checks++;
|
|
4330
|
-
}
|
|
4331
|
-
if (slide.data.title) {
|
|
4332
|
-
const title = slide.data.title;
|
|
4333
|
-
const titleWords = title.split(/\s+/).length;
|
|
4334
|
-
if (titleWords > 10) {
|
|
4335
|
-
issues.push({
|
|
4336
|
-
slideIndex: i,
|
|
4337
|
-
dimension: "clarity",
|
|
4338
|
-
severity: "warning",
|
|
4339
|
-
message: `Slide ${i + 1}: Title too long (${titleWords} words)`,
|
|
4340
|
-
currentValue: titleWords,
|
|
4341
|
-
expectedValue: "2-8 words",
|
|
4342
|
-
autoFixable: true,
|
|
4343
|
-
fixSuggestion: "Use action-oriented, concise title"
|
|
4344
|
-
});
|
|
4345
|
-
totalScore += 75;
|
|
4346
|
-
} else {
|
|
4347
|
-
totalScore += 100;
|
|
4348
|
-
}
|
|
4349
|
-
checks++;
|
|
4350
|
-
}
|
|
4351
|
-
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);
|
|
4352
|
-
const maxElements = this.mode === "keynote" ? 4 : 6;
|
|
4353
|
-
if (elementCount > maxElements) {
|
|
4354
|
-
issues.push({
|
|
4355
|
-
slideIndex: i,
|
|
4356
|
-
dimension: "clarity",
|
|
4357
|
-
severity: "warning",
|
|
4358
|
-
message: `Slide ${i + 1}: Too many elements (${elementCount} > ${maxElements})`,
|
|
4359
|
-
currentValue: elementCount,
|
|
4360
|
-
expectedValue: maxElements,
|
|
4361
|
-
autoFixable: true,
|
|
4362
|
-
fixSuggestion: "Split into multiple slides for clarity"
|
|
4363
|
-
});
|
|
4364
|
-
totalScore += 70;
|
|
4365
|
-
} else {
|
|
4366
|
-
totalScore += 100;
|
|
4367
|
-
}
|
|
4368
|
-
checks++;
|
|
4369
|
-
}
|
|
4370
|
-
const score = checks > 0 ? Math.round(totalScore / checks) : 100;
|
|
4371
|
-
return {
|
|
4372
|
-
name: "Clarity",
|
|
4373
|
-
score,
|
|
4374
|
-
weight: DIMENSION_WEIGHTS.clarity,
|
|
4375
|
-
issues,
|
|
4376
|
-
details: {
|
|
4377
|
-
slidesAnalyzed: slides.length,
|
|
4378
|
-
mode: this.mode
|
|
4379
|
-
}
|
|
4380
|
-
};
|
|
4381
|
-
}
|
|
4382
|
-
/**
|
|
4383
|
-
* Score effectiveness dimension (expert methodology compliance)
|
|
4384
|
-
*/
|
|
4385
|
-
async scoreEffectiveness(slides) {
|
|
4386
|
-
const issues = [];
|
|
4387
|
-
let score = 100;
|
|
4388
|
-
if (slides.length >= 3) {
|
|
4389
|
-
const firstSlide = slides[0];
|
|
4390
|
-
const lastSlide = slides[slides.length - 1];
|
|
4391
|
-
if (firstSlide && !["title", "hero"].includes(firstSlide.type)) {
|
|
4392
|
-
issues.push({
|
|
4393
|
-
slideIndex: 0,
|
|
4394
|
-
dimension: "effectiveness",
|
|
4395
|
-
severity: "warning",
|
|
4396
|
-
message: "Presentation should start with a title or hero slide",
|
|
4397
|
-
currentValue: firstSlide.type,
|
|
4398
|
-
expectedValue: "title or hero",
|
|
4399
|
-
autoFixable: false,
|
|
4400
|
-
fixSuggestion: "Add a compelling opening slide"
|
|
4401
|
-
});
|
|
4402
|
-
score -= 10;
|
|
4403
|
-
}
|
|
4404
|
-
if (lastSlide && !["thank-you", "cta", "closing"].includes(lastSlide.type)) {
|
|
4405
|
-
issues.push({
|
|
4406
|
-
slideIndex: slides.length - 1,
|
|
4407
|
-
dimension: "effectiveness",
|
|
4408
|
-
severity: "warning",
|
|
4409
|
-
message: "Presentation should end with a closing or CTA slide",
|
|
4410
|
-
currentValue: lastSlide.type,
|
|
4411
|
-
expectedValue: "thank-you, cta, or closing",
|
|
4412
|
-
autoFixable: false,
|
|
4413
|
-
fixSuggestion: "Add a clear call-to-action or closing"
|
|
4414
|
-
});
|
|
4415
|
-
score -= 10;
|
|
4416
|
-
}
|
|
4417
|
-
}
|
|
4418
|
-
const keyMessages = slides.filter((s) => s.data.keyMessage);
|
|
4419
|
-
if (keyMessages.length > 0 && keyMessages.length !== 3 && keyMessages.length > 4) {
|
|
4420
|
-
issues.push({
|
|
4421
|
-
slideIndex: -1,
|
|
4422
|
-
dimension: "effectiveness",
|
|
4423
|
-
severity: "info",
|
|
4424
|
-
message: `Consider using Rule of Three: ${keyMessages.length} key messages found`,
|
|
4425
|
-
currentValue: keyMessages.length,
|
|
4426
|
-
expectedValue: 3,
|
|
4427
|
-
autoFixable: false,
|
|
4428
|
-
fixSuggestion: "Group messages into 3 main themes"
|
|
4429
|
-
});
|
|
4430
|
-
score -= 5;
|
|
4431
|
-
}
|
|
4432
|
-
const hasScqaElements = slides.some(
|
|
4433
|
-
(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")
|
|
4434
|
-
);
|
|
4435
|
-
if (!hasScqaElements && this.presentationType === "consulting_deck") {
|
|
4436
|
-
issues.push({
|
|
4437
|
-
slideIndex: -1,
|
|
4438
|
-
dimension: "effectiveness",
|
|
4439
|
-
severity: "warning",
|
|
4440
|
-
message: "Consulting deck should follow SCQA structure (Situation, Complication, Question, Answer)",
|
|
4441
|
-
autoFixable: false,
|
|
4442
|
-
fixSuggestion: "Organize content using Barbara Minto Pyramid Principle"
|
|
4443
|
-
});
|
|
4444
|
-
score -= 10;
|
|
4445
|
-
}
|
|
4446
|
-
const firstSlideType = slides[0]?.type;
|
|
4447
|
-
const lastSlideType = slides[slides.length - 1]?.type;
|
|
4448
|
-
return {
|
|
4449
|
-
name: "Effectiveness",
|
|
4450
|
-
score: Math.max(0, score),
|
|
4451
|
-
weight: DIMENSION_WEIGHTS.effectiveness,
|
|
4452
|
-
issues,
|
|
4453
|
-
details: {
|
|
4454
|
-
presentationType: this.presentationType,
|
|
4455
|
-
slideCount: slides.length,
|
|
4456
|
-
hasOpeningSlide: firstSlideType ? ["title", "hero"].includes(firstSlideType) : false,
|
|
4457
|
-
hasClosingSlide: lastSlideType ? ["thank-you", "cta", "closing"].includes(lastSlideType) : false
|
|
4458
|
-
}
|
|
4459
|
-
};
|
|
5226
|
+
// src/qa/VisualQualityEvaluator.ts
|
|
5227
|
+
var path = __toESM(require("path"));
|
|
5228
|
+
var fs = __toESM(require("fs"));
|
|
5229
|
+
var VisualQualityEvaluator = class {
|
|
5230
|
+
browser = null;
|
|
5231
|
+
page = null;
|
|
5232
|
+
screenshotDir;
|
|
5233
|
+
constructor(screenshotDir = "/tmp/presentation-qa") {
|
|
5234
|
+
this.screenshotDir = screenshotDir;
|
|
4460
5235
|
}
|
|
4461
5236
|
/**
|
|
4462
|
-
*
|
|
5237
|
+
* Evaluate a presentation's visual quality.
|
|
5238
|
+
* This opens the HTML in a real browser and evaluates each slide.
|
|
4463
5239
|
*/
|
|
4464
|
-
async
|
|
4465
|
-
|
|
4466
|
-
|
|
4467
|
-
const hasCssVariables = html.includes("var(--") || html.includes(":root");
|
|
4468
|
-
if (!hasCssVariables) {
|
|
4469
|
-
issues.push({
|
|
4470
|
-
slideIndex: -1,
|
|
4471
|
-
dimension: "consistency",
|
|
4472
|
-
severity: "warning",
|
|
4473
|
-
message: "Presentation lacks CSS variables for consistent styling",
|
|
4474
|
-
autoFixable: true,
|
|
4475
|
-
fixSuggestion: "Use CSS variables for colors, fonts, and spacing"
|
|
4476
|
-
});
|
|
4477
|
-
score -= 10;
|
|
5240
|
+
async evaluate(htmlPath) {
|
|
5241
|
+
if (!fs.existsSync(this.screenshotDir)) {
|
|
5242
|
+
fs.mkdirSync(this.screenshotDir, { recursive: true });
|
|
4478
5243
|
}
|
|
4479
|
-
|
|
4480
|
-
|
|
4481
|
-
|
|
4482
|
-
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
|
|
4486
|
-
|
|
4487
|
-
|
|
5244
|
+
try {
|
|
5245
|
+
await this.launchBrowser();
|
|
5246
|
+
const absolutePath = path.resolve(htmlPath);
|
|
5247
|
+
await this.page.goto(`file://${absolutePath}`, { waitUntil: "networkidle" });
|
|
5248
|
+
await this.page.waitForSelector(".reveal", { timeout: 5e3 });
|
|
5249
|
+
await this.page.waitForTimeout(1e3);
|
|
5250
|
+
const slideCount = await this.getSlideCount();
|
|
5251
|
+
console.log(`Evaluating ${slideCount} slides...`);
|
|
5252
|
+
const slideScores = [];
|
|
5253
|
+
for (let i = 0; i < slideCount; i++) {
|
|
5254
|
+
const score = await this.evaluateSlide(i);
|
|
5255
|
+
slideScores.push(score);
|
|
4488
5256
|
}
|
|
5257
|
+
const narrativeFlow = this.evaluateNarrativeFlow(slideScores);
|
|
5258
|
+
const visualConsistency = this.evaluateVisualConsistency(slideScores);
|
|
5259
|
+
const contentQuality = this.evaluateContentQuality(slideScores);
|
|
5260
|
+
const executiveReadiness = this.evaluateExecutiveReadiness(slideScores);
|
|
5261
|
+
const overallScore = Math.round(
|
|
5262
|
+
narrativeFlow.score + visualConsistency.score + contentQuality.score + executiveReadiness.score
|
|
5263
|
+
);
|
|
5264
|
+
const verdict = this.determineVerdict(overallScore);
|
|
5265
|
+
const { topIssues, topStrengths } = this.extractTopIssuesAndStrengths(slideScores);
|
|
5266
|
+
return {
|
|
5267
|
+
overallScore,
|
|
5268
|
+
narrativeFlow,
|
|
5269
|
+
visualConsistency,
|
|
5270
|
+
contentQuality,
|
|
5271
|
+
executiveReadiness,
|
|
5272
|
+
slideScores,
|
|
5273
|
+
verdict,
|
|
5274
|
+
verdictExplanation: this.explainVerdict(verdict, overallScore),
|
|
5275
|
+
topIssues,
|
|
5276
|
+
topStrengths
|
|
5277
|
+
};
|
|
5278
|
+
} finally {
|
|
5279
|
+
await this.closeBrowser();
|
|
4489
5280
|
}
|
|
4490
|
-
if (titlePatterns.size > 1) {
|
|
4491
|
-
issues.push({
|
|
4492
|
-
slideIndex: -1,
|
|
4493
|
-
dimension: "consistency",
|
|
4494
|
-
severity: "warning",
|
|
4495
|
-
message: `Inconsistent title casing: ${Array.from(titlePatterns).join(", ")}`,
|
|
4496
|
-
autoFixable: true,
|
|
4497
|
-
fixSuggestion: "Use consistent title case throughout"
|
|
4498
|
-
});
|
|
4499
|
-
score -= 10;
|
|
4500
|
-
}
|
|
4501
|
-
const fontMatches = html.match(/font-family:\s*([^;]+);/gi) || [];
|
|
4502
|
-
const uniqueFonts = new Set(fontMatches.map((f) => f.toLowerCase()));
|
|
4503
|
-
if (uniqueFonts.size > 3) {
|
|
4504
|
-
issues.push({
|
|
4505
|
-
slideIndex: -1,
|
|
4506
|
-
dimension: "consistency",
|
|
4507
|
-
severity: "warning",
|
|
4508
|
-
message: `Too many font families (${uniqueFonts.size} > 3)`,
|
|
4509
|
-
autoFixable: true,
|
|
4510
|
-
fixSuggestion: "Use 2-3 complementary fonts max"
|
|
4511
|
-
});
|
|
4512
|
-
score -= 10;
|
|
4513
|
-
}
|
|
4514
|
-
return {
|
|
4515
|
-
name: "Consistency",
|
|
4516
|
-
score: Math.max(0, score),
|
|
4517
|
-
weight: DIMENSION_WEIGHTS.consistency,
|
|
4518
|
-
issues,
|
|
4519
|
-
details: {
|
|
4520
|
-
hasCssVariables,
|
|
4521
|
-
titlePatterns: Array.from(titlePatterns),
|
|
4522
|
-
fontFamilyCount: uniqueFonts.size
|
|
4523
|
-
}
|
|
4524
|
-
};
|
|
4525
5281
|
}
|
|
4526
|
-
|
|
4527
|
-
|
|
4528
|
-
|
|
4529
|
-
|
|
4530
|
-
|
|
4531
|
-
|
|
4532
|
-
|
|
4533
|
-
|
|
4534
|
-
|
|
4535
|
-
|
|
4536
|
-
return text.split(/\s+/).filter((w) => w.length > 0).length;
|
|
5282
|
+
// ===========================================================================
|
|
5283
|
+
// BROWSER MANAGEMENT
|
|
5284
|
+
// ===========================================================================
|
|
5285
|
+
async launchBrowser() {
|
|
5286
|
+
const { chromium: chromium2 } = await import("playwright");
|
|
5287
|
+
this.browser = await chromium2.launch({ headless: true });
|
|
5288
|
+
const context = await this.browser.newContext({
|
|
5289
|
+
viewport: { width: 1920, height: 1080 }
|
|
5290
|
+
});
|
|
5291
|
+
this.page = await context.newPage();
|
|
4537
5292
|
}
|
|
4538
|
-
|
|
4539
|
-
|
|
4540
|
-
|
|
4541
|
-
|
|
4542
|
-
|
|
4543
|
-
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");
|
|
4544
|
-
lines.push("\u2551 7-DIMENSION QUALITY ASSESSMENT \u2551");
|
|
4545
|
-
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");
|
|
4546
|
-
lines.push("");
|
|
4547
|
-
lines.push(`Overall Score: ${result.overallScore}/100 ${result.passed ? "\u2705 PASSED" : "\u274C FAILED"}`);
|
|
4548
|
-
lines.push(`Threshold: ${result.threshold}/100`);
|
|
4549
|
-
lines.push("");
|
|
4550
|
-
lines.push("Dimension Breakdown:");
|
|
4551
|
-
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");
|
|
4552
|
-
const dims = result.dimensions;
|
|
4553
|
-
const formatDim = (name, d) => {
|
|
4554
|
-
const bar = "\u2588".repeat(Math.floor(d.score / 10)) + "\u2591".repeat(10 - Math.floor(d.score / 10));
|
|
4555
|
-
const status = d.score >= 95 ? "\u2705" : d.score >= 80 ? "\u26A0\uFE0F" : "\u274C";
|
|
4556
|
-
return `${status} ${name.padEnd(14)} ${bar} ${d.score.toString().padStart(3)}/100 (${(d.weight * 100).toFixed(0)}%)`;
|
|
4557
|
-
};
|
|
4558
|
-
lines.push(formatDim("Layout", dims.layout));
|
|
4559
|
-
lines.push(formatDim("Contrast", dims.contrast));
|
|
4560
|
-
lines.push(formatDim("Graphics", dims.graphics));
|
|
4561
|
-
lines.push(formatDim("Content", dims.content));
|
|
4562
|
-
lines.push(formatDim("Clarity", dims.clarity));
|
|
4563
|
-
lines.push(formatDim("Effectiveness", dims.effectiveness));
|
|
4564
|
-
lines.push(formatDim("Consistency", dims.consistency));
|
|
4565
|
-
lines.push("");
|
|
4566
|
-
const errors = result.issues.filter((i) => i.severity === "error");
|
|
4567
|
-
const warnings = result.issues.filter((i) => i.severity === "warning");
|
|
4568
|
-
if (errors.length > 0) {
|
|
4569
|
-
lines.push("\u274C Errors:");
|
|
4570
|
-
errors.forEach((e) => lines.push(` \u2022 ${e.message}`));
|
|
4571
|
-
lines.push("");
|
|
4572
|
-
}
|
|
4573
|
-
if (warnings.length > 0) {
|
|
4574
|
-
lines.push("\u26A0\uFE0F Warnings:");
|
|
4575
|
-
warnings.slice(0, 10).forEach((w) => lines.push(` \u2022 ${w.message}`));
|
|
4576
|
-
if (warnings.length > 10) {
|
|
4577
|
-
lines.push(` ... and ${warnings.length - 10} more warnings`);
|
|
4578
|
-
}
|
|
4579
|
-
lines.push("");
|
|
4580
|
-
}
|
|
4581
|
-
const autoFixable = result.issues.filter((i) => i.autoFixable);
|
|
4582
|
-
if (autoFixable.length > 0) {
|
|
4583
|
-
lines.push(`\u{1F527} ${autoFixable.length} issues can be auto-fixed`);
|
|
5293
|
+
async closeBrowser() {
|
|
5294
|
+
if (this.browser) {
|
|
5295
|
+
await this.browser.close();
|
|
5296
|
+
this.browser = null;
|
|
5297
|
+
this.page = null;
|
|
4584
5298
|
}
|
|
4585
|
-
return lines.join("\n");
|
|
4586
5299
|
}
|
|
4587
|
-
|
|
4588
|
-
|
|
4589
|
-
|
|
4590
|
-
|
|
4591
|
-
|
|
4592
|
-
mode;
|
|
4593
|
-
presentationType;
|
|
4594
|
-
constructor(mode, presentationType) {
|
|
4595
|
-
this.mode = mode;
|
|
4596
|
-
this.presentationType = presentationType;
|
|
5300
|
+
async getSlideCount() {
|
|
5301
|
+
return await this.page.evaluate(() => {
|
|
5302
|
+
const slides = document.querySelectorAll(".slides > section");
|
|
5303
|
+
return slides.length;
|
|
5304
|
+
});
|
|
4597
5305
|
}
|
|
4598
|
-
|
|
4599
|
-
|
|
4600
|
-
|
|
4601
|
-
async
|
|
4602
|
-
this.
|
|
4603
|
-
|
|
4604
|
-
|
|
4605
|
-
|
|
4606
|
-
|
|
4607
|
-
|
|
4608
|
-
|
|
4609
|
-
|
|
4610
|
-
fixesApplied.push(result);
|
|
4611
|
-
} else {
|
|
4612
|
-
fixesSkipped.push(result);
|
|
5306
|
+
// ===========================================================================
|
|
5307
|
+
// SLIDE-LEVEL EVALUATION
|
|
5308
|
+
// ===========================================================================
|
|
5309
|
+
async evaluateSlide(slideIndex) {
|
|
5310
|
+
await this.page.evaluate((index) => {
|
|
5311
|
+
window.Reveal?.slide(index, 0);
|
|
5312
|
+
}, slideIndex);
|
|
5313
|
+
await this.page.waitForTimeout(600);
|
|
5314
|
+
await this.page.evaluate(() => {
|
|
5315
|
+
const currentSlide = document.querySelector(".present");
|
|
5316
|
+
if (currentSlide) {
|
|
5317
|
+
currentSlide.getAnimations().forEach((anim) => anim.finish());
|
|
4613
5318
|
}
|
|
5319
|
+
});
|
|
5320
|
+
await this.page.waitForTimeout(100);
|
|
5321
|
+
const screenshotPath = path.join(this.screenshotDir, `slide-${slideIndex}.png`);
|
|
5322
|
+
await this.page.screenshot({ path: screenshotPath, fullPage: false });
|
|
5323
|
+
const slideData = await this.page.evaluate(() => {
|
|
5324
|
+
const currentSlide = document.querySelector(".present");
|
|
5325
|
+
if (!currentSlide) return null;
|
|
5326
|
+
const title = currentSlide.querySelector("h1, h2, .title")?.textContent?.trim() || "";
|
|
5327
|
+
const body = currentSlide.querySelector(".body, p:not(.subtitle)")?.textContent?.trim() || "";
|
|
5328
|
+
const bullets = Array.from(currentSlide.querySelectorAll("li")).map((li) => li.textContent?.trim() || "");
|
|
5329
|
+
const hasImage = !!currentSlide.querySelector("img");
|
|
5330
|
+
const hasChart = !!currentSlide.querySelector(".chart, svg, canvas");
|
|
5331
|
+
const classList = Array.from(currentSlide.classList);
|
|
5332
|
+
const backgroundColor = window.getComputedStyle(currentSlide).backgroundColor;
|
|
5333
|
+
const titleEl = currentSlide.querySelector("h1, h2, .title");
|
|
5334
|
+
const titleStyles = titleEl ? window.getComputedStyle(titleEl) : null;
|
|
5335
|
+
return {
|
|
5336
|
+
title,
|
|
5337
|
+
body,
|
|
5338
|
+
bullets,
|
|
5339
|
+
hasImage,
|
|
5340
|
+
hasChart,
|
|
5341
|
+
classList,
|
|
5342
|
+
backgroundColor,
|
|
5343
|
+
titleFontSize: titleStyles?.fontSize || "",
|
|
5344
|
+
titleColor: titleStyles?.color || "",
|
|
5345
|
+
contentLength: (title + body + bullets.join(" ")).length
|
|
5346
|
+
};
|
|
5347
|
+
});
|
|
5348
|
+
return this.scoreSlide(slideIndex, slideData, screenshotPath);
|
|
5349
|
+
}
|
|
5350
|
+
scoreSlide(slideIndex, slideData, screenshotPath) {
|
|
5351
|
+
if (!slideData) {
|
|
5352
|
+
return {
|
|
5353
|
+
slideIndex,
|
|
5354
|
+
slideType: "unknown",
|
|
5355
|
+
visualImpact: 0,
|
|
5356
|
+
visualImpactNotes: "Could not analyze slide",
|
|
5357
|
+
contentClarity: 0,
|
|
5358
|
+
contentClarityNotes: "Could not analyze slide",
|
|
5359
|
+
professionalPolish: 0,
|
|
5360
|
+
professionalPolishNotes: "Could not analyze slide",
|
|
5361
|
+
themeCoherence: 0,
|
|
5362
|
+
themeCoherenceNotes: "Could not analyze slide",
|
|
5363
|
+
totalScore: 0,
|
|
5364
|
+
screenshotPath
|
|
5365
|
+
};
|
|
5366
|
+
}
|
|
5367
|
+
const slideType = this.inferSlideType(slideData);
|
|
5368
|
+
let visualImpact = 5;
|
|
5369
|
+
const visualNotes = [];
|
|
5370
|
+
if (slideData.hasImage) {
|
|
5371
|
+
visualImpact += 2;
|
|
5372
|
+
visualNotes.push("Has imagery");
|
|
4614
5373
|
}
|
|
4615
|
-
|
|
5374
|
+
if (slideData.hasChart) {
|
|
5375
|
+
visualImpact += 2;
|
|
5376
|
+
visualNotes.push("Has data visualization");
|
|
5377
|
+
}
|
|
5378
|
+
const highImpactTypes = [
|
|
5379
|
+
"big-number",
|
|
5380
|
+
"big_number",
|
|
5381
|
+
"metrics-grid",
|
|
5382
|
+
"metrics_grid",
|
|
5383
|
+
"three-column",
|
|
5384
|
+
"three_column",
|
|
5385
|
+
"three-points",
|
|
5386
|
+
"three_points",
|
|
5387
|
+
"title-impact",
|
|
5388
|
+
"title_impact",
|
|
5389
|
+
"cta",
|
|
5390
|
+
"call-to-action",
|
|
5391
|
+
"comparison",
|
|
5392
|
+
"timeline",
|
|
5393
|
+
"process",
|
|
5394
|
+
"quote",
|
|
5395
|
+
"testimonial"
|
|
5396
|
+
];
|
|
5397
|
+
if (highImpactTypes.some((t) => slideType.includes(t.replace(/_/g, "-")) || slideType.includes(t.replace(/-/g, "_")))) {
|
|
5398
|
+
visualImpact += 2;
|
|
5399
|
+
visualNotes.push("High-impact slide type");
|
|
5400
|
+
}
|
|
5401
|
+
if (slideType === "single-statement" && slideData.body.length < 50) {
|
|
5402
|
+
visualImpact += 2;
|
|
5403
|
+
visualNotes.push("Clean single statement");
|
|
5404
|
+
}
|
|
5405
|
+
if (slideType === "title" && slideData.title.length > 0 && slideData.title.length < 80) {
|
|
5406
|
+
visualImpact += 1;
|
|
5407
|
+
visualNotes.push("Strong title");
|
|
5408
|
+
}
|
|
5409
|
+
if (slideData.contentLength > 300) {
|
|
5410
|
+
visualImpact -= 3;
|
|
5411
|
+
visualNotes.push("Too much text - overwhelming");
|
|
5412
|
+
}
|
|
5413
|
+
if (slideData.bullets.length > 5) {
|
|
5414
|
+
visualImpact -= 2;
|
|
5415
|
+
visualNotes.push("Too many bullets");
|
|
5416
|
+
}
|
|
5417
|
+
visualImpact = Math.max(0, Math.min(10, visualImpact));
|
|
5418
|
+
let contentClarity = 7;
|
|
5419
|
+
const clarityNotes = [];
|
|
5420
|
+
if (slideData.title.length > 80) {
|
|
5421
|
+
contentClarity -= 2;
|
|
5422
|
+
clarityNotes.push("Title too long");
|
|
5423
|
+
}
|
|
5424
|
+
if (slideData.title.length === 0) {
|
|
5425
|
+
contentClarity -= 3;
|
|
5426
|
+
clarityNotes.push("No title");
|
|
5427
|
+
}
|
|
5428
|
+
if (slideData.body && slideData.body.length > 0 && slideData.body.length < 200) {
|
|
5429
|
+
contentClarity += 1;
|
|
5430
|
+
clarityNotes.push("Good content length");
|
|
5431
|
+
}
|
|
5432
|
+
const avgBulletLength = slideData.bullets.length > 0 ? slideData.bullets.reduce((sum, b) => sum + b.length, 0) / slideData.bullets.length : 0;
|
|
5433
|
+
if (avgBulletLength > 100) {
|
|
5434
|
+
contentClarity -= 2;
|
|
5435
|
+
clarityNotes.push("Bullets too long - not scannable");
|
|
5436
|
+
}
|
|
5437
|
+
contentClarity = Math.max(0, Math.min(10, contentClarity));
|
|
5438
|
+
let professionalPolish = 6;
|
|
5439
|
+
const polishNotes = [];
|
|
5440
|
+
const titleFontSize = parseFloat(slideData.titleFontSize || "0");
|
|
5441
|
+
if (titleFontSize >= 40) {
|
|
5442
|
+
professionalPolish += 2;
|
|
5443
|
+
polishNotes.push("Strong title typography");
|
|
5444
|
+
} else if (titleFontSize < 24) {
|
|
5445
|
+
professionalPolish -= 1;
|
|
5446
|
+
polishNotes.push("Title could be more prominent");
|
|
5447
|
+
}
|
|
5448
|
+
if (slideData.contentLength > 10 && slideData.contentLength < 200) {
|
|
5449
|
+
professionalPolish += 1;
|
|
5450
|
+
polishNotes.push("Well-balanced content");
|
|
5451
|
+
}
|
|
5452
|
+
const polishedTypes = [
|
|
5453
|
+
"big-number",
|
|
5454
|
+
"metrics-grid",
|
|
5455
|
+
"three-column",
|
|
5456
|
+
"three-points",
|
|
5457
|
+
"comparison",
|
|
5458
|
+
"timeline",
|
|
5459
|
+
"process",
|
|
5460
|
+
"cta",
|
|
5461
|
+
"title",
|
|
5462
|
+
"thank-you"
|
|
5463
|
+
];
|
|
5464
|
+
if (polishedTypes.some((t) => slideType.includes(t))) {
|
|
5465
|
+
professionalPolish += 1;
|
|
5466
|
+
polishNotes.push("Well-structured layout");
|
|
5467
|
+
}
|
|
5468
|
+
professionalPolish = Math.max(0, Math.min(10, professionalPolish));
|
|
5469
|
+
let themeCoherence = 7;
|
|
5470
|
+
const coherenceNotes = [];
|
|
5471
|
+
if (slideData.classList.some((c) => c.includes("slide-"))) {
|
|
5472
|
+
themeCoherence += 1;
|
|
5473
|
+
coherenceNotes.push("Has slide type class");
|
|
5474
|
+
}
|
|
5475
|
+
themeCoherence = Math.max(0, Math.min(10, themeCoherence));
|
|
5476
|
+
const totalScore = visualImpact + contentClarity + professionalPolish + themeCoherence;
|
|
4616
5477
|
return {
|
|
4617
|
-
|
|
4618
|
-
|
|
4619
|
-
|
|
4620
|
-
|
|
5478
|
+
slideIndex,
|
|
5479
|
+
slideType,
|
|
5480
|
+
visualImpact,
|
|
5481
|
+
visualImpactNotes: visualNotes.join("; ") || "Standard",
|
|
5482
|
+
contentClarity,
|
|
5483
|
+
contentClarityNotes: clarityNotes.join("; ") || "Good",
|
|
5484
|
+
professionalPolish,
|
|
5485
|
+
professionalPolishNotes: polishNotes.join("; ") || "Acceptable",
|
|
5486
|
+
themeCoherence,
|
|
5487
|
+
themeCoherenceNotes: coherenceNotes.join("; ") || "Consistent",
|
|
5488
|
+
totalScore,
|
|
5489
|
+
screenshotPath
|
|
4621
5490
|
};
|
|
4622
5491
|
}
|
|
4623
|
-
|
|
4624
|
-
|
|
4625
|
-
|
|
4626
|
-
|
|
4627
|
-
|
|
4628
|
-
|
|
4629
|
-
|
|
4630
|
-
|
|
4631
|
-
|
|
4632
|
-
|
|
4633
|
-
|
|
4634
|
-
|
|
4635
|
-
|
|
4636
|
-
|
|
4637
|
-
|
|
4638
|
-
|
|
4639
|
-
|
|
4640
|
-
|
|
4641
|
-
|
|
4642
|
-
|
|
4643
|
-
|
|
4644
|
-
|
|
4645
|
-
|
|
4646
|
-
|
|
4647
|
-
result.description = `No auto-fix available for ${issue.dimension}`;
|
|
4648
|
-
return result;
|
|
4649
|
-
}
|
|
5492
|
+
inferSlideType(slideData) {
|
|
5493
|
+
const classList = slideData.classList || [];
|
|
5494
|
+
for (const cls of classList) {
|
|
5495
|
+
if (cls.includes("metrics-grid")) return "metrics-grid";
|
|
5496
|
+
if (cls.includes("three-column")) return "three-column";
|
|
5497
|
+
if (cls.includes("three-points")) return "three-points";
|
|
5498
|
+
if (cls.includes("two-column")) return "two-column";
|
|
5499
|
+
if (cls.includes("big-number")) return "big-number";
|
|
5500
|
+
if (cls.includes("comparison")) return "comparison";
|
|
5501
|
+
if (cls.includes("timeline")) return "timeline";
|
|
5502
|
+
if (cls.includes("process")) return "process";
|
|
5503
|
+
if (cls.includes("quote")) return "quote";
|
|
5504
|
+
if (cls.includes("testimonial")) return "testimonial";
|
|
5505
|
+
if (cls.includes("cta")) return "cta";
|
|
5506
|
+
if (cls.includes("thank-you")) return "thank-you";
|
|
5507
|
+
if (cls.includes("title")) return "title";
|
|
5508
|
+
if (cls.includes("bullet")) return "bullet-points";
|
|
5509
|
+
if (cls.includes("single-statement")) return "single-statement";
|
|
5510
|
+
if (cls.includes("agenda")) return "agenda";
|
|
5511
|
+
}
|
|
5512
|
+
if (!slideData.body && slideData.bullets.length > 0) return "bullet-points";
|
|
5513
|
+
if (slideData.body && !slideData.bullets.length) return "content";
|
|
5514
|
+
if (slideData.hasChart) return "data-visualization";
|
|
5515
|
+
return "standard";
|
|
4650
5516
|
}
|
|
4651
|
-
|
|
4652
|
-
|
|
4653
|
-
|
|
4654
|
-
|
|
4655
|
-
|
|
4656
|
-
|
|
4657
|
-
|
|
4658
|
-
const
|
|
4659
|
-
if (!
|
|
4660
|
-
|
|
4661
|
-
|
|
4662
|
-
|
|
4663
|
-
|
|
4664
|
-
|
|
4665
|
-
|
|
4666
|
-
|
|
4667
|
-
|
|
4668
|
-
|
|
4669
|
-
|
|
4670
|
-
|
|
4671
|
-
|
|
4672
|
-
|
|
4673
|
-
|
|
4674
|
-
|
|
4675
|
-
|
|
4676
|
-
|
|
4677
|
-
|
|
5517
|
+
// ===========================================================================
|
|
5518
|
+
// PRESENTATION-LEVEL EVALUATION
|
|
5519
|
+
// ===========================================================================
|
|
5520
|
+
evaluateNarrativeFlow(slideScores) {
|
|
5521
|
+
let score = 25;
|
|
5522
|
+
const notes = [];
|
|
5523
|
+
const firstSlide = slideScores[0];
|
|
5524
|
+
const hasStrongOpening = Boolean(firstSlide && (firstSlide.slideType === "title" || firstSlide.visualImpact >= 7));
|
|
5525
|
+
if (!hasStrongOpening) {
|
|
5526
|
+
score -= 5;
|
|
5527
|
+
notes.push("Opening could be stronger");
|
|
5528
|
+
}
|
|
5529
|
+
const middleSlides = slideScores.slice(1, -1);
|
|
5530
|
+
const highImpactMiddle = middleSlides.filter((s) => s.visualImpact >= 7).length;
|
|
5531
|
+
const hasCompellingMiddle = highImpactMiddle >= middleSlides.length * 0.3;
|
|
5532
|
+
if (!hasCompellingMiddle) {
|
|
5533
|
+
score -= 7;
|
|
5534
|
+
notes.push("Middle section needs more visual variety");
|
|
5535
|
+
}
|
|
5536
|
+
const lastSlide = slideScores[slideScores.length - 1];
|
|
5537
|
+
const secondLastSlide = slideScores[slideScores.length - 2];
|
|
5538
|
+
const closingTypes = ["cta", "thank-you", "call-to-action"];
|
|
5539
|
+
const hasMemorableClose = Boolean(
|
|
5540
|
+
lastSlide && (closingTypes.includes(lastSlide.slideType) || lastSlide.visualImpact >= 7) || secondLastSlide && closingTypes.includes(secondLastSlide.slideType)
|
|
5541
|
+
);
|
|
5542
|
+
if (!hasMemorableClose) {
|
|
5543
|
+
score -= 5;
|
|
5544
|
+
notes.push("Ending should be more memorable");
|
|
4678
5545
|
}
|
|
4679
|
-
|
|
4680
|
-
|
|
4681
|
-
|
|
4682
|
-
|
|
4683
|
-
|
|
4684
|
-
result.newValue = slide.data.bullets.length;
|
|
4685
|
-
result.applied = true;
|
|
4686
|
-
result.description = `Reduced bullets from ${result.originalValue} to ${result.newValue}`;
|
|
4687
|
-
}
|
|
5546
|
+
const slideTypes = new Set(slideScores.map((s) => s.slideType));
|
|
5547
|
+
const storyArcComplete = slideTypes.size >= 3;
|
|
5548
|
+
if (!storyArcComplete) {
|
|
5549
|
+
score -= 5;
|
|
5550
|
+
notes.push("Needs more variety in slide types");
|
|
4688
5551
|
}
|
|
4689
|
-
return
|
|
5552
|
+
return {
|
|
5553
|
+
score: Math.max(0, score),
|
|
5554
|
+
hasStrongOpening,
|
|
5555
|
+
hasCompellingMiddle,
|
|
5556
|
+
hasMemorableClose,
|
|
5557
|
+
storyArcComplete,
|
|
5558
|
+
notes: notes.join(". ") || "Good narrative structure"
|
|
5559
|
+
};
|
|
4690
5560
|
}
|
|
4691
|
-
|
|
4692
|
-
|
|
4693
|
-
|
|
4694
|
-
|
|
4695
|
-
|
|
4696
|
-
|
|
5561
|
+
evaluateVisualConsistency(slideScores) {
|
|
5562
|
+
let score = 25;
|
|
5563
|
+
const notes = [];
|
|
5564
|
+
const visualScores = slideScores.map((s) => s.visualImpact);
|
|
5565
|
+
const avgVisual = visualScores.reduce((a, b) => a + b, 0) / visualScores.length;
|
|
5566
|
+
const variance = visualScores.reduce((sum, s) => sum + Math.pow(s - avgVisual, 2), 0) / visualScores.length;
|
|
5567
|
+
const colorPaletteConsistent = variance < 4;
|
|
5568
|
+
if (!colorPaletteConsistent) {
|
|
5569
|
+
score -= 5;
|
|
5570
|
+
notes.push("Visual quality varies too much between slides");
|
|
4697
5571
|
}
|
|
4698
|
-
const
|
|
4699
|
-
|
|
4700
|
-
|
|
4701
|
-
|
|
4702
|
-
|
|
4703
|
-
|
|
4704
|
-
slide.data.keyMessage = this.condenseText(slide.data.keyMessage, maxWords);
|
|
4705
|
-
result.newValue = slide.data.keyMessage;
|
|
4706
|
-
result.applied = true;
|
|
4707
|
-
result.description = `Shortened key message to ${maxWords} words`;
|
|
4708
|
-
}
|
|
5572
|
+
const polishScores = slideScores.map((s) => s.professionalPolish);
|
|
5573
|
+
const avgPolish = polishScores.reduce((a, b) => a + b, 0) / polishScores.length;
|
|
5574
|
+
const typographyConsistent = avgPolish >= 6;
|
|
5575
|
+
if (!typographyConsistent) {
|
|
5576
|
+
score -= 5;
|
|
5577
|
+
notes.push("Typography could be more polished");
|
|
4709
5578
|
}
|
|
4710
|
-
|
|
4711
|
-
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
|
|
4715
|
-
|
|
4716
|
-
slide.data.title = words.slice(0, 6).join(" ");
|
|
4717
|
-
}
|
|
4718
|
-
result.newValue = slide.data.title;
|
|
4719
|
-
result.applied = true;
|
|
4720
|
-
result.description = `Shortened title from ${originalLength} to ${slide.data.title.split(/\s+/).length} words`;
|
|
4721
|
-
}
|
|
5579
|
+
const coherenceScores = slideScores.map((s) => s.themeCoherence);
|
|
5580
|
+
const avgCoherence = coherenceScores.reduce((a, b) => a + b, 0) / coherenceScores.length;
|
|
5581
|
+
const layoutPatternsConsistent = avgCoherence >= 6;
|
|
5582
|
+
if (!layoutPatternsConsistent) {
|
|
5583
|
+
score -= 5;
|
|
5584
|
+
notes.push("Layout patterns should be more consistent");
|
|
4722
5585
|
}
|
|
4723
|
-
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
|
|
4727
|
-
result.applied = true;
|
|
4728
|
-
result.description = "Removed subtitle to reduce element count";
|
|
4729
|
-
} else if (slide.data.body && slide.data.bullets && slide.data.bullets.length > 0) {
|
|
4730
|
-
delete slide.data.body;
|
|
4731
|
-
result.applied = true;
|
|
4732
|
-
result.description = "Removed body text, keeping bullets";
|
|
4733
|
-
}
|
|
4734
|
-
result.newValue = this.countElements(slide);
|
|
5586
|
+
const professionalLook = avgPolish >= 7 && avgCoherence >= 7;
|
|
5587
|
+
if (!professionalLook) {
|
|
5588
|
+
score -= 5;
|
|
5589
|
+
notes.push("Overall polish could be improved");
|
|
4735
5590
|
}
|
|
4736
|
-
return
|
|
5591
|
+
return {
|
|
5592
|
+
score: Math.max(0, score),
|
|
5593
|
+
colorPaletteConsistent,
|
|
5594
|
+
typographyConsistent,
|
|
5595
|
+
layoutPatternsConsistent,
|
|
5596
|
+
professionalLook,
|
|
5597
|
+
notes: notes.join(". ") || "Consistent visual design"
|
|
5598
|
+
};
|
|
4737
5599
|
}
|
|
4738
|
-
|
|
4739
|
-
|
|
4740
|
-
|
|
4741
|
-
|
|
4742
|
-
|
|
4743
|
-
|
|
5600
|
+
evaluateContentQuality(slideScores) {
|
|
5601
|
+
let score = 25;
|
|
5602
|
+
const notes = [];
|
|
5603
|
+
const clarityScores = slideScores.map((s) => s.contentClarity);
|
|
5604
|
+
const avgClarity = clarityScores.reduce((a, b) => a + b, 0) / clarityScores.length;
|
|
5605
|
+
const messagesAreClear = avgClarity >= 7;
|
|
5606
|
+
if (!messagesAreClear) {
|
|
5607
|
+
score -= 7;
|
|
5608
|
+
notes.push("Messages could be clearer");
|
|
5609
|
+
}
|
|
5610
|
+
const lowClarity = slideScores.filter((s) => s.contentClarity < 5).length;
|
|
5611
|
+
const appropriateDepth = lowClarity < slideScores.length * 0.2;
|
|
5612
|
+
if (!appropriateDepth) {
|
|
5613
|
+
score -= 5;
|
|
5614
|
+
notes.push("Some slides have content issues");
|
|
4744
5615
|
}
|
|
4745
|
-
const
|
|
4746
|
-
|
|
4747
|
-
|
|
4748
|
-
|
|
4749
|
-
|
|
4750
|
-
|
|
4751
|
-
|
|
4752
|
-
if (slide.data.body) {
|
|
4753
|
-
slide.data.body = this.condenseText(slide.data.body, Math.floor(targetWords * 0.5));
|
|
4754
|
-
}
|
|
4755
|
-
if (slide.data.bullets && slide.data.bullets.length > 0) {
|
|
4756
|
-
const wordsPerBullet = Math.floor(targetWords / (slide.data.bullets.length * 2));
|
|
4757
|
-
slide.data.bullets = slide.data.bullets.map((b) => this.condenseText(b, wordsPerBullet));
|
|
4758
|
-
}
|
|
4759
|
-
result.newValue = this.countWords(slide);
|
|
4760
|
-
result.applied = true;
|
|
4761
|
-
result.description = `Reduced content from ${result.originalValue} to ${result.newValue} words for better whitespace`;
|
|
5616
|
+
const overloadedSlides = slideScores.filter(
|
|
5617
|
+
(s) => s.contentClarityNotes.includes("too long") || s.visualImpactNotes.includes("Too much")
|
|
5618
|
+
).length;
|
|
5619
|
+
const noOverload = overloadedSlides === 0;
|
|
5620
|
+
if (!noOverload) {
|
|
5621
|
+
score -= 5;
|
|
5622
|
+
notes.push(`${overloadedSlides} slides have too much content`);
|
|
4762
5623
|
}
|
|
4763
|
-
|
|
4764
|
-
|
|
4765
|
-
|
|
4766
|
-
|
|
4767
|
-
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
let fixedCount = 0;
|
|
4771
|
-
for (const slide of slides) {
|
|
4772
|
-
if (slide.data.title) {
|
|
4773
|
-
const original = slide.data.title;
|
|
4774
|
-
slide.data.title = this.toTitleCase(slide.data.title);
|
|
4775
|
-
if (slide.data.title !== original) fixedCount++;
|
|
4776
|
-
}
|
|
4777
|
-
}
|
|
4778
|
-
result.originalValue = "Mixed casing";
|
|
4779
|
-
result.newValue = "Title Case";
|
|
4780
|
-
result.applied = fixedCount > 0;
|
|
4781
|
-
result.description = `Applied Title Case to ${fixedCount} slide titles`;
|
|
5624
|
+
const insightSlides = slideScores.filter(
|
|
5625
|
+
(s) => s.visualImpact >= 7 && s.contentClarity >= 7
|
|
5626
|
+
).length;
|
|
5627
|
+
const actionableInsights = insightSlides >= slideScores.length * 0.3;
|
|
5628
|
+
if (!actionableInsights) {
|
|
5629
|
+
score -= 5;
|
|
5630
|
+
notes.push("Need more high-impact insight slides");
|
|
4782
5631
|
}
|
|
4783
|
-
return
|
|
4784
|
-
|
|
4785
|
-
|
|
4786
|
-
|
|
4787
|
-
|
|
4788
|
-
|
|
4789
|
-
|
|
4790
|
-
|
|
4791
|
-
result.applied = false;
|
|
4792
|
-
return result;
|
|
5632
|
+
return {
|
|
5633
|
+
score: Math.max(0, score),
|
|
5634
|
+
messagesAreClear,
|
|
5635
|
+
appropriateDepth,
|
|
5636
|
+
noOverload,
|
|
5637
|
+
actionableInsights,
|
|
5638
|
+
notes: notes.join(". ") || "Strong content quality"
|
|
5639
|
+
};
|
|
4793
5640
|
}
|
|
4794
|
-
|
|
4795
|
-
|
|
4796
|
-
|
|
4797
|
-
|
|
4798
|
-
|
|
4799
|
-
const
|
|
4800
|
-
|
|
4801
|
-
|
|
4802
|
-
|
|
4803
|
-
|
|
4804
|
-
"
|
|
4805
|
-
|
|
4806
|
-
|
|
4807
|
-
|
|
4808
|
-
|
|
4809
|
-
"
|
|
4810
|
-
|
|
4811
|
-
|
|
4812
|
-
|
|
4813
|
-
|
|
4814
|
-
"
|
|
4815
|
-
"would",
|
|
4816
|
-
"could",
|
|
4817
|
-
"should",
|
|
4818
|
-
"might"
|
|
4819
|
-
]);
|
|
4820
|
-
let filtered = words.filter((w) => !fillerWords.has(w.toLowerCase()));
|
|
4821
|
-
if (filtered.length <= maxWords) {
|
|
4822
|
-
return filtered.join(" ");
|
|
4823
|
-
}
|
|
4824
|
-
const punctuation = [".", ",", ";", ":", "-"];
|
|
4825
|
-
let breakPoint = maxWords;
|
|
4826
|
-
for (let i = maxWords - 1; i >= maxWords - 5 && i >= 0; i--) {
|
|
4827
|
-
const word = filtered[i];
|
|
4828
|
-
if (word && punctuation.some((p) => word.endsWith(p))) {
|
|
4829
|
-
breakPoint = i + 1;
|
|
4830
|
-
break;
|
|
4831
|
-
}
|
|
5641
|
+
evaluateExecutiveReadiness(slideScores) {
|
|
5642
|
+
let score = 25;
|
|
5643
|
+
const notes = [];
|
|
5644
|
+
const avgVisual = slideScores.reduce((sum, s) => sum + s.visualImpact, 0) / slideScores.length;
|
|
5645
|
+
const avgPolish = slideScores.reduce((sum, s) => sum + s.professionalPolish, 0) / slideScores.length;
|
|
5646
|
+
const avgClarity = slideScores.reduce((sum, s) => sum + s.contentClarity, 0) / slideScores.length;
|
|
5647
|
+
const avgTotal = slideScores.reduce((sum, s) => sum + s.totalScore, 0) / slideScores.length;
|
|
5648
|
+
const wouldImpress = avgTotal >= 26;
|
|
5649
|
+
if (!wouldImpress) {
|
|
5650
|
+
score -= 7;
|
|
5651
|
+
notes.push("Needs more visual impact to impress");
|
|
5652
|
+
}
|
|
5653
|
+
const readyForBoardroom = avgPolish >= 6 && avgClarity >= 6;
|
|
5654
|
+
if (!readyForBoardroom) {
|
|
5655
|
+
score -= 7;
|
|
5656
|
+
notes.push("Needs more polish for executive audience");
|
|
5657
|
+
}
|
|
5658
|
+
const compelling = avgVisual >= 6;
|
|
5659
|
+
if (!compelling) {
|
|
5660
|
+
score -= 5;
|
|
5661
|
+
notes.push("Could be more visually compelling");
|
|
4832
5662
|
}
|
|
4833
|
-
|
|
4834
|
-
|
|
4835
|
-
if (!
|
|
4836
|
-
|
|
5663
|
+
const excellentSlides = slideScores.filter((s) => s.totalScore >= 30).length;
|
|
5664
|
+
const shareworthy = excellentSlides >= slideScores.length * 0.4;
|
|
5665
|
+
if (!shareworthy) {
|
|
5666
|
+
score -= 5;
|
|
5667
|
+
notes.push("Less than 40% of slides are excellent");
|
|
4837
5668
|
}
|
|
4838
|
-
return
|
|
5669
|
+
return {
|
|
5670
|
+
score: Math.max(0, score),
|
|
5671
|
+
wouldImpress,
|
|
5672
|
+
readyForBoardroom,
|
|
5673
|
+
compelling,
|
|
5674
|
+
shareworthy,
|
|
5675
|
+
notes: notes.join(". ") || "Executive-ready presentation"
|
|
5676
|
+
};
|
|
4839
5677
|
}
|
|
4840
|
-
|
|
4841
|
-
|
|
4842
|
-
|
|
4843
|
-
|
|
4844
|
-
|
|
4845
|
-
|
|
4846
|
-
|
|
4847
|
-
|
|
4848
|
-
|
|
4849
|
-
|
|
4850
|
-
|
|
4851
|
-
|
|
4852
|
-
"
|
|
4853
|
-
"
|
|
4854
|
-
"
|
|
4855
|
-
"
|
|
4856
|
-
"
|
|
4857
|
-
|
|
4858
|
-
|
|
4859
|
-
|
|
4860
|
-
|
|
4861
|
-
|
|
4862
|
-
|
|
4863
|
-
|
|
5678
|
+
// ===========================================================================
|
|
5679
|
+
// VERDICT & SUMMARY
|
|
5680
|
+
// ===========================================================================
|
|
5681
|
+
determineVerdict(score) {
|
|
5682
|
+
if (score >= 90) return "world-class";
|
|
5683
|
+
if (score >= 80) return "professional";
|
|
5684
|
+
if (score >= 65) return "acceptable";
|
|
5685
|
+
if (score >= 50) return "needs-work";
|
|
5686
|
+
return "poor";
|
|
5687
|
+
}
|
|
5688
|
+
explainVerdict(verdict, score) {
|
|
5689
|
+
const explanations = {
|
|
5690
|
+
"world-class": `Score: ${score}/100. This presentation would impress any audience. Ready for TED, boardrooms, and high-stakes pitches.`,
|
|
5691
|
+
"professional": `Score: ${score}/100. Solid professional presentation. Ready for most business contexts with minor polish.`,
|
|
5692
|
+
"acceptable": `Score: ${score}/100. Meets basic standards but lacks the polish and impact of world-class work.`,
|
|
5693
|
+
"needs-work": `Score: ${score}/100. Significant improvements needed in visual design, content clarity, or structure.`,
|
|
5694
|
+
"poor": `Score: ${score}/100. Fundamental issues with content, design, or structure. Major rework required.`
|
|
5695
|
+
};
|
|
5696
|
+
return explanations[verdict];
|
|
5697
|
+
}
|
|
5698
|
+
extractTopIssuesAndStrengths(slideScores) {
|
|
5699
|
+
const issues = [];
|
|
5700
|
+
const strengths = [];
|
|
5701
|
+
for (const slide of slideScores) {
|
|
5702
|
+
if (slide.visualImpact < 5) {
|
|
5703
|
+
issues.push(`Slide ${slide.slideIndex}: Low visual impact - ${slide.visualImpactNotes}`);
|
|
4864
5704
|
}
|
|
4865
|
-
if (
|
|
4866
|
-
|
|
5705
|
+
if (slide.contentClarity < 5) {
|
|
5706
|
+
issues.push(`Slide ${slide.slideIndex}: Clarity issue - ${slide.contentClarityNotes}`);
|
|
4867
5707
|
}
|
|
4868
|
-
|
|
4869
|
-
|
|
4870
|
-
}
|
|
4871
|
-
/**
|
|
4872
|
-
* Count words in a slide.
|
|
4873
|
-
*/
|
|
4874
|
-
countWords(slide) {
|
|
4875
|
-
let text = "";
|
|
4876
|
-
if (slide.data.title) text += slide.data.title + " ";
|
|
4877
|
-
if (slide.data.subtitle) text += slide.data.subtitle + " ";
|
|
4878
|
-
if (slide.data.body) text += slide.data.body + " ";
|
|
4879
|
-
if (slide.data.bullets) text += slide.data.bullets.join(" ") + " ";
|
|
4880
|
-
if (slide.data.keyMessage) text += slide.data.keyMessage + " ";
|
|
4881
|
-
return text.split(/\s+/).filter((w) => w.length > 0).length;
|
|
4882
|
-
}
|
|
4883
|
-
/**
|
|
4884
|
-
* Count elements in a slide.
|
|
4885
|
-
*/
|
|
4886
|
-
countElements(slide) {
|
|
4887
|
-
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);
|
|
4888
|
-
}
|
|
4889
|
-
/**
|
|
4890
|
-
* Generate a summary of fixes applied.
|
|
4891
|
-
*/
|
|
4892
|
-
generateSummary(applied, skipped) {
|
|
4893
|
-
const lines = [];
|
|
4894
|
-
lines.push(`
|
|
4895
|
-
\u{1F527} Auto-Fix Summary`);
|
|
4896
|
-
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`);
|
|
4897
|
-
lines.push(`Fixes Applied: ${applied.length}`);
|
|
4898
|
-
lines.push(`Fixes Skipped: ${skipped.length}`);
|
|
4899
|
-
lines.push("");
|
|
4900
|
-
if (applied.length > 0) {
|
|
4901
|
-
lines.push("\u2705 Applied Fixes:");
|
|
4902
|
-
for (const fix of applied) {
|
|
4903
|
-
lines.push(` \u2022 ${fix.description}`);
|
|
5708
|
+
if (slide.totalScore >= 35) {
|
|
5709
|
+
strengths.push(`Slide ${slide.slideIndex}: Excellent overall (${slide.slideType})`);
|
|
4904
5710
|
}
|
|
5711
|
+
}
|
|
5712
|
+
return {
|
|
5713
|
+
topIssues: issues.slice(0, 5),
|
|
5714
|
+
topStrengths: strengths.slice(0, 3)
|
|
5715
|
+
};
|
|
5716
|
+
}
|
|
5717
|
+
// ===========================================================================
|
|
5718
|
+
// FORMATTED REPORT
|
|
5719
|
+
// ===========================================================================
|
|
5720
|
+
generateReport(result) {
|
|
5721
|
+
const lines = [
|
|
5722
|
+
"\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",
|
|
5723
|
+
"\u2551 VISUAL QUALITY EVALUATION REPORT \u2551",
|
|
5724
|
+
"\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",
|
|
5725
|
+
"",
|
|
5726
|
+
` VERDICT: ${result.verdict.toUpperCase()}`,
|
|
5727
|
+
` ${result.verdictExplanation}`,
|
|
5728
|
+
"",
|
|
5729
|
+
"\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",
|
|
5730
|
+
"\u2502 DIMENSION SCORES \u2502",
|
|
5731
|
+
"\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",
|
|
5732
|
+
`\u2502 Narrative Flow: ${this.scoreBar(result.narrativeFlow.score, 25)} ${result.narrativeFlow.score}/25`,
|
|
5733
|
+
`\u2502 Visual Consistency: ${this.scoreBar(result.visualConsistency.score, 25)} ${result.visualConsistency.score}/25`,
|
|
5734
|
+
`\u2502 Content Quality: ${this.scoreBar(result.contentQuality.score, 25)} ${result.contentQuality.score}/25`,
|
|
5735
|
+
`\u2502 Executive Readiness: ${this.scoreBar(result.executiveReadiness.score, 25)} ${result.executiveReadiness.score}/25`,
|
|
5736
|
+
"\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",
|
|
5737
|
+
`\u2502 OVERALL SCORE: ${this.scoreBar(result.overallScore, 100)} ${result.overallScore}/100`,
|
|
5738
|
+
"\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",
|
|
5739
|
+
""
|
|
5740
|
+
];
|
|
5741
|
+
if (result.topStrengths.length > 0) {
|
|
5742
|
+
lines.push("\u2713 TOP STRENGTHS:");
|
|
5743
|
+
result.topStrengths.forEach((s) => lines.push(` \u2022 ${s}`));
|
|
4905
5744
|
lines.push("");
|
|
4906
5745
|
}
|
|
4907
|
-
if (
|
|
4908
|
-
lines.push("\
|
|
4909
|
-
|
|
4910
|
-
|
|
4911
|
-
|
|
4912
|
-
|
|
4913
|
-
|
|
4914
|
-
|
|
5746
|
+
if (result.topIssues.length > 0) {
|
|
5747
|
+
lines.push("\u2717 TOP ISSUES:");
|
|
5748
|
+
result.topIssues.forEach((i) => lines.push(` \u2022 ${i}`));
|
|
5749
|
+
lines.push("");
|
|
5750
|
+
}
|
|
5751
|
+
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");
|
|
5752
|
+
lines.push("\u2502 SLIDE-BY-SLIDE BREAKDOWN \u2502");
|
|
5753
|
+
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");
|
|
5754
|
+
for (const slide of result.slideScores) {
|
|
5755
|
+
const scoreColor = slide.totalScore >= 30 ? "\u2713" : slide.totalScore >= 20 ? "\u25D0" : "\u2717";
|
|
5756
|
+
lines.push(`\u2502 ${scoreColor} Slide ${slide.slideIndex.toString().padStart(2)} (${slide.slideType.padEnd(18)}): ${slide.totalScore}/40`);
|
|
5757
|
+
lines.push(`\u2502 Visual: ${slide.visualImpact}/10 Clarity: ${slide.contentClarity}/10 Polish: ${slide.professionalPolish}/10 Theme: ${slide.themeCoherence}/10`);
|
|
4915
5758
|
}
|
|
5759
|
+
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");
|
|
5760
|
+
lines.push("");
|
|
5761
|
+
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");
|
|
4916
5762
|
return lines.join("\n");
|
|
4917
5763
|
}
|
|
5764
|
+
scoreBar(score, max) {
|
|
5765
|
+
const percentage = score / max;
|
|
5766
|
+
const filled = Math.round(percentage * 20);
|
|
5767
|
+
const empty = 20 - filled;
|
|
5768
|
+
return "\u2588".repeat(filled) + "\u2591".repeat(empty);
|
|
5769
|
+
}
|
|
4918
5770
|
};
|
|
4919
|
-
function
|
|
4920
|
-
|
|
5771
|
+
async function evaluatePresentation(htmlPath, screenshotDir) {
|
|
5772
|
+
const evaluator = new VisualQualityEvaluator(screenshotDir);
|
|
5773
|
+
return evaluator.evaluate(htmlPath);
|
|
4921
5774
|
}
|
|
4922
5775
|
|
|
4923
5776
|
// src/generators/html/RevealJsGenerator.ts
|
|
@@ -5211,7 +6064,7 @@ ${slides}
|
|
|
5211
6064
|
.reveal .number {
|
|
5212
6065
|
font-size: 4em;
|
|
5213
6066
|
font-weight: 800;
|
|
5214
|
-
color: var(--color-
|
|
6067
|
+
color: var(--color-primary);
|
|
5215
6068
|
text-align: center;
|
|
5216
6069
|
}
|
|
5217
6070
|
|
|
@@ -5262,12 +6115,12 @@ ${slides}
|
|
|
5262
6115
|
.reveal .metric-value {
|
|
5263
6116
|
font-size: 2em;
|
|
5264
6117
|
font-weight: 700;
|
|
5265
|
-
color: var(--color-
|
|
6118
|
+
color: var(--color-primary);
|
|
5266
6119
|
}
|
|
5267
6120
|
|
|
5268
6121
|
.reveal .metric-label {
|
|
5269
6122
|
font-size: 0.8em;
|
|
5270
|
-
color: var(--color-text
|
|
6123
|
+
color: var(--color-text);
|
|
5271
6124
|
}
|
|
5272
6125
|
|
|
5273
6126
|
.reveal .metric-change {
|
|
@@ -5344,27 +6197,31 @@ ${slides}
|
|
|
5344
6197
|
================================================================ */
|
|
5345
6198
|
|
|
5346
6199
|
/* Title slide: Bold background - makes strong first impression */
|
|
5347
|
-
.
|
|
5348
|
-
|
|
5349
|
-
|
|
5350
|
-
|
|
5351
|
-
|
|
5352
|
-
.reveal .slide-title
|
|
5353
|
-
.reveal .slide-title
|
|
5354
|
-
|
|
6200
|
+
/* Using .slides section.slide-title for higher specificity than .reveal .slides section */
|
|
6201
|
+
.reveal .slides section.slide-title {
|
|
6202
|
+
background-color: ${titleBg} !important;
|
|
6203
|
+
background-image: linear-gradient(135deg, ${titleBg} 0%, ${titleBgEnd} 100%);
|
|
6204
|
+
}
|
|
6205
|
+
.reveal .slides section.slide-title h1,
|
|
6206
|
+
.reveal .slides section.slide-title h2,
|
|
6207
|
+
.reveal .slides section.slide-title p,
|
|
6208
|
+
.reveal .slides section.slide-title .subtitle {
|
|
6209
|
+
color: ${isDark ? "#ffffff" : "#ffffff"} !important;
|
|
6210
|
+
-webkit-text-fill-color: ${isDark ? "#ffffff" : "#ffffff"} !important;
|
|
6211
|
+
background: none !important;
|
|
5355
6212
|
}
|
|
5356
6213
|
|
|
5357
6214
|
/* Section dividers: Subtle visual breaks */
|
|
5358
|
-
.reveal .slide-section-divider {
|
|
6215
|
+
.reveal .slides section.slide-section-divider {
|
|
5359
6216
|
background: ${isDark ? this.lightenColor(background, 8) : `linear-gradient(180deg, ${this.lightenColor(primary, 85)} 0%, ${this.lightenColor(primary, 92)} 100%)`};
|
|
5360
6217
|
}
|
|
5361
6218
|
|
|
5362
6219
|
/* Big number slides: Clean with accent highlight - emphasizes the data */
|
|
5363
|
-
.reveal .slide-big-number {
|
|
6220
|
+
.reveal .slides section.slide-big-number {
|
|
5364
6221
|
background: var(--color-background);
|
|
5365
6222
|
border-left: 8px solid var(--color-highlight);
|
|
5366
6223
|
}
|
|
5367
|
-
.reveal .slide-big-number .number {
|
|
6224
|
+
.reveal .slides section.slide-big-number .number {
|
|
5368
6225
|
font-size: 5em;
|
|
5369
6226
|
background: linear-gradient(135deg, var(--color-highlight) 0%, var(--color-accent) 100%);
|
|
5370
6227
|
-webkit-background-clip: text;
|
|
@@ -5373,78 +6230,198 @@ ${slides}
|
|
|
5373
6230
|
}
|
|
5374
6231
|
|
|
5375
6232
|
/* Metrics grid: Light accent background - data-focused */
|
|
5376
|
-
.reveal .slide-metrics-grid {
|
|
6233
|
+
.reveal .slides section.slide-metrics-grid {
|
|
5377
6234
|
background: ${isDark ? this.lightenColor(background, 8) : this.lightenColor(accent, 90)};
|
|
5378
6235
|
}
|
|
5379
|
-
${isDark ? `.reveal .slide-metrics-grid { color: ${text}; }` : ""}
|
|
6236
|
+
${isDark ? `.reveal .slides section.slide-metrics-grid { color: ${text}; }` : ""}
|
|
5380
6237
|
|
|
5381
|
-
/* CTA slide: Highlight color - drives action */
|
|
5382
|
-
.reveal .slide-cta {
|
|
5383
|
-
background:
|
|
5384
|
-
|
|
5385
|
-
|
|
5386
|
-
.reveal .slide-cta
|
|
5387
|
-
|
|
5388
|
-
|
|
5389
|
-
|
|
6238
|
+
/* CTA slide: Highlight color - drives action (darkened for 7:1 contrast with white) */
|
|
6239
|
+
.reveal .slides section.slide-cta {
|
|
6240
|
+
background-color: ${this.darkenColor(highlight, 30)} !important;
|
|
6241
|
+
background-image: linear-gradient(135deg, ${this.darkenColor(highlight, 25)} 0%, ${this.darkenColor(accent, 30)} 100%);
|
|
6242
|
+
}
|
|
6243
|
+
.reveal .slides section.slide-cta h2,
|
|
6244
|
+
.reveal .slides section.slide-cta p {
|
|
6245
|
+
color: #ffffff !important;
|
|
6246
|
+
-webkit-text-fill-color: #ffffff !important;
|
|
6247
|
+
background: none !important;
|
|
6248
|
+
}
|
|
6249
|
+
.reveal .slides section.slide-cta .cta-button {
|
|
5390
6250
|
background: #ffffff;
|
|
5391
6251
|
color: var(--color-highlight);
|
|
5392
6252
|
}
|
|
5393
6253
|
|
|
5394
6254
|
/* Thank you slide: Matches title for bookend effect */
|
|
5395
|
-
.reveal .slide-thank-you {
|
|
5396
|
-
background:
|
|
6255
|
+
.reveal .slides section.slide-thank-you {
|
|
6256
|
+
background-color: ${titleBg} !important;
|
|
6257
|
+
background-image: linear-gradient(135deg, ${titleBg} 0%, ${titleBgEnd} 100%);
|
|
5397
6258
|
}
|
|
5398
|
-
.reveal .slide-thank-you h2,
|
|
5399
|
-
.reveal .slide-thank-you p,
|
|
5400
|
-
.reveal .slide-thank-you .subtitle {
|
|
5401
|
-
color:
|
|
6259
|
+
.reveal .slides section.slide-thank-you h2,
|
|
6260
|
+
.reveal .slides section.slide-thank-you p,
|
|
6261
|
+
.reveal .slides section.slide-thank-you .subtitle {
|
|
6262
|
+
color: #ffffff !important;
|
|
6263
|
+
-webkit-text-fill-color: #ffffff !important;
|
|
6264
|
+
background: none !important;
|
|
5402
6265
|
}
|
|
5403
6266
|
|
|
5404
6267
|
/* Quote slides: Elegant subtle background */
|
|
5405
|
-
.reveal .slide-quote {
|
|
6268
|
+
.reveal .slides section.slide-quote {
|
|
5406
6269
|
background: ${isDark ? this.lightenColor(background, 5) : this.lightenColor(secondary, 92)};
|
|
5407
6270
|
}
|
|
5408
|
-
.reveal .slide-quote blockquote {
|
|
6271
|
+
.reveal .slides section.slide-quote blockquote {
|
|
5409
6272
|
border-left-color: var(--color-accent);
|
|
5410
6273
|
font-size: 1.3em;
|
|
5411
6274
|
}
|
|
5412
|
-
${isDark ? `.reveal .slide-quote { color: ${text}; }` : ""}
|
|
6275
|
+
${isDark ? `.reveal .slides section.slide-quote { color: ${text}; }` : ""}
|
|
5413
6276
|
|
|
5414
6277
|
/* Single statement: Clean, centered, impactful */
|
|
5415
|
-
.reveal .slide-single-statement {
|
|
6278
|
+
.reveal .slides section.slide-single-statement {
|
|
5416
6279
|
background: var(--color-background);
|
|
5417
6280
|
}
|
|
5418
|
-
.reveal .slide-single-statement .statement,
|
|
5419
|
-
.reveal .slide-single-statement .big-idea-text {
|
|
6281
|
+
.reveal .slides section.slide-single-statement .statement,
|
|
6282
|
+
.reveal .slides section.slide-single-statement .big-idea-text {
|
|
5420
6283
|
font-size: 2.2em;
|
|
5421
6284
|
max-width: 80%;
|
|
5422
6285
|
margin: 0 auto;
|
|
5423
6286
|
}
|
|
5424
6287
|
|
|
5425
6288
|
/* Comparison slides: Split visual */
|
|
5426
|
-
.reveal .slide-comparison .columns
|
|
6289
|
+
.reveal .slide-comparison .columns,
|
|
6290
|
+
.reveal .slide-comparison .comparison-container {
|
|
5427
6291
|
gap: 60px;
|
|
5428
6292
|
}
|
|
5429
|
-
.reveal .slide-comparison .column:first-child
|
|
6293
|
+
.reveal .slide-comparison .column:first-child,
|
|
6294
|
+
.reveal .slide-comparison .comparison-left {
|
|
5430
6295
|
border-right: 2px solid ${this.lightenColor(secondary, 70)};
|
|
5431
6296
|
padding-right: 30px;
|
|
5432
6297
|
}
|
|
6298
|
+
.reveal .column-title {
|
|
6299
|
+
font-size: 1.3em;
|
|
6300
|
+
font-weight: 600;
|
|
6301
|
+
margin-bottom: 0.5em;
|
|
6302
|
+
color: var(--color-primary);
|
|
6303
|
+
}
|
|
6304
|
+
.reveal .column-content,
|
|
6305
|
+
.reveal .column-body {
|
|
6306
|
+
line-height: 1.6;
|
|
6307
|
+
}
|
|
5433
6308
|
|
|
5434
|
-
/* Timeline/Process: Visual flow */
|
|
6309
|
+
/* Timeline/Process: Visual flow - ENHANCED */
|
|
6310
|
+
.reveal .slide-timeline .slide-content,
|
|
6311
|
+
.reveal .slide-process .slide-content {
|
|
6312
|
+
justify-content: center;
|
|
6313
|
+
}
|
|
5435
6314
|
.reveal .slide-timeline .steps,
|
|
5436
|
-
.reveal .slide-
|
|
6315
|
+
.reveal .slide-timeline .timeline,
|
|
6316
|
+
.reveal .slide-process .steps,
|
|
6317
|
+
.reveal .slide-process .process-steps {
|
|
5437
6318
|
display: flex;
|
|
5438
|
-
gap:
|
|
6319
|
+
gap: 24px;
|
|
6320
|
+
flex-wrap: wrap;
|
|
6321
|
+
justify-content: center;
|
|
6322
|
+
align-items: stretch;
|
|
6323
|
+
margin-top: 30px;
|
|
6324
|
+
padding: 0 20px;
|
|
5439
6325
|
}
|
|
5440
6326
|
.reveal .slide-timeline .step,
|
|
5441
|
-
.reveal .slide-
|
|
5442
|
-
|
|
5443
|
-
|
|
5444
|
-
|
|
5445
|
-
|
|
5446
|
-
|
|
6327
|
+
.reveal .slide-timeline .timeline-item,
|
|
6328
|
+
.reveal .slide-process .step,
|
|
6329
|
+
.reveal .slide-process .process-step {
|
|
6330
|
+
flex: 1 1 200px;
|
|
6331
|
+
min-width: 180px;
|
|
6332
|
+
max-width: 280px;
|
|
6333
|
+
padding: 28px 24px;
|
|
6334
|
+
background: ${isDark ? this.lightenColor(background, 10) : "#ffffff"};
|
|
6335
|
+
border-radius: 12px;
|
|
6336
|
+
border-top: 5px solid var(--color-accent);
|
|
6337
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
|
5447
6338
|
${isDark ? `color: ${text};` : ""}
|
|
6339
|
+
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
|
6340
|
+
}
|
|
6341
|
+
.reveal .slide-timeline .step:hover,
|
|
6342
|
+
.reveal .slide-timeline .timeline-item:hover,
|
|
6343
|
+
.reveal .slide-process .step:hover,
|
|
6344
|
+
.reveal .slide-process .process-step:hover {
|
|
6345
|
+
transform: translateY(-4px);
|
|
6346
|
+
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.12);
|
|
6347
|
+
}
|
|
6348
|
+
.reveal .step-number {
|
|
6349
|
+
display: inline-flex;
|
|
6350
|
+
align-items: center;
|
|
6351
|
+
justify-content: center;
|
|
6352
|
+
width: 40px;
|
|
6353
|
+
height: 40px;
|
|
6354
|
+
background: var(--color-accent);
|
|
6355
|
+
color: #ffffff;
|
|
6356
|
+
border-radius: 50%;
|
|
6357
|
+
font-size: 1.2em;
|
|
6358
|
+
font-weight: 700;
|
|
6359
|
+
margin-bottom: 16px;
|
|
6360
|
+
}
|
|
6361
|
+
.reveal .step-title {
|
|
6362
|
+
font-size: 1.3em;
|
|
6363
|
+
font-weight: 600;
|
|
6364
|
+
margin-bottom: 8px;
|
|
6365
|
+
color: var(--color-primary);
|
|
6366
|
+
}
|
|
6367
|
+
.reveal .step-desc {
|
|
6368
|
+
font-size: 0.95em;
|
|
6369
|
+
line-height: 1.5;
|
|
6370
|
+
color: var(--color-text-light);
|
|
6371
|
+
}
|
|
6372
|
+
.reveal .step-arrow {
|
|
6373
|
+
display: flex;
|
|
6374
|
+
align-items: center;
|
|
6375
|
+
font-size: 1.5em;
|
|
6376
|
+
color: var(--color-accent);
|
|
6377
|
+
}
|
|
6378
|
+
|
|
6379
|
+
/* Improved slide layout - PROFESSIONAL */
|
|
6380
|
+
.reveal .slides section {
|
|
6381
|
+
overflow: hidden;
|
|
6382
|
+
}
|
|
6383
|
+
.reveal .slides section .slide-content {
|
|
6384
|
+
overflow-y: auto;
|
|
6385
|
+
overflow-x: hidden;
|
|
6386
|
+
max-height: calc(100vh - 120px);
|
|
6387
|
+
}
|
|
6388
|
+
|
|
6389
|
+
/* Three column professional styling */
|
|
6390
|
+
.reveal .three-columns .column {
|
|
6391
|
+
padding: 24px;
|
|
6392
|
+
background: ${isDark ? this.lightenColor(background, 8) : "#f8f9fa"};
|
|
6393
|
+
border-radius: 12px;
|
|
6394
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
|
6395
|
+
}
|
|
6396
|
+
.reveal .three-columns .column-title {
|
|
6397
|
+
font-size: 1.3em;
|
|
6398
|
+
color: var(--color-primary);
|
|
6399
|
+
border-bottom: 2px solid var(--color-accent);
|
|
6400
|
+
padding-bottom: 8px;
|
|
6401
|
+
margin-bottom: 12px;
|
|
6402
|
+
}
|
|
6403
|
+
|
|
6404
|
+
/* Bullets professional styling */
|
|
6405
|
+
.reveal .bullets {
|
|
6406
|
+
list-style: none;
|
|
6407
|
+
margin: 0;
|
|
6408
|
+
padding: 0;
|
|
6409
|
+
}
|
|
6410
|
+
.reveal .bullets li {
|
|
6411
|
+
position: relative;
|
|
6412
|
+
padding-left: 28px;
|
|
6413
|
+
margin-bottom: 16px;
|
|
6414
|
+
line-height: 1.5;
|
|
6415
|
+
}
|
|
6416
|
+
.reveal .bullets li::before {
|
|
6417
|
+
content: "";
|
|
6418
|
+
position: absolute;
|
|
6419
|
+
left: 0;
|
|
6420
|
+
top: 8px;
|
|
6421
|
+
width: 8px;
|
|
6422
|
+
height: 8px;
|
|
6423
|
+
background: var(--color-accent);
|
|
6424
|
+
border-radius: 50%;
|
|
5448
6425
|
}
|
|
5449
6426
|
|
|
5450
6427
|
/* Progress bar enhancement */
|
|
@@ -5682,182 +6659,6 @@ ${slides}
|
|
|
5682
6659
|
}
|
|
5683
6660
|
};
|
|
5684
6661
|
|
|
5685
|
-
// src/qa/IterativeQAEngine.ts
|
|
5686
|
-
var DEFAULT_OPTIONS = {
|
|
5687
|
-
minScore: 95,
|
|
5688
|
-
maxIterations: 5,
|
|
5689
|
-
verbose: true
|
|
5690
|
-
};
|
|
5691
|
-
var IterativeQAEngine = class {
|
|
5692
|
-
kb;
|
|
5693
|
-
scorer;
|
|
5694
|
-
fixer;
|
|
5695
|
-
generator;
|
|
5696
|
-
mode;
|
|
5697
|
-
presentationType;
|
|
5698
|
-
config;
|
|
5699
|
-
constructor(mode, presentationType, config) {
|
|
5700
|
-
this.mode = mode;
|
|
5701
|
-
this.presentationType = presentationType;
|
|
5702
|
-
this.config = config;
|
|
5703
|
-
this.scorer = new SevenDimensionScorer(mode, presentationType);
|
|
5704
|
-
this.fixer = new AutoFixEngine(mode, presentationType);
|
|
5705
|
-
this.generator = new RevealJsGenerator();
|
|
5706
|
-
}
|
|
5707
|
-
/**
|
|
5708
|
-
* Run the iterative QA process.
|
|
5709
|
-
*/
|
|
5710
|
-
async run(initialSlides, initialHtml, options = {}) {
|
|
5711
|
-
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
5712
|
-
this.kb = await getKnowledgeGateway();
|
|
5713
|
-
const iterations = [];
|
|
5714
|
-
let currentSlides = initialSlides;
|
|
5715
|
-
let currentHtml = initialHtml;
|
|
5716
|
-
let scoringResult;
|
|
5717
|
-
let autoFixSummary = "";
|
|
5718
|
-
let totalFixesApplied = 0;
|
|
5719
|
-
if (opts.verbose) {
|
|
5720
|
-
logger.progress("\n\u{1F504} Starting Iterative QA Process");
|
|
5721
|
-
logger.info(` Target Score: ${opts.minScore}/100`);
|
|
5722
|
-
logger.info(` Max Iterations: ${opts.maxIterations}`);
|
|
5723
|
-
logger.progress("");
|
|
5724
|
-
}
|
|
5725
|
-
for (let i = 0; i < opts.maxIterations; i++) {
|
|
5726
|
-
const iterationNum = i + 1;
|
|
5727
|
-
if (opts.verbose) {
|
|
5728
|
-
logger.progress(`\u{1F4CA} Iteration ${iterationNum}/${opts.maxIterations}...`);
|
|
5729
|
-
}
|
|
5730
|
-
scoringResult = await this.scorer.score(currentSlides, currentHtml, opts.minScore);
|
|
5731
|
-
iterations.push({
|
|
5732
|
-
iteration: iterationNum,
|
|
5733
|
-
score: scoringResult.overallScore,
|
|
5734
|
-
dimensionScores: {
|
|
5735
|
-
layout: scoringResult.dimensions.layout.score,
|
|
5736
|
-
contrast: scoringResult.dimensions.contrast.score,
|
|
5737
|
-
graphics: scoringResult.dimensions.graphics.score,
|
|
5738
|
-
content: scoringResult.dimensions.content.score,
|
|
5739
|
-
clarity: scoringResult.dimensions.clarity.score,
|
|
5740
|
-
effectiveness: scoringResult.dimensions.effectiveness.score,
|
|
5741
|
-
consistency: scoringResult.dimensions.consistency.score
|
|
5742
|
-
},
|
|
5743
|
-
fixesApplied: 0,
|
|
5744
|
-
timestamp: /* @__PURE__ */ new Date()
|
|
5745
|
-
});
|
|
5746
|
-
if (opts.verbose) {
|
|
5747
|
-
logger.info(` Score: ${scoringResult.overallScore}/100`);
|
|
5748
|
-
}
|
|
5749
|
-
if (scoringResult.passed) {
|
|
5750
|
-
if (opts.verbose) {
|
|
5751
|
-
logger.success(`PASSED - Score meets threshold (${opts.minScore})`);
|
|
5752
|
-
}
|
|
5753
|
-
break;
|
|
5754
|
-
}
|
|
5755
|
-
if (i < opts.maxIterations - 1) {
|
|
5756
|
-
if (opts.verbose) {
|
|
5757
|
-
logger.warn("Below threshold, applying auto-fixes...");
|
|
5758
|
-
}
|
|
5759
|
-
const fixResult = await this.fixer.fix(currentSlides, scoringResult);
|
|
5760
|
-
if (fixResult.fixesApplied.length > 0) {
|
|
5761
|
-
currentSlides = fixResult.slidesFixed;
|
|
5762
|
-
currentHtml = await this.generator.generate(currentSlides, this.config);
|
|
5763
|
-
const lastIteration = iterations[iterations.length - 1];
|
|
5764
|
-
if (lastIteration) {
|
|
5765
|
-
lastIteration.fixesApplied = fixResult.fixesApplied.length;
|
|
5766
|
-
}
|
|
5767
|
-
totalFixesApplied += fixResult.fixesApplied.length;
|
|
5768
|
-
if (opts.verbose) {
|
|
5769
|
-
logger.info(` \u{1F527} Applied ${fixResult.fixesApplied.length} fixes`);
|
|
5770
|
-
}
|
|
5771
|
-
autoFixSummary += fixResult.summary + "\n";
|
|
5772
|
-
} else {
|
|
5773
|
-
if (opts.verbose) {
|
|
5774
|
-
logger.warn("No auto-fixes available, manual review needed");
|
|
5775
|
-
}
|
|
5776
|
-
break;
|
|
5777
|
-
}
|
|
5778
|
-
}
|
|
5779
|
-
}
|
|
5780
|
-
if (!scoringResult.passed) {
|
|
5781
|
-
scoringResult = await this.scorer.score(currentSlides, currentHtml, opts.minScore);
|
|
5782
|
-
}
|
|
5783
|
-
const report = this.generateReport(
|
|
5784
|
-
scoringResult,
|
|
5785
|
-
iterations,
|
|
5786
|
-
opts,
|
|
5787
|
-
totalFixesApplied
|
|
5788
|
-
);
|
|
5789
|
-
if (opts.verbose) {
|
|
5790
|
-
logger.info(report);
|
|
5791
|
-
}
|
|
5792
|
-
return {
|
|
5793
|
-
finalScore: scoringResult.overallScore,
|
|
5794
|
-
passed: scoringResult.passed,
|
|
5795
|
-
threshold: opts.minScore,
|
|
5796
|
-
iterations,
|
|
5797
|
-
totalIterations: iterations.length,
|
|
5798
|
-
maxIterations: opts.maxIterations,
|
|
5799
|
-
slides: currentSlides,
|
|
5800
|
-
html: currentHtml,
|
|
5801
|
-
finalScoring: scoringResult,
|
|
5802
|
-
autoFixSummary,
|
|
5803
|
-
report
|
|
5804
|
-
};
|
|
5805
|
-
}
|
|
5806
|
-
/**
|
|
5807
|
-
* Generate a comprehensive report.
|
|
5808
|
-
*/
|
|
5809
|
-
generateReport(finalScoring, iterations, options, totalFixesApplied) {
|
|
5810
|
-
const lines = [];
|
|
5811
|
-
lines.push("");
|
|
5812
|
-
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");
|
|
5813
|
-
lines.push("\u2551 ITERATIVE QA FINAL REPORT \u2551");
|
|
5814
|
-
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");
|
|
5815
|
-
lines.push("");
|
|
5816
|
-
const passStatus = finalScoring.passed ? "\u2705 PASSED" : "\u274C FAILED";
|
|
5817
|
-
lines.push(`Final Score: ${finalScoring.overallScore}/100 ${passStatus}`);
|
|
5818
|
-
lines.push(`Threshold: ${options.minScore}/100`);
|
|
5819
|
-
lines.push(`Iterations: ${iterations.length}/${options.maxIterations}`);
|
|
5820
|
-
lines.push(`Total Fixes Applied: ${totalFixesApplied}`);
|
|
5821
|
-
lines.push("");
|
|
5822
|
-
if (iterations.length > 1) {
|
|
5823
|
-
lines.push("Score Progression:");
|
|
5824
|
-
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");
|
|
5825
|
-
for (const iter of iterations) {
|
|
5826
|
-
const bar = "\u2588".repeat(Math.floor(iter.score / 10)) + "\u2591".repeat(10 - Math.floor(iter.score / 10));
|
|
5827
|
-
lines.push(` Iter ${iter.iteration}: ${bar} ${iter.score}/100 (+${iter.fixesApplied} fixes)`);
|
|
5828
|
-
}
|
|
5829
|
-
lines.push("");
|
|
5830
|
-
}
|
|
5831
|
-
lines.push(this.scorer.formatReport(finalScoring));
|
|
5832
|
-
lines.push("");
|
|
5833
|
-
lines.push("\u{1F4DA} Knowledge Base Compliance:");
|
|
5834
|
-
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");
|
|
5835
|
-
lines.push(` Mode: ${this.mode}`);
|
|
5836
|
-
lines.push(` Presentation Type: ${this.presentationType}`);
|
|
5837
|
-
lines.push(` Word Limits: ${this.mode === "keynote" ? "6-25" : "40-80"} per slide`);
|
|
5838
|
-
lines.push(` Expert Frameworks: Duarte, Reynolds, Gallo, Anderson`);
|
|
5839
|
-
lines.push("");
|
|
5840
|
-
if (!finalScoring.passed) {
|
|
5841
|
-
lines.push("\u{1F4CB} Recommendations for Manual Review:");
|
|
5842
|
-
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");
|
|
5843
|
-
const manualIssues = finalScoring.issues.filter((i) => !i.autoFixable);
|
|
5844
|
-
for (const issue of manualIssues.slice(0, 5)) {
|
|
5845
|
-
lines.push(` \u2022 ${issue.message}`);
|
|
5846
|
-
if (issue.fixSuggestion) {
|
|
5847
|
-
lines.push(` \u2192 ${issue.fixSuggestion}`);
|
|
5848
|
-
}
|
|
5849
|
-
}
|
|
5850
|
-
if (manualIssues.length > 5) {
|
|
5851
|
-
lines.push(` ... and ${manualIssues.length - 5} more issues`);
|
|
5852
|
-
}
|
|
5853
|
-
}
|
|
5854
|
-
return lines.join("\n");
|
|
5855
|
-
}
|
|
5856
|
-
};
|
|
5857
|
-
function createIterativeQAEngine(mode, presentationType, config) {
|
|
5858
|
-
return new IterativeQAEngine(mode, presentationType, config);
|
|
5859
|
-
}
|
|
5860
|
-
|
|
5861
6662
|
// src/generators/pptx/PowerPointGenerator.ts
|
|
5862
6663
|
var import_pptxgenjs = __toESM(require("pptxgenjs"));
|
|
5863
6664
|
|
|
@@ -6627,11 +7428,11 @@ var PDFGenerator = class {
|
|
|
6627
7428
|
waitUntil: ["networkidle0", "domcontentloaded"]
|
|
6628
7429
|
});
|
|
6629
7430
|
await page.evaluate(() => {
|
|
6630
|
-
return new Promise((
|
|
7431
|
+
return new Promise((resolve2) => {
|
|
6631
7432
|
if (document.fonts && document.fonts.ready) {
|
|
6632
|
-
document.fonts.ready.then(() =>
|
|
7433
|
+
document.fonts.ready.then(() => resolve2());
|
|
6633
7434
|
} else {
|
|
6634
|
-
setTimeout(
|
|
7435
|
+
setTimeout(resolve2, 1e3);
|
|
6635
7436
|
}
|
|
6636
7437
|
});
|
|
6637
7438
|
});
|
|
@@ -6811,46 +7612,38 @@ var PresentationEngine = class {
|
|
|
6811
7612
|
}
|
|
6812
7613
|
let qaResults;
|
|
6813
7614
|
let score = 100;
|
|
6814
|
-
let
|
|
6815
|
-
|
|
6816
|
-
|
|
7615
|
+
let visualQAResult = null;
|
|
7616
|
+
const finalSlides = slides;
|
|
7617
|
+
const finalHtml = outputs.html;
|
|
6817
7618
|
if (!config.skipQA && outputs.html) {
|
|
6818
|
-
const threshold = config.qaThreshold ??
|
|
6819
|
-
|
|
6820
|
-
const
|
|
6821
|
-
|
|
6822
|
-
|
|
6823
|
-
|
|
6824
|
-
|
|
6825
|
-
|
|
6826
|
-
|
|
6827
|
-
);
|
|
6828
|
-
|
|
6829
|
-
|
|
6830
|
-
|
|
6831
|
-
|
|
6832
|
-
|
|
6833
|
-
score = iterativeResult.finalScore;
|
|
6834
|
-
finalSlides = iterativeResult.slides;
|
|
6835
|
-
finalHtml = iterativeResult.html;
|
|
6836
|
-
if (outputs.html) {
|
|
6837
|
-
outputs.html = finalHtml;
|
|
6838
|
-
}
|
|
6839
|
-
qaResults = this.buildQAResultsFrom7Dimension(iterativeResult);
|
|
6840
|
-
if (!iterativeResult.passed) {
|
|
6841
|
-
throw new QAFailureError(score, threshold, qaResults);
|
|
6842
|
-
}
|
|
6843
|
-
} else {
|
|
6844
|
-
logger.progress("\u{1F50D} Running QA validation (legacy mode)...");
|
|
6845
|
-
qaResults = await this.qaEngine.validate(outputs.html, {
|
|
6846
|
-
mode: config.mode,
|
|
6847
|
-
strictMode: true
|
|
6848
|
-
});
|
|
6849
|
-
score = this.scoreCalculator.calculate(qaResults);
|
|
6850
|
-
logger.info(`\u{1F4CA} QA Score: ${score}/100`);
|
|
7619
|
+
const threshold = config.qaThreshold ?? 80;
|
|
7620
|
+
logger.progress("\u{1F50D} Running Visual Quality Evaluation (Playwright)...");
|
|
7621
|
+
const tempDir = fs2.mkdtempSync(path2.join(os.tmpdir(), "presentation-qa-"));
|
|
7622
|
+
const tempHtmlPath = path2.join(tempDir, "presentation.html");
|
|
7623
|
+
fs2.writeFileSync(tempHtmlPath, outputs.html);
|
|
7624
|
+
try {
|
|
7625
|
+
const visualEvaluator = new VisualQualityEvaluator(path2.join(tempDir, "screenshots"));
|
|
7626
|
+
visualQAResult = await visualEvaluator.evaluate(tempHtmlPath);
|
|
7627
|
+
score = visualQAResult.overallScore;
|
|
7628
|
+
logger.info(`\u{1F4CA} Visual QA Score: ${score}/100 (${visualQAResult.verdict.toUpperCase()})`);
|
|
7629
|
+
logger.info(` Narrative Flow: ${visualQAResult.narrativeFlow.score}/25`);
|
|
7630
|
+
logger.info(` Visual Consistency: ${visualQAResult.visualConsistency.score}/25`);
|
|
7631
|
+
logger.info(` Content Quality: ${visualQAResult.contentQuality.score}/25`);
|
|
7632
|
+
logger.info(` Executive Readiness: ${visualQAResult.executiveReadiness.score}/25`);
|
|
7633
|
+
qaResults = this.buildQAResultsFromVisual(visualQAResult);
|
|
6851
7634
|
if (score < threshold) {
|
|
7635
|
+
logger.warn(`\u26A0\uFE0F Score ${score} below threshold ${threshold}`);
|
|
7636
|
+
if (visualQAResult.topIssues.length > 0) {
|
|
7637
|
+
logger.warn("Top issues:");
|
|
7638
|
+
visualQAResult.topIssues.forEach((issue) => logger.warn(` \u2022 ${issue}`));
|
|
7639
|
+
}
|
|
6852
7640
|
throw new QAFailureError(score, threshold, qaResults);
|
|
6853
7641
|
}
|
|
7642
|
+
} finally {
|
|
7643
|
+
try {
|
|
7644
|
+
fs2.rmSync(tempDir, { recursive: true, force: true });
|
|
7645
|
+
} catch {
|
|
7646
|
+
}
|
|
6854
7647
|
}
|
|
6855
7648
|
} else {
|
|
6856
7649
|
qaResults = this.qaEngine.createEmptyResults();
|
|
@@ -6866,7 +7659,7 @@ var PresentationEngine = class {
|
|
|
6866
7659
|
logger.warn(`PDF generation failed (non-critical): ${errorMsg}`);
|
|
6867
7660
|
}
|
|
6868
7661
|
}
|
|
6869
|
-
const metadata = this.buildMetadata(config, analysis, finalSlides,
|
|
7662
|
+
const metadata = this.buildMetadata(config, analysis, finalSlides, visualQAResult);
|
|
6870
7663
|
return {
|
|
6871
7664
|
outputs,
|
|
6872
7665
|
qaResults,
|
|
@@ -6875,43 +7668,57 @@ var PresentationEngine = class {
|
|
|
6875
7668
|
};
|
|
6876
7669
|
}
|
|
6877
7670
|
/**
|
|
6878
|
-
* Build QA results structure from
|
|
7671
|
+
* Build QA results structure from visual quality evaluation.
|
|
6879
7672
|
*/
|
|
6880
|
-
|
|
6881
|
-
const
|
|
7673
|
+
buildQAResultsFromVisual(visualResult) {
|
|
7674
|
+
const passed = visualResult.overallScore >= 80;
|
|
6882
7675
|
return {
|
|
6883
|
-
passed
|
|
6884
|
-
score:
|
|
7676
|
+
passed,
|
|
7677
|
+
score: visualResult.overallScore,
|
|
6885
7678
|
visual: {
|
|
6886
|
-
whitespacePercentage:
|
|
6887
|
-
|
|
6888
|
-
|
|
7679
|
+
whitespacePercentage: visualResult.visualConsistency.score * 4,
|
|
7680
|
+
// Scale 25 to 100
|
|
7681
|
+
layoutBalance: visualResult.visualConsistency.professionalLook ? 1 : 0.7,
|
|
7682
|
+
colorContrast: visualResult.visualConsistency.colorPaletteConsistent ? 0.95 : 0.8
|
|
6889
7683
|
},
|
|
6890
7684
|
content: {
|
|
6891
|
-
perSlide:
|
|
6892
|
-
|
|
7685
|
+
perSlide: visualResult.slideScores.map((slide) => ({
|
|
7686
|
+
slideIndex: slide.slideIndex,
|
|
7687
|
+
wordCount: 0,
|
|
7688
|
+
// Not tracked by visual QA
|
|
7689
|
+
bulletCount: 0,
|
|
7690
|
+
withinLimit: true,
|
|
7691
|
+
hasActionTitle: slide.contentClarity >= 7,
|
|
7692
|
+
issues: slide.contentClarity < 7 ? [slide.contentClarityNotes] : []
|
|
7693
|
+
})),
|
|
7694
|
+
issues: visualResult.topIssues.map((issue) => ({
|
|
7695
|
+
severity: "warning",
|
|
7696
|
+
message: issue,
|
|
7697
|
+
dimension: "content"
|
|
7698
|
+
}))
|
|
6893
7699
|
},
|
|
6894
7700
|
accessibility: {
|
|
6895
|
-
wcagLevel:
|
|
6896
|
-
issues:
|
|
7701
|
+
wcagLevel: visualResult.visualConsistency.professionalLook ? "AA" : "A",
|
|
7702
|
+
issues: []
|
|
6897
7703
|
},
|
|
6898
|
-
issues:
|
|
6899
|
-
severity:
|
|
6900
|
-
message: issue
|
|
6901
|
-
slideIndex:
|
|
6902
|
-
dimension:
|
|
7704
|
+
issues: visualResult.topIssues.map((issue, i) => ({
|
|
7705
|
+
severity: "warning",
|
|
7706
|
+
message: issue,
|
|
7707
|
+
slideIndex: i,
|
|
7708
|
+
dimension: "visual"
|
|
6903
7709
|
})),
|
|
6904
7710
|
dimensions: {
|
|
6905
|
-
layout:
|
|
6906
|
-
contrast:
|
|
6907
|
-
graphics:
|
|
6908
|
-
content:
|
|
6909
|
-
clarity:
|
|
6910
|
-
effectiveness:
|
|
6911
|
-
consistency:
|
|
7711
|
+
layout: visualResult.narrativeFlow.score * 4,
|
|
7712
|
+
contrast: visualResult.visualConsistency.colorPaletteConsistent ? 100 : 80,
|
|
7713
|
+
graphics: visualResult.executiveReadiness.score * 4,
|
|
7714
|
+
content: visualResult.contentQuality.score * 4,
|
|
7715
|
+
clarity: visualResult.contentQuality.messagesAreClear ? 100 : 75,
|
|
7716
|
+
effectiveness: visualResult.executiveReadiness.wouldImpress ? 100 : 60,
|
|
7717
|
+
consistency: visualResult.visualConsistency.score * 4
|
|
6912
7718
|
},
|
|
6913
|
-
iterations:
|
|
6914
|
-
|
|
7719
|
+
iterations: [],
|
|
7720
|
+
// Visual QA is single-pass, no iteration history
|
|
7721
|
+
report: visualResult.verdictExplanation
|
|
6915
7722
|
};
|
|
6916
7723
|
}
|
|
6917
7724
|
/**
|
|
@@ -6997,7 +7804,7 @@ var PresentationEngine = class {
|
|
|
6997
7804
|
/**
|
|
6998
7805
|
* Build presentation metadata.
|
|
6999
7806
|
*/
|
|
7000
|
-
buildMetadata(config, analysis, slides,
|
|
7807
|
+
buildMetadata(config, analysis, slides, visualResult) {
|
|
7001
7808
|
const wordCounts = slides.map((s) => this.countWords(s));
|
|
7002
7809
|
const totalWords = wordCounts.reduce((sum, count) => sum + count, 0);
|
|
7003
7810
|
const avgWordsPerSlide = Math.round(totalWords / slides.length);
|
|
@@ -7015,10 +7822,9 @@ var PresentationEngine = class {
|
|
|
7015
7822
|
frameworks: this.detectFrameworks(analysis),
|
|
7016
7823
|
presentationType: config.presentationType || analysis.detectedType
|
|
7017
7824
|
};
|
|
7018
|
-
if (
|
|
7019
|
-
metadata.qaIterations =
|
|
7020
|
-
metadata.qaMaxIterations =
|
|
7021
|
-
metadata.dimensionScores = iterativeResult.finalScoring.dimensions;
|
|
7825
|
+
if (visualResult) {
|
|
7826
|
+
metadata.qaIterations = 1;
|
|
7827
|
+
metadata.qaMaxIterations = 1;
|
|
7022
7828
|
}
|
|
7023
7829
|
return metadata;
|
|
7024
7830
|
}
|
|
@@ -7260,7 +8066,7 @@ var UnsplashImageProvider = class {
|
|
|
7260
8066
|
};
|
|
7261
8067
|
}
|
|
7262
8068
|
delay(ms) {
|
|
7263
|
-
return new Promise((
|
|
8069
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
7264
8070
|
}
|
|
7265
8071
|
};
|
|
7266
8072
|
var CompositeImageProvider = class {
|
|
@@ -7311,16 +8117,11 @@ async function generate(config) {
|
|
|
7311
8117
|
const engine = new PresentationEngine();
|
|
7312
8118
|
return engine.generate(config);
|
|
7313
8119
|
}
|
|
7314
|
-
async function validate(
|
|
7315
|
-
const
|
|
7316
|
-
|
|
7317
|
-
const score = qaEngine.calculateScore(results);
|
|
7318
|
-
return {
|
|
7319
|
-
...results,
|
|
7320
|
-
score
|
|
7321
|
-
};
|
|
8120
|
+
async function validate(htmlPath, options) {
|
|
8121
|
+
const evaluator = new VisualQualityEvaluator(options?.screenshotDir);
|
|
8122
|
+
return evaluator.evaluate(htmlPath);
|
|
7322
8123
|
}
|
|
7323
|
-
var VERSION = "
|
|
8124
|
+
var VERSION = "9.0.0";
|
|
7324
8125
|
var index_default = {
|
|
7325
8126
|
generate,
|
|
7326
8127
|
validate,
|
|
@@ -7330,13 +8131,11 @@ var index_default = {
|
|
|
7330
8131
|
};
|
|
7331
8132
|
// Annotate the CommonJS export names for ESM import in node:
|
|
7332
8133
|
0 && (module.exports = {
|
|
7333
|
-
AutoFixEngine,
|
|
7334
8134
|
ChartJsProvider,
|
|
7335
8135
|
CompositeChartProvider,
|
|
7336
8136
|
CompositeImageProvider,
|
|
7337
8137
|
ContentAnalyzer,
|
|
7338
8138
|
ContentPatternClassifier,
|
|
7339
|
-
IterativeQAEngine,
|
|
7340
8139
|
KnowledgeGateway,
|
|
7341
8140
|
LocalImageProvider,
|
|
7342
8141
|
MermaidProvider,
|
|
@@ -7348,7 +8147,6 @@ var index_default = {
|
|
|
7348
8147
|
QuickChartProvider,
|
|
7349
8148
|
RevealJsGenerator,
|
|
7350
8149
|
ScoreCalculator,
|
|
7351
|
-
SevenDimensionScorer,
|
|
7352
8150
|
SlideFactory,
|
|
7353
8151
|
SlideGenerator,
|
|
7354
8152
|
TemplateEngine,
|
|
@@ -7356,11 +8154,11 @@ var index_default = {
|
|
|
7356
8154
|
UnsplashImageProvider,
|
|
7357
8155
|
VERSION,
|
|
7358
8156
|
ValidationError,
|
|
7359
|
-
|
|
8157
|
+
VisualQualityEvaluator,
|
|
7360
8158
|
createDefaultChartProvider,
|
|
7361
8159
|
createDefaultImageProvider,
|
|
7362
|
-
createIterativeQAEngine,
|
|
7363
8160
|
createSlideFactory,
|
|
8161
|
+
evaluatePresentation,
|
|
7364
8162
|
generate,
|
|
7365
8163
|
getKnowledgeGateway,
|
|
7366
8164
|
initSlideGenerator,
|