retiam 2.0.1 → 2.0.3

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.
Files changed (63) hide show
  1. package/THIRD_PARTY_NOTICES.md +3 -3
  2. package/agent/AGENTS.md +70 -0
  3. package/agent/CLAUDE.md +2 -0
  4. package/agent/skill/SKILL.md +114 -0
  5. package/agent/skill/examples/AreaLesson.ts +48 -0
  6. package/agent/skill/examples/ExplainMotion.ts +32 -0
  7. package/agent/skill/examples/NarratedMotion.ts +50 -0
  8. package/agent/skill/examples/SecantToTangent.ts +142 -0
  9. package/agent/skill/references/api.md +111 -0
  10. package/agent/skill/references/explainers.md +123 -0
  11. package/agent/skill/references/quality.md +151 -0
  12. package/agent/skill/references/styles.md +58 -0
  13. package/agent/skill/references/tts.md +166 -0
  14. package/agent/templates/explainer/lesson.json +25 -0
  15. package/agent/templates/explainer/review.json +6 -0
  16. package/agent/templates/explainer/styles.json +92 -0
  17. package/bin/agent-kit.js +26 -0
  18. package/bin/cli.js +24 -4
  19. package/bin/context.js +57 -0
  20. package/bin/init.js +71 -12
  21. package/bin/lesson-project.js +30 -0
  22. package/bin/lesson.js +195 -0
  23. package/bin/lint.js +6 -6
  24. package/bin/snapshot-runtime.js +185 -0
  25. package/bin/snapshot.js +32 -225
  26. package/dist/THIRD_PARTY_NOTICES.md +3 -3
  27. package/dist/UTIF-D6vx2UEX.min.js +1 -0
  28. package/dist/{UTIF-2Ear_0Uo.js → UTIF-mkffT70R.js} +1 -1
  29. package/dist/decompress-D7LzbvPD.min.js +15 -0
  30. package/dist/{index-B2sJBy4c.js → index-CdBPrYbr.js} +3830 -3795
  31. package/dist/index-jgFyrc2D.min.js +10472 -0
  32. package/dist/libs/explainer/example.d.ts +3 -0
  33. package/dist/libs/explainer/index.d.ts +5 -0
  34. package/dist/libs/explainer/index.js +135 -0
  35. package/dist/libs/explainer/plan.d.ts +12 -0
  36. package/dist/libs/explainer/plan.js +219 -0
  37. package/dist/libs/explainer/runtime.d.ts +24 -0
  38. package/dist/libs/explainer/styles.d.ts +3 -0
  39. package/dist/libs/explainer/types.d.ts +124 -0
  40. package/dist/libs/explainer/visuals.d.ts +18 -0
  41. package/dist/libs/tts/animations/Speak.d.ts +13 -7
  42. package/dist/libs/tts/components/TTSNarrationComponent.d.ts +9 -7
  43. package/dist/libs/tts/core/TTSCache.d.ts +3 -3
  44. package/dist/libs/tts/core/TTSClient.d.ts +3 -3
  45. package/dist/libs/tts/core/types.d.ts +48 -6
  46. package/dist/libs/tts/index.d.ts +2 -1
  47. package/dist/libs/tts/index.js +203 -1333
  48. package/dist/libs/tts/node/index.d.ts +10 -0
  49. package/dist/libs/tts/node/index.js +791 -0
  50. package/dist/libs/tts/providers/ElevenLabsTTSProvider.d.ts +30 -0
  51. package/dist/libs/tts/providers/ITTSProvider.d.ts +2 -2
  52. package/dist/libs/vite/index.d.ts +28 -0
  53. package/dist/libs/vite/index.js +158 -0
  54. package/dist/main-Cc4QQbSK.min.js +5939 -0
  55. package/dist/{main-D_G4uIju.js → main-DMu_5Qy7.js} +1 -1
  56. package/dist/msEdgeV2-D3w4Xsn-.js +642 -0
  57. package/dist/opentype.module-B-1cYAb-.min.js +1 -0
  58. package/dist/retiam.es.js +1 -1
  59. package/dist/retiam.es.min.js +1 -0
  60. package/dist/src/engine/animation/Animation.d.ts +1 -0
  61. package/dist/src/engine/entities/composites/text/mathjaxInterop.d.ts +6 -0
  62. package/package.json +15 -8
  63. package/readme.md +27 -7
