@thanh01.pmt/curriculum-kit 1.2.1 → 1.3.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.
@@ -13,6 +13,7 @@ interface SingleArtifactRequest {
13
13
  studentCount?: string;
14
14
  language?: string;
15
15
  sectionLanguageContract?: Record<string, Record<string, string>> | string;
16
+ slidesEngine?: 'html' | 'marp';
16
17
  failClosedContext?: boolean;
17
18
  extraContext?: string;
18
19
  config?: Record<string, any>;
@@ -13,6 +13,7 @@ interface SingleArtifactRequest {
13
13
  studentCount?: string;
14
14
  language?: string;
15
15
  sectionLanguageContract?: Record<string, Record<string, string>> | string;
16
+ slidesEngine?: 'html' | 'marp';
16
17
  failClosedContext?: boolean;
17
18
  extraContext?: string;
18
19
  config?: Record<string, any>;
package/dist/index.cjs CHANGED
@@ -5432,6 +5432,54 @@ ${input.languageDirective}
5432
5432
 
5433
5433
  ${input.headingDirective}`.trim();
5434
5434
  }
5435
+ function buildHtmlDeckSlidePrompt(input) {
5436
+ const contract = input.customContract || `## HTML SLIDE ENGINE \u2014 AGENT GENERATION CONTRACT
5437
+ You generate structured slide decks for the LearnWell HTML Slide Presentation Engine.
5438
+ Your output MUST be a single, valid JSON object matching the Slide Deck JSON schema below.
5439
+
5440
+ ### \u{1F3A8} Available Layout Presets (Use layoutId):
5441
+ 1. **hero-cover**: Title/Cover slide with title, subtitle, presenter, date, tag.
5442
+ 2. **two-column-split**: Side-by-side comparison (col1Title, col1Items[], col2Title, col2Items[]).
5443
+ 3. **code-explainer**: Code walkthrough with filename, language, code, highlightLines, annotations[].
5444
+ 4. **metrics-3-card**: 3 key stat cards (card1Title, card1Val, card1Desc, card2..., card3...).
5445
+ 5. **process-flow**: Step-by-step pipeline (steps: [{ label, desc, status }]).
5446
+ 6. **quiz-checkpoint**: Quick multiple choice question (question, options[], answerIdx, explanation).
5447
+ 7. **tiered-practice-3cards**: Differentiated tiers (foundation, standard, challenge cards).
5448
+ 8. **takeaways-summary**: Summary review with bullet takeaways.
5449
+
5450
+ ### \u{1F399}\uFE0F Mandatory 3-Part Presenter Notes on EVERY slide:
5451
+ Every slide MUST include notes with:
5452
+ - Talk Track: 2-3 sentences speaking script
5453
+ - Cold-Call: 1 check question
5454
+ - Scaffolding Tip: 1 analogy or hint
5455
+
5456
+ ### Output JSON Format:
5457
+ \`\`\`json
5458
+ {
5459
+ "title": "${input.lessonTitle}",
5460
+ "theme": "ocean",
5461
+ "slides": [
5462
+ {
5463
+ "layoutId": "hero-cover",
5464
+ "slots": { "title": "${input.lessonTitle}", "subtitle": "..." },
5465
+ "notes": "Talk Track: ...\\nCold-Call: ...\\nScaffolding Tip: ..."
5466
+ }
5467
+ ]
5468
+ }
5469
+ \`\`\``;
5470
+ return `You are @illustrator & @content (Interactive HTML Slide Deck Presentation Architects).
5471
+ Based on Master Lesson Plan \`LESSON_${input.lessonCode}.md\`, pedagogical model (${input.pedagogyLabel}), and the HTML SLIDE ENGINE CONTRACT below, author the complete, highly engaging HTML presentation deck in JSON format: \`SLIDE_${input.lessonCode}\`.
5472
+
5473
+ ${contract}
5474
+
5475
+ ${input.languageDirective}
5476
+
5477
+ ${input.headingDirective}${input.glossaryBlock ? `
5478
+
5479
+ ${input.glossaryBlock}` : ""}
5480
+
5481
+ Output ONLY the raw JSON object. Do not include extra conversational text or markdown code fences.`.trim();
5482
+ }
5435
5483
 
5436
5484
  // src/ai/prompts/teacherGuidePrompt.ts
5437
5485
  function buildTeacherGuidePrompt(input) {
@@ -12462,46 +12510,175 @@ ${buildHeadingDirective("QUIZ", slcMarkdown, targetLang)}`;
12462
12510
  const slideRelPath = `_content/${unitCode}/SLIDE_${lessonCode}.md`;
12463
12511
  if (artifactScope.includes("SLIDE") && (!await storage.readArtifact(projectId, slideRelPath) || force)) {
12464
12512
  onProgress?.("@illustrator", `[4/4] Authoring Presentation Slide Deck: \`SLIDE_${lessonCode}.md\`...`);
12465
- const canonicalSlideTemplate = loadCurriculumTemplate("SLIDE_template.md");
12466
- const templateScaffold = canonicalSlideTemplate || `---
12513
+ const isHtmlEngine = options.slidesEngine === "html";
12514
+ if (isHtmlEngine) {
12515
+ let presentationKitAi = null;
12516
+ try {
12517
+ presentationKitAi = await import('@thanh01.pmt/presentation-kit/ai');
12518
+ } catch {
12519
+ }
12520
+ const slidePrompt = buildHtmlDeckSlidePrompt({
12521
+ lessonCode,
12522
+ lessonTitle,
12523
+ pedagogyLabel,
12524
+ languageDirective,
12525
+ headingDirective: buildHeadingDirective("SLIDE", slcMarkdown, targetLang),
12526
+ glossaryBlock: glossaryContext ? `[GLOSSARY TERMS (use these exact definitions)]:
12527
+ ${glossaryContext}` : void 0,
12528
+ customContract: presentationKitAi?.SLIDE_HTML_PROMPT_CONTRACT
12529
+ });
12530
+ const rawSlide = await runCurriculumAIInference(
12531
+ [{ role: "user", content: slidePrompt }],
12532
+ satelliteContext,
12533
+ {},
12534
+ (chunk, type) => {
12535
+ options.onProgress?.("@illustrator", chunk, { type: type || "content", artifactType: "SLIDE" });
12536
+ }
12537
+ );
12538
+ if (!rawSlide || rawSlide.startsWith("\u26A0\uFE0F") || rawSlide.length < 200) {
12539
+ throw createAiInferenceError({
12540
+ errorCode: "ERR_AI_RESPONSE_MALFORMED",
12541
+ agent: "@illustrator",
12542
+ artifactType: "SLIDE",
12543
+ lessonId: lessonCode,
12544
+ message: `Agent @illustrator failed to author valid HTML Slides for ${lessonCode}.`,
12545
+ rawError: rawSlide || "Empty AI response"
12546
+ });
12547
+ }
12548
+ let deckJson = null;
12549
+ const extractCleanJson = (str) => {
12550
+ const fenceMatch = str.match(/```(?:json)?\s*([\s\S]*?)\s*```/i);
12551
+ const candidate = fenceMatch ? fenceMatch[1] : str;
12552
+ return candidate.trim();
12553
+ };
12554
+ try {
12555
+ deckJson = JSON.parse(extractCleanJson(rawSlide));
12556
+ } catch {
12557
+ try {
12558
+ const { jsonrepair: jsonrepair2 } = await import('jsonrepair');
12559
+ deckJson = JSON.parse(jsonrepair2(extractCleanJson(rawSlide)));
12560
+ } catch {
12561
+ }
12562
+ }
12563
+ let validation = presentationKitAi?.validateHtmlSlideDeck ? presentationKitAi.validateHtmlSlideDeck(deckJson, true) : deckJson && Array.isArray(deckJson.slides) ? { valid: true, score: 95, issues: [], slideCount: deckJson.slides.length, notesCoveragePercent: 100 } : { valid: false, score: 0, issues: [{ message: "Malformed JSON slide deck" }], slideCount: 0, notesCoveragePercent: 0 };
12564
+ if (!validation.valid && presentationKitAi?.validateHtmlSlideDeck) {
12565
+ const repairIssues = (validation.issues || []).map((i) => `- Slide ${i.slideIndex ?? "?"}: ${i.message}`).join("\n");
12566
+ onProgress?.("@illustrator", `\u26A0\uFE0F HTML slide deck failed schema validation \u2014 retrying once with targeted repair feedback...`);
12567
+ const repairPrompt = `Your previous slide deck failed strict schema validation:
12568
+ ${repairIssues}
12569
+
12570
+ Fix ALL issues and output the complete corrected JSON object strictly matching the schema:`;
12571
+ const repairedRaw = await runCurriculumAIInference(
12572
+ [{ role: "user", content: repairPrompt }],
12573
+ satelliteContext,
12574
+ {},
12575
+ (chunk, type) => {
12576
+ options.onProgress?.("@illustrator", chunk, { type: type || "content", artifactType: "SLIDE" });
12577
+ }
12578
+ );
12579
+ try {
12580
+ deckJson = JSON.parse(extractCleanJson(repairedRaw));
12581
+ validation = presentationKitAi.validateHtmlSlideDeck(deckJson, true);
12582
+ } catch {
12583
+ try {
12584
+ const { jsonrepair: jsonrepair2 } = await import('jsonrepair');
12585
+ deckJson = JSON.parse(jsonrepair2(extractCleanJson(repairedRaw)));
12586
+ validation = presentationKitAi.validateHtmlSlideDeck(deckJson, true);
12587
+ } catch {
12588
+ }
12589
+ }
12590
+ }
12591
+ const markdownWrapper = `---
12592
+ id: "SLIDE_${lessonCode}"
12593
+ title: "${titleHeader(lessonTitle)}"
12594
+ type: "SLIDE"
12595
+ format: "html-deck"
12596
+ engine: "html"
12597
+ phase: "P2"
12598
+ deliverable: "P2-T09"
12599
+ version: "v1.0"
12600
+ date: "${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}"
12601
+ ---
12602
+
12603
+ \`\`\`json
12604
+ ${JSON.stringify(deckJson || {}, null, 2)}
12605
+ \`\`\`
12606
+ `;
12607
+ await storage.saveArtifact(projectId, slideRelPath, markdownWrapper);
12608
+ producedArtifacts.push(`SLIDE_${lessonCode}.md`);
12609
+ if (deckJson) {
12610
+ const deckJsonStr = JSON.stringify(deckJson, null, 2);
12611
+ await storage.saveArtifact(projectId, `_content/${unitCode}/SLIDE_${lessonCode}.deck.json`, deckJsonStr);
12612
+ producedArtifacts.push(`SLIDE_${lessonCode}.deck.json`);
12613
+ if (presentationKitAi?.compileHtmlDeck) {
12614
+ try {
12615
+ const compiled = presentationKitAi.compileHtmlDeck(deckJson);
12616
+ if (compiled?.html) {
12617
+ await storage.saveArtifact(projectId, `_content/${unitCode}/SLIDE_${lessonCode}.html`, compiled.html);
12618
+ producedArtifacts.push(`SLIDE_${lessonCode}.html`);
12619
+ }
12620
+ } catch (compErr) {
12621
+ console.warn(`[produceSingleLesson] Failed compiling HTML deck for ${lessonCode}:`, compErr);
12622
+ }
12623
+ }
12624
+ }
12625
+ const passed = Boolean(validation.valid && (validation.score ?? 100) >= 80);
12626
+ const score = validation.score ?? (passed ? 95 : 50);
12627
+ await storage.updateArtifactState(projectId, lessonCode, "SLIDE", {
12628
+ state: passed ? "approved" : "rejected",
12629
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
12630
+ contentHash: computeContentHash(markdownWrapper),
12631
+ review: {
12632
+ decision: passed ? "APPROVED" : "NEEDS_REVISION",
12633
+ reviewedBy: "@agent-as-judge",
12634
+ reviewedAt: (/* @__PURE__ */ new Date()).toISOString(),
12635
+ score,
12636
+ critique: passed ? `HTML Slide Deck passed strict schema validation (${score}/100, ${validation.slideCount ?? deckJson?.slides?.length ?? 0} slides). Auto-approved.` : `HTML Slide Deck failed schema validation: ${(validation.issues || []).map((i) => i.message).join("; ")}`
12637
+ }
12638
+ });
12639
+ onProgress?.("@reviewer", passed ? `\u2705 SLIDE judge PASS (${score}/100) [html-deck schema validated]` : `\u26A0\uFE0F SLIDE judge NEEDS_REVISION (${score}/100) [html-deck schema issues]`);
12640
+ } else {
12641
+ const canonicalSlideTemplate = loadCurriculumTemplate("SLIDE_template.md");
12642
+ const templateScaffold = canonicalSlideTemplate || `---
12467
12643
  marp: true
12468
12644
  theme: default
12469
12645
  paginate: true
12470
12646
  header: "${titleHeader(lessonTitle)}"
12471
12647
  footer: "Curriculum OS \u2022 ${lessonCode} (${pedagogyLabel})"
12472
12648
  ---`;
12473
- const slidePrompt = buildMarpMarkdownSlidePrompt({
12474
- lessonCode,
12475
- templateScaffold,
12476
- languageDirective,
12477
- headingDirective: buildHeadingDirective("SLIDE", slcMarkdown, targetLang)
12478
- });
12479
- const rawSlide = await runCurriculumAIInference(
12480
- [{ role: "user", content: slidePrompt }],
12481
- satelliteContext,
12482
- {},
12483
- (chunk, type) => {
12484
- options.onProgress?.("@illustrator", chunk, { type: type || "content", artifactType: "SLIDE" });
12649
+ const slidePrompt = buildMarpMarkdownSlidePrompt({
12650
+ lessonCode,
12651
+ templateScaffold,
12652
+ languageDirective,
12653
+ headingDirective: buildHeadingDirective("SLIDE", slcMarkdown, targetLang)
12654
+ });
12655
+ const rawSlide = await runCurriculumAIInference(
12656
+ [{ role: "user", content: slidePrompt }],
12657
+ satelliteContext,
12658
+ {},
12659
+ (chunk, type) => {
12660
+ options.onProgress?.("@illustrator", chunk, { type: type || "content", artifactType: "SLIDE" });
12661
+ }
12662
+ );
12663
+ if (!rawSlide || rawSlide.startsWith("\u26A0\uFE0F") || rawSlide.length < 200) {
12664
+ throw createAiInferenceError({
12665
+ errorCode: "ERR_AI_RESPONSE_MALFORMED",
12666
+ agent: "@illustrator",
12667
+ artifactType: "SLIDE",
12668
+ lessonId: lessonCode,
12669
+ message: `Agent @illustrator failed to author valid Slides for ${lessonCode}.`,
12670
+ rawError: rawSlide || "Empty AI response"
12671
+ });
12485
12672
  }
12486
- );
12487
- if (!rawSlide || rawSlide.startsWith("\u26A0\uFE0F") || rawSlide.length < 200) {
12488
- throw createAiInferenceError({
12489
- errorCode: "ERR_AI_RESPONSE_MALFORMED",
12490
- agent: "@illustrator",
12491
- artifactType: "SLIDE",
12492
- lessonId: lessonCode,
12493
- message: `Agent @illustrator failed to author valid Slides for ${lessonCode}.`,
12494
- rawError: rawSlide || "Empty AI response"
12673
+ await storage.saveArtifact(projectId, slideRelPath, rawSlide);
12674
+ await storage.updateArtifactState(projectId, lessonCode, "SLIDE", {
12675
+ state: "completed",
12676
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
12677
+ contentHash: computeContentHash(rawSlide)
12495
12678
  });
12679
+ producedArtifacts.push(`SLIDE_${lessonCode}.md`);
12680
+ await judgeSat("SLIDE", rawSlide);
12496
12681
  }
12497
- await storage.saveArtifact(projectId, slideRelPath, rawSlide);
12498
- await storage.updateArtifactState(projectId, lessonCode, "SLIDE", {
12499
- state: "completed",
12500
- generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
12501
- contentHash: computeContentHash(rawSlide)
12502
- });
12503
- producedArtifacts.push(`SLIDE_${lessonCode}.md`);
12504
- await judgeSat("SLIDE", rawSlide);
12505
12682
  }
12506
12683
  const guideRelPath = `_content/${unitCode}/GUIDE_${lessonCode}.md`;
12507
12684
  if (artifactScope.includes("GUIDE") && (!await storage.readArtifact(projectId, guideRelPath) || force)) {
@@ -13444,6 +13621,24 @@ function getArtifactSpecializedInvariants(type, req) {
13444
13621
  const studentCount = req.studentCount || "25";
13445
13622
  switch (type) {
13446
13623
  case "slide": {
13624
+ if (req.slidesEngine === "html") {
13625
+ return `
13626
+ ### \u{1F3AC} INTERACTIVE HTML SLIDE ENGINE CONTRACT:
13627
+ You generate structured slide decks for the LearnWell HTML Slide Presentation Engine.
13628
+ Output a single valid JSON object matching: { "title": "...", "theme": "ocean", "slides": [{ "layoutId": "hero-cover", "slots": { ... }, "notes": "..." }] }
13629
+
13630
+ Layout Presets available:
13631
+ - hero-cover (title, subtitle, presenter, date, tag)
13632
+ - two-column-split (col1Title, col1Items[], col2Title, col2Items[])
13633
+ - code-explainer (filename, language, code, highlightLines, annotations[])
13634
+ - metrics-3-card (card1Title, card1Val, card1Desc, ...)
13635
+ - process-flow (steps: [{ label, desc, status }])
13636
+ - quiz-checkpoint (question, options[], answerIdx, explanation)
13637
+ - tiered-practice-3cards (foundation, standard, challenge cards)
13638
+ - takeaways-summary (takeaways: bullet points)
13639
+
13640
+ Mandatory Presenter Notes on every slide with Talk Track, Cold-Call, and Scaffolding Tip.`;
13641
+ }
13447
13642
  const slideCount = cfg.slideCount || 10;
13448
13643
  const presentationStyle = cfg.presentationStyle || "modern";
13449
13644
  const presentationDuration = cfg.presentationDuration || 45;
@@ -17524,6 +17719,7 @@ exports.buildExpositionExcerpt = buildExpositionExcerpt;
17524
17719
  exports.buildExtensionPrompt = buildExtensionPrompt;
17525
17720
  exports.buildHandoutPrompt = buildHandoutPrompt;
17526
17721
  exports.buildHeadingDirective = buildHeadingDirective;
17722
+ exports.buildHtmlDeckSlidePrompt = buildHtmlDeckSlidePrompt;
17527
17723
  exports.buildImagePrompt = buildImagePrompt;
17528
17724
  exports.buildJudgePrompt = buildJudgePrompt;
17529
17725
  exports.buildLanguageDirective = buildLanguageDirective;