@pie-players/pie-assessment-toolkit 0.3.44 → 0.3.45

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 (191) hide show
  1. package/README.md +54 -69
  2. package/dist/components/ItemToolBar.custom-element.js +479 -324
  3. package/dist/components/PieAssessmentToolkit.custom-element.js +19498 -3590
  4. package/dist/components/SectionToolBar.custom-element.js +761 -507
  5. package/dist/index.d.ts +3 -6
  6. package/dist/index.js +1 -3
  7. package/dist/index.js.map +1 -1
  8. package/dist/policy/core/ToolPolicyEngine.d.ts +1 -2
  9. package/dist/policy/core/ToolPolicyEngine.js +1 -2
  10. package/dist/policy/core/ToolPolicyEngine.js.map +1 -1
  11. package/dist/policy/core/pnp-policy-inputs.d.ts +2 -3
  12. package/dist/policy/core/pnp-policy-inputs.js +2 -3
  13. package/dist/policy/core/pnp-policy-inputs.js.map +1 -1
  14. package/dist/policy/core/provenance.d.ts +7 -34
  15. package/dist/policy/core/provenance.js +4 -21
  16. package/dist/policy/core/provenance.js.map +1 -1
  17. package/dist/policy/sources/PnpPolicySource.d.ts +6 -19
  18. package/dist/policy/sources/PnpPolicySource.js +6 -19
  19. package/dist/policy/sources/PnpPolicySource.js.map +1 -1
  20. package/dist/runtime/SectionRuntimeEngine.d.ts +13 -28
  21. package/dist/runtime/SectionRuntimeEngine.js +13 -27
  22. package/dist/runtime/SectionRuntimeEngine.js.map +1 -1
  23. package/dist/runtime/adapter/SectionEngineAdapter.d.ts +2 -2
  24. package/dist/runtime/adapter/SectionEngineAdapter.js +2 -2
  25. package/dist/runtime/adapter/SectionEngineAdapter.js.map +1 -1
  26. package/dist/runtime/adapter/coordinator-bridge.d.ts +1 -1
  27. package/dist/runtime/adapter/coordinator-bridge.js +1 -1
  28. package/dist/runtime/adapter/coordinator-bridge.js.map +1 -1
  29. package/dist/runtime/adapter/dom-event-bridge.d.ts +1 -1
  30. package/dist/runtime/adapter/dom-event-bridge.js +1 -1
  31. package/dist/runtime/adapter/dom-event-bridge.js.map +1 -1
  32. package/dist/runtime/catalog-registration.d.ts +12 -0
  33. package/dist/runtime/catalog-registration.js +54 -0
  34. package/dist/runtime/catalog-registration.js.map +1 -0
  35. package/dist/runtime/core/engine-output.d.ts +2 -2
  36. package/dist/runtime/core/engine-output.js +2 -2
  37. package/dist/runtime/core/engine-output.js.map +1 -1
  38. package/dist/runtime/core/engine-readiness.d.ts +2 -2
  39. package/dist/runtime/core/engine-readiness.js.map +1 -1
  40. package/dist/runtime/core/engine-resolver.d.ts +13 -41
  41. package/dist/runtime/core/engine-resolver.js +19 -76
  42. package/dist/runtime/core/engine-resolver.js.map +1 -1
  43. package/dist/runtime/core/engine-transition.d.ts +1 -1
  44. package/dist/runtime/core/engine-transition.js +1 -1
  45. package/dist/runtime/core/engine-transition.js.map +1 -1
  46. package/dist/runtime/engine.d.ts +1 -1
  47. package/dist/runtime/engine.js.map +1 -1
  48. package/dist/runtime/section-runtime-engine-host-context.d.ts +22 -15
  49. package/dist/runtime/section-runtime-engine-host-context.js +10 -10
  50. package/dist/runtime/section-runtime-engine-host-context.js.map +1 -1
  51. package/dist/runtime/stage-emit-gate.d.ts +4 -4
  52. package/dist/runtime/stage-emit-gate.js +3 -3
  53. package/dist/runtime/stage-emit-gate.js.map +1 -1
  54. package/dist/services/AccessibilityCatalogResolver.d.ts +23 -0
  55. package/dist/services/AccessibilityCatalogResolver.js +200 -28
  56. package/dist/services/AccessibilityCatalogResolver.js.map +1 -1
  57. package/dist/services/HighlightCoordinator.d.ts +25 -0
  58. package/dist/services/HighlightCoordinator.js +125 -0
  59. package/dist/services/HighlightCoordinator.js.map +1 -1
  60. package/dist/services/SSMLExtractor.d.ts +16 -1
  61. package/dist/services/SSMLExtractor.js +86 -7
  62. package/dist/services/SSMLExtractor.js.map +1 -1
  63. package/dist/services/TTSService.d.ts +30 -4
  64. package/dist/services/TTSService.js +652 -159
  65. package/dist/services/TTSService.js.map +1 -1
  66. package/dist/services/ToolRegistry.d.ts +2 -2
  67. package/dist/services/ToolRegistry.js +1 -1
  68. package/dist/services/ToolRegistry.js.map +1 -1
  69. package/dist/services/ToolkitCoordinator.d.ts +16 -73
  70. package/dist/services/ToolkitCoordinator.js +34 -113
  71. package/dist/services/ToolkitCoordinator.js.map +1 -1
  72. package/dist/services/interfaces.d.ts +20 -2
  73. package/dist/services/interfaces.js.map +1 -1
  74. package/dist/services/tool-config-validation.d.ts +1 -1
  75. package/dist/services/tool-config-validation.js +7 -7
  76. package/dist/services/tool-config-validation.js.map +1 -1
  77. package/dist/services/tool-instance-id.js +1 -1
  78. package/dist/services/tool-instance-id.js.map +1 -1
  79. package/dist/services/tool-providers/TTSToolProvider.js +3 -3
  80. package/dist/services/tool-providers/TTSToolProvider.js.map +1 -1
  81. package/dist/services/tools-config-normalizer.d.ts +13 -2
  82. package/dist/services/tools-config-normalizer.js +3 -8
  83. package/dist/services/tools-config-normalizer.js.map +1 -1
  84. package/dist/services/tts/browser-provider.js +2 -0
  85. package/dist/services/tts/browser-provider.js.map +1 -1
  86. package/dist/services/tts/catalog-span-alignment.d.ts +39 -0
  87. package/dist/services/tts/catalog-span-alignment.js +300 -0
  88. package/dist/services/tts/catalog-span-alignment.js.map +1 -0
  89. package/dist/services/tts/generated-speech/assemble-plan.d.ts +33 -0
  90. package/dist/services/tts/generated-speech/assemble-plan.js +111 -0
  91. package/dist/services/tts/generated-speech/assemble-plan.js.map +1 -0
  92. package/dist/services/tts/generated-speech/dom/build-dom-plan.d.ts +25 -0
  93. package/dist/services/tts/generated-speech/dom/build-dom-plan.js +66 -0
  94. package/dist/services/tts/generated-speech/dom/build-dom-plan.js.map +1 -0
  95. package/dist/services/tts/generated-speech/dom/to-chunk-inputs.d.ts +19 -0
  96. package/dist/services/tts/generated-speech/dom/to-chunk-inputs.js +71 -0
  97. package/dist/services/tts/generated-speech/dom/to-chunk-inputs.js.map +1 -0
  98. package/dist/services/tts/generated-speech/dom/types.d.ts +37 -0
  99. package/dist/services/tts/generated-speech/dom/types.js +2 -0
  100. package/dist/services/tts/generated-speech/dom/types.js.map +1 -0
  101. package/dist/services/tts/generated-speech/index.d.ts +17 -0
  102. package/dist/services/tts/generated-speech/index.js +17 -0
  103. package/dist/services/tts/generated-speech/index.js.map +1 -0
  104. package/dist/services/tts/generated-speech/math-speech-cache.d.ts +15 -0
  105. package/dist/services/tts/generated-speech/math-speech-cache.js +78 -0
  106. package/dist/services/tts/generated-speech/math-speech-cache.js.map +1 -0
  107. package/dist/services/tts/generated-speech/types.d.ts +104 -0
  108. package/dist/services/tts/generated-speech/types.js +18 -0
  109. package/dist/services/tts/generated-speech/types.js.map +1 -0
  110. package/dist/services/tts/highlight-pipeline/boundary-normalizer.d.ts +2 -0
  111. package/dist/services/tts/highlight-pipeline/boundary-normalizer.js +106 -0
  112. package/dist/services/tts/highlight-pipeline/boundary-normalizer.js.map +1 -0
  113. package/dist/services/tts/highlight-pipeline/create-highlight-plan.d.ts +6 -0
  114. package/dist/services/tts/highlight-pipeline/create-highlight-plan.js +217 -0
  115. package/dist/services/tts/highlight-pipeline/create-highlight-plan.js.map +1 -0
  116. package/dist/services/tts/highlight-pipeline/highlight-policy.d.ts +2 -0
  117. package/dist/services/tts/highlight-pipeline/highlight-policy.js +32 -0
  118. package/dist/services/tts/highlight-pipeline/highlight-policy.js.map +1 -0
  119. package/dist/services/tts/highlight-pipeline/index.d.ts +9 -0
  120. package/dist/services/tts/highlight-pipeline/index.js +9 -0
  121. package/dist/services/tts/highlight-pipeline/index.js.map +1 -0
  122. package/dist/services/tts/highlight-pipeline/math-semantic-resolver.d.ts +3 -0
  123. package/dist/services/tts/highlight-pipeline/math-semantic-resolver.js +32 -0
  124. package/dist/services/tts/highlight-pipeline/math-semantic-resolver.js.map +1 -0
  125. package/dist/services/tts/highlight-pipeline/normalize-speech-chunks.d.ts +29 -0
  126. package/dist/services/tts/highlight-pipeline/normalize-speech-chunks.js +44 -0
  127. package/dist/services/tts/highlight-pipeline/normalize-speech-chunks.js.map +1 -0
  128. package/dist/services/tts/highlight-pipeline/prose-boundary-resolver.d.ts +2 -0
  129. package/dist/services/tts/highlight-pipeline/prose-boundary-resolver.js +20 -0
  130. package/dist/services/tts/highlight-pipeline/prose-boundary-resolver.js.map +1 -0
  131. package/dist/services/tts/highlight-pipeline/readable-region.d.ts +1 -0
  132. package/dist/services/tts/highlight-pipeline/readable-region.js +19 -0
  133. package/dist/services/tts/highlight-pipeline/readable-region.js.map +1 -0
  134. package/dist/services/tts/highlight-pipeline/rendered-math-target-resolver.d.ts +6 -0
  135. package/dist/services/tts/highlight-pipeline/rendered-math-target-resolver.js +198 -0
  136. package/dist/services/tts/highlight-pipeline/rendered-math-target-resolver.js.map +1 -0
  137. package/dist/services/tts/highlight-pipeline/types.d.ts +72 -0
  138. package/dist/services/tts/highlight-pipeline/types.js +2 -0
  139. package/dist/services/tts/highlight-pipeline/types.js.map +1 -0
  140. package/dist/services/tts/highlight-pipeline/visible-map-range.d.ts +2 -0
  141. package/dist/services/tts/highlight-pipeline/visible-map-range.js +22 -0
  142. package/dist/services/tts/highlight-pipeline/visible-map-range.js.map +1 -0
  143. package/dist/services/tts/math-alignment/index.d.ts +30 -0
  144. package/dist/services/tts/math-alignment/index.js +66 -0
  145. package/dist/services/tts/math-alignment/index.js.map +1 -0
  146. package/dist/services/tts/math-alignment/mathml-tokenizer.d.ts +2 -0
  147. package/dist/services/tts/math-alignment/mathml-tokenizer.js +317 -0
  148. package/dist/services/tts/math-alignment/mathml-tokenizer.js.map +1 -0
  149. package/dist/services/tts/math-alignment/range-resolver.d.ts +10 -0
  150. package/dist/services/tts/math-alignment/range-resolver.js +13 -0
  151. package/dist/services/tts/math-alignment/range-resolver.js.map +1 -0
  152. package/dist/services/tts/math-alignment/sequence-aligner.d.ts +7 -0
  153. package/dist/services/tts/math-alignment/sequence-aligner.js +119 -0
  154. package/dist/services/tts/math-alignment/sequence-aligner.js.map +1 -0
  155. package/dist/services/tts/math-alignment/speech-tokenizer.d.ts +31 -0
  156. package/dist/services/tts/math-alignment/speech-tokenizer.js +143 -0
  157. package/dist/services/tts/math-alignment/speech-tokenizer.js.map +1 -0
  158. package/dist/services/tts/math-alignment/types.d.ts +58 -0
  159. package/dist/services/tts/math-alignment/types.js +2 -0
  160. package/dist/services/tts/math-alignment/types.js.map +1 -0
  161. package/dist/services/tts/math-aware-text-processing.d.ts +18 -0
  162. package/dist/services/tts/math-aware-text-processing.js +348 -0
  163. package/dist/services/tts/math-aware-text-processing.js.map +1 -0
  164. package/dist/services/tts/math-speech.d.ts +44 -0
  165. package/dist/services/tts/math-speech.js +147 -0
  166. package/dist/services/tts/math-speech.js.map +1 -0
  167. package/dist/services/tts/mathml-sanitization.d.ts +1 -0
  168. package/dist/services/tts/mathml-sanitization.js +170 -0
  169. package/dist/services/tts/mathml-sanitization.js.map +1 -0
  170. package/dist/services/tts/ssml/spoken-text.d.ts +33 -0
  171. package/dist/services/tts/ssml/spoken-text.js +159 -0
  172. package/dist/services/tts/ssml/spoken-text.js.map +1 -0
  173. package/dist/services/tts/text-processing.d.ts +3 -0
  174. package/dist/services/tts/text-processing.js +30 -4
  175. package/dist/services/tts/text-processing.js.map +1 -1
  176. package/dist/services/tts-runtime-config.d.ts +17 -1
  177. package/dist/services/tts-runtime-config.js +11 -1
  178. package/dist/services/tts-runtime-config.js.map +1 -1
  179. package/dist/tools/registrations/accessibility-tools.js +0 -1
  180. package/dist/tools/registrations/accessibility-tools.js.map +1 -1
  181. package/dist/tools/registrations/calculator.js +41 -2
  182. package/dist/tools/registrations/calculator.js.map +1 -1
  183. package/dist/tools/tool-tag-map.js +0 -1
  184. package/dist/tools/tool-tag-map.js.map +1 -1
  185. package/package.json +8 -11
  186. package/dist/services/PNPToolResolver.d.ts +0 -280
  187. package/dist/services/PNPToolResolver.js +0 -514
  188. package/dist/services/PNPToolResolver.js.map +0 -1
  189. package/dist/services/pnp-provenance.d.ts +0 -166
  190. package/dist/services/pnp-provenance.js +0 -231
  191. package/dist/services/pnp-provenance.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assemble-plan.js","sourceRoot":"","sources":["../../../../src/services/tts/generated-speech/assemble-plan.ts"],"names":[],"mappings":"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","sourcesContent":["import type { MathAwareSpeechChunk } from \"../math-aware-text-processing.js\";\nimport {\n\tresolveMathSpeechFromChunks,\n\ttype ResolvedMathSpeech,\n\ttype SREMathSpeechOptions,\n} from \"../math-speech.js\";\nimport { normalizeTextForSpeech } from \"../text-processing.js\";\nimport { segmentSentences as segmentTextToSentences } from \"../text-segmentation.js\";\nimport type { AssembledSegment, AssembledSpeech } from \"./types.js\";\n\ntype MathChunk = Extract<MathAwareSpeechChunk, { type: \"math\" }>;\n\n/**\n * Resolves spoken text for a single math chunk. Injectable so the runtime can\n * supply a memoized resolver (deterministic caching) and tests can stub SRE.\n * Mirrors `resolveMathSpeechFromChunks([chunk], options)`.\n */\nexport type MathSpeechResolver = (\n\tchunk: MathChunk,\n\toptions: {\n\t\tlanguage?: string;\n\t\tproduceSsml?: boolean;\n\t\tmathSpeech?: SREMathSpeechOptions;\n\t},\n) => Promise<ResolvedMathSpeech>;\n\nconst defaultMathSpeechResolver: MathSpeechResolver = (chunk, options) =>\n\tresolveMathSpeechFromChunks([chunk], options);\n\nconst normalizeLocale = (language?: string): string =>\n\t(language || \"en\").split(\"-\")[0].toLowerCase() || \"en\";\n\nconst PRESERVE_PROSE_CHUNK_TAGS = new Set([\n\t\"H1\",\n\t\"H2\",\n\t\"H3\",\n\t\"H4\",\n\t\"H5\",\n\t\"H6\",\n\t\"LI\",\n\t\"LABEL\",\n\t\"BUTTON\",\n]);\n\nconst PRESERVE_PROSE_CHUNK_ROLES = new Set([\n\t\"heading\",\n\t\"listitem\",\n\t\"option\",\n\t\"radio\",\n]);\n\nconst shouldPreserveProseChunk = (\n\tchunk: MathAwareSpeechChunk,\n): boolean => {\n\tif (chunk.type !== \"text\" || !chunk.sourceElement) return false;\n\tconst tagName = chunk.sourceElement.tagName.toUpperCase();\n\tconst role = (chunk.sourceElement.getAttribute(\"role\") || \"\").toLowerCase();\n\treturn (\n\t\tPRESERVE_PROSE_CHUNK_TAGS.has(tagName) ||\n\t\tPRESERVE_PROSE_CHUNK_ROLES.has(role)\n\t);\n};\n\n/**\n * Assemble an anchor-free speech plan from already-collected math-aware chunks.\n *\n * PURE: no DOM walking, no app-service imports. Preserves the spoken-text and\n * visible-span bookkeeping from the inline generated path, so the runtime output\n * is stable once serialized to plain text.\n */\nexport const assembleGeneratedSpeech = async (args: {\n\tchunks: ReadonlyArray<MathAwareSpeechChunk>;\n\tvisibleText: string;\n\tlanguage?: string;\n\tmathSpeech?: SREMathSpeechOptions;\n\t/** Request SRE SSML per math equation (used by the SSML playback format). */\n\tproduceSsml?: boolean;\n\tresolveMathSpeech?: MathSpeechResolver;\n}): Promise<AssembledSpeech> => {\n\tconst resolveMathSpeech = args.resolveMathSpeech ?? defaultMathSpeechResolver;\n\tconst { visibleText } = args;\n\tconst segments: AssembledSegment[] = [];\n\n\t// Cursor-based span tracking:\n\t// locate each chunk's normalized text within the aggregate visible text,\n\t// advancing a cursor so math fallbacks reserve their span even though only\n\t// prose spans are sliced for highlighting.\n\tlet visibleCursor = 0;\n\tconst nextVisibleSpan = (text: string): { start: number; end: number } => {\n\t\tconst normalized = normalizeTextForSpeech(text);\n\t\tconst found = visibleText.indexOf(normalized, visibleCursor);\n\t\tconst start = found >= 0 ? found : visibleCursor;\n\t\tconst end = start + normalized.length;\n\t\tvisibleCursor = end;\n\t\treturn { start, end };\n\t};\n\n\tconst speechParts: string[] = [];\n\n\tfor (let index = 0; index < args.chunks.length; index++) {\n\t\tconst chunk = args.chunks[index];\n\t\tif (chunk.type === \"text\") {\n\t\t\tconst proseSegments = shouldPreserveProseChunk(chunk)\n\t\t\t\t? [{ text: chunk.text, offset: 0 }]\n\t\t\t\t: segmentTextToSentences(chunk.text, {\n\t\t\t\t\t\tlocale: args.language,\n\t\t\t\t\t});\n\t\t\tfor (const prose of proseSegments) {\n\t\t\t\tconst text = normalizeTextForSpeech(prose.text);\n\t\t\t\tif (!text) continue;\n\t\t\t\tconst visibleSpan = nextVisibleSpan(text);\n\t\t\t\tsegments.push({\n\t\t\t\t\tsourceChunkIndex: index,\n\t\t\t\t\tsegment: {\n\t\t\t\t\t\tkind: \"prose\",\n\t\t\t\t\t\tspokenText: text,\n\t\t\t\t\t\tvisibleText: text,\n\t\t\t\t\t\tvisibleSpan,\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t\tspeechParts.push(text);\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\t\tconst visibleSpan = nextVisibleSpan(chunk.fallbackText);\n\t\tconst generated = await resolveMathSpeech(chunk, {\n\t\t\tlanguage: args.language,\n\t\t\tproduceSsml: args.produceSsml,\n\t\t\tmathSpeech: args.mathSpeech,\n\t\t});\n\t\tconst usedFallback = !generated.speechText;\n\t\tconst spokenText = generated.speechText || chunk.fallbackText;\n\t\tsegments.push({\n\t\t\tsourceChunkIndex: index,\n\t\t\tsegment: {\n\t\t\t\tkind: \"math\",\n\t\t\t\tspokenText,\n\t\t\t\tmathml: chunk.mathml,\n\t\t\t\tfallbackText: chunk.fallbackText,\n\t\t\t\tvisibleSpan,\n\t\t\t\tusedFallback,\n\t\t\t\tssml: generated.ssml,\n\t\t\t},\n\t\t});\n\t\tspeechParts.push(spokenText);\n\t}\n\n\treturn {\n\t\tsegments,\n\t\tlocale: normalizeLocale(args.language),\n\t\tvisibleText,\n\t\tplainSpeechText: normalizeTextForSpeech(speechParts.join(\" \")),\n\t};\n};\n"]}
