@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.
- package/README.md +24 -0
- package/dist/components/ItemToolBar.custom-element.js +197 -84
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/services/SSMLExtractor.d.ts.map +1 -1
- package/dist/services/SSMLExtractor.js +189 -2
- package/dist/services/SSMLExtractor.js.map +1 -1
- package/dist/services/TTSService.d.ts +12 -1
- package/dist/services/TTSService.d.ts.map +1 -1
- package/dist/services/TTSService.js +101 -24
- package/dist/services/TTSService.js.map +1 -1
- package/dist/services/ToolRegistry.d.ts.map +1 -1
- package/dist/services/ToolRegistry.js +30 -0
- package/dist/services/ToolRegistry.js.map +1 -1
- package/dist/services/ToolkitCoordinator.d.ts.map +1 -1
- package/dist/services/ToolkitCoordinator.js +8 -2
- package/dist/services/ToolkitCoordinator.js.map +1 -1
- package/dist/services/interfaces.d.ts +7 -0
- package/dist/services/interfaces.d.ts.map +1 -1
- package/dist/services/interfaces.js.map +1 -1
- package/dist/services/tool-config-validation.js +1 -1
- package/dist/services/tool-config-validation.js.map +1 -1
- package/dist/services/tools-config-normalizer.d.ts +1 -1
- package/dist/services/tools-config-normalizer.d.ts.map +1 -1
- package/dist/services/tts-control-events.d.ts +14 -0
- package/dist/services/tts-control-events.d.ts.map +1 -0
- package/dist/services/tts-control-events.js +8 -0
- package/dist/services/tts-control-events.js.map +1 -0
- package/dist/tools/calculators/desmos-provider.js +18 -0
- package/dist/tools/calculators/desmos-provider.js.map +1 -1
- package/dist/tools/default-tool-module-loaders.d.ts.map +1 -1
- package/dist/tools/default-tool-module-loaders.js +8 -2
- package/dist/tools/default-tool-module-loaders.js.map +1 -1
- 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, "&")
|
|
192
|
+
.replace(/</g, "<")
|
|
193
|
+
.replace(/>/g, ">");
|
|
194
|
+
}
|
|
195
|
+
function escapeXmlAttr(value) {
|
|
196
|
+
return value
|
|
197
|
+
.replace(/&/g, "&")
|
|
198
|
+
.replace(/</g, "<")
|
|
199
|
+
.replace(/>/g, ">")
|
|
200
|
+
.replace(/"/g, """);
|
|
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
|
-
//
|
|
111
|
-
|
|
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":"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"}
|
|
@@ -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
|
|
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":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EACX,YAAY,EAEZ,SAAS,EACT,uBAAuB,EACvB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,mCAAmC,CAAC;AACtF,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;
|
|
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
|
-
|
|
479
|
-
if (
|
|
526
|
+
getSegmentIndexForOffset(segments, offset) {
|
|
527
|
+
if (segments.length === 0)
|
|
480
528
|
return -1;
|
|
481
529
|
let index = -1;
|
|
482
|
-
for (let i = 0; i <
|
|
483
|
-
if (
|
|
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
|
-
|
|
541
|
+
const segments = this.sentenceHighlightSegments;
|
|
542
|
+
if (!this.highlightCoordinator || segments.length === 0)
|
|
494
543
|
return;
|
|
495
|
-
const segmentIndex = this.
|
|
544
|
+
const segmentIndex = this.getSegmentIndexForOffset(segments, offset);
|
|
496
545
|
if (segmentIndex < 0)
|
|
497
546
|
return;
|
|
498
|
-
const segment =
|
|
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
|
-
|
|
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("
|
|
651
|
-
return
|
|
652
|
-
if (message.includes("
|
|
653
|
-
return
|
|
654
|
-
if (message.includes("
|
|
655
|
-
return
|
|
656
|
-
if (message.includes("
|
|
657
|
-
return
|
|
658
|
-
|
|
659
|
-
|
|
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
|
*/
|