@thanh01.pmt/curriculum-kit 1.4.17 → 1.4.20
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/{gateSettings-L3FR2-MO.d.cts → gateSettings-DabOqP6_.d.cts} +94 -1
- package/dist/{gateSettings-L3FR2-MO.d.ts → gateSettings-DabOqP6_.d.ts} +94 -1
- package/dist/index.cjs +876 -141
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +253 -112
- package/dist/index.d.ts +253 -112
- package/dist/index.mjs +870 -143
- package/dist/index.mjs.map +1 -1
- package/dist/workflow/index.cjs +359 -1
- package/dist/workflow/index.cjs.map +1 -1
- package/dist/workflow/index.d.cts +2 -2
- package/dist/workflow/index.d.ts +2 -2
- package/dist/workflow/index.mjs +359 -1
- package/dist/workflow/index.mjs.map +1 -1
- package/package.json +23 -22
- package/LICENSE +0 -21
package/dist/index.cjs
CHANGED
|
@@ -1597,10 +1597,91 @@ var slideProductionWorkflow_exports = {};
|
|
|
1597
1597
|
__export(slideProductionWorkflow_exports, {
|
|
1598
1598
|
GeneratedSlideArraySchema: () => exports.GeneratedSlideArraySchema,
|
|
1599
1599
|
GeneratedSlideSchema: () => exports.GeneratedSlideSchema,
|
|
1600
|
+
HybridBlueprintArraySchema: () => exports.HybridBlueprintArraySchema,
|
|
1601
|
+
HybridBlueprintItemSchema: () => exports.HybridBlueprintItemSchema,
|
|
1602
|
+
HybridDeckSlideArraySchema: () => exports.HybridDeckSlideArraySchema,
|
|
1603
|
+
HybridDeckSlideSchema: () => exports.HybridDeckSlideSchema,
|
|
1604
|
+
HybridPipelineError: () => exports.HybridPipelineError,
|
|
1600
1605
|
SlideBlueprintArraySchema: () => exports.SlideBlueprintArraySchema,
|
|
1601
1606
|
SlideBlueprintItemSchema: () => exports.SlideBlueprintItemSchema,
|
|
1602
|
-
executeSlideProductionWorkflow: () => executeSlideProductionWorkflow
|
|
1607
|
+
executeSlideProductionWorkflow: () => executeSlideProductionWorkflow,
|
|
1608
|
+
extractJsonArray: () => extractJsonArray,
|
|
1609
|
+
validateHybridDeckSlides: () => validateHybridDeckSlides
|
|
1603
1610
|
});
|
|
1611
|
+
function extractJsonArray(raw) {
|
|
1612
|
+
if (!raw) return { error: "empty response" };
|
|
1613
|
+
let text = raw.replace(/<think>[\s\S]*?<\/think>/gi, "").trim();
|
|
1614
|
+
text = text.replace(/^```(?:json)?\s*/m, "").replace(/```\s*$/m, "").trim();
|
|
1615
|
+
const start = text.indexOf("[");
|
|
1616
|
+
const end = text.lastIndexOf("]");
|
|
1617
|
+
if (start < 0 || end <= start) {
|
|
1618
|
+
return {
|
|
1619
|
+
error: "no JSON array span found",
|
|
1620
|
+
head: text.slice(0, 300),
|
|
1621
|
+
tail: text.slice(-300)
|
|
1622
|
+
};
|
|
1623
|
+
}
|
|
1624
|
+
const span = text.slice(start, end + 1);
|
|
1625
|
+
try {
|
|
1626
|
+
return { value: JSON.parse(span) };
|
|
1627
|
+
} catch {
|
|
1628
|
+
}
|
|
1629
|
+
try {
|
|
1630
|
+
return { value: JSON.parse(jsonrepair.jsonrepair(span)) };
|
|
1631
|
+
} catch (e) {
|
|
1632
|
+
return {
|
|
1633
|
+
error: "JSON.parse/jsonrepair failed: " + String(e?.message || e).slice(0, 120),
|
|
1634
|
+
head: text.slice(0, 300),
|
|
1635
|
+
tail: text.slice(-300)
|
|
1636
|
+
};
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
function validateHybridDeckSlides(slides, blueprint) {
|
|
1640
|
+
const v = [];
|
|
1641
|
+
if (blueprint.length > 0 && slides.length !== blueprint.length) {
|
|
1642
|
+
v.push(`slide count mismatch: got ${slides.length}, blueprint requires ${blueprint.length}`);
|
|
1643
|
+
}
|
|
1644
|
+
slides.forEach((s, i) => {
|
|
1645
|
+
const n = i + 1;
|
|
1646
|
+
const title = typeof s?.title === "string" ? s.title : "";
|
|
1647
|
+
if (!title.trim()) v.push(`slide ${n}: missing/empty title`);
|
|
1648
|
+
else if (title.length > 150) v.push(`slide ${n}: title too long (${title.length} chars, max 150) \u2014 repetition-loop guard`);
|
|
1649
|
+
const notes = typeof s?.notes === "string" ? s.notes.trim() : "";
|
|
1650
|
+
if (notes.length < 80) v.push(`slide ${n}: presenter notes missing or too short (${notes.length} chars, min 80)`);
|
|
1651
|
+
const code = s?.slots?.code;
|
|
1652
|
+
if (typeof code === "string") {
|
|
1653
|
+
if (code.length > 6e3) v.push(`slide ${n}: code block too long (${code.length} chars, max 6000)`);
|
|
1654
|
+
if (/\/\/\s*TODO|<CODE>|your code here/i.test(code)) v.push(`slide ${n}: placeholder code detected (TODO/<CODE>)`);
|
|
1655
|
+
}
|
|
1656
|
+
});
|
|
1657
|
+
return v.slice(0, 10);
|
|
1658
|
+
}
|
|
1659
|
+
async function inferTextJson(schema, systemPrompt, userPrompt, options, label) {
|
|
1660
|
+
const model = getAIModel(options.modelOptions);
|
|
1661
|
+
try {
|
|
1662
|
+
const { text, finishReason } = await ai.generateText({
|
|
1663
|
+
model,
|
|
1664
|
+
system: systemPrompt || void 0,
|
|
1665
|
+
prompt: userPrompt,
|
|
1666
|
+
maxOutputTokens: options.maxOutputTokens ?? 65536
|
|
1667
|
+
});
|
|
1668
|
+
const extracted = extractJsonArray(text);
|
|
1669
|
+
if (!("value" in extracted) || extracted.value === void 0) {
|
|
1670
|
+
console.warn(
|
|
1671
|
+
`[SlideProductionWorkflow] ${label}: JSON extraction failed (${extracted.error}); finish=${finishReason}`,
|
|
1672
|
+
extracted.head ? `head=${String(extracted.head).slice(0, 150)}` : ""
|
|
1673
|
+
);
|
|
1674
|
+
return null;
|
|
1675
|
+
}
|
|
1676
|
+
const validated = schema.safeParse(extracted.value);
|
|
1677
|
+
if (validated.success) return validated.data;
|
|
1678
|
+
console.warn(`[SlideProductionWorkflow] ${label}: output failed schema validation:`, validated.error?.message);
|
|
1679
|
+
return null;
|
|
1680
|
+
} catch (err) {
|
|
1681
|
+
console.warn(`[SlideProductionWorkflow] ${label}: generateText failed:`, err?.message || err);
|
|
1682
|
+
return null;
|
|
1683
|
+
}
|
|
1684
|
+
}
|
|
1604
1685
|
async function inferStructured(schema, systemPrompt, userPrompt, options, label) {
|
|
1605
1686
|
try {
|
|
1606
1687
|
const model = getAIModel(options.modelOptions);
|
|
@@ -1642,9 +1723,142 @@ async function inferStructured(schema, systemPrompt, userPrompt, options, label)
|
|
|
1642
1723
|
return null;
|
|
1643
1724
|
}
|
|
1644
1725
|
}
|
|
1726
|
+
function normalizeBlueprintItems(items) {
|
|
1727
|
+
return items.sort((a, b) => (a.slideIndex ?? 0) - (b.slideIndex ?? 0)).map((item, idx) => ({
|
|
1728
|
+
slideIndex: idx + 1,
|
|
1729
|
+
clusterId: item.clusterId ?? Math.floor(idx / 5) + 1,
|
|
1730
|
+
clusterTitle: item.clusterTitle || "Cluster",
|
|
1731
|
+
lessonPhase: item.lessonPhase || "Content",
|
|
1732
|
+
layoutId: item.layoutId,
|
|
1733
|
+
title: item.title,
|
|
1734
|
+
pedagogicalGoal: item.pedagogicalGoal || "",
|
|
1735
|
+
contentFocus: item.contentFocus || [],
|
|
1736
|
+
codeSnippetIntent: item.codeSnippetIntent ?? void 0,
|
|
1737
|
+
visualIntent: item.visualIntent ?? void 0
|
|
1738
|
+
}));
|
|
1739
|
+
}
|
|
1740
|
+
function buildHybridDeckPrompts(params) {
|
|
1741
|
+
const { lessonFlow, blueprint, skillPrompt, language, languageDirective, headingDirective } = params;
|
|
1742
|
+
const blueprintText = JSON.stringify(
|
|
1743
|
+
blueprint.map((s) => ({
|
|
1744
|
+
slideIndex: s.slideIndex,
|
|
1745
|
+
lessonPhase: s.lessonPhase,
|
|
1746
|
+
layoutId: s.layoutId,
|
|
1747
|
+
title: s.title,
|
|
1748
|
+
pedagogicalGoal: s.pedagogicalGoal,
|
|
1749
|
+
contentFocus: s.contentFocus,
|
|
1750
|
+
codeSnippetIntent: s.codeSnippetIntent ?? void 0,
|
|
1751
|
+
visualIntent: s.visualIntent ?? void 0
|
|
1752
|
+
})),
|
|
1753
|
+
null,
|
|
1754
|
+
1
|
|
1755
|
+
);
|
|
1756
|
+
const phasesContent = lessonFlow.phases.length > 0 ? lessonFlow.phases.map((p) => `### ${p.phaseName}
|
|
1757
|
+
${p.content}`).join("\n\n") : lessonFlow.rawContent;
|
|
1758
|
+
const systemPrompt = [
|
|
1759
|
+
skillPrompt,
|
|
1760
|
+
languageDirective,
|
|
1761
|
+
headingDirective,
|
|
1762
|
+
`
|
|
1763
|
+
### OPERATIONAL GROUND RULES (FULL-DECK AUTHORING):
|
|
1764
|
+
1. **EXACT COUNT: author EXACTLY ${blueprint.length} slide objects \u2014 ONE per blueprint item, SAME ORDER, SAME layoutId.** Do NOT merge, skip, reorder, or add slides.
|
|
1765
|
+
2. **ZERO ABBREVIATION / NO "// TODO"**: All code snippets MUST be real, fully authored, compilable code relevant to "${lessonFlow.lessonTitle}". Never write placeholders, stubs, or "<CODE>".
|
|
1766
|
+
3. **MANDATORY 3-PART PRESENTER NOTES ON EVERY SLIDE (120+ chars)**: SCRIPT (60-90s spoken talk track with an intuitive analogy), COLD CALL / CHECK (one targeted understanding question), SCAFFOLDING / GOTCHA (one common misconception or bug).
|
|
1767
|
+
4. Titles must be < 100 characters \u2014 never repeat or loop text.
|
|
1768
|
+
5. Output ONLY the JSON array. No prose, no markdown fences.
|
|
1769
|
+
`.trim()
|
|
1770
|
+
].filter(Boolean).join("\n\n");
|
|
1771
|
+
const userPrompt = `
|
|
1772
|
+
### BLUEPRINT (${blueprint.length} slides \u2014 AUTHOR ALL OF THEM, in this exact order):
|
|
1773
|
+
${blueprintText}
|
|
1774
|
+
|
|
1775
|
+
### LESSON GROUND TRUTH:
|
|
1776
|
+
- Lesson Title: "${lessonFlow.lessonTitle}"
|
|
1777
|
+
- Target Duration: ${lessonFlow.estimatedDuration}
|
|
1778
|
+
- Pedagogy: "${lessonFlow.pedagogicalModel.toUpperCase()}"
|
|
1779
|
+
- Language: "${language}"
|
|
1780
|
+
|
|
1781
|
+
### LESSON PHASE CONTENT (SOURCE OF TRUTH FOR REAL CONTENT):
|
|
1782
|
+
${phasesContent.slice(0, 16e3)}
|
|
1783
|
+
|
|
1784
|
+
---
|
|
1785
|
+
|
|
1786
|
+
### OUTPUT:
|
|
1787
|
+
A single JSON array of exactly ${blueprint.length} slide objects:
|
|
1788
|
+
\`\`\`json
|
|
1789
|
+
[
|
|
1790
|
+
{
|
|
1791
|
+
"id": "slide-1",
|
|
1792
|
+
"layoutId": "${blueprint[0]?.layoutId || "split-concept-code"}",
|
|
1793
|
+
"title": "${blueprint[0]?.title || "Slide Title"}",
|
|
1794
|
+
"slots": { "...layout-specific slots with REAL content..." },
|
|
1795
|
+
"notes": "SCRIPT: ...\\nCOLD CALL: ...\\nSCAFFOLDING: ..."
|
|
1796
|
+
}
|
|
1797
|
+
]
|
|
1798
|
+
\`\`\`
|
|
1799
|
+
`.trim();
|
|
1800
|
+
return { systemPrompt, userPrompt };
|
|
1801
|
+
}
|
|
1802
|
+
async function runHybridPipeline(ctx) {
|
|
1803
|
+
const { lessonFlow, blueprintPrompt, skillPrompt, language, languageDirective, headingDirective, maxRetries, options, onProgress } = ctx;
|
|
1804
|
+
let blueprint = null;
|
|
1805
|
+
for (let attempt = 1; attempt <= maxRetries && !blueprint; attempt++) {
|
|
1806
|
+
onProgress?.("@illustrator", `[1/4] L\u1EADp D\xE0n \xFD Slides \u2014 hybrid blueprint (l\u1EA7n ${attempt}/${maxRetries})...`);
|
|
1807
|
+
const parsed = await inferTextJson(exports.HybridBlueprintArraySchema, "", blueprintPrompt, options, `hybrid-blueprint#${attempt}`);
|
|
1808
|
+
if (parsed && parsed.length > 0) {
|
|
1809
|
+
blueprint = normalizeBlueprintItems(parsed);
|
|
1810
|
+
} else if (attempt < maxRetries) {
|
|
1811
|
+
onProgress?.("@illustrator", `\u26A0\uFE0F Hybrid blueprint l\u1EA7n ${attempt}/${maxRetries} l\u1ED7i \u2014 th\u1EED l\u1EA1i...`, { type: "warning" });
|
|
1812
|
+
}
|
|
1813
|
+
}
|
|
1814
|
+
if (!blueprint || blueprint.length === 0) {
|
|
1815
|
+
throw new exports.HybridPipelineError(`blueprint failed after ${maxRetries} attempts`);
|
|
1816
|
+
}
|
|
1817
|
+
onProgress?.("@illustrator", `[1/4] D\xE0n \xFD ${blueprint.length} slides ho\xE0n t\u1EA5t \u2014 chuy\u1EC3n sang authoring to\xE0n deck...`);
|
|
1818
|
+
const { systemPrompt, userPrompt } = buildHybridDeckPrompts({
|
|
1819
|
+
lessonFlow,
|
|
1820
|
+
blueprint,
|
|
1821
|
+
skillPrompt,
|
|
1822
|
+
language,
|
|
1823
|
+
languageDirective,
|
|
1824
|
+
headingDirective
|
|
1825
|
+
});
|
|
1826
|
+
let slides = null;
|
|
1827
|
+
let lastViolations = [];
|
|
1828
|
+
for (let attempt = 1; attempt <= maxRetries && !slides; attempt++) {
|
|
1829
|
+
onProgress?.("@illustrator", `[2/4] Sinh chi ti\u1EBFt to\xE0n b\u1ED9 ${blueprint.length} slides trong 1 l\u1EA7n g\u1ECDi (l\u1EA7n ${attempt}/${maxRetries})...`);
|
|
1830
|
+
const feedback = attempt > 1 && lastViolations.length > 0 ? `
|
|
1831
|
+
|
|
1832
|
+
### \u26A0\uFE0F PREVIOUS ATTEMPT REJECTED \u2014 fix these violations:
|
|
1833
|
+
${lastViolations.map((x) => "- " + x).join("\n")}
|
|
1834
|
+
Return exactly ${blueprint.length} slides, same order as the blueprint.` : "";
|
|
1835
|
+
const parsed = await inferTextJson(exports.HybridDeckSlideArraySchema, systemPrompt, userPrompt + feedback, options, `hybrid-author#${attempt}`);
|
|
1836
|
+
if (!parsed || parsed.length === 0) {
|
|
1837
|
+
lastViolations = ["Output missing, empty, or not a valid JSON array of slide objects"];
|
|
1838
|
+
} else {
|
|
1839
|
+
const violations = validateHybridDeckSlides(parsed, blueprint);
|
|
1840
|
+
if (violations.length === 0) {
|
|
1841
|
+
slides = parsed;
|
|
1842
|
+
break;
|
|
1843
|
+
}
|
|
1844
|
+
lastViolations = violations;
|
|
1845
|
+
}
|
|
1846
|
+
if (attempt < maxRetries) {
|
|
1847
|
+
onProgress?.("@illustrator", `\u26A0\uFE0F Deck authoring l\u1EA7n ${attempt}/${maxRetries} vi ph\u1EA1m guardrails \u2014 retry v\u1EDBi corrective feedback...`, {
|
|
1848
|
+
type: "warning",
|
|
1849
|
+
violations: lastViolations
|
|
1850
|
+
});
|
|
1851
|
+
}
|
|
1852
|
+
}
|
|
1853
|
+
if (!slides) {
|
|
1854
|
+
throw new exports.HybridPipelineError(
|
|
1855
|
+
`deck authoring failed after ${maxRetries} attempts. Last violations: ${lastViolations.slice(0, 3).join("; ")}`
|
|
1856
|
+
);
|
|
1857
|
+
}
|
|
1858
|
+
return { slides, blueprint };
|
|
1859
|
+
}
|
|
1645
1860
|
async function executeSlideProductionWorkflow(options) {
|
|
1646
1861
|
const {
|
|
1647
|
-
lessonMarkdown,
|
|
1648
1862
|
lessonCode,
|
|
1649
1863
|
lessonTitle,
|
|
1650
1864
|
targetSlideCount,
|
|
@@ -1655,6 +1869,7 @@ async function executeSlideProductionWorkflow(options) {
|
|
|
1655
1869
|
maxRetries = 3,
|
|
1656
1870
|
onProgress
|
|
1657
1871
|
} = options;
|
|
1872
|
+
const engineRequested = options.engine ?? "hybrid";
|
|
1658
1873
|
let presentationKitSkills = null;
|
|
1659
1874
|
let presentationKitCore = null;
|
|
1660
1875
|
try {
|
|
@@ -1665,117 +1880,140 @@ async function executeSlideProductionWorkflow(options) {
|
|
|
1665
1880
|
presentationKitCore = await import('@thanh01.pmt/presentation-kit');
|
|
1666
1881
|
} catch {
|
|
1667
1882
|
}
|
|
1668
|
-
const
|
|
1669
|
-
onProgress?.("@illustrator", `[1/4] Ph\xE2n t\xEDch m\u1EA1ch b\xE0i gi\u1EA3ng LESSON & L\u1EADp D\xE0n \xFD ${countLabel}Slides...`);
|
|
1670
|
-
const lessonFlow = parseLessonFlow(lessonMarkdown);
|
|
1883
|
+
const lessonFlow = parseLessonFlow(options.lessonMarkdown);
|
|
1671
1884
|
const getStylePreset = presentationKitSkills?.getStylePreset;
|
|
1672
1885
|
const stylePreset = getStylePreset ? getStylePreset(stylePresetId) : { name: "Blue Professional" };
|
|
1886
|
+
const skillPrompt = presentationKitSkills?.getFrontendSlidesSkillPrompt ? presentationKitSkills.getFrontendSlidesSkillPrompt({ stylePresetId }) : "";
|
|
1673
1887
|
const blueprintPrompt = buildSlideBlueprintPrompt({
|
|
1674
1888
|
lessonFlow,
|
|
1675
1889
|
targetSlideCount,
|
|
1676
1890
|
stylePresetName: stylePreset?.name || "Blue Professional"
|
|
1677
1891
|
});
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1892
|
+
let blueprintItems;
|
|
1893
|
+
let allGeneratedSlides;
|
|
1894
|
+
let engineUsed = engineRequested;
|
|
1895
|
+
const hybridResult = engineRequested === "hybrid" ? await runHybridPipeline({
|
|
1896
|
+
lessonFlow,
|
|
1897
|
+
blueprintPrompt,
|
|
1898
|
+
skillPrompt,
|
|
1899
|
+
language,
|
|
1900
|
+
languageDirective,
|
|
1901
|
+
headingDirective,
|
|
1902
|
+
maxRetries,
|
|
1903
|
+
options,
|
|
1904
|
+
onProgress
|
|
1905
|
+
}).catch((hybridErr) => {
|
|
1906
|
+
console.warn(`[SlideProductionWorkflow] Hybrid engine failed: ${hybridErr?.message || hybridErr} \u2014 falling back to chunked pipeline.`);
|
|
1907
|
+
onProgress?.("@illustrator", `\u26A0\uFE0F Hybrid engine l\u1ED7i \u2014 chuy\u1EC3n sang chunked pipeline (per-cluster)...`, { type: "warning" });
|
|
1908
|
+
return null;
|
|
1909
|
+
}) : null;
|
|
1910
|
+
if (hybridResult) {
|
|
1911
|
+
blueprintItems = hybridResult.blueprint;
|
|
1912
|
+
allGeneratedSlides = hybridResult.slides.map((s, idx) => ({
|
|
1913
|
+
id: s.id || `slide-${idx + 1}`,
|
|
1914
|
+
layoutId: s.layoutId,
|
|
1915
|
+
title: s.title,
|
|
1916
|
+
slots: s.slots || {},
|
|
1917
|
+
notes: s.notes || ""
|
|
1918
|
+
}));
|
|
1919
|
+
} else {
|
|
1920
|
+
engineUsed = "chunked";
|
|
1921
|
+
const countLabel = targetSlideCount ? `${targetSlideCount} ` : "h\u1EEFu c\u01A1 ";
|
|
1922
|
+
onProgress?.("@illustrator", `[1/4] Ph\xE2n t\xEDch m\u1EA1ch b\xE0i gi\u1EA3ng LESSON & L\u1EADp D\xE0n \xFD ${countLabel}Slides...`);
|
|
1923
|
+
blueprintItems = await pRetry__default.default(
|
|
1924
|
+
async () => {
|
|
1925
|
+
const items = await inferStructured(
|
|
1926
|
+
exports.SlideBlueprintArraySchema,
|
|
1927
|
+
"",
|
|
1928
|
+
blueprintPrompt,
|
|
1929
|
+
options,
|
|
1930
|
+
"blueprint"
|
|
1931
|
+
);
|
|
1932
|
+
if (!items || items.length === 0) {
|
|
1933
|
+
throw new Error("Blueprint generation returned empty or schema-invalid output");
|
|
1934
|
+
}
|
|
1935
|
+
return normalizeBlueprintItems(items);
|
|
1936
|
+
},
|
|
1937
|
+
{
|
|
1938
|
+
retries: maxRetries - 1,
|
|
1939
|
+
onFailedAttempt: (err) => {
|
|
1940
|
+
onProgress?.("@illustrator", `\u26A0\uFE0F Blueprint attempt ${err.attemptNumber}/${maxRetries} failed \u2014 retrying...`, {
|
|
1941
|
+
type: "warning"
|
|
1942
|
+
});
|
|
1943
|
+
}
|
|
1706
1944
|
}
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
const matchingPhases = lessonFlow.phases.filter((p) => clusterPhaseNames.has(p.phaseName.toLowerCase()));
|
|
1726
|
-
const lessonExcerpt = matchingPhases.length > 0 ? matchingPhases.map((p) => `### ${p.phaseName}
|
|
1945
|
+
);
|
|
1946
|
+
const clustersMap = /* @__PURE__ */ new Map();
|
|
1947
|
+
for (const item of blueprintItems) {
|
|
1948
|
+
const cId = item.clusterId || Math.floor((item.slideIndex - 1) / 5) + 1;
|
|
1949
|
+
if (!clustersMap.has(cId)) clustersMap.set(cId, []);
|
|
1950
|
+
clustersMap.get(cId).push(item);
|
|
1951
|
+
}
|
|
1952
|
+
const clusters = Array.from(clustersMap.entries()).sort(([a], [b]) => a - b);
|
|
1953
|
+
allGeneratedSlides = [];
|
|
1954
|
+
const failedClusters = [];
|
|
1955
|
+
let clusterIdx = 0;
|
|
1956
|
+
for (const [cId, clusterSlides] of clusters) {
|
|
1957
|
+
clusterIdx++;
|
|
1958
|
+
const clusterTitle = clusterSlides[0]?.clusterTitle || `Giai \u0111o\u1EA1n ${clusterIdx}`;
|
|
1959
|
+
onProgress?.("@illustrator", `[2/4] Sinh chi ti\u1EBFt C\u1EE5m ${clusterIdx}/${clusters.length}: "${clusterTitle}" (${clusterSlides.length} slides)...`);
|
|
1960
|
+
const clusterPhaseNames = new Set(clusterSlides.map((s) => s.lessonPhase.toLowerCase()));
|
|
1961
|
+
const matchingPhases = lessonFlow.phases.filter((p) => clusterPhaseNames.has(p.phaseName.toLowerCase()));
|
|
1962
|
+
const lessonExcerpt = matchingPhases.length > 0 ? matchingPhases.map((p) => `### ${p.phaseName}
|
|
1727
1963
|
${p.content}`).join("\n\n") : lessonFlow.rawContent.slice(0, 3500);
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1964
|
+
const { systemPrompt, userPrompt } = buildSlideBatchPrompt({
|
|
1965
|
+
clusterId: cId,
|
|
1966
|
+
clusterTitle,
|
|
1967
|
+
clusterSlides,
|
|
1968
|
+
lessonFlow,
|
|
1969
|
+
lessonExcerpt,
|
|
1970
|
+
skillPrompt,
|
|
1971
|
+
language,
|
|
1972
|
+
languageDirective,
|
|
1973
|
+
headingDirective
|
|
1974
|
+
});
|
|
1975
|
+
try {
|
|
1976
|
+
const batchSlides = await pRetry__default.default(
|
|
1977
|
+
async () => {
|
|
1978
|
+
const slides = await inferStructured(
|
|
1979
|
+
exports.GeneratedSlideArraySchema,
|
|
1980
|
+
systemPrompt,
|
|
1981
|
+
userPrompt,
|
|
1982
|
+
options,
|
|
1983
|
+
`cluster-${cId}`
|
|
1984
|
+
);
|
|
1985
|
+
if (!slides || slides.length === 0) {
|
|
1986
|
+
throw new Error(`Cluster ${cId} returned empty or schema-invalid output`);
|
|
1987
|
+
}
|
|
1988
|
+
return slides;
|
|
1989
|
+
},
|
|
1990
|
+
{
|
|
1991
|
+
retries: maxRetries - 1,
|
|
1992
|
+
onFailedAttempt: (err) => {
|
|
1993
|
+
onProgress?.("@illustrator", `\u26A0\uFE0F C\u1EE5m ${clusterIdx} l\u1EA7n ${err.attemptNumber}/${maxRetries} l\u1ED7i \u2014 retry ri\xEAng c\u1EE5m n\xE0y...`, {
|
|
1994
|
+
type: "warning"
|
|
1995
|
+
});
|
|
1996
|
+
}
|
|
1760
1997
|
}
|
|
1761
|
-
|
|
1998
|
+
);
|
|
1999
|
+
allGeneratedSlides.push(...batchSlides);
|
|
2000
|
+
} catch (clusterErr) {
|
|
2001
|
+
failedClusters.push(cId);
|
|
2002
|
+
console.error(`[SlideProductionWorkflow] Cluster ${cId} permanently failed after ${maxRetries} attempts:`, clusterErr?.message || clusterErr);
|
|
2003
|
+
}
|
|
2004
|
+
}
|
|
2005
|
+
if (failedClusters.length > 0) {
|
|
2006
|
+
throw new Error(
|
|
2007
|
+
`[SlideProductionWorkflow] Failed to generate ${failedClusters.length}/${clusters.length} cluster(s) (clusterId: ${failedClusters.join(", ")}) after ${maxRetries} attempts each. Aborting to prevent placeholder/degraded slide output. Review provider keys/model availability and retry.`
|
|
1762
2008
|
);
|
|
1763
|
-
allGeneratedSlides.push(...batchSlides);
|
|
1764
|
-
} catch (clusterErr) {
|
|
1765
|
-
failedClusters.push(cId);
|
|
1766
|
-
console.error(`[SlideProductionWorkflow] Cluster ${cId} permanently failed after ${maxRetries} attempts:`, clusterErr?.message || clusterErr);
|
|
1767
2009
|
}
|
|
1768
2010
|
}
|
|
1769
|
-
if (failedClusters.length > 0) {
|
|
1770
|
-
throw new Error(
|
|
1771
|
-
`[SlideProductionWorkflow] Failed to generate ${failedClusters.length}/${clusters.length} cluster(s) (clusterId: ${failedClusters.join(", ")}) after ${maxRetries} attempts each. Aborting to prevent placeholder/degraded slide output. Review provider keys/model availability and retry.`
|
|
1772
|
-
);
|
|
1773
|
-
}
|
|
1774
2011
|
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)...`);
|
|
1775
2012
|
const normalizer = presentationKitCore?.normalizeSlideSlots;
|
|
1776
2013
|
const normalizedSlides = allGeneratedSlides.map((s, idx) => {
|
|
1777
2014
|
const base = normalizer ? normalizer(s) : s;
|
|
1778
2015
|
if (!base.id) base.id = `slide-${idx + 1}`;
|
|
2016
|
+
if (!base.slots || typeof base.slots !== "object") base.slots = {};
|
|
1779
2017
|
return base;
|
|
1780
2018
|
});
|
|
1781
2019
|
const deckJson = {
|
|
@@ -1785,16 +2023,19 @@ ${p.content}`).join("\n\n") : lessonFlow.rawContent.slice(0, 3500);
|
|
|
1785
2023
|
slides: normalizedSlides
|
|
1786
2024
|
};
|
|
1787
2025
|
let compiledHtml;
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
} catch (compErr) {
|
|
1796
|
-
console.warn(`[SlideProductionWorkflow] Failed compiling HTML deck:`, compErr?.message || compErr);
|
|
2026
|
+
try {
|
|
2027
|
+
let compiled = null;
|
|
2028
|
+
if (typeof presentationKitCore?.compileHtmlDeckAsync === "function") {
|
|
2029
|
+
compiled = await presentationKitCore.compileHtmlDeckAsync(deckJson);
|
|
2030
|
+
}
|
|
2031
|
+
if (!compiled?.html && typeof presentationKitCore?.compileHtmlDeck === "function") {
|
|
2032
|
+
compiled = presentationKitCore.compileHtmlDeck(deckJson);
|
|
1797
2033
|
}
|
|
2034
|
+
if (compiled?.html) {
|
|
2035
|
+
compiledHtml = compiled.html;
|
|
2036
|
+
}
|
|
2037
|
+
} catch (compErr) {
|
|
2038
|
+
console.warn(`[SlideProductionWorkflow] Failed compiling HTML deck:`, compErr?.message || compErr);
|
|
1798
2039
|
}
|
|
1799
2040
|
const markdownWrapper = `---
|
|
1800
2041
|
id: "SLIDE_${lessonCode}"
|
|
@@ -1818,10 +2059,11 @@ ${JSON.stringify(deckJson, null, 2)}
|
|
|
1818
2059
|
compiledHtml,
|
|
1819
2060
|
markdownWrapper,
|
|
1820
2061
|
blueprint: blueprintItems,
|
|
1821
|
-
slideCount: normalizedSlides.length
|
|
2062
|
+
slideCount: normalizedSlides.length,
|
|
2063
|
+
engine: engineUsed
|
|
1822
2064
|
};
|
|
1823
2065
|
}
|
|
1824
|
-
exports.SlideBlueprintItemSchema = void 0; exports.SlideBlueprintArraySchema = void 0; exports.GeneratedSlideSchema = void 0; exports.GeneratedSlideArraySchema = void 0;
|
|
2066
|
+
var LAYOUT_ID_ENUM; exports.SlideBlueprintItemSchema = void 0; exports.SlideBlueprintArraySchema = void 0; exports.GeneratedSlideSchema = void 0; exports.GeneratedSlideArraySchema = void 0; exports.HybridBlueprintItemSchema = void 0; exports.HybridBlueprintArraySchema = void 0; exports.HybridDeckSlideSchema = void 0; exports.HybridDeckSlideArraySchema = void 0; exports.HybridPipelineError = void 0;
|
|
1825
2067
|
var init_slideProductionWorkflow = __esm({
|
|
1826
2068
|
"src/services/slideProductionWorkflow.ts"() {
|
|
1827
2069
|
init_lessonFlowParser();
|
|
@@ -1829,37 +2071,61 @@ var init_slideProductionWorkflow = __esm({
|
|
|
1829
2071
|
init_slideBatchPrompt();
|
|
1830
2072
|
init_provider_factory();
|
|
1831
2073
|
init_streamRunner();
|
|
2074
|
+
LAYOUT_ID_ENUM = zod.z.enum([
|
|
2075
|
+
"hero-cover",
|
|
2076
|
+
"split-concept-code",
|
|
2077
|
+
"two-columns-compare",
|
|
2078
|
+
"three-cards-grid",
|
|
2079
|
+
"timeline-steps",
|
|
2080
|
+
"metric-callout",
|
|
2081
|
+
"checkpoint-quiz",
|
|
2082
|
+
"tiered-practice-3cards",
|
|
2083
|
+
"summary-takeaways"
|
|
2084
|
+
]);
|
|
1832
2085
|
exports.SlideBlueprintItemSchema = zod.z.object({
|
|
1833
2086
|
slideIndex: zod.z.number().int().min(1),
|
|
1834
|
-
clusterId: zod.z.number().int().min(1).default(1),
|
|
1835
|
-
clusterTitle: zod.z.string().default("Cluster"),
|
|
1836
|
-
lessonPhase: zod.z.string().default("Content"),
|
|
1837
|
-
layoutId:
|
|
1838
|
-
"hero-cover",
|
|
1839
|
-
"split-concept-code",
|
|
1840
|
-
"two-columns-compare",
|
|
1841
|
-
"three-cards-grid",
|
|
1842
|
-
"timeline-steps",
|
|
1843
|
-
"metric-callout",
|
|
1844
|
-
"checkpoint-quiz",
|
|
1845
|
-
"tiered-practice-3cards",
|
|
1846
|
-
"summary-takeaways"
|
|
1847
|
-
]),
|
|
2087
|
+
clusterId: zod.z.number().int().min(1).nullish().default(1),
|
|
2088
|
+
clusterTitle: zod.z.string().nullish().default("Cluster"),
|
|
2089
|
+
lessonPhase: zod.z.string().nullish().default("Content"),
|
|
2090
|
+
layoutId: LAYOUT_ID_ENUM,
|
|
1848
2091
|
title: zod.z.string().min(1),
|
|
1849
|
-
pedagogicalGoal: zod.z.string().default(""),
|
|
1850
|
-
contentFocus: zod.z.array(zod.z.string()).default([]),
|
|
1851
|
-
codeSnippetIntent: zod.z.string().
|
|
1852
|
-
visualIntent: zod.z.string().
|
|
2092
|
+
pedagogicalGoal: zod.z.string().nullish().default(""),
|
|
2093
|
+
contentFocus: zod.z.array(zod.z.string()).nullish().default([]),
|
|
2094
|
+
codeSnippetIntent: zod.z.string().nullish(),
|
|
2095
|
+
visualIntent: zod.z.string().nullish()
|
|
1853
2096
|
});
|
|
1854
2097
|
exports.SlideBlueprintArraySchema = zod.z.array(exports.SlideBlueprintItemSchema);
|
|
1855
2098
|
exports.GeneratedSlideSchema = zod.z.object({
|
|
1856
|
-
id: zod.z.string().
|
|
1857
|
-
layoutId: zod.z.string(),
|
|
2099
|
+
id: zod.z.string().nullish(),
|
|
2100
|
+
layoutId: zod.z.string().min(1),
|
|
1858
2101
|
title: zod.z.string().min(1),
|
|
1859
|
-
slots: zod.z.record(zod.z.any()).default({}),
|
|
1860
|
-
notes: zod.z.string().default("")
|
|
2102
|
+
slots: zod.z.record(zod.z.any()).nullish().default({}),
|
|
2103
|
+
notes: zod.z.string().nullish().default("")
|
|
1861
2104
|
});
|
|
1862
2105
|
exports.GeneratedSlideArraySchema = zod.z.array(exports.GeneratedSlideSchema);
|
|
2106
|
+
exports.HybridBlueprintItemSchema = zod.z.object({
|
|
2107
|
+
slideIndex: zod.z.number().int().min(1),
|
|
2108
|
+
clusterId: zod.z.number().int().min(1).nullish(),
|
|
2109
|
+
clusterTitle: zod.z.string().nullish(),
|
|
2110
|
+
lessonPhase: zod.z.string().nullish(),
|
|
2111
|
+
layoutId: LAYOUT_ID_ENUM,
|
|
2112
|
+
title: zod.z.string().min(1).max(300),
|
|
2113
|
+
pedagogicalGoal: zod.z.string().nullish().default(""),
|
|
2114
|
+
contentFocus: zod.z.array(zod.z.string()).nullish().default([]),
|
|
2115
|
+
codeSnippetIntent: zod.z.string().nullish(),
|
|
2116
|
+
visualIntent: zod.z.string().nullish()
|
|
2117
|
+
});
|
|
2118
|
+
exports.HybridBlueprintArraySchema = zod.z.array(exports.HybridBlueprintItemSchema);
|
|
2119
|
+
exports.HybridDeckSlideSchema = zod.z.object({
|
|
2120
|
+
id: zod.z.string().nullish(),
|
|
2121
|
+
layoutId: zod.z.string().min(1),
|
|
2122
|
+
title: zod.z.string().min(1).max(300),
|
|
2123
|
+
slots: zod.z.record(zod.z.any()).nullish().default({}),
|
|
2124
|
+
notes: zod.z.string().nullish().default("")
|
|
2125
|
+
});
|
|
2126
|
+
exports.HybridDeckSlideArraySchema = zod.z.array(exports.HybridDeckSlideSchema);
|
|
2127
|
+
exports.HybridPipelineError = class extends Error {
|
|
2128
|
+
};
|
|
1863
2129
|
}
|
|
1864
2130
|
});
|
|
1865
2131
|
var LearningObjectiveRowSchema = zod.z.object({
|
|
@@ -10688,8 +10954,310 @@ ${lines.join("\n")}
|
|
|
10688
10954
|
- For example: if canonical section is "Computational Thinking Focus", your heading must be "## [REQUIRED] ${headings["Computational Thinking Focus"] || "Computational Thinking Focus"}".`;
|
|
10689
10955
|
}
|
|
10690
10956
|
|
|
10957
|
+
// src/services/curriculumHorizon.ts
|
|
10958
|
+
init_errors();
|
|
10959
|
+
var DETAILED_BRIDGE_WINDOW = 2;
|
|
10960
|
+
var BOUNDARY_PEEK_WINDOW = 2;
|
|
10961
|
+
function parseAllSessions(plan, frameworkMarkdown) {
|
|
10962
|
+
if (plan) {
|
|
10963
|
+
const rawSessions = Array.isArray(plan.sessions) ? plan.sessions : Array.isArray(plan) ? plan : [];
|
|
10964
|
+
if (rawSessions.length > 0) {
|
|
10965
|
+
return rawSessions.map((s, idx) => ({
|
|
10966
|
+
id: s.id || `L${String(idx + 1).padStart(2, "0")}`,
|
|
10967
|
+
order: typeof s.order === "number" ? s.order : idx + 1,
|
|
10968
|
+
title: s.title || "",
|
|
10969
|
+
prose_objective: s.prose_objective || s.objective || "",
|
|
10970
|
+
new_keywords: Array.isArray(s.new_keywords) ? s.new_keywords : Array.isArray(s.keywords) ? s.keywords : [],
|
|
10971
|
+
prerequisite_keywords: Array.isArray(s.prerequisite_keywords) ? s.prerequisite_keywords : [],
|
|
10972
|
+
depth_assignments: Array.isArray(s.depth_assignments) ? s.depth_assignments : []
|
|
10973
|
+
}));
|
|
10974
|
+
}
|
|
10975
|
+
}
|
|
10976
|
+
if (frameworkMarkdown && typeof frameworkMarkdown === "string") {
|
|
10977
|
+
const lines = frameworkMarkdown.split("\n");
|
|
10978
|
+
let headerCols = [];
|
|
10979
|
+
const sessions = [];
|
|
10980
|
+
for (const line of lines) {
|
|
10981
|
+
const trimmed = line.trim();
|
|
10982
|
+
if (!trimmed.startsWith("|")) continue;
|
|
10983
|
+
const cols = trimmed.split("|").slice(1, -1).map((c) => c.trim());
|
|
10984
|
+
const lower = cols.map((c) => c.toLowerCase());
|
|
10985
|
+
if (lower.some((c) => c.includes("lesson code") || c === "m\xE3 b\xE0i" || c.includes("m\xE3 b\xE0i h\u1ECDc"))) {
|
|
10986
|
+
headerCols = cols;
|
|
10987
|
+
continue;
|
|
10988
|
+
}
|
|
10989
|
+
if (/^[-: |]+$/.test(trimmed.slice(1, -1))) continue;
|
|
10990
|
+
if (cols.length < 3) continue;
|
|
10991
|
+
let lessonCode = "";
|
|
10992
|
+
let title = "";
|
|
10993
|
+
let objective = "";
|
|
10994
|
+
let concept = "";
|
|
10995
|
+
let keywordsStr = "";
|
|
10996
|
+
if (headerCols.length > 0) {
|
|
10997
|
+
const col = (name) => {
|
|
10998
|
+
const idx = headerCols.findIndex((h) => h.toLowerCase().includes(name));
|
|
10999
|
+
return idx >= 0 ? cols[idx] || "" : "";
|
|
11000
|
+
};
|
|
11001
|
+
lessonCode = (col("lesson code") || col("m\xE3 b\xE0i") || cols[1] || "").replace(/\*\*/g, "").trim();
|
|
11002
|
+
title = (col("title") || col("t\xEAn") || cols[2] || "").replace(/\*\*/g, "").trim();
|
|
11003
|
+
objective = col("learning objective") || col("objective") || col("m\u1EE5c ti\xEAu") || "";
|
|
11004
|
+
concept = col("key concept") || col("concept") || col("kh\xE1i ni\u1EC7m") || "";
|
|
11005
|
+
keywordsStr = col("keywords") || col("t\u1EEB kh\xF3a") || "";
|
|
11006
|
+
} else {
|
|
11007
|
+
lessonCode = (cols[1] || "").replace(/\*\*/g, "").trim();
|
|
11008
|
+
title = (cols[2] || "").replace(/\*\*/g, "").trim();
|
|
11009
|
+
objective = cols[5] || "";
|
|
11010
|
+
concept = cols[4] || "";
|
|
11011
|
+
}
|
|
11012
|
+
if (lessonCode && /^[A-Za-z0-9_\-]+$/.test(lessonCode)) {
|
|
11013
|
+
const keywords = keywordsStr ? keywordsStr.split(/[,;\n]/).map((k) => k.trim().replace(/^`|`$/g, "")).filter(Boolean) : concept ? concept.split(/[,;\n]/).map((k) => k.trim().replace(/^`|`$/g, "")).filter(Boolean) : [];
|
|
11014
|
+
sessions.push({
|
|
11015
|
+
id: lessonCode,
|
|
11016
|
+
order: sessions.length + 1,
|
|
11017
|
+
title: title || lessonCode,
|
|
11018
|
+
prose_objective: objective,
|
|
11019
|
+
new_keywords: keywords
|
|
11020
|
+
});
|
|
11021
|
+
}
|
|
11022
|
+
}
|
|
11023
|
+
if (sessions.length > 0) {
|
|
11024
|
+
return sessions;
|
|
11025
|
+
}
|
|
11026
|
+
}
|
|
11027
|
+
return [];
|
|
11028
|
+
}
|
|
11029
|
+
async function extractCurriculumHorizon(opts) {
|
|
11030
|
+
const { plan, frameworkMarkdown, targetLessonId, storage, projectId } = opts;
|
|
11031
|
+
const sessions = parseAllSessions(plan, frameworkMarkdown);
|
|
11032
|
+
if (sessions.length === 0) {
|
|
11033
|
+
throw new exports.CurriculumError({
|
|
11034
|
+
errorCode: "ERR_LESSON_NOT_IN_SOT",
|
|
11035
|
+
lessonId: targetLessonId,
|
|
11036
|
+
message: `[CurriculumHorizon] No sessions could be parsed from CURRICULUM_PLAN.json or CURRICULUM_FRAMEWORK.md. Fail fast \u2014 cannot construct curriculum horizon without SOT.`,
|
|
11037
|
+
suggestedAction: "Ensure CURRICULUM_PLAN.json or CURRICULUM_FRAMEWORK.md is generated and contains valid sessions.",
|
|
11038
|
+
retryable: false
|
|
11039
|
+
});
|
|
11040
|
+
}
|
|
11041
|
+
const targetCodePattern = new RegExp("^" + targetLessonId.replace(/_/g, "[_-]") + "$", "i");
|
|
11042
|
+
const targetIndex = sessions.findIndex(
|
|
11043
|
+
(s) => s.id === targetLessonId || targetCodePattern.test(s.id)
|
|
11044
|
+
);
|
|
11045
|
+
if (targetIndex < 0) {
|
|
11046
|
+
throw new exports.CurriculumError({
|
|
11047
|
+
errorCode: "ERR_LESSON_NOT_IN_SOT",
|
|
11048
|
+
lessonId: targetLessonId,
|
|
11049
|
+
message: `[CurriculumHorizon] Lesson "${targetLessonId}" was not found among the ${sessions.length} planned sessions. Fail fast \u2014 refusing to generate unanchored lesson.`,
|
|
11050
|
+
suggestedAction: `Check lesson id against planned sessions: [${sessions.map((s) => s.id).join(", ")}]`,
|
|
11051
|
+
retryable: false
|
|
11052
|
+
});
|
|
11053
|
+
}
|
|
11054
|
+
const currentSession = sessions[targetIndex];
|
|
11055
|
+
const compactEnd = Math.max(0, targetIndex - DETAILED_BRIDGE_WINDOW);
|
|
11056
|
+
const compactSessions = sessions.slice(0, compactEnd);
|
|
11057
|
+
const masteredKeywords = Array.from(
|
|
11058
|
+
new Set(
|
|
11059
|
+
compactSessions.flatMap((s) => s.new_keywords || []).map((k) => k.trim()).filter(Boolean)
|
|
11060
|
+
)
|
|
11061
|
+
);
|
|
11062
|
+
const masteredConcepts = Array.from(
|
|
11063
|
+
new Set(
|
|
11064
|
+
compactSessions.map((s) => s.title || s.prose_objective || "").map((t) => t.trim()).filter(Boolean)
|
|
11065
|
+
)
|
|
11066
|
+
);
|
|
11067
|
+
const bridgeStart = Math.max(0, targetIndex - DETAILED_BRIDGE_WINDOW);
|
|
11068
|
+
const bridgeSessions = sessions.slice(bridgeStart, targetIndex);
|
|
11069
|
+
const detailedBridge = [];
|
|
11070
|
+
for (const session of bridgeSessions) {
|
|
11071
|
+
const bridgeIndex = sessions.indexOf(session) + 1;
|
|
11072
|
+
const bridge = {
|
|
11073
|
+
lessonId: session.id,
|
|
11074
|
+
lessonIndex: bridgeIndex,
|
|
11075
|
+
title: session.title,
|
|
11076
|
+
proseObjective: session.prose_objective,
|
|
11077
|
+
keywords: session.new_keywords || []
|
|
11078
|
+
};
|
|
11079
|
+
if (storage && projectId) {
|
|
11080
|
+
try {
|
|
11081
|
+
const candidates = [
|
|
11082
|
+
`_content/${session.id.replace(/_L\d+$/, "")}/LESSON_${session.id}.md`,
|
|
11083
|
+
`_content/LESSON_${session.id}.md`,
|
|
11084
|
+
`LESSON_${session.id}.md`
|
|
11085
|
+
];
|
|
11086
|
+
let lessonMd = "";
|
|
11087
|
+
for (const c of candidates) {
|
|
11088
|
+
try {
|
|
11089
|
+
const raw = await storage.readArtifact(projectId, c);
|
|
11090
|
+
if (raw) {
|
|
11091
|
+
lessonMd = raw;
|
|
11092
|
+
break;
|
|
11093
|
+
}
|
|
11094
|
+
} catch {
|
|
11095
|
+
}
|
|
11096
|
+
}
|
|
11097
|
+
if (lessonMd) {
|
|
11098
|
+
const ledger = extractSymbolLedger(lessonMd);
|
|
11099
|
+
if (ledger.primarySymbol || ledger.entryFileName || ledger.keySymbols.length > 0) {
|
|
11100
|
+
bridge.symbolLedger = {
|
|
11101
|
+
primaryStructOrClass: ledger.primarySymbol,
|
|
11102
|
+
mainEntryFile: ledger.entryFileName,
|
|
11103
|
+
keyVariables: ledger.keySymbols
|
|
11104
|
+
};
|
|
11105
|
+
}
|
|
11106
|
+
}
|
|
11107
|
+
} catch {
|
|
11108
|
+
}
|
|
11109
|
+
}
|
|
11110
|
+
detailedBridge.push(bridge);
|
|
11111
|
+
}
|
|
11112
|
+
const peekStart = targetIndex + 1;
|
|
11113
|
+
const peekEnd = Math.min(sessions.length, peekStart + BOUNDARY_PEEK_WINDOW);
|
|
11114
|
+
const boundaryPeek = sessions.slice(peekStart, peekEnd).map((s, idx) => ({
|
|
11115
|
+
lessonId: s.id,
|
|
11116
|
+
lessonIndex: peekStart + idx + 1,
|
|
11117
|
+
title: s.title,
|
|
11118
|
+
keywords: s.new_keywords || []
|
|
11119
|
+
}));
|
|
11120
|
+
return {
|
|
11121
|
+
targetLessonId,
|
|
11122
|
+
targetLessonIndex: targetIndex + 1,
|
|
11123
|
+
totalLessons: sessions.length,
|
|
11124
|
+
targetTitle: currentSession.title,
|
|
11125
|
+
targetKeywords: currentSession.new_keywords || [],
|
|
11126
|
+
compactMasterySet: {
|
|
11127
|
+
masteredKeywords,
|
|
11128
|
+
masteredConcepts
|
|
11129
|
+
},
|
|
11130
|
+
detailedBridge,
|
|
11131
|
+
boundaryPeek
|
|
11132
|
+
};
|
|
11133
|
+
}
|
|
11134
|
+
function renderHorizonPromptBlock(horizon) {
|
|
11135
|
+
const {
|
|
11136
|
+
targetLessonId,
|
|
11137
|
+
targetLessonIndex,
|
|
11138
|
+
totalLessons,
|
|
11139
|
+
targetTitle,
|
|
11140
|
+
targetKeywords,
|
|
11141
|
+
compactMasterySet,
|
|
11142
|
+
detailedBridge,
|
|
11143
|
+
boundaryPeek
|
|
11144
|
+
} = horizon;
|
|
11145
|
+
const lines = [
|
|
11146
|
+
`# \u{1F9ED} CURRICULUM HORIZON & SCAFFOLDING MATRIX (Lesson ${targetLessonIndex}/${totalLessons}: "${targetTitle}" [${targetLessonId}])`
|
|
11147
|
+
];
|
|
11148
|
+
if (compactMasterySet.masteredKeywords.length > 0 || compactMasterySet.masteredConcepts.length > 0) {
|
|
11149
|
+
const rawKeywords = compactMasterySet.masteredKeywords;
|
|
11150
|
+
const displayedKeywords = rawKeywords.length > 20 ? rawKeywords.slice(-20) : rawKeywords;
|
|
11151
|
+
const vocab = displayedKeywords.map((k) => `\`${k}\``).join(", ");
|
|
11152
|
+
const moreSuffix = rawKeywords.length > 20 ? ` *(+${rawKeywords.length - 20} earlier terms)*` : "";
|
|
11153
|
+
const concepts = compactMasterySet.masteredConcepts.length > 0 ? `
|
|
11154
|
+
- **Prior Concept Foundations:** ${compactMasterySet.masteredConcepts.slice(-3).join("; ")}` : "";
|
|
11155
|
+
lines.push(
|
|
11156
|
+
`
|
|
11157
|
+
## 1. \u{1F393} MASTERED VOCABULARY (Prior Lessons 1..${Math.max(1, targetLessonIndex - 3)} \u2014 Compact Set):`,
|
|
11158
|
+
`- **Mastered Terms & Syntax:** ${vocab || "(Core fundamentals)"}${moreSuffix}${concepts}`
|
|
11159
|
+
);
|
|
11160
|
+
} else if (targetLessonIndex === 1) {
|
|
11161
|
+
lines.push(
|
|
11162
|
+
`
|
|
11163
|
+
## 1. \u{1F393} PRIOR KNOWLEDGE FRONTIER:`,
|
|
11164
|
+
`- **Entry Point:** Inaugural lesson (Lesson 1/${totalLessons}). Students have no prior course vocabulary. All concepts must be introduced from baseline.`
|
|
11165
|
+
);
|
|
11166
|
+
}
|
|
11167
|
+
if (detailedBridge.length > 0) {
|
|
11168
|
+
lines.push(`
|
|
11169
|
+
## 2. \u{1F309} IMMEDIATE PREDECESSOR CONTEXT (Bridge Lessons):`);
|
|
11170
|
+
for (const bridge of detailedBridge) {
|
|
11171
|
+
lines.push(`### Lesson ${bridge.lessonIndex} (${bridge.lessonId}): ${bridge.title}`);
|
|
11172
|
+
if (bridge.proseObjective) {
|
|
11173
|
+
const obj = bridge.proseObjective.length > 120 ? bridge.proseObjective.slice(0, 117) + "..." : bridge.proseObjective;
|
|
11174
|
+
lines.push(`- **Objective:** ${obj}`);
|
|
11175
|
+
}
|
|
11176
|
+
if (bridge.keywords.length > 0) {
|
|
11177
|
+
lines.push(`- **Keywords:** ${bridge.keywords.map((k) => `\`${k}\``).join(", ")}`);
|
|
11178
|
+
}
|
|
11179
|
+
if (bridge.symbolLedger) {
|
|
11180
|
+
const parts = [];
|
|
11181
|
+
if (bridge.symbolLedger.primaryStructOrClass) {
|
|
11182
|
+
parts.push(`Primary Struct: \`${bridge.symbolLedger.primaryStructOrClass}\``);
|
|
11183
|
+
}
|
|
11184
|
+
if (bridge.symbolLedger.mainEntryFile) {
|
|
11185
|
+
parts.push(`Entry File: \`${bridge.symbolLedger.mainEntryFile}\``);
|
|
11186
|
+
}
|
|
11187
|
+
if (bridge.symbolLedger.keyVariables?.length) {
|
|
11188
|
+
parts.push(`Symbols: ${bridge.symbolLedger.keyVariables.map((v) => `\`${v}\``).join(", ")}`);
|
|
11189
|
+
}
|
|
11190
|
+
if (parts.length > 0) {
|
|
11191
|
+
lines.push(`- **Code Symbol Ledger:** ${parts.join(" | ")}`);
|
|
11192
|
+
}
|
|
11193
|
+
}
|
|
11194
|
+
}
|
|
11195
|
+
lines.push(
|
|
11196
|
+
`\u{1F449} INSTRUCTION: Seamlessly connect the opening Hook and Guided Practice by referencing the student's recent work from Lesson ${detailedBridge[detailedBridge.length - 1]?.lessonIndex}.`
|
|
11197
|
+
);
|
|
11198
|
+
}
|
|
11199
|
+
const currentKeywordsStr = targetKeywords.length > 0 ? targetKeywords.map((k) => `\`${k}\``).join(", ") : "`Current Lesson Concepts`";
|
|
11200
|
+
lines.push(
|
|
11201
|
+
`
|
|
11202
|
+
## 3. \u{1F3AF} ALLOWED DESIGN SPACE (Positive-Only Allow List):`,
|
|
11203
|
+
`- **Student Toolkit:** Mastered Vocabulary + Immediate Bridge Keywords + Current Lesson Scope (${currentKeywordsStr}).`,
|
|
11204
|
+
`- **MANDATE:** ALL extension challenges (EXT), hands-on activities (ACT), and assessment questions (QUIZ) MUST be 100% solvable using ONLY items within this toolkit. Do NOT require unintroduced APIs!`
|
|
11205
|
+
);
|
|
11206
|
+
if (boundaryPeek.length > 0) {
|
|
11207
|
+
const nextLesson = boundaryPeek[0];
|
|
11208
|
+
const nextKeywords = nextLesson.keywords.length > 0 ? nextLesson.keywords.map((k) => `\`${k}\``).join(", ") : "upcoming features";
|
|
11209
|
+
lines.push(
|
|
11210
|
+
`
|
|
11211
|
+
## 4. \u{1F6A7} BOUNDARY (Next Lesson Peek):`,
|
|
11212
|
+
`- **Upcoming Lesson ${nextLesson.lessonIndex} ("${nextLesson.title}"):** Introduces ${nextKeywords}.`,
|
|
11213
|
+
`- **BOUNDARY MANDATE:** These concepts are NOT yet available to the student. Do not introduce or require these future concepts.`
|
|
11214
|
+
);
|
|
11215
|
+
}
|
|
11216
|
+
return lines.join("\n");
|
|
11217
|
+
}
|
|
11218
|
+
function validateHorizonCompliance(artifactContent, horizon) {
|
|
11219
|
+
const issues = [];
|
|
11220
|
+
const forbiddenMatches = [];
|
|
11221
|
+
if (!artifactContent || horizon.boundaryPeek.length === 0) {
|
|
11222
|
+
return { compliant: true, issues: [], forbiddenMatches: [] };
|
|
11223
|
+
}
|
|
11224
|
+
const contentLower = artifactContent.toLowerCase();
|
|
11225
|
+
const immediateNext = horizon.boundaryPeek[0];
|
|
11226
|
+
if (immediateNext && immediateNext.keywords.length > 0) {
|
|
11227
|
+
for (const kw of immediateNext.keywords) {
|
|
11228
|
+
const trimmed = kw.trim().toLowerCase();
|
|
11229
|
+
if (trimmed.length <= 2) continue;
|
|
11230
|
+
const pattern = new RegExp(`\\b${trimmed.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`, "i");
|
|
11231
|
+
if (pattern.test(contentLower)) {
|
|
11232
|
+
const inMastered = horizon.compactMasterySet.masteredKeywords.some(
|
|
11233
|
+
(m) => m.toLowerCase() === trimmed
|
|
11234
|
+
);
|
|
11235
|
+
const inBridge = horizon.detailedBridge.some(
|
|
11236
|
+
(b) => b.keywords.some((bk) => bk.toLowerCase() === trimmed)
|
|
11237
|
+
);
|
|
11238
|
+
const inCurrent = horizon.targetKeywords.some(
|
|
11239
|
+
(ck) => ck.toLowerCase() === trimmed
|
|
11240
|
+
);
|
|
11241
|
+
if (!inMastered && !inBridge && !inCurrent) {
|
|
11242
|
+
forbiddenMatches.push(kw);
|
|
11243
|
+
issues.push(
|
|
11244
|
+
`Artifact contains boundary concept "${kw}" scheduled for future Lesson ${immediateNext.lessonIndex} ("${immediateNext.title}").`
|
|
11245
|
+
);
|
|
11246
|
+
}
|
|
11247
|
+
}
|
|
11248
|
+
}
|
|
11249
|
+
}
|
|
11250
|
+
return {
|
|
11251
|
+
compliant: issues.length === 0,
|
|
11252
|
+
issues,
|
|
11253
|
+
forbiddenMatches
|
|
11254
|
+
};
|
|
11255
|
+
}
|
|
11256
|
+
|
|
10691
11257
|
// src/services/contextBuilder.ts
|
|
10692
11258
|
var DEFAULT_LESSON_PRIORITIES = [
|
|
11259
|
+
"Symbol & Identifier Ledger",
|
|
11260
|
+
"Artifact Contract",
|
|
10693
11261
|
"A. Lesson Design Plan",
|
|
10694
11262
|
"B. Lesson Flow",
|
|
10695
11263
|
"Learning Objectives & Evidence",
|
|
@@ -10908,6 +11476,12 @@ function normalizeHeading(str) {
|
|
|
10908
11476
|
return str.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[^\w\s]/g, " ").replace(/\s+/g, " ").trim();
|
|
10909
11477
|
}
|
|
10910
11478
|
function findCanonicalFuzzy(norm) {
|
|
11479
|
+
if (norm.includes("symbol") || norm.includes("identifier ledger") || norm.includes("dinh danh")) {
|
|
11480
|
+
return "Symbol & Identifier Ledger";
|
|
11481
|
+
}
|
|
11482
|
+
if (norm.includes("artifact contract") || norm.includes("hop dong hoc lieu")) {
|
|
11483
|
+
return "Artifact Contract";
|
|
11484
|
+
}
|
|
10911
11485
|
if (norm.includes("lesson design plan") || norm.includes("ke hoach thiet ke")) {
|
|
10912
11486
|
return "A. Lesson Design Plan";
|
|
10913
11487
|
}
|
|
@@ -10958,6 +11532,39 @@ function deriveArtifactTypeFromFileName(fileName) {
|
|
|
10958
11532
|
const token = base.match(/^([A-Z][A-Z0-9_]*?)(?=_|$)/);
|
|
10959
11533
|
return token ? token[1] : void 0;
|
|
10960
11534
|
}
|
|
11535
|
+
function extractSymbolLedger(lessonMarkdown) {
|
|
11536
|
+
const result = {
|
|
11537
|
+
keySymbols: [],
|
|
11538
|
+
rawBlock: ""
|
|
11539
|
+
};
|
|
11540
|
+
if (!lessonMarkdown) return result;
|
|
11541
|
+
const ledgerMatch = lessonMarkdown.match(/###?\s*(?:Symbol & Identifier Ledger|Bảng Định Danh|Artifact Contract)[\s\S]*?(?=\n##|\n---|$)/i);
|
|
11542
|
+
if (ledgerMatch) {
|
|
11543
|
+
result.rawBlock = ledgerMatch[0].trim();
|
|
11544
|
+
}
|
|
11545
|
+
const structMatch = lessonMarkdown.match(/(?:Primary Struct|Primary Class|Primary Function|Main Component|Primary Module|Struct chính|Class chính|Hàm chính)[:\s*`]+([A-Za-z0-9_]+)/i);
|
|
11546
|
+
if (structMatch) {
|
|
11547
|
+
result.primarySymbol = structMatch[1];
|
|
11548
|
+
result.keySymbols.push(structMatch[1]);
|
|
11549
|
+
} else {
|
|
11550
|
+
const codeStructMatch = lessonMarkdown.match(/(?:struct|class|interface|type|def|function)\s+([A-Za-z0-9_]+)/);
|
|
11551
|
+
if (codeStructMatch) {
|
|
11552
|
+
const sym = codeStructMatch[1];
|
|
11553
|
+
result.primarySymbol = sym;
|
|
11554
|
+
result.keySymbols.push(sym);
|
|
11555
|
+
}
|
|
11556
|
+
}
|
|
11557
|
+
const fileMatch = lessonMarkdown.match(/(?:Entry File|Source File|Target File|Tên file mã nguồn)[:\s*`]+([A-Za-z0-9_\-\.]+\.[a-z0-9_]+)/i);
|
|
11558
|
+
if (fileMatch) {
|
|
11559
|
+
result.entryFileName = fileMatch[1];
|
|
11560
|
+
} else {
|
|
11561
|
+
const codeFileComment = lessonMarkdown.match(/(?:\/\/\s*|#\s*|\/\*\s*)([A-Za-z0-9_\-]+\.[a-zA-Z0-9]+)/);
|
|
11562
|
+
if (codeFileComment) {
|
|
11563
|
+
result.entryFileName = codeFileComment[1];
|
|
11564
|
+
}
|
|
11565
|
+
}
|
|
11566
|
+
return result;
|
|
11567
|
+
}
|
|
10961
11568
|
|
|
10962
11569
|
// src/services/knowledgeExpositionService.ts
|
|
10963
11570
|
var EXPOSITION_REL = (lessonCode) => "_sot/expositions/" + lessonCode + ".md";
|
|
@@ -10977,8 +11584,22 @@ var DEPTH_RULES = {
|
|
|
10977
11584
|
cio: "Write WHAT + WHY + HOW: include the mechanism, step by step, still technology-independent.",
|
|
10978
11585
|
sio: "Write WHAT + WHY + HOW + SPECIFIC: include concrete implementations and how the real project keywords are used."
|
|
10979
11586
|
};
|
|
10980
|
-
function
|
|
11587
|
+
function buildDomainLexiconGuardrail(techStack, hardwarePlatform) {
|
|
11588
|
+
const targetTech = (techStack || "").trim();
|
|
11589
|
+
const targetHw = (hardwarePlatform || "").trim();
|
|
11590
|
+
const platformAnchor = [targetTech, targetHw].filter(Boolean).join(" | ");
|
|
11591
|
+
if (!platformAnchor) {
|
|
11592
|
+
return `- \u{1F512} TECHNICAL DOMAIN ANCHOR: Strictly adhere to the declared curriculum domain. Use ONLY native, standard, idiomatic syntax and mechanisms. Strictly FORBIDDEN from using foreign paradigms, cross-domain jargon, or hallucinated APIs.`;
|
|
11593
|
+
}
|
|
11594
|
+
return `- \u{1F512} STRICT DOMAIN & TECHNICAL PLATFORM ANCHOR:
|
|
11595
|
+
\u2022 Declared Platform: "${platformAnchor}"
|
|
11596
|
+
\u2022 NATIVE PARADIGM ONLY: You MUST strictly use standard, idiomatic syntax, conventions, naming patterns, and mental models of "${platformAnchor}".
|
|
11597
|
+
\u2022 ANTI-CONTAMINATION INVARIANT: Strictly FORBIDDEN from importing or using syntax, units, keywords, tags, or concepts from unrelated/foreign ecosystems (e.g., do NOT leak Web/HTML/CSS tags or units into native mobile/embedded stacks, do NOT leak dynamic scripting into statically typed systems, do NOT use hardware/circuit terminology in pure-software courses).
|
|
11598
|
+
\u2022 REAL APIS ONLY: Every method, modifier, property, and API referenced must exist in the standard SDK of "${platformAnchor}".`;
|
|
11599
|
+
}
|
|
11600
|
+
function buildSystemPrompt(targetLanguage = "vi", techStack, hardwarePlatform) {
|
|
10981
11601
|
const langDirective = buildLanguageDirective(targetLanguage);
|
|
11602
|
+
const domainGuardrails = buildDomainLexiconGuardrail(techStack, hardwarePlatform);
|
|
10982
11603
|
return [
|
|
10983
11604
|
"You write KNOWLEDGE_EXPOSITION: self-study reading material for a student in a teacherless program.",
|
|
10984
11605
|
"Rules:",
|
|
@@ -10987,6 +11608,7 @@ function buildSystemPrompt(targetLanguage = "vi") {
|
|
|
10987
11608
|
"3. Respect each concept depth target (ULO/CIO/SIO) \u2014 do not overshoot.",
|
|
10988
11609
|
"4. Student-facing only: no teacher instructions, no classroom management text.",
|
|
10989
11610
|
"5. Output the full markdown document following the provided section skeleton exactly (headers and placeholders preserved).",
|
|
11611
|
+
domainGuardrails,
|
|
10990
11612
|
langDirective
|
|
10991
11613
|
].join("\n");
|
|
10992
11614
|
}
|
|
@@ -11023,7 +11645,7 @@ function buildUserPrompt(session, plan, glossary, targetLanguage = "vi", slcMark
|
|
|
11023
11645
|
].filter(Boolean).join("\n");
|
|
11024
11646
|
}
|
|
11025
11647
|
async function ensureKnowledgeExposition(options) {
|
|
11026
|
-
const { projectId, lessonCode, plan, glossary, llmFn, storage } = options;
|
|
11648
|
+
const { projectId, lessonCode, plan, glossary, llmFn, storage, techStack, hardwarePlatform } = options;
|
|
11027
11649
|
const session = plan.sessions.find((s) => s.id === lessonCode);
|
|
11028
11650
|
if (!session) throw new Error("Session " + lessonCode + " not found in plan " + plan.plan_id);
|
|
11029
11651
|
const existing = await storage.readArtifact(projectId, EXPOSITION_REL(lessonCode));
|
|
@@ -11038,7 +11660,7 @@ async function ensureKnowledgeExposition(options) {
|
|
|
11038
11660
|
}
|
|
11039
11661
|
const targetLanguage = options.targetLanguage || plan.translation?.target_language || "vi";
|
|
11040
11662
|
const content = (await llmFn(
|
|
11041
|
-
buildSystemPrompt(targetLanguage),
|
|
11663
|
+
buildSystemPrompt(targetLanguage, techStack, hardwarePlatform),
|
|
11042
11664
|
buildUserPrompt(session, plan, glossary, targetLanguage, options.slcMarkdown)
|
|
11043
11665
|
)).trim();
|
|
11044
11666
|
if (content.length < 200) {
|
|
@@ -11108,6 +11730,18 @@ async function ensureExpositionForLesson(storage, projectId, lessonCode, options
|
|
|
11108
11730
|
}
|
|
11109
11731
|
const scopedTerms = new Set(plan.glossary_scope.find((g) => g.session_id === lessonCode)?.terms ?? []);
|
|
11110
11732
|
glossary = glossary.filter((g) => scopedTerms.has(g.term));
|
|
11733
|
+
let hw = options.hardwarePlatform;
|
|
11734
|
+
let tech = options.techStack;
|
|
11735
|
+
if (!hw || !tech) {
|
|
11736
|
+
try {
|
|
11737
|
+
const lpRaw = await storage.readSotDocument(projectId, "LEARNER_PROFILE.md");
|
|
11738
|
+
if (lpRaw) {
|
|
11739
|
+
const hwMatch = lpRaw.match(/(?:Student Equipment|Hardware|Platform|Thiết bị)[:\s*`]+([^\n\r]+)/i);
|
|
11740
|
+
if (hwMatch && !hw) hw = hwMatch[1].trim();
|
|
11741
|
+
}
|
|
11742
|
+
} catch {
|
|
11743
|
+
}
|
|
11744
|
+
}
|
|
11111
11745
|
const { runCurriculumAIInference: runCurriculumAIInference2 } = await Promise.resolve().then(() => (init_streamRunner(), streamRunner_exports));
|
|
11112
11746
|
const targetLang = options.targetLanguage || plan.translation?.target_language || "vi";
|
|
11113
11747
|
const result = await ensureKnowledgeExposition({
|
|
@@ -11117,6 +11751,8 @@ async function ensureExpositionForLesson(storage, projectId, lessonCode, options
|
|
|
11117
11751
|
glossary,
|
|
11118
11752
|
targetLanguage: targetLang,
|
|
11119
11753
|
slcMarkdown: options.slcMarkdown,
|
|
11754
|
+
techStack: tech,
|
|
11755
|
+
hardwarePlatform: hw,
|
|
11120
11756
|
llmFn: async (systemPrompt, userPrompt) => {
|
|
11121
11757
|
const runnerOpts = options.customInference ? { customInference: options.customInference } : {};
|
|
11122
11758
|
const out = await runCurriculumAIInference2(
|
|
@@ -25143,7 +25779,11 @@ type: "LESSON_EDP"
|
|
|
25143
25779
|
|
|
25144
25780
|
### 3. Resource Map (Slide, Handout, Starter Code, Lab Tools)
|
|
25145
25781
|
### 4. Assessment Map (Rubric criteria, formative checkpoints, remediation paths)
|
|
25146
|
-
### 5. Artifact Contract
|
|
25782
|
+
### 5. Artifact Contract & Code Symbol Ledger
|
|
25783
|
+
- **Primary Struct / Class / Component:** [Explicit canonical identifier e.g. ProfileCardView]
|
|
25784
|
+
- **Entry Source File:** [Explicit file name e.g. ProfileCardView.swift]
|
|
25785
|
+
- **Key Functions & State Variables:** [Canonical identifiers used across all student activities]
|
|
25786
|
+
- **Artifact Specifications:** [Deliverable requirements for ACT, SLIDE, QUIZ, GUIDE, WKS, CODE, EXT]
|
|
25147
25787
|
|
|
25148
25788
|
---
|
|
25149
25789
|
|
|
@@ -25193,7 +25833,11 @@ type: "LESSON_GENERAL"
|
|
|
25193
25833
|
|
|
25194
25834
|
### 3. Resource Map
|
|
25195
25835
|
### 4. Assessment Map
|
|
25196
|
-
### 5. Artifact Contract
|
|
25836
|
+
### 5. Artifact Contract & Code Symbol Ledger
|
|
25837
|
+
- **Primary Struct / Class / Component:** [Explicit canonical identifier e.g. MainComponent]
|
|
25838
|
+
- **Entry Source File:** [Explicit file name e.g. MainComponent.ext]
|
|
25839
|
+
- **Key Functions & State Variables:** [Canonical identifiers used across all student activities]
|
|
25840
|
+
- **Artifact Specifications:** [Deliverable requirements for ACT, SLIDE, QUIZ, GUIDE, WKS, CODE, EXT]
|
|
25197
25841
|
|
|
25198
25842
|
---
|
|
25199
25843
|
|
|
@@ -25241,7 +25885,11 @@ type: "LESSON_5E"
|
|
|
25241
25885
|
|
|
25242
25886
|
### 3. Resource Map
|
|
25243
25887
|
### 4. Assessment Map
|
|
25244
|
-
### 5. Artifact Contract
|
|
25888
|
+
### 5. Artifact Contract & Code Symbol Ledger
|
|
25889
|
+
- **Primary Struct / Class / Component:** [Explicit canonical identifier e.g. MainComponent]
|
|
25890
|
+
- **Entry Source File:** [Explicit file name e.g. MainComponent.ext]
|
|
25891
|
+
- **Key Functions & State Variables:** [Canonical identifiers used across all student activities]
|
|
25892
|
+
- **Artifact Specifications:** [Deliverable requirements for ACT, SLIDE, QUIZ, GUIDE, WKS, CODE, EXT]
|
|
25245
25893
|
|
|
25246
25894
|
---
|
|
25247
25895
|
|
|
@@ -25420,6 +26068,32 @@ async function produceSingleLesson(storage, projectId, lessonId, options = {}) {
|
|
|
25420
26068
|
|
|
25421
26069
|
[CURRICULUM FRAMEWORK EXCERPT]:
|
|
25422
26070
|
${buildFrameworkExcerptForLesson(framework, lessonId)}`;
|
|
26071
|
+
let horizon = null;
|
|
26072
|
+
let horizonBlock = "";
|
|
26073
|
+
try {
|
|
26074
|
+
let planObj = null;
|
|
26075
|
+
const planRaw = await storage.readArtifact(projectId, "_sot/CURRICULUM_PLAN.json");
|
|
26076
|
+
if (planRaw) {
|
|
26077
|
+
try {
|
|
26078
|
+
planObj = JSON.parse(planRaw);
|
|
26079
|
+
} catch {
|
|
26080
|
+
}
|
|
26081
|
+
}
|
|
26082
|
+
horizon = await extractCurriculumHorizon({
|
|
26083
|
+
plan: planObj,
|
|
26084
|
+
frameworkMarkdown: framework,
|
|
26085
|
+
targetLessonId: lessonCode,
|
|
26086
|
+
storage,
|
|
26087
|
+
projectId
|
|
26088
|
+
});
|
|
26089
|
+
if (horizon) {
|
|
26090
|
+
horizonBlock = `
|
|
26091
|
+
|
|
26092
|
+
${renderHorizonPromptBlock(horizon)}`;
|
|
26093
|
+
}
|
|
26094
|
+
} catch (hErr) {
|
|
26095
|
+
console.warn(`[produceSingleLesson] Notice: Curriculum Horizon extraction:`, hErr?.message || hErr);
|
|
26096
|
+
}
|
|
25423
26097
|
const glossaryBlock = glossaryContext ? `
|
|
25424
26098
|
|
|
25425
26099
|
[GLOSSARY TERMS (use these exact definitions)]:
|
|
@@ -25440,7 +26114,7 @@ ${sessionSliceContext}` : "";
|
|
|
25440
26114
|
${sg}
|
|
25441
26115
|
|
|
25442
26116
|
[REFERENCE PACK GROUND TRUTH]:
|
|
25443
|
-
${ref}${standardsBlock}${glossaryBlock}${expositionBlock}${sessionSliceBlock}`;
|
|
26117
|
+
${ref}${standardsBlock}${glossaryBlock}${expositionBlock}${sessionSliceBlock}${horizonBlock}`;
|
|
25444
26118
|
let commonContext = assembleCommonContext(effectiveRefPack, effectiveStyleGuide);
|
|
25445
26119
|
const MAX_COMPOSITE_PROMPT_CHARS = 3e4;
|
|
25446
26120
|
const runnerOptions = options.customInference ? { customInference: options.customInference } : {};
|
|
@@ -25816,17 +26490,23 @@ ${currentContent}` }],
|
|
|
25816
26490
|
artifactType: "LESSON"
|
|
25817
26491
|
});
|
|
25818
26492
|
const lessonExcerpt = lessonExcerptResult.excerpt;
|
|
25819
|
-
|
|
25820
|
-
source: "LESSON",
|
|
26493
|
+
({
|
|
25821
26494
|
verified: lessonExcerptResult.verified,
|
|
25822
26495
|
sectionAware: lessonExcerptResult.sectionAware,
|
|
25823
26496
|
issues: lessonExcerptResult.issues
|
|
25824
|
-
};
|
|
25825
|
-
|
|
26497
|
+
});
|
|
26498
|
+
const symbolLedger = extractSymbolLedger(lessonContent);
|
|
26499
|
+
const symbolLedgerBlock = symbolLedger.primarySymbol ? `
|
|
26500
|
+
|
|
26501
|
+
[MANDATORY CODE SYMBOL REGISTRY (Inherited from LESSON Plan)]:
|
|
26502
|
+
- Primary Struct / Class / Component: \`${symbolLedger.primarySymbol}\`
|
|
26503
|
+
- Entry Source File: \`${symbolLedger.entryFileName || symbolLedger.primarySymbol}\`
|
|
26504
|
+
- Key Identifiers to inherit verbatim: ${symbolLedger.keySymbols.map((s) => `\`${s}\``).join(", ")}
|
|
26505
|
+
- INVARIANT: You MUST use these exact identifier names in all code examples, exercises, and test assertions. Do NOT invent new struct/class names!` : "";
|
|
25826
26506
|
const satelliteContext = `${commonContext}
|
|
25827
26507
|
|
|
25828
26508
|
[CANONICAL LESSON PLAN (${pedagogyLabel})]:
|
|
25829
|
-
${lessonExcerpt}`;
|
|
26509
|
+
${lessonExcerpt}${symbolLedgerBlock}`;
|
|
25830
26510
|
const judgeSat = (sat, content) => {
|
|
25831
26511
|
if (gateModeFor(gates, sat) !== "LLM_JUDGE") return Promise.resolve();
|
|
25832
26512
|
return judgeSatelliteArtifact({
|
|
@@ -26329,6 +27009,12 @@ ${buildHeadingDirective("CODE", slcMarkdown, targetLang)}`;
|
|
|
26329
27009
|
c3: "### Challenge 3: [Name] (Difficulty: \u2B50\u2B50\u2B50\u2B50\u2B50) \u2014 Extension Milestones (Level 1: Ninja \u2192 Level 2: Guru \u2192 Level 3: Master)",
|
|
26330
27010
|
meta: "## [REQUIRED] Metacognitive Deep Dive & Engineering Trade-offs"
|
|
26331
27011
|
};
|
|
27012
|
+
const zpdCeilingPrompt = horizon ? `
|
|
27013
|
+
4. \u{1F3AF} CURRICULUM HORIZON ALLOWED SPACE & BOUNDARY MANDATE:
|
|
27014
|
+
- Student's COMPLETE allowed toolkit = Mastered Vocabulary + Immediate Bridge + Current Lesson Scope (${horizon.targetKeywords.join(", ") || "Current Lesson Concepts"}).
|
|
27015
|
+
- All extension challenges MUST be 100% solvable using ONLY items within this toolkit.
|
|
27016
|
+
${horizon.boundaryPeek.length > 0 ? `- STRICT BOUNDARY: Upcoming Lesson ${horizon.boundaryPeek[0].lessonIndex} introduces "${horizon.boundaryPeek[0].title}" (${horizon.boundaryPeek[0].keywords.join(", ")}). These are STRICTLY OUT OF SCOPE. Do NOT introduce or require these future APIs.` : ""}` : `
|
|
27017
|
+
4. ZPD SCOPE: Focus on architectural edge cases, algorithmic optimization, and creative variations within current and past lesson concepts.`;
|
|
26332
27018
|
const extPrompt = `You are @activity (Lead STEM Specialist & Differentiated Extension Designer).
|
|
26333
27019
|
Based on Master Lesson Plan \`LESSON_${lessonCode}.md\`, author the advanced extension challenge: \`EXT_${lessonCode}.md\`.
|
|
26334
27020
|
|
|
@@ -26360,6 +27046,7 @@ ${extHeadings.sub}
|
|
|
26360
27046
|
- ${extHeadings.c3}
|
|
26361
27047
|
- ${extHeadings.meta}
|
|
26362
27048
|
3. Output 100% clean Markdown directly \u2014 NO code fences around the document.
|
|
27049
|
+
${zpdCeilingPrompt}
|
|
26363
27050
|
|
|
26364
27051
|
${languageDirective}
|
|
26365
27052
|
|
|
@@ -26890,6 +27577,29 @@ async function generateSingleArtifact(req) {
|
|
|
26890
27577
|
const artifactSpecializedPrompt = getArtifactSpecializedInvariants(artifactType, {
|
|
26891
27578
|
...req,
|
|
26892
27579
|
studentCount});
|
|
27580
|
+
const techStack = req.config?.techStack || req.hardwarePlatform?.[0] || "Declared Technical Domain";
|
|
27581
|
+
const domainGuardrail = buildDomainLexiconGuardrail(techStack, hwStr);
|
|
27582
|
+
const symbolLedger = contextSot.lessonPlan ? extractSymbolLedger(contextSot.lessonPlan) : null;
|
|
27583
|
+
const symbolLedgerPrompt = symbolLedger?.primarySymbol ? `
|
|
27584
|
+
7. MANDATORY CODE SYMBOL REGISTRY (Inherited from Canonical LESSON):
|
|
27585
|
+
- Primary Struct / Class: \`${symbolLedger.primarySymbol}\`
|
|
27586
|
+
- Entry Source File: \`${symbolLedger.entryFileName || symbolLedger.primarySymbol + ".swift"}\`
|
|
27587
|
+
- Key Identifiers to inherit: ${symbolLedger.keySymbols.map((s) => `\`${s}\``).join(", ")}
|
|
27588
|
+
- INVARIANT: You MUST use these exact identifier names in all code examples, exercises, and starter templates. Do NOT invent conflicting struct or class names.` : "";
|
|
27589
|
+
let horizon = contextSot.horizon || null;
|
|
27590
|
+
if (!horizon && (contextSot.plan || contextSot.framework) && lessonId) {
|
|
27591
|
+
try {
|
|
27592
|
+
horizon = await extractCurriculumHorizon({
|
|
27593
|
+
plan: contextSot.plan,
|
|
27594
|
+
frameworkMarkdown: contextSot.framework,
|
|
27595
|
+
targetLessonId: lessonId
|
|
27596
|
+
});
|
|
27597
|
+
} catch {
|
|
27598
|
+
}
|
|
27599
|
+
}
|
|
27600
|
+
const horizonPrompt = horizon ? `
|
|
27601
|
+
|
|
27602
|
+
${renderHorizonPromptBlock(horizon)}` : "";
|
|
26893
27603
|
const systemPrompt = `You are ${meta.persona}, an expert Curriculum & Pedagogical Specialist in the Curriculum OS Multi-Agent Team.
|
|
26894
27604
|
Your task is to author a canonical, publication-grade learning artifact of type "${artifactType.toUpperCase()}".
|
|
26895
27605
|
|
|
@@ -26900,7 +27610,10 @@ ${headingDirective}
|
|
|
26900
27610
|
3. PEDAGOGICAL GROUNDING: Strictly align with the ${pedagogy.toUpperCase()} model, Bloom's Revised Taxonomy, and IEEE/ACM CS2023 guidelines.
|
|
26901
27611
|
4. SCAFFOLDING LEVEL: "${scaffoldingLevel.toUpperCase()}".${scaffoldingRule}
|
|
26902
27612
|
5. TARGET AUDIENCE: ${gradeLevel ? `Grade ${gradeLevel}, ` : ""}${targetAge} on ${hwStr}.${deviceRule}${classDynamicsRule}${extraContextRule}
|
|
26903
|
-
6.
|
|
27613
|
+
6. DOMAIN & TECH STACK GUARDRAILS:
|
|
27614
|
+
${domainGuardrail}
|
|
27615
|
+
${symbolLedgerPrompt}
|
|
27616
|
+
8. OUTPUT INTEGRITY: Output clean Markdown starting directly with YAML frontmatter. Do not wrap entire response in triple backticks.${artifactSpecializedPrompt}${horizonPrompt}${mediaPolicy ? `
|
|
26904
27617
|
|
|
26905
27618
|
IMAGE PLANNING (media ledger) \u2014 QUOTA GEN: t\u1ED1i \u0111a ${mediaPolicy.maxGenImagesPerArtifact} \u1EA3nh AI cho artifact n\xE0y (\u1EA3nh search kho kh\xF4ng gi\u1EDBi h\u1EA1n).
|
|
26906
27619
|
Khi n\u1ED9i dung c\u1EA7n minh h\u1ECDa (diagram quy tr\xECnh, s\u01A1 \u0111\u1ED3 kh\xE1i ni\u1EC7m, step-by-step visual), ch\xE8n placeholder \u0111\xFAng format [IMAGE: slug] (slug ch\u1EEF th\u01B0\u1EDDng-g\u1EA1ch ngang, unique trong b\xE0i, vd img-for-loop-diagram) T\u1EA0I \u0110\xDANG CH\u1ED6 c\u1EA7n \u1EA3nh, tr\xEAn d\xF2ng ri\xEAng. KH\xD4NG t\u1EF1 sinh \u1EA3nh, KH\xD4NG d\xF9ng markdown image \u2014 placeholder s\u1EBD \u0111\u01B0\u1EE3c thay b\u1EB1ng \u1EA3nh th\u1EADt sau khi Media Curator duy\u1EC7t. Ch\u1EC9 \u0111\u1EB7t cho \u1EA3nh TH\u1EF0C S\u1EF0 c\u1EA7n thi\u1EBFt.` : ""}`;
|
|
@@ -27380,6 +28093,20 @@ ${includeUnitTests ? "- Unit Testing: Provide automated assertions or test runne
|
|
|
27380
28093
|
1. **CLASS-SCALED INVENTORY FOR ${studentCount} STUDENTS**:
|
|
27381
28094
|
- Exact quantities calculated for ${studentCount} students.
|
|
27382
28095
|
- Component specifications, estimated unit cost, and affordable alternatives.`;
|
|
28096
|
+
case "ext": {
|
|
28097
|
+
const zpdCeilingRule = `- \u{1F3AF} CURRICULUM HORIZON ALLOWED SPACE & BOUNDARY MANDATE:
|
|
28098
|
+
\u2022 SCOPE LOCK: All extension challenges MUST be 100% solvable using ONLY items from the student's Mastered Vocabulary and Current Lesson Scope.
|
|
28099
|
+
\u2022 STRICTLY FORBIDDEN: NEVER introduce unintroduced APIs or mechanisms from upcoming lessons (No complex event buses, No remote network/API calls, No persistent databases, No async concurrency, No out-of-scope hardware/sensors unless explicitly part of the allowed toolkit).`;
|
|
28100
|
+
return `
|
|
28101
|
+
### \u{1F680} EXTENSION CHALLENGE ARCHITECTURE INVARIANTS:
|
|
28102
|
+
${zpdCeilingRule}
|
|
28103
|
+
1. **STRUCTURED 3-TIER CHALLENGES**:
|
|
28104
|
+
- Challenge 1 (Mission Briefing): High-stakes real-world scenario applying today's concepts.
|
|
28105
|
+
- Challenge 2 (Architectural Constraints & Edge Cases): Deeper technical rules without violating prerequisite boundaries.
|
|
28106
|
+
- Challenge 3 (Extension Milestones): Progressive milestones (Level 1: Novice \u2192 Level 2: Advanced \u2192 Level 3: Master).
|
|
28107
|
+
2. **METACOGNITIVE & ENGINEERING TRADE-OFFS**:
|
|
28108
|
+
- Explicit reflection prompts evaluating architectural choices, code maintainability, and design patterns.`;
|
|
28109
|
+
}
|
|
27383
28110
|
case "lesson": {
|
|
27384
28111
|
const lessonDuration = cfg.lessonDuration || 90;
|
|
27385
28112
|
const pedagogicalModel = cfg.pedagogicalModel || req.pedagogy || "5e";
|
|
@@ -31203,6 +31930,7 @@ exports.buildCurriculumContext = buildCurriculumContext;
|
|
|
31203
31930
|
exports.buildCurriculumPlan = buildCurriculumPlan;
|
|
31204
31931
|
exports.buildDeliveryPackages = buildDeliveryPackages;
|
|
31205
31932
|
exports.buildDiagnosticQuizPrompt = buildDiagnosticQuizPrompt;
|
|
31933
|
+
exports.buildDomainLexiconGuardrail = buildDomainLexiconGuardrail;
|
|
31206
31934
|
exports.buildExpositionContext = buildExpositionContext;
|
|
31207
31935
|
exports.buildExpositionExcerpt = buildExpositionExcerpt;
|
|
31208
31936
|
exports.buildExtensionPrompt = buildExtensionPrompt;
|
|
@@ -31248,11 +31976,14 @@ exports.executeCurriculumCommand = executeCurriculumCommand;
|
|
|
31248
31976
|
exports.executeSlideProductionWorkflow = executeSlideProductionWorkflow;
|
|
31249
31977
|
exports.exportAllProjectQuizzes = exportAllProjectQuizzes;
|
|
31250
31978
|
exports.expositionCacheKey = expositionCacheKey;
|
|
31979
|
+
exports.extractCurriculumHorizon = extractCurriculumHorizon;
|
|
31980
|
+
exports.extractJsonArray = extractJsonArray;
|
|
31251
31981
|
exports.extractScopeSequenceRows = extractScopeSequenceRows;
|
|
31252
31982
|
exports.extractSectionHeadingsFromSLC = extractSectionHeadingsFromSLC;
|
|
31253
31983
|
exports.extractSessionSlice = extractSessionSlice;
|
|
31254
31984
|
exports.extractStandardRefs = extractStandardRefs;
|
|
31255
31985
|
exports.extractStreamChunk = extractStreamChunk;
|
|
31986
|
+
exports.extractSymbolLedger = extractSymbolLedger;
|
|
31256
31987
|
exports.extractThoughtAndContent = extractThoughtAndContent;
|
|
31257
31988
|
exports.findStandardStatement = findStandardStatement;
|
|
31258
31989
|
exports.formatQuizzesToCsv = formatQuizzesToCsv;
|
|
@@ -31305,6 +32036,7 @@ exports.loadSotTemplate = loadSotTemplate;
|
|
|
31305
32036
|
exports.normalizePlanningGraph = normalizePlanningGraph;
|
|
31306
32037
|
exports.normalizeSlcContract = normalizeSlcContract;
|
|
31307
32038
|
exports.packagerTools = packagerTools;
|
|
32039
|
+
exports.parseAllSessions = parseAllSessions;
|
|
31308
32040
|
exports.parseGateSettings = parseGateSettings;
|
|
31309
32041
|
exports.parseQuizMarkdown = parseQuizMarkdown;
|
|
31310
32042
|
exports.parseRoadmapJsonToProjectPayload = parseRoadmapJsonToProjectPayload;
|
|
@@ -31314,6 +32046,7 @@ exports.publishToGitHub = publishToGitHub;
|
|
|
31314
32046
|
exports.publishToSupabase = publishToSupabase;
|
|
31315
32047
|
exports.rankGenCandidates = rankGenCandidates;
|
|
31316
32048
|
exports.renderFrameworkFromPlan = renderFrameworkFromPlan;
|
|
32049
|
+
exports.renderHorizonPromptBlock = renderHorizonPromptBlock;
|
|
31317
32050
|
exports.renderMediaPlaceholder = renderMediaPlaceholder;
|
|
31318
32051
|
exports.researcherTools = researcherTools;
|
|
31319
32052
|
exports.resolveGateSettings = resolveGateSettings;
|
|
@@ -31350,6 +32083,8 @@ exports.uploadAssetToBucket = uploadAssetToBucket;
|
|
|
31350
32083
|
exports.validateArtifactDependencies = validateArtifactDependencies;
|
|
31351
32084
|
exports.validateCurriculumPlan = validateCurriculumPlan;
|
|
31352
32085
|
exports.validateFrameworkPack = validateFrameworkPack;
|
|
32086
|
+
exports.validateHorizonCompliance = validateHorizonCompliance;
|
|
32087
|
+
exports.validateHybridDeckSlides = validateHybridDeckSlides;
|
|
31353
32088
|
exports.validateMarkdownTables = validateMarkdownTables;
|
|
31354
32089
|
exports.validateMermaidSyntax = validateMermaidSyntax;
|
|
31355
32090
|
exports.withAutoRepair = withAutoRepair;
|