@@ -0,0 +1,25 @@
1
+ import type { TextProcessingOptions } from "../../text-processing.js";
2
+ import { type MathSpeechResolver } from "../assemble-plan.js";
3
+ import type { SREMathSpeechOptions } from "../../math-speech.js";
4
+ import type { GeneratedSpeechPlan } from "../types.js";
5
+ import type { DomAnchor } from "./types.js";
6
+ export interface BuildGeneratedSpeechResult {
7
+ plan: GeneratedSpeechPlan<DomAnchor>;
8
+ containsMathMarkup: boolean;
9
+ /** Aggregate visible text from the DOM walk (may be empty). */
10
+ visibleText: string;
11
+ }
12
+ /**
13
+ * DOM adapter: walk a live content root, assemble the (pure) speech plan, and
14
+ * bind each segment to its live-DOM anchor. This is the only generated-speech
15
+ * entry point that touches the DOM; assembly and serialization stay pure.
16
+ */
17
+ export declare const buildGeneratedSpeechFromRoot: (args: {
18
+ contentRoot: Element;
19
+ language?: string;
20
+ mathSpeech?: SREMathSpeechOptions;
21
+ textProcessingOptions?: TextProcessingOptions;
22
+ /** Request SRE SSML per equation (for the SSML playback format). */
23
+ produceSsml?: boolean;
24
+ resolveMathSpeech?: MathSpeechResolver;
25
+ }) => Promise<BuildGeneratedSpeechResult>;
@@ -0,0 +1,66 @@
1
+ import { resolveReadableRegion } from "../../highlight-pipeline/index.js";
2
+ import { collectMathAwareTextAndMap } from "../../math-aware-text-processing.js";
3
+ import { assembleGeneratedSpeech, } from "../assemble-plan.js";
4
+ const sliceVisibleMap = (map, start, end) => {
5
+ const sliced = new Map();
6
+ for (let index = start; index < end; index++) {
7
+ const mapping = map.get(index);
8
+ if (mapping) {
9
+ sliced.set(index - start, mapping);
10
+ }
11
+ }
12
+ return sliced;
13
+ };
14
+ /**
15
+ * DOM adapter: walk a live content root, assemble the (pure) speech plan, and
16
+ * bind each segment to its live-DOM anchor. This is the only generated-speech
17
+ * entry point that touches the DOM; assembly and serialization stay pure.
18
+ */
19
+ export const buildGeneratedSpeechFromRoot = async (args) => {
20
+ const extracted = collectMathAwareTextAndMap(args.contentRoot, args.textProcessingOptions);
21
+ const assembled = await assembleGeneratedSpeech({
22
+ chunks: extracted.chunks,
23
+ visibleText: extracted.visibleText,
24
+ language: args.language,
25
+ mathSpeech: args.mathSpeech,
26
+ produceSsml: args.produceSsml,
27
+ resolveMathSpeech: args.resolveMathSpeech,
28
+ });
29
+ const items = assembled.segments.map(({ segment, sourceChunkIndex }) => {
30
+ const sourceChunk = extracted.chunks[sourceChunkIndex];
31
+ if (segment.kind === "prose") {
32
+ const sourceElement = sourceChunk?.type === "text"
33
+ ? (sourceChunk.sourceElement ?? args.contentRoot)
34
+ : args.contentRoot;
35
+ return {
36
+ segment,
37
+ anchor: {
38
+ sourceElement,
39
+ regionElement: resolveReadableRegion(sourceElement, args.contentRoot),
40
+ visibleMap: sliceVisibleMap(extracted.map, segment.visibleSpan.start, segment.visibleSpan.end),
41
+ },
42
+ };
43
+ }
44
+ const mathElement = sourceChunk?.type === "math"
45
+ ? (sourceChunk.sourceElement ?? null)
46
+ : null;
47
+ return {
48
+ segment,
49
+ anchor: {
50
+ sourceElement: mathElement,
51
+ regionElement: mathElement,
52
+ },
53
+ };
54
+ });
55
+ return {
56
+ plan: {
57
+ items,
58
+ locale: assembled.locale,
59
+ visibleText: assembled.visibleText,
60
+ plainSpeechText: assembled.plainSpeechText,
61
+ },
62
+ containsMathMarkup: extracted.containsMathMarkup,
63
+ visibleText: extracted.visibleText,
64
+ };
65
+ };
66
+ //# sourceMappingURL=build-dom-plan.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build-dom-plan.js","sourceRoot":"","sources":["../../../../../src/services/tts/generated-speech/dom/build-dom-plan.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,mCAAmC,CAAC;AAC1E,OAAO,EAAE,0BAA0B,EAAE,MAAM,qCAAqC,CAAC;AAKjF,OAAO,EACN,uBAAuB,GAEvB,MAAM,qBAAqB,CAAC;AAK7B,MAAM,eAAe,GAAG,CACvB,GAAsB,EACtB,KAAa,EACb,GAAW,EACS,EAAE;IACtB,MAAM,MAAM,GAAsB,IAAI,GAAG,EAAE,CAAC;IAC5C,KAAK,IAAI,KAAK,GAAG,KAAK,EAAE,KAAK,GAAG,GAAG,EAAE,KAAK,EAAE,EAAE,CAAC;QAC9C,MAAM,OAAO,GAAG,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QAC/B,IAAI,OAAO,EAAE,CAAC;YACb,MAAM,CAAC,GAAG,CAAC,KAAK,GAAG,KAAK,EAAE,OAAO,CAAC,CAAC;QACpC,CAAC;IACF,CAAC;IACD,OAAO,MAAM,CAAC;AACf,CAAC,CAAC;AASF;;;;GAIG;AACH,MAAM,CAAC,MAAM,4BAA4B,GAAG,KAAK,EAAE,IAQlD,EAAuC,EAAE;IACzC,MAAM,SAAS,GAAG,0BAA0B,CAC3C,IAAI,CAAC,WAAW,EAChB,IAAI,CAAC,qBAAqB,CAC1B,CAAC;IACF,MAAM,SAAS,GAAG,MAAM,uBAAuB,CAAC;QAC/C,MAAM,EAAE,SAAS,CAAC,MAAM;QACxB,WAAW,EAAE,SAAS,CAAC,WAAW;QAClC,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,UAAU,EAAE,IAAI,CAAC,UAAU;QAC3B,WAAW,EAAE,IAAI,CAAC,WAAW;QAC7B,iBAAiB,EAAE,IAAI,CAAC,iBAAiB;KACzC,CAAC,CAAC;IAEH,MAAM,KAAK,GAA0B,SAAS,CAAC,QAAQ,CAAC,GAAG,CAC1D,CAAC,EAAE,OAAO,EAAE,gBAAgB,EAAE,EAAE,EAAE;QACjC,MAAM,WAAW,GAAG,SAAS,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC;QACvD,IAAI,OAAO,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;YAC9B,MAAM,aAAa,GAClB,WAAW,EAAE,IAAI,KAAK,MAAM;gBAC3B,CAAC,CAAC,CAAC,WAAW,CAAC,aAAa,IAAI,IAAI,CAAC,WAAW,CAAC;gBACjD,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC;YACrB,OAAO;gBACN,OAAO;gBACP,MAAM,EAAE;oBACP,aAAa;oBACb,aAAa,EAAE,qBAAqB,CACnC,aAAa,EACb,IAAI,CAAC,WAAW,CAChB;oBACD,UAAU,EAAE,eAAe,CAC1B,SAAS,CAAC,GAAG,EACb,OAAO,CAAC,WAAW,CAAC,KAAK,EACzB,OAAO,CAAC,WAAW,CAAC,GAAG,CACvB;iBACD;aACD,CAAC;QACH,CAAC;QACD,MAAM,WAAW,GAChB,WAAW,EAAE,IAAI,KAAK,MAAM;YAC3B,CAAC,CAAC,CAAC,WAAW,CAAC,aAAa,IAAI,IAAI,CAAC;YACrC,CAAC,CAAC,IAAI,CAAC;QACT,OAAO;YACN,OAAO;YACP,MAAM,EAAE;gBACP,aAAa,EAAE,WAAW;gBAC1B,aAAa,EAAE,WAAW;aAC1B;SACD,CAAC;IACH,CAAC,CACD,CAAC;IAEF,OAAO;QACN,IAAI,EAAE;YACL,KAAK;YACL,MAAM,EAAE,SAAS,CAAC,MAAM;YACxB,WAAW,EAAE,SAAS,CAAC,WAAW;YAClC,eAAe,EAAE,SAAS,CAAC,eAAe;SAC1C;QACD,kBAAkB,EAAE,SAAS,CAAC,kBAAkB;QAChD,WAAW,EAAE,SAAS,CAAC,WAAW;KAClC,CAAC;AACH,CAAC,CAAC","sourcesContent":["import { resolveReadableRegion } from \"../../highlight-pipeline/index.js\";\nimport { collectMathAwareTextAndMap } from \"../../math-aware-text-processing.js\";\nimport type {\n\tNormalizedTextMap,\n\tTextProcessingOptions,\n} from \"../../text-processing.js\";\nimport {\n\tassembleGeneratedSpeech,\n\ttype MathSpeechResolver,\n} from \"../assemble-plan.js\";\nimport type { SREMathSpeechOptions } from \"../../math-speech.js\";\nimport type { GeneratedSpeechPlan, PlanItem } from \"../types.js\";\nimport type { DomAnchor } from \"./types.js\";\n\nconst sliceVisibleMap = (\n\tmap: NormalizedTextMap,\n\tstart: number,\n\tend: number,\n): NormalizedTextMap => {\n\tconst sliced: NormalizedTextMap = new Map();\n\tfor (let index = start; index < end; index++) {\n\t\tconst mapping = map.get(index);\n\t\tif (mapping) {\n\t\t\tsliced.set(index - start, mapping);\n\t\t}\n\t}\n\treturn sliced;\n};\n\nexport interface BuildGeneratedSpeechResult {\n\tplan: GeneratedSpeechPlan<DomAnchor>;\n\tcontainsMathMarkup: boolean;\n\t/** Aggregate visible text from the DOM walk (may be empty). */\n\tvisibleText: string;\n}\n\n/**\n * DOM adapter: walk a live content root, assemble the (pure) speech plan, and\n * bind each segment to its live-DOM anchor. This is the only generated-speech\n * entry point that touches the DOM; assembly and serialization stay pure.\n */\nexport const buildGeneratedSpeechFromRoot = async (args: {\n\tcontentRoot: Element;\n\tlanguage?: string;\n\tmathSpeech?: SREMathSpeechOptions;\n\ttextProcessingOptions?: TextProcessingOptions;\n\t/** Request SRE SSML per equation (for the SSML playback format). */\n\tproduceSsml?: boolean;\n\tresolveMathSpeech?: MathSpeechResolver;\n}): Promise<BuildGeneratedSpeechResult> => {\n\tconst extracted = collectMathAwareTextAndMap(\n\t\targs.contentRoot,\n\t\targs.textProcessingOptions,\n\t);\n\tconst assembled = await assembleGeneratedSpeech({\n\t\tchunks: extracted.chunks,\n\t\tvisibleText: extracted.visibleText,\n\t\tlanguage: args.language,\n\t\tmathSpeech: args.mathSpeech,\n\t\tproduceSsml: args.produceSsml,\n\t\tresolveMathSpeech: args.resolveMathSpeech,\n\t});\n\n\tconst items: PlanItem<DomAnchor>[] = assembled.segments.map(\n\t\t({ segment, sourceChunkIndex }) => {\n\t\t\tconst sourceChunk = extracted.chunks[sourceChunkIndex];\n\t\t\tif (segment.kind === \"prose\") {\n\t\t\t\tconst sourceElement =\n\t\t\t\t\tsourceChunk?.type === \"text\"\n\t\t\t\t\t\t? (sourceChunk.sourceElement ?? args.contentRoot)\n\t\t\t\t\t\t: args.contentRoot;\n\t\t\t\treturn {\n\t\t\t\t\tsegment,\n\t\t\t\t\tanchor: {\n\t\t\t\t\t\tsourceElement,\n\t\t\t\t\t\tregionElement: resolveReadableRegion(\n\t\t\t\t\t\t\tsourceElement,\n\t\t\t\t\t\t\targs.contentRoot,\n\t\t\t\t\t\t),\n\t\t\t\t\t\tvisibleMap: sliceVisibleMap(\n\t\t\t\t\t\t\textracted.map,\n\t\t\t\t\t\t\tsegment.visibleSpan.start,\n\t\t\t\t\t\t\tsegment.visibleSpan.end,\n\t\t\t\t\t\t),\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\t\t\tconst mathElement =\n\t\t\t\tsourceChunk?.type === \"math\"\n\t\t\t\t\t? (sourceChunk.sourceElement ?? null)\n\t\t\t\t\t: null;\n\t\t\treturn {\n\t\t\t\tsegment,\n\t\t\t\tanchor: {\n\t\t\t\t\tsourceElement: mathElement,\n\t\t\t\t\tregionElement: mathElement,\n\t\t\t\t},\n\t\t\t};\n\t\t},\n\t);\n\n\treturn {\n\t\tplan: {\n\t\t\titems,\n\t\t\tlocale: assembled.locale,\n\t\t\tvisibleText: assembled.visibleText,\n\t\t\tplainSpeechText: assembled.plainSpeechText,\n\t\t},\n\t\tcontainsMathMarkup: extracted.containsMathMarkup,\n\t\tvisibleText: extracted.visibleText,\n\t};\n};\n"]}
@@ -0,0 +1,19 @@
1
+ import type { GeneratedSpeechPlan } from "../types.js";
2
+ import type { DomAnchor, GeneratedSpeechChunk } from "./types.js";
3
+ export interface PlanToChunksOptions {
4
+ /**
5
+ * Playback format:
6
+ * - "plain" (default): plain spoken text for every chunk (Phase A, the
7
+ * stable generated-speech behavior).
8
+ * - "ssml": math chunks with SRE SSML send that SSML to the provider (with
9
+ * a plain fallback attached); prose chunks always stay plain.
10
+ */
11
+ format?: "plain" | "ssml";
12
+ }
13
+ /**
14
+ * Convert an anchored speech plan into playback chunks for
15
+ * `TTSService.speakCatalogChunk`. The aggregate `contentToSpeak` stays plain
16
+ * (see `plan.plainSpeechText`), so no SSML leaks into break-detection or seek
17
+ * planning — SSML lives only in per-chunk `speechText`.
18
+ */
19
+ export declare const planToCompositionChunkInputs: (plan: GeneratedSpeechPlan<DomAnchor>, options?: PlanToChunksOptions) => GeneratedSpeechChunk[];
@@ -0,0 +1,71 @@
1
+ import { createCatalogSpanAlignment } from "../../catalog-span-alignment.js";
2
+ import { createRangeFromVisibleMap } from "../../highlight-pipeline/visible-map-range.js";
3
+ import { createMathAwareAlignment } from "../../math-alignment/index.js";
4
+ const buildProseChunk = (segment, anchor) => {
5
+ const alignment = createCatalogSpanAlignment({
6
+ speechText: segment.spokenText,
7
+ visibleText: segment.spokenText,
8
+ });
9
+ return {
10
+ speechText: segment.spokenText,
11
+ visibleText: segment.visibleText,
12
+ sourceElement: anchor.sourceElement,
13
+ regionElement: anchor.regionElement,
14
+ regionRange: createRangeFromVisibleMap(anchor.visibleMap, 0, segment.visibleText.length) ?? undefined,
15
+ speechMatchesVisibleText: true,
16
+ playbackMode: alignment.playbackMode,
17
+ alignment,
18
+ visibleMap: anchor.visibleMap,
19
+ };
20
+ };
21
+ const buildPlainMathChunk = (segment, anchor) => {
22
+ const mathElement = anchor.sourceElement;
23
+ return {
24
+ speechText: segment.spokenText,
25
+ visibleText: segment.fallbackText,
26
+ sourceElement: mathElement,
27
+ regionElement: anchor.regionElement,
28
+ speechMatchesVisibleText: segment.spokenText === segment.fallbackText,
29
+ mathAlignment: mathElement
30
+ ? createMathAwareAlignment({
31
+ mathElement,
32
+ speechText: segment.spokenText,
33
+ })
34
+ : undefined,
35
+ };
36
+ };
37
+ const buildSsmlMathChunk = (segment, anchor, ssml) => {
38
+ const mathElement = anchor.sourceElement;
39
+ return {
40
+ speechText: ssml,
41
+ visibleText: segment.fallbackText,
42
+ sourceElement: mathElement,
43
+ regionElement: anchor.regionElement,
44
+ // SSML never equals the visible fallback; the region-vs-token decision is
45
+ // driven by alignment confidence, not by this flag.
46
+ speechMatchesVisibleText: false,
47
+ // SINGLE SOURCE OF TRUTH: the exact SSML string sent to the provider is
48
+ // also the string alignment tokenizes, so provider word-boundary offsets
49
+ // (into the raw SSML) map back to spoken tokens via `rawToSpokenOffsetMap`.
50
+ mathAlignment: mathElement
51
+ ? createMathAwareAlignment({ mathElement, speechText: ssml })
52
+ : undefined,
53
+ // Defensive net: if the provider rejects the SSML, retry with plain text.
54
+ plainFallback: buildPlainMathChunk(segment, anchor),
55
+ };
56
+ };
57
+ /**
58
+ * Convert an anchored speech plan into playback chunks for
59
+ * `TTSService.speakCatalogChunk`. The aggregate `contentToSpeak` stays plain
60
+ * (see `plan.plainSpeechText`), so no SSML leaks into break-detection or seek
61
+ * planning — SSML lives only in per-chunk `speechText`.
62
+ */
63
+ export const planToCompositionChunkInputs = (plan, options = {}) => plan.items.map(({ segment, anchor }) => {
64
+ if (segment.kind === "prose")
65
+ return buildProseChunk(segment, anchor);
66
+ if (options.format === "ssml" && segment.ssml) {
67
+ return buildSsmlMathChunk(segment, anchor, segment.ssml);
68
+ }
69
+ return buildPlainMathChunk(segment, anchor);
70
+ });
71
+ //# sourceMappingURL=to-chunk-inputs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"to-chunk-inputs.js","sourceRoot":"","sources":["../../../../../src/services/tts/generated-speech/dom/to-chunk-inputs.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,0BAA0B,EAAE,MAAM,iCAAiC,CAAC;AAC7E,OAAO,EAAE,yBAAyB,EAAE,MAAM,+CAA+C,CAAC;AAC1F,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AAmBzE,MAAM,eAAe,GAAG,CACvB,OAAqB,EACrB,MAAiB,EACM,EAAE;IACzB,MAAM,SAAS,GAAG,0BAA0B,CAAC;QAC5C,UAAU,EAAE,OAAO,CAAC,UAAU;QAC9B,WAAW,EAAE,OAAO,CAAC,UAAU;KAC/B,CAAC,CAAC;IACH,OAAO;QACN,UAAU,EAAE,OAAO,CAAC,UAAU;QAC9B,WAAW,EAAE,OAAO,CAAC,WAAW;QAChC,aAAa,EAAE,MAAM,CAAC,aAAa;QACnC,aAAa,EAAE,MAAM,CAAC,aAAa;QACnC,WAAW,EACV,yBAAyB,CACxB,MAAM,CAAC,UAAU,EACjB,CAAC,EACD,OAAO,CAAC,WAAW,CAAC,MAAM,CAC1B,IAAI,SAAS;QACf,wBAAwB,EAAE,IAAI;QAC9B,YAAY,EAAE,SAAS,CAAC,YAAY;QACpC,SAAS;QACT,UAAU,EAAE,MAAM,CAAC,UAAU;KAC7B,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,mBAAmB,GAAG,CAC3B,OAAoB,EACpB,MAAiB,EACM,EAAE;IACzB,MAAM,WAAW,GAAG,MAAM,CAAC,aAAa,CAAC;IACzC,OAAO;QACN,UAAU,EAAE,OAAO,CAAC,UAAU;QAC9B,WAAW,EAAE,OAAO,CAAC,YAAY;QACjC,aAAa,EAAE,WAAW;QAC1B,aAAa,EAAE,MAAM,CAAC,aAAa;QACnC,wBAAwB,EAAE,OAAO,CAAC,UAAU,KAAK,OAAO,CAAC,YAAY;QACrE,aAAa,EAAE,WAAW;YACzB,CAAC,CAAC,wBAAwB,CAAC;gBACzB,WAAW;gBACX,UAAU,EAAE,OAAO,CAAC,UAAU;aAC9B,CAAC;YACH,CAAC,CAAC,SAAS;KACZ,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,kBAAkB,GAAG,CAC1B,OAAoB,EACpB,MAAiB,EACjB,IAAY,EACW,EAAE;IACzB,MAAM,WAAW,GAAG,MAAM,CAAC,aAAa,CAAC;IACzC,OAAO;QACN,UAAU,EAAE,IAAI;QAChB,WAAW,EAAE,OAAO,CAAC,YAAY;QACjC,aAAa,EAAE,WAAW;QAC1B,aAAa,EAAE,MAAM,CAAC,aAAa;QACnC,0EAA0E;QAC1E,oDAAoD;QACpD,wBAAwB,EAAE,KAAK;QAC/B,wEAAwE;QACxE,yEAAyE;QACzE,4EAA4E;QAC5E,aAAa,EAAE,WAAW;YACzB,CAAC,CAAC,wBAAwB,CAAC,EAAE,WAAW,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC;YAC7D,CAAC,CAAC,SAAS;QACZ,0EAA0E;QAC1E,aAAa,EAAE,mBAAmB,CAAC,OAAO,EAAE,MAAM,CAAC;KACnD,CAAC;AACH,CAAC,CAAC;AAEF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,4BAA4B,GAAG,CAC3C,IAAoC,EACpC,UAA+B,EAAE,EACR,EAAE,CAC3B,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE;IACtC,IAAI,OAAO,CAAC,IAAI,KAAK,OAAO;QAAE,OAAO,eAAe,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IACtE,IAAI,OAAO,CAAC,MAAM,KAAK,MAAM,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;QAC/C,OAAO,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1D,CAAC;IACD,OAAO,mBAAmB,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;AAC7C,CAAC,CAAC,CAAC","sourcesContent":["import { createCatalogSpanAlignment } from \"../../catalog-span-alignment.js\";\nimport { createRangeFromVisibleMap } from \"../../highlight-pipeline/visible-map-range.js\";\nimport { createMathAwareAlignment } from \"../../math-alignment/index.js\";\nimport type {\n\tGeneratedSpeechPlan,\n\tMathSegment,\n\tProseSegment,\n} from \"../types.js\";\nimport type { DomAnchor, GeneratedSpeechChunk } from \"./types.js\";\n\nexport interface PlanToChunksOptions {\n\t/**\n\t * Playback format:\n\t * - \"plain\" (default): plain spoken text for every chunk (Phase A, the\n\t * stable generated-speech behavior).\n\t * - \"ssml\": math chunks with SRE SSML send that SSML to the provider (with\n\t * a plain fallback attached); prose chunks always stay plain.\n\t */\n\tformat?: \"plain\" | \"ssml\";\n}\n\nconst buildProseChunk = (\n\tsegment: ProseSegment,\n\tanchor: DomAnchor,\n): GeneratedSpeechChunk => {\n\tconst alignment = createCatalogSpanAlignment({\n\t\tspeechText: segment.spokenText,\n\t\tvisibleText: segment.spokenText,\n\t});\n\treturn {\n\t\tspeechText: segment.spokenText,\n\t\tvisibleText: segment.visibleText,\n\t\tsourceElement: anchor.sourceElement,\n\t\tregionElement: anchor.regionElement,\n\t\tregionRange:\n\t\t\tcreateRangeFromVisibleMap(\n\t\t\t\tanchor.visibleMap,\n\t\t\t\t0,\n\t\t\t\tsegment.visibleText.length,\n\t\t\t) ?? undefined,\n\t\tspeechMatchesVisibleText: true,\n\t\tplaybackMode: alignment.playbackMode,\n\t\talignment,\n\t\tvisibleMap: anchor.visibleMap,\n\t};\n};\n\nconst buildPlainMathChunk = (\n\tsegment: MathSegment,\n\tanchor: DomAnchor,\n): GeneratedSpeechChunk => {\n\tconst mathElement = anchor.sourceElement;\n\treturn {\n\t\tspeechText: segment.spokenText,\n\t\tvisibleText: segment.fallbackText,\n\t\tsourceElement: mathElement,\n\t\tregionElement: anchor.regionElement,\n\t\tspeechMatchesVisibleText: segment.spokenText === segment.fallbackText,\n\t\tmathAlignment: mathElement\n\t\t\t? createMathAwareAlignment({\n\t\t\t\t\tmathElement,\n\t\t\t\t\tspeechText: segment.spokenText,\n\t\t\t\t})\n\t\t\t: undefined,\n\t};\n};\n\nconst buildSsmlMathChunk = (\n\tsegment: MathSegment,\n\tanchor: DomAnchor,\n\tssml: string,\n): GeneratedSpeechChunk => {\n\tconst mathElement = anchor.sourceElement;\n\treturn {\n\t\tspeechText: ssml,\n\t\tvisibleText: segment.fallbackText,\n\t\tsourceElement: mathElement,\n\t\tregionElement: anchor.regionElement,\n\t\t// SSML never equals the visible fallback; the region-vs-token decision is\n\t\t// driven by alignment confidence, not by this flag.\n\t\tspeechMatchesVisibleText: false,\n\t\t// SINGLE SOURCE OF TRUTH: the exact SSML string sent to the provider is\n\t\t// also the string alignment tokenizes, so provider word-boundary offsets\n\t\t// (into the raw SSML) map back to spoken tokens via `rawToSpokenOffsetMap`.\n\t\tmathAlignment: mathElement\n\t\t\t? createMathAwareAlignment({ mathElement, speechText: ssml })\n\t\t\t: undefined,\n\t\t// Defensive net: if the provider rejects the SSML, retry with plain text.\n\t\tplainFallback: buildPlainMathChunk(segment, anchor),\n\t};\n};\n\n/**\n * Convert an anchored speech plan into playback chunks for\n * `TTSService.speakCatalogChunk`. The aggregate `contentToSpeak` stays plain\n * (see `plan.plainSpeechText`), so no SSML leaks into break-detection or seek\n * planning — SSML lives only in per-chunk `speechText`.\n */\nexport const planToCompositionChunkInputs = (\n\tplan: GeneratedSpeechPlan<DomAnchor>,\n\toptions: PlanToChunksOptions = {},\n): GeneratedSpeechChunk[] =>\n\tplan.items.map(({ segment, anchor }) => {\n\t\tif (segment.kind === \"prose\") return buildProseChunk(segment, anchor);\n\t\tif (options.format === \"ssml\" && segment.ssml) {\n\t\t\treturn buildSsmlMathChunk(segment, anchor, segment.ssml);\n\t\t}\n\t\treturn buildPlainMathChunk(segment, anchor);\n\t});\n"]}
@@ -0,0 +1,37 @@
1
+ import type { CatalogChunkPlaybackMode, CatalogSpanAlignment } from "../../catalog-span-alignment.js";
2
+ import type { SpeechCompositionChunkInput } from "../../highlight-pipeline/index.js";
3
+ import type { MathAwareAlignment } from "../../math-alignment/index.js";
4
+ import type { NormalizedTextMap } from "../../text-processing.js";
5
+ /**
6
+ * Live-DOM anchor for a plan segment (runtime binding). The DOM adapter is the
7
+ * only place that produces these; the pure core never references DOM nodes.
8
+ */
9
+ export interface DomAnchor {
10
+ sourceElement: Element | null;
11
+ regionElement: Element | null;
12
+ /**
13
+ * Prose only: per-segment slice of the visible-text map, used to resolve
14
+ * highlight ranges. Math segments highlight via `mathAlignment` instead.
15
+ */
16
+ visibleMap?: NormalizedTextMap;
17
+ }
18
+ /**
19
+ * Playback chunk consumed by `TTSService.speakCatalogChunk`. Structurally the
20
+ * same shape the authored-catalog path produces; extends the highlight
21
+ * pipeline's stable {@link SpeechCompositionChunkInput} with the two playback
22
+ * fields `speakCatalogChunk` reads directly (`playbackMode`,
23
+ * `speechMatchesVisibleText`).
24
+ */
25
+ export interface GeneratedSpeechChunk extends SpeechCompositionChunkInput {
26
+ speechMatchesVisibleText: boolean;
27
+ playbackMode?: CatalogChunkPlaybackMode;
28
+ /**
29
+ * Plain-text variant of this chunk for speak-time fallback. Present only on
30
+ * SSML chunks; if the provider rejects the SSML `speechText`, the runtime
31
+ * retries with this plain chunk (same anchors, plain alignment). Browsers
32
+ * and SSML-incapable providers never receive the SSML variant in the first
33
+ * place (build-time provider gating), so this is a defensive net.
34
+ */
35
+ plainFallback?: GeneratedSpeechChunk;
36
+ }
37
+ export type { CatalogSpanAlignment, MathAwareAlignment };
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../../src/services/tts/generated-speech/dom/types.ts"],"names":[],"mappings":"","sourcesContent":["import type {\n\tCatalogChunkPlaybackMode,\n\tCatalogSpanAlignment,\n} from \"../../catalog-span-alignment.js\";\nimport type { SpeechCompositionChunkInput } from \"../../highlight-pipeline/index.js\";\nimport type { MathAwareAlignment } from \"../../math-alignment/index.js\";\nimport type { NormalizedTextMap } from \"../../text-processing.js\";\n\n/**\n * Live-DOM anchor for a plan segment (runtime binding). The DOM adapter is the\n * only place that produces these; the pure core never references DOM nodes.\n */\nexport interface DomAnchor {\n\tsourceElement: Element | null;\n\tregionElement: Element | null;\n\t/**\n\t * Prose only: per-segment slice of the visible-text map, used to resolve\n\t * highlight ranges. Math segments highlight via `mathAlignment` instead.\n\t */\n\tvisibleMap?: NormalizedTextMap;\n}\n\n/**\n * Playback chunk consumed by `TTSService.speakCatalogChunk`. Structurally the\n * same shape the authored-catalog path produces; extends the highlight\n * pipeline's stable {@link SpeechCompositionChunkInput} with the two playback\n * fields `speakCatalogChunk` reads directly (`playbackMode`,\n * `speechMatchesVisibleText`).\n */\nexport interface GeneratedSpeechChunk extends SpeechCompositionChunkInput {\n\tspeechMatchesVisibleText: boolean;\n\tplaybackMode?: CatalogChunkPlaybackMode;\n\t/**\n\t * Plain-text variant of this chunk for speak-time fallback. Present only on\n\t * SSML chunks; if the provider rejects the SSML `speechText`, the runtime\n\t * retries with this plain chunk (same anchors, plain alignment). Browsers\n\t * and SSML-incapable providers never receive the SSML variant in the first\n\t * place (build-time provider gating), so this is a defensive net.\n\t */\n\tplainFallback?: GeneratedSpeechChunk;\n}\n\nexport type { CatalogSpanAlignment, MathAwareAlignment };\n"]}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Generated-speech module (PIE-623): runtime speech composition for math items
3
+ * that ship without authored SSML / `accessibilityCatalogs`.
4
+ *
5
+ * Layering:
6
+ * - Pure core (`./assemble-plan`, `./math-speech-cache`, `./types`): no DOM
7
+ * walking, no runtime/Svelte imports. Enforced by
8
+ * `scripts/check-speech-composition-purity.mjs`.
9
+ * - DOM adapter (`./dom/*`): binds the plan to live DOM and emits playback
10
+ * chunks for `TTSService`.
11
+ */
12
+ export * from "./types.js";
13
+ export { assembleGeneratedSpeech, type MathSpeechResolver } from "./assemble-plan.js";
14
+ export { createMemoizedMathSpeechResolver, type MathSpeechCacheOptions, } from "./math-speech-cache.js";
15
+ export { buildGeneratedSpeechFromRoot, type BuildGeneratedSpeechResult, } from "./dom/build-dom-plan.js";
16
+ export { planToCompositionChunkInputs } from "./dom/to-chunk-inputs.js";
17
+ export type { DomAnchor, GeneratedSpeechChunk } from "./dom/types.js";
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Generated-speech module (PIE-623): runtime speech composition for math items
3
+ * that ship without authored SSML / `accessibilityCatalogs`.
4
+ *
5
+ * Layering:
6
+ * - Pure core (`./assemble-plan`, `./math-speech-cache`, `./types`): no DOM
7
+ * walking, no runtime/Svelte imports. Enforced by
8
+ * `scripts/check-speech-composition-purity.mjs`.
9
+ * - DOM adapter (`./dom/*`): binds the plan to live DOM and emits playback
10
+ * chunks for `TTSService`.
11
+ */
12
+ export * from "./types.js";
13
+ export { assembleGeneratedSpeech } from "./assemble-plan.js";
14
+ export { createMemoizedMathSpeechResolver, } from "./math-speech-cache.js";
15
+ export { buildGeneratedSpeechFromRoot, } from "./dom/build-dom-plan.js";
16
+ export { planToCompositionChunkInputs } from "./dom/to-chunk-inputs.js";
17
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/services/tts/generated-speech/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,uBAAuB,EAA2B,MAAM,oBAAoB,CAAC;AACtF,OAAO,EACN,gCAAgC,GAEhC,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACN,4BAA4B,GAE5B,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,4BAA4B,EAAE,MAAM,0BAA0B,CAAC","sourcesContent":["/**\n * Generated-speech module (PIE-623): runtime speech composition for math items\n * that ship without authored SSML / `accessibilityCatalogs`.\n *\n * Layering:\n * - Pure core (`./assemble-plan`, `./math-speech-cache`, `./types`): no DOM\n * walking, no runtime/Svelte imports. Enforced by\n * `scripts/check-speech-composition-purity.mjs`.\n * - DOM adapter (`./dom/*`): binds the plan to live DOM and emits playback\n * chunks for `TTSService`.\n */\n\nexport * from \"./types.js\";\nexport { assembleGeneratedSpeech, type MathSpeechResolver } from \"./assemble-plan.js\";\nexport {\n\tcreateMemoizedMathSpeechResolver,\n\ttype MathSpeechCacheOptions,\n} from \"./math-speech-cache.js\";\nexport {\n\tbuildGeneratedSpeechFromRoot,\n\ttype BuildGeneratedSpeechResult,\n} from \"./dom/build-dom-plan.js\";\nexport { planToCompositionChunkInputs } from \"./dom/to-chunk-inputs.js\";\nexport type { DomAnchor, GeneratedSpeechChunk } from \"./dom/types.js\";\n"]}
@@ -0,0 +1,15 @@
1
+ import type { MathSpeechResolver } from "./assemble-plan.js";
2
+ export interface MathSpeechCacheOptions {
3
+ maxEntries?: number;
4
+ /** Underlying resolver; defaults to the real SRE call. Injectable for tests. */
5
+ resolve?: MathSpeechResolver;
6
+ }
7
+ /**
8
+ * Build a memoized {@link MathSpeechResolver}.
9
+ *
10
+ * Caches only DOM-free spoken-text results keyed by
11
+ * `(SRE version, locale, output mode, SRE mathSpeech options, MathML source)`.
12
+ * Never caches DOM nodes or maps. Bounded by a simple LRU so long sessions do
13
+ * not grow unbounded.
14
+ */
15
+ export declare const createMemoizedMathSpeechResolver: (options?: MathSpeechCacheOptions) => MathSpeechResolver;
@@ -0,0 +1,78 @@
1
+ import { normalizeSREMathSpeechOptions, resolveMathSpeechFromChunks, } from "../math-speech.js";
2
+ const normalizeLocale = (language) => (language || "en").split("-")[0].toLowerCase() || "en";
3
+ // Bump when speech-rule-engine output semantics change in a way that must
4
+ // invalidate cached spoken text (e.g. an SRE upgrade with different phrasing).
5
+ const SRE_CACHE_VERSION = "sre-v1";
6
+ const DEFAULT_MAX_ENTRIES = 256;
7
+ const stableStringify = (value) => {
8
+ if (Array.isArray(value)) {
9
+ return `array:[${value.map(stableStringify).join(",")}]`;
10
+ }
11
+ if (value && typeof value === "object") {
12
+ return `object:{${Object.entries(value)
13
+ .sort(([left], [right]) => left.localeCompare(right))
14
+ .map(([key, entry]) => `${JSON.stringify(key)}:${stableStringify(entry)}`)
15
+ .join(",")}}`;
16
+ }
17
+ if (value === null)
18
+ return "null:null";
19
+ if (typeof value === "undefined")
20
+ return "undefined:";
21
+ if (typeof value === "function")
22
+ return `function:${value.name || "anonymous"}`;
23
+ if (typeof value === "symbol")
24
+ return `symbol:${String(value)}`;
25
+ if (typeof value === "bigint")
26
+ return `bigint:${value.toString()}`;
27
+ if (typeof value === "number") {
28
+ return Number.isFinite(value) ? `number:${value}` : `number:${String(value)}`;
29
+ }
30
+ return `${typeof value}:${JSON.stringify(value)}`;
31
+ };
32
+ const cacheKeyFor = (language, produceSsml, mathSpeech, mathml) => `${SRE_CACHE_VERSION}\u0000${normalizeLocale(language)}\u0000${produceSsml ? "ssml" : "plain"}\u0000${stableStringify(mathSpeech ?? null)}\u0000${mathml}`;
33
+ /**
34
+ * Build a memoized {@link MathSpeechResolver}.
35
+ *
36
+ * Caches only DOM-free spoken-text results keyed by
37
+ * `(SRE version, locale, output mode, SRE mathSpeech options, MathML source)`.
38
+ * Never caches DOM nodes or maps. Bounded by a simple LRU so long sessions do
39
+ * not grow unbounded.
40
+ */
41
+ export const createMemoizedMathSpeechResolver = (options = {}) => {
42
+ const maxEntries = Math.max(1, options.maxEntries ?? DEFAULT_MAX_ENTRIES);
43
+ const resolve = options.resolve ??
44
+ ((chunk, opts) => resolveMathSpeechFromChunks([chunk], opts));
45
+ const cache = new Map();
46
+ const setCached = (key, value) => {
47
+ cache.delete(key);
48
+ cache.set(key, value);
49
+ while (cache.size > maxEntries) {
50
+ const oldest = cache.keys().next().value;
51
+ if (oldest !== undefined)
52
+ cache.delete(oldest);
53
+ }
54
+ };
55
+ return async (chunk, opts) => {
56
+ const mathSpeech = normalizeSREMathSpeechOptions(opts.mathSpeech);
57
+ const produceSsml = opts.produceSsml === true;
58
+ const key = cacheKeyFor(opts.language, produceSsml, mathSpeech, chunk.mathml);
59
+ const cached = cache.get(key);
60
+ if (cached) {
61
+ // Refresh LRU recency.
62
+ cache.delete(key);
63
+ cache.set(key, cached);
64
+ return cached;
65
+ }
66
+ const resolved = await resolve(chunk, { ...opts, mathSpeech });
67
+ if (produceSsml && resolved.ssml && maxEntries > 1) {
68
+ setCached(cacheKeyFor(opts.language, false, mathSpeech, chunk.mathml), {
69
+ speechText: resolved.speechText,
70
+ usedMathSpeech: resolved.usedMathSpeech,
71
+ usedFallback: resolved.usedFallback,
72
+ });
73
+ }
74
+ setCached(key, resolved);
75
+ return resolved;
76
+ };
77
+ };
78
+ //# sourceMappingURL=math-speech-cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"math-speech-cache.js","sourceRoot":"","sources":["../../../../src/services/tts/generated-speech/math-speech-cache.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,6BAA6B,EAC7B,2BAA2B,GAG3B,MAAM,mBAAmB,CAAC;AAG3B,MAAM,eAAe,GAAG,CAAC,QAAiB,EAAU,EAAE,CACrD,CAAC,QAAQ,IAAI,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,IAAI,IAAI,CAAC;AAExD,0EAA0E;AAC1E,+EAA+E;AAC/E,MAAM,iBAAiB,GAAG,QAAQ,CAAC;AAEnC,MAAM,mBAAmB,GAAG,GAAG,CAAC;AAEhC,MAAM,eAAe,GAAG,CAAC,KAAc,EAAU,EAAE;IAClD,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO,UAAU,KAAK,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;IAC1D,CAAC;IACD,IAAI,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QACxC,OAAO,WAAW,MAAM,CAAC,OAAO,CAAC,KAAgC,CAAC;aAChE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;aACpD,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,eAAe,CAAC,KAAK,CAAC,EAAE,CAAC;aACzE,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;IAChB,CAAC;IACD,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,WAAW,CAAC;IACvC,IAAI,OAAO,KAAK,KAAK,WAAW;QAAE,OAAO,YAAY,CAAC;IACtD,IAAI,OAAO,KAAK,KAAK,UAAU;QAAE,OAAO,YAAY,KAAK,CAAC,IAAI,IAAI,WAAW,EAAE,CAAC;IAChF,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,UAAU,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;IAChE,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,UAAU,KAAK,CAAC,QAAQ,EAAE,EAAE,CAAC;IACnE,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC/B,OAAO,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,UAAU,KAAK,EAAE,CAAC,CAAC,CAAC,UAAU,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;IAC/E,CAAC;IACD,OAAO,GAAG,OAAO,KAAK,IAAI,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC;AACnD,CAAC,CAAC;AAEF,MAAM,WAAW,GAAG,CACnB,QAA4B,EAC5B,WAAoB,EACpB,UAA4C,EAC5C,MAAc,EACL,EAAE,CACX,GAAG,iBAAiB,SAAS,eAAe,CAAC,QAAQ,CAAC,SACrD,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OACxB,SAAS,eAAe,CAAC,UAAU,IAAI,IAAI,CAAC,SAAS,MAAM,EAAE,CAAC;AAQ/D;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,gCAAgC,GAAG,CAC/C,UAAkC,EAAE,EACf,EAAE;IACvB,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,CAAC,UAAU,IAAI,mBAAmB,CAAC,CAAC;IAC1E,MAAM,OAAO,GACZ,OAAO,CAAC,OAAO;QACf,CAAC,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,2BAA2B,CAAC,CAAC,KAAK,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;IAC/D,MAAM,KAAK,GAAG,IAAI,GAAG,EAA8B,CAAC;IACpD,MAAM,SAAS,GAAG,CAAC,GAAW,EAAE,KAAyB,EAAQ,EAAE;QAClE,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAClB,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QACtB,OAAO,KAAK,CAAC,IAAI,GAAG,UAAU,EAAE,CAAC;YAChC,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC;YACzC,IAAI,MAAM,KAAK,SAAS;gBAAE,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAChD,CAAC;IACF,CAAC,CAAC;IAEF,OAAO,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE;QAC5B,MAAM,UAAU,GAAG,6BAA6B,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAClE,MAAM,WAAW,GAAG,IAAI,CAAC,WAAW,KAAK,IAAI,CAAC;QAC9C,MAAM,GAAG,GAAG,WAAW,CACtB,IAAI,CAAC,QAAQ,EACb,WAAW,EACX,UAAU,EACV,KAAK,CAAC,MAAM,CACZ,CAAC;QACF,MAAM,MAAM,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC9B,IAAI,MAAM,EAAE,CAAC;YACZ,uBAAuB;YACvB,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAClB,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;YACvB,OAAO,MAAM,CAAC;QACf,CAAC;QACD,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,KAAK,EAAE,EAAE,GAAG,IAAI,EAAE,UAAU,EAAE,CAAC,CAAC;QAC/D,IAAI,WAAW,IAAI,QAAQ,CAAC,IAAI,IAAI,UAAU,GAAG,CAAC,EAAE,CAAC;YACpD,SAAS,CACR,WAAW,CAAC,IAAI,CAAC,QAAQ,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,CAAC,MAAM,CAAC,EAC3D;gBACC,UAAU,EAAE,QAAQ,CAAC,UAAU;gBAC/B,cAAc,EAAE,QAAQ,CAAC,cAAc;gBACvC,YAAY,EAAE,QAAQ,CAAC,YAAY;aACnC,CACD,CAAC;QACH,CAAC;QACD,SAAS,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;QACzB,OAAO,QAAQ,CAAC;IACjB,CAAC,CAAC;AACH,CAAC,CAAC","sourcesContent":["import {\n\tnormalizeSREMathSpeechOptions,\n\tresolveMathSpeechFromChunks,\n\ttype ResolvedMathSpeech,\n\ttype SREMathSpeechOptions,\n} from \"../math-speech.js\";\nimport type { MathSpeechResolver } from \"./assemble-plan.js\";\n\nconst normalizeLocale = (language?: string): string =>\n\t(language || \"en\").split(\"-\")[0].toLowerCase() || \"en\";\n\n// Bump when speech-rule-engine output semantics change in a way that must\n// invalidate cached spoken text (e.g. an SRE upgrade with different phrasing).\nconst SRE_CACHE_VERSION = \"sre-v1\";\n\nconst DEFAULT_MAX_ENTRIES = 256;\n\nconst stableStringify = (value: unknown): string => {\n\tif (Array.isArray(value)) {\n\t\treturn `array:[${value.map(stableStringify).join(\",\")}]`;\n\t}\n\tif (value && typeof value === \"object\") {\n\t\treturn `object:{${Object.entries(value as Record<string, unknown>)\n\t\t\t.sort(([left], [right]) => left.localeCompare(right))\n\t\t\t.map(([key, entry]) => `${JSON.stringify(key)}:${stableStringify(entry)}`)\n\t\t\t.join(\",\")}}`;\n\t}\n\tif (value === null) return \"null:null\";\n\tif (typeof value === \"undefined\") return \"undefined:\";\n\tif (typeof value === \"function\") return `function:${value.name || \"anonymous\"}`;\n\tif (typeof value === \"symbol\") return `symbol:${String(value)}`;\n\tif (typeof value === \"bigint\") return `bigint:${value.toString()}`;\n\tif (typeof value === \"number\") {\n\t\treturn Number.isFinite(value) ? `number:${value}` : `number:${String(value)}`;\n\t}\n\treturn `${typeof value}:${JSON.stringify(value)}`;\n};\n\nconst cacheKeyFor = (\n\tlanguage: string | undefined,\n\tproduceSsml: boolean,\n\tmathSpeech: SREMathSpeechOptions | undefined,\n\tmathml: string,\n): string =>\n\t`${SRE_CACHE_VERSION}\\u0000${normalizeLocale(language)}\\u0000${\n\t\tproduceSsml ? \"ssml\" : \"plain\"\n\t}\\u0000${stableStringify(mathSpeech ?? null)}\\u0000${mathml}`;\n\nexport interface MathSpeechCacheOptions {\n\tmaxEntries?: number;\n\t/** Underlying resolver; defaults to the real SRE call. Injectable for tests. */\n\tresolve?: MathSpeechResolver;\n}\n\n/**\n * Build a memoized {@link MathSpeechResolver}.\n *\n * Caches only DOM-free spoken-text results keyed by\n * `(SRE version, locale, output mode, SRE mathSpeech options, MathML source)`.\n * Never caches DOM nodes or maps. Bounded by a simple LRU so long sessions do\n * not grow unbounded.\n */\nexport const createMemoizedMathSpeechResolver = (\n\toptions: MathSpeechCacheOptions = {},\n): MathSpeechResolver => {\n\tconst maxEntries = Math.max(1, options.maxEntries ?? DEFAULT_MAX_ENTRIES);\n\tconst resolve =\n\t\toptions.resolve ??\n\t\t((chunk, opts) => resolveMathSpeechFromChunks([chunk], opts));\n\tconst cache = new Map<string, ResolvedMathSpeech>();\n\tconst setCached = (key: string, value: ResolvedMathSpeech): void => {\n\t\tcache.delete(key);\n\t\tcache.set(key, value);\n\t\twhile (cache.size > maxEntries) {\n\t\t\tconst oldest = cache.keys().next().value;\n\t\t\tif (oldest !== undefined) cache.delete(oldest);\n\t\t}\n\t};\n\n\treturn async (chunk, opts) => {\n\t\tconst mathSpeech = normalizeSREMathSpeechOptions(opts.mathSpeech);\n\t\tconst produceSsml = opts.produceSsml === true;\n\t\tconst key = cacheKeyFor(\n\t\t\topts.language,\n\t\t\tproduceSsml,\n\t\t\tmathSpeech,\n\t\t\tchunk.mathml,\n\t\t);\n\t\tconst cached = cache.get(key);\n\t\tif (cached) {\n\t\t\t// Refresh LRU recency.\n\t\t\tcache.delete(key);\n\t\t\tcache.set(key, cached);\n\t\t\treturn cached;\n\t\t}\n\t\tconst resolved = await resolve(chunk, { ...opts, mathSpeech });\n\t\tif (produceSsml && resolved.ssml && maxEntries > 1) {\n\t\t\tsetCached(\n\t\t\t\tcacheKeyFor(opts.language, false, mathSpeech, chunk.mathml),\n\t\t\t\t{\n\t\t\t\t\tspeechText: resolved.speechText,\n\t\t\t\t\tusedMathSpeech: resolved.usedMathSpeech,\n\t\t\t\t\tusedFallback: resolved.usedFallback,\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\t\tsetCached(key, resolved);\n\t\treturn resolved;\n\t};\n};\n"]}
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Generated-speech plan model (PURE core).
3
+ *
4
+ * This module is the speech-composition core for items that contain math but
5
+ * ship without authored SSML / `accessibilityCatalogs` (PIE-623). It is
6
+ * deliberately free of any imports from `TTSService`, the highlight renderer,
7
+ * `HighlightCoordinator`, Svelte, or custom-element entrypoints
8
+ * (`scripts/check-speech-composition-purity.mjs` enforces this). DOM primitive
9
+ * types (`Element`) and an injectable SRE resolver are allowed; live-DOM
10
+ * walking, anchor binding, alignment, and rendering live in `./dom/`.
11
+ *
12
+ * The plan is generic over `Anchor` so the same assembly + SSML serialization
13
+ * can be reused by a future authoring/persistable path (serializable locators)
14
+ * without rewriting the core. The runtime binds live DOM via `DomAnchor`
15
+ * (see `./dom/types.ts`).
16
+ */
17
+ export type StructuralBreakStrength = "weak" | "medium" | "strong";
18
+ export interface ProseSegment {
19
+ kind: "prose";
20
+ /** Spoken words for this prose run. Identical to `visibleText` today. */
21
+ spokenText: string;
22
+ /** Visible text run this segment was collected from. */
23
+ visibleText: string;
24
+ /**
25
+ * Character span of this run within the plan's aggregate `visibleText`.
26
+ * Used by the DOM adapter to slice the visible-text map for highlighting.
27
+ */
28
+ visibleSpan: {
29
+ start: number;
30
+ end: number;
31
+ };
32
+ }
33
+ export interface MathSegment {
34
+ kind: "math";
35
+ /** Spoken words for the equation (SRE output, or the visible fallback). */
36
+ spokenText: string;
37
+ /** Canonical MathML for the equation. */
38
+ mathml: string;
39
+ /** Visible fallback text (rendered glyphs) for the equation. */
40
+ fallbackText: string;
41
+ /** Character span of `fallbackText` within the plan's aggregate visible text. */
42
+ visibleSpan: {
43
+ start: number;
44
+ end: number;
45
+ };
46
+ /** True when SRE produced nothing and the visible fallback is spoken instead. */
47
+ usedFallback: boolean;
48
+ /**
49
+ * SRE SSML rendering of the equation (a self-contained `<speak>` document),
50
+ * present only when SSML production was requested and SRE produced trackable
51
+ * SSML. Highlight-safe (structural tags only); the same string is fed to the
52
+ * provider and to alignment so raw-SSML word offsets map correctly.
53
+ */
54
+ ssml?: string;
55
+ }
56
+ /**
57
+ * A structural pause. Breaks are never standalone speakable units: the DOM
58
+ * adapter folds them into the SSML of an adjacent speakable segment (or relies
59
+ * on the gap between sequential per-chunk utterances). Modeled here so the
60
+ * plan can carry pause intent for the SSML serializer.
61
+ */
62
+ export interface StructuralBreak {
63
+ kind: "break";
64
+ strength: StructuralBreakStrength;
65
+ }
66
+ export type SpeechSegment = ProseSegment | MathSegment;
67
+ export type PlanSegment = SpeechSegment | StructuralBreak;
68
+ /** One assembled (still anchor-free) segment plus its source chunk index. */
69
+ export interface AssembledSegment {
70
+ segment: SpeechSegment;
71
+ /** Index into the source `MathAwareSpeechChunk[]` used to attach anchors. */
72
+ sourceChunkIndex: number;
73
+ }
74
+ /** Pure assembly output: spoken text + visible spans, no DOM anchors yet. */
75
+ export interface AssembledSpeech {
76
+ segments: AssembledSegment[];
77
+ locale: string;
78
+ /** Aggregate visible text (prose + math fallbacks) in document order. */
79
+ visibleText: string;
80
+ /**
81
+ * Aggregate plain spoken text (NEVER SSML). Carried so playback metadata /
82
+ * flags can stay plain even when per-chunk payloads become SSML.
83
+ */
84
+ plainSpeechText: string;
85
+ }
86
+ /**
87
+ * One ordered plan entry pairing a content segment with an opaque `Anchor`
88
+ * that points back at its source.
89
+ */
90
+ export interface PlanItem<Anchor> {
91
+ segment: SpeechSegment;
92
+ anchor: Anchor;
93
+ }
94
+ /**
95
+ * An anchored speech plan, generic over the anchor type. `serializeToSsml`
96
+ * consumes it without touching `Anchor` (content is anchor-free); the DOM
97
+ * adapter consumes it to build highlight chunks.
98
+ */
99
+ export interface GeneratedSpeechPlan<Anchor> {
100
+ items: PlanItem<Anchor>[];
101
+ locale: string;
102
+ visibleText: string;
103
+ plainSpeechText: string;
104
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Generated-speech plan model (PURE core).
3
+ *
4
+ * This module is the speech-composition core for items that contain math but
5
+ * ship without authored SSML / `accessibilityCatalogs` (PIE-623). It is
6
+ * deliberately free of any imports from `TTSService`, the highlight renderer,
7
+ * `HighlightCoordinator`, Svelte, or custom-element entrypoints
8
+ * (`scripts/check-speech-composition-purity.mjs` enforces this). DOM primitive
9
+ * types (`Element`) and an injectable SRE resolver are allowed; live-DOM
10
+ * walking, anchor binding, alignment, and rendering live in `./dom/`.
11
+ *
12
+ * The plan is generic over `Anchor` so the same assembly + SSML serialization
13
+ * can be reused by a future authoring/persistable path (serializable locators)
14
+ * without rewriting the core. The runtime binds live DOM via `DomAnchor`
15
+ * (see `./dom/types.ts`).
16
+ */
17
+ export {};
18
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../src/services/tts/generated-speech/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG","sourcesContent":["/**\n * Generated-speech plan model (PURE core).\n *\n * This module is the speech-composition core for items that contain math but\n * ship without authored SSML / `accessibilityCatalogs` (PIE-623). It is\n * deliberately free of any imports from `TTSService`, the highlight renderer,\n * `HighlightCoordinator`, Svelte, or custom-element entrypoints\n * (`scripts/check-speech-composition-purity.mjs` enforces this). DOM primitive\n * types (`Element`) and an injectable SRE resolver are allowed; live-DOM\n * walking, anchor binding, alignment, and rendering live in `./dom/`.\n *\n * The plan is generic over `Anchor` so the same assembly + SSML serialization\n * can be reused by a future authoring/persistable path (serializable locators)\n * without rewriting the core. The runtime binds live DOM via `DomAnchor`\n * (see `./dom/types.ts`).\n */\n\nexport type StructuralBreakStrength = \"weak\" | \"medium\" | \"strong\";\n\nexport interface ProseSegment {\n\tkind: \"prose\";\n\t/** Spoken words for this prose run. Identical to `visibleText` today. */\n\tspokenText: string;\n\t/** Visible text run this segment was collected from. */\n\tvisibleText: string;\n\t/**\n\t * Character span of this run within the plan's aggregate `visibleText`.\n\t * Used by the DOM adapter to slice the visible-text map for highlighting.\n\t */\n\tvisibleSpan: { start: number; end: number };\n}\n\nexport interface MathSegment {\n\tkind: \"math\";\n\t/** Spoken words for the equation (SRE output, or the visible fallback). */\n\tspokenText: string;\n\t/** Canonical MathML for the equation. */\n\tmathml: string;\n\t/** Visible fallback text (rendered glyphs) for the equation. */\n\tfallbackText: string;\n\t/** Character span of `fallbackText` within the plan's aggregate visible text. */\n\tvisibleSpan: { start: number; end: number };\n\t/** True when SRE produced nothing and the visible fallback is spoken instead. */\n\tusedFallback: boolean;\n\t/**\n\t * SRE SSML rendering of the equation (a self-contained `<speak>` document),\n\t * present only when SSML production was requested and SRE produced trackable\n\t * SSML. Highlight-safe (structural tags only); the same string is fed to the\n\t * provider and to alignment so raw-SSML word offsets map correctly.\n\t */\n\tssml?: string;\n}\n\n/**\n * A structural pause. Breaks are never standalone speakable units: the DOM\n * adapter folds them into the SSML of an adjacent speakable segment (or relies\n * on the gap between sequential per-chunk utterances). Modeled here so the\n * plan can carry pause intent for the SSML serializer.\n */\nexport interface StructuralBreak {\n\tkind: \"break\";\n\tstrength: StructuralBreakStrength;\n}\n\nexport type SpeechSegment = ProseSegment | MathSegment;\nexport type PlanSegment = SpeechSegment | StructuralBreak;\n\n/** One assembled (still anchor-free) segment plus its source chunk index. */\nexport interface AssembledSegment {\n\tsegment: SpeechSegment;\n\t/** Index into the source `MathAwareSpeechChunk[]` used to attach anchors. */\n\tsourceChunkIndex: number;\n}\n\n/** Pure assembly output: spoken text + visible spans, no DOM anchors yet. */\nexport interface AssembledSpeech {\n\tsegments: AssembledSegment[];\n\tlocale: string;\n\t/** Aggregate visible text (prose + math fallbacks) in document order. */\n\tvisibleText: string;\n\t/**\n\t * Aggregate plain spoken text (NEVER SSML). Carried so playback metadata /\n\t * flags can stay plain even when per-chunk payloads become SSML.\n\t */\n\tplainSpeechText: string;\n}\n\n/**\n * One ordered plan entry pairing a content segment with an opaque `Anchor`\n * that points back at its source.\n */\nexport interface PlanItem<Anchor> {\n\tsegment: SpeechSegment;\n\tanchor: Anchor;\n}\n\n/**\n * An anchored speech plan, generic over the anchor type. `serializeToSsml`\n * consumes it without touching `Anchor` (content is anchor-free); the DOM\n * adapter consumes it to build highlight chunks.\n */\nexport interface GeneratedSpeechPlan<Anchor> {\n\titems: PlanItem<Anchor>[];\n\tlocale: string;\n\tvisibleText: string;\n\tplainSpeechText: string;\n}\n"]}
@@ -0,0 +1,2 @@
1
+ import type { NormalizedBoundaryEvent, TTSBoundaryEvent, TTSHighlightChunk } from "./types.js";
2
+ export declare const normalizeBoundaryEvent: (chunk: TTSHighlightChunk, event: TTSBoundaryEvent) => NormalizedBoundaryEvent;