claude-presentation-master 6.1.0 → 7.2.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 +298 -562
- package/bin/auto-fix-presentation.cjs +446 -0
- package/bin/cli.js +95 -32
- package/bin/qa-presentation.cjs +279 -0
- package/bin/score-presentation.cjs +488 -0
- package/dist/index.d.mts +744 -66
- package/dist/index.d.ts +744 -66
- package/dist/index.js +2745 -549
- package/dist/index.mjs +2736 -549
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -30,10 +30,13 @@ 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,
|
|
33
34
|
ChartJsProvider: () => ChartJsProvider,
|
|
34
35
|
CompositeChartProvider: () => CompositeChartProvider,
|
|
35
36
|
CompositeImageProvider: () => CompositeImageProvider,
|
|
36
37
|
ContentAnalyzer: () => ContentAnalyzer,
|
|
38
|
+
ContentPatternClassifier: () => ContentPatternClassifier,
|
|
39
|
+
IterativeQAEngine: () => IterativeQAEngine,
|
|
37
40
|
KnowledgeGateway: () => KnowledgeGateway,
|
|
38
41
|
LocalImageProvider: () => LocalImageProvider,
|
|
39
42
|
MermaidProvider: () => MermaidProvider,
|
|
@@ -45,17 +48,23 @@ __export(index_exports, {
|
|
|
45
48
|
QuickChartProvider: () => QuickChartProvider,
|
|
46
49
|
RevealJsGenerator: () => RevealJsGenerator,
|
|
47
50
|
ScoreCalculator: () => ScoreCalculator,
|
|
51
|
+
SevenDimensionScorer: () => SevenDimensionScorer,
|
|
48
52
|
SlideFactory: () => SlideFactory,
|
|
53
|
+
SlideGenerator: () => SlideGenerator,
|
|
49
54
|
TemplateEngine: () => TemplateEngine,
|
|
50
55
|
TemplateNotFoundError: () => TemplateNotFoundError,
|
|
51
56
|
UnsplashImageProvider: () => UnsplashImageProvider,
|
|
52
57
|
VERSION: () => VERSION,
|
|
53
58
|
ValidationError: () => ValidationError,
|
|
59
|
+
createAutoFixEngine: () => createAutoFixEngine,
|
|
54
60
|
createDefaultChartProvider: () => createDefaultChartProvider,
|
|
55
61
|
createDefaultImageProvider: () => createDefaultImageProvider,
|
|
62
|
+
createIterativeQAEngine: () => createIterativeQAEngine,
|
|
63
|
+
createSlideFactory: () => createSlideFactory,
|
|
56
64
|
default: () => index_default,
|
|
57
65
|
generate: () => generate,
|
|
58
66
|
getKnowledgeGateway: () => getKnowledgeGateway,
|
|
67
|
+
initSlideGenerator: () => initSlideGenerator,
|
|
59
68
|
validate: () => validate
|
|
60
69
|
});
|
|
61
70
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -350,6 +359,439 @@ var KnowledgeGateway = class {
|
|
|
350
359
|
this.ensureLoaded();
|
|
351
360
|
return this.kb;
|
|
352
361
|
}
|
|
362
|
+
// ==========================================================================
|
|
363
|
+
// KB-DRIVEN SLIDEFACTORY METHODS (v7.0.0)
|
|
364
|
+
// ==========================================================================
|
|
365
|
+
/**
|
|
366
|
+
* Get allowed slide types for a specific presentation type.
|
|
367
|
+
* CRITICAL: SlideFactory must ONLY use types from this list.
|
|
368
|
+
*/
|
|
369
|
+
getAllowedSlideTypes(type) {
|
|
370
|
+
this.ensureLoaded();
|
|
371
|
+
const typeConfig = this.kb.presentation_types?.[type];
|
|
372
|
+
if (!typeConfig?.slide_types_allowed) {
|
|
373
|
+
const mode = this.getModeForType(type);
|
|
374
|
+
const templates = this.getSlideTemplates(mode);
|
|
375
|
+
return templates.map((t) => t.name.toLowerCase().replace(/\s+/g, "_"));
|
|
376
|
+
}
|
|
377
|
+
return typeConfig.slide_types_allowed;
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Get validation rules for a specific presentation type.
|
|
381
|
+
* Returns word limits, bullet limits, whitespace requirements, etc.
|
|
382
|
+
*/
|
|
383
|
+
getValidationRules(type) {
|
|
384
|
+
this.ensureLoaded();
|
|
385
|
+
const typeConfig = this.kb.presentation_types?.[type];
|
|
386
|
+
if (!typeConfig?.validation_rules) {
|
|
387
|
+
const mode = this.getModeForType(type);
|
|
388
|
+
return mode === "keynote" ? {
|
|
389
|
+
wordsPerSlide: { min: 1, max: 25, ideal: 10 },
|
|
390
|
+
whitespace: { min: 40, ideal: 50 },
|
|
391
|
+
bulletsPerSlide: { max: 3 },
|
|
392
|
+
actionTitlesRequired: false,
|
|
393
|
+
sourcesRequired: false
|
|
394
|
+
} : {
|
|
395
|
+
wordsPerSlide: { min: 40, max: 80, ideal: 60 },
|
|
396
|
+
whitespace: { min: 25, ideal: 30 },
|
|
397
|
+
bulletsPerSlide: { max: 5 },
|
|
398
|
+
actionTitlesRequired: true,
|
|
399
|
+
sourcesRequired: true
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
const rules = typeConfig.validation_rules;
|
|
403
|
+
return {
|
|
404
|
+
wordsPerSlide: {
|
|
405
|
+
min: rules.words_per_slide?.min ?? 1,
|
|
406
|
+
max: rules.words_per_slide?.max ?? 80,
|
|
407
|
+
ideal: rules.words_per_slide?.ideal ?? 40
|
|
408
|
+
},
|
|
409
|
+
whitespace: {
|
|
410
|
+
min: rules.whitespace?.min ?? 25,
|
|
411
|
+
ideal: rules.whitespace?.ideal ?? 30
|
|
412
|
+
},
|
|
413
|
+
bulletsPerSlide: {
|
|
414
|
+
max: rules.bullets_per_slide?.max ?? 5
|
|
415
|
+
},
|
|
416
|
+
actionTitlesRequired: rules.action_titles_required ?? false,
|
|
417
|
+
sourcesRequired: rules.sources_required ?? false,
|
|
418
|
+
calloutsRequired: rules.callouts_required,
|
|
419
|
+
denseDataAllowed: rules.dense_data_allowed,
|
|
420
|
+
codeBlocksAllowed: rules.code_blocks_allowed,
|
|
421
|
+
diagramsRequired: rules.diagrams_required
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* Get required elements that MUST be in the deck for this type.
|
|
426
|
+
*/
|
|
427
|
+
getRequiredElements(type) {
|
|
428
|
+
this.ensureLoaded();
|
|
429
|
+
const typeConfig = this.kb.presentation_types?.[type];
|
|
430
|
+
return typeConfig?.required_elements ?? [];
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* Get anti-patterns to avoid for this presentation type.
|
|
434
|
+
*/
|
|
435
|
+
getAntiPatternsForType(type) {
|
|
436
|
+
this.ensureLoaded();
|
|
437
|
+
const typeConfig = this.kb.presentation_types?.[type];
|
|
438
|
+
return typeConfig?.anti_patterns ?? [];
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Get typography specifications for this presentation type.
|
|
442
|
+
*/
|
|
443
|
+
getTypographyForType(type) {
|
|
444
|
+
this.ensureLoaded();
|
|
445
|
+
const typeConfig = this.kb.presentation_types?.[type];
|
|
446
|
+
const typo = typeConfig?.typography;
|
|
447
|
+
if (!typo) {
|
|
448
|
+
const mode = this.getModeForType(type);
|
|
449
|
+
const modeTypo = this.getTypography(mode);
|
|
450
|
+
const maxFontsValue = modeTypo.max_fonts;
|
|
451
|
+
return {
|
|
452
|
+
titles: modeTypo.titles ?? "48px, Bold",
|
|
453
|
+
body: modeTypo.body_text ?? "24px",
|
|
454
|
+
maxFonts: typeof maxFontsValue === "number" ? maxFontsValue : 2
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
return {
|
|
458
|
+
titles: typo.titles ?? "48px, Bold",
|
|
459
|
+
body: typo.body ?? "24px",
|
|
460
|
+
maxFonts: typeof typo.max_fonts === "number" ? typo.max_fonts : 2,
|
|
461
|
+
actionTitle: typo.action_title,
|
|
462
|
+
sectionHeaders: typo.section_headers,
|
|
463
|
+
dataLabels: typo.data_labels,
|
|
464
|
+
code: typo.code
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
/**
|
|
468
|
+
* Get CSS variables recipe for this presentation type.
|
|
469
|
+
*/
|
|
470
|
+
getCSSVariablesForType(type) {
|
|
471
|
+
this.ensureLoaded();
|
|
472
|
+
const recipes = this.kb.type_visual_recipes;
|
|
473
|
+
if (recipes?.[type]?.css_variables) {
|
|
474
|
+
return recipes[type].css_variables;
|
|
475
|
+
}
|
|
476
|
+
return `:root {
|
|
477
|
+
--color-background: #FAFAF9;
|
|
478
|
+
--color-primary: #0F172A;
|
|
479
|
+
--color-secondary: #475569;
|
|
480
|
+
--color-accent: #0369A1;
|
|
481
|
+
--color-text: #18181B;
|
|
482
|
+
--font-display: 'Inter', sans-serif;
|
|
483
|
+
--font-body: 'Inter', sans-serif;
|
|
484
|
+
}`;
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
* Get scoring weights for QA evaluation for this type.
|
|
488
|
+
*/
|
|
489
|
+
getScoringWeights(type) {
|
|
490
|
+
this.ensureLoaded();
|
|
491
|
+
const typeConfig = this.kb.presentation_types?.[type];
|
|
492
|
+
const weights = typeConfig?.scoring_weights;
|
|
493
|
+
if (!weights) {
|
|
494
|
+
return {
|
|
495
|
+
visualQuality: 30,
|
|
496
|
+
contentQuality: 30,
|
|
497
|
+
expertCompliance: 30,
|
|
498
|
+
accessibility: 10
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
return {
|
|
502
|
+
visualQuality: weights.visual_quality ?? 30,
|
|
503
|
+
contentQuality: weights.content_quality ?? 30,
|
|
504
|
+
expertCompliance: weights.expert_compliance ?? 30,
|
|
505
|
+
accessibility: weights.accessibility ?? 10
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
/**
|
|
509
|
+
* Get story structure framework for this presentation type.
|
|
510
|
+
*/
|
|
511
|
+
getStoryStructure(type) {
|
|
512
|
+
this.ensureLoaded();
|
|
513
|
+
const typeConfig = this.kb.presentation_types?.[type];
|
|
514
|
+
const mode = this.getModeForType(type);
|
|
515
|
+
if (mode === "business") {
|
|
516
|
+
return {
|
|
517
|
+
framework: "scqa",
|
|
518
|
+
requiredElements: ["situation", "complication", "answer"],
|
|
519
|
+
optionalElements: ["question", "evidence", "recommendation"]
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
return {
|
|
523
|
+
framework: "sparkline",
|
|
524
|
+
requiredElements: ["what_is", "what_could_be", "call_to_action"],
|
|
525
|
+
optionalElements: ["star_moment", "hook"]
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
/**
|
|
529
|
+
* Map a content pattern to the best allowed slide type.
|
|
530
|
+
* CRITICAL: This is how SlideFactory decides which slide type to use.
|
|
531
|
+
*/
|
|
532
|
+
mapContentPatternToSlideType(pattern, allowedTypes) {
|
|
533
|
+
const patternToTypes = {
|
|
534
|
+
big_number: ["big_number", "big-number", "data_insight", "metrics_grid", "metrics-grid"],
|
|
535
|
+
comparison: ["comparison", "options_comparison", "two_column", "two-column"],
|
|
536
|
+
timeline: ["timeline", "process_timeline", "process", "roadmap"],
|
|
537
|
+
process: ["process", "process_timeline", "timeline", "three_column", "three-column"],
|
|
538
|
+
metrics: ["metrics_grid", "metrics-grid", "data_insight", "big_number", "big-number"],
|
|
539
|
+
quote: ["quote", "testimonial", "social_proof", "social-proof"],
|
|
540
|
+
code: ["code_snippet", "technical", "two_column", "two-column"],
|
|
541
|
+
bullets: ["bullet_points", "bullet-points", "detailed_findings", "two_column", "two-column"],
|
|
542
|
+
prose: ["two_column", "two-column", "bullet_points", "bullet-points", "single_statement"]
|
|
543
|
+
};
|
|
544
|
+
const preferredTypes = patternToTypes[pattern.primaryPattern] || patternToTypes.prose || [];
|
|
545
|
+
for (const preferred of preferredTypes) {
|
|
546
|
+
const underscoreVersion = preferred.replace(/-/g, "_");
|
|
547
|
+
const dashVersion = preferred.replace(/_/g, "-");
|
|
548
|
+
if (allowedTypes.includes(preferred)) return preferred;
|
|
549
|
+
if (allowedTypes.includes(underscoreVersion)) return underscoreVersion;
|
|
550
|
+
if (allowedTypes.includes(dashVersion)) return dashVersion;
|
|
551
|
+
}
|
|
552
|
+
const contentTypes = [
|
|
553
|
+
"bullet_points",
|
|
554
|
+
"bullet-points",
|
|
555
|
+
"two_column",
|
|
556
|
+
"two-column",
|
|
557
|
+
"three_column",
|
|
558
|
+
"three-column",
|
|
559
|
+
"data_insight"
|
|
560
|
+
];
|
|
561
|
+
for (const ct of contentTypes) {
|
|
562
|
+
if (allowedTypes.includes(ct)) return ct;
|
|
563
|
+
}
|
|
564
|
+
return allowedTypes[0] ?? "bullet-points";
|
|
565
|
+
}
|
|
566
|
+
/**
|
|
567
|
+
* Get a specific slide template by name from the KB.
|
|
568
|
+
*/
|
|
569
|
+
getSlideTemplateByName(name) {
|
|
570
|
+
this.ensureLoaded();
|
|
571
|
+
const keynoteTemplates = this.kb.slide_templates?.keynote_mode ?? [];
|
|
572
|
+
const businessTemplates = this.kb.slide_templates?.business_mode ?? [];
|
|
573
|
+
const normalizedName = name.toLowerCase().replace(/[-_]/g, " ");
|
|
574
|
+
for (const template of [...keynoteTemplates, ...businessTemplates]) {
|
|
575
|
+
const templateName = template.name.toLowerCase().replace(/[-_]/g, " ");
|
|
576
|
+
if (templateName.includes(normalizedName) || normalizedName.includes(templateName)) {
|
|
577
|
+
return {
|
|
578
|
+
name: template.name,
|
|
579
|
+
purpose: template.purpose,
|
|
580
|
+
elements: template.elements,
|
|
581
|
+
components: template.components,
|
|
582
|
+
wordLimit: template.word_limit ?? 60
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
return void 0;
|
|
587
|
+
}
|
|
588
|
+
// ==========================================================================
|
|
589
|
+
// SLIDE DEFAULTS & LABELS (v7.1.0 - Replace ALL Hardcoded Values)
|
|
590
|
+
// ==========================================================================
|
|
591
|
+
/**
|
|
592
|
+
* Get slide defaults for structural slides (titles, messages, labels).
|
|
593
|
+
* CRITICAL: SlideFactory must use these instead of hardcoded strings.
|
|
594
|
+
*/
|
|
595
|
+
getSlideDefaults(type) {
|
|
596
|
+
this.ensureLoaded();
|
|
597
|
+
const mode = this.getModeForType(type);
|
|
598
|
+
const rules = this.getValidationRules(type);
|
|
599
|
+
const maxWords = rules.wordsPerSlide.max;
|
|
600
|
+
return {
|
|
601
|
+
agenda: { title: mode === "business" ? "Agenda" : "What We'll Cover" },
|
|
602
|
+
thankYou: {
|
|
603
|
+
title: mode === "business" ? "Thank You" : "Thank You",
|
|
604
|
+
subtitle: mode === "business" ? "Questions?" : "Let's Connect"
|
|
605
|
+
},
|
|
606
|
+
cta: {
|
|
607
|
+
title: mode === "business" ? "Next Steps" : "Take Action",
|
|
608
|
+
message: mode === "business" ? "Recommended Actions" : "Ready to Begin?",
|
|
609
|
+
fallback: mode === "business" ? "Contact us to learn more" : "Take the next step"
|
|
610
|
+
},
|
|
611
|
+
metricsGrid: {
|
|
612
|
+
title: mode === "business" ? "Key Metrics" : "The Numbers",
|
|
613
|
+
maxMetrics: 4
|
|
614
|
+
},
|
|
615
|
+
code: {
|
|
616
|
+
label: "Code Example",
|
|
617
|
+
maxChars: 500
|
|
618
|
+
},
|
|
619
|
+
comparison: {
|
|
620
|
+
leftLabel: mode === "business" ? "Current State" : "Before",
|
|
621
|
+
rightLabel: mode === "business" ? "Future State" : "After",
|
|
622
|
+
optionLabels: ["Option A", "Option B", "Option C", "Option D"]
|
|
623
|
+
},
|
|
624
|
+
column: { labelTemplate: "Point {n}" },
|
|
625
|
+
subtitle: { maxWords: Math.min(15, Math.floor(maxWords / 3)) },
|
|
626
|
+
context: { maxWords: Math.min(30, Math.floor(maxWords / 2)) },
|
|
627
|
+
step: { maxWords: Math.min(20, Math.floor(maxWords / 4)) },
|
|
628
|
+
columnContent: { maxWords: Math.min(25, Math.floor(maxWords / 3)) }
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
/**
|
|
632
|
+
* Get SCQA framework titles from KB (for business mode).
|
|
633
|
+
*/
|
|
634
|
+
getSCQATitles(type) {
|
|
635
|
+
this.ensureLoaded();
|
|
636
|
+
const mode = this.getModeForType(type);
|
|
637
|
+
if (mode === "business") {
|
|
638
|
+
return {
|
|
639
|
+
situation: "Current Situation",
|
|
640
|
+
complication: "The Challenge",
|
|
641
|
+
question: "The Critical Question",
|
|
642
|
+
answer: "Our Recommendation"
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
return {
|
|
646
|
+
situation: "Where We Are",
|
|
647
|
+
complication: "What's At Stake",
|
|
648
|
+
question: "The Question We Face",
|
|
649
|
+
answer: "The Path Forward"
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
/**
|
|
653
|
+
* Get Sparkline framework titles from KB (for keynote mode).
|
|
654
|
+
*/
|
|
655
|
+
getSparklineTitles(type) {
|
|
656
|
+
this.ensureLoaded();
|
|
657
|
+
return {
|
|
658
|
+
whatIs: "Where We Are Today",
|
|
659
|
+
whatCouldBe: "What Could Be",
|
|
660
|
+
callToAdventure: "The Call to Adventure",
|
|
661
|
+
newBliss: "The New World"
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
/**
|
|
665
|
+
* Get insight markers for detecting action titles.
|
|
666
|
+
* These are words/phrases that indicate an insight vs a topic.
|
|
667
|
+
*/
|
|
668
|
+
getInsightMarkers() {
|
|
669
|
+
this.ensureLoaded();
|
|
670
|
+
return [
|
|
671
|
+
// Trend indicators
|
|
672
|
+
"increase",
|
|
673
|
+
"decrease",
|
|
674
|
+
"grew",
|
|
675
|
+
"declined",
|
|
676
|
+
"achieve",
|
|
677
|
+
"exceed",
|
|
678
|
+
"improve",
|
|
679
|
+
"reduce",
|
|
680
|
+
"save",
|
|
681
|
+
"gain",
|
|
682
|
+
"lost",
|
|
683
|
+
"doubled",
|
|
684
|
+
"tripled",
|
|
685
|
+
"outperform",
|
|
686
|
+
"underperform",
|
|
687
|
+
"accelerate",
|
|
688
|
+
"decelerate",
|
|
689
|
+
// Quantitative markers
|
|
690
|
+
"%",
|
|
691
|
+
"percent",
|
|
692
|
+
"million",
|
|
693
|
+
"billion",
|
|
694
|
+
"thousand",
|
|
695
|
+
"$",
|
|
696
|
+
"\u20AC",
|
|
697
|
+
"\xA3",
|
|
698
|
+
"ROI",
|
|
699
|
+
"revenue",
|
|
700
|
+
"cost",
|
|
701
|
+
"margin",
|
|
702
|
+
"growth",
|
|
703
|
+
"decline",
|
|
704
|
+
// Causality markers
|
|
705
|
+
"due to",
|
|
706
|
+
"because",
|
|
707
|
+
"resulting in",
|
|
708
|
+
"leading to",
|
|
709
|
+
"enabling",
|
|
710
|
+
"driving",
|
|
711
|
+
"caused by",
|
|
712
|
+
"attributed to",
|
|
713
|
+
"as a result of",
|
|
714
|
+
// Action verbs (Minto Pyramid style)
|
|
715
|
+
"should",
|
|
716
|
+
"must",
|
|
717
|
+
"need to",
|
|
718
|
+
"recommend",
|
|
719
|
+
"propose",
|
|
720
|
+
"suggest",
|
|
721
|
+
"requires",
|
|
722
|
+
"demands",
|
|
723
|
+
"enables",
|
|
724
|
+
"prevents",
|
|
725
|
+
"ensures"
|
|
726
|
+
];
|
|
727
|
+
}
|
|
728
|
+
/**
|
|
729
|
+
* Get word limit for a specific slide element type.
|
|
730
|
+
*/
|
|
731
|
+
getWordLimitForElement(type, element) {
|
|
732
|
+
const rules = this.getValidationRules(type);
|
|
733
|
+
const maxWords = rules.wordsPerSlide.max;
|
|
734
|
+
const bulletsMax = rules.bulletsPerSlide.max;
|
|
735
|
+
switch (element) {
|
|
736
|
+
case "title":
|
|
737
|
+
return Math.min(15, Math.floor(maxWords / 4));
|
|
738
|
+
case "subtitle":
|
|
739
|
+
return Math.min(15, Math.floor(maxWords / 5));
|
|
740
|
+
case "bullet":
|
|
741
|
+
return Math.floor(maxWords / (bulletsMax || 5));
|
|
742
|
+
case "body":
|
|
743
|
+
return maxWords;
|
|
744
|
+
case "quote":
|
|
745
|
+
return Math.min(40, maxWords);
|
|
746
|
+
case "step":
|
|
747
|
+
return Math.min(20, Math.floor(maxWords / 4));
|
|
748
|
+
default:
|
|
749
|
+
return maxWords;
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
/**
|
|
753
|
+
* Validate a slide against KB rules for the given presentation type.
|
|
754
|
+
*/
|
|
755
|
+
validateSlideAgainstKB(slide, type) {
|
|
756
|
+
const rules = this.getValidationRules(type);
|
|
757
|
+
const allowedTypes = this.getAllowedSlideTypes(type);
|
|
758
|
+
const violations = [];
|
|
759
|
+
const warnings = [];
|
|
760
|
+
const normalizedType = slide.type.replace(/-/g, "_");
|
|
761
|
+
const isAllowed = allowedTypes.some(
|
|
762
|
+
(t) => t === slide.type || t === normalizedType || t.replace(/_/g, "-") === slide.type
|
|
763
|
+
);
|
|
764
|
+
if (!isAllowed) {
|
|
765
|
+
violations.push(`Slide type '${slide.type}' not allowed for ${type}. Allowed: ${allowedTypes.join(", ")}`);
|
|
766
|
+
}
|
|
767
|
+
if (slide.wordCount > rules.wordsPerSlide.max) {
|
|
768
|
+
violations.push(`Word count ${slide.wordCount} exceeds max ${rules.wordsPerSlide.max}`);
|
|
769
|
+
} else if (slide.wordCount < rules.wordsPerSlide.min) {
|
|
770
|
+
warnings.push(`Word count ${slide.wordCount} below min ${rules.wordsPerSlide.min}`);
|
|
771
|
+
}
|
|
772
|
+
if (slide.bulletCount > rules.bulletsPerSlide.max) {
|
|
773
|
+
violations.push(`Bullet count ${slide.bulletCount} exceeds max ${rules.bulletsPerSlide.max}`);
|
|
774
|
+
}
|
|
775
|
+
if (rules.actionTitlesRequired && !slide.hasActionTitle) {
|
|
776
|
+
violations.push(`Action title required for ${type} but not present`);
|
|
777
|
+
}
|
|
778
|
+
if (rules.sourcesRequired && !slide.hasSource) {
|
|
779
|
+
warnings.push(`Source citation recommended for ${type}`);
|
|
780
|
+
}
|
|
781
|
+
const fixes = {};
|
|
782
|
+
if (slide.wordCount > rules.wordsPerSlide.max) {
|
|
783
|
+
fixes.wordCount = rules.wordsPerSlide.max;
|
|
784
|
+
}
|
|
785
|
+
if (slide.bulletCount > rules.bulletsPerSlide.max) {
|
|
786
|
+
fixes.bulletCount = rules.bulletsPerSlide.max;
|
|
787
|
+
}
|
|
788
|
+
return {
|
|
789
|
+
valid: violations.length === 0,
|
|
790
|
+
violations,
|
|
791
|
+
warnings,
|
|
792
|
+
fixes
|
|
793
|
+
};
|
|
794
|
+
}
|
|
353
795
|
};
|
|
354
796
|
var gatewayInstance = null;
|
|
355
797
|
async function getKnowledgeGateway() {
|
|
@@ -487,12 +929,22 @@ var ContentAnalyzer = class {
|
|
|
487
929
|
}
|
|
488
930
|
/**
|
|
489
931
|
* Parse content based on type
|
|
932
|
+
* CRITICAL: Strip code blocks FIRST to prevent code from becoming slides
|
|
490
933
|
*/
|
|
491
934
|
parseContent(content, contentType) {
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
}
|
|
495
|
-
|
|
935
|
+
let text = content;
|
|
936
|
+
text = text.replace(/```[\s\S]*?```/g, "");
|
|
937
|
+
text = text.replace(/`[^`]{50,}`/g, "");
|
|
938
|
+
text = text.split("\n").filter((line) => {
|
|
939
|
+
const trimmed = line.trim();
|
|
940
|
+
if (/^(import|export|const|let|var|function|class|interface|type|async|await|return|if|for|while)\s/.test(trimmed)) return false;
|
|
941
|
+
if (/^(\/\/|\/\*|\*|#!)/.test(trimmed)) return false;
|
|
942
|
+
if (/^\s*[{}\[\]();]/.test(trimmed)) return false;
|
|
943
|
+
if (/^[\w.]+\s*\(.*\)\s*;?\s*$/.test(trimmed)) return false;
|
|
944
|
+
if (/^[\w.]+\s*=\s*/.test(trimmed) && /[{(\[]/.test(trimmed)) return false;
|
|
945
|
+
return true;
|
|
946
|
+
}).join("\n");
|
|
947
|
+
return text;
|
|
496
948
|
}
|
|
497
949
|
/**
|
|
498
950
|
* Extract the main title from content
|
|
@@ -610,6 +1062,7 @@ var ContentAnalyzer = class {
|
|
|
610
1062
|
}
|
|
611
1063
|
/**
|
|
612
1064
|
* Extract SCQA structure (Barbara Minto)
|
|
1065
|
+
* CRITICAL: Answer must be clean prose, NOT bullet points
|
|
613
1066
|
*/
|
|
614
1067
|
extractSCQA(text) {
|
|
615
1068
|
const paragraphs = text.split(/\n\n+/).filter((p) => p.trim());
|
|
@@ -617,34 +1070,45 @@ var ContentAnalyzer = class {
|
|
|
617
1070
|
let complication = "";
|
|
618
1071
|
let question = "";
|
|
619
1072
|
let answer = "";
|
|
620
|
-
for (const para of paragraphs.slice(0,
|
|
1073
|
+
for (const para of paragraphs.slice(0, 5)) {
|
|
1074
|
+
if (para.startsWith("-") || para.startsWith("#") || para.startsWith("|")) continue;
|
|
621
1075
|
if (this.containsSignals(para.toLowerCase(), this.situationSignals)) {
|
|
622
1076
|
situation = this.extractFirstSentence(para);
|
|
623
1077
|
break;
|
|
624
1078
|
}
|
|
625
1079
|
}
|
|
626
1080
|
for (const para of paragraphs) {
|
|
1081
|
+
if (para.startsWith("-") || para.startsWith("|")) continue;
|
|
627
1082
|
if (this.containsSignals(para.toLowerCase(), this.complicationSignals)) {
|
|
628
1083
|
complication = this.extractFirstSentence(para);
|
|
629
1084
|
break;
|
|
630
1085
|
}
|
|
631
1086
|
}
|
|
632
|
-
const
|
|
633
|
-
|
|
634
|
-
|
|
1087
|
+
for (const para of paragraphs) {
|
|
1088
|
+
if (para.startsWith("-") || para.includes("\n-") || para.startsWith("|")) continue;
|
|
1089
|
+
if (para.startsWith("*") && !para.startsWith("**")) continue;
|
|
635
1090
|
const lowerPara = para.toLowerCase();
|
|
636
1091
|
if (this.containsSignals(lowerPara, this.answerSignals) && !this.containsSignals(lowerPara, this.ctaSignals)) {
|
|
637
|
-
|
|
638
|
-
|
|
1092
|
+
const sentence = this.extractFirstSentence(para);
|
|
1093
|
+
if (sentence.split(/\s+/).length >= 8 && /\b(is|are|will|can|provides?|delivers?|enables?)\b/i.test(sentence)) {
|
|
1094
|
+
answer = sentence;
|
|
1095
|
+
break;
|
|
1096
|
+
}
|
|
639
1097
|
}
|
|
640
1098
|
}
|
|
641
|
-
if (!situation && paragraphs.length > 0
|
|
642
|
-
|
|
1099
|
+
if (!situation && paragraphs.length > 0) {
|
|
1100
|
+
for (const para of paragraphs.slice(0, 5)) {
|
|
1101
|
+
if (!para.startsWith("-") && !para.startsWith("#") && para.length > 50) {
|
|
1102
|
+
situation = this.extractFirstSentence(para);
|
|
1103
|
+
break;
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
643
1106
|
}
|
|
644
|
-
if (!answer
|
|
645
|
-
for (const para of paragraphs
|
|
1107
|
+
if (!answer) {
|
|
1108
|
+
for (const para of paragraphs) {
|
|
1109
|
+
if (para.startsWith("-") || para.includes("\n-")) continue;
|
|
646
1110
|
const lowerPara = para.toLowerCase();
|
|
647
|
-
if (lowerPara.includes("
|
|
1111
|
+
if (lowerPara.includes("bottom line") || lowerPara.includes("delivers both") || lowerPara.includes("the result") || lowerPara.includes("in summary")) {
|
|
648
1112
|
answer = this.extractFirstSentence(para);
|
|
649
1113
|
break;
|
|
650
1114
|
}
|
|
@@ -803,56 +1267,123 @@ var ContentAnalyzer = class {
|
|
|
803
1267
|
}
|
|
804
1268
|
/**
|
|
805
1269
|
* Extract data points (metrics with values)
|
|
806
|
-
*
|
|
1270
|
+
* REWRITTEN: Proper markdown table parsing and meaningful label extraction
|
|
807
1271
|
*/
|
|
808
1272
|
extractDataPoints(text) {
|
|
809
1273
|
const dataPoints = [];
|
|
810
1274
|
const usedValues = /* @__PURE__ */ new Set();
|
|
811
|
-
const
|
|
812
|
-
if (
|
|
813
|
-
const
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
const
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
usedValues.add(valueCell);
|
|
822
|
-
dataPoints.push({ value: valueCell, label: labelCell.slice(0, 40) });
|
|
823
|
-
}
|
|
1275
|
+
const bulletPatterns = text.match(/^[-*]\s+([^:]+):\s*(\$?[\d,.]+[MBK%]?(?:\s*\([^)]+\))?)/gm);
|
|
1276
|
+
if (bulletPatterns) {
|
|
1277
|
+
for (const match of bulletPatterns) {
|
|
1278
|
+
const parsed = match.match(/^[-*]\s+([^:]+):\s*(\$?[\d,.]+[MBK%]?)/);
|
|
1279
|
+
if (parsed && parsed[1] && parsed[2]) {
|
|
1280
|
+
const label = parsed[1].trim();
|
|
1281
|
+
const value = parsed[2].trim();
|
|
1282
|
+
if (!usedValues.has(value) && label.length >= 5) {
|
|
1283
|
+
usedValues.add(value);
|
|
1284
|
+
dataPoints.push({ value, label: this.cleanMetricLabel(label) });
|
|
824
1285
|
}
|
|
825
1286
|
}
|
|
826
1287
|
}
|
|
827
1288
|
}
|
|
828
1289
|
const lines = text.split("\n");
|
|
1290
|
+
let inTable = false;
|
|
1291
|
+
let tableRows = [];
|
|
1292
|
+
let headerRow = [];
|
|
829
1293
|
for (const line of lines) {
|
|
830
|
-
if (line.includes("|"))
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
1294
|
+
if (line.includes("|") && line.trim().length > 3) {
|
|
1295
|
+
const cells = line.split("|").map((c) => c.trim()).filter((c) => c.length > 0);
|
|
1296
|
+
if (cells.every((c) => /^[-:]+$/.test(c))) continue;
|
|
1297
|
+
if (!inTable) {
|
|
1298
|
+
headerRow = cells;
|
|
1299
|
+
inTable = true;
|
|
1300
|
+
} else {
|
|
1301
|
+
tableRows.push(cells);
|
|
1302
|
+
}
|
|
1303
|
+
} else if (inTable && tableRows.length > 0) {
|
|
1304
|
+
this.extractMetricsFromTable(headerRow, tableRows, dataPoints, usedValues);
|
|
1305
|
+
inTable = false;
|
|
1306
|
+
tableRows = [];
|
|
1307
|
+
headerRow = [];
|
|
835
1308
|
}
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
1309
|
+
}
|
|
1310
|
+
if (tableRows.length > 0) {
|
|
1311
|
+
this.extractMetricsFromTable(headerRow, tableRows, dataPoints, usedValues);
|
|
1312
|
+
}
|
|
1313
|
+
for (const line of lines) {
|
|
1314
|
+
if (line.includes("|")) continue;
|
|
1315
|
+
if (/```/.test(line)) continue;
|
|
1316
|
+
const percentPattern = line.match(/(\d+(?:\.\d+)?%)\s+(reduction|increase|improvement|growth|decrease)\s+(?:in\s+)?([^.]+)/i);
|
|
1317
|
+
if (percentPattern && percentPattern[1] && percentPattern[3]) {
|
|
1318
|
+
const value = percentPattern[1];
|
|
1319
|
+
if (!usedValues.has(value)) {
|
|
1320
|
+
usedValues.add(value);
|
|
1321
|
+
const action = percentPattern[2] || "change";
|
|
1322
|
+
const subject = percentPattern[3].slice(0, 30).trim();
|
|
1323
|
+
dataPoints.push({ value, label: `${action} in ${subject}` });
|
|
843
1324
|
}
|
|
844
1325
|
}
|
|
845
1326
|
}
|
|
846
1327
|
return dataPoints.slice(0, 4);
|
|
847
1328
|
}
|
|
1329
|
+
/**
|
|
1330
|
+
* Extract metrics from a parsed markdown table
|
|
1331
|
+
*/
|
|
1332
|
+
extractMetricsFromTable(headers, rows, dataPoints, usedValues) {
|
|
1333
|
+
for (const row of rows) {
|
|
1334
|
+
const label = row[0];
|
|
1335
|
+
if (!label || label.length < 3) continue;
|
|
1336
|
+
for (let i = 1; i < row.length; i++) {
|
|
1337
|
+
const cell = row[i];
|
|
1338
|
+
if (!cell) continue;
|
|
1339
|
+
if (/^\$?[\d,]+\.?\d*[%MBK]?$/.test(cell.replace(/[,\s]/g, "")) || /^\d+\s*(seconds?|minutes?|hours?|ms|gb|mb)$/i.test(cell)) {
|
|
1340
|
+
if (!usedValues.has(cell)) {
|
|
1341
|
+
usedValues.add(cell);
|
|
1342
|
+
const colHeader = headers[i] || "";
|
|
1343
|
+
const fullLabel = colHeader && colHeader !== label ? `${label} (${colHeader})` : label;
|
|
1344
|
+
dataPoints.push({
|
|
1345
|
+
value: cell,
|
|
1346
|
+
label: this.cleanMetricLabel(fullLabel)
|
|
1347
|
+
});
|
|
1348
|
+
break;
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
/**
|
|
1355
|
+
* Clean a metric label to ensure it's complete and meaningful
|
|
1356
|
+
*/
|
|
1357
|
+
cleanMetricLabel(raw) {
|
|
1358
|
+
let label = raw.replace(/\*\*/g, "").replace(/\|/g, " ").replace(/[:\-–—]+$/, "").replace(/\s+/g, " ").trim();
|
|
1359
|
+
if (label.length > 0) {
|
|
1360
|
+
label = label.charAt(0).toUpperCase() + label.slice(1);
|
|
1361
|
+
}
|
|
1362
|
+
if (label.length > 40) {
|
|
1363
|
+
const words = label.split(/\s+/);
|
|
1364
|
+
label = "";
|
|
1365
|
+
for (const word of words) {
|
|
1366
|
+
if ((label + " " + word).length <= 40) {
|
|
1367
|
+
label = label ? label + " " + word : word;
|
|
1368
|
+
} else {
|
|
1369
|
+
break;
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
return label || "Value";
|
|
1374
|
+
}
|
|
848
1375
|
/**
|
|
849
1376
|
* Extract a meaningful label from a line containing a metric
|
|
850
1377
|
*/
|
|
851
1378
|
extractLabelFromLine(line, value) {
|
|
1379
|
+
const colonMatch = line.match(/([^:]+):\s*\$?[\d]/);
|
|
1380
|
+
if (colonMatch && colonMatch[1]) {
|
|
1381
|
+
return this.cleanMetricLabel(colonMatch[1]);
|
|
1382
|
+
}
|
|
852
1383
|
const cleaned = line.replace(/\*\*/g, "").replace(/\|/g, " ").trim();
|
|
853
1384
|
const beforeValue = cleaned.split(value)[0] || "";
|
|
854
1385
|
const words = beforeValue.split(/\s+/).filter((w) => w.length > 2);
|
|
855
|
-
return words.slice(-4).join(" ")
|
|
1386
|
+
return this.cleanMetricLabel(words.slice(-4).join(" "));
|
|
856
1387
|
}
|
|
857
1388
|
/**
|
|
858
1389
|
* Check if text contains any of the signals
|
|
@@ -879,531 +1410,897 @@ var ContentAnalyzer = class {
|
|
|
879
1410
|
}
|
|
880
1411
|
};
|
|
881
1412
|
|
|
882
|
-
// src/core/
|
|
883
|
-
var
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
1413
|
+
// src/core/ContentPatternClassifier.ts
|
|
1414
|
+
var ContentPatternClassifier = class {
|
|
1415
|
+
// Regex patterns for content detection
|
|
1416
|
+
patterns = {
|
|
1417
|
+
// Big numbers: $4.88M, 23%, 10x, 500+, etc.
|
|
1418
|
+
bigNumber: /(\$[\d,.]+[MBK]?|\d+(?:\.\d+)?%|\d+[xX]|\d{2,}(?:\+|,\d{3})+)/,
|
|
1419
|
+
// Comparison language
|
|
1420
|
+
comparison: /\b(vs\.?|versus|compared\s+to|before\s+(?:and\s+)?after|better\s+than|worse\s+than|alternative|option\s+[A-Z1-3])\b/i,
|
|
1421
|
+
// Timeline markers
|
|
1422
|
+
timeline: /\b(phase\s*\d|week\s*\d|month\s*\d|day\s*\d|Q[1-4]|20\d{2}|step\s*\d|year\s*\d|sprint\s*\d)\b/i,
|
|
1423
|
+
// Process/steps language
|
|
1424
|
+
process: /\b(step\s*\d|stage\s*\d|pillar|phase|first|second|third|finally|then|next|workflow|process)\b/i,
|
|
1425
|
+
// Quote patterns
|
|
1426
|
+
quote: /^[""]|[""]\s*[-—–]\s*|^\s*>\s*|[""]$/,
|
|
1427
|
+
// Code patterns
|
|
1428
|
+
code: /```|`[^`]+`|function\s*\(|const\s+\w+\s*=|import\s+{|class\s+\w+|=>\s*{/,
|
|
1429
|
+
// Metric patterns (for detecting multiple metrics)
|
|
1430
|
+
metric: /(\d+(?:\.\d+)?(?:%|[xX]|[MBK])?)\s*[-–:]\s*|\b(?:increased|decreased|grew|improved|reduced)\s+(?:by\s+)?(\d+)/gi
|
|
1431
|
+
};
|
|
889
1432
|
/**
|
|
890
|
-
*
|
|
1433
|
+
* Classify a content section to determine its primary pattern.
|
|
1434
|
+
* Returns a ContentPattern object used by SlideFactory to select slide type.
|
|
891
1435
|
*/
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
const
|
|
895
|
-
|
|
896
|
-
|
|
1436
|
+
classify(section) {
|
|
1437
|
+
const fullText = `${section.header} ${section.content} ${section.bullets.join(" ")}`;
|
|
1438
|
+
const wordCount = fullText.split(/\s+/).filter((w) => w.length > 0).length;
|
|
1439
|
+
const hasBigNumber = this.patterns.bigNumber.test(fullText);
|
|
1440
|
+
const bigNumberMatch = fullText.match(this.patterns.bigNumber);
|
|
1441
|
+
const hasComparison = this.patterns.comparison.test(fullText);
|
|
1442
|
+
const hasTimeline = this.patterns.timeline.test(fullText);
|
|
1443
|
+
const hasProcess = this.patterns.process.test(section.header) || section.bullets.length >= 3 && this.hasNumberedItems(section.bullets);
|
|
1444
|
+
const hasQuote = this.patterns.quote.test(section.content);
|
|
1445
|
+
const hasCode = this.patterns.code.test(section.content);
|
|
1446
|
+
const metricMatches = fullText.match(this.patterns.metric);
|
|
1447
|
+
const hasMetrics = section.metrics.length >= 3 || metricMatches && metricMatches.length >= 3;
|
|
1448
|
+
let primaryPattern = "prose";
|
|
1449
|
+
if (hasQuote && section.content.length > 20) {
|
|
1450
|
+
primaryPattern = "quote";
|
|
1451
|
+
} else if (hasCode) {
|
|
1452
|
+
primaryPattern = "code";
|
|
1453
|
+
} else if (hasBigNumber && wordCount < 30) {
|
|
1454
|
+
primaryPattern = "big_number";
|
|
1455
|
+
} else if (hasMetrics) {
|
|
1456
|
+
primaryPattern = "metrics";
|
|
1457
|
+
} else if (hasComparison) {
|
|
1458
|
+
primaryPattern = "comparison";
|
|
1459
|
+
} else if (hasTimeline) {
|
|
1460
|
+
primaryPattern = "timeline";
|
|
1461
|
+
} else if (hasProcess && section.bullets.length >= 3) {
|
|
1462
|
+
primaryPattern = "process";
|
|
1463
|
+
} else if (section.bullets.length >= 2) {
|
|
1464
|
+
primaryPattern = "bullets";
|
|
1465
|
+
} else {
|
|
1466
|
+
primaryPattern = "prose";
|
|
897
1467
|
}
|
|
898
|
-
|
|
899
|
-
|
|
1468
|
+
const result = {
|
|
1469
|
+
hasBigNumber,
|
|
1470
|
+
hasComparison,
|
|
1471
|
+
hasTimeline,
|
|
1472
|
+
hasProcess,
|
|
1473
|
+
hasMetrics: !!hasMetrics,
|
|
1474
|
+
hasQuote,
|
|
1475
|
+
hasCode,
|
|
1476
|
+
bulletCount: section.bullets.length,
|
|
1477
|
+
wordCount,
|
|
1478
|
+
primaryPattern
|
|
1479
|
+
};
|
|
1480
|
+
if (bigNumberMatch && bigNumberMatch[1]) {
|
|
1481
|
+
result.bigNumberValue = bigNumberMatch[1];
|
|
1482
|
+
}
|
|
1483
|
+
return result;
|
|
900
1484
|
}
|
|
901
1485
|
/**
|
|
902
|
-
*
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
* 5. Metrics slide - if data points exist
|
|
910
|
-
* 6. Solution slide - SCQA answer
|
|
911
|
-
* 7. STAR moments - only if high-quality (per Duarte)
|
|
912
|
-
* 8. CTA slide - if call to action exists
|
|
913
|
-
* 9. Thank you slide - always last
|
|
914
|
-
*/
|
|
915
|
-
async createSlides(analysis, mode) {
|
|
916
|
-
const slides = [];
|
|
917
|
-
let slideIndex = 0;
|
|
918
|
-
this.usedContent.clear();
|
|
919
|
-
slides.push(this.createTitleSlide(slideIndex++, analysis));
|
|
920
|
-
this.isContentUsed(analysis.titles[0] ?? "");
|
|
921
|
-
const substantiveSections = analysis.sections.filter(
|
|
922
|
-
(s) => s.level === 2 && (s.bullets.length > 0 || s.content.length > 50)
|
|
923
|
-
);
|
|
924
|
-
if (mode === "business" && substantiveSections.length >= 3) {
|
|
925
|
-
slides.push(this.createAgendaSlide(slideIndex++, analysis));
|
|
926
|
-
}
|
|
927
|
-
if (analysis.scqa.situation && analysis.scqa.situation.length > 30 && !this.isContentUsed(analysis.scqa.situation)) {
|
|
928
|
-
slides.push(this.createContextSlide(slideIndex++, analysis, mode));
|
|
929
|
-
}
|
|
930
|
-
if (analysis.scqa.complication && analysis.scqa.complication.length > 30 && !this.isContentUsed(analysis.scqa.complication)) {
|
|
931
|
-
slides.push(this.createProblemSlide(slideIndex++, analysis, mode));
|
|
932
|
-
}
|
|
933
|
-
for (const section of substantiveSections.slice(0, 4)) {
|
|
934
|
-
const headerUsed = this.isContentUsed(section.header);
|
|
935
|
-
const contentUsed = section.content.length > 30 && this.isContentUsed(section.content.slice(0, 80));
|
|
936
|
-
if (!headerUsed && !contentUsed) {
|
|
937
|
-
if (section.bullets.length > 0) {
|
|
938
|
-
slides.push(this.createSectionBulletSlide(slideIndex++, section, mode));
|
|
939
|
-
} else if (section.content.length > 50) {
|
|
940
|
-
slides.push(this.createSectionContentSlide(slideIndex++, section, mode));
|
|
941
|
-
}
|
|
1486
|
+
* Check if bullets appear to be numbered/ordered items
|
|
1487
|
+
*/
|
|
1488
|
+
hasNumberedItems(bullets) {
|
|
1489
|
+
let numberedCount = 0;
|
|
1490
|
+
for (const bullet of bullets) {
|
|
1491
|
+
if (/^\d+[.)]\s|^[a-z][.)]\s|^step\s*\d/i.test(bullet.trim())) {
|
|
1492
|
+
numberedCount++;
|
|
942
1493
|
}
|
|
943
1494
|
}
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
1495
|
+
return numberedCount >= 2;
|
|
1496
|
+
}
|
|
1497
|
+
/**
|
|
1498
|
+
* Extract the big number value from content for display
|
|
1499
|
+
*/
|
|
1500
|
+
extractBigNumber(content) {
|
|
1501
|
+
const match = content.match(this.patterns.bigNumber);
|
|
1502
|
+
if (!match || !match[1]) return null;
|
|
1503
|
+
const value = match[1];
|
|
1504
|
+
const afterNumber = content.slice(content.indexOf(value) + value.length);
|
|
1505
|
+
const context = afterNumber.trim().slice(0, 60).split(/[.!?]/)[0]?.trim() ?? "";
|
|
1506
|
+
return { value, context };
|
|
1507
|
+
}
|
|
1508
|
+
/**
|
|
1509
|
+
* Extract comparison elements from content
|
|
1510
|
+
*/
|
|
1511
|
+
extractComparison(section) {
|
|
1512
|
+
const text = section.content;
|
|
1513
|
+
const vsMatch = text.match(/(.{10,50})\s+(?:vs\.?|versus)\s+(.{10,50})/i);
|
|
1514
|
+
if (vsMatch && vsMatch[1] && vsMatch[2]) {
|
|
1515
|
+
return { left: vsMatch[1].trim(), right: vsMatch[2].trim() };
|
|
1516
|
+
}
|
|
1517
|
+
const beforeAfterMatch = text.match(/before[:\s]+(.{10,100})(?:after[:\s]+(.{10,100}))?/i);
|
|
1518
|
+
if (beforeAfterMatch && beforeAfterMatch[1]) {
|
|
1519
|
+
const left = beforeAfterMatch[1].trim();
|
|
1520
|
+
const right = beforeAfterMatch[2] ? beforeAfterMatch[2].trim() : "After";
|
|
1521
|
+
return { left, right };
|
|
1522
|
+
}
|
|
1523
|
+
if (section.bullets.length === 2) {
|
|
1524
|
+
const left = section.bullets[0];
|
|
1525
|
+
const right = section.bullets[1];
|
|
1526
|
+
if (left && right) {
|
|
1527
|
+
return { left, right };
|
|
948
1528
|
}
|
|
949
1529
|
}
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
const
|
|
957
|
-
for (const
|
|
958
|
-
const
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
1530
|
+
return null;
|
|
1531
|
+
}
|
|
1532
|
+
/**
|
|
1533
|
+
* Extract timeline/process steps from content
|
|
1534
|
+
*/
|
|
1535
|
+
extractSteps(section) {
|
|
1536
|
+
const steps = [];
|
|
1537
|
+
for (const bullet of section.bullets) {
|
|
1538
|
+
const stepMatch = bullet.match(/^((?:step|phase|stage)\s*\d+|[1-9]\.|\d+\))\s*[-:.]?\s*(.+)/i);
|
|
1539
|
+
if (stepMatch && stepMatch[1] && stepMatch[2]) {
|
|
1540
|
+
steps.push({
|
|
1541
|
+
label: stepMatch[1].trim(),
|
|
1542
|
+
description: stepMatch[2].trim()
|
|
1543
|
+
});
|
|
1544
|
+
} else {
|
|
1545
|
+
steps.push({
|
|
1546
|
+
label: `${steps.length + 1}`,
|
|
1547
|
+
description: bullet
|
|
1548
|
+
});
|
|
962
1549
|
}
|
|
963
1550
|
}
|
|
964
|
-
|
|
965
|
-
slides.push(this.createCTASlide(slideIndex++, analysis, mode));
|
|
966
|
-
}
|
|
967
|
-
slides.push(this.createThankYouSlide(slideIndex++));
|
|
968
|
-
return slides;
|
|
1551
|
+
return steps;
|
|
969
1552
|
}
|
|
970
1553
|
/**
|
|
971
|
-
*
|
|
1554
|
+
* Determine if content is suitable for a three-column layout
|
|
1555
|
+
* (3 pillars, 3 key points, etc.)
|
|
972
1556
|
*/
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
return
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
title: this.truncate(section.header, 60),
|
|
980
|
-
bullets: bullets.map((b) => this.cleanText(b).slice(0, 80))
|
|
981
|
-
},
|
|
982
|
-
classes: ["slide-bullet-points"]
|
|
983
|
-
};
|
|
1557
|
+
isSuitableForThreeColumn(section) {
|
|
1558
|
+
if (section.bullets.length === 3) return true;
|
|
1559
|
+
if (/three|3\s*(pillar|point|key|step)/i.test(section.header)) return true;
|
|
1560
|
+
const threePartPattern = /first[,.].*second[,.].*third/i;
|
|
1561
|
+
if (threePartPattern.test(section.content)) return true;
|
|
1562
|
+
return false;
|
|
984
1563
|
}
|
|
985
1564
|
/**
|
|
986
|
-
*
|
|
1565
|
+
* Check if content should be displayed as a quote slide
|
|
987
1566
|
*/
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
type: "single-statement",
|
|
993
|
-
data: {
|
|
994
|
-
title: this.truncate(this.extractFirstSentence(section.content), 80),
|
|
995
|
-
keyMessage: section.header
|
|
996
|
-
},
|
|
997
|
-
classes: ["slide-single-statement"]
|
|
998
|
-
};
|
|
1567
|
+
isQuoteContent(section) {
|
|
1568
|
+
const content = section.content.trim();
|
|
1569
|
+
if (!this.patterns.quote.test(content)) {
|
|
1570
|
+
return { isQuote: false };
|
|
999
1571
|
}
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
body: this.truncate(section.content, 200)
|
|
1006
|
-
},
|
|
1007
|
-
classes: ["slide-two-column"]
|
|
1008
|
-
};
|
|
1572
|
+
const attrMatch = content.match(/[-—–]\s*(.+)$/);
|
|
1573
|
+
if (attrMatch && attrMatch[1]) {
|
|
1574
|
+
return { isQuote: true, attribution: attrMatch[1].trim() };
|
|
1575
|
+
}
|
|
1576
|
+
return { isQuote: true };
|
|
1009
1577
|
}
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1578
|
+
};
|
|
1579
|
+
|
|
1580
|
+
// src/core/SlideFactory.ts
|
|
1581
|
+
var SlideFactory = class {
|
|
1582
|
+
kb;
|
|
1583
|
+
presentationType;
|
|
1584
|
+
classifier;
|
|
1585
|
+
config;
|
|
1586
|
+
usedContent;
|
|
1587
|
+
usedTitles;
|
|
1588
|
+
constructor(kb, type) {
|
|
1589
|
+
this.kb = kb;
|
|
1590
|
+
this.presentationType = type;
|
|
1591
|
+
this.classifier = new ContentPatternClassifier();
|
|
1592
|
+
this.usedContent = /* @__PURE__ */ new Set();
|
|
1593
|
+
this.usedTitles = /* @__PURE__ */ new Set();
|
|
1594
|
+
this.config = this.loadKBConfig(type);
|
|
1595
|
+
console.log(` \u2713 SlideFactory v7.1.0 initialized for ${type} (${this.config.mode} mode)`);
|
|
1596
|
+
console.log(` \u2713 KB Config loaded: ${this.config.allowedTypes.length} allowed types`);
|
|
1017
1597
|
}
|
|
1018
1598
|
/**
|
|
1019
|
-
*
|
|
1599
|
+
* Load ALL configuration from KB - ZERO hardcoded values after this.
|
|
1020
1600
|
*/
|
|
1021
|
-
|
|
1022
|
-
const metrics = dataPoints.slice(0, 4).map((dp) => ({
|
|
1023
|
-
value: dp.value,
|
|
1024
|
-
label: this.cleanText(dp.label).slice(0, 40)
|
|
1025
|
-
}));
|
|
1601
|
+
loadKBConfig(type) {
|
|
1026
1602
|
return {
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1603
|
+
defaults: this.kb.getSlideDefaults(type),
|
|
1604
|
+
scqaTitles: this.kb.getSCQATitles(type),
|
|
1605
|
+
sparklineTitles: this.kb.getSparklineTitles(type),
|
|
1606
|
+
rules: this.kb.getValidationRules(type),
|
|
1607
|
+
allowedTypes: this.kb.getAllowedSlideTypes(type),
|
|
1608
|
+
mode: this.kb.getModeForType(type),
|
|
1609
|
+
insightMarkers: this.kb.getInsightMarkers(),
|
|
1610
|
+
glanceTest: this.kb.getDuarteGlanceTest(),
|
|
1611
|
+
millersLaw: this.kb.getMillersLaw(),
|
|
1612
|
+
typography: this.kb.getTypographyForType(type),
|
|
1613
|
+
antiPatterns: this.kb.getAntiPatternsForType(type),
|
|
1614
|
+
requiredElements: this.kb.getRequiredElements(type)
|
|
1034
1615
|
};
|
|
1035
1616
|
}
|
|
1036
1617
|
/**
|
|
1037
|
-
* Create
|
|
1618
|
+
* Create all slides from content analysis.
|
|
1619
|
+
* This is the main entry point - orchestrates the entire slide creation process.
|
|
1038
1620
|
*/
|
|
1621
|
+
async createSlides(analysis) {
|
|
1622
|
+
const slides = [];
|
|
1623
|
+
const storyStructure = this.kb.getStoryStructure(this.presentationType);
|
|
1624
|
+
console.log(` \u2713 Using ${storyStructure.framework} story framework`);
|
|
1625
|
+
slides.push(this.createTitleSlide(0, analysis));
|
|
1626
|
+
const minSectionsForAgenda = Math.max(3, this.config.rules.bulletsPerSlide.max - 2);
|
|
1627
|
+
if (this.config.mode === "business" && analysis.sections.length >= minSectionsForAgenda) {
|
|
1628
|
+
slides.push(this.createAgendaSlide(slides.length, analysis));
|
|
1629
|
+
}
|
|
1630
|
+
if (storyStructure.framework === "scqa") {
|
|
1631
|
+
this.addSCQASlides(slides, analysis);
|
|
1632
|
+
} else if (storyStructure.framework === "sparkline") {
|
|
1633
|
+
this.addSparklineSlides(slides, analysis);
|
|
1634
|
+
}
|
|
1635
|
+
for (const section of analysis.sections) {
|
|
1636
|
+
const contentKey = this.normalizeKey(section.header);
|
|
1637
|
+
if (this.usedContent.has(contentKey)) continue;
|
|
1638
|
+
this.usedContent.add(contentKey);
|
|
1639
|
+
const pattern = this.classifier.classify(section);
|
|
1640
|
+
const slideType = this.kb.mapContentPatternToSlideType(pattern, this.config.allowedTypes);
|
|
1641
|
+
const slide = this.createSlideByType(slides.length, slideType, section, pattern);
|
|
1642
|
+
if (slide) {
|
|
1643
|
+
slides.push(slide);
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1646
|
+
const minDataPointsForMetrics = 2;
|
|
1647
|
+
if (analysis.dataPoints.length >= minDataPointsForMetrics) {
|
|
1648
|
+
const hasMetricsSlide = slides.some(
|
|
1649
|
+
(s) => s.type === "metrics-grid" || s.type === "big-number"
|
|
1650
|
+
);
|
|
1651
|
+
if (!hasMetricsSlide) {
|
|
1652
|
+
slides.push(this.createMetricsGridSlide(slides.length, analysis.dataPoints));
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1655
|
+
if (analysis.sparkline?.callToAdventure || analysis.scqa?.answer) {
|
|
1656
|
+
slides.push(this.createCTASlide(slides.length, analysis));
|
|
1657
|
+
}
|
|
1658
|
+
slides.push(this.createThankYouSlide(slides.length));
|
|
1659
|
+
const validatedSlides = this.validateAndFixSlides(slides);
|
|
1660
|
+
this.checkRequiredElements(validatedSlides);
|
|
1661
|
+
this.checkAntiPatterns(validatedSlides);
|
|
1662
|
+
console.log(` \u2713 Created ${validatedSlides.length} validated slides`);
|
|
1663
|
+
return validatedSlides;
|
|
1664
|
+
}
|
|
1665
|
+
// ===========================================================================
|
|
1666
|
+
// SLIDE TYPE ROUTER - Routes to appropriate creator based on KB-selected type
|
|
1667
|
+
// ===========================================================================
|
|
1668
|
+
createSlideByType(index, type, section, pattern) {
|
|
1669
|
+
const normalizedType = type.toLowerCase().replace(/_/g, "-");
|
|
1670
|
+
switch (normalizedType) {
|
|
1671
|
+
case "big-number":
|
|
1672
|
+
case "data-insight":
|
|
1673
|
+
return this.createBigNumberSlide(index, section, pattern);
|
|
1674
|
+
case "comparison":
|
|
1675
|
+
case "options-comparison":
|
|
1676
|
+
return this.createComparisonSlide(index, section);
|
|
1677
|
+
case "timeline":
|
|
1678
|
+
case "process-timeline":
|
|
1679
|
+
case "roadmap":
|
|
1680
|
+
return this.createTimelineSlide(index, section);
|
|
1681
|
+
case "process":
|
|
1682
|
+
return this.createProcessSlide(index, section);
|
|
1683
|
+
case "three-column":
|
|
1684
|
+
return this.createThreeColumnSlide(index, section);
|
|
1685
|
+
case "two-column":
|
|
1686
|
+
return this.createTwoColumnSlide(index, section);
|
|
1687
|
+
case "quote":
|
|
1688
|
+
case "testimonial":
|
|
1689
|
+
case "social-proof":
|
|
1690
|
+
return this.createQuoteSlide(index, section);
|
|
1691
|
+
case "metrics-grid":
|
|
1692
|
+
return this.createMetricsGridSlide(index, section.metrics);
|
|
1693
|
+
case "bullet-points":
|
|
1694
|
+
case "detailed-findings":
|
|
1695
|
+
return this.createBulletSlide(index, section);
|
|
1696
|
+
case "single-statement":
|
|
1697
|
+
case "big-idea":
|
|
1698
|
+
return this.createSingleStatementSlide(index, section);
|
|
1699
|
+
case "code-snippet":
|
|
1700
|
+
case "technical":
|
|
1701
|
+
return this.createCodeSlide(index, section);
|
|
1702
|
+
default:
|
|
1703
|
+
console.log(` \u26A0 Unknown slide type '${type}', using bullet-points`);
|
|
1704
|
+
return this.createBulletSlide(index, section);
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1707
|
+
// ===========================================================================
|
|
1708
|
+
// STRUCTURAL SLIDES (title, agenda, thank you) - ALL from KB
|
|
1709
|
+
// ===========================================================================
|
|
1039
1710
|
createTitleSlide(index, analysis) {
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
subtitle =
|
|
1711
|
+
const data = {
|
|
1712
|
+
title: this.truncateText(analysis.title, this.config.rules.wordsPerSlide.max)
|
|
1713
|
+
};
|
|
1714
|
+
if (analysis.keyMessages[0]) {
|
|
1715
|
+
data.subtitle = this.truncateText(
|
|
1716
|
+
analysis.keyMessages[0],
|
|
1717
|
+
this.config.defaults.subtitle.maxWords
|
|
1718
|
+
// FROM KB
|
|
1719
|
+
);
|
|
1045
1720
|
}
|
|
1046
1721
|
return {
|
|
1047
1722
|
index,
|
|
1048
1723
|
type: "title",
|
|
1049
|
-
data
|
|
1050
|
-
|
|
1051
|
-
subtitle: this.truncate(subtitle, 80),
|
|
1052
|
-
keyMessage: analysis.scqa.answer
|
|
1053
|
-
},
|
|
1054
|
-
classes: ["slide-title"]
|
|
1724
|
+
data,
|
|
1725
|
+
classes: ["title-slide"]
|
|
1055
1726
|
};
|
|
1056
1727
|
}
|
|
1057
|
-
/**
|
|
1058
|
-
* Create an agenda slide.
|
|
1059
|
-
*/
|
|
1060
1728
|
createAgendaSlide(index, analysis) {
|
|
1729
|
+
const agendaItems = analysis.sections.filter((s) => s.level <= 2).slice(0, this.config.rules.bulletsPerSlide.max).map((s) => this.cleanText(s.header));
|
|
1061
1730
|
return {
|
|
1062
1731
|
index,
|
|
1063
1732
|
type: "agenda",
|
|
1064
1733
|
data: {
|
|
1065
|
-
title:
|
|
1066
|
-
|
|
1734
|
+
title: this.config.defaults.agenda.title,
|
|
1735
|
+
// FROM KB - not hardcoded 'Agenda'
|
|
1736
|
+
bullets: agendaItems
|
|
1067
1737
|
},
|
|
1068
|
-
classes: ["slide
|
|
1738
|
+
classes: ["agenda-slide"]
|
|
1069
1739
|
};
|
|
1070
1740
|
}
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1741
|
+
createThankYouSlide(index) {
|
|
1742
|
+
return {
|
|
1743
|
+
index,
|
|
1744
|
+
type: "thank-you",
|
|
1745
|
+
data: {
|
|
1746
|
+
title: this.config.defaults.thankYou.title,
|
|
1747
|
+
// FROM KB - not hardcoded 'Thank You'
|
|
1748
|
+
subtitle: this.config.defaults.thankYou.subtitle
|
|
1749
|
+
// FROM KB - not hardcoded 'Questions?'
|
|
1750
|
+
},
|
|
1751
|
+
classes: ["thank-you-slide"]
|
|
1752
|
+
};
|
|
1753
|
+
}
|
|
1754
|
+
// ===========================================================================
|
|
1755
|
+
// STORY FRAMEWORK SLIDES (SCQA, Sparkline) - ALL titles from KB
|
|
1756
|
+
// ===========================================================================
|
|
1757
|
+
addSCQASlides(slides, analysis) {
|
|
1758
|
+
const scqa = analysis.scqa;
|
|
1759
|
+
const titles = this.config.scqaTitles;
|
|
1760
|
+
if (scqa?.situation && !this.usedContent.has("scqa-situation")) {
|
|
1761
|
+
this.usedContent.add("scqa-situation");
|
|
1762
|
+
slides.push({
|
|
1763
|
+
index: slides.length,
|
|
1078
1764
|
type: "single-statement",
|
|
1079
1765
|
data: {
|
|
1080
|
-
title:
|
|
1081
|
-
|
|
1766
|
+
title: titles.situation,
|
|
1767
|
+
// FROM KB - not hardcoded 'Current Situation'
|
|
1768
|
+
body: this.truncateText(scqa.situation, this.config.rules.wordsPerSlide.max)
|
|
1082
1769
|
},
|
|
1083
|
-
classes: ["slide
|
|
1084
|
-
};
|
|
1770
|
+
classes: ["situation-slide"]
|
|
1771
|
+
});
|
|
1772
|
+
}
|
|
1773
|
+
if (scqa?.complication && !this.usedContent.has("scqa-complication")) {
|
|
1774
|
+
this.usedContent.add("scqa-complication");
|
|
1775
|
+
slides.push({
|
|
1776
|
+
index: slides.length,
|
|
1777
|
+
type: "single-statement",
|
|
1778
|
+
data: {
|
|
1779
|
+
title: titles.complication,
|
|
1780
|
+
// FROM KB - not hardcoded 'The Challenge'
|
|
1781
|
+
body: this.truncateText(scqa.complication, this.config.rules.wordsPerSlide.max)
|
|
1782
|
+
},
|
|
1783
|
+
classes: ["complication-slide"]
|
|
1784
|
+
});
|
|
1785
|
+
}
|
|
1786
|
+
if (scqa?.question && !this.usedContent.has("scqa-question")) {
|
|
1787
|
+
this.usedContent.add("scqa-question");
|
|
1788
|
+
slides.push({
|
|
1789
|
+
index: slides.length,
|
|
1790
|
+
type: "single-statement",
|
|
1791
|
+
data: {
|
|
1792
|
+
title: titles.question,
|
|
1793
|
+
// FROM KB - not hardcoded 'The Question'
|
|
1794
|
+
body: this.truncateText(scqa.question, this.config.rules.wordsPerSlide.max)
|
|
1795
|
+
},
|
|
1796
|
+
classes: ["question-slide"]
|
|
1797
|
+
});
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
addSparklineSlides(slides, analysis) {
|
|
1801
|
+
const spark = analysis.sparkline;
|
|
1802
|
+
const titles = this.config.sparklineTitles;
|
|
1803
|
+
const whatIsFirst = spark?.whatIs?.[0];
|
|
1804
|
+
if (whatIsFirst && !this.usedContent.has("spark-what-is")) {
|
|
1805
|
+
this.usedContent.add("spark-what-is");
|
|
1806
|
+
slides.push({
|
|
1807
|
+
index: slides.length,
|
|
1808
|
+
type: "single-statement",
|
|
1809
|
+
data: {
|
|
1810
|
+
title: titles.whatIs,
|
|
1811
|
+
// FROM KB - not hardcoded 'Where We Are Today'
|
|
1812
|
+
body: this.truncateText(whatIsFirst, this.config.rules.wordsPerSlide.max)
|
|
1813
|
+
},
|
|
1814
|
+
classes: ["what-is-slide"]
|
|
1815
|
+
});
|
|
1816
|
+
}
|
|
1817
|
+
const whatCouldBeFirst = spark?.whatCouldBe?.[0];
|
|
1818
|
+
if (whatCouldBeFirst && !this.usedContent.has("spark-could-be")) {
|
|
1819
|
+
this.usedContent.add("spark-could-be");
|
|
1820
|
+
slides.push({
|
|
1821
|
+
index: slides.length,
|
|
1822
|
+
type: "single-statement",
|
|
1823
|
+
data: {
|
|
1824
|
+
title: titles.whatCouldBe,
|
|
1825
|
+
// FROM KB - not hardcoded 'What Could Be'
|
|
1826
|
+
body: this.truncateText(whatCouldBeFirst, this.config.rules.wordsPerSlide.max)
|
|
1827
|
+
},
|
|
1828
|
+
classes: ["what-could-be-slide"]
|
|
1829
|
+
});
|
|
1085
1830
|
}
|
|
1831
|
+
}
|
|
1832
|
+
// ===========================================================================
|
|
1833
|
+
// CONTENT SLIDES - ALL values from KB
|
|
1834
|
+
// ===========================================================================
|
|
1835
|
+
createBigNumberSlide(index, section, pattern) {
|
|
1836
|
+
const bigNumber = this.classifier.extractBigNumber(
|
|
1837
|
+
`${section.header} ${section.content} ${section.bullets.join(" ")}`
|
|
1838
|
+
);
|
|
1086
1839
|
return {
|
|
1087
1840
|
index,
|
|
1088
|
-
type: "
|
|
1841
|
+
type: "big-number",
|
|
1089
1842
|
data: {
|
|
1090
|
-
title:
|
|
1091
|
-
|
|
1092
|
-
|
|
1843
|
+
title: this.createTitle(section.header, section),
|
|
1844
|
+
keyMessage: bigNumber?.value || pattern.bigNumberValue || "0",
|
|
1845
|
+
body: bigNumber?.context || this.truncateText(
|
|
1846
|
+
section.content,
|
|
1847
|
+
this.config.defaults.context.maxWords
|
|
1848
|
+
// FROM KB - not hardcoded 30
|
|
1849
|
+
)
|
|
1093
1850
|
},
|
|
1094
|
-
classes: ["
|
|
1851
|
+
classes: ["big-number-slide"]
|
|
1095
1852
|
};
|
|
1096
1853
|
}
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
type: "big-idea",
|
|
1105
|
-
data: {
|
|
1106
|
-
title: this.truncate(analysis.scqa.complication, 60),
|
|
1107
|
-
keyMessage: "The challenge we face"
|
|
1108
|
-
},
|
|
1109
|
-
classes: ["slide-big-idea"]
|
|
1110
|
-
};
|
|
1111
|
-
}
|
|
1854
|
+
createComparisonSlide(index, section) {
|
|
1855
|
+
const comparison = this.classifier.extractComparison(section);
|
|
1856
|
+
const labels = this.config.defaults.comparison;
|
|
1857
|
+
const leftFallback = labels.optionLabels[0] ?? "Option A";
|
|
1858
|
+
const rightFallback = labels.optionLabels[1] ?? "Option B";
|
|
1859
|
+
const leftColumn = comparison?.left || section.bullets[0] || leftFallback;
|
|
1860
|
+
const rightColumn = comparison?.right || section.bullets[1] || rightFallback;
|
|
1112
1861
|
return {
|
|
1113
1862
|
index,
|
|
1114
|
-
type: "
|
|
1863
|
+
type: "comparison",
|
|
1115
1864
|
data: {
|
|
1116
|
-
title:
|
|
1117
|
-
|
|
1118
|
-
|
|
1865
|
+
title: this.createTitle(section.header, section),
|
|
1866
|
+
columns: [
|
|
1867
|
+
{
|
|
1868
|
+
title: labels.leftLabel,
|
|
1869
|
+
// FROM KB - not hardcoded 'Before'
|
|
1870
|
+
content: this.truncateText(leftColumn, this.config.rules.wordsPerSlide.max)
|
|
1871
|
+
},
|
|
1872
|
+
{
|
|
1873
|
+
title: labels.rightLabel,
|
|
1874
|
+
// FROM KB - not hardcoded 'After'
|
|
1875
|
+
content: this.truncateText(rightColumn, this.config.rules.wordsPerSlide.max)
|
|
1876
|
+
}
|
|
1877
|
+
]
|
|
1119
1878
|
},
|
|
1120
|
-
classes: ["slide
|
|
1879
|
+
classes: ["comparison-slide"]
|
|
1121
1880
|
};
|
|
1122
1881
|
}
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
data: {
|
|
1132
|
-
title: this.truncate(message, 60),
|
|
1133
|
-
keyMessage: message
|
|
1134
|
-
},
|
|
1135
|
-
classes: ["slide-single-statement"]
|
|
1136
|
-
};
|
|
1137
|
-
}
|
|
1882
|
+
createTimelineSlide(index, section) {
|
|
1883
|
+
const steps = this.classifier.extractSteps(section);
|
|
1884
|
+
const maxSteps = Math.min(
|
|
1885
|
+
steps.length,
|
|
1886
|
+
this.config.rules.bulletsPerSlide.max,
|
|
1887
|
+
this.config.millersLaw.maxItems
|
|
1888
|
+
// FROM KB - 7±2 rule
|
|
1889
|
+
);
|
|
1138
1890
|
return {
|
|
1139
1891
|
index,
|
|
1140
|
-
type: "
|
|
1892
|
+
type: "timeline",
|
|
1141
1893
|
data: {
|
|
1142
|
-
title: this.
|
|
1143
|
-
|
|
1144
|
-
|
|
1894
|
+
title: this.createTitle(section.header, section),
|
|
1895
|
+
steps: steps.slice(0, maxSteps).map((step) => ({
|
|
1896
|
+
label: step.label,
|
|
1897
|
+
description: this.truncateText(
|
|
1898
|
+
step.description,
|
|
1899
|
+
this.config.defaults.step.maxWords
|
|
1900
|
+
// FROM KB - not hardcoded 20
|
|
1901
|
+
)
|
|
1902
|
+
}))
|
|
1145
1903
|
},
|
|
1146
|
-
classes: ["slide
|
|
1904
|
+
classes: ["timeline-slide"]
|
|
1147
1905
|
};
|
|
1148
1906
|
}
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1907
|
+
createProcessSlide(index, section) {
|
|
1908
|
+
const steps = this.classifier.extractSteps(section);
|
|
1909
|
+
const maxSteps = Math.min(steps.length, this.config.rules.bulletsPerSlide.max);
|
|
1910
|
+
return {
|
|
1911
|
+
index,
|
|
1912
|
+
type: "process",
|
|
1913
|
+
data: {
|
|
1914
|
+
title: this.createTitle(section.header, section),
|
|
1915
|
+
steps: steps.slice(0, maxSteps).map((step, i) => ({
|
|
1916
|
+
number: i + 1,
|
|
1917
|
+
title: step.label,
|
|
1918
|
+
description: this.truncateText(
|
|
1919
|
+
step.description,
|
|
1920
|
+
this.config.defaults.step.maxWords
|
|
1921
|
+
// FROM KB - not hardcoded 15
|
|
1922
|
+
)
|
|
1923
|
+
}))
|
|
1924
|
+
},
|
|
1925
|
+
classes: ["process-slide"]
|
|
1926
|
+
};
|
|
1927
|
+
}
|
|
1928
|
+
createThreeColumnSlide(index, section) {
|
|
1929
|
+
const columns = section.bullets.slice(0, 3);
|
|
1930
|
+
while (columns.length < 3) {
|
|
1931
|
+
columns.push("");
|
|
1166
1932
|
}
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1933
|
+
const labelTemplate = this.config.defaults.column.labelTemplate;
|
|
1934
|
+
return {
|
|
1935
|
+
index,
|
|
1936
|
+
type: "three-column",
|
|
1937
|
+
data: {
|
|
1938
|
+
title: this.createTitle(section.header, section),
|
|
1939
|
+
columns: columns.map((content, i) => ({
|
|
1940
|
+
title: labelTemplate.replace("{n}", String(i + 1)),
|
|
1941
|
+
// FROM KB - not hardcoded 'Point ${i+1}'
|
|
1942
|
+
content: this.truncateText(
|
|
1943
|
+
content,
|
|
1944
|
+
this.config.defaults.columnContent.maxWords
|
|
1945
|
+
// FROM KB - not hardcoded 25
|
|
1946
|
+
)
|
|
1947
|
+
}))
|
|
1948
|
+
},
|
|
1949
|
+
classes: ["three-column-slide"]
|
|
1950
|
+
};
|
|
1951
|
+
}
|
|
1952
|
+
createTwoColumnSlide(index, section) {
|
|
1953
|
+
const midpoint = Math.ceil(section.bullets.length / 2);
|
|
1954
|
+
const leftBullets = section.bullets.slice(0, midpoint);
|
|
1955
|
+
const rightBullets = section.bullets.slice(midpoint);
|
|
1956
|
+
const wordsPerBullet = Math.floor(
|
|
1957
|
+
this.config.rules.wordsPerSlide.max / this.config.rules.bulletsPerSlide.max
|
|
1958
|
+
);
|
|
1959
|
+
return {
|
|
1960
|
+
index,
|
|
1961
|
+
type: "two-column",
|
|
1962
|
+
data: {
|
|
1963
|
+
title: this.createTitle(section.header, section),
|
|
1964
|
+
leftColumn: {
|
|
1965
|
+
bullets: leftBullets.map((b) => this.truncateText(this.cleanText(b), wordsPerBullet))
|
|
1174
1966
|
},
|
|
1175
|
-
|
|
1176
|
-
|
|
1967
|
+
rightColumn: {
|
|
1968
|
+
bullets: rightBullets.map((b) => this.truncateText(this.cleanText(b), wordsPerBullet))
|
|
1969
|
+
}
|
|
1970
|
+
},
|
|
1971
|
+
classes: ["two-column-slide"]
|
|
1972
|
+
};
|
|
1973
|
+
}
|
|
1974
|
+
createQuoteSlide(index, section) {
|
|
1975
|
+
const quoteInfo = this.classifier.isQuoteContent(section);
|
|
1976
|
+
const quoteText = section.content.replace(/^[""]|[""]$/g, "").trim();
|
|
1977
|
+
const quoteWordLimit = Math.min(
|
|
1978
|
+
this.config.glanceTest.wordLimit * 2,
|
|
1979
|
+
// FROM KB - not hardcoded
|
|
1980
|
+
this.config.rules.wordsPerSlide.max
|
|
1981
|
+
);
|
|
1982
|
+
const data = {
|
|
1983
|
+
title: this.createTitle(section.header, section),
|
|
1984
|
+
quote: this.truncateText(quoteText, quoteWordLimit)
|
|
1985
|
+
};
|
|
1986
|
+
if (quoteInfo.attribution) {
|
|
1987
|
+
data.attribution = quoteInfo.attribution;
|
|
1177
1988
|
}
|
|
1178
1989
|
return {
|
|
1179
1990
|
index,
|
|
1180
1991
|
type: "quote",
|
|
1992
|
+
data,
|
|
1993
|
+
classes: ["quote-slide"]
|
|
1994
|
+
};
|
|
1995
|
+
}
|
|
1996
|
+
createMetricsGridSlide(index, metrics) {
|
|
1997
|
+
const maxMetrics = this.config.defaults.metricsGrid.maxMetrics;
|
|
1998
|
+
const cleanedMetrics = metrics.slice(0, maxMetrics).map((m) => ({
|
|
1999
|
+
value: this.cleanText(m.value),
|
|
2000
|
+
label: this.cleanMetricLabel(m.label)
|
|
2001
|
+
}));
|
|
2002
|
+
return {
|
|
2003
|
+
index,
|
|
2004
|
+
type: "metrics-grid",
|
|
1181
2005
|
data: {
|
|
1182
|
-
|
|
1183
|
-
|
|
2006
|
+
title: this.config.defaults.metricsGrid.title,
|
|
2007
|
+
// FROM KB - not hardcoded 'Key Metrics'
|
|
2008
|
+
metrics: cleanedMetrics
|
|
1184
2009
|
},
|
|
1185
|
-
classes: ["slide
|
|
2010
|
+
classes: ["metrics-grid-slide"]
|
|
1186
2011
|
};
|
|
1187
2012
|
}
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
if (
|
|
1193
|
-
return
|
|
1194
|
-
index,
|
|
1195
|
-
type: "big-idea",
|
|
1196
|
-
data: {
|
|
1197
|
-
title: this.truncate(analysis.scqa.answer, 60),
|
|
1198
|
-
keyMessage: "Our answer"
|
|
1199
|
-
},
|
|
1200
|
-
classes: ["slide-big-idea"]
|
|
1201
|
-
};
|
|
2013
|
+
createBulletSlide(index, section) {
|
|
2014
|
+
const maxBullets = this.config.rules.bulletsPerSlide.max;
|
|
2015
|
+
const wordsPerBullet = Math.floor(this.config.rules.wordsPerSlide.max / maxBullets);
|
|
2016
|
+
const cleanedBullets = section.bullets.slice(0, maxBullets).map((b) => this.truncateText(this.cleanText(b), wordsPerBullet)).filter((b) => b.length > 0);
|
|
2017
|
+
if (cleanedBullets.length === 0 && section.content) {
|
|
2018
|
+
return this.createSingleStatementSlide(index, section);
|
|
1202
2019
|
}
|
|
1203
2020
|
return {
|
|
1204
2021
|
index,
|
|
1205
|
-
type: "
|
|
2022
|
+
type: "bullet-points",
|
|
1206
2023
|
data: {
|
|
1207
|
-
title:
|
|
1208
|
-
|
|
1209
|
-
bullets: analysis.sparkline.whatCouldBe.slice(0, 4)
|
|
2024
|
+
title: this.createTitle(section.header, section),
|
|
2025
|
+
bullets: cleanedBullets
|
|
1210
2026
|
},
|
|
1211
|
-
classes: ["
|
|
2027
|
+
classes: ["bullet-points-slide"]
|
|
1212
2028
|
};
|
|
1213
2029
|
}
|
|
1214
|
-
|
|
1215
|
-
* Create a call-to-action slide.
|
|
1216
|
-
*/
|
|
1217
|
-
createCTASlide(index, analysis, mode) {
|
|
2030
|
+
createSingleStatementSlide(index, section) {
|
|
1218
2031
|
return {
|
|
1219
2032
|
index,
|
|
1220
|
-
type: "
|
|
2033
|
+
type: "single-statement",
|
|
1221
2034
|
data: {
|
|
1222
|
-
title:
|
|
1223
|
-
body:
|
|
1224
|
-
|
|
2035
|
+
title: this.createTitle(section.header, section),
|
|
2036
|
+
body: this.truncateText(
|
|
2037
|
+
section.content || section.bullets[0] || "",
|
|
2038
|
+
this.config.rules.wordsPerSlide.max
|
|
2039
|
+
// FROM KB
|
|
2040
|
+
)
|
|
1225
2041
|
},
|
|
1226
|
-
classes: ["slide
|
|
2042
|
+
classes: ["single-statement-slide"]
|
|
1227
2043
|
};
|
|
1228
2044
|
}
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
createThankYouSlide(index) {
|
|
2045
|
+
createCodeSlide(index, section) {
|
|
2046
|
+
const codeMatch = section.content.match(/```[\s\S]*?```|`[^`]+`/);
|
|
2047
|
+
const code = codeMatch ? codeMatch[0].replace(/```/g, "").trim() : section.content;
|
|
1233
2048
|
return {
|
|
1234
2049
|
index,
|
|
1235
|
-
type: "
|
|
2050
|
+
type: "two-column",
|
|
2051
|
+
// Code slides use two-column layout
|
|
1236
2052
|
data: {
|
|
1237
|
-
title:
|
|
1238
|
-
|
|
2053
|
+
title: this.createTitle(section.header, section),
|
|
2054
|
+
leftColumn: {
|
|
2055
|
+
body: this.config.defaults.code.label
|
|
2056
|
+
// FROM KB - not hardcoded 'Code Example'
|
|
2057
|
+
},
|
|
2058
|
+
rightColumn: {
|
|
2059
|
+
body: code.slice(0, this.config.defaults.code.maxChars)
|
|
2060
|
+
// FROM KB - not hardcoded 500
|
|
2061
|
+
}
|
|
1239
2062
|
},
|
|
1240
|
-
classes: ["slide
|
|
2063
|
+
classes: ["code-slide"]
|
|
1241
2064
|
};
|
|
1242
2065
|
}
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
templates.set("title", {
|
|
1249
|
-
type: "title",
|
|
1250
|
-
requiredFields: ["title"],
|
|
1251
|
-
optionalFields: ["subtitle", "author", "date"],
|
|
1252
|
-
keynoteSuitable: true,
|
|
1253
|
-
businessSuitable: true,
|
|
1254
|
-
maxWords: 15
|
|
1255
|
-
});
|
|
1256
|
-
templates.set("big-idea", {
|
|
1257
|
-
type: "big-idea",
|
|
1258
|
-
requiredFields: ["title"],
|
|
1259
|
-
optionalFields: ["keyMessage"],
|
|
1260
|
-
keynoteSuitable: true,
|
|
1261
|
-
businessSuitable: false,
|
|
1262
|
-
maxWords: 10
|
|
1263
|
-
});
|
|
1264
|
-
templates.set("single-statement", {
|
|
1265
|
-
type: "single-statement",
|
|
1266
|
-
requiredFields: ["title"],
|
|
1267
|
-
optionalFields: ["keyMessage"],
|
|
1268
|
-
keynoteSuitable: true,
|
|
1269
|
-
businessSuitable: false,
|
|
1270
|
-
maxWords: 15
|
|
1271
|
-
});
|
|
1272
|
-
templates.set("big-number", {
|
|
1273
|
-
type: "big-number",
|
|
1274
|
-
requiredFields: ["title"],
|
|
1275
|
-
optionalFields: ["subtitle", "source"],
|
|
1276
|
-
keynoteSuitable: true,
|
|
1277
|
-
businessSuitable: true,
|
|
1278
|
-
maxWords: 10
|
|
1279
|
-
});
|
|
1280
|
-
templates.set("quote", {
|
|
1281
|
-
type: "quote",
|
|
1282
|
-
requiredFields: ["quote"],
|
|
1283
|
-
optionalFields: ["attribution", "source"],
|
|
1284
|
-
keynoteSuitable: true,
|
|
1285
|
-
businessSuitable: true,
|
|
1286
|
-
maxWords: 30
|
|
1287
|
-
});
|
|
1288
|
-
templates.set("bullet-points", {
|
|
1289
|
-
type: "bullet-points",
|
|
1290
|
-
requiredFields: ["title", "bullets"],
|
|
1291
|
-
optionalFields: ["body"],
|
|
1292
|
-
keynoteSuitable: false,
|
|
1293
|
-
businessSuitable: true,
|
|
1294
|
-
maxWords: 80
|
|
1295
|
-
});
|
|
1296
|
-
templates.set("two-column", {
|
|
1297
|
-
type: "two-column",
|
|
1298
|
-
requiredFields: ["title"],
|
|
1299
|
-
optionalFields: ["body", "bullets", "images"],
|
|
1300
|
-
keynoteSuitable: false,
|
|
1301
|
-
businessSuitable: true,
|
|
1302
|
-
maxWords: 100
|
|
1303
|
-
});
|
|
1304
|
-
templates.set("agenda", {
|
|
1305
|
-
type: "agenda",
|
|
1306
|
-
requiredFields: ["title", "bullets"],
|
|
1307
|
-
optionalFields: [],
|
|
1308
|
-
keynoteSuitable: false,
|
|
1309
|
-
businessSuitable: true,
|
|
1310
|
-
maxWords: 50
|
|
1311
|
-
});
|
|
1312
|
-
templates.set("cta", {
|
|
2066
|
+
createCTASlide(index, analysis) {
|
|
2067
|
+
const ctaDefaults = this.config.defaults.cta;
|
|
2068
|
+
const ctaText = analysis.sparkline?.callToAdventure || analysis.scqa?.answer || ctaDefaults.fallback;
|
|
2069
|
+
return {
|
|
2070
|
+
index,
|
|
1313
2071
|
type: "cta",
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
2072
|
+
data: {
|
|
2073
|
+
title: ctaDefaults.title,
|
|
2074
|
+
// FROM KB - not hardcoded 'Next Steps'
|
|
2075
|
+
body: this.truncateText(ctaText, this.config.rules.wordsPerSlide.max),
|
|
2076
|
+
keyMessage: ctaDefaults.message
|
|
2077
|
+
// FROM KB - not hardcoded 'Ready to Begin?'
|
|
2078
|
+
},
|
|
2079
|
+
classes: ["cta-slide"]
|
|
2080
|
+
};
|
|
2081
|
+
}
|
|
2082
|
+
// ===========================================================================
|
|
2083
|
+
// VALIDATION - Ensure all slides comply with KB rules AND FIX ISSUES
|
|
2084
|
+
// ===========================================================================
|
|
2085
|
+
validateAndFixSlides(slides) {
|
|
2086
|
+
const validatedSlides = [];
|
|
2087
|
+
for (const slide of slides) {
|
|
2088
|
+
const wordCount = this.countWords(slide);
|
|
2089
|
+
const bulletCount = slide.data.bullets?.length || 0;
|
|
2090
|
+
const validation = this.kb.validateSlideAgainstKB(
|
|
2091
|
+
{
|
|
2092
|
+
type: slide.type,
|
|
2093
|
+
wordCount,
|
|
2094
|
+
bulletCount,
|
|
2095
|
+
hasActionTitle: this.isActionTitle(slide.data.title),
|
|
2096
|
+
hasSource: !!slide.data.source
|
|
2097
|
+
},
|
|
2098
|
+
this.presentationType
|
|
2099
|
+
);
|
|
2100
|
+
if (validation.fixes.wordCount) {
|
|
2101
|
+
if (slide.data.body) {
|
|
2102
|
+
slide.data.body = this.truncateText(slide.data.body, validation.fixes.wordCount);
|
|
2103
|
+
}
|
|
2104
|
+
if (slide.data.bullets) {
|
|
2105
|
+
const wordsPerBullet = Math.floor(validation.fixes.wordCount / slide.data.bullets.length);
|
|
2106
|
+
slide.data.bullets = slide.data.bullets.map((b) => this.truncateText(b, wordsPerBullet));
|
|
2107
|
+
}
|
|
2108
|
+
}
|
|
2109
|
+
if (validation.fixes.bulletCount && slide.data.bullets) {
|
|
2110
|
+
slide.data.bullets = slide.data.bullets.slice(0, validation.fixes.bulletCount);
|
|
2111
|
+
}
|
|
2112
|
+
if (validation.violations.length > 0) {
|
|
2113
|
+
console.log(` \u26A0 Slide ${slide.index} (${slide.type}): Fixed ${Object.keys(validation.fixes).length} issues, ${validation.violations.length} remaining`);
|
|
2114
|
+
}
|
|
2115
|
+
if (validation.warnings.length > 0) {
|
|
2116
|
+
slide.notes = (slide.notes || "") + "\nWarnings: " + validation.warnings.join(", ");
|
|
2117
|
+
}
|
|
2118
|
+
validatedSlides.push(slide);
|
|
2119
|
+
}
|
|
2120
|
+
return validatedSlides;
|
|
1329
2121
|
}
|
|
1330
|
-
// === Helper Methods ===
|
|
1331
2122
|
/**
|
|
1332
|
-
*
|
|
1333
|
-
* CRITICAL: Must strip all formatting to prevent garbage in slides
|
|
2123
|
+
* Check that required elements exist in the deck (from KB)
|
|
1334
2124
|
*/
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
2125
|
+
checkRequiredElements(slides) {
|
|
2126
|
+
const required = this.config.requiredElements;
|
|
2127
|
+
if (required.length === 0) return;
|
|
2128
|
+
const slideTypes = slides.map((s) => s.type);
|
|
2129
|
+
const missingElements = [];
|
|
2130
|
+
for (const element of required) {
|
|
2131
|
+
const lowerElement = element.toLowerCase();
|
|
2132
|
+
if (lowerElement.includes("hook") || lowerElement.includes("opening")) {
|
|
2133
|
+
const titleSlide = slides.find((s) => s.type === "title");
|
|
2134
|
+
if (!titleSlide || !titleSlide.data.subtitle) {
|
|
2135
|
+
missingElements.push(element);
|
|
2136
|
+
}
|
|
2137
|
+
}
|
|
2138
|
+
if (lowerElement.includes("call to action") || lowerElement.includes("cta")) {
|
|
2139
|
+
if (!slideTypes.includes("cta")) {
|
|
2140
|
+
missingElements.push(element);
|
|
2141
|
+
}
|
|
2142
|
+
}
|
|
2143
|
+
if (lowerElement.includes("star moment") || lowerElement.includes("memorable")) {
|
|
2144
|
+
const hasStarMoment = slideTypes.some(
|
|
2145
|
+
(t) => t === "big-number" || t === "quote" || t === "single-statement"
|
|
2146
|
+
);
|
|
2147
|
+
if (!hasStarMoment) {
|
|
2148
|
+
missingElements.push(element);
|
|
2149
|
+
}
|
|
2150
|
+
}
|
|
2151
|
+
}
|
|
2152
|
+
if (missingElements.length > 0) {
|
|
2153
|
+
console.log(` \u26A0 Missing required elements: ${missingElements.join(", ")}`);
|
|
2154
|
+
}
|
|
1338
2155
|
}
|
|
1339
2156
|
/**
|
|
1340
|
-
*
|
|
1341
|
-
* CRITICAL: Never cut mid-number (99.5% should not become 99.)
|
|
2157
|
+
* Check for anti-patterns from KB
|
|
1342
2158
|
*/
|
|
1343
|
-
|
|
1344
|
-
const
|
|
1345
|
-
if (
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
break;
|
|
2159
|
+
checkAntiPatterns(slides) {
|
|
2160
|
+
const antiPatterns = this.config.antiPatterns;
|
|
2161
|
+
if (antiPatterns.length === 0) return;
|
|
2162
|
+
const violations = [];
|
|
2163
|
+
for (const pattern of antiPatterns) {
|
|
2164
|
+
const lowerPattern = pattern.toLowerCase();
|
|
2165
|
+
if (lowerPattern.includes("bullet") && this.config.mode === "keynote") {
|
|
2166
|
+
const bulletSlides = slides.filter(
|
|
2167
|
+
(s) => s.type === "bullet-points" || s.data.bullets && s.data.bullets.length > 3
|
|
2168
|
+
);
|
|
2169
|
+
if (bulletSlides.length > 0) {
|
|
2170
|
+
violations.push(`${pattern} (${bulletSlides.length} slides)`);
|
|
1356
2171
|
}
|
|
1357
2172
|
}
|
|
1358
|
-
if (
|
|
1359
|
-
|
|
2173
|
+
if (lowerPattern.includes("text") || lowerPattern.includes("overload")) {
|
|
2174
|
+
const overloadedSlides = slides.filter((s) => {
|
|
2175
|
+
const wordCount = this.countWords(s);
|
|
2176
|
+
return wordCount > this.config.glanceTest.wordLimit;
|
|
2177
|
+
});
|
|
2178
|
+
if (overloadedSlides.length > 0) {
|
|
2179
|
+
violations.push(`${pattern} (${overloadedSlides.length} slides exceed glance test)`);
|
|
2180
|
+
}
|
|
1360
2181
|
}
|
|
1361
2182
|
}
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
2183
|
+
if (violations.length > 0) {
|
|
2184
|
+
console.log(` \u26A0 Anti-pattern violations: ${violations.join(", ")}`);
|
|
2185
|
+
}
|
|
2186
|
+
}
|
|
2187
|
+
// ===========================================================================
|
|
2188
|
+
// HELPER METHODS - All use KB configuration
|
|
2189
|
+
// ===========================================================================
|
|
2190
|
+
/**
|
|
2191
|
+
* Create a title for a slide - uses action titles for business mode per KB
|
|
2192
|
+
*/
|
|
2193
|
+
createTitle(header, section) {
|
|
2194
|
+
const cleanHeader = this.cleanText(header);
|
|
2195
|
+
if (this.usedTitles.has(cleanHeader.toLowerCase())) {
|
|
2196
|
+
return `${cleanHeader} (continued)`;
|
|
1367
2197
|
}
|
|
1368
|
-
|
|
1369
|
-
|
|
2198
|
+
this.usedTitles.add(cleanHeader.toLowerCase());
|
|
2199
|
+
if (this.config.rules.actionTitlesRequired) {
|
|
2200
|
+
return this.createActionTitle(cleanHeader, section);
|
|
1370
2201
|
}
|
|
1371
|
-
return
|
|
2202
|
+
return cleanHeader;
|
|
1372
2203
|
}
|
|
1373
2204
|
/**
|
|
1374
|
-
*
|
|
2205
|
+
* Create an action title (Minto/McKinsey style)
|
|
2206
|
+
* Title should communicate the conclusion, not the topic
|
|
1375
2207
|
*/
|
|
1376
|
-
|
|
1377
|
-
const
|
|
1378
|
-
const
|
|
1379
|
-
if (
|
|
1380
|
-
return
|
|
2208
|
+
createActionTitle(header, section) {
|
|
2209
|
+
const titleWordLimit = this.kb.getWordLimitForElement(this.presentationType, "title");
|
|
2210
|
+
const firstBullet = section.bullets[0];
|
|
2211
|
+
if (firstBullet && this.isInsightful(firstBullet)) {
|
|
2212
|
+
return this.truncateText(this.cleanText(firstBullet), titleWordLimit);
|
|
2213
|
+
}
|
|
2214
|
+
if (section.content && this.isInsightful(section.content)) {
|
|
2215
|
+
const sentences = section.content.split(/[.!?]/);
|
|
2216
|
+
const firstSentence = sentences[0];
|
|
2217
|
+
if (firstSentence) {
|
|
2218
|
+
return this.truncateText(this.cleanText(firstSentence), titleWordLimit);
|
|
2219
|
+
}
|
|
1381
2220
|
}
|
|
1382
|
-
|
|
1383
|
-
|
|
2221
|
+
return header;
|
|
2222
|
+
}
|
|
2223
|
+
/**
|
|
2224
|
+
* Check if text contains an insight (not just a topic) - uses KB markers
|
|
2225
|
+
*/
|
|
2226
|
+
isInsightful(text) {
|
|
2227
|
+
const lowerText = text.toLowerCase();
|
|
2228
|
+
return this.config.insightMarkers.some(
|
|
2229
|
+
(marker) => (
|
|
2230
|
+
// FROM KB - not hardcoded array
|
|
2231
|
+
lowerText.includes(marker.toLowerCase())
|
|
2232
|
+
)
|
|
2233
|
+
);
|
|
2234
|
+
}
|
|
2235
|
+
/**
|
|
2236
|
+
* Check if a title is an action title
|
|
2237
|
+
*/
|
|
2238
|
+
isActionTitle(title) {
|
|
2239
|
+
if (!title) return false;
|
|
2240
|
+
return this.isInsightful(title);
|
|
2241
|
+
}
|
|
2242
|
+
/**
|
|
2243
|
+
* Count words in a slide
|
|
2244
|
+
*/
|
|
2245
|
+
countWords(slide) {
|
|
2246
|
+
let text = "";
|
|
2247
|
+
const data = slide.data;
|
|
2248
|
+
if (data.title) text += data.title + " ";
|
|
2249
|
+
if (data.subtitle) text += data.subtitle + " ";
|
|
2250
|
+
if (data.body) text += data.body + " ";
|
|
2251
|
+
if (data.keyMessage) text += data.keyMessage + " ";
|
|
2252
|
+
if (data.bullets) text += data.bullets.join(" ") + " ";
|
|
2253
|
+
if (data.quote) text += data.quote + " ";
|
|
2254
|
+
return text.split(/\s+/).filter((w) => w.length > 0).length;
|
|
1384
2255
|
}
|
|
1385
2256
|
/**
|
|
1386
|
-
*
|
|
2257
|
+
* Truncate text to word limit at sentence boundaries
|
|
1387
2258
|
*/
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
const
|
|
1391
|
-
if (
|
|
1392
|
-
return
|
|
2259
|
+
truncateText(text, maxWords) {
|
|
2260
|
+
const cleaned = this.cleanText(text);
|
|
2261
|
+
const words = cleaned.split(/\s+/);
|
|
2262
|
+
if (words.length <= maxWords) {
|
|
2263
|
+
return cleaned;
|
|
2264
|
+
}
|
|
2265
|
+
const sentences = cleaned.match(/[^.!?]+[.!?]?/g) || [cleaned];
|
|
2266
|
+
let result = "";
|
|
2267
|
+
for (const sentence of sentences) {
|
|
2268
|
+
const testResult = result + sentence;
|
|
2269
|
+
if (testResult.split(/\s+/).length <= maxWords) {
|
|
2270
|
+
result = testResult;
|
|
2271
|
+
} else {
|
|
2272
|
+
break;
|
|
2273
|
+
}
|
|
1393
2274
|
}
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
2275
|
+
if (result.trim().length > 0) {
|
|
2276
|
+
return result.trim();
|
|
2277
|
+
}
|
|
2278
|
+
return words.slice(0, maxWords).join(" ") + "...";
|
|
2279
|
+
}
|
|
2280
|
+
/**
|
|
2281
|
+
* Clean text by removing content markers and normalizing
|
|
2282
|
+
*/
|
|
2283
|
+
cleanText(text) {
|
|
2284
|
+
if (!text) return "";
|
|
2285
|
+
return text.replace(/\[HEADER\]\s*/g, "").replace(/\[BULLET\]\s*/g, "").replace(/\[NUMBERED\]\s*/g, "").replace(/\[EMPHASIS\]/g, "").replace(/\[\/EMPHASIS\]/g, "").replace(/\[CODE BLOCK\]/g, "").replace(/\[IMAGE\]/g, "").replace(/\*\*/g, "").replace(/\*/g, "").replace(/#{1,6}\s*/g, "").replace(/\s+/g, " ").trim();
|
|
2286
|
+
}
|
|
2287
|
+
/**
|
|
2288
|
+
* Clean metric labels (strip table syntax)
|
|
2289
|
+
*/
|
|
2290
|
+
cleanMetricLabel(label) {
|
|
2291
|
+
if (!label) return "";
|
|
2292
|
+
return label.replace(/\|/g, " ").replace(/-{3,}/g, "").replace(/\s{2,}/g, " ").replace(/^\s*\d+\.\s*/, "").trim();
|
|
1397
2293
|
}
|
|
1398
2294
|
/**
|
|
1399
|
-
*
|
|
2295
|
+
* Normalize a key for deduplication
|
|
1400
2296
|
*/
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
const firstSentence = cleaned.match(/^[^.!?]+[.!?]?/);
|
|
1404
|
-
return firstSentence ? firstSentence[0].slice(0, 80) : cleaned.slice(0, 80);
|
|
2297
|
+
normalizeKey(text) {
|
|
2298
|
+
return text.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
1405
2299
|
}
|
|
1406
2300
|
};
|
|
2301
|
+
function createSlideFactory(kb, type) {
|
|
2302
|
+
return new SlideFactory(kb, type);
|
|
2303
|
+
}
|
|
1407
2304
|
|
|
1408
2305
|
// src/core/TemplateEngine.ts
|
|
1409
2306
|
var import_handlebars = __toESM(require("handlebars"));
|
|
@@ -2814,99 +3711,1044 @@ var QAEngine = class {
|
|
|
2814
3711
|
score -= Math.round((1 - signalPass / slideCount) * 30);
|
|
2815
3712
|
return Math.max(0, score);
|
|
2816
3713
|
}
|
|
2817
|
-
calculateExpertScore(results) {
|
|
2818
|
-
const experts = [results.duarte, results.reynolds, results.gallo, results.anderson];
|
|
2819
|
-
const totalScore = experts.reduce((sum, e) => sum + e.score, 0);
|
|
2820
|
-
return totalScore / experts.length;
|
|
3714
|
+
calculateExpertScore(results) {
|
|
3715
|
+
const experts = [results.duarte, results.reynolds, results.gallo, results.anderson];
|
|
3716
|
+
const totalScore = experts.reduce((sum, e) => sum + e.score, 0);
|
|
3717
|
+
return totalScore / experts.length;
|
|
3718
|
+
}
|
|
3719
|
+
calculateA11yScore(results) {
|
|
3720
|
+
let score = 100;
|
|
3721
|
+
score -= Math.min(40, results.fontSizeIssues.length * 10);
|
|
3722
|
+
score -= Math.min(40, results.contrastIssues.length * 10);
|
|
3723
|
+
if (results.focusCoverage < 1) score -= 20;
|
|
3724
|
+
return Math.max(0, score);
|
|
3725
|
+
}
|
|
3726
|
+
// ===========================================================================
|
|
3727
|
+
// ISSUE COLLECTION
|
|
3728
|
+
// ===========================================================================
|
|
3729
|
+
collectIssues(visual, content, expert, accessibility) {
|
|
3730
|
+
const issues = [];
|
|
3731
|
+
visual.perSlide.forEach((slide) => {
|
|
3732
|
+
slide.issues.forEach((issue) => {
|
|
3733
|
+
issues.push({
|
|
3734
|
+
severity: slide.whitespace > 70 || slide.whitespace < 20 ? "error" : "warning",
|
|
3735
|
+
category: "visual",
|
|
3736
|
+
slideIndex: slide.slideIndex,
|
|
3737
|
+
message: issue
|
|
3738
|
+
});
|
|
3739
|
+
});
|
|
3740
|
+
});
|
|
3741
|
+
content.perSlide.forEach((slide) => {
|
|
3742
|
+
slide.issues.forEach((issue) => {
|
|
3743
|
+
issues.push({
|
|
3744
|
+
severity: "warning",
|
|
3745
|
+
category: "content",
|
|
3746
|
+
slideIndex: slide.slideIndex,
|
|
3747
|
+
message: issue
|
|
3748
|
+
});
|
|
3749
|
+
});
|
|
3750
|
+
});
|
|
3751
|
+
content.glanceTest.filter((g) => !g.passed).forEach((g) => {
|
|
3752
|
+
const issue = {
|
|
3753
|
+
severity: "warning",
|
|
3754
|
+
category: "content",
|
|
3755
|
+
slideIndex: g.slideIndex,
|
|
3756
|
+
message: `Glance test failed: "${g.keyMessage.substring(0, 50)}..." takes ${g.readingTime}s to read`
|
|
3757
|
+
};
|
|
3758
|
+
if (g.recommendation) {
|
|
3759
|
+
issue.suggestion = g.recommendation;
|
|
3760
|
+
}
|
|
3761
|
+
issues.push(issue);
|
|
3762
|
+
});
|
|
3763
|
+
[expert.duarte, expert.reynolds, expert.gallo, expert.anderson].forEach((e) => {
|
|
3764
|
+
e.violations.forEach((v) => {
|
|
3765
|
+
issues.push({
|
|
3766
|
+
severity: "warning",
|
|
3767
|
+
category: "expert",
|
|
3768
|
+
message: `${e.expertName}: ${v}`
|
|
3769
|
+
});
|
|
3770
|
+
});
|
|
3771
|
+
});
|
|
3772
|
+
accessibility.fontSizeIssues.forEach((issue) => {
|
|
3773
|
+
issues.push({
|
|
3774
|
+
severity: "error",
|
|
3775
|
+
category: "accessibility",
|
|
3776
|
+
slideIndex: issue.slideIndex,
|
|
3777
|
+
message: `Font size ${issue.actualSize}px below minimum ${issue.minimumSize}px`,
|
|
3778
|
+
suggestion: `Increase font size to at least ${issue.minimumSize}px`
|
|
3779
|
+
});
|
|
3780
|
+
});
|
|
3781
|
+
accessibility.contrastIssues.forEach((issue) => {
|
|
3782
|
+
issues.push({
|
|
3783
|
+
severity: "error",
|
|
3784
|
+
category: "accessibility",
|
|
3785
|
+
slideIndex: issue.slideIndex,
|
|
3786
|
+
message: `Contrast ratio ${issue.ratio.toFixed(2)} below required ${issue.required}`,
|
|
3787
|
+
suggestion: "Increase contrast between text and background"
|
|
3788
|
+
});
|
|
3789
|
+
});
|
|
3790
|
+
return issues;
|
|
3791
|
+
}
|
|
3792
|
+
// ===========================================================================
|
|
3793
|
+
// BROWSER MANAGEMENT
|
|
3794
|
+
// ===========================================================================
|
|
3795
|
+
async initBrowser() {
|
|
3796
|
+
if (!this.browser) {
|
|
3797
|
+
this.browser = await import_playwright.chromium.launch({ headless: true });
|
|
3798
|
+
}
|
|
3799
|
+
}
|
|
3800
|
+
async closeBrowser() {
|
|
3801
|
+
if (this.browser) {
|
|
3802
|
+
await this.browser.close();
|
|
3803
|
+
this.browser = null;
|
|
3804
|
+
}
|
|
3805
|
+
}
|
|
3806
|
+
};
|
|
3807
|
+
|
|
3808
|
+
// src/qa/SevenDimensionScorer.ts
|
|
3809
|
+
var DIMENSION_WEIGHTS = {
|
|
3810
|
+
layout: 0.15,
|
|
3811
|
+
contrast: 0.15,
|
|
3812
|
+
graphics: 0.1,
|
|
3813
|
+
content: 0.2,
|
|
3814
|
+
clarity: 0.15,
|
|
3815
|
+
effectiveness: 0.15,
|
|
3816
|
+
consistency: 0.1
|
|
3817
|
+
};
|
|
3818
|
+
var SevenDimensionScorer = class {
|
|
3819
|
+
kb;
|
|
3820
|
+
mode;
|
|
3821
|
+
presentationType;
|
|
3822
|
+
constructor(mode, presentationType) {
|
|
3823
|
+
this.mode = mode;
|
|
3824
|
+
this.presentationType = presentationType;
|
|
3825
|
+
}
|
|
3826
|
+
/**
|
|
3827
|
+
* Score a presentation across all 7 dimensions.
|
|
3828
|
+
*/
|
|
3829
|
+
async score(slides, html, threshold = 95) {
|
|
3830
|
+
this.kb = await getKnowledgeGateway();
|
|
3831
|
+
const layout = await this.scoreLayout(slides, html);
|
|
3832
|
+
const contrast = await this.scoreContrast(html);
|
|
3833
|
+
const graphics = await this.scoreGraphics(slides);
|
|
3834
|
+
const content = await this.scoreContent(slides);
|
|
3835
|
+
const clarity = await this.scoreClarity(slides);
|
|
3836
|
+
const effectiveness = await this.scoreEffectiveness(slides);
|
|
3837
|
+
const consistency = await this.scoreConsistency(slides, html);
|
|
3838
|
+
const overallScore = Math.round(
|
|
3839
|
+
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
|
|
3840
|
+
);
|
|
3841
|
+
const issues = [
|
|
3842
|
+
...layout.issues,
|
|
3843
|
+
...contrast.issues,
|
|
3844
|
+
...graphics.issues,
|
|
3845
|
+
...content.issues,
|
|
3846
|
+
...clarity.issues,
|
|
3847
|
+
...effectiveness.issues,
|
|
3848
|
+
...consistency.issues
|
|
3849
|
+
];
|
|
3850
|
+
return {
|
|
3851
|
+
overallScore,
|
|
3852
|
+
dimensions: {
|
|
3853
|
+
layout,
|
|
3854
|
+
contrast,
|
|
3855
|
+
graphics,
|
|
3856
|
+
content,
|
|
3857
|
+
clarity,
|
|
3858
|
+
effectiveness,
|
|
3859
|
+
consistency
|
|
3860
|
+
},
|
|
3861
|
+
issues,
|
|
3862
|
+
passed: overallScore >= threshold,
|
|
3863
|
+
threshold
|
|
3864
|
+
};
|
|
3865
|
+
}
|
|
3866
|
+
/**
|
|
3867
|
+
* Score layout dimension (whitespace, visual balance, structure)
|
|
3868
|
+
*/
|
|
3869
|
+
async scoreLayout(slides, html) {
|
|
3870
|
+
const issues = [];
|
|
3871
|
+
let totalScore = 0;
|
|
3872
|
+
let checks = 0;
|
|
3873
|
+
const minWhitespace = this.mode === "keynote" ? 0.45 : 0.35;
|
|
3874
|
+
const maxWhitespace = 0.6;
|
|
3875
|
+
const slideSections = html.match(/<section[^>]*>[\s\S]*?<\/section>/gi) || [];
|
|
3876
|
+
for (let i = 0; i < slideSections.length; i++) {
|
|
3877
|
+
const section = slideSections[i];
|
|
3878
|
+
if (!section) continue;
|
|
3879
|
+
const textContent = section.replace(/<[^>]+>/g, "").trim();
|
|
3880
|
+
const totalArea = 1920 * 1080;
|
|
3881
|
+
const estimatedTextArea = textContent.length * 100;
|
|
3882
|
+
const whitespaceRatio = 1 - Math.min(estimatedTextArea / totalArea, 1);
|
|
3883
|
+
if (whitespaceRatio < minWhitespace) {
|
|
3884
|
+
issues.push({
|
|
3885
|
+
slideIndex: i,
|
|
3886
|
+
dimension: "layout",
|
|
3887
|
+
severity: "error",
|
|
3888
|
+
message: `Slide ${i + 1}: Insufficient whitespace (${Math.round(whitespaceRatio * 100)}% < ${Math.round(minWhitespace * 100)}%)`,
|
|
3889
|
+
currentValue: whitespaceRatio,
|
|
3890
|
+
expectedValue: minWhitespace,
|
|
3891
|
+
autoFixable: true,
|
|
3892
|
+
fixSuggestion: "Reduce content or increase margins"
|
|
3893
|
+
});
|
|
3894
|
+
totalScore += 50;
|
|
3895
|
+
} else if (whitespaceRatio > maxWhitespace) {
|
|
3896
|
+
issues.push({
|
|
3897
|
+
slideIndex: i,
|
|
3898
|
+
dimension: "layout",
|
|
3899
|
+
severity: "warning",
|
|
3900
|
+
message: `Slide ${i + 1}: Too much whitespace (${Math.round(whitespaceRatio * 100)}% > ${Math.round(maxWhitespace * 100)}%)`,
|
|
3901
|
+
currentValue: whitespaceRatio,
|
|
3902
|
+
expectedValue: maxWhitespace,
|
|
3903
|
+
autoFixable: false,
|
|
3904
|
+
fixSuggestion: "Add more content or reduce margins"
|
|
3905
|
+
});
|
|
3906
|
+
totalScore += 80;
|
|
3907
|
+
} else {
|
|
3908
|
+
totalScore += 100;
|
|
3909
|
+
}
|
|
3910
|
+
checks++;
|
|
3911
|
+
}
|
|
3912
|
+
for (let i = 0; i < slides.length; i++) {
|
|
3913
|
+
const slide = slides[i];
|
|
3914
|
+
if (!slide) continue;
|
|
3915
|
+
if (!["thank-you", "section-divider"].includes(slide.type)) {
|
|
3916
|
+
if (!slide.data.title || slide.data.title.trim().length === 0) {
|
|
3917
|
+
issues.push({
|
|
3918
|
+
slideIndex: i,
|
|
3919
|
+
dimension: "layout",
|
|
3920
|
+
severity: "warning",
|
|
3921
|
+
message: `Slide ${i + 1}: Missing title`,
|
|
3922
|
+
autoFixable: false,
|
|
3923
|
+
fixSuggestion: "Add a clear slide title"
|
|
3924
|
+
});
|
|
3925
|
+
totalScore += 70;
|
|
3926
|
+
} else {
|
|
3927
|
+
totalScore += 100;
|
|
3928
|
+
}
|
|
3929
|
+
checks++;
|
|
3930
|
+
}
|
|
3931
|
+
}
|
|
3932
|
+
const score = checks > 0 ? Math.round(totalScore / checks) : 100;
|
|
3933
|
+
return {
|
|
3934
|
+
name: "Layout",
|
|
3935
|
+
score,
|
|
3936
|
+
weight: DIMENSION_WEIGHTS.layout,
|
|
3937
|
+
issues,
|
|
3938
|
+
details: {
|
|
3939
|
+
slidesAnalyzed: slides.length,
|
|
3940
|
+
whitespaceTarget: `${Math.round(minWhitespace * 100)}-${Math.round(maxWhitespace * 100)}%`
|
|
3941
|
+
}
|
|
3942
|
+
};
|
|
3943
|
+
}
|
|
3944
|
+
/**
|
|
3945
|
+
* Score contrast dimension (WCAG compliance, readability)
|
|
3946
|
+
*/
|
|
3947
|
+
async scoreContrast(html) {
|
|
3948
|
+
const issues = [];
|
|
3949
|
+
let score = 100;
|
|
3950
|
+
const minContrastRatio = 4.5;
|
|
3951
|
+
const colorMatches = html.match(/color:\s*([^;]+);/gi) || [];
|
|
3952
|
+
const bgColorMatches = html.match(/background(-color)?:\s*([^;]+);/gi) || [];
|
|
3953
|
+
const hasGoodContrast = html.includes("color: #fff") || html.includes("color: white") || html.includes("color: #18181B") || html.includes("color: #F5F5F4");
|
|
3954
|
+
const hasDarkBackground = html.includes("background-color: #18181B") || html.includes("background: #18181B") || html.includes("background-color: rgb(24, 24, 27)");
|
|
3955
|
+
if (html.includes("color: gray") || html.includes("color: #999") || html.includes("color: #888")) {
|
|
3956
|
+
issues.push({
|
|
3957
|
+
slideIndex: -1,
|
|
3958
|
+
dimension: "contrast",
|
|
3959
|
+
severity: "error",
|
|
3960
|
+
message: "Low contrast text color detected (gray text)",
|
|
3961
|
+
currentValue: "gray",
|
|
3962
|
+
expectedValue: "High contrast color",
|
|
3963
|
+
autoFixable: true,
|
|
3964
|
+
fixSuggestion: "Use white (#fff) or dark (#18181B) text depending on background"
|
|
3965
|
+
});
|
|
3966
|
+
score -= 20;
|
|
3967
|
+
}
|
|
3968
|
+
if (!hasGoodContrast && !hasDarkBackground) {
|
|
3969
|
+
issues.push({
|
|
3970
|
+
slideIndex: -1,
|
|
3971
|
+
dimension: "contrast",
|
|
3972
|
+
severity: "warning",
|
|
3973
|
+
message: "Could not verify WCAG-compliant contrast ratios",
|
|
3974
|
+
autoFixable: false,
|
|
3975
|
+
fixSuggestion: "Ensure text has 4.5:1 contrast ratio against background"
|
|
3976
|
+
});
|
|
3977
|
+
score -= 10;
|
|
3978
|
+
}
|
|
3979
|
+
const hasSmallFont = html.match(/font-size:\s*(1[0-3]|[0-9])px/i) !== null;
|
|
3980
|
+
if (hasSmallFont) {
|
|
3981
|
+
issues.push({
|
|
3982
|
+
slideIndex: -1,
|
|
3983
|
+
dimension: "contrast",
|
|
3984
|
+
severity: "error",
|
|
3985
|
+
message: "Font size too small for presentation (< 14px)",
|
|
3986
|
+
currentValue: "Small font",
|
|
3987
|
+
expectedValue: "18px minimum for body text",
|
|
3988
|
+
autoFixable: true,
|
|
3989
|
+
fixSuggestion: "Increase font size to minimum 18px"
|
|
3990
|
+
});
|
|
3991
|
+
score -= 15;
|
|
3992
|
+
}
|
|
3993
|
+
return {
|
|
3994
|
+
name: "Contrast",
|
|
3995
|
+
score: Math.max(0, score),
|
|
3996
|
+
weight: DIMENSION_WEIGHTS.contrast,
|
|
3997
|
+
issues,
|
|
3998
|
+
details: {
|
|
3999
|
+
wcagLevel: "AA",
|
|
4000
|
+
minContrastRatio,
|
|
4001
|
+
colorDefinitions: colorMatches.length,
|
|
4002
|
+
backgroundDefinitions: bgColorMatches.length
|
|
4003
|
+
}
|
|
4004
|
+
};
|
|
4005
|
+
}
|
|
4006
|
+
/**
|
|
4007
|
+
* Score graphics dimension (images, placement, relevance)
|
|
4008
|
+
*/
|
|
4009
|
+
async scoreGraphics(slides) {
|
|
4010
|
+
const issues = [];
|
|
4011
|
+
let score = 100;
|
|
4012
|
+
let slidesWithImages = 0;
|
|
4013
|
+
let totalImageSlides = 0;
|
|
4014
|
+
for (let i = 0; i < slides.length; i++) {
|
|
4015
|
+
const slide = slides[i];
|
|
4016
|
+
if (!slide) continue;
|
|
4017
|
+
const shouldHaveImage = ["hero", "image", "feature"].includes(slide.type);
|
|
4018
|
+
if (shouldHaveImage) {
|
|
4019
|
+
totalImageSlides++;
|
|
4020
|
+
if (slide.data.image || slide.data.backgroundImage) {
|
|
4021
|
+
slidesWithImages++;
|
|
4022
|
+
} else {
|
|
4023
|
+
issues.push({
|
|
4024
|
+
slideIndex: i,
|
|
4025
|
+
dimension: "graphics",
|
|
4026
|
+
severity: "warning",
|
|
4027
|
+
message: `Slide ${i + 1} (${slide.type}): Missing expected image`,
|
|
4028
|
+
autoFixable: false,
|
|
4029
|
+
fixSuggestion: "Add a relevant image or change slide type"
|
|
4030
|
+
});
|
|
4031
|
+
score -= 5;
|
|
4032
|
+
}
|
|
4033
|
+
}
|
|
4034
|
+
}
|
|
4035
|
+
return {
|
|
4036
|
+
name: "Graphics",
|
|
4037
|
+
score: Math.max(0, score),
|
|
4038
|
+
weight: DIMENSION_WEIGHTS.graphics,
|
|
4039
|
+
issues,
|
|
4040
|
+
details: {
|
|
4041
|
+
slidesWithImages,
|
|
4042
|
+
expectedImageSlides: totalImageSlides,
|
|
4043
|
+
imageRatio: totalImageSlides > 0 ? slidesWithImages / totalImageSlides : 1
|
|
4044
|
+
}
|
|
4045
|
+
};
|
|
4046
|
+
}
|
|
4047
|
+
/**
|
|
4048
|
+
* Score content dimension (word limits, bullet counts, structure)
|
|
4049
|
+
* This is critical - must use KB rules exactly.
|
|
4050
|
+
*/
|
|
4051
|
+
async scoreContent(slides) {
|
|
4052
|
+
const issues = [];
|
|
4053
|
+
let totalScore = 0;
|
|
4054
|
+
let checks = 0;
|
|
4055
|
+
const wordLimits = this.kb.getWordLimits(this.mode);
|
|
4056
|
+
const bulletLimits = this.kb.getBulletLimits(this.mode);
|
|
4057
|
+
const defaultMaxWords = this.mode === "keynote" ? 25 : 80;
|
|
4058
|
+
const defaultMinWords = this.mode === "keynote" ? 3 : 15;
|
|
4059
|
+
const defaultMaxBullets = 5;
|
|
4060
|
+
for (let i = 0; i < slides.length; i++) {
|
|
4061
|
+
const slide = slides[i];
|
|
4062
|
+
if (!slide) continue;
|
|
4063
|
+
const wordCount = this.countWords(slide);
|
|
4064
|
+
const slideType = slide.type;
|
|
4065
|
+
const maxWords = wordLimits[slideType] ?? defaultMaxWords;
|
|
4066
|
+
const minWords = defaultMinWords;
|
|
4067
|
+
if (wordCount > maxWords) {
|
|
4068
|
+
const severity = wordCount > maxWords * 1.5 ? "error" : "warning";
|
|
4069
|
+
issues.push({
|
|
4070
|
+
slideIndex: i,
|
|
4071
|
+
dimension: "content",
|
|
4072
|
+
severity,
|
|
4073
|
+
message: `Slide ${i + 1}: Word count ${wordCount} exceeds limit of ${maxWords} for ${this.mode} mode`,
|
|
4074
|
+
currentValue: wordCount,
|
|
4075
|
+
expectedValue: maxWords,
|
|
4076
|
+
autoFixable: true,
|
|
4077
|
+
fixSuggestion: "Condense text to key points only"
|
|
4078
|
+
});
|
|
4079
|
+
totalScore += severity === "error" ? 40 : 70;
|
|
4080
|
+
} else if (wordCount < minWords && !["title", "section-divider", "thank-you"].includes(slide.type)) {
|
|
4081
|
+
issues.push({
|
|
4082
|
+
slideIndex: i,
|
|
4083
|
+
dimension: "content",
|
|
4084
|
+
severity: "warning",
|
|
4085
|
+
message: `Slide ${i + 1}: Word count ${wordCount} may be too sparse (min: ${minWords})`,
|
|
4086
|
+
currentValue: wordCount,
|
|
4087
|
+
expectedValue: minWords,
|
|
4088
|
+
autoFixable: false,
|
|
4089
|
+
fixSuggestion: "Add supporting content"
|
|
4090
|
+
});
|
|
4091
|
+
totalScore += 80;
|
|
4092
|
+
} else {
|
|
4093
|
+
totalScore += 100;
|
|
4094
|
+
}
|
|
4095
|
+
checks++;
|
|
4096
|
+
if (slide.data.bullets && Array.isArray(slide.data.bullets)) {
|
|
4097
|
+
const bulletCount = slide.data.bullets.length;
|
|
4098
|
+
const maxBullets = bulletLimits[slideType] ?? defaultMaxBullets;
|
|
4099
|
+
if (bulletCount > maxBullets) {
|
|
4100
|
+
issues.push({
|
|
4101
|
+
slideIndex: i,
|
|
4102
|
+
dimension: "content",
|
|
4103
|
+
severity: "error",
|
|
4104
|
+
message: `Slide ${i + 1}: ${bulletCount} bullets exceeds limit of ${maxBullets}`,
|
|
4105
|
+
currentValue: bulletCount,
|
|
4106
|
+
expectedValue: maxBullets,
|
|
4107
|
+
autoFixable: true,
|
|
4108
|
+
fixSuggestion: "Reduce to top 3-5 key points"
|
|
4109
|
+
});
|
|
4110
|
+
totalScore += 50;
|
|
4111
|
+
} else {
|
|
4112
|
+
totalScore += 100;
|
|
4113
|
+
}
|
|
4114
|
+
checks++;
|
|
4115
|
+
}
|
|
4116
|
+
}
|
|
4117
|
+
const score = checks > 0 ? Math.round(totalScore / checks) : 100;
|
|
4118
|
+
return {
|
|
4119
|
+
name: "Content",
|
|
4120
|
+
score,
|
|
4121
|
+
weight: DIMENSION_WEIGHTS.content,
|
|
4122
|
+
issues,
|
|
4123
|
+
details: {
|
|
4124
|
+
mode: this.mode,
|
|
4125
|
+
wordLimits,
|
|
4126
|
+
bulletLimits,
|
|
4127
|
+
totalSlides: slides.length
|
|
4128
|
+
}
|
|
4129
|
+
};
|
|
4130
|
+
}
|
|
4131
|
+
/**
|
|
4132
|
+
* Score clarity dimension (message focus, information density)
|
|
4133
|
+
*/
|
|
4134
|
+
async scoreClarity(slides) {
|
|
4135
|
+
const issues = [];
|
|
4136
|
+
let totalScore = 0;
|
|
4137
|
+
let checks = 0;
|
|
4138
|
+
for (let i = 0; i < slides.length; i++) {
|
|
4139
|
+
const slide = slides[i];
|
|
4140
|
+
if (!slide) continue;
|
|
4141
|
+
if (slide.data.keyMessage) {
|
|
4142
|
+
const keyMessageWords = slide.data.keyMessage.split(/\s+/).length;
|
|
4143
|
+
const maxKeyMessageWords = this.mode === "keynote" ? 15 : 25;
|
|
4144
|
+
if (keyMessageWords > maxKeyMessageWords) {
|
|
4145
|
+
issues.push({
|
|
4146
|
+
slideIndex: i,
|
|
4147
|
+
dimension: "clarity",
|
|
4148
|
+
severity: "warning",
|
|
4149
|
+
message: `Slide ${i + 1}: Key message too long (${keyMessageWords} words > ${maxKeyMessageWords})`,
|
|
4150
|
+
currentValue: keyMessageWords,
|
|
4151
|
+
expectedValue: maxKeyMessageWords,
|
|
4152
|
+
autoFixable: true,
|
|
4153
|
+
fixSuggestion: "Shorten key message to one impactful sentence"
|
|
4154
|
+
});
|
|
4155
|
+
totalScore += 70;
|
|
4156
|
+
} else {
|
|
4157
|
+
totalScore += 100;
|
|
4158
|
+
}
|
|
4159
|
+
checks++;
|
|
4160
|
+
}
|
|
4161
|
+
if (slide.data.title) {
|
|
4162
|
+
const title = slide.data.title;
|
|
4163
|
+
const titleWords = title.split(/\s+/).length;
|
|
4164
|
+
if (titleWords > 10) {
|
|
4165
|
+
issues.push({
|
|
4166
|
+
slideIndex: i,
|
|
4167
|
+
dimension: "clarity",
|
|
4168
|
+
severity: "warning",
|
|
4169
|
+
message: `Slide ${i + 1}: Title too long (${titleWords} words)`,
|
|
4170
|
+
currentValue: titleWords,
|
|
4171
|
+
expectedValue: "2-8 words",
|
|
4172
|
+
autoFixable: true,
|
|
4173
|
+
fixSuggestion: "Use action-oriented, concise title"
|
|
4174
|
+
});
|
|
4175
|
+
totalScore += 75;
|
|
4176
|
+
} else {
|
|
4177
|
+
totalScore += 100;
|
|
4178
|
+
}
|
|
4179
|
+
checks++;
|
|
4180
|
+
}
|
|
4181
|
+
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);
|
|
4182
|
+
const maxElements = this.mode === "keynote" ? 4 : 6;
|
|
4183
|
+
if (elementCount > maxElements) {
|
|
4184
|
+
issues.push({
|
|
4185
|
+
slideIndex: i,
|
|
4186
|
+
dimension: "clarity",
|
|
4187
|
+
severity: "warning",
|
|
4188
|
+
message: `Slide ${i + 1}: Too many elements (${elementCount} > ${maxElements})`,
|
|
4189
|
+
currentValue: elementCount,
|
|
4190
|
+
expectedValue: maxElements,
|
|
4191
|
+
autoFixable: true,
|
|
4192
|
+
fixSuggestion: "Split into multiple slides for clarity"
|
|
4193
|
+
});
|
|
4194
|
+
totalScore += 70;
|
|
4195
|
+
} else {
|
|
4196
|
+
totalScore += 100;
|
|
4197
|
+
}
|
|
4198
|
+
checks++;
|
|
4199
|
+
}
|
|
4200
|
+
const score = checks > 0 ? Math.round(totalScore / checks) : 100;
|
|
4201
|
+
return {
|
|
4202
|
+
name: "Clarity",
|
|
4203
|
+
score,
|
|
4204
|
+
weight: DIMENSION_WEIGHTS.clarity,
|
|
4205
|
+
issues,
|
|
4206
|
+
details: {
|
|
4207
|
+
slidesAnalyzed: slides.length,
|
|
4208
|
+
mode: this.mode
|
|
4209
|
+
}
|
|
4210
|
+
};
|
|
4211
|
+
}
|
|
4212
|
+
/**
|
|
4213
|
+
* Score effectiveness dimension (expert methodology compliance)
|
|
4214
|
+
*/
|
|
4215
|
+
async scoreEffectiveness(slides) {
|
|
4216
|
+
const issues = [];
|
|
4217
|
+
let score = 100;
|
|
4218
|
+
if (slides.length >= 3) {
|
|
4219
|
+
const firstSlide = slides[0];
|
|
4220
|
+
const lastSlide = slides[slides.length - 1];
|
|
4221
|
+
if (firstSlide && !["title", "hero"].includes(firstSlide.type)) {
|
|
4222
|
+
issues.push({
|
|
4223
|
+
slideIndex: 0,
|
|
4224
|
+
dimension: "effectiveness",
|
|
4225
|
+
severity: "warning",
|
|
4226
|
+
message: "Presentation should start with a title or hero slide",
|
|
4227
|
+
currentValue: firstSlide.type,
|
|
4228
|
+
expectedValue: "title or hero",
|
|
4229
|
+
autoFixable: false,
|
|
4230
|
+
fixSuggestion: "Add a compelling opening slide"
|
|
4231
|
+
});
|
|
4232
|
+
score -= 10;
|
|
4233
|
+
}
|
|
4234
|
+
if (lastSlide && !["thank-you", "cta", "closing"].includes(lastSlide.type)) {
|
|
4235
|
+
issues.push({
|
|
4236
|
+
slideIndex: slides.length - 1,
|
|
4237
|
+
dimension: "effectiveness",
|
|
4238
|
+
severity: "warning",
|
|
4239
|
+
message: "Presentation should end with a closing or CTA slide",
|
|
4240
|
+
currentValue: lastSlide.type,
|
|
4241
|
+
expectedValue: "thank-you, cta, or closing",
|
|
4242
|
+
autoFixable: false,
|
|
4243
|
+
fixSuggestion: "Add a clear call-to-action or closing"
|
|
4244
|
+
});
|
|
4245
|
+
score -= 10;
|
|
4246
|
+
}
|
|
4247
|
+
}
|
|
4248
|
+
const keyMessages = slides.filter((s) => s.data.keyMessage);
|
|
4249
|
+
if (keyMessages.length > 0 && keyMessages.length !== 3 && keyMessages.length > 4) {
|
|
4250
|
+
issues.push({
|
|
4251
|
+
slideIndex: -1,
|
|
4252
|
+
dimension: "effectiveness",
|
|
4253
|
+
severity: "info",
|
|
4254
|
+
message: `Consider using Rule of Three: ${keyMessages.length} key messages found`,
|
|
4255
|
+
currentValue: keyMessages.length,
|
|
4256
|
+
expectedValue: 3,
|
|
4257
|
+
autoFixable: false,
|
|
4258
|
+
fixSuggestion: "Group messages into 3 main themes"
|
|
4259
|
+
});
|
|
4260
|
+
score -= 5;
|
|
4261
|
+
}
|
|
4262
|
+
const hasScqaElements = slides.some(
|
|
4263
|
+
(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")
|
|
4264
|
+
);
|
|
4265
|
+
if (!hasScqaElements && this.presentationType === "consulting_deck") {
|
|
4266
|
+
issues.push({
|
|
4267
|
+
slideIndex: -1,
|
|
4268
|
+
dimension: "effectiveness",
|
|
4269
|
+
severity: "warning",
|
|
4270
|
+
message: "Consulting deck should follow SCQA structure (Situation, Complication, Question, Answer)",
|
|
4271
|
+
autoFixable: false,
|
|
4272
|
+
fixSuggestion: "Organize content using Barbara Minto Pyramid Principle"
|
|
4273
|
+
});
|
|
4274
|
+
score -= 10;
|
|
4275
|
+
}
|
|
4276
|
+
const firstSlideType = slides[0]?.type;
|
|
4277
|
+
const lastSlideType = slides[slides.length - 1]?.type;
|
|
4278
|
+
return {
|
|
4279
|
+
name: "Effectiveness",
|
|
4280
|
+
score: Math.max(0, score),
|
|
4281
|
+
weight: DIMENSION_WEIGHTS.effectiveness,
|
|
4282
|
+
issues,
|
|
4283
|
+
details: {
|
|
4284
|
+
presentationType: this.presentationType,
|
|
4285
|
+
slideCount: slides.length,
|
|
4286
|
+
hasOpeningSlide: firstSlideType ? ["title", "hero"].includes(firstSlideType) : false,
|
|
4287
|
+
hasClosingSlide: lastSlideType ? ["thank-you", "cta", "closing"].includes(lastSlideType) : false
|
|
4288
|
+
}
|
|
4289
|
+
};
|
|
4290
|
+
}
|
|
4291
|
+
/**
|
|
4292
|
+
* Score consistency dimension (style uniformity, design coherence)
|
|
4293
|
+
*/
|
|
4294
|
+
async scoreConsistency(slides, html) {
|
|
4295
|
+
const issues = [];
|
|
4296
|
+
let score = 100;
|
|
4297
|
+
const hasCssVariables = html.includes("var(--") || html.includes(":root");
|
|
4298
|
+
if (!hasCssVariables) {
|
|
4299
|
+
issues.push({
|
|
4300
|
+
slideIndex: -1,
|
|
4301
|
+
dimension: "consistency",
|
|
4302
|
+
severity: "warning",
|
|
4303
|
+
message: "Presentation lacks CSS variables for consistent styling",
|
|
4304
|
+
autoFixable: true,
|
|
4305
|
+
fixSuggestion: "Use CSS variables for colors, fonts, and spacing"
|
|
4306
|
+
});
|
|
4307
|
+
score -= 10;
|
|
4308
|
+
}
|
|
4309
|
+
const titlePatterns = /* @__PURE__ */ new Set();
|
|
4310
|
+
for (const slide of slides) {
|
|
4311
|
+
if (slide.data.title) {
|
|
4312
|
+
const isUpperCase = slide.data.title === slide.data.title.toUpperCase();
|
|
4313
|
+
const words = slide.data.title.split(" ").filter((w) => w.length > 0);
|
|
4314
|
+
const isTitleCase = words.length > 0 && words.every(
|
|
4315
|
+
(w) => w.length > 0 && w[0] === w[0]?.toUpperCase()
|
|
4316
|
+
);
|
|
4317
|
+
titlePatterns.add(isUpperCase ? "UPPER" : isTitleCase ? "Title" : "sentence");
|
|
4318
|
+
}
|
|
4319
|
+
}
|
|
4320
|
+
if (titlePatterns.size > 1) {
|
|
4321
|
+
issues.push({
|
|
4322
|
+
slideIndex: -1,
|
|
4323
|
+
dimension: "consistency",
|
|
4324
|
+
severity: "warning",
|
|
4325
|
+
message: `Inconsistent title casing: ${Array.from(titlePatterns).join(", ")}`,
|
|
4326
|
+
autoFixable: true,
|
|
4327
|
+
fixSuggestion: "Use consistent title case throughout"
|
|
4328
|
+
});
|
|
4329
|
+
score -= 10;
|
|
4330
|
+
}
|
|
4331
|
+
const fontMatches = html.match(/font-family:\s*([^;]+);/gi) || [];
|
|
4332
|
+
const uniqueFonts = new Set(fontMatches.map((f) => f.toLowerCase()));
|
|
4333
|
+
if (uniqueFonts.size > 3) {
|
|
4334
|
+
issues.push({
|
|
4335
|
+
slideIndex: -1,
|
|
4336
|
+
dimension: "consistency",
|
|
4337
|
+
severity: "warning",
|
|
4338
|
+
message: `Too many font families (${uniqueFonts.size} > 3)`,
|
|
4339
|
+
autoFixable: true,
|
|
4340
|
+
fixSuggestion: "Use 2-3 complementary fonts max"
|
|
4341
|
+
});
|
|
4342
|
+
score -= 10;
|
|
4343
|
+
}
|
|
4344
|
+
return {
|
|
4345
|
+
name: "Consistency",
|
|
4346
|
+
score: Math.max(0, score),
|
|
4347
|
+
weight: DIMENSION_WEIGHTS.consistency,
|
|
4348
|
+
issues,
|
|
4349
|
+
details: {
|
|
4350
|
+
hasCssVariables,
|
|
4351
|
+
titlePatterns: Array.from(titlePatterns),
|
|
4352
|
+
fontFamilyCount: uniqueFonts.size
|
|
4353
|
+
}
|
|
4354
|
+
};
|
|
4355
|
+
}
|
|
4356
|
+
/**
|
|
4357
|
+
* Count words in a slide.
|
|
4358
|
+
*/
|
|
4359
|
+
countWords(slide) {
|
|
4360
|
+
let text = "";
|
|
4361
|
+
if (slide.data.title) text += slide.data.title + " ";
|
|
4362
|
+
if (slide.data.subtitle) text += slide.data.subtitle + " ";
|
|
4363
|
+
if (slide.data.body) text += slide.data.body + " ";
|
|
4364
|
+
if (slide.data.bullets) text += slide.data.bullets.join(" ") + " ";
|
|
4365
|
+
if (slide.data.keyMessage) text += slide.data.keyMessage + " ";
|
|
4366
|
+
return text.split(/\s+/).filter((w) => w.length > 0).length;
|
|
4367
|
+
}
|
|
4368
|
+
/**
|
|
4369
|
+
* Get a formatted report of the scoring results.
|
|
4370
|
+
*/
|
|
4371
|
+
formatReport(result) {
|
|
4372
|
+
const lines = [];
|
|
4373
|
+
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");
|
|
4374
|
+
lines.push("\u2551 7-DIMENSION QUALITY ASSESSMENT \u2551");
|
|
4375
|
+
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");
|
|
4376
|
+
lines.push("");
|
|
4377
|
+
lines.push(`Overall Score: ${result.overallScore}/100 ${result.passed ? "\u2705 PASSED" : "\u274C FAILED"}`);
|
|
4378
|
+
lines.push(`Threshold: ${result.threshold}/100`);
|
|
4379
|
+
lines.push("");
|
|
4380
|
+
lines.push("Dimension Breakdown:");
|
|
4381
|
+
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");
|
|
4382
|
+
const dims = result.dimensions;
|
|
4383
|
+
const formatDim = (name, d) => {
|
|
4384
|
+
const bar = "\u2588".repeat(Math.floor(d.score / 10)) + "\u2591".repeat(10 - Math.floor(d.score / 10));
|
|
4385
|
+
const status = d.score >= 95 ? "\u2705" : d.score >= 80 ? "\u26A0\uFE0F" : "\u274C";
|
|
4386
|
+
return `${status} ${name.padEnd(14)} ${bar} ${d.score.toString().padStart(3)}/100 (${(d.weight * 100).toFixed(0)}%)`;
|
|
4387
|
+
};
|
|
4388
|
+
lines.push(formatDim("Layout", dims.layout));
|
|
4389
|
+
lines.push(formatDim("Contrast", dims.contrast));
|
|
4390
|
+
lines.push(formatDim("Graphics", dims.graphics));
|
|
4391
|
+
lines.push(formatDim("Content", dims.content));
|
|
4392
|
+
lines.push(formatDim("Clarity", dims.clarity));
|
|
4393
|
+
lines.push(formatDim("Effectiveness", dims.effectiveness));
|
|
4394
|
+
lines.push(formatDim("Consistency", dims.consistency));
|
|
4395
|
+
lines.push("");
|
|
4396
|
+
const errors = result.issues.filter((i) => i.severity === "error");
|
|
4397
|
+
const warnings = result.issues.filter((i) => i.severity === "warning");
|
|
4398
|
+
if (errors.length > 0) {
|
|
4399
|
+
lines.push("\u274C Errors:");
|
|
4400
|
+
errors.forEach((e) => lines.push(` \u2022 ${e.message}`));
|
|
4401
|
+
lines.push("");
|
|
4402
|
+
}
|
|
4403
|
+
if (warnings.length > 0) {
|
|
4404
|
+
lines.push("\u26A0\uFE0F Warnings:");
|
|
4405
|
+
warnings.slice(0, 10).forEach((w) => lines.push(` \u2022 ${w.message}`));
|
|
4406
|
+
if (warnings.length > 10) {
|
|
4407
|
+
lines.push(` ... and ${warnings.length - 10} more warnings`);
|
|
4408
|
+
}
|
|
4409
|
+
lines.push("");
|
|
4410
|
+
}
|
|
4411
|
+
const autoFixable = result.issues.filter((i) => i.autoFixable);
|
|
4412
|
+
if (autoFixable.length > 0) {
|
|
4413
|
+
lines.push(`\u{1F527} ${autoFixable.length} issues can be auto-fixed`);
|
|
4414
|
+
}
|
|
4415
|
+
return lines.join("\n");
|
|
4416
|
+
}
|
|
4417
|
+
};
|
|
4418
|
+
|
|
4419
|
+
// src/qa/AutoFixEngine.ts
|
|
4420
|
+
var AutoFixEngine = class {
|
|
4421
|
+
kb;
|
|
4422
|
+
mode;
|
|
4423
|
+
presentationType;
|
|
4424
|
+
constructor(mode, presentationType) {
|
|
4425
|
+
this.mode = mode;
|
|
4426
|
+
this.presentationType = presentationType;
|
|
4427
|
+
}
|
|
4428
|
+
/**
|
|
4429
|
+
* Apply automatic fixes to slides based on scoring results.
|
|
4430
|
+
*/
|
|
4431
|
+
async fix(slides, scoringResult) {
|
|
4432
|
+
this.kb = await getKnowledgeGateway();
|
|
4433
|
+
const slidesFixed = JSON.parse(JSON.stringify(slides));
|
|
4434
|
+
const fixesApplied = [];
|
|
4435
|
+
const fixesSkipped = [];
|
|
4436
|
+
const autoFixableIssues = scoringResult.issues.filter((i) => i.autoFixable);
|
|
4437
|
+
for (const issue of autoFixableIssues) {
|
|
4438
|
+
const result = await this.applyFix(slidesFixed, issue);
|
|
4439
|
+
if (result.applied) {
|
|
4440
|
+
fixesApplied.push(result);
|
|
4441
|
+
} else {
|
|
4442
|
+
fixesSkipped.push(result);
|
|
4443
|
+
}
|
|
4444
|
+
}
|
|
4445
|
+
const summary = this.generateSummary(fixesApplied, fixesSkipped);
|
|
4446
|
+
return {
|
|
4447
|
+
slidesFixed,
|
|
4448
|
+
fixesApplied,
|
|
4449
|
+
fixesSkipped,
|
|
4450
|
+
summary
|
|
4451
|
+
};
|
|
4452
|
+
}
|
|
4453
|
+
/**
|
|
4454
|
+
* Apply a single fix based on the issue.
|
|
4455
|
+
*/
|
|
4456
|
+
async applyFix(slides, issue) {
|
|
4457
|
+
const result = {
|
|
4458
|
+
slideIndex: issue.slideIndex,
|
|
4459
|
+
dimension: issue.dimension,
|
|
4460
|
+
originalValue: issue.currentValue,
|
|
4461
|
+
newValue: null,
|
|
4462
|
+
description: issue.message,
|
|
4463
|
+
applied: false
|
|
4464
|
+
};
|
|
4465
|
+
switch (issue.dimension) {
|
|
4466
|
+
case "content":
|
|
4467
|
+
return this.fixContentIssue(slides, issue, result);
|
|
4468
|
+
case "clarity":
|
|
4469
|
+
return this.fixClarityIssue(slides, issue, result);
|
|
4470
|
+
case "layout":
|
|
4471
|
+
return this.fixLayoutIssue(slides, issue, result);
|
|
4472
|
+
case "consistency":
|
|
4473
|
+
return this.fixConsistencyIssue(slides, issue, result);
|
|
4474
|
+
case "contrast":
|
|
4475
|
+
return this.fixContrastIssue(slides, issue, result);
|
|
4476
|
+
default:
|
|
4477
|
+
result.description = `No auto-fix available for ${issue.dimension}`;
|
|
4478
|
+
return result;
|
|
4479
|
+
}
|
|
4480
|
+
}
|
|
4481
|
+
/**
|
|
4482
|
+
* Fix content-related issues (word count, bullet count).
|
|
4483
|
+
*/
|
|
4484
|
+
fixContentIssue(slides, issue, result) {
|
|
4485
|
+
if (issue.slideIndex < 0 || issue.slideIndex >= slides.length) {
|
|
4486
|
+
return result;
|
|
4487
|
+
}
|
|
4488
|
+
const slide = slides[issue.slideIndex];
|
|
4489
|
+
if (!slide) return result;
|
|
4490
|
+
if (issue.message.includes("Word count") && issue.message.includes("exceeds")) {
|
|
4491
|
+
const maxWords = issue.expectedValue;
|
|
4492
|
+
result.originalValue = this.countWords(slide);
|
|
4493
|
+
if (slide.data.body) {
|
|
4494
|
+
slide.data.body = this.condenseText(slide.data.body, maxWords / 2);
|
|
4495
|
+
}
|
|
4496
|
+
if (slide.data.bullets && slide.data.bullets.length > 0) {
|
|
4497
|
+
const bulletCount = slide.data.bullets.length;
|
|
4498
|
+
slide.data.bullets = slide.data.bullets.map(
|
|
4499
|
+
(bullet) => this.condenseText(bullet, Math.floor(maxWords / bulletCount))
|
|
4500
|
+
);
|
|
4501
|
+
}
|
|
4502
|
+
if (slide.data.subtitle) {
|
|
4503
|
+
slide.data.subtitle = this.condenseText(slide.data.subtitle, 10);
|
|
4504
|
+
}
|
|
4505
|
+
result.newValue = this.countWords(slide);
|
|
4506
|
+
result.applied = result.newValue <= maxWords;
|
|
4507
|
+
result.description = `Condensed content from ${result.originalValue} to ${result.newValue} words`;
|
|
4508
|
+
}
|
|
4509
|
+
if (issue.message.includes("bullets exceeds")) {
|
|
4510
|
+
const maxBullets = issue.expectedValue;
|
|
4511
|
+
if (slide.data.bullets) {
|
|
4512
|
+
result.originalValue = slide.data.bullets.length;
|
|
4513
|
+
slide.data.bullets = slide.data.bullets.slice(0, maxBullets);
|
|
4514
|
+
result.newValue = slide.data.bullets.length;
|
|
4515
|
+
result.applied = true;
|
|
4516
|
+
result.description = `Reduced bullets from ${result.originalValue} to ${result.newValue}`;
|
|
4517
|
+
}
|
|
4518
|
+
}
|
|
4519
|
+
return result;
|
|
4520
|
+
}
|
|
4521
|
+
/**
|
|
4522
|
+
* Fix clarity-related issues (key message length, title length).
|
|
4523
|
+
*/
|
|
4524
|
+
fixClarityIssue(slides, issue, result) {
|
|
4525
|
+
if (issue.slideIndex < 0 || issue.slideIndex >= slides.length) {
|
|
4526
|
+
return result;
|
|
4527
|
+
}
|
|
4528
|
+
const slide = slides[issue.slideIndex];
|
|
4529
|
+
if (!slide) return result;
|
|
4530
|
+
if (issue.message.includes("Key message too long")) {
|
|
4531
|
+
if (slide.data.keyMessage) {
|
|
4532
|
+
result.originalValue = slide.data.keyMessage;
|
|
4533
|
+
const maxWords = issue.expectedValue;
|
|
4534
|
+
slide.data.keyMessage = this.condenseText(slide.data.keyMessage, maxWords);
|
|
4535
|
+
result.newValue = slide.data.keyMessage;
|
|
4536
|
+
result.applied = true;
|
|
4537
|
+
result.description = `Shortened key message to ${maxWords} words`;
|
|
4538
|
+
}
|
|
4539
|
+
}
|
|
4540
|
+
if (issue.message.includes("Title too long")) {
|
|
4541
|
+
if (slide.data.title) {
|
|
4542
|
+
result.originalValue = slide.data.title;
|
|
4543
|
+
const words = slide.data.title.split(/\s+/);
|
|
4544
|
+
const originalLength = words.length;
|
|
4545
|
+
if (words.length > 6) {
|
|
4546
|
+
slide.data.title = words.slice(0, 6).join(" ");
|
|
4547
|
+
}
|
|
4548
|
+
result.newValue = slide.data.title;
|
|
4549
|
+
result.applied = true;
|
|
4550
|
+
result.description = `Shortened title from ${originalLength} to ${slide.data.title.split(/\s+/).length} words`;
|
|
4551
|
+
}
|
|
4552
|
+
}
|
|
4553
|
+
if (issue.message.includes("Too many elements")) {
|
|
4554
|
+
result.originalValue = issue.currentValue;
|
|
4555
|
+
if (slide.data.subtitle && slide.data.body) {
|
|
4556
|
+
delete slide.data.subtitle;
|
|
4557
|
+
result.applied = true;
|
|
4558
|
+
result.description = "Removed subtitle to reduce element count";
|
|
4559
|
+
} else if (slide.data.body && slide.data.bullets && slide.data.bullets.length > 0) {
|
|
4560
|
+
delete slide.data.body;
|
|
4561
|
+
result.applied = true;
|
|
4562
|
+
result.description = "Removed body text, keeping bullets";
|
|
4563
|
+
}
|
|
4564
|
+
result.newValue = this.countElements(slide);
|
|
4565
|
+
}
|
|
4566
|
+
return result;
|
|
2821
4567
|
}
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
if (
|
|
2827
|
-
|
|
4568
|
+
/**
|
|
4569
|
+
* Fix layout-related issues.
|
|
4570
|
+
*/
|
|
4571
|
+
fixLayoutIssue(slides, issue, result) {
|
|
4572
|
+
if (issue.slideIndex < 0 || issue.slideIndex >= slides.length) {
|
|
4573
|
+
return result;
|
|
4574
|
+
}
|
|
4575
|
+
const slide = slides[issue.slideIndex];
|
|
4576
|
+
if (!slide) return result;
|
|
4577
|
+
if (issue.message.includes("Insufficient whitespace")) {
|
|
4578
|
+
const currentWordCount = this.countWords(slide);
|
|
4579
|
+
const targetReduction = 0.3;
|
|
4580
|
+
const targetWords = Math.floor(currentWordCount * (1 - targetReduction));
|
|
4581
|
+
result.originalValue = currentWordCount;
|
|
4582
|
+
if (slide.data.body) {
|
|
4583
|
+
slide.data.body = this.condenseText(slide.data.body, Math.floor(targetWords * 0.5));
|
|
4584
|
+
}
|
|
4585
|
+
if (slide.data.bullets && slide.data.bullets.length > 0) {
|
|
4586
|
+
const wordsPerBullet = Math.floor(targetWords / (slide.data.bullets.length * 2));
|
|
4587
|
+
slide.data.bullets = slide.data.bullets.map((b) => this.condenseText(b, wordsPerBullet));
|
|
4588
|
+
}
|
|
4589
|
+
result.newValue = this.countWords(slide);
|
|
4590
|
+
result.applied = true;
|
|
4591
|
+
result.description = `Reduced content from ${result.originalValue} to ${result.newValue} words for better whitespace`;
|
|
4592
|
+
}
|
|
4593
|
+
return result;
|
|
2828
4594
|
}
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
slide
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
});
|
|
2842
|
-
});
|
|
2843
|
-
});
|
|
2844
|
-
content.perSlide.forEach((slide) => {
|
|
2845
|
-
slide.issues.forEach((issue) => {
|
|
2846
|
-
issues.push({
|
|
2847
|
-
severity: "warning",
|
|
2848
|
-
category: "content",
|
|
2849
|
-
slideIndex: slide.slideIndex,
|
|
2850
|
-
message: issue
|
|
2851
|
-
});
|
|
2852
|
-
});
|
|
2853
|
-
});
|
|
2854
|
-
content.glanceTest.filter((g) => !g.passed).forEach((g) => {
|
|
2855
|
-
const issue = {
|
|
2856
|
-
severity: "warning",
|
|
2857
|
-
category: "content",
|
|
2858
|
-
slideIndex: g.slideIndex,
|
|
2859
|
-
message: `Glance test failed: "${g.keyMessage.substring(0, 50)}..." takes ${g.readingTime}s to read`
|
|
2860
|
-
};
|
|
2861
|
-
if (g.recommendation) {
|
|
2862
|
-
issue.suggestion = g.recommendation;
|
|
4595
|
+
/**
|
|
4596
|
+
* Fix consistency-related issues.
|
|
4597
|
+
*/
|
|
4598
|
+
fixConsistencyIssue(slides, issue, result) {
|
|
4599
|
+
if (issue.message.includes("Inconsistent title casing")) {
|
|
4600
|
+
let fixedCount = 0;
|
|
4601
|
+
for (const slide of slides) {
|
|
4602
|
+
if (slide.data.title) {
|
|
4603
|
+
const original = slide.data.title;
|
|
4604
|
+
slide.data.title = this.toTitleCase(slide.data.title);
|
|
4605
|
+
if (slide.data.title !== original) fixedCount++;
|
|
4606
|
+
}
|
|
2863
4607
|
}
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
category: "expert",
|
|
2871
|
-
message: `${e.expertName}: ${v}`
|
|
2872
|
-
});
|
|
2873
|
-
});
|
|
2874
|
-
});
|
|
2875
|
-
accessibility.fontSizeIssues.forEach((issue) => {
|
|
2876
|
-
issues.push({
|
|
2877
|
-
severity: "error",
|
|
2878
|
-
category: "accessibility",
|
|
2879
|
-
slideIndex: issue.slideIndex,
|
|
2880
|
-
message: `Font size ${issue.actualSize}px below minimum ${issue.minimumSize}px`,
|
|
2881
|
-
suggestion: `Increase font size to at least ${issue.minimumSize}px`
|
|
2882
|
-
});
|
|
2883
|
-
});
|
|
2884
|
-
accessibility.contrastIssues.forEach((issue) => {
|
|
2885
|
-
issues.push({
|
|
2886
|
-
severity: "error",
|
|
2887
|
-
category: "accessibility",
|
|
2888
|
-
slideIndex: issue.slideIndex,
|
|
2889
|
-
message: `Contrast ratio ${issue.ratio.toFixed(2)} below required ${issue.required}`,
|
|
2890
|
-
suggestion: "Increase contrast between text and background"
|
|
2891
|
-
});
|
|
2892
|
-
});
|
|
2893
|
-
return issues;
|
|
4608
|
+
result.originalValue = "Mixed casing";
|
|
4609
|
+
result.newValue = "Title Case";
|
|
4610
|
+
result.applied = fixedCount > 0;
|
|
4611
|
+
result.description = `Applied Title Case to ${fixedCount} slide titles`;
|
|
4612
|
+
}
|
|
4613
|
+
return result;
|
|
2894
4614
|
}
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
4615
|
+
/**
|
|
4616
|
+
* Fix contrast-related issues.
|
|
4617
|
+
* Note: These are CSS fixes, typically handled at generation time.
|
|
4618
|
+
*/
|
|
4619
|
+
fixContrastIssue(slides, issue, result) {
|
|
4620
|
+
result.description = "Contrast issues flagged for CSS regeneration";
|
|
4621
|
+
result.applied = false;
|
|
4622
|
+
return result;
|
|
4623
|
+
}
|
|
4624
|
+
/**
|
|
4625
|
+
* Condense text to approximately maxWords.
|
|
4626
|
+
* Uses smart truncation that preserves meaning.
|
|
4627
|
+
*/
|
|
4628
|
+
condenseText(text, maxWords) {
|
|
4629
|
+
const words = text.split(/\s+/);
|
|
4630
|
+
if (words.length <= maxWords) {
|
|
4631
|
+
return text;
|
|
4632
|
+
}
|
|
4633
|
+
const fillerWords = /* @__PURE__ */ new Set([
|
|
4634
|
+
"very",
|
|
4635
|
+
"really",
|
|
4636
|
+
"actually",
|
|
4637
|
+
"basically",
|
|
4638
|
+
"literally",
|
|
4639
|
+
"obviously",
|
|
4640
|
+
"clearly",
|
|
4641
|
+
"simply",
|
|
4642
|
+
"just",
|
|
4643
|
+
"that",
|
|
4644
|
+
"which",
|
|
4645
|
+
"would",
|
|
4646
|
+
"could",
|
|
4647
|
+
"should",
|
|
4648
|
+
"might"
|
|
4649
|
+
]);
|
|
4650
|
+
let filtered = words.filter((w) => !fillerWords.has(w.toLowerCase()));
|
|
4651
|
+
if (filtered.length <= maxWords) {
|
|
4652
|
+
return filtered.join(" ");
|
|
4653
|
+
}
|
|
4654
|
+
const punctuation = [".", ",", ";", ":", "-"];
|
|
4655
|
+
let breakPoint = maxWords;
|
|
4656
|
+
for (let i = maxWords - 1; i >= maxWords - 5 && i >= 0; i--) {
|
|
4657
|
+
const word = filtered[i];
|
|
4658
|
+
if (word && punctuation.some((p) => word.endsWith(p))) {
|
|
4659
|
+
breakPoint = i + 1;
|
|
4660
|
+
break;
|
|
4661
|
+
}
|
|
2901
4662
|
}
|
|
4663
|
+
filtered = filtered.slice(0, breakPoint);
|
|
4664
|
+
let result = filtered.join(" ");
|
|
4665
|
+
if (!result.endsWith(".") && !result.endsWith("!") && !result.endsWith("?")) {
|
|
4666
|
+
result = result.replace(/[,;:]$/, "") + "...";
|
|
4667
|
+
}
|
|
4668
|
+
return result;
|
|
2902
4669
|
}
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
4670
|
+
/**
|
|
4671
|
+
* Convert text to Title Case.
|
|
4672
|
+
*/
|
|
4673
|
+
toTitleCase(text) {
|
|
4674
|
+
const minorWords = /* @__PURE__ */ new Set([
|
|
4675
|
+
"a",
|
|
4676
|
+
"an",
|
|
4677
|
+
"the",
|
|
4678
|
+
"and",
|
|
4679
|
+
"but",
|
|
4680
|
+
"or",
|
|
4681
|
+
"for",
|
|
4682
|
+
"nor",
|
|
4683
|
+
"on",
|
|
4684
|
+
"at",
|
|
4685
|
+
"to",
|
|
4686
|
+
"by",
|
|
4687
|
+
"of",
|
|
4688
|
+
"in",
|
|
4689
|
+
"with"
|
|
4690
|
+
]);
|
|
4691
|
+
return text.split(" ").map((word, index) => {
|
|
4692
|
+
if (index === 0) {
|
|
4693
|
+
return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
|
|
4694
|
+
}
|
|
4695
|
+
if (minorWords.has(word.toLowerCase())) {
|
|
4696
|
+
return word.toLowerCase();
|
|
4697
|
+
}
|
|
4698
|
+
return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
|
|
4699
|
+
}).join(" ");
|
|
4700
|
+
}
|
|
4701
|
+
/**
|
|
4702
|
+
* Count words in a slide.
|
|
4703
|
+
*/
|
|
4704
|
+
countWords(slide) {
|
|
4705
|
+
let text = "";
|
|
4706
|
+
if (slide.data.title) text += slide.data.title + " ";
|
|
4707
|
+
if (slide.data.subtitle) text += slide.data.subtitle + " ";
|
|
4708
|
+
if (slide.data.body) text += slide.data.body + " ";
|
|
4709
|
+
if (slide.data.bullets) text += slide.data.bullets.join(" ") + " ";
|
|
4710
|
+
if (slide.data.keyMessage) text += slide.data.keyMessage + " ";
|
|
4711
|
+
return text.split(/\s+/).filter((w) => w.length > 0).length;
|
|
4712
|
+
}
|
|
4713
|
+
/**
|
|
4714
|
+
* Count elements in a slide.
|
|
4715
|
+
*/
|
|
4716
|
+
countElements(slide) {
|
|
4717
|
+
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);
|
|
4718
|
+
}
|
|
4719
|
+
/**
|
|
4720
|
+
* Generate a summary of fixes applied.
|
|
4721
|
+
*/
|
|
4722
|
+
generateSummary(applied, skipped) {
|
|
4723
|
+
const lines = [];
|
|
4724
|
+
lines.push(`
|
|
4725
|
+
\u{1F527} Auto-Fix Summary`);
|
|
4726
|
+
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`);
|
|
4727
|
+
lines.push(`Fixes Applied: ${applied.length}`);
|
|
4728
|
+
lines.push(`Fixes Skipped: ${skipped.length}`);
|
|
4729
|
+
lines.push("");
|
|
4730
|
+
if (applied.length > 0) {
|
|
4731
|
+
lines.push("\u2705 Applied Fixes:");
|
|
4732
|
+
for (const fix of applied) {
|
|
4733
|
+
lines.push(` \u2022 ${fix.description}`);
|
|
4734
|
+
}
|
|
4735
|
+
lines.push("");
|
|
4736
|
+
}
|
|
4737
|
+
if (skipped.length > 0) {
|
|
4738
|
+
lines.push("\u26A0\uFE0F Skipped Fixes (require manual attention):");
|
|
4739
|
+
for (const fix of skipped.slice(0, 5)) {
|
|
4740
|
+
lines.push(` \u2022 ${fix.description}`);
|
|
4741
|
+
}
|
|
4742
|
+
if (skipped.length > 5) {
|
|
4743
|
+
lines.push(` ... and ${skipped.length - 5} more`);
|
|
4744
|
+
}
|
|
2907
4745
|
}
|
|
4746
|
+
return lines.join("\n");
|
|
2908
4747
|
}
|
|
2909
4748
|
};
|
|
4749
|
+
function createAutoFixEngine(mode, presentationType) {
|
|
4750
|
+
return new AutoFixEngine(mode, presentationType);
|
|
4751
|
+
}
|
|
2910
4752
|
|
|
2911
4753
|
// src/generators/html/RevealJsGenerator.ts
|
|
2912
4754
|
var RevealJsGenerator = class {
|
|
@@ -3502,6 +5344,182 @@ ${slides}
|
|
|
3502
5344
|
}
|
|
3503
5345
|
};
|
|
3504
5346
|
|
|
5347
|
+
// src/qa/IterativeQAEngine.ts
|
|
5348
|
+
var DEFAULT_OPTIONS = {
|
|
5349
|
+
minScore: 95,
|
|
5350
|
+
maxIterations: 5,
|
|
5351
|
+
verbose: true
|
|
5352
|
+
};
|
|
5353
|
+
var IterativeQAEngine = class {
|
|
5354
|
+
kb;
|
|
5355
|
+
scorer;
|
|
5356
|
+
fixer;
|
|
5357
|
+
generator;
|
|
5358
|
+
mode;
|
|
5359
|
+
presentationType;
|
|
5360
|
+
config;
|
|
5361
|
+
constructor(mode, presentationType, config) {
|
|
5362
|
+
this.mode = mode;
|
|
5363
|
+
this.presentationType = presentationType;
|
|
5364
|
+
this.config = config;
|
|
5365
|
+
this.scorer = new SevenDimensionScorer(mode, presentationType);
|
|
5366
|
+
this.fixer = new AutoFixEngine(mode, presentationType);
|
|
5367
|
+
this.generator = new RevealJsGenerator();
|
|
5368
|
+
}
|
|
5369
|
+
/**
|
|
5370
|
+
* Run the iterative QA process.
|
|
5371
|
+
*/
|
|
5372
|
+
async run(initialSlides, initialHtml, options = {}) {
|
|
5373
|
+
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
5374
|
+
this.kb = await getKnowledgeGateway();
|
|
5375
|
+
const iterations = [];
|
|
5376
|
+
let currentSlides = initialSlides;
|
|
5377
|
+
let currentHtml = initialHtml;
|
|
5378
|
+
let scoringResult;
|
|
5379
|
+
let autoFixSummary = "";
|
|
5380
|
+
let totalFixesApplied = 0;
|
|
5381
|
+
if (opts.verbose) {
|
|
5382
|
+
console.log("\n\u{1F504} Starting Iterative QA Process");
|
|
5383
|
+
console.log(` Target Score: ${opts.minScore}/100`);
|
|
5384
|
+
console.log(` Max Iterations: ${opts.maxIterations}`);
|
|
5385
|
+
console.log("");
|
|
5386
|
+
}
|
|
5387
|
+
for (let i = 0; i < opts.maxIterations; i++) {
|
|
5388
|
+
const iterationNum = i + 1;
|
|
5389
|
+
if (opts.verbose) {
|
|
5390
|
+
console.log(`\u{1F4CA} Iteration ${iterationNum}/${opts.maxIterations}...`);
|
|
5391
|
+
}
|
|
5392
|
+
scoringResult = await this.scorer.score(currentSlides, currentHtml, opts.minScore);
|
|
5393
|
+
iterations.push({
|
|
5394
|
+
iteration: iterationNum,
|
|
5395
|
+
score: scoringResult.overallScore,
|
|
5396
|
+
dimensionScores: {
|
|
5397
|
+
layout: scoringResult.dimensions.layout.score,
|
|
5398
|
+
contrast: scoringResult.dimensions.contrast.score,
|
|
5399
|
+
graphics: scoringResult.dimensions.graphics.score,
|
|
5400
|
+
content: scoringResult.dimensions.content.score,
|
|
5401
|
+
clarity: scoringResult.dimensions.clarity.score,
|
|
5402
|
+
effectiveness: scoringResult.dimensions.effectiveness.score,
|
|
5403
|
+
consistency: scoringResult.dimensions.consistency.score
|
|
5404
|
+
},
|
|
5405
|
+
fixesApplied: 0,
|
|
5406
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
5407
|
+
});
|
|
5408
|
+
if (opts.verbose) {
|
|
5409
|
+
console.log(` Score: ${scoringResult.overallScore}/100`);
|
|
5410
|
+
}
|
|
5411
|
+
if (scoringResult.passed) {
|
|
5412
|
+
if (opts.verbose) {
|
|
5413
|
+
console.log(` \u2705 PASSED - Score meets threshold (${opts.minScore})`);
|
|
5414
|
+
}
|
|
5415
|
+
break;
|
|
5416
|
+
}
|
|
5417
|
+
if (i < opts.maxIterations - 1) {
|
|
5418
|
+
if (opts.verbose) {
|
|
5419
|
+
console.log(` \u26A0\uFE0F Below threshold, applying auto-fixes...`);
|
|
5420
|
+
}
|
|
5421
|
+
const fixResult = await this.fixer.fix(currentSlides, scoringResult);
|
|
5422
|
+
if (fixResult.fixesApplied.length > 0) {
|
|
5423
|
+
currentSlides = fixResult.slidesFixed;
|
|
5424
|
+
currentHtml = await this.generator.generate(currentSlides, this.config);
|
|
5425
|
+
const lastIteration = iterations[iterations.length - 1];
|
|
5426
|
+
if (lastIteration) {
|
|
5427
|
+
lastIteration.fixesApplied = fixResult.fixesApplied.length;
|
|
5428
|
+
}
|
|
5429
|
+
totalFixesApplied += fixResult.fixesApplied.length;
|
|
5430
|
+
if (opts.verbose) {
|
|
5431
|
+
console.log(` \u{1F527} Applied ${fixResult.fixesApplied.length} fixes`);
|
|
5432
|
+
}
|
|
5433
|
+
autoFixSummary += fixResult.summary + "\n";
|
|
5434
|
+
} else {
|
|
5435
|
+
if (opts.verbose) {
|
|
5436
|
+
console.log(` \u26A0\uFE0F No auto-fixes available, manual review needed`);
|
|
5437
|
+
}
|
|
5438
|
+
break;
|
|
5439
|
+
}
|
|
5440
|
+
}
|
|
5441
|
+
}
|
|
5442
|
+
if (!scoringResult.passed) {
|
|
5443
|
+
scoringResult = await this.scorer.score(currentSlides, currentHtml, opts.minScore);
|
|
5444
|
+
}
|
|
5445
|
+
const report = this.generateReport(
|
|
5446
|
+
scoringResult,
|
|
5447
|
+
iterations,
|
|
5448
|
+
opts,
|
|
5449
|
+
totalFixesApplied
|
|
5450
|
+
);
|
|
5451
|
+
if (opts.verbose) {
|
|
5452
|
+
console.log(report);
|
|
5453
|
+
}
|
|
5454
|
+
return {
|
|
5455
|
+
finalScore: scoringResult.overallScore,
|
|
5456
|
+
passed: scoringResult.passed,
|
|
5457
|
+
threshold: opts.minScore,
|
|
5458
|
+
iterations,
|
|
5459
|
+
totalIterations: iterations.length,
|
|
5460
|
+
maxIterations: opts.maxIterations,
|
|
5461
|
+
slides: currentSlides,
|
|
5462
|
+
html: currentHtml,
|
|
5463
|
+
finalScoring: scoringResult,
|
|
5464
|
+
autoFixSummary,
|
|
5465
|
+
report
|
|
5466
|
+
};
|
|
5467
|
+
}
|
|
5468
|
+
/**
|
|
5469
|
+
* Generate a comprehensive report.
|
|
5470
|
+
*/
|
|
5471
|
+
generateReport(finalScoring, iterations, options, totalFixesApplied) {
|
|
5472
|
+
const lines = [];
|
|
5473
|
+
lines.push("");
|
|
5474
|
+
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");
|
|
5475
|
+
lines.push("\u2551 ITERATIVE QA FINAL REPORT \u2551");
|
|
5476
|
+
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");
|
|
5477
|
+
lines.push("");
|
|
5478
|
+
const passStatus = finalScoring.passed ? "\u2705 PASSED" : "\u274C FAILED";
|
|
5479
|
+
lines.push(`Final Score: ${finalScoring.overallScore}/100 ${passStatus}`);
|
|
5480
|
+
lines.push(`Threshold: ${options.minScore}/100`);
|
|
5481
|
+
lines.push(`Iterations: ${iterations.length}/${options.maxIterations}`);
|
|
5482
|
+
lines.push(`Total Fixes Applied: ${totalFixesApplied}`);
|
|
5483
|
+
lines.push("");
|
|
5484
|
+
if (iterations.length > 1) {
|
|
5485
|
+
lines.push("Score Progression:");
|
|
5486
|
+
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");
|
|
5487
|
+
for (const iter of iterations) {
|
|
5488
|
+
const bar = "\u2588".repeat(Math.floor(iter.score / 10)) + "\u2591".repeat(10 - Math.floor(iter.score / 10));
|
|
5489
|
+
lines.push(` Iter ${iter.iteration}: ${bar} ${iter.score}/100 (+${iter.fixesApplied} fixes)`);
|
|
5490
|
+
}
|
|
5491
|
+
lines.push("");
|
|
5492
|
+
}
|
|
5493
|
+
lines.push(this.scorer.formatReport(finalScoring));
|
|
5494
|
+
lines.push("");
|
|
5495
|
+
lines.push("\u{1F4DA} Knowledge Base Compliance:");
|
|
5496
|
+
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");
|
|
5497
|
+
lines.push(` Mode: ${this.mode}`);
|
|
5498
|
+
lines.push(` Presentation Type: ${this.presentationType}`);
|
|
5499
|
+
lines.push(` Word Limits: ${this.mode === "keynote" ? "6-25" : "40-80"} per slide`);
|
|
5500
|
+
lines.push(` Expert Frameworks: Duarte, Reynolds, Gallo, Anderson`);
|
|
5501
|
+
lines.push("");
|
|
5502
|
+
if (!finalScoring.passed) {
|
|
5503
|
+
lines.push("\u{1F4CB} Recommendations for Manual Review:");
|
|
5504
|
+
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");
|
|
5505
|
+
const manualIssues = finalScoring.issues.filter((i) => !i.autoFixable);
|
|
5506
|
+
for (const issue of manualIssues.slice(0, 5)) {
|
|
5507
|
+
lines.push(` \u2022 ${issue.message}`);
|
|
5508
|
+
if (issue.fixSuggestion) {
|
|
5509
|
+
lines.push(` \u2192 ${issue.fixSuggestion}`);
|
|
5510
|
+
}
|
|
5511
|
+
}
|
|
5512
|
+
if (manualIssues.length > 5) {
|
|
5513
|
+
lines.push(` ... and ${manualIssues.length - 5} more issues`);
|
|
5514
|
+
}
|
|
5515
|
+
}
|
|
5516
|
+
return lines.join("\n");
|
|
5517
|
+
}
|
|
5518
|
+
};
|
|
5519
|
+
function createIterativeQAEngine(mode, presentationType, config) {
|
|
5520
|
+
return new IterativeQAEngine(mode, presentationType, config);
|
|
5521
|
+
}
|
|
5522
|
+
|
|
3505
5523
|
// src/generators/pptx/PowerPointGenerator.ts
|
|
3506
5524
|
var import_pptxgenjs = __toESM(require("pptxgenjs"));
|
|
3507
5525
|
|
|
@@ -4236,15 +6254,14 @@ var PowerPointGenerator = class {
|
|
|
4236
6254
|
// src/core/PresentationEngine.ts
|
|
4237
6255
|
var PresentationEngine = class {
|
|
4238
6256
|
contentAnalyzer;
|
|
4239
|
-
slideFactory;
|
|
4240
6257
|
templateEngine;
|
|
4241
6258
|
scoreCalculator;
|
|
4242
6259
|
qaEngine;
|
|
4243
6260
|
htmlGenerator;
|
|
4244
6261
|
pptxGenerator;
|
|
6262
|
+
kb;
|
|
4245
6263
|
constructor() {
|
|
4246
6264
|
this.contentAnalyzer = new ContentAnalyzer();
|
|
4247
|
-
this.slideFactory = new SlideFactory();
|
|
4248
6265
|
this.templateEngine = new TemplateEngine();
|
|
4249
6266
|
this.scoreCalculator = new ScoreCalculator();
|
|
4250
6267
|
this.qaEngine = new QAEngine();
|
|
@@ -4259,10 +6276,15 @@ var PresentationEngine = class {
|
|
|
4259
6276
|
*/
|
|
4260
6277
|
async generate(config) {
|
|
4261
6278
|
this.validateConfig(config);
|
|
6279
|
+
console.log("\u{1F4DA} Loading knowledge base...");
|
|
6280
|
+
this.kb = await getKnowledgeGateway();
|
|
4262
6281
|
console.log("\u{1F4DD} Analyzing content...");
|
|
4263
6282
|
const analysis = await this.contentAnalyzer.analyze(config.content, config.contentType);
|
|
6283
|
+
const presentationType = config.presentationType || analysis.detectedType;
|
|
6284
|
+
console.log(` \u2713 Presentation type: ${presentationType}`);
|
|
6285
|
+
const slideFactory = createSlideFactory(this.kb, presentationType);
|
|
4264
6286
|
console.log("\u{1F3A8} Creating slides...");
|
|
4265
|
-
const slides = await
|
|
6287
|
+
const slides = await slideFactory.createSlides(analysis);
|
|
4266
6288
|
console.log("\u2705 Validating structure...");
|
|
4267
6289
|
const structureErrors = this.validateStructure(slides, config.mode);
|
|
4268
6290
|
if (structureErrors.length > 0) {
|
|
@@ -4283,23 +6305,52 @@ var PresentationEngine = class {
|
|
|
4283
6305
|
}
|
|
4284
6306
|
let qaResults;
|
|
4285
6307
|
let score = 100;
|
|
6308
|
+
let iterativeResult = null;
|
|
6309
|
+
let finalSlides = slides;
|
|
6310
|
+
let finalHtml = outputs.html;
|
|
4286
6311
|
if (!config.skipQA && outputs.html) {
|
|
4287
|
-
console.log("\u{1F50D} Running QA validation...");
|
|
4288
|
-
qaResults = await this.qaEngine.validate(outputs.html, {
|
|
4289
|
-
mode: config.mode,
|
|
4290
|
-
strictMode: true
|
|
4291
|
-
});
|
|
4292
|
-
score = this.scoreCalculator.calculate(qaResults);
|
|
4293
|
-
console.log(`\u{1F4CA} QA Score: ${score}/100`);
|
|
4294
6312
|
const threshold = config.qaThreshold ?? 95;
|
|
4295
|
-
|
|
4296
|
-
|
|
6313
|
+
const maxIterations = config.maxIterations ?? 5;
|
|
6314
|
+
const useIterativeQA = config.useIterativeQA !== false;
|
|
6315
|
+
if (useIterativeQA) {
|
|
6316
|
+
console.log("\u{1F50D} Running Iterative QA (7-Dimension Scoring)...");
|
|
6317
|
+
const iterativeEngine = createIterativeQAEngine(
|
|
6318
|
+
config.mode,
|
|
6319
|
+
presentationType,
|
|
6320
|
+
config
|
|
6321
|
+
);
|
|
6322
|
+
iterativeResult = await iterativeEngine.run(slides, outputs.html, {
|
|
6323
|
+
minScore: threshold,
|
|
6324
|
+
maxIterations,
|
|
6325
|
+
verbose: true
|
|
6326
|
+
});
|
|
6327
|
+
score = iterativeResult.finalScore;
|
|
6328
|
+
finalSlides = iterativeResult.slides;
|
|
6329
|
+
finalHtml = iterativeResult.html;
|
|
6330
|
+
if (outputs.html) {
|
|
6331
|
+
outputs.html = finalHtml;
|
|
6332
|
+
}
|
|
6333
|
+
qaResults = this.buildQAResultsFrom7Dimension(iterativeResult);
|
|
6334
|
+
if (!iterativeResult.passed) {
|
|
6335
|
+
throw new QAFailureError(score, threshold, qaResults);
|
|
6336
|
+
}
|
|
6337
|
+
} else {
|
|
6338
|
+
console.log("\u{1F50D} Running QA validation (legacy mode)...");
|
|
6339
|
+
qaResults = await this.qaEngine.validate(outputs.html, {
|
|
6340
|
+
mode: config.mode,
|
|
6341
|
+
strictMode: true
|
|
6342
|
+
});
|
|
6343
|
+
score = this.scoreCalculator.calculate(qaResults);
|
|
6344
|
+
console.log(`\u{1F4CA} QA Score: ${score}/100`);
|
|
6345
|
+
if (score < threshold) {
|
|
6346
|
+
throw new QAFailureError(score, threshold, qaResults);
|
|
6347
|
+
}
|
|
4297
6348
|
}
|
|
4298
6349
|
} else {
|
|
4299
6350
|
qaResults = this.qaEngine.createEmptyResults();
|
|
4300
6351
|
console.log("\u26A0\uFE0F QA validation skipped (NOT RECOMMENDED)");
|
|
4301
6352
|
}
|
|
4302
|
-
const metadata = this.buildMetadata(config, analysis,
|
|
6353
|
+
const metadata = this.buildMetadata(config, analysis, finalSlides, iterativeResult);
|
|
4303
6354
|
return {
|
|
4304
6355
|
outputs,
|
|
4305
6356
|
qaResults,
|
|
@@ -4307,6 +6358,46 @@ var PresentationEngine = class {
|
|
|
4307
6358
|
metadata
|
|
4308
6359
|
};
|
|
4309
6360
|
}
|
|
6361
|
+
/**
|
|
6362
|
+
* Build QA results structure from 7-dimension scoring.
|
|
6363
|
+
*/
|
|
6364
|
+
buildQAResultsFrom7Dimension(iterativeResult) {
|
|
6365
|
+
const scoring = iterativeResult.finalScoring;
|
|
6366
|
+
return {
|
|
6367
|
+
passed: scoring.passed,
|
|
6368
|
+
score: scoring.overallScore,
|
|
6369
|
+
visual: {
|
|
6370
|
+
whitespacePercentage: scoring.dimensions.layout.score,
|
|
6371
|
+
layoutBalance: scoring.dimensions.layout.score / 100,
|
|
6372
|
+
colorContrast: scoring.dimensions.contrast.score / 100
|
|
6373
|
+
},
|
|
6374
|
+
content: {
|
|
6375
|
+
perSlide: [],
|
|
6376
|
+
issues: scoring.issues.filter((i) => i.dimension === "content")
|
|
6377
|
+
},
|
|
6378
|
+
accessibility: {
|
|
6379
|
+
wcagLevel: scoring.dimensions.contrast.score >= 95 ? "AAA" : "AA",
|
|
6380
|
+
issues: scoring.issues.filter((i) => i.dimension === "contrast")
|
|
6381
|
+
},
|
|
6382
|
+
issues: scoring.issues.map((issue) => ({
|
|
6383
|
+
severity: issue.severity,
|
|
6384
|
+
message: issue.message,
|
|
6385
|
+
slideIndex: issue.slideIndex,
|
|
6386
|
+
dimension: issue.dimension
|
|
6387
|
+
})),
|
|
6388
|
+
dimensions: {
|
|
6389
|
+
layout: scoring.dimensions.layout.score,
|
|
6390
|
+
contrast: scoring.dimensions.contrast.score,
|
|
6391
|
+
graphics: scoring.dimensions.graphics.score,
|
|
6392
|
+
content: scoring.dimensions.content.score,
|
|
6393
|
+
clarity: scoring.dimensions.clarity.score,
|
|
6394
|
+
effectiveness: scoring.dimensions.effectiveness.score,
|
|
6395
|
+
consistency: scoring.dimensions.consistency.score
|
|
6396
|
+
},
|
|
6397
|
+
iterations: iterativeResult.iterations,
|
|
6398
|
+
report: iterativeResult.report
|
|
6399
|
+
};
|
|
6400
|
+
}
|
|
4310
6401
|
/**
|
|
4311
6402
|
* Validate presentation configuration.
|
|
4312
6403
|
*/
|
|
@@ -4377,13 +6468,13 @@ var PresentationEngine = class {
|
|
|
4377
6468
|
/**
|
|
4378
6469
|
* Build presentation metadata.
|
|
4379
6470
|
*/
|
|
4380
|
-
buildMetadata(config, analysis, slides) {
|
|
6471
|
+
buildMetadata(config, analysis, slides, iterativeResult) {
|
|
4381
6472
|
const wordCounts = slides.map((s) => this.countWords(s));
|
|
4382
6473
|
const totalWords = wordCounts.reduce((sum, count) => sum + count, 0);
|
|
4383
6474
|
const avgWordsPerSlide = Math.round(totalWords / slides.length);
|
|
4384
6475
|
const minutesPerSlide = config.mode === "keynote" ? 1.5 : 2;
|
|
4385
6476
|
const estimatedDuration = Math.round(slides.length * minutesPerSlide);
|
|
4386
|
-
|
|
6477
|
+
const metadata = {
|
|
4387
6478
|
title: config.title,
|
|
4388
6479
|
author: config.author ?? "Unknown",
|
|
4389
6480
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -4392,8 +6483,15 @@ var PresentationEngine = class {
|
|
|
4392
6483
|
wordCount: totalWords,
|
|
4393
6484
|
avgWordsPerSlide,
|
|
4394
6485
|
estimatedDuration,
|
|
4395
|
-
frameworks: this.detectFrameworks(analysis)
|
|
6486
|
+
frameworks: this.detectFrameworks(analysis),
|
|
6487
|
+
presentationType: config.presentationType || analysis.detectedType
|
|
4396
6488
|
};
|
|
6489
|
+
if (iterativeResult) {
|
|
6490
|
+
metadata.qaIterations = iterativeResult.totalIterations;
|
|
6491
|
+
metadata.qaMaxIterations = iterativeResult.maxIterations;
|
|
6492
|
+
metadata.dimensionScores = iterativeResult.finalScoring.dimensions;
|
|
6493
|
+
}
|
|
6494
|
+
return metadata;
|
|
4397
6495
|
}
|
|
4398
6496
|
/**
|
|
4399
6497
|
* Detect which expert frameworks were applied.
|
|
@@ -4416,6 +6514,95 @@ var PresentationEngine = class {
|
|
|
4416
6514
|
}
|
|
4417
6515
|
};
|
|
4418
6516
|
|
|
6517
|
+
// src/core/SlideGenerator.ts
|
|
6518
|
+
var SlideGenerator = class {
|
|
6519
|
+
kb;
|
|
6520
|
+
factory;
|
|
6521
|
+
mode = "keynote";
|
|
6522
|
+
presentationType = "ted_keynote";
|
|
6523
|
+
async initialize() {
|
|
6524
|
+
this.kb = await getKnowledgeGateway();
|
|
6525
|
+
}
|
|
6526
|
+
/**
|
|
6527
|
+
* Generate slides from analyzed content using KB-driven SlideFactory.
|
|
6528
|
+
*
|
|
6529
|
+
* @version 7.0.0 - Now delegates ALL slide creation to SlideFactory
|
|
6530
|
+
* SlideFactory queries KB for every decision:
|
|
6531
|
+
* - Allowed slide types per presentation type
|
|
6532
|
+
* - Word limits per type
|
|
6533
|
+
* - Bullet limits per type
|
|
6534
|
+
* - Content pattern to slide type mapping
|
|
6535
|
+
* - Slide validation against KB rules
|
|
6536
|
+
*/
|
|
6537
|
+
async generate(analysis, type) {
|
|
6538
|
+
await this.initialize();
|
|
6539
|
+
this.presentationType = type || analysis.detectedType;
|
|
6540
|
+
this.mode = this.kb.getModeForType(this.presentationType);
|
|
6541
|
+
console.log(` \u2713 Using ${this.mode} mode for ${this.presentationType}`);
|
|
6542
|
+
this.factory = createSlideFactory(this.kb, this.presentationType);
|
|
6543
|
+
const slides = await this.factory.createSlides(analysis);
|
|
6544
|
+
const legacySlides = this.convertToLegacyFormat(slides);
|
|
6545
|
+
console.log(` \u2713 Generated ${legacySlides.length} KB-validated slides`);
|
|
6546
|
+
return legacySlides;
|
|
6547
|
+
}
|
|
6548
|
+
/**
|
|
6549
|
+
* Convert new Slide format to legacy format for backwards compatibility
|
|
6550
|
+
*/
|
|
6551
|
+
convertToLegacyFormat(slides) {
|
|
6552
|
+
return slides.map((slide) => ({
|
|
6553
|
+
index: slide.index,
|
|
6554
|
+
type: slide.type,
|
|
6555
|
+
title: slide.data.title || "",
|
|
6556
|
+
content: {
|
|
6557
|
+
subtitle: slide.data.subtitle,
|
|
6558
|
+
statement: slide.data.body,
|
|
6559
|
+
body: slide.data.body,
|
|
6560
|
+
bullets: slide.data.bullets,
|
|
6561
|
+
metrics: slide.data.metrics,
|
|
6562
|
+
quote: slide.data.quote ? { text: slide.data.quote, attribution: slide.data.attribution } : void 0,
|
|
6563
|
+
callToAction: slide.data.keyMessage
|
|
6564
|
+
},
|
|
6565
|
+
notes: slide.notes,
|
|
6566
|
+
template: this.mapTypeToTemplate(slide.type)
|
|
6567
|
+
}));
|
|
6568
|
+
}
|
|
6569
|
+
/**
|
|
6570
|
+
* Map slide type to template name for backwards compatibility
|
|
6571
|
+
*/
|
|
6572
|
+
mapTypeToTemplate(type) {
|
|
6573
|
+
const templateMap = {
|
|
6574
|
+
"title": "Title Impact",
|
|
6575
|
+
"agenda": "Detailed Findings",
|
|
6576
|
+
"big-number": "Data Insight",
|
|
6577
|
+
"metrics-grid": "Data Insight",
|
|
6578
|
+
"bullet-points": "Detailed Findings",
|
|
6579
|
+
"two-column": "Two Column",
|
|
6580
|
+
"three-column": "Three Column",
|
|
6581
|
+
"comparison": "Comparison",
|
|
6582
|
+
"timeline": "Process Timeline",
|
|
6583
|
+
"process": "Process",
|
|
6584
|
+
"quote": "Quote",
|
|
6585
|
+
"single-statement": "Single Statement",
|
|
6586
|
+
"cta": "Call to Action",
|
|
6587
|
+
"thank-you": "Title Impact"
|
|
6588
|
+
};
|
|
6589
|
+
return templateMap[type] || "Detailed Findings";
|
|
6590
|
+
}
|
|
6591
|
+
// =========================================================================
|
|
6592
|
+
// All slide creation is now handled by SlideFactory
|
|
6593
|
+
// Old methods removed in v7.0.0 - KB-driven SlideFactory handles:
|
|
6594
|
+
// - createTitleSlide, createAgendaSlide, createSituationSlide, etc.
|
|
6595
|
+
// - Content pattern classification
|
|
6596
|
+
// - KB-based slide type selection
|
|
6597
|
+
// - Text truncation, cleaning, deduplication
|
|
6598
|
+
// =========================================================================
|
|
6599
|
+
};
|
|
6600
|
+
async function initSlideGenerator() {
|
|
6601
|
+
const generator = new SlideGenerator();
|
|
6602
|
+
await generator.initialize();
|
|
6603
|
+
return generator;
|
|
6604
|
+
}
|
|
6605
|
+
|
|
4419
6606
|
// src/media/ImageProvider.ts
|
|
4420
6607
|
var LocalImageProvider = class {
|
|
4421
6608
|
name = "local";
|
|
@@ -4604,7 +6791,7 @@ async function validate(presentation, options) {
|
|
|
4604
6791
|
score
|
|
4605
6792
|
};
|
|
4606
6793
|
}
|
|
4607
|
-
var VERSION = "
|
|
6794
|
+
var VERSION = "7.1.0";
|
|
4608
6795
|
var index_default = {
|
|
4609
6796
|
generate,
|
|
4610
6797
|
validate,
|
|
@@ -4614,10 +6801,13 @@ var index_default = {
|
|
|
4614
6801
|
};
|
|
4615
6802
|
// Annotate the CommonJS export names for ESM import in node:
|
|
4616
6803
|
0 && (module.exports = {
|
|
6804
|
+
AutoFixEngine,
|
|
4617
6805
|
ChartJsProvider,
|
|
4618
6806
|
CompositeChartProvider,
|
|
4619
6807
|
CompositeImageProvider,
|
|
4620
6808
|
ContentAnalyzer,
|
|
6809
|
+
ContentPatternClassifier,
|
|
6810
|
+
IterativeQAEngine,
|
|
4621
6811
|
KnowledgeGateway,
|
|
4622
6812
|
LocalImageProvider,
|
|
4623
6813
|
MermaidProvider,
|
|
@@ -4629,16 +6819,22 @@ var index_default = {
|
|
|
4629
6819
|
QuickChartProvider,
|
|
4630
6820
|
RevealJsGenerator,
|
|
4631
6821
|
ScoreCalculator,
|
|
6822
|
+
SevenDimensionScorer,
|
|
4632
6823
|
SlideFactory,
|
|
6824
|
+
SlideGenerator,
|
|
4633
6825
|
TemplateEngine,
|
|
4634
6826
|
TemplateNotFoundError,
|
|
4635
6827
|
UnsplashImageProvider,
|
|
4636
6828
|
VERSION,
|
|
4637
6829
|
ValidationError,
|
|
6830
|
+
createAutoFixEngine,
|
|
4638
6831
|
createDefaultChartProvider,
|
|
4639
6832
|
createDefaultImageProvider,
|
|
6833
|
+
createIterativeQAEngine,
|
|
6834
|
+
createSlideFactory,
|
|
4640
6835
|
generate,
|
|
4641
6836
|
getKnowledgeGateway,
|
|
6837
|
+
initSlideGenerator,
|
|
4642
6838
|
validate
|
|
4643
6839
|
});
|
|
4644
6840
|
/**
|