@thanh01.pmt/curriculum-kit 1.4.11 → 1.4.13

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/dist/index.cjs CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  var fs2 = require('fs');
4
4
  var path3 = require('path');
5
+ var jsonrepair = require('jsonrepair');
5
6
  var zod = require('zod');
6
7
  var google = require('@ai-sdk/google');
7
8
  var openai = require('@ai-sdk/openai');
@@ -12,7 +13,6 @@ var fsPromises = require('fs/promises');
12
13
  var crypto = require('crypto');
13
14
  var supabaseJs = require('@supabase/supabase-js');
14
15
  var url = require('url');
15
- var jsonrepair = require('jsonrepair');
16
16
  var rest = require('@octokit/rest');
17
17
 
18
18
  var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
@@ -1174,6 +1174,493 @@ var init_artifactLinter = __esm({
1174
1174
  "src/pipeline/artifactLinter.ts"() {
1175
1175
  }
1176
1176
  });
1177
+
1178
+ // src/parsers/lessonFlowParser.ts
1179
+ function parseLessonFlow(lessonMarkdown) {
1180
+ const result = {
1181
+ lessonId: "",
1182
+ lessonTitle: "",
1183
+ pedagogicalModel: "5e",
1184
+ estimatedDuration: "90 mins",
1185
+ activities: [],
1186
+ phases: [],
1187
+ rawContent: lessonMarkdown
1188
+ };
1189
+ if (!lessonMarkdown || typeof lessonMarkdown !== "string") {
1190
+ return result;
1191
+ }
1192
+ const fmMatch = lessonMarkdown.match(/^---\s*\n([\s\S]*?)\n---/);
1193
+ if (fmMatch) {
1194
+ const fm = fmMatch[1];
1195
+ const idMatch = fm.match(/id:\s*["']?([^"'\n]+)["']?/i);
1196
+ if (idMatch) result.lessonId = idMatch[1].trim();
1197
+ const titleMatch = fm.match(/title:\s*["']?([^"'\n]+)["']?/i);
1198
+ if (titleMatch) result.lessonTitle = titleMatch[1].trim();
1199
+ const typeMatch = fm.match(/type:\s*["']?([^"'\n]+)["']?/i);
1200
+ if (typeMatch) {
1201
+ const t = typeMatch[1].toLowerCase();
1202
+ if (t.includes("edp")) result.pedagogicalModel = "edp";
1203
+ else if (t.includes("pbl")) result.pedagogicalModel = "pbl";
1204
+ else if (t.includes("cra")) result.pedagogicalModel = "cra";
1205
+ else if (t.includes("5e")) result.pedagogicalModel = "5e";
1206
+ else result.pedagogicalModel = typeMatch[1].trim();
1207
+ }
1208
+ }
1209
+ const durMatch = lessonMarkdown.match(/Estimated Duration:\s*([^\n]+)/i);
1210
+ if (durMatch) result.estimatedDuration = durMatch[1].trim();
1211
+ const contractMatch = lessonMarkdown.match(/SLIDE must visualize:?\s*([^\n]+)/i);
1212
+ if (contractMatch) result.slideContract = contractMatch[1].trim();
1213
+ const actSeqSectionMatch = lessonMarkdown.match(/###\s*(?:\d+\.\s*)?Activity Sequence[\s\S]*?(?=(?:###|\n##\s+|$))/i);
1214
+ if (actSeqSectionMatch) {
1215
+ const tableText = actSeqSectionMatch[0];
1216
+ const lines = tableText.split("\n");
1217
+ let inTable = false;
1218
+ let headerParsed = false;
1219
+ for (const line of lines) {
1220
+ const trimmed = line.trim();
1221
+ if (trimmed.startsWith("|") && trimmed.endsWith("|")) {
1222
+ if (!inTable) {
1223
+ inTable = true;
1224
+ continue;
1225
+ }
1226
+ if (trimmed.includes("---")) {
1227
+ headerParsed = true;
1228
+ continue;
1229
+ }
1230
+ if (headerParsed) {
1231
+ const cells = trimmed.split("|").map((c) => c.trim()).filter((_, idx, arr) => idx > 0 && idx < arr.length - 1);
1232
+ if (cells.length >= 7) {
1233
+ const seqNum = parseInt(cells[0], 10) || result.activities.length + 1;
1234
+ result.activities.push({
1235
+ seq: seqNum,
1236
+ phase: cells[1] || `Phase ${seqNum}`,
1237
+ activityType: cells[2] || "",
1238
+ actor: cells[3] || "",
1239
+ purpose: cells[4] || "",
1240
+ studentAction: cells[5] || "",
1241
+ teacherMove: cells[6] || "",
1242
+ time: cells[9] || cells[cells.length - 2] || "",
1243
+ artifactContract: cells[cells.length - 1] || ""
1244
+ });
1245
+ }
1246
+ }
1247
+ } else if (inTable && trimmed.length > 0 && !trimmed.startsWith(">")) {
1248
+ inTable = false;
1249
+ }
1250
+ }
1251
+ }
1252
+ const flowMatch = lessonMarkdown.match(/##\s*\[?REQUIRED\]?\s*B\.\s*Lesson Flow([\s\S]*?)$/i);
1253
+ const flowSection = flowMatch ? flowMatch[1] : lessonMarkdown;
1254
+ const phaseRegex = /###\s*(?:(\d+)\.\s*)?([^\n—\-]+)(?:[—\-]\s*\[?([^\]\n]+)\]?)?\s*\n([\s\S]*?)(?=(?:###\s*(?:\d+\.)?|$))/gi;
1255
+ let pMatch;
1256
+ while ((pMatch = phaseRegex.exec(flowSection)) !== null) {
1257
+ const rawPhaseName = pMatch[2].trim();
1258
+ const timing = pMatch[3]?.trim();
1259
+ const content = pMatch[4].trim();
1260
+ const codeSnippets = [];
1261
+ const codeRegex = /```(?:[a-zA-Z0-9_\-]+)?\s*\n([\s\S]*?)```/g;
1262
+ let cMatch;
1263
+ while ((cMatch = codeRegex.exec(content)) !== null) {
1264
+ if (!cMatch[1].includes("graph TD") && !cMatch[1].includes("graph LR")) {
1265
+ codeSnippets.push(cMatch[1].trim());
1266
+ }
1267
+ }
1268
+ const mermaidDiagrams = [];
1269
+ const mermaidRegex = /```mermaid\s*\n([\s\S]*?)```/g;
1270
+ let mMatch;
1271
+ while ((mMatch = mermaidRegex.exec(content)) !== null) {
1272
+ mermaidDiagrams.push(mMatch[1].trim());
1273
+ }
1274
+ result.phases.push({
1275
+ phaseName: rawPhaseName,
1276
+ title: rawPhaseName,
1277
+ timing,
1278
+ content,
1279
+ codeSnippets,
1280
+ mermaidDiagrams
1281
+ });
1282
+ }
1283
+ return result;
1284
+ }
1285
+ var init_lessonFlowParser = __esm({
1286
+ "src/parsers/lessonFlowParser.ts"() {
1287
+ }
1288
+ });
1289
+
1290
+ // src/ai/prompts/slideBlueprintPrompt.ts
1291
+ function buildSlideBlueprintPrompt(params) {
1292
+ const { lessonFlow, targetSlideCount, stylePresetName = "Blue Professional" } = params;
1293
+ const activitiesSummary = lessonFlow.activities.map(
1294
+ (a) => `Seq ${a.seq} [${a.phase}]: ${a.purpose} (Teacher: ${a.teacherMove} | Student: ${a.studentAction} | Time: ${a.time})`
1295
+ ).join("\n");
1296
+ const phasesSummary = lessonFlow.phases.map(
1297
+ (p) => `Phase "${p.phaseName}" (${p.timing || "Standard"}):
1298
+ ${p.content.slice(0, 500)}...`
1299
+ ).join("\n\n");
1300
+ const countGuidance = targetSlideCount ? `The target is approximately ${targetSlideCount} slides.` : `Determine the organic, optimal number of slides (typically 12 to 25 slides) based directly on the number of activities, instructional moves, and depth of content in the LESSON plan.`;
1301
+ return `
1302
+ You are @illustrator, Chief Slide Architect for the Course Builder OS.
1303
+ Your task is to analyze the canonical LESSON plan and architect an authoritative, high-fidelity **SLIDE BLUEPRINT**.
1304
+ ${countGuidance}
1305
+
1306
+ ### \u{1F3DB}\uFE0F PEDAGOGICAL GROUND TRUTH (FROM CANONICAL LESSON):
1307
+ - Lesson Title: "${lessonFlow.lessonTitle}"
1308
+ - Pedagogical Framework: "${lessonFlow.pedagogicalModel.toUpperCase()}"
1309
+ - Target Duration: ${lessonFlow.estimatedDuration}
1310
+ - Active Visual Style: "${stylePresetName}"
1311
+ ${lessonFlow.slideContract ? `- Slide Artifact Contract: "${lessonFlow.slideContract}"` : ""}
1312
+
1313
+ ### \u{1F5FA}\uFE0F CANONICAL ACTIVITY SEQUENCE:
1314
+ ${activitiesSummary || "Standard phased sequence"}
1315
+
1316
+ ### \u{1F4D6} LESSON FLOW EXCERPT:
1317
+ ${phasesSummary || lessonFlow.rawContent.slice(0, 3e3)}
1318
+
1319
+ ---
1320
+
1321
+ ### \u{1F3AF} ARCHITECTURAL RULES (STRICTLY INVARIANT):
1322
+ 1. **ORGANIC PEDAGOGICAL FIDELITY (1-TO-1 PROJECTION)**:
1323
+ - The slide sequence MUST follow the EXACT flow of the LESSON phases (${lessonFlow.pedagogicalModel.toUpperCase()}).
1324
+ - The number of slides is determined by the substance of the lesson \u2014 do NOT stretch thin content or cram rich multi-step labs into too few slides.
1325
+ - Do NOT invent artificial phases that do not exist in the lesson plan.
1326
+ - For example:
1327
+ * If 5E: Engage -> Explore -> Explain -> Elaborate -> Evaluate.
1328
+ * If EDP: Ask/Problem -> Imagine & Plan -> Create & Prototype -> Test & Debug -> Improve & Share.
1329
+ * If PBL: Driving Question -> Inquiry -> Milestone Sprint -> Review & Exhibition.
1330
+
1331
+ 2. **CHUNKY CLUSTERS (4 TO 6 SLIDES PER CLUSTER)**:
1332
+ - Partition the slides into natural clusters corresponding to the major pedagogical transitions in the lesson.
1333
+ - Each cluster MUST contain 4 to 6 slides to ensure downstream token generation remains focused, high-fidelity, and non-repetitive.
1334
+
1335
+ 3. **SEMANTIC LAYOUT SLOTS (CHOOSE FROM VERIFIED TEMPLATES)**:
1336
+ Use one of these valid layoutIds for each slide:
1337
+ - \`hero-cover\`: Title, subtitle, topic badge, metadata.
1338
+ - \`split-concept-code\`: Left side explanation/bullets, right side prominent code card or diagram.
1339
+ - \`two-columns-compare\`: Side-by-side comparison (Before vs After, Scratch vs Swift, Theory vs Practice).
1340
+ - \`three-cards-grid\`: 3 architectural pillars, components, or principles.
1341
+ - \`timeline-steps\`: Step-by-step workflow, pipeline sequence, or execution trace.
1342
+ - \`metric-callout\`: High-impact statistical or performance highlight.
1343
+ - \`checkpoint-quiz\`: Formative diagnostic question with multiple choice.
1344
+ - \`tiered-practice-3cards\`: Bronze, Silver, Gold hands-on challenge specs.
1345
+ - \`summary-takeaways\`: Final recap, key rules, and next milestone.
1346
+
1347
+ ---
1348
+
1349
+ ### \u{1F4E4} OUTPUT FORMAT:
1350
+ Output ONLY a valid JSON array of ${targetSlideCount} objects. Do NOT include markdown text outside the JSON.
1351
+ Format:
1352
+ \`\`\`json
1353
+ [
1354
+ {
1355
+ "slideIndex": 1,
1356
+ "clusterId": 1,
1357
+ "clusterTitle": "Opening & Hook",
1358
+ "lessonPhase": "Engage",
1359
+ "layoutId": "hero-cover",
1360
+ "title": "Title of Slide 1",
1361
+ "pedagogicalGoal": "Specific learning goal of this slide",
1362
+ "contentFocus": ["Bullet 1 focus", "Bullet 2 focus"],
1363
+ "codeSnippetIntent": "Description of code snippet to show, if any",
1364
+ "visualIntent": "Description of visual diagram to display, if any"
1365
+ }
1366
+ ]
1367
+ \`\`\`
1368
+ `.trim();
1369
+ }
1370
+ var init_slideBlueprintPrompt = __esm({
1371
+ "src/ai/prompts/slideBlueprintPrompt.ts"() {
1372
+ }
1373
+ });
1374
+
1375
+ // src/ai/prompts/slideBatchPrompt.ts
1376
+ function buildSlideBatchPrompt(params) {
1377
+ const {
1378
+ clusterId,
1379
+ clusterTitle,
1380
+ clusterSlides,
1381
+ lessonFlow,
1382
+ lessonExcerpt,
1383
+ skillPrompt,
1384
+ language = "Vietnamese",
1385
+ languageDirective = "",
1386
+ headingDirective = ""
1387
+ } = params;
1388
+ const slidesSpec = clusterSlides.map((s) => `
1389
+ - Slide ${s.slideIndex} [Phase: ${s.lessonPhase}] -> Layout: "${s.layoutId}"
1390
+ Title: "${s.title}"
1391
+ Pedagogical Goal: ${s.pedagogicalGoal}
1392
+ Key Bullet Targets: ${s.contentFocus.join("; ")}
1393
+ ${s.codeSnippetIntent ? `Code Focus: ${s.codeSnippetIntent}` : ""}
1394
+ ${s.visualIntent ? `Visual Focus: ${s.visualIntent}` : ""}
1395
+ `).join("\n");
1396
+ const systemPrompt = `
1397
+ ${skillPrompt}
1398
+
1399
+ ---
1400
+ ### OPERATIONAL GROUND RULES FOR CHUNK GENERATION:
1401
+ ${languageDirective}
1402
+ ${headingDirective}
1403
+ 1. **FOCUS ON CLUSTER ${clusterId}: "${clusterTitle}"**:
1404
+ Generate EXACTLY the ${clusterSlides.length} slides requested in the user prompt.
1405
+ 2. **ZERO ABBREVIATION / NO "// TODO"**:
1406
+ All code snippets MUST be real, fully authored, compilable code relevant to ${lessonFlow.lessonTitle}. Never write placeholders, stubs, or "<CODE>".
1407
+ 3. **MANDATORY 3-PART PRESENTER NOTES ON EVERY SLIDE**:
1408
+ Every slide object MUST have a "notes" field formatted with:
1409
+ - SCRIPT: 60-90s spoken teacher talk track with an intuitive analogy.
1410
+ - COLD CALL / CHECK: One targeted question to check for student understanding.
1411
+ - SCAFFOLDING / GOTCHA: One common misconception or bug to watch out for.
1412
+ 4. **OUTPUT FORMAT**:
1413
+ Output ONLY a valid JSON array of ${clusterSlides.length} slide objects.
1414
+ `.trim();
1415
+ const userPrompt = `
1416
+ ### LESSON GROUND TRUTH:
1417
+ - Lesson Title: "${lessonFlow.lessonTitle}"
1418
+ - Target Duration: ${lessonFlow.estimatedDuration}
1419
+ - Pedagogy: "${lessonFlow.pedagogicalModel.toUpperCase()}"
1420
+ - Language: "${language}"
1421
+
1422
+ ### LESSON PHASE CONTENT (SOURCE OF TRUTH):
1423
+ ${lessonExcerpt || lessonFlow.rawContent.slice(0, 4e3)}
1424
+
1425
+ ---
1426
+
1427
+ ### REQUIRED SLIDES TO GENERATE FOR THIS CLUSTER:
1428
+ ${slidesSpec}
1429
+
1430
+ ---
1431
+
1432
+ ### OUTPUT SCHEMA:
1433
+ Output a single JSON array of slide objects strictly matching the layout slots:
1434
+ \`\`\`json
1435
+ [
1436
+ {
1437
+ "id": "slide-${clusterSlides[0]?.slideIndex || 1}",
1438
+ "layoutId": "${clusterSlides[0]?.layoutId || "split-concept-code"}",
1439
+ "title": "${clusterSlides[0]?.title || "Slide Title"}",
1440
+ "slots": {
1441
+ "lead": "One clear, punchy subtitle or thesis statement",
1442
+ "bullets": [
1443
+ "First key insight (8-12 words max)",
1444
+ "Second key insight (8-12 words max)",
1445
+ "Third key insight (8-12 words max)"
1446
+ ],
1447
+ "code": "// Executable code here",
1448
+ "codeLanguage": "swift",
1449
+ "codeHighlight": "1-3",
1450
+ "card1Title": "...",
1451
+ "card1Desc": "..."
1452
+ },
1453
+ "notes": "SCRIPT: ...\\nCOLD CALL: ...\\nSCAFFOLDING: ..."
1454
+ }
1455
+ ]
1456
+ \`\`\`
1457
+ `.trim();
1458
+ return { systemPrompt, userPrompt };
1459
+ }
1460
+ var init_slideBatchPrompt = __esm({
1461
+ "src/ai/prompts/slideBatchPrompt.ts"() {
1462
+ }
1463
+ });
1464
+
1465
+ // src/services/slideProductionWorkflow.ts
1466
+ var slideProductionWorkflow_exports = {};
1467
+ __export(slideProductionWorkflow_exports, {
1468
+ executeSlideProductionWorkflow: () => executeSlideProductionWorkflow
1469
+ });
1470
+ function extractCleanJson(raw) {
1471
+ const fenceMatch = raw.match(/```(?:json)?\s*([\s\S]*?)\s*```/i);
1472
+ const candidate = fenceMatch ? fenceMatch[1] : raw;
1473
+ return candidate.trim();
1474
+ }
1475
+ async function executeSlideProductionWorkflow(options) {
1476
+ const {
1477
+ lessonMarkdown,
1478
+ lessonCode,
1479
+ lessonTitle,
1480
+ targetSlideCount,
1481
+ stylePresetId = "blue-professional",
1482
+ language = "Vietnamese",
1483
+ languageDirective = "",
1484
+ headingDirective = "",
1485
+ satelliteContext,
1486
+ runnerOptions,
1487
+ onProgress
1488
+ } = options;
1489
+ let presentationKitSkills = null;
1490
+ let presentationKitCore = null;
1491
+ try {
1492
+ presentationKitSkills = await import('@thanh01.pmt/presentation-kit/skills');
1493
+ } catch {
1494
+ }
1495
+ try {
1496
+ presentationKitCore = await import('@thanh01.pmt/presentation-kit');
1497
+ } catch {
1498
+ }
1499
+ const countLabel = targetSlideCount ? `${targetSlideCount} ` : "h\u1EEFu c\u01A1 ";
1500
+ onProgress?.("@illustrator", `[1/4] Ph\xE2n t\xEDch m\u1EA1ch b\xE0i gi\u1EA3ng LESSON & L\u1EADp D\xE0n \xFD ${countLabel}Slides...`);
1501
+ const lessonFlow = parseLessonFlow(lessonMarkdown);
1502
+ const stylePreset = presentationKitSkills?.getStylePreset ? presentationKitSkills.getStylePreset(stylePresetId) : { name: "Blue Professional" };
1503
+ const blueprintPrompt = buildSlideBlueprintPrompt({
1504
+ lessonFlow,
1505
+ targetSlideCount,
1506
+ stylePresetName: stylePreset?.name || "Blue Professional"
1507
+ });
1508
+ const rawBlueprint = await runCurriculumAIInference(
1509
+ [{ role: "user", content: blueprintPrompt }],
1510
+ satelliteContext,
1511
+ runnerOptions,
1512
+ (chunk, type) => {
1513
+ onProgress?.("@illustrator", chunk, { type: type || "content", artifactType: "SLIDE" });
1514
+ }
1515
+ );
1516
+ let blueprintItems = [];
1517
+ try {
1518
+ blueprintItems = JSON.parse(extractCleanJson(rawBlueprint));
1519
+ } catch {
1520
+ try {
1521
+ blueprintItems = JSON.parse(jsonrepair.jsonrepair(extractCleanJson(rawBlueprint)));
1522
+ } catch (e) {
1523
+ console.warn(`[SlideProductionWorkflow] Failed parsing blueprint JSON, constructing fallback:`, e);
1524
+ const fallbackCount = targetSlideCount || Math.max(12, Math.min(24, Math.max(lessonFlow.phases.length, 4) * 4));
1525
+ blueprintItems = Array.from({ length: fallbackCount }, (_, i) => ({
1526
+ slideIndex: i + 1,
1527
+ clusterId: Math.floor(i / 5) + 1,
1528
+ clusterTitle: `Cluster ${Math.floor(i / 5) + 1}`,
1529
+ lessonPhase: lessonFlow.phases[Math.min(i, lessonFlow.phases.length - 1)]?.phaseName || "Content",
1530
+ layoutId: i === 0 ? "hero-cover" : i === fallbackCount - 1 ? "summary-takeaways" : "split-concept-code",
1531
+ title: `Slide ${i + 1}: ${lessonTitle}`,
1532
+ pedagogicalGoal: `Teach step ${i + 1} of ${lessonTitle}`,
1533
+ contentFocus: ["Key point 1", "Key point 2", "Key point 3"]
1534
+ }));
1535
+ }
1536
+ }
1537
+ const clustersMap = /* @__PURE__ */ new Map();
1538
+ for (const item of blueprintItems) {
1539
+ const cId = item.clusterId || Math.floor((item.slideIndex - 1) / 5) + 1;
1540
+ if (!clustersMap.has(cId)) clustersMap.set(cId, []);
1541
+ clustersMap.get(cId).push(item);
1542
+ }
1543
+ const clusters = Array.from(clustersMap.entries()).sort(([a], [b]) => a - b);
1544
+ const allGeneratedSlides = [];
1545
+ const skillPrompt = presentationKitSkills?.getFrontendSlidesSkillPrompt ? presentationKitSkills.getFrontendSlidesSkillPrompt({ stylePresetId }) : "";
1546
+ let clusterIdx = 0;
1547
+ for (const [cId, clusterSlides] of clusters) {
1548
+ clusterIdx++;
1549
+ const clusterTitle = clusterSlides[0]?.clusterTitle || `Giai \u0111o\u1EA1n ${clusterIdx}`;
1550
+ onProgress?.("@illustrator", `[2/4] Sinh chi ti\u1EBFt C\u1EE5m ${clusterIdx}/${clusters.length}: "${clusterTitle}" (${clusterSlides.length} slides)...`);
1551
+ const clusterPhaseNames = new Set(clusterSlides.map((s) => s.lessonPhase.toLowerCase()));
1552
+ const matchingPhases = lessonFlow.phases.filter(
1553
+ (p) => clusterPhaseNames.has(p.phaseName.toLowerCase())
1554
+ );
1555
+ const lessonExcerpt = matchingPhases.length > 0 ? matchingPhases.map((p) => `### ${p.phaseName}
1556
+ ${p.content}`).join("\n\n") : lessonFlow.rawContent.slice(0, 3500);
1557
+ const { systemPrompt, userPrompt } = buildSlideBatchPrompt({
1558
+ clusterId: cId,
1559
+ clusterTitle,
1560
+ clusterSlides,
1561
+ lessonFlow,
1562
+ lessonExcerpt,
1563
+ skillPrompt,
1564
+ language,
1565
+ languageDirective,
1566
+ headingDirective
1567
+ });
1568
+ const rawBatch = await runCurriculumAIInference(
1569
+ [
1570
+ { role: "system", content: systemPrompt },
1571
+ { role: "user", content: userPrompt }
1572
+ ],
1573
+ satelliteContext,
1574
+ runnerOptions,
1575
+ (chunk, type) => {
1576
+ onProgress?.("@illustrator", chunk, { type: type || "content", artifactType: "SLIDE" });
1577
+ }
1578
+ );
1579
+ let batchSlides = [];
1580
+ try {
1581
+ batchSlides = JSON.parse(extractCleanJson(rawBatch));
1582
+ } catch {
1583
+ try {
1584
+ batchSlides = JSON.parse(jsonrepair.jsonrepair(extractCleanJson(rawBatch)));
1585
+ } catch (err) {
1586
+ console.warn(`[SlideProductionWorkflow] Batch ${cId} JSON parse failed, synthesizing slides:`, err);
1587
+ batchSlides = clusterSlides.map((s) => ({
1588
+ id: `slide-${s.slideIndex}`,
1589
+ layoutId: s.layoutId,
1590
+ title: s.title,
1591
+ slots: {
1592
+ lead: s.pedagogicalGoal,
1593
+ bullets: s.contentFocus,
1594
+ code: s.codeSnippetIntent || "// Executable demo code"
1595
+ },
1596
+ notes: `SCRIPT: Spoken explanation for ${s.title}.
1597
+ COLD CALL: What happens when this logic runs?
1598
+ SCAFFOLDING: Ensure proper syntax and indentation.`
1599
+ }));
1600
+ }
1601
+ }
1602
+ if (Array.isArray(batchSlides)) {
1603
+ allGeneratedSlides.push(...batchSlides);
1604
+ }
1605
+ }
1606
+ onProgress?.("@illustrator", `[3/4] Chu\u1EA9n h\xF3a b\u1ED1 c\u1EE5c v\xE0 bi\xEAn d\u1ECBch 1920\xD71080 Stage Deck (${allGeneratedSlides.length} slides)...`);
1607
+ const normalizer = presentationKitCore?.normalizeSlideSlots;
1608
+ const normalizedSlides = allGeneratedSlides.map((s, idx) => {
1609
+ const base = normalizer ? normalizer(s) : s;
1610
+ if (!base.id) base.id = `slide-${idx + 1}`;
1611
+ return base;
1612
+ });
1613
+ const deckJson = {
1614
+ id: `deck-${lessonCode}`,
1615
+ title: lessonTitle,
1616
+ theme: stylePresetId,
1617
+ slides: normalizedSlides
1618
+ };
1619
+ let compiledHtml;
1620
+ const compiler = presentationKitCore?.compileHtmlDeck;
1621
+ if (compiler) {
1622
+ try {
1623
+ const compiled = compiler(deckJson);
1624
+ if (compiled?.html) {
1625
+ compiledHtml = compiled.html;
1626
+ }
1627
+ } catch (compErr) {
1628
+ console.warn(`[SlideProductionWorkflow] Failed compiling HTML deck:`, compErr);
1629
+ }
1630
+ }
1631
+ const markdownWrapper = `---
1632
+ id: "SLIDE_${lessonCode}"
1633
+ title: "${lessonTitle}"
1634
+ type: "SLIDE"
1635
+ format: "html-deck"
1636
+ engine: "html"
1637
+ phase: "P2"
1638
+ deliverable: "P2-T09"
1639
+ version: "v2.0"
1640
+ date: "${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}"
1641
+ ---
1642
+
1643
+ \`\`\`json
1644
+ ${JSON.stringify(deckJson, null, 2)}
1645
+ \`\`\`
1646
+ `;
1647
+ onProgress?.("@illustrator", `[4/4] Ho\xE0n t\u1EA5t b\u1ED9 tr\xECnh chi\u1EBFu HTML 20 trang \u0111\u1EA1t chu\u1EA9n Fixed 16:9 Stage!`);
1648
+ return {
1649
+ deckJson,
1650
+ compiledHtml,
1651
+ markdownWrapper,
1652
+ blueprint: blueprintItems,
1653
+ slideCount: normalizedSlides.length
1654
+ };
1655
+ }
1656
+ var init_slideProductionWorkflow = __esm({
1657
+ "src/services/slideProductionWorkflow.ts"() {
1658
+ init_lessonFlowParser();
1659
+ init_slideBlueprintPrompt();
1660
+ init_slideBatchPrompt();
1661
+ init_streamRunner();
1662
+ }
1663
+ });
1177
1664
  var LearningObjectiveRowSchema = zod.z.object({
1178
1665
  code: zod.z.string().describe('LO code, e.g. "LO1"'),
1179
1666
  objective: zod.z.string().describe("Bloom-tagged learning objective"),
@@ -25340,104 +25827,24 @@ ${buildHeadingDirective("QUIZ", slcMarkdown, targetLang)}`;
25340
25827
  presentationKitCore = await import('@thanh01.pmt/presentation-kit');
25341
25828
  } catch {
25342
25829
  }
25343
- const slidePrompt = buildHtmlDeckSlidePrompt({
25830
+ const { executeSlideProductionWorkflow: executeSlideProductionWorkflow2 } = await Promise.resolve().then(() => (init_slideProductionWorkflow(), slideProductionWorkflow_exports));
25831
+ const workflowResult = await executeSlideProductionWorkflow2({
25832
+ lessonMarkdown: lessonContent || "",
25344
25833
  lessonCode,
25345
25834
  lessonTitle,
25346
- pedagogyLabel,
25835
+ stylePresetId: "blue-professional",
25836
+ language: targetLang || "Vietnamese",
25347
25837
  languageDirective,
25348
25838
  headingDirective: buildHeadingDirective("SLIDE", slcMarkdown, targetLang),
25349
- glossaryBlock: glossaryContext ? `[GLOSSARY TERMS (use these exact definitions)]:
25350
- ${glossaryContext}` : void 0,
25351
- customContract: presentationKitAi?.SLIDE_HTML_PROMPT_CONTRACT
25352
- });
25353
- const rawSlide = await runCurriculumAIInference(
25354
- [{ role: "user", content: slidePrompt }],
25355
25839
  satelliteContext,
25356
25840
  runnerOptions,
25357
- (chunk, type) => {
25358
- options.onProgress?.("@illustrator", chunk, { type: type || "content", artifactType: "SLIDE" });
25841
+ onProgress: (agent, msg, meta) => {
25842
+ options.onProgress?.(agent, msg, meta || { type: "content", artifactType: "SLIDE" });
25359
25843
  }
25360
- );
25361
- if (!rawSlide || rawSlide.startsWith("\u26A0\uFE0F") || rawSlide.length < 200) {
25362
- throw createAiInferenceError({
25363
- errorCode: "ERR_AI_RESPONSE_MALFORMED",
25364
- agent: "@illustrator",
25365
- artifactType: "SLIDE",
25366
- lessonId: lessonCode,
25367
- message: `Agent @illustrator failed to author valid HTML Slides for ${lessonCode}.`,
25368
- rawError: rawSlide || "Empty AI response"
25369
- });
25370
- }
25371
- let deckJson = null;
25372
- const extractCleanJson = (str) => {
25373
- const fenceMatch = str.match(/```(?:json)?\s*([\s\S]*?)\s*```/i);
25374
- const candidate = fenceMatch ? fenceMatch[1] : str;
25375
- return candidate.trim();
25376
- };
25377
- const normalizeDeckSlides = (deck) => {
25378
- const normalizer = presentationKitCore?.normalizeSlideSlots || presentationKitAi?.normalizeSlideSlots;
25379
- if (deck && Array.isArray(deck.slides) && normalizer) {
25380
- deck.slides = deck.slides.map((s) => normalizer(s));
25381
- }
25382
- return deck;
25383
- };
25384
- try {
25385
- deckJson = JSON.parse(extractCleanJson(rawSlide));
25386
- deckJson = normalizeDeckSlides(deckJson);
25387
- } catch {
25388
- try {
25389
- const { jsonrepair: jsonrepair2 } = await import('jsonrepair');
25390
- deckJson = JSON.parse(jsonrepair2(extractCleanJson(rawSlide)));
25391
- deckJson = normalizeDeckSlides(deckJson);
25392
- } catch {
25393
- }
25394
- }
25395
- 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 };
25396
- if (!validation.valid && presentationKitAi?.validateHtmlSlideDeck) {
25397
- const repairIssues = (validation.issues || []).map((i) => `- Slide ${i.slideIndex ?? "?"}: ${i.message}`).join("\n");
25398
- onProgress?.("@illustrator", `\u26A0\uFE0F HTML slide deck failed schema validation \u2014 retrying once with targeted repair feedback...`);
25399
- const repairPrompt = `Your previous slide deck failed strict schema validation:
25400
- ${repairIssues}
25401
-
25402
- Fix ALL issues and output the complete corrected JSON object strictly matching the schema:`;
25403
- const repairedRaw = await runCurriculumAIInference(
25404
- [{ role: "user", content: repairPrompt }],
25405
- satelliteContext,
25406
- runnerOptions,
25407
- (chunk, type) => {
25408
- options.onProgress?.("@illustrator", chunk, { type: type || "content", artifactType: "SLIDE" });
25409
- }
25410
- );
25411
- try {
25412
- deckJson = JSON.parse(extractCleanJson(repairedRaw));
25413
- deckJson = normalizeDeckSlides(deckJson);
25414
- validation = presentationKitAi.validateHtmlSlideDeck(deckJson, true);
25415
- } catch {
25416
- try {
25417
- const { jsonrepair: jsonrepair2 } = await import('jsonrepair');
25418
- deckJson = JSON.parse(jsonrepair2(extractCleanJson(repairedRaw)));
25419
- deckJson = normalizeDeckSlides(deckJson);
25420
- validation = presentationKitAi.validateHtmlSlideDeck(deckJson, true);
25421
- } catch {
25422
- }
25423
- }
25424
- }
25425
- const markdownWrapper = `---
25426
- id: "SLIDE_${lessonCode}"
25427
- title: "${titleHeader(lessonTitle)}"
25428
- type: "SLIDE"
25429
- format: "html-deck"
25430
- engine: "html"
25431
- phase: "P2"
25432
- deliverable: "P2-T09"
25433
- version: "v1.0"
25434
- date: "${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}"
25435
- ---
25436
-
25437
- \`\`\`json
25438
- ${JSON.stringify(deckJson || {}, null, 2)}
25439
- \`\`\`
25440
- `;
25844
+ });
25845
+ let deckJson = workflowResult.deckJson;
25846
+ const markdownWrapper = workflowResult.markdownWrapper;
25847
+ let validation = presentationKitAi?.validateHtmlSlideDeck ? presentationKitAi.validateHtmlSlideDeck(deckJson, true) : deckJson && Array.isArray(deckJson.slides) ? { valid: true, score: 95, issues: [], slideCount: deckJson.slides.length} : { valid: false, score: 0, issues: [{ message: "Malformed JSON slide deck" }], slideCount: 0};
25441
25848
  await storage.saveArtifact(projectId, slideRelPath, markdownWrapper);
25442
25849
  producedArtifacts.push(`SLIDE_${lessonCode}.md`);
25443
25850
  if (deckJson) {
@@ -28075,6 +28482,9 @@ function isTranslationDue(target, context) {
28075
28482
  const total = context.totalLessonsByUnit[unit] ?? 0;
28076
28483
  return total > 0 && done >= total;
28077
28484
  }
28485
+
28486
+ // src/services/index.ts
28487
+ init_slideProductionWorkflow();
28078
28488
  var LocalWorkspaceManager = class {
28079
28489
  baseDir;
28080
28490
  constructor(baseDir) {
@@ -30669,6 +31079,7 @@ exports.ensureExpositionForLesson = ensureExpositionForLesson;
30669
31079
  exports.ensureKnowledgeExposition = ensureKnowledgeExposition;
30670
31080
  exports.evaluateStandardsCoverage = evaluateStandardsCoverage;
30671
31081
  exports.executeCurriculumCommand = executeCurriculumCommand;
31082
+ exports.executeSlideProductionWorkflow = executeSlideProductionWorkflow;
30672
31083
  exports.exportAllProjectQuizzes = exportAllProjectQuizzes;
30673
31084
  exports.expositionCacheKey = expositionCacheKey;
30674
31085
  exports.extractScopeSequenceRows = extractScopeSequenceRows;