@@ -0,0 +1,3 @@
1
+ import { LessonPlan, StyleInput } from './types';
2
+ /** A small executable reference, to replace with the requested subject's construction. */
3
+ export declare function createExampleLesson(style?: StyleInput): LessonPlan;
@@ -0,0 +1,5 @@
1
+ /** Teaching plans, visual profiles, narration cues, and a shared production clock. */
2
+ export * from './plan';
3
+ export * from './runtime';
4
+ export * from './visuals';
5
+ export * from './example';
@@ -0,0 +1,135 @@
1
+ import { assertLesson as S, voiceKey as M, speechKey as y, compileLesson as E, resolveStyle as A } from "./plan.js";
2
+ import { STYLE_PROFILES as ae, lessonCaptions as oe, validateLesson as se } from "./plan.js";
3
+ import { CustomAnimation as x, TrackAnimation as q, Waiting as C, hex as v, Color as $, Excalidraw as z, Create as N, Tex as D, CENTER as L, Rate as k } from "retiam";
4
+ import { TTS as T } from "retiam/tts";
5
+ /*! Retiam: noncommercial use only unless separately licensed. See ../../LICENSE and ../../COMMERCIAL_LICENSE.md. Third-party notices: ../../THIRD_PARTY_NOTICES.md and ../../THIRD_PARTY_LICENSES.txt. */
6
+ function Y(r, t, i = {}) {
7
+ S(t);
8
+ const u = (async () => {
9
+ const s = /* @__PURE__ */ new Map();
10
+ let a = i.prepared;
11
+ if (t.narration.enabled) {
12
+ const f = t.narration.provider ?? "msedge";
13
+ if (!a && !i.synthesize && T.provider.name !== (f === "msedge" ? "msedge-tts" : f))
14
+ throw new Error("Initialize and select the lesson narration provider, or provide prepared speech or a synthesize adapter.");
15
+ const c = r.audio;
16
+ if (a && a.voiceKey !== M(t))
17
+ throw new Error("Prepared narration uses different voice settings. Prepare narration again.");
18
+ a = a ?? { version: 1, voiceKey: M(t), assets: {} };
19
+ const p = t.beats.flatMap((e) => e.narration.map((o) => ({ beat: e.id, segment: o })));
20
+ let w = 0;
21
+ await Promise.all(Array.from({ length: Math.min(3, p.length) }, async () => {
22
+ for (; w < p.length; ) {
23
+ const e = p[w++], o = y(e.beat, e.segment.id);
24
+ let g;
25
+ if (i.prepared) {
26
+ const d = a.assets[o];
27
+ if (!d || d.text !== e.segment.text || (d.speechText ?? d.text) !== (e.segment.speechText ?? e.segment.text) || !d.url)
28
+ throw new Error(`Missing/stale prepared asset: ${o}.`);
29
+ const l = await fetch(d.url);
30
+ if (!l.ok)
31
+ throw new Error(`Narration asset ${d.url}: HTTP ${l.status}.`);
32
+ if (g = await c.context.decodeAudioData(await l.arrayBuffer()), Math.abs(g.duration * 1e3 - d.durationMs) > 120)
33
+ throw new Error(`Narration duration mismatch: ${o}.`);
34
+ } else {
35
+ const d = e.segment.speechText ?? e.segment.text, l = await (i.synthesize?.(d) ?? T.synthesize(d, { voice: t.narration.voice ?? (f === "msedge" ? "en-US-AndrewNeural" : void 0), speed: t.narration.speed, pitch: t.narration.pitch, volume: 1, displayText: e.segment.text, elevenlabs: t.narration.elevenlabs }));
36
+ a.assets[o] = { text: e.segment.text, speechText: e.segment.speechText, durationMs: l.durationMs, words: (l.wordBoundaries ?? []).map((n) => ({ text: n.text, offsetMs: n.offsetMs, durationMs: n.durationMs })) }, g = l.rawBuffer ? await c.context.decodeAudioData(l.rawBuffer.slice(0)) : l.toAudioBuffer(c.context);
37
+ }
38
+ s.set(o, g);
39
+ }
40
+ }));
41
+ }
42
+ const h = E(t, a, i.fps ?? 60), m = A(t.style);
43
+ return { plan: t, timeline: h, style: m, async run(f) {
44
+ const c = new Set(t.beats.map((e) => e.id));
45
+ for (const e of Object.keys(f))
46
+ if (!c.has(e))
47
+ throw new Error(`Unknown beat handler '${e}'.`);
48
+ const p = [];
49
+ for (const e of h.beats) {
50
+ const o = f[e.id];
51
+ if (!o)
52
+ throw new Error(`Missing beat handler '${e.id}'.`);
53
+ const g = (n) => {
54
+ const b = e.cues[n];
55
+ if (b === void 0)
56
+ throw new Error(`Unknown cue '${e.id}/${n}'.`);
57
+ return b;
58
+ }, d = o({ beat: e, durationMs: e.durationMs, style: m, cue: g, at: (n, b) => new x(() => {
59
+ }, { delay: g(n), duration: 1, onStart: b }) });
60
+ for (const n of d)
61
+ if (!Number.isFinite(n.delay) || !Number.isFinite(n.duration) || n.delay < 0 || n.duration < 0 || n.delay + n.duration > e.durationMs + 0.1)
62
+ throw new Error(`Animation exceeds beat '${e.id}' (${e.durationMs} ms).`);
63
+ const l = new q([...d, new C({ duration: e.durationMs })]);
64
+ l.delay = e.startMs, p.push(l);
65
+ }
66
+ const w = r.composer?.time ?? 0;
67
+ if (t.narration.enabled)
68
+ for (const e of h.beats)
69
+ for (const o of e.segments) {
70
+ const g = s.get(y(e.id, o.id));
71
+ r.audio.timeline.addClip(g, w + e.startMs + o.startMs, r.audio.voiceoverGain, { volume: t.narration.volume ?? 1, bus: "voiceover" }), r.audio.timeline.addVolumeAutomation("bgm", w + e.startMs + o.startMs, r.audio.bgmVolume * 0.25, 200), r.audio.timeline.addVolumeAutomation("bgm", w + e.startMs + o.startMs + o.durationMs, r.audio.bgmVolume, 200);
72
+ }
73
+ r.clearColor(v(m.background)), await r.play(new q(p));
74
+ } };
75
+ })();
76
+ return r.composer?.pendingTasks.push(u), u;
77
+ }
78
+ function J(r, t, i = "primary", u = !1) {
79
+ const s = t[i];
80
+ return r.setStroke(t.strokeWidth, v(s)), r.setFill(u ? v(s).alpha(0.18) : $.NONE), t.rough && r.toExcalidraw({ preset: "clean", stroke: s, strokeWidth: t.strokeWidth, roughness: t.roughness, fill: u ? s : "transparent", fillStyle: t.fillStyle, fillGap: 0.18, fillAlpha: 0.1, seed: 17, boil: !1 }), r;
81
+ }
82
+ function Q(r, t, i = 0, u = t.motion.drawMs) {
83
+ return t.rough ? z.createAnim(r, { delay: i, duration: u }) : new N(r, { delay: i, duration: u });
84
+ }
85
+ function X(r, t, i, u, s, a = {}) {
86
+ const h = new D(t, { fontSize: 0.42, anchor: L, fill: v(s.primary), opacity: 0 });
87
+ r.add(h);
88
+ let m, f;
89
+ return new x((c) => {
90
+ h.transform.position.set([m[0] + (f[0] - m[0]) * c, m[1] + (f[1] - m[1]) * c + (a.arc ?? 0.45) * Math.sin(Math.PI * c), m[2] + (f[2] - m[2]) * c]), h.setOpacity(Math.min(1, c * 8, (1 - c) * 8));
91
+ }, { delay: a.delay ?? 0, duration: a.duration ?? s.motion.moveMs, rate_func: k.smooth(), onStart: () => {
92
+ m = i(), f = u();
93
+ }, onComplete: () => r.remove(h) });
94
+ }
95
+ function Z(r, t = 0, i = 900, u = 0.1) {
96
+ let s;
97
+ return new x((a) => {
98
+ const h = 1 + u * Math.sin(Math.PI * a);
99
+ r.transform.setScale([s[0] * h, s[1] * h, s[2]]);
100
+ }, { delay: t, duration: i, rate_func: k.smooth(), onStart: () => {
101
+ const a = r.transform.getScale();
102
+ s = [a.x, a.y, a.z];
103
+ } });
104
+ }
105
+ const I = 1, P = "Why does area grow so fast?", B = "How a square's area changes when its side length doubles", F = "Curious beginners", H = "See why doubling both side lengths produces four unit squares.", K = "mathematical", O = { enabled: !1, provider: "msedge", voice: "en-US-AndrewNeural", speed: 1, pitch: "+0Hz", volume: 1 }, V = [], W = [{ id: "area-model", text: "A square of side two can be partitioned into four squares of side one.", sourceIds: [], illustrative: !0 }], j = [{ id: "predict", goal: "Invite a prediction before exposing the area.", visibleChange: "Draw one unit square and mark its side length.", focus: "The original square", minDurationMs: 4500, narration: [{ id: "question", text: "Here is one unit square. If we double its side length, what happens to its area?", pauseAfterMs: 900 }], cues: [{ id: "show", segment: "question" }] }, { id: "grow", goal: "Separate a change in side length from a change in area.", visibleChange: "Expand the outline while retaining the original square.", focus: "Both sides grow", minDurationMs: 5e3, narration: [{ id: "expand", text: "Let's double both sides. The original square still fits in the corner. How much new space have we made?", pauseAfterMs: 700 }], cues: [{ id: "expand", segment: "expand" }] }, { id: "count", goal: "Discover the four equal areas through decomposition.", visibleChange: "Reveal the three additional unit squares in sequence.", focus: "Four equal pieces", minDurationMs: 5e3, narration: [{ id: "reveal", text: "One here, another above, and one more in the corner. Together with our original square, that makes four." }], cues: [{ id: "reveal", segment: "reveal" }] }, { id: "connect", goal: "Connect the construction to the general area relationship.", visibleChange: "Keep all four pieces visible and reveal the equation below them.", focus: "Side length and area", minDurationMs: 4500, narration: [{ id: "insight", text: "Two times the side length gives four times the area. That's the square in side squared: two growing directions, multiplied together." }], cues: [{ id: "equation", segment: "insight" }] }], G = {
106
+ version: I,
107
+ title: P,
108
+ topic: B,
109
+ audience: F,
110
+ objective: H,
111
+ style: K,
112
+ narration: O,
113
+ sources: V,
114
+ claims: W,
115
+ beats: j
116
+ };
117
+ function ee(r = "mathematical") {
118
+ return { ...structuredClone(G), style: r };
119
+ }
120
+ export {
121
+ ae as STYLE_PROFILES,
122
+ S as assertLesson,
123
+ E as compileLesson,
124
+ ee as createExampleLesson,
125
+ Q as drawGeometry,
126
+ Z as emphasize,
127
+ oe as lessonCaptions,
128
+ Y as prepareLesson,
129
+ A as resolveStyle,
130
+ y as speechKey,
131
+ J as styleGeometry,
132
+ X as transferValue,
133
+ se as validateLesson,
134
+ M as voiceKey
135
+ };
@@ -0,0 +1,12 @@
1
+ import { LessonPlan, PreparedSpeech, CompiledLesson, Diagnostic } from './types';
2
+ export * from './types';
3
+ export { STYLE_PROFILES, resolveStyle } from './styles';
4
+ export declare const speechKey: (beat: string, segment: string) => string;
5
+ export declare const voiceKey: (plan: LessonPlan) => string;
6
+ /** Structural checks do not claim that facts, visuals, or teaching quality are correct. */
7
+ export declare function validateLesson(value: unknown): Diagnostic[];
8
+ export declare function assertLesson(value: unknown): asserts value is LessonPlan;
9
+ /** Compile one shared clock. Timing estimates are explicitly labeled until speech is prepared. */
10
+ export declare function compileLesson(plan: LessonPlan, prepared?: PreparedSpeech, fps?: number): CompiledLesson;
11
+ /** Cue groups never span a segment boundary or expose the answer during a prediction pause. */
12
+ export declare function lessonCaptions(timeline: CompiledLesson, wordsPerCue?: number): string;
@@ -0,0 +1,219 @@
1
+ /*! Retiam: noncommercial use only unless separately licensed. See ../../LICENSE and ../../COMMERCIAL_LICENSE.md. Third-party notices: ../../THIRD_PARTY_NOTICES.md and ../../THIRD_PARTY_LICENSES.txt. */
2
+ const w = { name: "Dijkstra-style mathematical explainer", background: "#0A1020", ink: "#ECF0F5", muted: "#74869C", primary: "#6CD5FF", secondary: "#F7CF73", font: "roboto", strokeWidth: 0.035, rough: !1, roughness: 0, fillStyle: "solid", motion: { drawMs: 1200, moveMs: 1400, holdMs: 700 }, accents: ["#65F0C4", "#FF879F", "#BA98FF", "#55DDE0"] }, x = { name: "Excalidraw whiteboard", background: "#FFFDF7", ink: "#263238", muted: "#7B858C", primary: "#1971C2", secondary: "#D46B08", font: "excalifont", strokeWidth: 0.04, rough: !0, roughness: 0.7, fillStyle: "hachure", motion: { drawMs: 1600, moveMs: 1500, holdMs: 900 }, accents: ["#1971C2", "#D46B08"] }, k = { name: "Minimal vector", background: "#F8FAFC", ink: "#17212B", muted: "#68788A", primary: "#2563EB", secondary: "#C2410C", font: "roboto", strokeWidth: 0.025, rough: !1, roughness: 0, fillStyle: "solid", motion: { drawMs: 900, moveMs: 1100, holdMs: 650 }, accents: ["#2563EB", "#C2410C"] }, E = { name: "Cinematic diagrams", background: "#080B18", ink: "#F4F1FF", muted: "#8290AA", primary: "#B79AFF", secondary: "#58E0CE", font: "roboto", strokeWidth: 0.04, rough: !1, roughness: 0, fillStyle: "solid", motion: { drawMs: 1500, moveMs: 2e3, holdMs: 1e3 }, accents: ["#B79AFF", "#58E0CE"] }, A = {
3
+ mathematical: w,
4
+ excalidraw: x,
5
+ minimal: k,
6
+ cinematic: E
7
+ }, b = A;
8
+ function F(i) {
9
+ const u = typeof i == "string" ? i : i?.extends;
10
+ if (!Object.hasOwn(b, u ?? ""))
11
+ throw new Error(`Unknown style '${u}'. Choose ${Object.keys(b).join(", ")} or extend one in the lesson JSON.`);
12
+ const e = b[u], s = typeof i == "string" ? {} : i;
13
+ for (const c of Object.keys(s))
14
+ if (c !== "extends" && !Object.hasOwn(e, c))
15
+ throw new Error(`Unknown style token '${c}'.`);
16
+ if (s.motion !== void 0 && (!s.motion || typeof s.motion != "object" || Array.isArray(s.motion)))
17
+ throw new Error("Style motion must be an object.");
18
+ for (const c of Object.keys(s.motion ?? {}))
19
+ if (!Object.hasOwn(e.motion, c))
20
+ throw new Error(`Unknown style motion token '${c}'.`);
21
+ const r = { ...e, ...s, motion: { ...e.motion, ...s.motion } };
22
+ for (const c of ["background", "ink", "muted", "primary", "secondary"])
23
+ if (!/^#[\da-f]{6}$/i.test(r[c]))
24
+ throw new Error(`Style ${c} must be a six-digit hex color.`);
25
+ if (!r.font?.trim() || !r.name?.trim() || typeof r.rough != "boolean" || !["solid", "hachure"].includes(r.fillStyle))
26
+ throw new Error("Invalid style font, name, rough flag, or fillStyle.");
27
+ if (!(r.strokeWidth > 0 && r.strokeWidth <= 0.5) || !(r.roughness >= 0 && r.roughness <= 3))
28
+ throw new Error("Invalid style strokeWidth or roughness.");
29
+ for (const [c, l] of Object.entries(r.motion))
30
+ if (!Number.isFinite(l) || l < 0)
31
+ throw new Error(`Invalid style motion.${c}.`);
32
+ if (!Array.isArray(r.accents) || !r.accents.length || r.accents.some((c) => !/^#[\da-f]{6}$/i.test(c)))
33
+ throw new Error("Style accents must contain hex colors.");
34
+ return { ...r, accents: [...r.accents] };
35
+ }
36
+ const y = /^[a-z][a-z0-9-]*$/, h = (i) => typeof i == "string" && i.trim().length > 0, g = (i) => i.toLowerCase().replace(/[^\p{L}\p{N}]/gu, ""), S = (i, u) => `${i}/${u}`, I = (i) => JSON.stringify({ provider: i.narration.provider ?? "msedge", voice: i.narration.voice ?? ((i.narration.provider ?? "msedge") === "msedge" ? "en-US-AndrewNeural" : null), speed: i.narration.speed ?? 1, pitch: i.narration.pitch ?? ((i.narration.provider ?? "msedge") === "msedge" ? "+0Hz" : null), elevenlabs: i.narration.elevenlabs });
37
+ function D(i) {
38
+ const u = [], e = (t, o) => u.push({ path: t, message: o });
39
+ if (!i || typeof i != "object" || Array.isArray(i))
40
+ return [{ path: "$", message: "Expected a lesson object." }];
41
+ const s = i;
42
+ s.version !== 1 && e("version", "Expected version 1.");
43
+ for (const t of ["title", "topic", "audience", "objective"])
44
+ h(s[t]) || e(t, "Provide a nonempty string.");
45
+ try {
46
+ F(s.style);
47
+ } catch (t) {
48
+ e("style", t.message);
49
+ }
50
+ (!s.narration || typeof s.narration.enabled != "boolean") && e("narration.enabled", "Choose true or false."), s.narration?.speed !== void 0 && (!Number.isFinite(s.narration.speed) || s.narration.speed <= 0) && e("narration.speed", "Must be positive."), s.narration?.volume !== void 0 && (!Number.isFinite(s.narration.volume) || s.narration.volume < 0 || s.narration.volume > 1) && e("narration.volume", "Must be between 0 and 1."), s.narration?.voice !== void 0 && !h(s.narration.voice) && e("narration.voice", "Must be a voice ID.");
51
+ for (const t of ["provider", "pitch"])
52
+ s.narration?.[t] !== void 0 && !h(s.narration[t]) && e(`narration.${t}`, "Must be a nonempty string.");
53
+ s.narration?.provider === "elevenlabs" && !h(s.narration.voice) && e("narration.voice", "Choose an ElevenLabs voice ID so prepared speech identifies its speaker.");
54
+ const r = s.narration?.elevenlabs;
55
+ if (r !== void 0) {
56
+ const t = "narration.elevenlabs";
57
+ if (!r || typeof r != "object" || Array.isArray(r)) e(t, "Expected ElevenLabs synthesis settings.");
58
+ else {
59
+ const o = ["modelId", "voiceSettings", "languageCode", "seed", "previousText", "nextText", "applyTextNormalization", "pronunciationDictionaries"];
60
+ for (const n of Object.keys(r))
61
+ o.includes(n) || e(`${t}.${n}`, "Unknown synthesis setting. Pass credentials to provider.init(), not the lesson.");
62
+ for (const n of ["modelId", "languageCode"])
63
+ r[n] !== void 0 && !h(r[n]) && e(`${t}.${n}`, "Must be a nonempty string.");
64
+ for (const n of ["previousText", "nextText"])
65
+ r[n] !== void 0 && typeof r[n] != "string" && e(`${t}.${n}`, "Must be a string.");
66
+ r.seed !== void 0 && (!Number.isInteger(r.seed) || r.seed < 0 || r.seed > 4294967295) && e(`${t}.seed`, "Must be an integer from 0 to 4294967295."), r.applyTextNormalization !== void 0 && !["auto", "on", "off"].includes(r.applyTextNormalization) && e(`${t}.applyTextNormalization`, "Choose auto, on, or off.");
67
+ const a = r.voiceSettings;
68
+ if (a !== void 0)
69
+ if (!a || typeof a != "object" || Array.isArray(a)) e(`${t}.voiceSettings`, "Expected a settings object.");
70
+ else for (const [n, m] of Object.entries(a))
71
+ n === "useSpeakerBoost" ? typeof m != "boolean" && e(`${t}.voiceSettings.${n}`, "Must be a boolean.") : ["stability", "similarityBoost", "style", "speed"].includes(n) ? (!Number.isFinite(m) || m < (n === "speed" ? 0.7 : 0) || m > (n === "speed" ? 1.2 : 1)) && e(`${t}.voiceSettings.${n}`, n === "speed" ? "Must be between 0.7 and 1.2." : "Must be between 0 and 1.") : e(`${t}.voiceSettings.${n}`, "Unknown voice setting.");
72
+ r.pronunciationDictionaries !== void 0 && (!Array.isArray(r.pronunciationDictionaries) || r.pronunciationDictionaries.length > 3 || r.pronunciationDictionaries.some((n) => !n || !h(n.id) || n.versionId !== void 0 && !h(n.versionId))) && e(`${t}.pronunciationDictionaries`, "Provide at most three dictionary IDs with optional version IDs.");
73
+ }
74
+ }
75
+ const c = /* @__PURE__ */ new Set();
76
+ Array.isArray(s.sources) ? s.sources.forEach((t, o) => {
77
+ !t || !y.test(t.id) || c.has(t.id) || !h(t.title) ? e(`sources[${o}]`, "Provide a unique source ID and title.") : c.add(t.id);
78
+ }) : e("sources", "Provide a sources array; it may be empty for an illustrative construction.");
79
+ const l = (t, o) => {
80
+ if (!Array.isArray(t))
81
+ e(o, "Expected source IDs.");
82
+ else
83
+ for (const a of t)
84
+ c.has(a) || e(o, `Unknown source '${a}'.`);
85
+ }, p = /* @__PURE__ */ new Set();
86
+ if (s.claims !== void 0 && !Array.isArray(s.claims))
87
+ e("claims", "Expected an array.");
88
+ else
89
+ for (const [t, o] of (s.claims ?? []).entries()) {
90
+ if (!o || !y.test(o.id) || p.has(o.id) || !h(o.text)) {
91
+ e(`claims[${t}]`, "Provide a unique claim ID and text.");
92
+ continue;
93
+ }
94
+ p.add(o.id), l(o.sourceIds, `claims[${t}].sourceIds`), o.illustrative !== void 0 && typeof o.illustrative != "boolean" && e(`claims[${t}].illustrative`, "Must be a boolean."), !o.illustrative && !o.sourceIds?.length && e(`claims[${t}].sourceIds`, "Factual claims need a source; mark illustrative models explicitly.");
95
+ }
96
+ const v = /* @__PURE__ */ new Set();
97
+ return !Array.isArray(s.beats) || !s.beats.length ? (e("beats", "Provide at least one beat."), u) : (s.beats.forEach((t, o) => {
98
+ const a = `beats[${o}]`;
99
+ if (!t || !y.test(t.id) || v.has(t.id)) {
100
+ e(a, "Beat IDs must be unique lowercase identifiers.");
101
+ return;
102
+ }
103
+ v.add(t.id);
104
+ for (const f of ["goal", "visibleChange", "focus"])
105
+ h(t[f]) || e(`${a}.${f}`, "Describe the learning goal, visible event, and attention target.");
106
+ t.minDurationMs !== void 0 && (!Number.isFinite(t.minDurationMs) || t.minDurationMs < 0) && e(`${a}.minDurationMs`, "Must be nonnegative."), t.sourceIds !== void 0 && l(t.sourceIds, `${a}.sourceIds`);
107
+ const n = /* @__PURE__ */ new Map();
108
+ !Array.isArray(t.narration) || !t.narration.length ? e(`${a}.narration`, "Provide at least one spoken segment, also used for silent preview timing.") : t.narration.forEach((f, d) => {
109
+ if (!f || !y.test(f.id) || n.has(f.id) || !h(f.text)) {
110
+ e(`${a}.narration[${d}]`, "Provide a unique segment ID and nonempty spoken text.");
111
+ return;
112
+ }
113
+ n.set(f.id, f.text), f.speechText !== void 0 && !h(f.speechText) && e(`${a}.narration[${d}].speechText`, "Must be nonempty synthesis text."), f.pauseAfterMs !== void 0 && (!Number.isFinite(f.pauseAfterMs) || f.pauseAfterMs < 0) && e(`${a}.narration[${d}].pauseAfterMs`, "Must be nonnegative.");
114
+ });
115
+ const m = /* @__PURE__ */ new Set();
116
+ if (t.cues !== void 0 && !Array.isArray(t.cues))
117
+ e(`${a}.cues`, "Expected an array.");
118
+ else
119
+ for (const [f, d] of (t.cues ?? []).entries()) {
120
+ if (!d || !y.test(d.id) || m.has(d.id)) {
121
+ e(`${a}.cues[${f}]`, "Provide a unique cue ID.");
122
+ continue;
123
+ }
124
+ m.add(d.id), n.has(d.segment) || e(`${a}.cues[${f}].segment`, "Cue references an unknown narration segment."), d.phrase !== void 0 && (!h(d.phrase) || !g(n.get(d.segment) ?? "").includes(g(d.phrase))) && e(`${a}.cues[${f}].phrase`, "Phrase must occur in the specified spoken segment."), d.occurrence !== void 0 && (!Number.isSafeInteger(d.occurrence) || d.occurrence < 1) && e(`${a}.cues[${f}].occurrence`, "Must be a positive occurrence number."), d.offsetMs !== void 0 && (!Number.isFinite(d.offsetMs) || d.offsetMs < 0) && e(`${a}.cues[${f}].offsetMs`, "Must be nonnegative.");
125
+ }
126
+ }), u);
127
+ }
128
+ function C(i) {
129
+ const u = D(i);
130
+ if (u.length)
131
+ throw new Error(u.map((e) => `${e.path}: ${e.message}`).join(`
132
+ `));
133
+ }
134
+ function N(i, u) {
135
+ const e = u.trim().split(/\s+/).map(g), s = [];
136
+ return i.forEach((r, c) => {
137
+ e.every((l, p) => g(i[c + p]?.text ?? "") === l) && s.push(c);
138
+ }), s;
139
+ }
140
+ function j(i, u, e = 60) {
141
+ if (C(i), !Number.isSafeInteger(e) || e < 1 || e > 240)
142
+ throw new Error("fps must be an integer from 1 to 240.");
143
+ if (i.narration.enabled && (!u || u.version !== 1 || !u.assets || typeof u.assets != "object" || u.voiceKey !== I(i)))
144
+ throw new Error("Narration is not prepared for these voice settings. Run retiam lesson prepare.");
145
+ let s = 0;
146
+ const r = [];
147
+ for (const c of i.beats) {
148
+ let l = 0;
149
+ const p = [];
150
+ for (const o of c.narration) {
151
+ const a = i.narration.enabled ? u.assets[S(c.id, o.id)] : void 0;
152
+ if (i.narration.enabled && (!a || a.text !== o.text || (a.speechText ?? a.text) !== (o.speechText ?? o.text)))
153
+ throw new Error(`Missing or stale audio: ${c.id}/${o.id}. Prepare narration again.`);
154
+ const n = a?.durationMs ?? Math.max(900, o.text.trim().split(/\s+/).length * 360);
155
+ if (!Number.isFinite(n) || n <= 0)
156
+ throw new Error(`Invalid speech duration: ${c.id}/${o.id}.`);
157
+ if (a && (!Array.isArray(a.words) || !a.words.length))
158
+ throw new Error(`Missing word timings: ${c.id}/${o.id}.`);
159
+ const m = a?.words ?? o.text.trim().split(/\s+/).map((d, $, M) => ({ text: d, offsetMs: n * $ / M.length, durationMs: n / M.length }));
160
+ let f = -1;
161
+ for (const d of m) {
162
+ if (!h(d.text) || !Number.isFinite(d.offsetMs) || !Number.isFinite(d.durationMs) || d.offsetMs < f - 0.1 || d.durationMs < 0 || d.offsetMs + d.durationMs > n + 1)
163
+ throw new Error(`Invalid word timings: ${c.id}/${o.id}.`);
164
+ f = d.offsetMs + d.durationMs;
165
+ }
166
+ p.push({ ...a, text: o.text, id: o.id, durationMs: n, words: m, startMs: l, pauseAfterMs: o.pauseAfterMs ?? 0 }), l += n + (o.pauseAfterMs ?? 0);
167
+ }
168
+ const v = Math.ceil(Math.max(c.minDurationMs ?? 0, l + 450) * e / 1e3 - 1e-8) * 1e3 / e, t = {};
169
+ for (const o of c.cues ?? []) {
170
+ const a = p.find((m) => m.id === o.segment);
171
+ let n = a.startMs;
172
+ if (o.phrase) {
173
+ const m = N(a.words, o.phrase);
174
+ if (m.length > 1 && o.occurrence === void 0)
175
+ throw new Error(`Ambiguous cue '${c.id}/${o.id}'; set occurrence.`);
176
+ const f = m[(o.occurrence ?? 1) - 1];
177
+ if (f === void 0)
178
+ throw new Error(`Unaligned cue '${c.id}/${o.id}': ${o.phrase}.`);
179
+ n += a.words[f].offsetMs;
180
+ }
181
+ if (n += o.offsetMs ?? 0, n > a.startMs + a.durationMs)
182
+ throw new Error(`Cue '${c.id}/${o.id}' falls outside its speech segment.`);
183
+ t[o.id] = n;
184
+ }
185
+ r.push({ id: c.id, startMs: s, durationMs: v, segments: p, cues: t }), s += v;
186
+ }
187
+ return { version: 1, timing: i.narration.enabled ? "recorded" : "estimated", fps: e, durationMs: s, beats: r };
188
+ }
189
+ function O(i, u = 8) {
190
+ if (!Number.isInteger(u) || u < 1)
191
+ throw new Error("wordsPerCue must be positive.");
192
+ const e = (c) => {
193
+ const l = Math.round(c);
194
+ return `${String(Math.floor(l / 36e5)).padStart(2, "0")}:${String(Math.floor(l / 6e4) % 60).padStart(2, "0")}:${String(Math.floor(l / 1e3) % 60).padStart(2, "0")},${String(l % 1e3).padStart(3, "0")}`;
195
+ };
196
+ let s = 0;
197
+ const r = [];
198
+ for (const c of i.beats)
199
+ for (const l of c.segments)
200
+ for (let p = 0; p < l.words.length; p += u) {
201
+ const v = l.words.slice(p, p + u), t = v.at(-1);
202
+ r.push(`${++s}
203
+ ${e(c.startMs + l.startMs + v[0].offsetMs)} --> ${e(c.startMs + l.startMs + t.offsetMs + t.durationMs)}
204
+ ${v.map((o) => o.text).join(" ")}
205
+ `);
206
+ }
207
+ return r.join(`
208
+ `);
209
+ }
210
+ export {
211
+ b as STYLE_PROFILES,
212
+ C as assertLesson,
213
+ j as compileLesson,
214
+ O as lessonCaptions,
215
+ F as resolveStyle,
216
+ S as speechKey,
217
+ D as validateLesson,
218
+ I as voiceKey
219
+ };
@@ -0,0 +1,24 @@
1
+ import { Animation, Scene } from 'retiam';
2
+ import { TTSAudioResult } from 'retiam/tts';
3
+ import { LessonPlan, PreparedSpeech, CompiledLesson, CompiledBeat, StyleProfile } from './types';
4
+ export interface BeatContext {
5
+ beat: CompiledBeat;
6
+ durationMs: number;
7
+ style: StyleProfile;
8
+ cue(id: string): number;
9
+ at(id: string, action: () => void): Animation;
10
+ }
11
+ export interface PreparedLesson {
12
+ plan: LessonPlan;
13
+ timeline: CompiledLesson;
14
+ style: StyleProfile;
15
+ /** Handlers build animations up front. Put state changes inside animations or at(). */
16
+ run(handlers: Record<string, (beat: BeatContext) => Animation[]>): Promise<void>;
17
+ }
18
+ export interface PrepareLessonOptions {
19
+ prepared?: PreparedSpeech;
20
+ fps?: number;
21
+ /** Optional adapter; defaults to the active retiam/tts provider. */
22
+ synthesize?: (text: string) => Promise<TTSAudioResult>;
23
+ }
24
+ export declare function prepareLesson(scene: Scene, plan: LessonPlan, options?: PrepareLessonOptions): Promise<PreparedLesson>;
@@ -0,0 +1,3 @@
1
+ import { StyleId, StyleInput, StyleProfile } from './types';
2
+ export declare const STYLE_PROFILES: Readonly<Record<StyleId, StyleProfile>>;
3
+ export declare function resolveStyle(input: StyleInput): StyleProfile;
@@ -0,0 +1,124 @@
1
+ import { ElevenLabsGenerateOptions } from '../tts/core/types';
2
+ /** A visual profile is independent of the topic and teaching sequence. */
3
+ export type StyleId = 'mathematical' | 'excalidraw' | 'minimal' | 'cinematic';
4
+ export interface StyleProfile {
5
+ name: string;
6
+ background: string;
7
+ ink: string;
8
+ muted: string;
9
+ primary: string;
10
+ secondary: string;
11
+ accents: string[];
12
+ font: string;
13
+ strokeWidth: number;
14
+ rough: boolean;
15
+ roughness: number;
16
+ fillStyle: 'solid' | 'hachure';
17
+ motion: {
18
+ drawMs: number;
19
+ moveMs: number;
20
+ holdMs: number;
21
+ };
22
+ }
23
+ export type StyleInput = StyleId | ({
24
+ extends: StyleId;
25
+ } & Partial<Omit<StyleProfile, 'motion'>> & {
26
+ motion?: Partial<StyleProfile['motion']>;
27
+ });
28
+ export interface LessonSource {
29
+ id: string;
30
+ title: string;
31
+ url?: string;
32
+ note?: string;
33
+ }
34
+ export interface LessonClaim {
35
+ id: string;
36
+ text: string;
37
+ sourceIds: string[];
38
+ illustrative?: boolean;
39
+ }
40
+ export interface NarrationSegment {
41
+ id: string;
42
+ text: string;
43
+ /** Optional synthesis text with delivery tags; text remains clean for captions/cues. */
44
+ speechText?: string;
45
+ pauseAfterMs?: number;
46
+ }
47
+ /** Phrases are scoped to a segment. Repeated phrases require an explicit occurrence (1-based). */
48
+ export interface LessonCue {
49
+ id: string;
50
+ segment: string;
51
+ phrase?: string;
52
+ occurrence?: number;
53
+ offsetMs?: number;
54
+ }
55
+ export interface LessonBeat {
56
+ id: string;
57
+ goal: string;
58
+ visibleChange: string;
59
+ focus: string;
60
+ minDurationMs?: number;
61
+ narration: NarrationSegment[];
62
+ cues?: LessonCue[];
63
+ sourceIds?: string[];
64
+ }
65
+ export interface LessonPlan {
66
+ version: 1;
67
+ title: string;
68
+ topic: string;
69
+ audience: string;
70
+ objective: string;
71
+ style: StyleInput;
72
+ narration: {
73
+ enabled: boolean;
74
+ provider?: string;
75
+ voice?: string;
76
+ speed?: number;
77
+ pitch?: string;
78
+ volume?: number;
79
+ elevenlabs?: ElevenLabsGenerateOptions;
80
+ };
81
+ sources: LessonSource[];
82
+ claims?: LessonClaim[];
83
+ beats: LessonBeat[];
84
+ }
85
+ export interface TimedWord {
86
+ text: string;
87
+ offsetMs: number;
88
+ durationMs: number;
89
+ }
90
+ export interface SpeechAsset {
91
+ text: string;
92
+ speechText?: string;
93
+ durationMs: number;
94
+ words: TimedWord[];
95
+ url?: string;
96
+ }
97
+ export interface PreparedSpeech {
98
+ version: 1;
99
+ voiceKey: string;
100
+ assets: Record<string, SpeechAsset>;
101
+ }
102
+ export interface CompiledSegment extends SpeechAsset {
103
+ id: string;
104
+ startMs: number;
105
+ pauseAfterMs: number;
106
+ }
107
+ export interface CompiledBeat {
108
+ id: string;
109
+ startMs: number;
110
+ durationMs: number;
111
+ segments: CompiledSegment[];
112
+ cues: Record<string, number>;
113
+ }
114
+ export interface CompiledLesson {
115
+ version: 1;
116
+ timing: 'recorded' | 'estimated';
117
+ fps: number;
118
+ durationMs: number;
119
+ beats: CompiledBeat[];
120
+ }
121
+ export interface Diagnostic {
122
+ path: string;
123
+ message: string;
124
+ }
@@ -0,0 +1,18 @@
1
+ import { Animation, Entity, Scene, VectorEntity } from 'retiam';
2
+ import { StyleProfile } from './types';
3
+ export type LessonPoint = [
4
+ number,
5
+ number,
6
+ number
7
+ ];
8
+ /** Apply a profile to geometry; text keeps readable glyphs and uses the profile's font. */
9
+ export declare function styleGeometry<T extends VectorEntity>(entity: T, style: StyleProfile, role?: 'primary' | 'secondary' | 'ink', fill?: boolean): T;
10
+ export declare function drawGeometry(entity: VectorEntity, style: StyleProfile, delay?: number, duration?: number): Animation;
11
+ /** Move a value between representations while preserving its visual identity. */
12
+ export declare function transferValue(scene: Scene, text: string, from: () => LessonPoint, to: () => LessonPoint, style: StyleProfile, options?: {
13
+ delay?: number;
14
+ duration?: number;
15
+ arc?: number;
16
+ }): Animation;
17
+ /** Attention without changing the object's persistent position or scale. */
18
+ export declare function emphasize(entity: Entity, delay?: number, duration?: number, strength?: number): Animation;
@@ -1,13 +1,17 @@
1
1
  import { Animation, AnimOptions, Entity, Scene } from 'retiam';
2
2
  import { Subtitles } from '../components/Subtitles';
3
- import { MsEdgeVoiceId, TTSAudioResult } from '../core/types';
3
+ import { VoiceId, TTSAudioResult, ElevenLabsGenerateOptions } from '../core/types';
4
4
  export interface SpeakOptions extends AnimOptions {
5
- /** Microsoft Edge neural voice ID for narration. */
6
- voice?: MsEdgeVoiceId;
7
- /** Speaking rate multiplier. Defaults to 1.0. */
5
+ /** Voice ID; omitted values use the active provider's default. */
6
+ voice?: VoiceId;
7
+ /** Speaking rate multiplier; omitted values inherit the provider default. */
8
8
  speed?: number;
9
9
  /** Pitch shift (for MsEdge TTS, e.g. "+0Hz", "+5st", "+10%"). */
10
10
  pitch?: string | number;
11
+ /** Clean subtitle text when speech contains delivery or pronunciation tags. */
12
+ displayText?: string;
13
+ /** Per-utterance ElevenLabs delivery and model overrides. */
14
+ elevenlabs?: ElevenLabsGenerateOptions;
11
15
  /** Optional Subtitles component to automatically synchronize closed captions. */
12
16
  subtitles?: Subtitles;
13
17
  /** Optional entity to animate with speaking micro-pulses. */
@@ -22,7 +26,7 @@ export interface SpeakOptions extends AnimOptions {
22
26
  /**
23
27
  * Retiam Animation for spoken narration.
24
28
  *
25
- * Synchronizes Microsoft Edge neural speech, voiceover auto-ducking
29
+ * Synchronizes speech from the active provider, voiceover auto-ducking
26
30
  * on Retiam's [[AudioManager]], timeline pacing, closed captions/subtitles, and entity micro-animations.
27
31
  *
28
32
  * @example
@@ -37,7 +41,7 @@ export interface SpeakOptions extends AnimOptions {
37
41
  */
38
42
  export declare class Speak extends Animation {
39
43
  readonly text: string;
40
- readonly voice: string;
44
+ readonly voice?: string;
41
45
  readonly speed: number;
42
46
  readonly pitch?: string | number;
43
47
  readonly subtitles?: Subtitles;
@@ -46,6 +50,8 @@ export declare class Speak extends Animation {
46
50
  readonly duckingRatio: number;
47
51
  readonly volume: number;
48
52
  private _audioResult;
53
+ private _audioTask;
54
+ private readonly _synthesisOptions;
49
55
  private _isStarted;
50
56
  private _baseScale;
51
57
  constructor(text: string, options?: SpeakOptions);
@@ -64,7 +70,7 @@ export declare class Speak extends Animation {
64
70
  /**
65
71
  * High-level helper function to speak narration in a Retiam Scene.
66
72
  *
67
- * Synthesizes speech using Microsoft Edge neural TTS,
73
+ * Synthesizes speech using the active provider,
68
74
  * synchronizes subtitles, ducks background music, and pauses scene progression
69
75
  * for the duration of the audio.
70
76
  *
@@ -1,10 +1,11 @@
1
1
  import { Component } from 'retiam';
2
- import { MsEdgeVoiceId, TTSAudioResult, TTSGenerateOptions, TTSPlaybackOptions } from '../core/types';
2
+ import { VoiceId, TTSAudioResult, TTSGenerateOptions, TTSPlaybackOptions, ElevenLabsGenerateOptions } from '../core/types';
3
3
  export interface TTSNarrationOptions {
4
- /** Preferred Microsoft Edge neural voice ID. */
5
- voice?: MsEdgeVoiceId;
6
- /** Default speaking rate. Defaults to 1.0. */
4
+ /** Voice ID; omitted values use the active provider's default. */
5
+ voice?: VoiceId;
6
+ /** Speaking rate override; omitted values inherit the provider default. */
7
7
  speed?: number;
8
+ elevenlabs?: ElevenLabsGenerateOptions;
8
9
  /** Whether to visually pulse the entity's scale during speech. Defaults to true. */
9
10
  animateSpeaking?: boolean;
10
11
  /** Scale pulse amplitude during speech (e.g. 0.06 = ±6% pulse). Defaults to 0.05. */
@@ -19,12 +20,13 @@ export interface TTSNarrationOptions {
19
20
  /**
20
21
  * Retiam ECS Component that empowers any visual Entity with voice narration capabilities.
21
22
  *
22
- * Features automatic Microsoft Edge neural speech synthesis,
23
+ * Features speech synthesis through the active provider,
23
24
  * voiceover ducking, and synchronized micro-interaction visual pulses while speaking.
24
25
  */
25
26
  export declare class TTSNarrationComponent extends Component {
26
- voice: string;
27
- speed: number;
27
+ voice?: string;
28
+ speed?: number;
29
+ elevenlabs?: ElevenLabsGenerateOptions;
28
30
  animateSpeaking: boolean;
29
31
  pulseAmplitude: number;
30
32
  pulseFrequency: number;