@pie-players/pie-assessment-toolkit 0.3.28 → 0.3.30

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 (36) hide show
  1. package/README.md +24 -0
  2. package/dist/components/ItemToolBar.custom-element.js +197 -84
  3. package/dist/index.d.ts +1 -0
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +1 -0
  6. package/dist/index.js.map +1 -1
  7. package/dist/services/SSMLExtractor.d.ts.map +1 -1
  8. package/dist/services/SSMLExtractor.js +189 -2
  9. package/dist/services/SSMLExtractor.js.map +1 -1
  10. package/dist/services/TTSService.d.ts +12 -1
  11. package/dist/services/TTSService.d.ts.map +1 -1
  12. package/dist/services/TTSService.js +101 -24
  13. package/dist/services/TTSService.js.map +1 -1
  14. package/dist/services/ToolRegistry.d.ts.map +1 -1
  15. package/dist/services/ToolRegistry.js +30 -0
  16. package/dist/services/ToolRegistry.js.map +1 -1
  17. package/dist/services/ToolkitCoordinator.d.ts.map +1 -1
  18. package/dist/services/ToolkitCoordinator.js +8 -2
  19. package/dist/services/ToolkitCoordinator.js.map +1 -1
  20. package/dist/services/interfaces.d.ts +7 -0
  21. package/dist/services/interfaces.d.ts.map +1 -1
  22. package/dist/services/interfaces.js.map +1 -1
  23. package/dist/services/tool-config-validation.js +1 -1
  24. package/dist/services/tool-config-validation.js.map +1 -1
  25. package/dist/services/tools-config-normalizer.d.ts +1 -1
  26. package/dist/services/tools-config-normalizer.d.ts.map +1 -1
  27. package/dist/services/tts-control-events.d.ts +14 -0
  28. package/dist/services/tts-control-events.d.ts.map +1 -0
  29. package/dist/services/tts-control-events.js +8 -0
  30. package/dist/services/tts-control-events.js.map +1 -0
  31. package/dist/tools/calculators/desmos-provider.js +18 -0
  32. package/dist/tools/calculators/desmos-provider.js.map +1 -1
  33. package/dist/tools/default-tool-module-loaders.d.ts.map +1 -1
  34. package/dist/tools/default-tool-module-loaders.js +8 -2
  35. package/dist/tools/default-tool-module-loaders.js.map +1 -1
  36. package/package.json +9 -8
@@ -16,6 +16,189 @@
16
16
  *
17
17
  * Part of PIE Assessment Toolkit.
18
18
  */
