@pie-players/pie-assessment-toolkit 1.0.0-beta.0 → 1.0.0-beta.2
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 +3 -1
- 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 +7 -7
- package/dist/components/SectionToolBar.custom-element.js +1 -1
- package/dist/components/chunks/ItemToolBar-341vf20n.js +2 -0
- package/dist/components/chunks/ItemToolBar-jpac75fa.js +4 -0
- package/dist/components/chunks/ItemToolBar-wbem8cna.js +40 -0
- package/dist/policy/core/compose-decision.js +2 -0
- package/dist/policy/core/decision-types.d.ts +10 -5
- package/dist/policy/core/feature-decision.d.ts +3 -3
- package/dist/policy/core/pnp-policy-inputs.d.ts +2 -1
- package/dist/policy/core/pnp-policy-inputs.js +6 -2
- package/dist/policy/core/provenance.d.ts +1 -1
- package/dist/policy/core/tool-parameters.d.ts +3 -2
- package/dist/policy/core/tool-parameters.js +17 -5
- package/dist/policy/sources/PnpPolicySource.d.ts +4 -2
- package/dist/policy/sources/PnpPolicySource.js +27 -1
- package/dist/services/TTSService.d.ts +5 -4
- package/dist/services/TTSService.js +25 -11
- package/dist/services/ToolRegistry.d.ts +4 -3
- package/dist/services/ToolRegistry.js +2 -1
- package/dist/services/ToolkitCoordinator.js +6 -5
- package/dist/services/interfaces.d.ts +4 -0
- package/dist/services/tool-context.d.ts +33 -2
- package/dist/services/tool-context.js +69 -49
- package/dist/services/toolbar-decided-tools.d.ts +1 -0
- package/dist/services/toolbar-decided-tools.js +1 -0
- package/dist/services/tts/browser-provider.js +9 -2
- package/dist/tools/registration.d.ts +2 -2
- package/dist/tools/registration.js +1 -1
- package/package.json +7 -7
- package/dist/components/chunks/ItemToolBar-3zrc4p3d.js +0 -4
- package/dist/components/chunks/ItemToolBar-9gqhfxtn.js +0 -40
- package/dist/components/chunks/ItemToolBar-hzc5wyjp.js +0 -2
|
@@ -19,6 +19,7 @@ export class PnpPolicySource {
|
|
|
19
19
|
const allSupports = new Set();
|
|
20
20
|
pnp?.supports?.forEach((s) => allSupports.add(s));
|
|
21
21
|
pnp?.prohibitedSupports?.forEach((s) => allSupports.add(s));
|
|
22
|
+
pnp?.activateAtInit?.forEach((s) => allSupports.add(s));
|
|
22
23
|
districtPolicy?.blockedTools?.forEach((s) => allSupports.add(s));
|
|
23
24
|
districtPolicy?.requiredTools?.forEach((s) => allSupports.add(s));
|
|
24
25
|
for (const s of Object.keys(testAdmin?.toolOverrides ?? {})) {
|
|
@@ -162,6 +163,7 @@ export class PnpPolicySource {
|
|
|
162
163
|
});
|
|
163
164
|
return;
|
|
164
165
|
}
|
|
166
|
+
const activateAtInit = ctx.pnp?.activateAtInit?.includes(supportId) === true;
|
|
165
167
|
const mandate = ctx.itemSettings?.requiredTools?.includes(supportId)
|
|
166
168
|
? "item-requirement"
|
|
167
169
|
: ctx.districtPolicy?.requiredTools?.includes(supportId)
|
|
@@ -175,6 +177,7 @@ export class PnpPolicySource {
|
|
|
175
177
|
out.perToolFlags.set(supportId, {
|
|
176
178
|
required: mandate !== undefined,
|
|
177
179
|
alwaysAvailable: true,
|
|
180
|
+
activateAtInit,
|
|
178
181
|
rule: "test-admin-override",
|
|
179
182
|
});
|
|
180
183
|
out.decisions.push({
|
|
@@ -194,6 +197,7 @@ export class PnpPolicySource {
|
|
|
194
197
|
out.perToolFlags.set(supportId, {
|
|
195
198
|
required: true,
|
|
196
199
|
alwaysAvailable: false,
|
|
200
|
+
activateAtInit,
|
|
197
201
|
rule: "item-requirement",
|
|
198
202
|
});
|
|
199
203
|
out.decisions.push({
|
|
@@ -213,6 +217,7 @@ export class PnpPolicySource {
|
|
|
213
217
|
out.perToolFlags.set(supportId, {
|
|
214
218
|
required: true,
|
|
215
219
|
alwaysAvailable: false,
|
|
220
|
+
activateAtInit,
|
|
216
221
|
rule: "district-requirement",
|
|
217
222
|
});
|
|
218
223
|
out.decisions.push({
|
|
@@ -226,11 +231,31 @@ export class PnpPolicySource {
|
|
|
226
231
|
});
|
|
227
232
|
return;
|
|
228
233
|
}
|
|
229
|
-
// 8. Profile supports (student needs)
|
|
234
|
+
// 8. Profile supports (student needs). `activateAtInit` grants as
|
|
235
|
+
// `supports` does and names the tool to turn on at start.
|
|
236
|
+
if (activateAtInit) {
|
|
237
|
+
out.perToolFlags.set(supportId, {
|
|
238
|
+
required: false,
|
|
239
|
+
alwaysAvailable: true,
|
|
240
|
+
activateAtInit,
|
|
241
|
+
rule: "pnp-activate-at-init",
|
|
242
|
+
});
|
|
243
|
+
out.decisions.push({
|
|
244
|
+
precedence: 8,
|
|
245
|
+
rule: "pnp-activate-at-init",
|
|
246
|
+
featureId: supportId,
|
|
247
|
+
action: "enable",
|
|
248
|
+
sourceType: "student",
|
|
249
|
+
reason: `Student PNP profile turns on "${supportId}" at start`,
|
|
250
|
+
value: ctx.pnp?.activateAtInit,
|
|
251
|
+
});
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
230
254
|
if (ctx.pnp?.supports?.includes(supportId)) {
|
|
231
255
|
out.perToolFlags.set(supportId, {
|
|
232
256
|
required: false,
|
|
233
257
|
alwaysAvailable: true,
|
|
258
|
+
activateAtInit: false,
|
|
234
259
|
rule: "pnp-support",
|
|
235
260
|
});
|
|
236
261
|
out.decisions.push({
|
|
@@ -281,6 +306,7 @@ export class PnpPolicySource {
|
|
|
281
306
|
["pnp-prohibited", pnp?.prohibitedSupports],
|
|
282
307
|
["item-requirement", items.flatMap((item) => item.requiredTools ?? [])],
|
|
283
308
|
["district-requirement", settings?.districtPolicy?.requiredTools],
|
|
309
|
+
["pnp-activate-at-init", pnp?.activateAtInit],
|
|
284
310
|
["pnp-support", pnp?.supports],
|
|
285
311
|
];
|
|
286
312
|
for (const [rule, ids] of named) {
|
|
@@ -121,7 +121,8 @@ export declare class TTSService {
|
|
|
121
121
|
*/
|
|
122
122
|
initialize(provider: ITTSProvider, config?: Partial<ToolkitTTSConfig>): Promise<void>;
|
|
123
123
|
/**
|
|
124
|
-
* Update rate, pitch, voice or provider options without
|
|
124
|
+
* Update rate, pitch, volume, voice or provider options without
|
|
125
|
+
* reinitializing.
|
|
125
126
|
* `providerOptions` merge over the ones already configured.
|
|
126
127
|
*/
|
|
127
128
|
updateSettings(settings: Partial<TTSConfig>): Promise<void>;
|
|
@@ -254,10 +255,10 @@ export declare class TTSService {
|
|
|
254
255
|
*/
|
|
255
256
|
speak(target: Range | Element, options?: SpeakOptions): Promise<void>;
|
|
256
257
|
/**
|
|
257
|
-
* Sets the rate a run starts at, after any rate write in
|
|
258
|
-
* no playback yet, so nothing restarts.
|
|
258
|
+
* Sets the rate, pitch and volume a run starts at, after any rate write in
|
|
259
|
+
* flight. The run has no playback yet, so nothing restarts.
|
|
259
260
|
*/
|
|
260
|
-
private
|
|
261
|
+
private applyRunSettings;
|
|
261
262
|
/**
|
|
262
263
|
* Suppression is read from the live ancestors of what is read, because the
|
|
263
264
|
* element carrying it may sit above the content root.
|
|
@@ -389,7 +389,8 @@ export class TTSService {
|
|
|
389
389
|
this.provider = await provider.initialize(config);
|
|
390
390
|
}
|
|
391
391
|
/**
|
|
392
|
-
* Update rate, pitch, voice or provider options without
|
|
392
|
+
* Update rate, pitch, volume, voice or provider options without
|
|
393
|
+
* reinitializing.
|
|
393
394
|
* `providerOptions` merge over the ones already configured.
|
|
394
395
|
*/
|
|
395
396
|
async updateSettings(settings) {
|
|
@@ -1160,24 +1161,37 @@ export class TTSService {
|
|
|
1160
1161
|
return;
|
|
1161
1162
|
}
|
|
1162
1163
|
const runId = waitingRunId ?? this.beginRun(owner);
|
|
1163
|
-
if (options.rate !== undefined
|
|
1164
|
-
|
|
1164
|
+
if (options.rate !== undefined ||
|
|
1165
|
+
options.pitch !== undefined ||
|
|
1166
|
+
options.volume !== undefined) {
|
|
1167
|
+
await this.applyRunSettings(options);
|
|
1165
1168
|
if (runId !== this.speakRunId)
|
|
1166
1169
|
return;
|
|
1167
1170
|
}
|
|
1168
1171
|
await this.speakContent(content.text, content.options, runId);
|
|
1169
1172
|
}
|
|
1170
1173
|
/**
|
|
1171
|
-
* Sets the rate a run starts at, after any rate write in
|
|
1172
|
-
* no playback yet, so nothing restarts.
|
|
1174
|
+
* Sets the rate, pitch and volume a run starts at, after any rate write in
|
|
1175
|
+
* flight. The run has no playback yet, so nothing restarts.
|
|
1173
1176
|
*/
|
|
1174
|
-
async
|
|
1177
|
+
async applyRunSettings(options) {
|
|
1175
1178
|
await this.playbackRateWriteQueue;
|
|
1176
|
-
const
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1179
|
+
const settings = {};
|
|
1180
|
+
if (options.rate !== undefined) {
|
|
1181
|
+
const nextRate = this.normalizePlaybackRate(options.rate);
|
|
1182
|
+
const currentRate = this.normalizePlaybackRate(Number(this.ttsConfig.rate ?? 1));
|
|
1183
|
+
if (nextRate !== currentRate)
|
|
1184
|
+
settings.rate = nextRate;
|
|
1185
|
+
}
|
|
1186
|
+
if (options.pitch !== undefined && options.pitch !== this.ttsConfig.pitch) {
|
|
1187
|
+
settings.pitch = options.pitch;
|
|
1188
|
+
}
|
|
1189
|
+
if (options.volume !== undefined &&
|
|
1190
|
+
options.volume !== this.ttsConfig.volume) {
|
|
1191
|
+
settings.volume = options.volume;
|
|
1192
|
+
}
|
|
1193
|
+
if (Object.keys(settings).length > 0)
|
|
1194
|
+
await this.updateSettings(settings);
|
|
1181
1195
|
}
|
|
1182
1196
|
/**
|
|
1183
1197
|
* Suppression is read from the live ancestors of what is read, because the
|
|
@@ -114,9 +114,10 @@ export interface ToolbarContext {
|
|
|
114
114
|
getResolvedToolContext?: (toolId: string) => ResolvedToolContext | null;
|
|
115
115
|
getToolRenderParams?: (toolId: string) => Record<string, unknown> | null;
|
|
116
116
|
/**
|
|
117
|
-
* The tool's policy parameters on this toolbar: the
|
|
118
|
-
*
|
|
119
|
-
* Authored content, so a registration checks what it
|
|
117
|
+
* The tool's policy parameters on this toolbar: the profile's, assessment's
|
|
118
|
+
* and, on an item's own toolbar, item's `toolParameters` entries merged key by
|
|
119
|
+
* key, later ones winning. Authored content, so a registration checks what it
|
|
120
|
+
* reads.
|
|
120
121
|
*/
|
|
121
122
|
getToolParameters?: <K extends string>(toolId: K) => ToolParametersFor<K> | null;
|
|
122
123
|
}
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* warnings and the toolbar and surface render helpers.
|
|
13
13
|
*/
|
|
14
14
|
import { dynamicMessageKey } from "@pie-players/pie-players-shared/i18n/provider";
|
|
15
|
+
import { settleAll } from "@pie-players/pie-players-shared/promise";
|
|
15
16
|
import { resolveToolTag, } from "../tools/tool-tag-map.js";
|
|
16
17
|
import { PENDING_INPUT_WARNING_DELAY_MS, warnOncePerDocument, } from "../runtime/page-warnings.js";
|
|
17
18
|
const logToolCallbackFailure = (toolId, phase, error) => console.error(`[ToolRegistry] Tool '${toolId}' ${phase} check threw:`, error);
|
|
@@ -608,7 +609,7 @@ export class ToolRegistry {
|
|
|
608
609
|
* tools loaded and usable. A failed tool is retried on the next call.
|
|
609
610
|
*/
|
|
610
611
|
async ensureToolModulesLoaded(toolIds) {
|
|
611
|
-
const results = await
|
|
612
|
+
const results = await settleAll(toolIds.map((toolId) => this.ensureToolModuleLoaded(toolId)));
|
|
612
613
|
const failures = new Map();
|
|
613
614
|
results.forEach((result, index) => {
|
|
614
615
|
if (result.status === "rejected")
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
* - tool-open requests;
|
|
22
22
|
* - config-change application and TTS reconfiguration, last.
|
|
23
23
|
*/
|
|
24
|
+
import { settleAll } from "@pie-players/pie-players-shared/promise";
|
|
24
25
|
import { normalizeToolsConfig, } from "./tools-config-normalizer.js";
|
|
25
26
|
import { collectToolConfigDiagnostics, normalizeToolConfigStrictness, reportToolConfigDiagnostics, } from "./tool-config-validation.js";
|
|
26
27
|
import { AccessibilityCatalogResolver } from "./AccessibilityCatalogResolver.js";
|
|
@@ -1272,7 +1273,7 @@ export class ToolkitCoordinator {
|
|
|
1272
1273
|
// retired controller out of the cache immediately, but do not let its
|
|
1273
1274
|
// replacement hydrate until that exact cohort's save/dispose pipeline has
|
|
1274
1275
|
// settled. Other cohorts remain independent.
|
|
1275
|
-
await
|
|
1276
|
+
await settleAll([pendingDisposal]);
|
|
1276
1277
|
}
|
|
1277
1278
|
if (this.disposePromise !== null) {
|
|
1278
1279
|
throw new ToolkitCoordinatorDisposedError();
|
|
@@ -1540,7 +1541,7 @@ export class ToolkitCoordinator {
|
|
|
1540
1541
|
return existingDisposal;
|
|
1541
1542
|
if (!initEntry)
|
|
1542
1543
|
return Promise.resolve();
|
|
1543
|
-
const retirementBarrier =
|
|
1544
|
+
const retirementBarrier = settleAll([initEntry.promise]).then(() => undefined);
|
|
1544
1545
|
return this.trackSectionControllerDisposal(mapKey, retirementBarrier);
|
|
1545
1546
|
}
|
|
1546
1547
|
const persistenceStrategy = this.sectionPersistenceStrategies.get(mapKey);
|
|
@@ -1576,7 +1577,7 @@ export class ToolkitCoordinator {
|
|
|
1576
1577
|
persistenceStrategy,
|
|
1577
1578
|
});
|
|
1578
1579
|
const disposalBarrier = initEntry
|
|
1579
|
-
?
|
|
1580
|
+
? settleAll([initEntry.promise]).then(() => controllerDisposalPromise)
|
|
1580
1581
|
: controllerDisposalPromise;
|
|
1581
1582
|
return this.trackSectionControllerDisposal(mapKey, disposalBarrier);
|
|
1582
1583
|
}
|
|
@@ -1694,7 +1695,7 @@ export class ToolkitCoordinator {
|
|
|
1694
1695
|
attemptId: key.attemptId,
|
|
1695
1696
|
});
|
|
1696
1697
|
}
|
|
1697
|
-
await
|
|
1698
|
+
await settleAll(Array.from(this.sectionControllerDisposePromises.values()));
|
|
1698
1699
|
this.sectionControllers.clear();
|
|
1699
1700
|
this.sectionControllerKeys.clear();
|
|
1700
1701
|
this.sectionControllerInitEntries.clear();
|
|
@@ -1742,7 +1743,7 @@ export class ToolkitCoordinator {
|
|
|
1742
1743
|
for (const promise of this.providerInitPromises.values()) {
|
|
1743
1744
|
pending.add(promise);
|
|
1744
1745
|
}
|
|
1745
|
-
await
|
|
1746
|
+
await settleAll(pending);
|
|
1746
1747
|
}
|
|
1747
1748
|
/**
|
|
1748
1749
|
* Initialize the TTS service from the `textToSpeech` tool config.
|
|
@@ -211,6 +211,10 @@ export interface SpeakOptions {
|
|
|
211
211
|
ownerId?: string;
|
|
212
212
|
/** The rate the read starts at; it stays the service's rate afterwards. */
|
|
213
213
|
rate?: number;
|
|
214
|
+
/** The pitch the read starts at, 0–2 with 1 normal; it stays the service's. */
|
|
215
|
+
pitch?: number;
|
|
216
|
+
/** The volume the read starts at, 0–1; it stays the service's. */
|
|
217
|
+
volume?: number;
|
|
214
218
|
}
|
|
215
219
|
/**
|
|
216
220
|
* TTS service interface
|
|
@@ -107,9 +107,40 @@ export type ToolContext = AssessmentToolContext | SectionToolContext | ItemToolC
|
|
|
107
107
|
*/
|
|
108
108
|
export declare function hasMathContent(context: ToolContext): boolean;
|
|
109
109
|
/**
|
|
110
|
-
*
|
|
110
|
+
* What one element of a context declares about the PNP supports it implements.
|
|
111
|
+
*
|
|
112
|
+
* An element declares them on its class, as `static pieSupports`: a list of AfA
|
|
113
|
+
* PNP 3.0 support terms, such as `answer-masking`. The toolkit reads the class
|
|
114
|
+
* `customElements` holds under the element's runtime tag, so the declaration is
|
|
115
|
+
* known before the element renders. What a support needs from the rendered
|
|
116
|
+
* element is between the element and the tool that provides it, and that tool
|
|
117
|
+
* documents it.
|
|
118
|
+
*/
|
|
119
|
+
export interface ElementSupports {
|
|
120
|
+
/** The element's model id. */
|
|
121
|
+
elementId: string | undefined;
|
|
122
|
+
/** The tag the item binds the element to, as authored. */
|
|
123
|
+
tag: string;
|
|
124
|
+
/** The element's package spec in the item's `elements` map, when it has one. */
|
|
125
|
+
packageSpec: string | undefined;
|
|
126
|
+
/**
|
|
127
|
+
* The terms the element's class declares: `undefined` while the class is
|
|
128
|
+
* undefined, and for a class that declares nothing. An empty list is a
|
|
129
|
+
* declaration that the element implements none.
|
|
130
|
+
*/
|
|
131
|
+
supports: readonly string[] | undefined;
|
|
132
|
+
/**
|
|
133
|
+
* The runtime tags the class can still be defined under, empty once it is
|
|
134
|
+
* defined. A definition landing under one of them can change `supports`.
|
|
135
|
+
*/
|
|
136
|
+
pendingTags: readonly string[];
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* The supports each element of a context declares: the element's own at element
|
|
140
|
+
* level, every element of the item or passage config at those levels, and none
|
|
141
|
+
* elsewhere. A model that names no element renders nothing and is skipped.
|
|
111
142
|
*/
|
|
112
|
-
export declare function
|
|
143
|
+
export declare function elementSupports(context: ToolContext): ElementSupports[];
|
|
113
144
|
/**
|
|
114
145
|
* Helper to check if context contains text that can be read aloud
|
|
115
146
|
*/
|
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
* Defines the context types passed to tools for making visibility and behavior decisions.
|
|
5
5
|
* Each context level provides increasingly specific information about where the tool is being used.
|
|
6
6
|
*/
|
|
7
|
+
import { runtimeElementTags } from "@pie-players/pie-players-shared/pie";
|
|
8
|
+
import { parseVersionedTagName } from "@pie-players/pie-players-shared/pie/tag-names";
|
|
7
9
|
/**
|
|
8
10
|
* Type guard to check if context is at item level
|
|
9
11
|
*/
|
|
@@ -300,63 +302,81 @@ export function hasMathContent(context) {
|
|
|
300
302
|
hasSpacedSubtraction(text) ||
|
|
301
303
|
hasAppliedPercentage(text));
|
|
302
304
|
}
|
|
303
|
-
|
|
304
|
-
"multiple-choice",
|
|
305
|
-
"inline-choice",
|
|
306
|
-
"select-text",
|
|
307
|
-
"ebsr",
|
|
308
|
-
]);
|
|
309
|
-
// Content names one interaction several ways: `multiple-choice`, `pie-multiple-choice`,
|
|
310
|
-
// `pie-element-multiple-choice`, a runtime `--version-*` tag, or any tag the item's
|
|
311
|
-
// `elements` map binds to `@pie-element/multiple-choice`.
|
|
312
|
-
const baseElementName = (tag) => tag
|
|
313
|
-
.toLowerCase()
|
|
314
|
-
.replace(/--version-[0-9a-z-]+$/, "")
|
|
315
|
-
.replace(/^pie-(element-)?/, "");
|
|
316
|
-
function isChoiceElement(tag, elements) {
|
|
317
|
-
if (CHOICE_INTERACTION_ELEMENTS.has(baseElementName(tag)))
|
|
318
|
-
return true;
|
|
305
|
+
function packageSpecFor(tag, elements) {
|
|
319
306
|
if (!elements || typeof elements !== "object")
|
|
320
|
-
return
|
|
307
|
+
return undefined;
|
|
321
308
|
const map = elements;
|
|
322
|
-
const spec = map[tag] ?? map[tag.
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
309
|
+
const spec = map[tag] ?? map[parseVersionedTagName(tag).baseName];
|
|
310
|
+
return typeof spec === "string" && spec ? spec : undefined;
|
|
311
|
+
}
|
|
312
|
+
function declaredSupports(elementClass) {
|
|
313
|
+
let declared;
|
|
314
|
+
try {
|
|
315
|
+
declared = elementClass.pieSupports;
|
|
316
|
+
}
|
|
317
|
+
catch {
|
|
318
|
+
return undefined;
|
|
319
|
+
}
|
|
320
|
+
if (!Array.isArray(declared))
|
|
321
|
+
return undefined;
|
|
322
|
+
return declared.filter((term) => typeof term === "string");
|
|
323
|
+
}
|
|
324
|
+
function readElementSupports(model, elements) {
|
|
325
|
+
if (!model || typeof model !== "object")
|
|
326
|
+
return null;
|
|
327
|
+
const { id, element } = model;
|
|
328
|
+
if (typeof element !== "string" || !element)
|
|
329
|
+
return null;
|
|
330
|
+
const packageSpec = packageSpecFor(element, elements);
|
|
331
|
+
const candidates = runtimeElementTags(element, packageSpec);
|
|
332
|
+
const registry = typeof customElements === "undefined" ? undefined : customElements;
|
|
333
|
+
for (const tag of candidates) {
|
|
334
|
+
const elementClass = registry?.get(tag);
|
|
335
|
+
if (elementClass) {
|
|
336
|
+
return {
|
|
337
|
+
elementId: typeof id === "string" ? id : undefined,
|
|
338
|
+
tag: element,
|
|
339
|
+
packageSpec,
|
|
340
|
+
supports: declaredSupports(elementClass),
|
|
341
|
+
pendingTags: [],
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
return {
|
|
346
|
+
elementId: typeof id === "string" ? id : undefined,
|
|
347
|
+
tag: element,
|
|
348
|
+
packageSpec,
|
|
349
|
+
supports: undefined,
|
|
350
|
+
pendingTags: registry ? candidates : [],
|
|
351
|
+
};
|
|
327
352
|
}
|
|
328
353
|
/**
|
|
329
|
-
*
|
|
354
|
+
* The supports each element of a context declares: the element's own at element
|
|
355
|
+
* level, every element of the item or passage config at those levels, and none
|
|
356
|
+
* elsewhere. A model that names no element renders nothing and is skipped.
|
|
330
357
|
*/
|
|
331
|
-
export function
|
|
358
|
+
export function elementSupports(context) {
|
|
359
|
+
let config;
|
|
360
|
+
let models;
|
|
332
361
|
if (isElementContext(context)) {
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
return false;
|
|
336
|
-
const model = findElementModel(config.models, context.elementId);
|
|
337
|
-
if (!model)
|
|
338
|
-
return false;
|
|
339
|
-
const type = model.element || "";
|
|
340
|
-
return isChoiceElement(type, config.elements);
|
|
362
|
+
config = context.item.config;
|
|
363
|
+
models = [findElementModel(config?.models, context.elementId)];
|
|
341
364
|
}
|
|
342
|
-
if (isItemContext(context)) {
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
if (!candidate || typeof candidate !== "object")
|
|
346
|
-
return false;
|
|
347
|
-
const model = candidate;
|
|
348
|
-
const type = model.element || "";
|
|
349
|
-
// A model that names its element has answered the question, whichever way
|
|
350
|
-
// the answer falls: `placement-ordering`, `categorize` and
|
|
351
|
-
// `drag-in-the-blank` each hold their draggables in `choices` without
|
|
352
|
-
// being choice interactions. The `choices` heuristic remains for configs
|
|
353
|
-
// that name no element.
|
|
354
|
-
if (type)
|
|
355
|
-
return isChoiceElement(type, config?.elements);
|
|
356
|
-
return Array.isArray(model.choices) && model.choices.length > 0;
|
|
357
|
-
});
|
|
365
|
+
else if (isItemContext(context)) {
|
|
366
|
+
config = context.item?.config;
|
|
367
|
+
models = normalizeModels(config?.models);
|
|
358
368
|
}
|
|
359
|
-
|
|
369
|
+
else if (isPassageContext(context)) {
|
|
370
|
+
config = context.passage?.config;
|
|
371
|
+
models = normalizeModels(config?.models);
|
|
372
|
+
}
|
|
373
|
+
else {
|
|
374
|
+
return [];
|
|
375
|
+
}
|
|
376
|
+
return models.flatMap((model) => {
|
|
377
|
+
const supports = readElementSupports(model, config?.elements);
|
|
378
|
+
return supports ? [supports] : [];
|
|
379
|
+
});
|
|
360
380
|
}
|
|
361
381
|
/**
|
|
362
382
|
* Helper to check if context contains text that can be read aloud
|
|
@@ -17,6 +17,7 @@ export function createDecidedToolsTracker() {
|
|
|
17
17
|
toolId: entry.toolId,
|
|
18
18
|
required: entry.required,
|
|
19
19
|
alwaysAvailable: entry.alwaysAvailable,
|
|
20
|
+
activateAtInit: entry.activateAtInit,
|
|
20
21
|
parameters: entry.parameters,
|
|
21
22
|
}));
|
|
22
23
|
if (last?.renderVersion === renderVersion &&
|
|
@@ -37,6 +37,8 @@ export const browserFallbackConfig = (config) => {
|
|
|
37
37
|
fallback.rate = config.rate;
|
|
38
38
|
if (config.pitch !== undefined)
|
|
39
39
|
fallback.pitch = config.pitch;
|
|
40
|
+
if (config.volume !== undefined)
|
|
41
|
+
fallback.volume = config.volume;
|
|
40
42
|
if (config.mathTokenHighlighting !== undefined) {
|
|
41
43
|
fallback.mathTokenHighlighting = config.mathTokenHighlighting;
|
|
42
44
|
}
|
|
@@ -437,8 +439,10 @@ class BrowserTTSProviderImpl {
|
|
|
437
439
|
utterance.voice = voice;
|
|
438
440
|
if (this.config?.rate)
|
|
439
441
|
utterance.rate = this.config.rate;
|
|
440
|
-
if (this.config?.pitch)
|
|
442
|
+
if (this.config?.pitch !== undefined)
|
|
441
443
|
utterance.pitch = this.config.pitch;
|
|
444
|
+
if (this.config?.volume !== undefined)
|
|
445
|
+
utterance.volume = this.config.volume;
|
|
442
446
|
let didStart = false;
|
|
443
447
|
let settled = false;
|
|
444
448
|
let startTimeout = null;
|
|
@@ -616,7 +620,7 @@ class BrowserTTSProviderImpl {
|
|
|
616
620
|
this.releaseHold();
|
|
617
621
|
}
|
|
618
622
|
/**
|
|
619
|
-
* Update settings dynamically (rate, pitch, voice)
|
|
623
|
+
* Update settings dynamically (rate, pitch, volume, voice)
|
|
620
624
|
* Changes take effect on the next speak() call
|
|
621
625
|
*/
|
|
622
626
|
updateSettings(settings) {
|
|
@@ -629,6 +633,9 @@ class BrowserTTSProviderImpl {
|
|
|
629
633
|
if (settings.pitch !== undefined) {
|
|
630
634
|
this.config.pitch = settings.pitch;
|
|
631
635
|
}
|
|
636
|
+
if (settings.volume !== undefined) {
|
|
637
|
+
this.config.volume = settings.volume;
|
|
638
|
+
}
|
|
632
639
|
if (settings.voice !== undefined) {
|
|
633
640
|
this.config.voice = settings.voice;
|
|
634
641
|
}
|
|
@@ -32,8 +32,8 @@ export type { ContentCapabilityPhase, ContentCapabilityPolicy, ResolveContentCap
|
|
|
32
32
|
export { resolveContentCapabilities } from "./content-capability-resolution.js";
|
|
33
33
|
export type { ToolSurfaceHost, ToolSurfaceHostInput, ToolSurfaceHostOptions, ToolSurfaceHostSnapshot, ToolSurfaceScope, } from "./tool-surface-host.js";
|
|
34
34
|
export { createToolSurfaceHost } from "./tool-surface-host.js";
|
|
35
|
-
export type { ToolContext } from "../services/tool-context.js";
|
|
36
|
-
export {
|
|
35
|
+
export type { ElementSupports, ToolContext, } from "../services/tool-context.js";
|
|
36
|
+
export { elementSupports, hasMathContent, hasReadableText, hasSpokenContent, hasScienceContent, } from "../services/tool-context.js";
|
|
37
37
|
export type { ParsedToolInstanceId } from "../services/tool-instance-id.js";
|
|
38
38
|
export { createScopedToolId, parseScopedToolId, } from "../services/tool-instance-id.js";
|
|
39
39
|
export type { ToolComponentFactory, ToolComponentFactoryMap, ToolComponentOverrides, ToolTagMap, } from "./tool-tag-map.js";
|
|
@@ -30,7 +30,7 @@ export { reportToolFailure } from "../services/tool-failure.js";
|
|
|
30
30
|
export { resolveFallbackToolIcon } from "../services/tool-icons.js";
|
|
31
31
|
export { resolveContentCapabilities } from "./content-capability-resolution.js";
|
|
32
32
|
export { createToolSurfaceHost } from "./tool-surface-host.js";
|
|
33
|
-
export {
|
|
33
|
+
export { elementSupports, hasMathContent, hasReadableText, hasSpokenContent, hasScienceContent, } from "../services/tool-context.js";
|
|
34
34
|
export { createScopedToolId, parseScopedToolId, } from "../services/tool-instance-id.js";
|
|
35
35
|
export { createToolElement, resolveToolTag } from "./tool-tag-map.js";
|
|
36
36
|
// Toolbar button/overlay wiring shared by every toolbar-toggle registration,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pie-players/pie-assessment-toolkit",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "PIE assessment toolkit: composable services + reference implementation for assessment players and tool coordination",
|
|
6
6
|
"license": "MIT",
|
|
@@ -69,17 +69,17 @@
|
|
|
69
69
|
"test": "bun test"
|
|
70
70
|
},
|
|
71
71
|
"dependencies": {
|
|
72
|
-
"@pie-players/pie-calculator": "1.0.0-beta.
|
|
73
|
-
"@pie-players/pie-context": "1.0.0-beta.
|
|
74
|
-
"@pie-players/pie-players-shared": "1.0.0-beta.
|
|
75
|
-
"@pie-players/pie-tts": "1.0.0-beta.
|
|
72
|
+
"@pie-players/pie-calculator": "1.0.0-beta.2",
|
|
73
|
+
"@pie-players/pie-context": "1.0.0-beta.2",
|
|
74
|
+
"@pie-players/pie-players-shared": "1.0.0-beta.2",
|
|
75
|
+
"@pie-players/pie-tts": "1.0.0-beta.2",
|
|
76
76
|
"speech-rule-engine": "5.0.0-rc.4"
|
|
77
77
|
},
|
|
78
78
|
"devDependencies": {
|
|
79
79
|
"@biomejs/biome": "^2.5.15",
|
|
80
80
|
"@happy-dom/global-registrator": "^20.14.5",
|
|
81
|
-
"@pie-players/tts-client-server": "1.0.0-beta.
|
|
82
|
-
"svelte": "^5.57.
|
|
81
|
+
"@pie-players/tts-client-server": "1.0.0-beta.2",
|
|
82
|
+
"svelte": "^5.57.2",
|
|
83
83
|
"svelte-check": "^4.7.6",
|
|
84
84
|
"typescript": "^5.9.3"
|
|
85
85
|
},
|