@pie-players/pie-assessment-toolkit 0.3.75 → 1.0.0-beta.0
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 +449 -541
- package/custom-elements.json +1315 -0
- package/dist/attempt/TestSession.d.ts +0 -1
- package/dist/attempt/TestSession.js +10 -5
- package/dist/components/ItemScope.custom-element.js +1 -1
- package/dist/components/ItemToolBar.custom-element.js +1 -1
- package/dist/components/PieAssessmentToolkit.custom-element.js +8 -8
- package/dist/components/SectionToolBar.custom-element.js +1 -1
- package/dist/components/chunks/{ItemToolBar-7tt7v2xp.js → ItemToolBar-3zrc4p3d.js} +4 -4
- package/dist/components/chunks/{ItemToolBar-jx38w99s.js → ItemToolBar-66hcwmfw.js} +2 -2
- package/dist/components/chunks/ItemToolBar-9gqhfxtn.js +40 -0
- package/dist/components/chunks/ItemToolBar-hzc5wyjp.js +2 -0
- package/dist/components/chunks/{ItemToolBar-97hx60z4.js → ItemToolBar-kkj69ptr.js} +1 -1
- package/dist/components/chunks/sre-engine-bj997w4b.js +1 -0
- package/dist/context/assessment-toolkit-context.d.ts +6 -0
- package/dist/index.d.ts +7 -7
- package/dist/index.js +4 -10
- package/dist/policy/core/PolicySource.d.ts +1 -1
- package/dist/policy/core/ToolPolicyEngine.d.ts +46 -7
- package/dist/policy/core/ToolPolicyEngine.js +73 -16
- package/dist/policy/core/compose-decision.d.ts +3 -3
- package/dist/policy/core/compose-decision.js +30 -30
- package/dist/policy/core/decision-types.d.ts +44 -32
- package/dist/policy/core/feature-decision.d.ts +17 -15
- package/dist/policy/core/feature-decision.js +11 -13
- package/dist/policy/core/pnp-policy-inputs.d.ts +5 -2
- package/dist/policy/core/pnp-policy-inputs.js +12 -32
- package/dist/policy/core/provenance.d.ts +1 -1
- package/dist/policy/core/tool-parameters.d.ts +11 -0
- package/dist/policy/core/tool-parameters.js +15 -0
- package/dist/policy/engine.d.ts +1 -1
- package/dist/policy/sources/PnpPolicySource.d.ts +14 -22
- package/dist/policy/sources/PnpPolicySource.js +30 -28
- package/dist/runtime/SectionControllerBinding.d.ts +6 -2
- package/dist/runtime/SectionControllerBinding.js +21 -15
- package/dist/runtime/adapter/subscriber-fanout.d.ts +0 -1
- package/dist/runtime/adapter/subscriber-fanout.js +1 -4
- package/dist/runtime/composition-emit-scheduler.d.ts +3 -3
- package/dist/runtime/composition-emit-scheduler.js +1 -1
- package/dist/runtime/content-region.d.ts +0 -4
- package/dist/runtime/content-region.js +2 -2
- package/dist/runtime/core/SectionEngineCore.d.ts +0 -2
- package/dist/runtime/core/SectionEngineCore.js +0 -4
- package/dist/runtime/core/cohort.d.ts +0 -5
- package/dist/runtime/core/cohort.js +0 -9
- package/dist/runtime/core/engine-output.d.ts +3 -2
- package/dist/runtime/core/engine-transition.js +3 -2
- package/dist/runtime/registration-events.d.ts +11 -14
- package/dist/runtime/runtime-id.d.ts +1 -1
- package/dist/runtime/runtime-id.js +1 -1
- package/dist/runtime/shell-scope.d.ts +4 -4
- package/dist/runtime/shell-scope.js +15 -9
- package/dist/runtime/tool-host-contract.d.ts +0 -4
- package/dist/runtime/tool-host-contract.js +1 -1
- package/dist/services/AccessibilityCatalogResolver.d.ts +21 -31
- package/dist/services/AccessibilityCatalogResolver.js +29 -42
- package/dist/services/ElementToolStateStore.d.ts +32 -61
- package/dist/services/ElementToolStateStore.js +37 -67
- package/dist/services/HighlightCoordinator.d.ts +9 -21
- package/dist/services/HighlightCoordinator.js +52 -80
- package/dist/services/RangeSerializer.d.ts +4 -35
- package/dist/services/RangeSerializer.js +4 -48
- package/dist/services/SSMLExtractor.d.ts +8 -31
- package/dist/services/SSMLExtractor.js +18 -40
- package/dist/services/TTSService.d.ts +49 -56
- package/dist/services/TTSService.js +308 -283
- package/dist/services/ToolCoordinator.d.ts +6 -49
- package/dist/services/ToolCoordinator.js +25 -61
- package/dist/services/ToolRegistry.d.ts +82 -80
- package/dist/services/ToolRegistry.js +91 -67
- package/dist/services/ToolkitCoordinator.d.ts +83 -81
- package/dist/services/ToolkitCoordinator.js +186 -174
- package/dist/services/framework-error.d.ts +30 -7
- package/dist/services/framework-error.js +10 -13
- package/dist/services/interfaces.d.ts +47 -71
- package/dist/services/section-controller-types.d.ts +7 -7
- package/dist/services/section-session-assignment.js +6 -8
- package/dist/services/spoken-audio-cards.d.ts +2 -4
- package/dist/services/spoken-audio-cards.js +2 -4
- package/dist/services/tool-config-validation.d.ts +5 -7
- package/dist/services/tool-config-validation.js +11 -26
- package/dist/services/tool-context.d.ts +7 -23
- package/dist/services/tool-context.js +99 -53
- package/dist/services/tool-failure.d.ts +30 -0
- package/dist/services/tool-failure.js +11 -0
- package/dist/services/tool-icons.d.ts +1 -2
- package/dist/services/tool-icons.js +2 -2
- package/dist/services/tool-instance-id.d.ts +8 -6
- package/dist/services/tool-instance-id.js +12 -18
- package/dist/services/tool-providers/TTSToolProvider.d.ts +6 -43
- package/dist/services/tool-providers/TTSToolProvider.js +7 -63
- package/dist/services/tool-providers/ToolProviderApi.d.ts +0 -7
- package/dist/services/tool-providers/ToolProviderRegistry.d.ts +7 -42
- package/dist/services/tool-providers/ToolProviderRegistry.js +21 -63
- package/dist/services/tool-request.d.ts +4 -0
- package/dist/services/tool-request.js +7 -2
- package/dist/services/toolbar-decided-tools.d.ts +1 -0
- package/dist/services/toolbar-decided-tools.js +1 -0
- package/dist/services/toolbar-items.js +2 -5
- package/dist/services/tools-config-normalizer.d.ts +18 -9
- package/dist/services/tools-config-normalizer.js +25 -19
- package/dist/services/tts/browser-provider.d.ts +2 -5
- package/dist/services/tts/browser-provider.js +4 -20
- package/dist/services/tts/catalog-span-alignment.js +14 -7
- package/dist/services/tts/flat-tree.d.ts +0 -7
- package/dist/services/tts/flat-tree.js +2 -2
- package/dist/services/tts/generated-speech/dom/to-chunk-inputs.d.ts +2 -2
- package/dist/services/tts/generated-speech/index.d.ts +1 -1
- package/dist/services/tts/generated-speech/index.js +1 -1
- package/dist/services/tts/generated-speech/types.d.ts +1 -1
- package/dist/services/tts/generated-speech/types.js +1 -1
- package/dist/services/tts/highlight-pipeline/create-highlight-plan.js +10 -12
- package/dist/services/tts/highlight-target-resolver.d.ts +12 -0
- package/dist/services/tts/math-aware-text-processing.js +10 -29
- package/dist/services/tts/math-speech.d.ts +1 -1
- package/dist/services/tts/math-speech.js +3 -0
- package/dist/services/tts/mathml-sanitization.d.ts +7 -0
- package/dist/services/tts/mathml-sanitization.js +9 -1
- package/dist/services/tts/provider-options.d.ts +12 -1
- package/dist/services/tts/ssml/spoken-text.js +1 -1
- package/dist/services/tts/start-failure.d.ts +0 -7
- package/dist/services/tts/start-failure.js +1 -1
- package/dist/services/tts/text-segmentation.d.ts +5 -0
- package/dist/services/tts/text-segmentation.js +13 -10
- package/dist/services/tts-runtime-config.d.ts +32 -12
- package/dist/services/tts-runtime-config.js +14 -20
- package/dist/tools/content-capability-resolution.d.ts +2 -2
- package/dist/tools/registration.d.ts +6 -5
- package/dist/tools/registration.js +10 -9
- package/dist/tools/registrations/toolbar-registration-helpers.d.ts +6 -0
- package/dist/tools/registrations/toolbar-registration-helpers.js +6 -0
- package/dist/tools/tool-surface-host.js +1 -8
- package/package.json +8 -6
- package/dist/components/chunks/ItemToolBar-bxwyhr5p.js +0 -2
- package/dist/components/chunks/ItemToolBar-rd2bb4p6.js +0 -40
- package/dist/components/chunks/sre-engine-jd8cc08x.js +0 -1
- package/dist/services/ContextVariableStore.d.ts +0 -113
- package/dist/services/ContextVariableStore.js +0 -206
- package/dist/services/tts-control-events.d.ts +0 -13
- package/dist/services/tts-control-events.js +0 -7
- package/dist/utils/logger.d.ts +0 -6
- package/dist/utils/logger.js +0 -10
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
* Uses CSS selector paths and node indices for robust serialization that
|
|
8
8
|
* survives content changes when possible. A path into an open shadow root names
|
|
9
9
|
* its host, then ` >>> `, then the path inside the root, so ranges in content
|
|
10
|
-
* that renders into shadow roots round-trip;
|
|
10
|
+
* that renders into shadow roots round-trip; a path with no ` >>> ` is a plain
|
|
11
|
+
* selector path.
|
|
11
12
|
*/
|
|
12
13
|
/**
|
|
13
14
|
* Serialized form of a Range suitable for storage.
|
|
@@ -79,19 +80,9 @@ export declare class RangeSerializer {
|
|
|
79
80
|
* Uses a hybrid approach:
|
|
80
81
|
* - For element nodes: CSS selector path
|
|
81
82
|
* - For text nodes: parent selector + text node index
|
|
82
|
-
*
|
|
83
|
-
* @param node - Node to get path for
|
|
84
|
-
* @param root - Root element
|
|
85
|
-
* @returns Path string
|
|
86
83
|
*/
|
|
87
84
|
private getNodePath;
|
|
88
|
-
/**
|
|
89
|
-
* Get CSS selector path to an element.
|
|
90
|
-
*
|
|
91
|
-
* @param element - Element to get path for
|
|
92
|
-
* @param root - Root element
|
|
93
|
-
* @returns CSS selector path
|
|
94
|
-
*/
|
|
85
|
+
/** Get CSS selector path to an element. */
|
|
95
86
|
private getElementPath;
|
|
96
87
|
/** The path to `shadowRoot` itself: its host's path and an empty step into it. */
|
|
97
88
|
private getShadowRootPath;
|
|
@@ -100,28 +91,6 @@ export declare class RangeSerializer {
|
|
|
100
91
|
* {@link SHADOW_STEP} continues in the shadow root of what the step before named.
|
|
101
92
|
*/
|
|
102
93
|
private resolveElementPath;
|
|
103
|
-
/**
|
|
104
|
-
* Find a node by its path from root.
|
|
105
|
-
*
|
|
106
|
-
* @param path - Path string from getNodePath
|
|
107
|
-
* @param root - Root element
|
|
108
|
-
* @returns Node, or null if not found
|
|
109
|
-
*/
|
|
94
|
+
/** The node a getNodePath path names, or null if not found. */
|
|
110
95
|
private findNodeByPath;
|
|
111
|
-
/**
|
|
112
|
-
* Batch serialize multiple ranges.
|
|
113
|
-
*
|
|
114
|
-
* @param ranges - Ranges to serialize
|
|
115
|
-
* @param root - Root element
|
|
116
|
-
* @returns Array of serialized ranges
|
|
117
|
-
*/
|
|
118
|
-
serializeMany(ranges: Range[], root: Element): SerializedRange[];
|
|
119
|
-
/**
|
|
120
|
-
* Batch deserialize multiple ranges.
|
|
121
|
-
*
|
|
122
|
-
* @param data - Array of serialized ranges
|
|
123
|
-
* @param root - Root element
|
|
124
|
-
* @returns Array of ranges (nulls for invalid ranges)
|
|
125
|
-
*/
|
|
126
|
-
deserializeMany(data: SerializedRange[], root: Element): (Range | null)[];
|
|
127
96
|
}
|
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
* Uses CSS selector paths and node indices for robust serialization that
|
|
8
8
|
* survives content changes when possible. A path into an open shadow root names
|
|
9
9
|
* its host, then ` >>> `, then the path inside the root, so ranges in content
|
|
10
|
-
* that renders into shadow roots round-trip;
|
|
10
|
+
* that renders into shadow roots round-trip; a path with no ` >>> ` is a plain
|
|
11
|
+
* selector path.
|
|
11
12
|
*/
|
|
12
13
|
import { isShadowRootNode } from "./tts/flat-tree.js";
|
|
13
14
|
/** Separates a shadow host's path from the path inside its shadow root. */
|
|
@@ -69,11 +70,9 @@ export class RangeSerializer {
|
|
|
69
70
|
const range = new Range();
|
|
70
71
|
range.setStart(startNode, data.startOffset);
|
|
71
72
|
range.setEnd(endNode, data.endOffset);
|
|
72
|
-
// Validate text hasn't changed
|
|
73
73
|
if (range.toString() === data.text) {
|
|
74
74
|
return range;
|
|
75
75
|
}
|
|
76
|
-
// Text changed, range is invalid
|
|
77
76
|
return null;
|
|
78
77
|
}
|
|
79
78
|
catch (error) {
|
|
@@ -88,22 +87,16 @@ export class RangeSerializer {
|
|
|
88
87
|
* Uses a hybrid approach:
|
|
89
88
|
* - For element nodes: CSS selector path
|
|
90
89
|
* - For text nodes: parent selector + text node index
|
|
91
|
-
*
|
|
92
|
-
* @param node - Node to get path for
|
|
93
|
-
* @param root - Root element
|
|
94
|
-
* @returns Path string
|
|
95
90
|
*/
|
|
96
91
|
getNodePath(node, root) {
|
|
97
92
|
if (node === root) {
|
|
98
93
|
return "";
|
|
99
94
|
}
|
|
100
|
-
// Handle text nodes
|
|
101
95
|
if (node.nodeType === Node.TEXT_NODE) {
|
|
102
96
|
const parent = node.parentNode;
|
|
103
97
|
if (!parent) {
|
|
104
98
|
throw new Error("Text node has no parent");
|
|
105
99
|
}
|
|
106
|
-
// Get index of this text node among its siblings
|
|
107
100
|
const textNodes = Array.from(parent.childNodes).filter((n) => n.nodeType === Node.TEXT_NODE);
|
|
108
101
|
const index = textNodes.indexOf(node);
|
|
109
102
|
const parentPath = isShadowRootNode(parent)
|
|
@@ -111,7 +104,6 @@ export class RangeSerializer {
|
|
|
111
104
|
: this.getElementPath(parent, root);
|
|
112
105
|
return `${parentPath}::text[${index}]`;
|
|
113
106
|
}
|
|
114
|
-
// Handle element nodes
|
|
115
107
|
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
116
108
|
return this.getElementPath(node, root);
|
|
117
109
|
}
|
|
@@ -120,13 +112,7 @@ export class RangeSerializer {
|
|
|
120
112
|
}
|
|
121
113
|
throw new Error(`Unsupported node type: ${node.nodeType}`);
|
|
122
114
|
}
|
|
123
|
-
/**
|
|
124
|
-
* Get CSS selector path to an element.
|
|
125
|
-
*
|
|
126
|
-
* @param element - Element to get path for
|
|
127
|
-
* @param root - Root element
|
|
128
|
-
* @returns CSS selector path
|
|
129
|
-
*/
|
|
115
|
+
/** Get CSS selector path to an element. */
|
|
130
116
|
getElementPath(element, root) {
|
|
131
117
|
if (element === root) {
|
|
132
118
|
return "";
|
|
@@ -207,24 +193,16 @@ export class RangeSerializer {
|
|
|
207
193
|
}
|
|
208
194
|
return scope;
|
|
209
195
|
}
|
|
210
|
-
/**
|
|
211
|
-
* Find a node by its path from root.
|
|
212
|
-
*
|
|
213
|
-
* @param path - Path string from getNodePath
|
|
214
|
-
* @param root - Root element
|
|
215
|
-
* @returns Node, or null if not found
|
|
216
|
-
*/
|
|
196
|
+
/** The node a getNodePath path names, or null if not found. */
|
|
217
197
|
findNodeByPath(path, root) {
|
|
218
198
|
if (path === "") {
|
|
219
199
|
return root;
|
|
220
200
|
}
|
|
221
|
-
// Handle text node paths
|
|
222
201
|
if (path.includes("::text[")) {
|
|
223
202
|
const marker = path.lastIndexOf("::text[");
|
|
224
203
|
const elementPath = path.slice(0, marker);
|
|
225
204
|
const textPart = path.slice(marker + "::text[".length);
|
|
226
205
|
const textIndex = Number.parseInt(textPart.replace("]", ""), 10);
|
|
227
|
-
// Find parent element
|
|
228
206
|
let parent;
|
|
229
207
|
try {
|
|
230
208
|
parent = this.resolveElementPath(elementPath, root);
|
|
@@ -235,11 +213,9 @@ export class RangeSerializer {
|
|
|
235
213
|
}
|
|
236
214
|
if (!parent)
|
|
237
215
|
return null;
|
|
238
|
-
// Find text node by index
|
|
239
216
|
const textNodes = Array.from(parent.childNodes).filter((n) => n.nodeType === Node.TEXT_NODE);
|
|
240
217
|
return textNodes[textIndex] || null;
|
|
241
218
|
}
|
|
242
|
-
// Handle element paths
|
|
243
219
|
try {
|
|
244
220
|
return this.resolveElementPath(path, root);
|
|
245
221
|
}
|
|
@@ -248,24 +224,4 @@ export class RangeSerializer {
|
|
|
248
224
|
return null;
|
|
249
225
|
}
|
|
250
226
|
}
|
|
251
|
-
/**
|
|
252
|
-
* Batch serialize multiple ranges.
|
|
253
|
-
*
|
|
254
|
-
* @param ranges - Ranges to serialize
|
|
255
|
-
* @param root - Root element
|
|
256
|
-
* @returns Array of serialized ranges
|
|
257
|
-
*/
|
|
258
|
-
serializeMany(ranges, root) {
|
|
259
|
-
return ranges.map((range) => this.serialize(range, root));
|
|
260
|
-
}
|
|
261
|
-
/**
|
|
262
|
-
* Batch deserialize multiple ranges.
|
|
263
|
-
*
|
|
264
|
-
* @param data - Array of serialized ranges
|
|
265
|
-
* @param root - Root element
|
|
266
|
-
* @returns Array of ranges (nulls for invalid ranges)
|
|
267
|
-
*/
|
|
268
|
-
deserializeMany(data, root) {
|
|
269
|
-
return data.map((item) => this.deserialize(item, root));
|
|
270
|
-
}
|
|
271
227
|
}
|
|
@@ -13,8 +13,6 @@
|
|
|
13
13
|
*
|
|
14
14
|
* This enables proper pronunciation, emphasis, and pacing for TTS without
|
|
15
15
|
* requiring authors to maintain separate catalog files.
|
|
16
|
-
*
|
|
17
|
-
* Part of PIE Assessment Toolkit.
|
|
18
16
|
*/
|
|
19
17
|
import type { AccessibilityCatalog, ConfigEntity } from "@pie-players/pie-players-shared/types";
|
|
20
18
|
/**
|
|
@@ -25,19 +23,11 @@ export interface ExtractionResult {
|
|
|
25
23
|
cleanedConfig: ConfigEntity;
|
|
26
24
|
}
|
|
27
25
|
/**
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
|
|
32
|
-
export declare function sanitizeSsmlElement(element: Element): void;
|
|
33
|
-
/**
|
|
34
|
-
* Serialize a sanitized `<speak>` element back to a minimal SSML string.
|
|
35
|
-
* Uses a manual walker instead of `outerHTML` so that any namespace
|
|
36
|
-
* quirks (e.g. `xmlns` attributes added by the HTML parser) are
|
|
37
|
-
* normalised and disallowed descendants cannot reappear through quirks
|
|
38
|
-
* in the serializer.
|
|
26
|
+
* SSML cut down to the allow-listed tags and attributes. Input with no markup,
|
|
27
|
+
* or with no DOMParser available, comes back unchanged. Otherwise the first
|
|
28
|
+
* <speak> element comes back sanitized, or, when there is none, the sanitized
|
|
29
|
+
* fragment with no <speak> wrapper.
|
|
39
30
|
*/
|
|
40
|
-
export declare function serializeSsmlElement(element: Element): string;
|
|
41
31
|
export declare function sanitizeSsmlString(input: string): string;
|
|
42
32
|
/**
|
|
43
33
|
* SSMLExtractor Service
|
|
@@ -46,26 +36,13 @@ export declare function sanitizeSsmlString(input: string): string;
|
|
|
46
36
|
*/
|
|
47
37
|
export declare class SSMLExtractor {
|
|
48
38
|
private catalogCounter;
|
|
49
|
-
/**
|
|
50
|
-
* Extract SSML from entire item config (markup + models)
|
|
51
|
-
*
|
|
52
|
-
* @param config Item configuration with potential embedded SSML
|
|
53
|
-
* @returns Extracted catalogs and cleaned config
|
|
54
|
-
*/
|
|
39
|
+
/** Extract SSML from the item's markup, prompts and choice labels. */
|
|
55
40
|
extractFromItemConfig(config: ConfigEntity): ExtractionResult;
|
|
56
|
-
/**
|
|
57
|
-
* Extract SSML from markup string
|
|
58
|
-
*
|
|
59
|
-
* @param markup HTML markup potentially containing <speak> elements
|
|
60
|
-
* @param idPrefix Prefix for generating catalog IDs
|
|
61
|
-
* @returns Extracted catalogs and cleaned markup
|
|
62
|
-
*/
|
|
41
|
+
/** Extract SSML from one markup string. */
|
|
63
42
|
private extractFromMarkup;
|
|
64
43
|
/**
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
* @param prefix Context prefix (e.g., 'prompt-q1', 'choice-q1-a')
|
|
68
|
-
* @returns Unique catalog ID (e.g., 'auto-prompt-q1-0')
|
|
44
|
+
* A catalog id unique to this extractor until reset(), e.g.
|
|
45
|
+
* `auto-prompt-q1-0`.
|
|
69
46
|
*/
|
|
70
47
|
private generateCatalogId;
|
|
71
48
|
/**
|
|
@@ -13,8 +13,6 @@
|
|
|
13
13
|
*
|
|
14
14
|
* This enables proper pronunciation, emphasis, and pacing for TTS without
|
|
15
15
|
* requiring authors to maintain separate catalog files.
|
|
16
|
-
*
|
|
17
|
-
* Part of PIE Assessment Toolkit.
|
|
18
16
|
*/
|
|
19
17
|
/**
|
|
20
18
|
* Allow-listed SSML elements (from the SSML 1.1 spec subset we forward to
|
|
@@ -38,11 +36,6 @@ const SSML_ALLOWED_TAGS = new Set([
|
|
|
38
36
|
"audio",
|
|
39
37
|
"lexicon",
|
|
40
38
|
]);
|
|
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
39
|
/**
|
|
47
40
|
* Tags whose content must be dropped entirely (rather than unwrapped to
|
|
48
41
|
* preserve phrasing text). These are dangerous containers whose text
|
|
@@ -58,6 +51,10 @@ const SSML_DROP_FULLY_TAGS = new Set([
|
|
|
58
51
|
"noscript",
|
|
59
52
|
"template",
|
|
60
53
|
]);
|
|
54
|
+
/**
|
|
55
|
+
* Allow-listed attributes across SSML elements. One list covers every tag
|
|
56
|
+
* because the union is small and consistent across providers.
|
|
57
|
+
*/
|
|
61
58
|
const SSML_ALLOWED_ATTRS = new Set([
|
|
62
59
|
"alphabet",
|
|
63
60
|
"ph",
|
|
@@ -153,7 +150,7 @@ function isSafeSsmlAudioSrc(raw) {
|
|
|
153
150
|
* replace disallowed elements with their text content (so phrasing order
|
|
154
151
|
* is preserved but no scripts / iframes / unknown tags survive).
|
|
155
152
|
*/
|
|
156
|
-
|
|
153
|
+
function sanitizeSsmlElement(element) {
|
|
157
154
|
// Walk children snapshot first because we may replace nodes as we go.
|
|
158
155
|
const children = Array.from(element.children);
|
|
159
156
|
for (const child of children) {
|
|
@@ -175,10 +172,8 @@ export function sanitizeSsmlElement(element) {
|
|
|
175
172
|
element.removeAttribute(attrName);
|
|
176
173
|
}
|
|
177
174
|
}
|
|
178
|
-
//
|
|
179
|
-
//
|
|
180
|
-
// it. Refuse non-http(s) schemes and private / cloud-metadata hosts
|
|
181
|
-
// at the SSML boundary so we never forward an SSRF primitive.
|
|
175
|
+
// `src` survives the attribute allow-list, and a server-side provider
|
|
176
|
+
// fetches it: keep it only for an http(s) URL off SSML_PRIVATE_AUDIO_HOSTS.
|
|
182
177
|
if (element.tagName.toLowerCase() === "audio") {
|
|
183
178
|
const src = element.getAttribute("src");
|
|
184
179
|
if (src !== null && !isSafeSsmlAudioSrc(src)) {
|
|
@@ -193,7 +188,7 @@ export function sanitizeSsmlElement(element) {
|
|
|
193
188
|
* normalised and disallowed descendants cannot reappear through quirks
|
|
194
189
|
* in the serializer.
|
|
195
190
|
*/
|
|
196
|
-
|
|
191
|
+
function serializeSsmlElement(element) {
|
|
197
192
|
const tagName = element.tagName.toLowerCase();
|
|
198
193
|
const attrs = Array.from(element.attributes)
|
|
199
194
|
.filter((attr) => SSML_ALLOWED_ATTRS.has(attr.name.toLowerCase()))
|
|
@@ -227,6 +222,12 @@ export function serializeSsmlElement(element) {
|
|
|
227
222
|
}
|
|
228
223
|
return `<${tagName}${attrs}>${childPieces.join("")}</${tagName}>`;
|
|
229
224
|
}
|
|
225
|
+
/**
|
|
226
|
+
* SSML cut down to the allow-listed tags and attributes. Input with no markup,
|
|
227
|
+
* or with no DOMParser available, comes back unchanged. Otherwise the first
|
|
228
|
+
* <speak> element comes back sanitized, or, when there is none, the sanitized
|
|
229
|
+
* fragment with no <speak> wrapper.
|
|
230
|
+
*/
|
|
230
231
|
export function sanitizeSsmlString(input) {
|
|
231
232
|
const trimmed = String(input || "").trim();
|
|
232
233
|
if (!trimmed || !trimmed.includes("<"))
|
|
@@ -281,12 +282,7 @@ function escapeXmlAttr(value) {
|
|
|
281
282
|
*/
|
|
282
283
|
export class SSMLExtractor {
|
|
283
284
|
catalogCounter = 0;
|
|
284
|
-
/**
|
|
285
|
-
* Extract SSML from entire item config (markup + models)
|
|
286
|
-
*
|
|
287
|
-
* @param config Item configuration with potential embedded SSML
|
|
288
|
-
* @returns Extracted catalogs and cleaned config
|
|
289
|
-
*/
|
|
285
|
+
/** Extract SSML from the item's markup, prompts and choice labels. */
|
|
290
286
|
extractFromItemConfig(config) {
|
|
291
287
|
const allCatalogs = [];
|
|
292
288
|
// Shallow clone of config + a fresh models array. The per-model/choice
|
|
@@ -336,18 +332,11 @@ export class SSMLExtractor {
|
|
|
336
332
|
cleanedConfig,
|
|
337
333
|
};
|
|
338
334
|
}
|
|
339
|
-
/**
|
|
340
|
-
* Extract SSML from markup string
|
|
341
|
-
*
|
|
342
|
-
* @param markup HTML markup potentially containing <speak> elements
|
|
343
|
-
* @param idPrefix Prefix for generating catalog IDs
|
|
344
|
-
* @returns Extracted catalogs and cleaned markup
|
|
345
|
-
*/
|
|
335
|
+
/** Extract SSML from one markup string. */
|
|
346
336
|
extractFromMarkup(markup, idPrefix) {
|
|
347
337
|
if (!markup || markup.trim() === "") {
|
|
348
338
|
return { catalogs: [], cleanedMarkup: markup };
|
|
349
339
|
}
|
|
350
|
-
// Check if running in browser environment
|
|
351
340
|
if (typeof window === "undefined" || typeof DOMParser === "undefined") {
|
|
352
341
|
console.warn("[SSMLExtractor] DOMParser not available (SSR?), skipping extraction");
|
|
353
342
|
return { catalogs: [], cleanedMarkup: markup };
|
|
@@ -356,16 +345,12 @@ export class SSMLExtractor {
|
|
|
356
345
|
try {
|
|
357
346
|
const parser = new DOMParser();
|
|
358
347
|
const doc = parser.parseFromString(expandSelfClosingVoidTags(markup), "text/html");
|
|
359
|
-
// Find all <speak> elements
|
|
360
348
|
const speakElements = Array.from(doc.querySelectorAll("speak"));
|
|
361
349
|
if (speakElements.length === 0) {
|
|
362
|
-
// No SSML found, return original markup
|
|
363
350
|
return { catalogs: [], cleanedMarkup: markup };
|
|
364
351
|
}
|
|
365
|
-
// Process each <speak> element
|
|
366
352
|
for (const speakEl of speakElements) {
|
|
367
353
|
try {
|
|
368
|
-
// Generate unique catalog ID
|
|
369
354
|
const catalogId = this.generateCatalogId(idPrefix);
|
|
370
355
|
// Sanitize before serializing so the SSML forwarded to the TTS
|
|
371
356
|
// provider only contains spec-allowed tags/attributes. This
|
|
@@ -373,7 +358,6 @@ export class SSMLExtractor {
|
|
|
373
358
|
// otherwise accept arbitrary HTML.
|
|
374
359
|
sanitizeSsmlElement(speakEl);
|
|
375
360
|
const ssmlContent = serializeSsmlElement(speakEl);
|
|
376
|
-
// Extract language (check both xml:lang and lang attributes)
|
|
377
361
|
const language = speakEl.getAttribute("xml:lang") ||
|
|
378
362
|
speakEl.getAttribute("lang") ||
|
|
379
363
|
"en-US";
|
|
@@ -409,7 +393,6 @@ export class SSMLExtractor {
|
|
|
409
393
|
else {
|
|
410
394
|
wrapper.setAttribute("data-catalog-idref", catalogId);
|
|
411
395
|
}
|
|
412
|
-
// Create catalog entry
|
|
413
396
|
catalogs.push({
|
|
414
397
|
identifier: catalogId,
|
|
415
398
|
cards: [
|
|
@@ -423,24 +406,19 @@ export class SSMLExtractor {
|
|
|
423
406
|
}
|
|
424
407
|
catch (error) {
|
|
425
408
|
console.error("[SSMLExtractor] Error processing <speak> element:", error);
|
|
426
|
-
// Continue with other elements
|
|
427
409
|
}
|
|
428
410
|
}
|
|
429
|
-
// Return cleaned markup
|
|
430
411
|
const cleanedMarkup = doc.body.innerHTML;
|
|
431
412
|
return { catalogs, cleanedMarkup };
|
|
432
413
|
}
|
|
433
414
|
catch (error) {
|
|
434
415
|
console.error("[SSMLExtractor] Error parsing markup:", error);
|
|
435
|
-
// Return original markup if parsing fails
|
|
436
416
|
return { catalogs: [], cleanedMarkup: markup };
|
|
437
417
|
}
|
|
438
418
|
}
|
|
439
419
|
/**
|
|
440
|
-
*
|
|
441
|
-
*
|
|
442
|
-
* @param prefix Context prefix (e.g., 'prompt-q1', 'choice-q1-a')
|
|
443
|
-
* @returns Unique catalog ID (e.g., 'auto-prompt-q1-0')
|
|
420
|
+
* A catalog id unique to this extractor until reset(), e.g.
|
|
421
|
+
* `auto-prompt-q1-0`.
|
|
444
422
|
*/
|
|
445
423
|
generateCatalogId(prefix) {
|
|
446
424
|
const id = `auto-${prefix}-${this.catalogCounter}`;
|
|
@@ -1,26 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* TTSService
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* and integrates with HighlightCoordinator for word highlighting.
|
|
4
|
+
* Owns one read-aloud session: the installed provider, playback state, and the
|
|
5
|
+
* word and sentence highlights painted through HighlightCoordinator.
|
|
7
6
|
*
|
|
8
|
-
*
|
|
9
|
-
* -
|
|
10
|
-
*
|
|
11
|
-
* -
|
|
12
|
-
*
|
|
13
|
-
* -
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
7
|
+
* Layout:
|
|
8
|
+
* - Provider and host wiring: initialize, setCatalogResolver, the highlight
|
|
9
|
+
* target resolver and math-speech sources.
|
|
10
|
+
* - Content resolution: catalog spoken card, then catalog-composed chunks
|
|
11
|
+
* (script and/or recording with a plain fallback), then generated speech.
|
|
12
|
+
* - Speech plan: structural pauses, seek segments, sentence segments and the
|
|
13
|
+
* normalized-to-DOM position map.
|
|
14
|
+
* - Playback: speakContent → executeSpeakPlayback, the run guard (speakRunId,
|
|
15
|
+
* runOwner), the pause hold and the playback-start barrier.
|
|
16
|
+
* - Highlight painting: boundary handling, the host resolver remap, catalog
|
|
17
|
+
* highlight plans.
|
|
18
|
+
* - Controls and state: pause/resume/stop/seek/rate, setState and listeners.
|
|
17
19
|
*/
|
|
18
|
-
import type { ITTSProvider, TTSConfig
|
|
20
|
+
import type { ITTSProvider, TTSConfig } from "@pie-players/pie-tts";
|
|
19
21
|
import type { AccessibilityCatalogResolver } from "./AccessibilityCatalogResolver.js";
|
|
20
22
|
import type { HighlightCoordinatorApi, SpeakOptions } from "./interfaces.js";
|
|
21
23
|
import type { TTSHighlightTargetResolverProvider } from "./tts/highlight-target-resolver.js";
|
|
22
24
|
import { type SREMathSpeechOptions } from "./tts/math-speech.js";
|
|
23
|
-
|
|
25
|
+
import type { ToolkitTTSConfig } from "./tts/provider-options.js";
|
|
24
26
|
/**
|
|
25
27
|
* Playback state
|
|
26
28
|
*/
|
|
@@ -32,8 +34,6 @@ export declare enum PlaybackState {
|
|
|
32
34
|
ERROR = "error"
|
|
33
35
|
}
|
|
34
36
|
/**
|
|
35
|
-
* TTSService
|
|
36
|
-
*
|
|
37
37
|
* Instantiable service for text-to-speech functionality.
|
|
38
38
|
* Each instance manages its own playback state.
|
|
39
39
|
*/
|
|
@@ -57,6 +57,7 @@ export declare class TTSService {
|
|
|
57
57
|
private listeners;
|
|
58
58
|
private lastError;
|
|
59
59
|
private speakRunId;
|
|
60
|
+
private runOwner;
|
|
60
61
|
private currentBoundaryOffset;
|
|
61
62
|
private activeWordBoundaryOffset;
|
|
62
63
|
private seekSegments;
|
|
@@ -74,12 +75,24 @@ export declare class TTSService {
|
|
|
74
75
|
private pendingPlaybackRateRequest;
|
|
75
76
|
private activePlaybackRate;
|
|
76
77
|
private activeHighlightMode;
|
|
78
|
+
private wordlessPartHighlight;
|
|
77
79
|
private lastRenderedRegionTarget;
|
|
78
80
|
private telemetryReporter;
|
|
79
81
|
private readonly generatedMathSpeechResolver;
|
|
80
|
-
constructor();
|
|
81
82
|
private clearPlaybackStartBarrier;
|
|
82
83
|
private notifyPlaybackStarted;
|
|
84
|
+
/**
|
|
85
|
+
* A word-mode part whose playback sends no boundaries, such as a server
|
|
86
|
+
* response without speech marks, highlights as a sentence-mode read does.
|
|
87
|
+
* Catalog chunks and a single speak highlight the same in both modes.
|
|
88
|
+
*/
|
|
89
|
+
private highlightWordlessPlayback;
|
|
90
|
+
/**
|
|
91
|
+
* Defers marking the run playing, and the highlights queued through
|
|
92
|
+
* runWhenPlaybackStarts, until the provider or the recording signals that
|
|
93
|
+
* audio started. Returns the barrier's cleanup, or null when nothing will
|
|
94
|
+
* signal a start, in which case the caller marks the run playing itself.
|
|
95
|
+
*/
|
|
83
96
|
private installPlaybackStartBarrier;
|
|
84
97
|
private runWhenPlaybackStarts;
|
|
85
98
|
/** Resolves when run `runId` may start its next part: at once unless paused. */
|
|
@@ -105,11 +118,8 @@ export declare class TTSService {
|
|
|
105
118
|
private emitTelemetry;
|
|
106
119
|
/**
|
|
107
120
|
* Initialize TTS service with a provider
|
|
108
|
-
*
|
|
109
|
-
* @param provider TTS provider instance
|
|
110
|
-
* @param config Provider configuration
|
|
111
121
|
*/
|
|
112
|
-
initialize(provider: ITTSProvider, config?: Partial<
|
|
122
|
+
initialize(provider: ITTSProvider, config?: Partial<ToolkitTTSConfig>): Promise<void>;
|
|
113
123
|
/**
|
|
114
124
|
* Update rate, pitch, voice or provider options without reinitializing.
|
|
115
125
|
* `providerOptions` merge over the ones already configured.
|
|
@@ -153,8 +163,6 @@ export declare class TTSService {
|
|
|
153
163
|
*
|
|
154
164
|
* When set, the speak() method will check for pre-authored spoken
|
|
155
165
|
* content in catalogs before falling back to generated TTS.
|
|
156
|
-
*
|
|
157
|
-
* @param resolver AccessibilityCatalogResolver instance
|
|
158
166
|
*/
|
|
159
167
|
setCatalogResolver(resolver: AccessibilityCatalogResolver): void;
|
|
160
168
|
private getHighlightResolverRuntime;
|
|
@@ -171,10 +179,6 @@ export declare class TTSService {
|
|
|
171
179
|
*/
|
|
172
180
|
private paintTTSWord;
|
|
173
181
|
private paintTTSSentenceRanges;
|
|
174
|
-
/**
|
|
175
|
-
* Get provider capabilities
|
|
176
|
-
*/
|
|
177
|
-
getCapabilities(): TTSProviderCapabilities | null;
|
|
178
182
|
private providerOptions;
|
|
179
183
|
/** The longest text the provider accepts in one speak, when it declares one. */
|
|
180
184
|
private maxTextLength;
|
|
@@ -187,8 +191,8 @@ export declare class TTSService {
|
|
|
187
191
|
/** Segments no longer than the provider's `maxTextLength`, offsets kept. */
|
|
188
192
|
private segmentsWithinLimit;
|
|
189
193
|
/**
|
|
190
|
-
*
|
|
191
|
-
*
|
|
194
|
+
* Text-collection options for `locale`, with the provider's boundary-spacing
|
|
195
|
+
* mode when it names a known one.
|
|
192
196
|
*/
|
|
193
197
|
private getTextProcessingOptions;
|
|
194
198
|
/**
|
|
@@ -203,12 +207,15 @@ export declare class TTSService {
|
|
|
203
207
|
* this service was initialized with.
|
|
204
208
|
*/
|
|
205
209
|
getMathSpeechOptions(): SREMathSpeechOptions | undefined;
|
|
210
|
+
/**
|
|
211
|
+
* Rebuilds normalizedToDOM from `element`'s visible text; traced runs also
|
|
212
|
+
* log how it differs from `spokenText`.
|
|
213
|
+
*/
|
|
206
214
|
private buildPositionMap;
|
|
207
215
|
private getStructuralPauseProfile;
|
|
208
216
|
private resolvePauseMsFromUnits;
|
|
209
217
|
private resolveHighlightMode;
|
|
210
218
|
private hasExplicitBreakSemantics;
|
|
211
|
-
private isElementHidden;
|
|
212
219
|
private getBoundaryStrength;
|
|
213
220
|
private getBoundaryAnchor;
|
|
214
221
|
/**
|
|
@@ -246,12 +253,19 @@ export declare class TTSService {
|
|
|
246
253
|
* running continues.
|
|
247
254
|
*/
|
|
248
255
|
speak(target: Range | Element, options?: SpeakOptions): Promise<void>;
|
|
256
|
+
/**
|
|
257
|
+
* Sets the rate a run starts at, after any rate write in flight. The run has
|
|
258
|
+
* no playback yet, so nothing restarts.
|
|
259
|
+
*/
|
|
260
|
+
private applyRunRate;
|
|
249
261
|
/**
|
|
250
262
|
* Suppression is read from the live ancestors of what is read, because the
|
|
251
263
|
* element carrying it may sit above the content root.
|
|
252
264
|
*/
|
|
253
265
|
private isSuppressedTarget;
|
|
254
266
|
private resolveElementTarget;
|
|
267
|
+
/** Whether the root's card, or a card of a node inside it, would be read. */
|
|
268
|
+
private holdsSpokenCard;
|
|
255
269
|
private resolveRangeTarget;
|
|
256
270
|
private speakContent;
|
|
257
271
|
/**
|
|
@@ -327,7 +341,8 @@ export declare class TTSService {
|
|
|
327
341
|
seekForward(units?: number): Promise<void>;
|
|
328
342
|
seekBackward(units?: number): Promise<void>;
|
|
329
343
|
/**
|
|
330
|
-
* Stop playback
|
|
344
|
+
* Stop playback and release the run owner. A run that ends on its own keeps
|
|
345
|
+
* its owner, so the owning control can replay it.
|
|
331
346
|
*/
|
|
332
347
|
stop(): void;
|
|
333
348
|
/**
|
|
@@ -340,41 +355,19 @@ export declare class TTSService {
|
|
|
340
355
|
* A disposed service cannot speak or be initialized again.
|
|
341
356
|
*/
|
|
342
357
|
dispose(): void;
|
|
343
|
-
/**
|
|
344
|
-
* Request UI-level TTS controls to hand off/deactivate.
|
|
345
|
-
*
|
|
346
|
-
* This is intentionally separate from playback controls so hosts can orchestrate
|
|
347
|
-
* control handoff explicitly (for example: stop playback, then dismiss controls).
|
|
348
|
-
*/
|
|
349
|
-
requestControlHandoff(): void;
|
|
350
|
-
/**
|
|
351
|
-
* Check if currently playing
|
|
352
|
-
*/
|
|
353
|
-
isPlaying(): boolean;
|
|
354
|
-
/**
|
|
355
|
-
* Check if paused
|
|
356
|
-
*/
|
|
357
|
-
isPaused(): boolean;
|
|
358
358
|
/**
|
|
359
359
|
* Get current state
|
|
360
360
|
*/
|
|
361
361
|
getState(): PlaybackState;
|
|
362
|
-
|
|
363
|
-
* Get current text being spoken
|
|
364
|
-
*/
|
|
365
|
-
getCurrentText(): string | null;
|
|
366
|
-
/**
|
|
367
|
-
* Get the last error message
|
|
368
|
-
* Returns null if no error has occurred
|
|
369
|
-
*/
|
|
370
|
-
getLastError(): string | null;
|
|
362
|
+
getRunOwner(): string | null;
|
|
371
363
|
/**
|
|
372
364
|
* Subscribe to playback state changes. The returned function unsubscribes;
|
|
373
365
|
* each subscription is its own, so the same callback can be subscribed twice.
|
|
374
366
|
*/
|
|
375
367
|
onStateChange(callback: (state: PlaybackState) => void): () => void;
|
|
376
368
|
/**
|
|
377
|
-
* Set state and notify listeners
|
|
369
|
+
* Set state and notify listeners. `announceUnchanged` notifies listeners even
|
|
370
|
+
* when the state does not change; telemetry fires only on a change.
|
|
378
371
|
*/
|
|
379
372
|
private setState;
|
|
380
373
|
}
|