19
+ /**
20
+ * Allow-listed SSML elements (from the SSML 1.1 spec subset we forward to
21
+ * TTS providers). Anything outside this list is unwrapped during
22
+ * sanitization so we never leak author-supplied scripts, iframes, or
23
+ * arbitrary HTML into the TTS pipeline.
24
+ */
25
+ const SSML_ALLOWED_TAGS = new Set([
26
+ "speak",
27
+ "break",
28
+ "prosody",
29
+ "emphasis",
30
+ "say-as",
31
+ "sub",
32
+ "mark",
33
+ "phoneme",
34
+ "lang",
35
+ "p",
36
+ "s",
37
+ "voice",
38
+ "audio",
39
+ "lexicon",
40
+ ]);
41
+ /**
42
+ * Allow-listed attributes across SSML elements. Attribute allow-listing is
43
+ * global (not per-tag) because the union is small and consistent across
44
+ * providers.
45
+ */
46
+ /**
47
+ * Tags whose content must be dropped entirely (rather than unwrapped to
48
+ * preserve phrasing text). These are dangerous containers whose text
49
+ * payload could be re-interpreted downstream.
50
+ */
51
+ const SSML_DROP_FULLY_TAGS = new Set([
52
+ "script",
53
+ "style",
54
+ "iframe",
55
+ "object",
56
+ "embed",
57
+ "foreignobject",
58
+ "noscript",
59
+ "template",
60
+ ]);
61
+ const SSML_ALLOWED_ATTRS = new Set([
62
+ "alphabet",
63
+ "ph",
64
+ "time",
65
+ "strength",
66
+ "rate",
67
+ "pitch",
68
+ "volume",
69
+ "name",
70
+ "interpret-as",
71
+ "format",
72
+ "detail",
73
+ "language",
74
+ "xml:lang",
75
+ "lang",
76
+ "gender",
77
+ "src",
78
+ ]);
79
+ /**
80
+ * Hosts that must never appear inside an SSML `<audio src>` because they
81
+ * would cause the server-side TTS provider to fetch a private / metadata
82
+ * endpoint on behalf of the request. Mirrors the SchoolCity provider's
83
+ * defense-in-depth check; the SSML-side block is cheap and belt-and-braces.
84
+ */
85
+ const SSML_PRIVATE_AUDIO_HOSTS = [
86
+ /^localhost$/i,
87
+ /^127(?:\.\d{1,3}){3}$/,
88
+ /^0(?:\.\d{1,3}){3}$/,
89
+ /^10(?:\.\d{1,3}){3}$/,
90
+ /^192\.168(?:\.\d{1,3}){2}$/,
91
+ /^172\.(?:1[6-9]|2\d|3[01])(?:\.\d{1,3}){2}$/,
92
+ /^169\.254(?:\.\d{1,3}){2}$/,
93
+ /^::1$/,
94
+ /^fe80:/i,
95
+ /^fc00:/i,
96
+ /^fd[0-9a-f]{2}:/i,
97
+ /^metadata\.google\.internal$/i,
98
+ /^metadata\.azure\.internal$/i,
99
+ /^metadata\.packet\.net$/i,
100
+ ];
101
+ function isSafeSsmlAudioSrc(raw) {
102
+ if (typeof raw !== "string" || raw.length === 0)
103
+ return false;
104
+ let parsed;
105
+ try {
106
+ parsed = new URL(raw);
107
+ }
108
+ catch {
109
+ return false;
110
+ }
111
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
112
+ return false;
113
+ }
114
+ const hostname = parsed.hostname.replace(/^\[|\]$/g, "").replace(/\.$/, "");
115
+ return !SSML_PRIVATE_AUDIO_HOSTS.some((pattern) => pattern.test(hostname));
116
+ }
117
+ /**
118
+ * Sanitize an SSML subtree in place: remove disallowed attributes and
119
+ * replace disallowed elements with their text content (so phrasing order
120
+ * is preserved but no scripts / iframes / unknown tags survive).
121
+ */
122
+ function sanitizeSsmlElement(element) {
123
+ // Walk children snapshot first because we may replace nodes as we go.
124
+ const children = Array.from(element.children);
125
+ for (const child of children) {
126
+ const tagName = child.tagName.toLowerCase();
127
+ if (SSML_DROP_FULLY_TAGS.has(tagName)) {
128
+ child.remove();
129
+ continue;
130
+ }
131
+ sanitizeSsmlElement(child);
132
+ if (!SSML_ALLOWED_TAGS.has(tagName)) {
133
+ const textNode = element.ownerDocument.createTextNode(child.textContent ?? "");
134
+ child.replaceWith(textNode);
135
+ }
136
+ }
137
+ // Strip disallowed attributes on the surviving node.
138
+ const attrNames = Array.from(element.attributes).map((attr) => attr.name);
139
+ for (const attrName of attrNames) {
140
+ if (!SSML_ALLOWED_ATTRS.has(attrName.toLowerCase())) {
141
+ element.removeAttribute(attrName);
142
+ }
143
+ }
144
+ // Belt-and-braces: even though `src` is allow-listed, SSML `<audio>`
145
+ // sends its URL to a server-side TTS provider that will dereference
146
+ // it. Refuse non-http(s) schemes and private / cloud-metadata hosts
147
+ // at the SSML boundary so we never forward an SSRF primitive.
148
+ if (element.tagName.toLowerCase() === "audio") {
149
+ const src = element.getAttribute("src");
150
+ if (src !== null && !isSafeSsmlAudioSrc(src)) {
151
+ element.removeAttribute("src");
152
+ }
153
+ }
154
+ }
155
+ /**
156
+ * Serialize a sanitized `<speak>` element back to a minimal SSML string.
157
+ * Uses a manual walker instead of `outerHTML` so that any namespace
158
+ * quirks (e.g. `xmlns` attributes added by the HTML parser) are
159
+ * normalised and disallowed descendants cannot reappear through quirks
160
+ * in the serializer.
161
+ */
162
+ function serializeSsmlElement(element) {
163
+ const tagName = element.tagName.toLowerCase();
164
+ const attrs = Array.from(element.attributes)
165
+ .filter((attr) => SSML_ALLOWED_ATTRS.has(attr.name.toLowerCase()))
166
+ .map((attr) => ` ${attr.name}="${escapeXmlAttr(attr.value)}"`)
167
+ .join("");
168
+ const childPieces = [];
169
+ for (const node of Array.from(element.childNodes)) {
170
+ if (node.nodeType === 3 /* text */) {
171
+ childPieces.push(escapeXmlText(node.nodeValue ?? ""));
172
+ }
173
+ else if (node.nodeType === 1 /* element */) {
174
+ const childElement = node;
175
+ const childTag = childElement.tagName.toLowerCase();
176
+ if (SSML_DROP_FULLY_TAGS.has(childTag)) {
177
+ continue;
178
+ }
179
+ if (SSML_ALLOWED_TAGS.has(childTag)) {
180
+ childPieces.push(serializeSsmlElement(childElement));
181
+ }
182
+ else {
183
+ childPieces.push(escapeXmlText(childElement.textContent ?? ""));
184
+ }
185
+ }
186
+ }
187
+ return `<${tagName}${attrs}>${childPieces.join("")}</${tagName}>`;
188
+ }
189
+ function escapeXmlText(value) {
190
+ return value
191
+ .replace(/&/g, "&amp;")
192
+ .replace(/</g, "&lt;")
193
+ .replace(/>/g, "&gt;");
194
+ }
195
+ function escapeXmlAttr(value) {
196
+ return value
197
+ .replace(/&/g, "&amp;")
198
+ .replace(/</g, "&lt;")
199
+ .replace(/>/g, "&gt;")
200
+ .replace(/"/g, "&quot;");
201
+ }
19
202
  /**
20
203
  * SSMLExtractor Service
21
204
  *
@@ -107,8 +290,12 @@ export class SSMLExtractor {
107
290
  try {
108
291
  // Generate unique catalog ID
109
292
  const catalogId = this.generateCatalogId(idPrefix);
110
- // Extract SSML content
111
- const ssmlContent = speakEl.outerHTML;
293
+ // Sanitize before serializing so the SSML forwarded to the TTS
294
+ // provider only contains spec-allowed tags/attributes. This
295
+ // closes an author-content -> TTS-server pipeline that would
296
+ // otherwise accept arbitrary HTML.
297
+ sanitizeSsmlElement(speakEl);
298
+ const ssmlContent = serializeSsmlElement(speakEl);
112
299
  // Extract language (check both xml:lang and lang attributes)
113
300
  const language = speakEl.getAttribute("xml:lang") ||
114
301
  speakEl.getAttribute("lang") ||
@@ -1 +1 @@
1
- {"version":3,"file":"SSMLExtractor.js","sourceRoot":"","sources":["../../src/services/SSMLExtractor.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"SSMLExtractor.js","sourceRoot":"","sources":["../../src/services/SSMLExtractor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAwBH;;;;;GAKG;AACH,MAAM,iBAAiB,GAAG,IAAI,GAAG,CAAC;IACjC,OAAO;IACP,OAAO;IACP,SAAS;IACT,UAAU;IACV,QAAQ;IACR,KAAK;IACL,MAAM;IACN,SAAS;IACT,MAAM;IACN,GAAG;IACH,GAAG;IACH,OAAO;IACP,OAAO;IACP,SAAS;CACT,CAAC,CAAC;AAEH;;;;GAIG;AACH;;;;GAIG;AACH,MAAM,oBAAoB,GAAG,IAAI,GAAG,CAAC;IACpC,QAAQ;IACR,OAAO;IACP,QAAQ;IACR,QAAQ;IACR,OAAO;IACP,eAAe;IACf,UAAU;IACV,UAAU;CACV,CAAC,CAAC;AAEH,MAAM,kBAAkB,GAAG,IAAI,GAAG,CAAC;IAClC,UAAU;IACV,IAAI;IACJ,MAAM;IACN,UAAU;IACV,MAAM;IACN,OAAO;IACP,QAAQ;IACR,MAAM;IACN,cAAc;IACd,QAAQ;IACR,QAAQ;IACR,UAAU;IACV,UAAU;IACV,MAAM;IACN,QAAQ;IACR,KAAK;CACL,CAAC,CAAC;AAEH;;;;;GAKG;AACH,MAAM,wBAAwB,GAAa;IAC1C,cAAc;IACd,uBAAuB;IACvB,qBAAqB;IACrB,sBAAsB;IACtB,4BAA4B;IAC5B,6CAA6C;IAC7C,4BAA4B;IAC5B,OAAO;IACP,SAAS;IACT,SAAS;IACT,kBAAkB;IAClB,+BAA+B;IAC/B,8BAA8B;IAC9B,0BAA0B;CAC1B,CAAC;AAEF,SAAS,kBAAkB,CAAC,GAAW;IACtC,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAC9D,IAAI,MAAW,CAAC;IAChB,IAAI,CAAC;QACJ,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;IACvB,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,KAAK,CAAC;IACd,CAAC;IACD,IAAI,MAAM,CAAC,QAAQ,KAAK,OAAO,IAAI,MAAM,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;QACjE,OAAO,KAAK,CAAC;IACd,CAAC;IACD,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IAC5E,OAAO,CAAC,wBAAwB,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;AAC5E,CAAC;AAED;;;;GAIG;AACH,SAAS,mBAAmB,CAAC,OAAgB;IAC5C,sEAAsE;IACtE,MAAM,QAAQ,GAAG,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC9C,KAAK,MAAM,KAAK,IAAI,QAAQ,EAAE,CAAC;QAC9B,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC;QAC5C,IAAI,oBAAoB,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;YACvC,KAAK,CAAC,MAAM,EAAE,CAAC;YACf,SAAS;QACV,CAAC;QACD,mBAAmB,CAAC,KAAK,CAAC,CAAC;QAC3B,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;YACrC,MAAM,QAAQ,GAAG,OAAO,CAAC,aAAa,CAAC,cAAc,CACpD,KAAK,CAAC,WAAW,IAAI,EAAE,CACvB,CAAC;YACF,KAAK,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;QAC7B,CAAC;IACF,CAAC;IACD,qDAAqD;IACrD,MAAM,SAAS,GAAG,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC1E,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;QAClC,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC,EAAE,CAAC;YACrD,OAAO,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC;QACnC,CAAC;IACF,CAAC;IACD,qEAAqE;IACrE,oEAAoE;IACpE,oEAAoE;IACpE,8DAA8D;IAC9D,IAAI,OAAO,CAAC,OAAO,CAAC,WAAW,EAAE,KAAK,OAAO,EAAE,CAAC;QAC/C,MAAM,GAAG,GAAG,OAAO,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC;QACxC,IAAI,GAAG,KAAK,IAAI,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,EAAE,CAAC;YAC9C,OAAO,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC;QAChC,CAAC;IACF,CAAC;AACF,CAAC;AAED;;;;;;GAMG;AACH,SAAS,oBAAoB,CAAC,OAAgB;IAC7C,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC;IAC9C,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC;SAC1C,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,kBAAkB,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;SACjE,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,IAAI,CAAC,IAAI,KAAK,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC;SAC7D,IAAI,CAAC,EAAE,CAAC,CAAC;IACX,MAAM,WAAW,GAAa,EAAE,CAAC;IACjC,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;QACnD,IAAI,IAAI,CAAC,QAAQ,KAAK,CAAC,CAAC,UAAU,EAAE,CAAC;YACpC,WAAW,CAAC,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,CAAC;QACvD,CAAC;aAAM,IAAI,IAAI,CAAC,QAAQ,KAAK,CAAC,CAAC,aAAa,EAAE,CAAC;YAC9C,MAAM,YAAY,GAAG,IAAe,CAAC;YACrC,MAAM,QAAQ,GAAG,YAAY,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC;YACpD,IAAI,oBAAoB,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;gBACxC,SAAS;YACV,CAAC;YACD,IAAI,iBAAiB,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;gBACrC,WAAW,CAAC,IAAI,CAAC,oBAAoB,CAAC,YAAY,CAAC,CAAC,CAAC;YACtD,CAAC;iBAAM,CAAC;gBACP,WAAW,CAAC,IAAI,CAAC,aAAa,CAAC,YAAY,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC,CAAC;YACjE,CAAC;QACF,CAAC;IACF,CAAC;IACD,OAAO,IAAI,OAAO,GAAG,KAAK,IAAI,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC,KAAK,OAAO,GAAG,CAAC;AACnE,CAAC;AAED,SAAS,aAAa,CAAC,KAAa;IACnC,OAAO,KAAK;SACV,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;AACzB,CAAC;AAED,SAAS,aAAa,CAAC,KAAa;IACnC,OAAO,KAAK;SACV,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;AAC3B,CAAC;AAED;;;;GAIG;AACH,MAAM,OAAO,aAAa;IACjB,cAAc,GAAG,CAAC,CAAC;IAE3B;;;;;OAKG;IACH,qBAAqB,CAAC,MAAoB;QACzC,MAAM,WAAW,GAA2B,EAAE,CAAC;QAE/C,+CAA+C;QAC/C,MAAM,aAAa,GAAiB;YACnC,GAAG,MAAM;YACT,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE;SAC/C,CAAC;QAEF,8CAA8C;QAC9C,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;YACnB,MAAM,YAAY,GAAG,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;YACrE,WAAW,CAAC,IAAI,CAAC,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC;YAC3C,aAAa,CAAC,MAAM,GAAG,YAAY,CAAC,aAAa,CAAC;QACnD,CAAC;QAED,kDAAkD;QAClD,IAAI,MAAM,CAAC,MAAM,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC/C,aAAa,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;gBAClD,MAAM,YAAY,GAAG,EAAE,GAAG,KAAK,EAAE,CAAC;gBAClC,MAAM,aAAa,GAA2B,EAAE,CAAC;gBAEjD,sBAAsB;gBACtB,IAAI,KAAK,CAAC,MAAM,IAAI,OAAO,KAAK,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;oBACtD,MAAM,YAAY,GAAG,IAAI,CAAC,iBAAiB,CAC1C,KAAK,CAAC,MAAM,EACZ,UAAU,KAAK,CAAC,EAAE,EAAE,CACpB,CAAC;oBACF,aAAa,CAAC,IAAI,CAAC,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC;oBAC7C,YAAY,CAAC,MAAM,GAAG,YAAY,CAAC,aAAa,CAAC;gBAClD,CAAC;gBAED,uBAAuB;gBACvB,IAAI,KAAK,CAAC,OAAO,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;oBACnD,YAAY,CAAC,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE;wBACnD,IAAI,MAAM,CAAC,KAAK,IAAI,OAAO,MAAM,CAAC,KAAK,KAAK,QAAQ,EAAE,CAAC;4BACtD,MAAM,YAAY,GAAG,IAAI,CAAC,iBAAiB,CAC1C,MAAM,CAAC,KAAK,EACZ,UAAU,KAAK,CAAC,EAAE,IAAI,MAAM,CAAC,KAAK,EAAE,CACpC,CAAC;4BACF,aAAa,CAAC,IAAI,CAAC,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC;4BAC7C,OAAO;gCACN,GAAG,MAAM;gCACT,KAAK,EAAE,YAAY,CAAC,aAAa;6BACjC,CAAC;wBACH,CAAC;wBACD,OAAO,MAAM,CAAC;oBACf,CAAC,CAAC,CAAC;gBACJ,CAAC;gBAED,WAAW,CAAC,IAAI,CAAC,GAAG,aAAa,CAAC,CAAC;gBACnC,OAAO,YAAY,CAAC;YACrB,CAAC,CAAC,CAAC;QACJ,CAAC;QAED,OAAO;YACN,QAAQ,EAAE,WAAW;YACrB,aAAa;SACb,CAAC;IACH,CAAC;IAED;;;;;;OAMG;IACK,iBAAiB,CACxB,MAAc,EACd,QAAgB;QAEhB,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;YACrC,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,aAAa,EAAE,MAAM,EAAE,CAAC;QAChD,CAAC;QAED,0CAA0C;QAC1C,IAAI,OAAO,MAAM,KAAK,WAAW,IAAI,OAAO,SAAS,KAAK,WAAW,EAAE,CAAC;YACvE,OAAO,CAAC,IAAI,CACX,qEAAqE,CACrE,CAAC;YACF,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,aAAa,EAAE,MAAM,EAAE,CAAC;QAChD,CAAC;QAED,MAAM,QAAQ,GAA2B,EAAE,CAAC;QAE5C,IAAI,CAAC;YACJ,MAAM,MAAM,GAAG,IAAI,SAAS,EAAE,CAAC;YAC/B,MAAM,GAAG,GAAG,MAAM,CAAC,eAAe,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;YAExD,4BAA4B;YAC5B,MAAM,aAAa,GAAG,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC,CAAC;YAEhE,IAAI,aAAa,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAChC,wCAAwC;gBACxC,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,aAAa,EAAE,MAAM,EAAE,CAAC;YAChD,CAAC;YAED,+BAA+B;YAC/B,KAAK,MAAM,OAAO,IAAI,aAAa,EAAE,CAAC;gBACrC,IAAI,CAAC;oBACJ,6BAA6B;oBAC7B,MAAM,SAAS,GAAG,IAAI,CAAC,iBAAiB,CAAC,QAAQ,CAAC,CAAC;oBAEnD,+DAA+D;oBAC/D,4DAA4D;oBAC5D,6DAA6D;oBAC7D,mCAAmC;oBACnC,mBAAmB,CAAC,OAAO,CAAC,CAAC;oBAC7B,MAAM,WAAW,GAAG,oBAAoB,CAAC,OAAO,CAAC,CAAC;oBAElD,6DAA6D;oBAC7D,MAAM,QAAQ,GACb,OAAO,CAAC,YAAY,CAAC,UAAU,CAAC;wBAChC,OAAO,CAAC,YAAY,CAAC,MAAM,CAAC;wBAC5B,OAAO,CAAC;oBAET,oCAAoC;oBACpC,MAAM,SAAS,GAAG,OAAO,CAAC,WAAW,IAAI,EAAE,CAAC;oBAE5C,wCAAwC;oBACxC,IAAI,OAAO,GAAG,OAAO,CAAC,aAAa,CAAC;oBAEpC,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,OAAO,KAAK,MAAM,EAAE,CAAC;wBAC5C,+CAA+C;wBAC/C,MAAM,IAAI,GAAG,GAAG,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;wBACvC,OAAO,CAAC,UAAU,EAAE,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;wBAChD,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,cAAc,CAAC,SAAS,CAAC,CAAC,CAAC;wBAChD,OAAO,GAAG,IAAI,CAAC;wBACf,OAAO,CAAC,MAAM,EAAE,CAAC;oBAClB,CAAC;yBAAM,CAAC;wBACP,oEAAoE;wBACpE,OAAO,CAAC,MAAM,EAAE,CAAC;oBAClB,CAAC;oBAED,oCAAoC;oBACpC,IAAI,OAAO,EAAE,CAAC;wBACb,OAAO,CAAC,YAAY,CAAC,iBAAiB,EAAE,SAAS,CAAC,CAAC;oBACpD,CAAC;oBAED,uBAAuB;oBACvB,QAAQ,CAAC,IAAI,CAAC;wBACb,UAAU,EAAE,SAAS;wBACrB,KAAK,EAAE;4BACN;gCACC,OAAO,EAAE,QAAQ;gCACjB,QAAQ;gCACR,OAAO,EAAE,WAAW;6BACpB;yBACD;qBACD,CAAC,CAAC;gBACJ,CAAC;gBAAC,OAAO,KAAK,EAAE,CAAC;oBAChB,OAAO,CAAC,KAAK,CACZ,mDAAmD,EACnD,KAAK,CACL,CAAC;oBACF,+BAA+B;gBAChC,CAAC;YACF,CAAC;YAED,wBAAwB;YACxB,MAAM,aAAa,GAAG,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC;YAEzC,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,CAAC;QACpC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,OAAO,CAAC,KAAK,CAAC,uCAAuC,EAAE,KAAK,CAAC,CAAC;YAC9D,0CAA0C;YAC1C,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,aAAa,EAAE,MAAM,EAAE,CAAC;QAChD,CAAC;IACF,CAAC;IAED;;;;;OAKG;IACK,iBAAiB,CAAC,MAAc;QACvC,MAAM,EAAE,GAAG,QAAQ,MAAM,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;QACnD,IAAI,CAAC,cAAc,EAAE,CAAC;QACtB,OAAO,EAAE,CAAC;IACX,CAAC;IAED;;OAEG;IACH,KAAK;QACJ,IAAI,CAAC,cAAc,GAAG,CAAC,CAAC;IACzB,CAAC;CACD"}
@@ -58,6 +58,7 @@ export declare class TTSService {
58
58
  private speakRunId;
59
59
  private currentBoundaryOffset;
60
60
  private seekSegments;
61
+ private sentenceHighlightSegments;
61
62
  private currentSeekSegmentIndex;
62
63
  private activeSentenceStartOffset;
63
64
  private activeHighlightMode;
@@ -117,10 +118,12 @@ export declare class TTSService {
117
118
  private createSpeechPlanSegments;
118
119
  private segmentSentences;
119
120
  private createSeekSegmentsFromText;
121
+ private splitSegmentsAtBoundaries;
122
+ private createSentenceHighlightSegments;
120
123
  private getCurrentSeekSegmentIndex;
121
124
  private speakWithPlan;
122
125
  private highlightSentenceSegment;
123
- private getSeekSegmentIndexForOffset;
126
+ private getSegmentIndexForOffset;
124
127
  private highlightSentenceForOffset;
125
128
  /**
126
129
  * Find text node and offsets for highlighting a word
@@ -139,6 +142,7 @@ export declare class TTSService {
139
142
  speak(text: string, options?: SpeakOptions): Promise<void>;
140
143
  private shouldAttemptBrowserFallback;
141
144
  private switchProviderToBrowser;
145
+ private isBrowserSpeechFallbackAvailable;
142
146
  private retryWithBrowserFallback;
143
147
  private applyLanguageSettings;
144
148
  private resolveSpeechContent;
@@ -174,6 +178,13 @@ export declare class TTSService {
174
178
  * Stop playback
175
179
  */
176
180
  stop(): void;
181
+ /**
182
+ * Request UI-level TTS controls to hand off/deactivate.
183
+ *
184
+ * This is intentionally separate from playback controls so hosts can orchestrate
185
+ * control handoff explicitly (for example: stop playback, then dismiss controls).
186
+ */
187
+ requestControlHandoff(): void;
177
188
  /**
178
189
  * Check if currently playing
179
190
  */
@@ -1 +1 @@
1
- {"version":3,"file":"TTSService.d.ts","sourceRoot":"","sources":["../../src/services/TTSService.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"TTSService.d.ts","sourceRoot":"","sources":["../../src/services/TTSService.ts"],"names":[],"mappings":"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"}
@@ -18,6 +18,7 @@
18
18
  import { BrowserTTSProvider } from "./tts/browser-provider.js";
19
19
  import { collectVisibleTextAndMap, extractVisibleText as extractVisibleTextFromDOM, isElementHiddenForTTS, normalizeTextForSpeech, } from "./tts/text-processing.js";
20
20
  import { segmentSentences as segmentTextToSentences, } from "./tts/text-segmentation.js";
21
+ import { PIE_TTS_CONTROL_HANDOFF_EVENT, } from "./tts-control-events.js";
21
22
  /**
22
23
  * Playback state
23
24
  */
@@ -50,6 +51,7 @@ export class TTSService {
50
51
  speakRunId = 0;
51
52
  currentBoundaryOffset = 0;
52
53
  seekSegments = [];
54
+ sentenceHighlightSegments = [];
53
55
  currentSeekSegmentIndex = 0;
54
56
  activeSentenceStartOffset = null;
55
57
  activeHighlightMode = "word";
@@ -403,6 +405,52 @@ export class TTSService {
403
405
  })
404
406
  .filter((segment) => segment.text.length > 0);
405
407
  }
408
+ splitSegmentsAtBoundaries(segments, boundaryOffsets, sourceText) {
409
+ if (segments.length === 0 || boundaryOffsets.length === 0)
410
+ return segments;
411
+ const normalizedBoundaries = Array.from(new Set(boundaryOffsets))
412
+ .filter((offset) => Number.isFinite(offset) && offset > 0)
413
+ .sort((left, right) => left - right);
414
+ if (normalizedBoundaries.length === 0)
415
+ return segments;
416
+ const result = [];
417
+ for (const segment of segments) {
418
+ const segmentStart = segment.startOffset;
419
+ const segmentEnd = segment.startOffset + segment.text.length;
420
+ const splitPoints = normalizedBoundaries.filter((offset) => offset > segmentStart && offset < segmentEnd);
421
+ if (splitPoints.length === 0) {
422
+ result.push(segment);
423
+ continue;
424
+ }
425
+ let cursor = segmentStart;
426
+ for (const point of [...splitPoints, segmentEnd]) {
427
+ const raw = sourceText.substring(cursor, point);
428
+ const leadingWhitespace = raw.match(/^\s*/)?.[0].length || 0;
429
+ const trailingWhitespace = raw.match(/\s*$/)?.[0].length || 0;
430
+ const startOffset = cursor + leadingWhitespace;
431
+ const endOffset = point - trailingWhitespace;
432
+ if (endOffset > startOffset) {
433
+ result.push({
434
+ text: sourceText.substring(startOffset, endOffset),
435
+ startOffset,
436
+ pauseMsAfter: 0,
437
+ });
438
+ }
439
+ cursor = point;
440
+ }
441
+ }
442
+ return result;
443
+ }
444
+ createSentenceHighlightSegments(args) {
445
+ const grammaticalSegments = this.createSeekSegmentsFromText(args.contentToSpeak);
446
+ if (!args.shouldUsePlan || args.playbackSegments.length === 0) {
447
+ return grammaticalSegments;
448
+ }
449
+ const structuralBoundaries = args.playbackSegments
450
+ .map((segment) => segment.startOffset)
451
+ .filter((offset) => offset > 0);
452
+ return this.splitSegmentsAtBoundaries(grammaticalSegments, structuralBoundaries, args.contentToSpeak);
453
+ }
406
454
  getCurrentSeekSegmentIndex() {
407
455
  if (this.seekSegments.length === 0)
408
456
  return 0;
@@ -475,12 +523,12 @@ export class TTSService {
475
523
  this.highlightCoordinator.highlightTTSSentence([range]);
476
524
  this.activeSentenceStartOffset = startOffset;
477
525
  }
478
- getSeekSegmentIndexForOffset(offset) {
479
- if (this.seekSegments.length === 0)
526
+ getSegmentIndexForOffset(segments, offset) {
527
+ if (segments.length === 0)
480
528
  return -1;
481
529
  let index = -1;
482
- for (let i = 0; i < this.seekSegments.length; i++) {
483
- if (this.seekSegments[i].startOffset <= offset) {
530
+ for (let i = 0; i < segments.length; i++) {
531
+ if (segments[i].startOffset <= offset) {
484
532
  index = i;
485
533
  }
486
534
  else {
@@ -490,12 +538,13 @@ export class TTSService {
490
538
  return index;
491
539
  }
492
540
  highlightSentenceForOffset(offset) {
493
- if (!this.highlightCoordinator || this.seekSegments.length === 0)
541
+ const segments = this.sentenceHighlightSegments;
542
+ if (!this.highlightCoordinator || segments.length === 0)
494
543
  return;
495
- const segmentIndex = this.getSeekSegmentIndexForOffset(offset);
544
+ const segmentIndex = this.getSegmentIndexForOffset(segments, offset);
496
545
  if (segmentIndex < 0)
497
546
  return;
498
- const segment = this.seekSegments[segmentIndex];
547
+ const segment = segments[segmentIndex];
499
548
  if (!segment)
500
549
  return;
501
550
  if (this.activeSentenceStartOffset === segment.startOffset)
@@ -580,6 +629,13 @@ export class TTSService {
580
629
  : shouldUsePlan && this.currentContentElement
581
630
  ? this.createSpeechPlan(this.currentContentElement, normalizedText)
582
631
  : this.createSeekSegmentsFromText(contentToSpeak);
632
+ this.sentenceHighlightSegments = this.hasExplicitBreakSemantics(contentToSpeak)
633
+ ? []
634
+ : this.createSentenceHighlightSegments({
635
+ contentToSpeak,
636
+ shouldUsePlan,
637
+ playbackSegments: this.seekSegments,
638
+ });
583
639
  this.setState(PlaybackState.LOADING);
584
640
  this.prepareHighlightsForSpeak({
585
641
  contentToSpeak,
@@ -645,23 +701,19 @@ export class TTSService {
645
701
  const message = (error instanceof Error ? error.message : String(error))
646
702
  .toLowerCase()
647
703
  .trim();
648
- if (!message)
704
+ // Avoid fallback for expected cancellation/superseded flows.
705
+ if (message.includes("superseded by a newer request"))
649
706
  return false;
650
- if (message.includes("text-to-speech service encountered an error"))
651
- return true;
652
- if (message.includes("service unavailable"))
653
- return true;
654
- if (message.includes("failed to fetch"))
655
- return true;
656
- if (message.includes("network error"))
657
- return true;
658
- if (message.includes("failed to download synthesized audio"))
659
- return true;
660
- if (/\b5\d{2}\b/.test(message))
661
- return true;
662
- if (/server returned 5\d{2}/.test(message))
663
- return true;
664
- return false;
707
+ if (message.includes("aborterror"))
708
+ return false;
709
+ if (message.includes("aborted"))
710
+ return false;
711
+ if (message.includes("cancelled"))
712
+ return false;
713
+ if (message.includes("canceled"))
714
+ return false;
715
+ // For non-browser providers, default to browser fallback on runtime errors.
716
+ return true;
665
717
  }
666
718
  async switchProviderToBrowser(reason) {
667
719
  if (!this.currentProvider)
@@ -703,9 +755,18 @@ export class TTSService {
703
755
  return false;
704
756
  }
705
757
  }
758
+ isBrowserSpeechFallbackAvailable() {
759
+ if (typeof window === "undefined")
760
+ return false;
761
+ if (!("speechSynthesis" in window))
762
+ return false;
763
+ return typeof globalThis.SpeechSynthesisUtterance === "function";
764
+ }
706
765
  async retryWithBrowserFallback(args) {
707
766
  if (!this.shouldAttemptBrowserFallback(args.error))
708
767
  return false;
768
+ if (!this.isBrowserSpeechFallbackAvailable())
769
+ return false;
709
770
  const switched = await this.switchProviderToBrowser(args.error);
710
771
  if (!switched)
711
772
  return false;
@@ -824,7 +885,7 @@ export class TTSService {
824
885
  if (args.highlightMode === "sentence" && args.shouldUsePlan) {
825
886
  return;
826
887
  }
827
- const initialSegment = this.seekSegments[0];
888
+ const initialSegment = this.sentenceHighlightSegments[0] || this.seekSegments[0];
828
889
  if (initialSegment) {
829
890
  this.highlightSentenceSegment(initialSegment.startOffset, initialSegment.text);
830
891
  console.log("[TTSService] Applied initial sentence highlighting");
@@ -889,6 +950,7 @@ export class TTSService {
889
950
  this.normalizedToDOM.clear();
890
951
  this.currentBoundaryOffset = 0;
891
952
  this.seekSegments = [];
953
+ this.sentenceHighlightSegments = [];
892
954
  this.currentSeekSegmentIndex = 0;
893
955
  this.activeSentenceStartOffset = null;
894
956
  }
@@ -1028,9 +1090,24 @@ export class TTSService {
1028
1090
  this.normalizedToDOM.clear();
1029
1091
  this.currentBoundaryOffset = 0;
1030
1092
  this.seekSegments = [];
1093
+ this.sentenceHighlightSegments = [];
1031
1094
  this.currentSeekSegmentIndex = 0;
1032
1095
  this.activeSentenceStartOffset = null;
1033
1096
  }
1097
+ /**
1098
+ * Request UI-level TTS controls to hand off/deactivate.
1099
+ *
1100
+ * This is intentionally separate from playback controls so hosts can orchestrate
1101
+ * control handoff explicitly (for example: stop playback, then dismiss controls).
1102
+ */
1103
+ requestControlHandoff() {
1104
+ if (typeof window === "undefined")
1105
+ return;
1106
+ const detail = { source: "host" };
1107
+ window.dispatchEvent(new CustomEvent(PIE_TTS_CONTROL_HANDOFF_EVENT, {
1108
+ detail,
1109
+ }));
1110
+ }
1034
1111
  /**
1035
1112
  * Check if currently playing
1036
1113
  */