@pie-players/pie-assessment-toolkit 0.3.9 → 0.3.11
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 +5 -0
- package/dist/components/PieAssessmentToolkit.custom-element.js +142 -85
- package/dist/context/assessment-toolkit-context.d.ts +8 -8
- package/dist/context/assessment-toolkit-context.d.ts.map +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/services/HighlightCoordinator.d.ts +2 -2
- package/dist/services/HighlightCoordinator.d.ts.map +1 -1
- package/dist/services/HighlightCoordinator.js +1 -1
- package/dist/services/HighlightCoordinator.js.map +1 -1
- package/dist/services/I18nService.d.ts +3 -3
- package/dist/services/I18nService.d.ts.map +1 -1
- package/dist/services/PNPToolResolver.d.ts +7 -7
- package/dist/services/PNPToolResolver.js +6 -6
- package/dist/services/TTSService.d.ts +21 -9
- package/dist/services/TTSService.d.ts.map +1 -1
- package/dist/services/TTSService.js +172 -173
- package/dist/services/TTSService.js.map +1 -1
- package/dist/services/ThemeProvider.d.ts +2 -2
- package/dist/services/ThemeProvider.d.ts.map +1 -1
- package/dist/services/ToolCoordinator.d.ts +2 -2
- package/dist/services/ToolCoordinator.d.ts.map +1 -1
- package/dist/services/ToolRegistry.d.ts +7 -7
- package/dist/services/ToolRegistry.d.ts.map +1 -1
- package/dist/services/ToolkitCoordinator.d.ts +19 -3
- package/dist/services/ToolkitCoordinator.d.ts.map +1 -1
- package/dist/services/ToolkitCoordinator.js +228 -214
- package/dist/services/ToolkitCoordinator.js.map +1 -1
- package/dist/services/interfaces.d.ts +20 -20
- package/dist/services/interfaces.d.ts.map +1 -1
- package/dist/services/interfaces.js +1 -1
- package/dist/services/interfaces.js.map +1 -1
- package/dist/services/pnp-provenance.d.ts +7 -7
- package/dist/services/pnp-provenance.js +1 -1
- package/dist/services/tool-providers/DesmosToolProvider.d.ts +3 -3
- package/dist/services/tool-providers/DesmosToolProvider.d.ts.map +1 -1
- package/dist/services/tool-providers/DesmosToolProvider.js +1 -1
- package/dist/services/tool-providers/TTSToolProvider.d.ts +3 -3
- package/dist/services/tool-providers/TTSToolProvider.d.ts.map +1 -1
- package/dist/services/tool-providers/TTSToolProvider.js +1 -1
- package/dist/services/tool-providers/ToolProviderApi.d.ts +26 -0
- package/dist/services/tool-providers/ToolProviderApi.d.ts.map +1 -0
- package/dist/services/tool-providers/ToolProviderApi.js +8 -0
- package/dist/services/tool-providers/ToolProviderApi.js.map +1 -0
- package/dist/services/tool-providers/ToolProviderRegistry.d.ts +3 -3
- package/dist/services/tool-providers/ToolProviderRegistry.d.ts.map +1 -1
- package/dist/services/tool-providers/ToolProviderRegistry.js.map +1 -1
- package/dist/services/tool-providers/index.d.ts +1 -1
- package/dist/services/tool-providers/index.d.ts.map +1 -1
- package/dist/services/tts/browser-provider.d.ts.map +1 -1
- package/dist/services/tts/browser-provider.js +6 -37
- package/dist/services/tts/browser-provider.js.map +1 -1
- package/dist/services/tts/text-segmentation.d.ts +10 -0
- package/dist/services/tts/text-segmentation.d.ts.map +1 -0
- package/dist/services/tts/text-segmentation.js +38 -0
- package/dist/services/tts/text-segmentation.js.map +1 -0
- package/dist/services/tts-runtime-config.d.ts +31 -0
- package/dist/services/tts-runtime-config.d.ts.map +1 -0
- package/dist/services/tts-runtime-config.js +54 -0
- package/dist/services/tts-runtime-config.js.map +1 -0
- package/dist/tools/registrations/tts.d.ts.map +1 -1
- package/dist/tools/registrations/tts.js +6 -57
- package/dist/tools/registrations/tts.js.map +1 -1
- package/package.json +7 -7
- package/dist/services/tool-providers/IToolProvider.d.ts +0 -136
- package/dist/services/tool-providers/IToolProvider.d.ts.map +0 -1
- package/dist/services/tool-providers/IToolProvider.js +0 -12
- package/dist/services/tool-providers/IToolProvider.js.map +0 -1
|
@@ -21,6 +21,7 @@ import { HighlightCoordinator } from "./HighlightCoordinator.js";
|
|
|
21
21
|
import { ToolCoordinator } from "./ToolCoordinator.js";
|
|
22
22
|
import { TTSService } from "./TTSService.js";
|
|
23
23
|
import { BrowserTTSProvider } from "./tts/browser-provider.js";
|
|
24
|
+
import { buildRuntimeTTSConfig, resolveTTSBackend, } from "./tts-runtime-config.js";
|
|
24
25
|
import { ToolProviderRegistry } from "./tool-providers/index.js";
|
|
25
26
|
import { createPackagedToolRegistry } from "./createDefaultToolRegistry.js";
|
|
26
27
|
const SECTION_ITEM_EVENT_TYPES = [
|
|
@@ -376,9 +377,38 @@ export class ToolkitCoordinator {
|
|
|
376
377
|
return this.sectionControllers.get(this.getSectionControllerMapKey(key));
|
|
377
378
|
}
|
|
378
379
|
subscribeSectionEvents(args) {
|
|
379
|
-
|
|
380
|
+
const controllerEntry = this.resolveSectionSubscriptionEntry(args);
|
|
381
|
+
if (!controllerEntry)
|
|
382
|
+
return () => { };
|
|
383
|
+
const { mapKey, controller } = controllerEntry;
|
|
384
|
+
const subscribe = controller.subscribe;
|
|
385
|
+
if (!subscribe) {
|
|
386
|
+
const resolvedKey = this.sectionControllerKeys.get(mapKey);
|
|
387
|
+
const sectionLabel = resolvedKey?.sectionId || args.sectionId || "<unknown>";
|
|
388
|
+
const attemptLabel = resolvedKey?.attemptId || args.attemptId || "<default>";
|
|
389
|
+
throw new Error(`[ToolkitCoordinator] subscribeSectionEvents could not subscribe: resolved controller for section "${sectionLabel}" attempt "${attemptLabel}" does not expose subscribe().`);
|
|
390
|
+
}
|
|
391
|
+
const listenerId = this.getOrCreateSectionEventListenerId(args.listener);
|
|
392
|
+
const subscriptionKey = `${mapKey}::${listenerId}`;
|
|
393
|
+
const previousSubscription = this.sectionEventSubscriptions.get(subscriptionKey);
|
|
394
|
+
previousSubscription?.();
|
|
395
|
+
const shouldDeliverEvent = this.buildSectionEventPredicate(args);
|
|
396
|
+
const unsubscribeController = subscribe.call(controller, (event) => {
|
|
397
|
+
if (!shouldDeliverEvent(event))
|
|
398
|
+
return;
|
|
399
|
+
args.listener(event);
|
|
400
|
+
});
|
|
401
|
+
const replayEvent = this.buildLoadingCompleteReplayEvent(controller);
|
|
402
|
+
if (replayEvent && shouldDeliverEvent(replayEvent)) {
|
|
403
|
+
args.listener(replayEvent);
|
|
404
|
+
}
|
|
405
|
+
const detach = this.createSectionEventDetachHandler(subscriptionKey, unsubscribeController);
|
|
406
|
+
this.sectionEventSubscriptions.set(subscriptionKey, detach);
|
|
407
|
+
return detach;
|
|
408
|
+
}
|
|
409
|
+
resolveSectionSubscriptionEntry(args) {
|
|
380
410
|
try {
|
|
381
|
-
|
|
411
|
+
return this.resolveSectionControllerForSubscription({
|
|
382
412
|
sectionId: args.sectionId,
|
|
383
413
|
attemptId: args.attemptId,
|
|
384
414
|
});
|
|
@@ -390,55 +420,30 @@ export class ToolkitCoordinator {
|
|
|
390
420
|
message.includes("subscribeSectionEvents is ambiguous for section");
|
|
391
421
|
if (isAmbiguousSectionWithoutAttempt) {
|
|
392
422
|
console.warn(message);
|
|
393
|
-
return
|
|
423
|
+
return null;
|
|
394
424
|
}
|
|
395
425
|
throw error;
|
|
396
426
|
}
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
const resolvedKey = this.sectionControllerKeys.get(controllerEntry.mapKey);
|
|
401
|
-
const sectionLabel = resolvedKey?.sectionId || args.sectionId || "<unknown>";
|
|
402
|
-
const attemptLabel = resolvedKey?.attemptId || args.attemptId || "<default>";
|
|
403
|
-
throw new Error(`[ToolkitCoordinator] subscribeSectionEvents could not subscribe: resolved controller for section "${sectionLabel}" attempt "${attemptLabel}" does not expose subscribe().`);
|
|
404
|
-
}
|
|
405
|
-
const { mapKey } = controllerEntry;
|
|
406
|
-
let listenerId = this.sectionEventListenerIds.get(args.listener);
|
|
427
|
+
}
|
|
428
|
+
getOrCreateSectionEventListenerId(listener) {
|
|
429
|
+
let listenerId = this.sectionEventListenerIds.get(listener);
|
|
407
430
|
if (!listenerId) {
|
|
408
431
|
listenerId = this.nextSectionEventListenerId++;
|
|
409
|
-
this.sectionEventListenerIds.set(
|
|
432
|
+
this.sectionEventListenerIds.set(listener, listenerId);
|
|
410
433
|
}
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
const eventTypeFilter = args.eventTypes
|
|
415
|
-
? new Set(args.eventTypes)
|
|
416
|
-
: null;
|
|
434
|
+
return listenerId;
|
|
435
|
+
}
|
|
436
|
+
buildSectionEventPredicate(args) {
|
|
437
|
+
const eventTypeFilter = args.eventTypes ? new Set(args.eventTypes) : null;
|
|
417
438
|
const itemIdFilter = args.itemIds ? new Set(args.itemIds) : null;
|
|
418
|
-
|
|
439
|
+
return (event) => {
|
|
419
440
|
if (eventTypeFilter || itemIdFilter) {
|
|
420
441
|
const eventType = event?.type || null;
|
|
421
442
|
if (eventTypeFilter && (!eventType || !eventTypeFilter.has(eventType))) {
|
|
422
443
|
return false;
|
|
423
444
|
}
|
|
424
445
|
if (itemIdFilter) {
|
|
425
|
-
const
|
|
426
|
-
if ("itemId" in event && typeof event.itemId === "string") {
|
|
427
|
-
eventItemCandidates.add(event.itemId);
|
|
428
|
-
}
|
|
429
|
-
if ("canonicalItemId" in event &&
|
|
430
|
-
typeof event.canonicalItemId === "string") {
|
|
431
|
-
eventItemCandidates.add(event.canonicalItemId);
|
|
432
|
-
}
|
|
433
|
-
if ("currentItemId" in event &&
|
|
434
|
-
typeof event.currentItemId === "string") {
|
|
435
|
-
eventItemCandidates.add(event.currentItemId);
|
|
436
|
-
}
|
|
437
|
-
if ("previousItemId" in event &&
|
|
438
|
-
typeof event.previousItemId === "string") {
|
|
439
|
-
eventItemCandidates.add(event.previousItemId);
|
|
440
|
-
}
|
|
441
|
-
const hasMatchingItem = Array.from(eventItemCandidates).some((itemId) => itemIdFilter.has(itemId));
|
|
446
|
+
const hasMatchingItem = Array.from(this.collectEventItemIds(event)).some((itemId) => itemIdFilter.has(itemId));
|
|
442
447
|
if (!hasMatchingItem) {
|
|
443
448
|
return false;
|
|
444
449
|
}
|
|
@@ -446,26 +451,44 @@ export class ToolkitCoordinator {
|
|
|
446
451
|
}
|
|
447
452
|
return true;
|
|
448
453
|
};
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
if (
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
totalRegistered: runtimeState.totalRegistered,
|
|
461
|
-
totalLoaded: runtimeState.totalLoaded,
|
|
462
|
-
currentItemIndex: runtimeState.currentItemIndex,
|
|
463
|
-
timestamp: Date.now(),
|
|
464
|
-
};
|
|
465
|
-
if (shouldDeliverEvent(loadingCompleteEvent)) {
|
|
466
|
-
args.listener(loadingCompleteEvent);
|
|
467
|
-
}
|
|
454
|
+
}
|
|
455
|
+
collectEventItemIds(event) {
|
|
456
|
+
const itemIds = new Set();
|
|
457
|
+
if ("itemId" in event && typeof event.itemId === "string") {
|
|
458
|
+
itemIds.add(event.itemId);
|
|
459
|
+
}
|
|
460
|
+
if ("canonicalItemId" in event && typeof event.canonicalItemId === "string") {
|
|
461
|
+
itemIds.add(event.canonicalItemId);
|
|
462
|
+
}
|
|
463
|
+
if ("currentItemId" in event && typeof event.currentItemId === "string") {
|
|
464
|
+
itemIds.add(event.currentItemId);
|
|
468
465
|
}
|
|
466
|
+
if ("previousItemId" in event && typeof event.previousItemId === "string") {
|
|
467
|
+
itemIds.add(event.previousItemId);
|
|
468
|
+
}
|
|
469
|
+
return itemIds;
|
|
470
|
+
}
|
|
471
|
+
buildLoadingCompleteReplayEvent(controller) {
|
|
472
|
+
const runtimeState = controller.getRuntimeState?.();
|
|
473
|
+
if (runtimeState?.loadingComplete !== true)
|
|
474
|
+
return null;
|
|
475
|
+
const totalRegistered = typeof runtimeState.totalRegistered === "number" &&
|
|
476
|
+
Number.isFinite(runtimeState.totalRegistered)
|
|
477
|
+
? Math.max(0, runtimeState.totalRegistered)
|
|
478
|
+
: 0;
|
|
479
|
+
const totalLoaded = typeof runtimeState.totalLoaded === "number" &&
|
|
480
|
+
Number.isFinite(runtimeState.totalLoaded)
|
|
481
|
+
? Math.max(0, runtimeState.totalLoaded)
|
|
482
|
+
: totalRegistered;
|
|
483
|
+
return {
|
|
484
|
+
type: "section-loading-complete",
|
|
485
|
+
totalRegistered,
|
|
486
|
+
totalLoaded,
|
|
487
|
+
currentItemIndex: runtimeState.currentItemIndex,
|
|
488
|
+
timestamp: Date.now(),
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
createSectionEventDetachHandler(subscriptionKey, unsubscribeController) {
|
|
469
492
|
const detach = () => {
|
|
470
493
|
const current = this.sectionEventSubscriptions.get(subscriptionKey);
|
|
471
494
|
if (current !== detach) {
|
|
@@ -474,7 +497,6 @@ export class ToolkitCoordinator {
|
|
|
474
497
|
this.sectionEventSubscriptions.delete(subscriptionKey);
|
|
475
498
|
unsubscribeController();
|
|
476
499
|
};
|
|
477
|
-
this.sectionEventSubscriptions.set(subscriptionKey, detach);
|
|
478
500
|
return detach;
|
|
479
501
|
}
|
|
480
502
|
/**
|
|
@@ -574,67 +596,101 @@ export class ToolkitCoordinator {
|
|
|
574
596
|
attemptId: args.attemptId,
|
|
575
597
|
};
|
|
576
598
|
const mapKey = this.getSectionControllerMapKey(key);
|
|
577
|
-
const existingController = this.
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
await existingController.updateInput?.(args.input);
|
|
585
|
-
}
|
|
599
|
+
const existingController = await this.resolveExistingSectionController({
|
|
600
|
+
mapKey,
|
|
601
|
+
key,
|
|
602
|
+
input: args.input,
|
|
603
|
+
updateExisting: args.updateExisting,
|
|
604
|
+
});
|
|
605
|
+
if (existingController)
|
|
586
606
|
return existingController;
|
|
587
|
-
}
|
|
588
607
|
const existingPromise = this.sectionControllerInitPromises.get(mapKey);
|
|
589
608
|
if (existingPromise)
|
|
590
609
|
return existingPromise;
|
|
591
|
-
const initPromise = (
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
const defaults = {
|
|
599
|
-
createDefaultController: args.createDefaultController,
|
|
600
|
-
};
|
|
601
|
-
const controller = (await this.hooks.createSectionController?.(context, defaults)) ??
|
|
602
|
-
(await defaults.createDefaultController());
|
|
603
|
-
await controller.configureSessionPersistence?.({
|
|
604
|
-
strategy: persistence,
|
|
605
|
-
context,
|
|
606
|
-
});
|
|
607
|
-
await controller.initialize?.(args.input);
|
|
608
|
-
await controller.hydrate?.();
|
|
609
|
-
this.sectionControllers.set(mapKey, controller);
|
|
610
|
-
this.sectionControllerKeys.set(mapKey, key);
|
|
611
|
-
this.emitSectionControllerLifecycle({
|
|
612
|
-
type: "ready",
|
|
613
|
-
key,
|
|
614
|
-
controller,
|
|
615
|
-
});
|
|
616
|
-
await this.hooks.onSectionControllerReady?.(context, controller);
|
|
617
|
-
await this.emitTelemetry("section-controller-ready", {
|
|
618
|
-
assessmentId: key.assessmentId,
|
|
619
|
-
sectionId: key.sectionId,
|
|
620
|
-
attemptId: key.attemptId,
|
|
621
|
-
});
|
|
622
|
-
return controller;
|
|
623
|
-
})().catch((err) => {
|
|
624
|
-
this.handleError(err, {
|
|
625
|
-
phase: "section-controller-init",
|
|
626
|
-
details: {
|
|
627
|
-
sectionId: args.sectionId,
|
|
628
|
-
attemptId: args.attemptId,
|
|
629
|
-
},
|
|
630
|
-
});
|
|
610
|
+
const initPromise = this.initializeNewSectionController({
|
|
611
|
+
args,
|
|
612
|
+
key,
|
|
613
|
+
mapKey,
|
|
614
|
+
})
|
|
615
|
+
.catch((err) => {
|
|
616
|
+
this.handleSectionControllerInitError(err, args);
|
|
631
617
|
throw err;
|
|
632
|
-
})
|
|
618
|
+
})
|
|
619
|
+
.finally(() => {
|
|
633
620
|
this.sectionControllerInitPromises.delete(mapKey);
|
|
634
621
|
});
|
|
635
622
|
this.sectionControllerInitPromises.set(mapKey, initPromise);
|
|
636
623
|
return initPromise;
|
|
637
624
|
}
|
|
625
|
+
async resolveExistingSectionController(args) {
|
|
626
|
+
const existingController = this.sectionControllers.get(args.mapKey);
|
|
627
|
+
if (!existingController)
|
|
628
|
+
return undefined;
|
|
629
|
+
this.sectionControllerKeys.set(args.mapKey, args.key);
|
|
630
|
+
if (args.updateExisting !== false) {
|
|
631
|
+
// Existing controllers keep their in-memory session state across
|
|
632
|
+
// input refresh; updateInput should rebuild composition/runtime view
|
|
633
|
+
// without resetting responses.
|
|
634
|
+
await existingController.updateInput?.(args.input);
|
|
635
|
+
}
|
|
636
|
+
return existingController;
|
|
637
|
+
}
|
|
638
|
+
createSectionControllerContext(args) {
|
|
639
|
+
return {
|
|
640
|
+
key: args.key,
|
|
641
|
+
coordinator: this,
|
|
642
|
+
input: args.input,
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
async initializeNewSectionController(args) {
|
|
646
|
+
const context = this.createSectionControllerContext({
|
|
647
|
+
key: args.key,
|
|
648
|
+
input: args.args.input,
|
|
649
|
+
});
|
|
650
|
+
const persistence = await this.resolveSectionPersistence(context);
|
|
651
|
+
const defaults = {
|
|
652
|
+
createDefaultController: args.args.createDefaultController,
|
|
653
|
+
};
|
|
654
|
+
const controller = (await this.hooks.createSectionController?.(context, defaults)) ??
|
|
655
|
+
(await defaults.createDefaultController());
|
|
656
|
+
await controller.configureSessionPersistence?.({
|
|
657
|
+
strategy: persistence,
|
|
658
|
+
context,
|
|
659
|
+
});
|
|
660
|
+
await controller.initialize?.(args.args.input);
|
|
661
|
+
await controller.hydrate?.();
|
|
662
|
+
await this.finalizeSectionControllerReady({
|
|
663
|
+
mapKey: args.mapKey,
|
|
664
|
+
key: args.key,
|
|
665
|
+
context,
|
|
666
|
+
controller,
|
|
667
|
+
});
|
|
668
|
+
return controller;
|
|
669
|
+
}
|
|
670
|
+
async finalizeSectionControllerReady(args) {
|
|
671
|
+
this.sectionControllers.set(args.mapKey, args.controller);
|
|
672
|
+
this.sectionControllerKeys.set(args.mapKey, args.key);
|
|
673
|
+
this.emitSectionControllerLifecycle({
|
|
674
|
+
type: "ready",
|
|
675
|
+
key: args.key,
|
|
676
|
+
controller: args.controller,
|
|
677
|
+
});
|
|
678
|
+
await this.hooks.onSectionControllerReady?.(args.context, args.controller);
|
|
679
|
+
await this.emitTelemetry("section-controller-ready", {
|
|
680
|
+
assessmentId: args.key.assessmentId,
|
|
681
|
+
sectionId: args.key.sectionId,
|
|
682
|
+
attemptId: args.key.attemptId,
|
|
683
|
+
});
|
|
684
|
+
}
|
|
685
|
+
handleSectionControllerInitError(err, args) {
|
|
686
|
+
this.handleError(err, {
|
|
687
|
+
phase: "section-controller-init",
|
|
688
|
+
details: {
|
|
689
|
+
sectionId: args.sectionId,
|
|
690
|
+
attemptId: args.attemptId,
|
|
691
|
+
},
|
|
692
|
+
});
|
|
693
|
+
}
|
|
638
694
|
async disposeSectionController(args) {
|
|
639
695
|
const key = {
|
|
640
696
|
assessmentId: this.assessmentId,
|
|
@@ -645,26 +701,17 @@ export class ToolkitCoordinator {
|
|
|
645
701
|
const controller = this.sectionControllers.get(mapKey);
|
|
646
702
|
if (!controller)
|
|
647
703
|
return;
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
if (!subscriptionKey.startsWith(subscriptionPrefix))
|
|
651
|
-
continue;
|
|
652
|
-
this.sectionEventSubscriptions.get(subscriptionKey)?.();
|
|
653
|
-
}
|
|
654
|
-
const context = {
|
|
704
|
+
this.detachSectionEventSubscriptionsForMapKey(mapKey);
|
|
705
|
+
const context = this.createSectionControllerContext({
|
|
655
706
|
key,
|
|
656
|
-
|
|
657
|
-
};
|
|
707
|
+
input: undefined,
|
|
708
|
+
});
|
|
658
709
|
try {
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
await this.emitTelemetry("section-controller-disposed", {
|
|
665
|
-
assessmentId: key.assessmentId,
|
|
666
|
-
sectionId: key.sectionId,
|
|
667
|
-
attemptId: key.attemptId,
|
|
710
|
+
await this.runSectionControllerDisposePipeline({
|
|
711
|
+
key,
|
|
712
|
+
context,
|
|
713
|
+
controller,
|
|
714
|
+
persistBeforeDispose: args.persistBeforeDispose,
|
|
668
715
|
});
|
|
669
716
|
}
|
|
670
717
|
catch (err) {
|
|
@@ -677,19 +724,47 @@ export class ToolkitCoordinator {
|
|
|
677
724
|
});
|
|
678
725
|
}
|
|
679
726
|
finally {
|
|
680
|
-
this.
|
|
681
|
-
|
|
682
|
-
this.emitSectionControllerLifecycle({
|
|
683
|
-
type: "disposed",
|
|
727
|
+
await this.finalizeSectionControllerDispose({
|
|
728
|
+
mapKey,
|
|
684
729
|
key,
|
|
730
|
+
context,
|
|
731
|
+
clearPersistence: args.clearPersistence,
|
|
685
732
|
});
|
|
686
|
-
if (args.clearPersistence) {
|
|
687
|
-
const strategy = this.sectionPersistenceStrategies.get(mapKey);
|
|
688
|
-
await strategy?.clearSession?.(context);
|
|
689
|
-
}
|
|
690
|
-
this.sectionPersistenceStrategies.delete(mapKey);
|
|
691
733
|
}
|
|
692
734
|
}
|
|
735
|
+
detachSectionEventSubscriptionsForMapKey(mapKey) {
|
|
736
|
+
const subscriptionPrefix = `${mapKey}::`;
|
|
737
|
+
for (const subscriptionKey of Array.from(this.sectionEventSubscriptions.keys())) {
|
|
738
|
+
if (!subscriptionKey.startsWith(subscriptionPrefix))
|
|
739
|
+
continue;
|
|
740
|
+
this.sectionEventSubscriptions.get(subscriptionKey)?.();
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
async runSectionControllerDisposePipeline(args) {
|
|
744
|
+
if (args.persistBeforeDispose !== false) {
|
|
745
|
+
await args.controller.persist?.();
|
|
746
|
+
}
|
|
747
|
+
await args.controller.dispose?.();
|
|
748
|
+
await this.hooks.onSectionControllerDispose?.(args.context, args.controller);
|
|
749
|
+
await this.emitTelemetry("section-controller-disposed", {
|
|
750
|
+
assessmentId: args.key.assessmentId,
|
|
751
|
+
sectionId: args.key.sectionId,
|
|
752
|
+
attemptId: args.key.attemptId,
|
|
753
|
+
});
|
|
754
|
+
}
|
|
755
|
+
async finalizeSectionControllerDispose(args) {
|
|
756
|
+
this.sectionControllers.delete(args.mapKey);
|
|
757
|
+
this.sectionControllerKeys.delete(args.mapKey);
|
|
758
|
+
this.emitSectionControllerLifecycle({
|
|
759
|
+
type: "disposed",
|
|
760
|
+
key: args.key,
|
|
761
|
+
});
|
|
762
|
+
if (args.clearPersistence) {
|
|
763
|
+
const strategy = this.sectionPersistenceStrategies.get(args.mapKey);
|
|
764
|
+
await strategy?.clearSession?.(args.context);
|
|
765
|
+
}
|
|
766
|
+
this.sectionPersistenceStrategies.delete(args.mapKey);
|
|
767
|
+
}
|
|
693
768
|
/**
|
|
694
769
|
* Initialize TTS service with provider
|
|
695
770
|
*/
|
|
@@ -706,92 +781,24 @@ export class ToolkitCoordinator {
|
|
|
706
781
|
async _initializeTTS(config) {
|
|
707
782
|
if (this.ttsInitialized)
|
|
708
783
|
return;
|
|
784
|
+
const resolvedToolConfig = this.resolveTTSToolConfig(config);
|
|
785
|
+
const resolvedBackend = resolveTTSBackend(resolvedToolConfig);
|
|
786
|
+
const runtimeTTSConfig = buildRuntimeTTSConfig(resolvedToolConfig);
|
|
709
787
|
await this.hooks.onBeforeTTSInit?.({
|
|
710
788
|
phase: "tts-init",
|
|
711
789
|
details: {
|
|
712
|
-
backend:
|
|
790
|
+
backend: resolvedBackend,
|
|
713
791
|
},
|
|
714
792
|
});
|
|
715
793
|
await this.emitTelemetry("tts-init-start", {
|
|
716
|
-
backend:
|
|
717
|
-
this.config.tools?.providers?.tts
|
|
718
|
-
?.backend ??
|
|
719
|
-
"browser",
|
|
794
|
+
backend: resolvedBackend,
|
|
720
795
|
});
|
|
721
|
-
const resolvedToolConfig = config ||
|
|
722
|
-
(this.config.tools?.providers?.tts || {});
|
|
723
|
-
const resolvedBackend = resolvedToolConfig.backend || "browser";
|
|
724
|
-
const runtimeProvider = resolvedToolConfig.serverProvider ||
|
|
725
|
-
resolvedToolConfig.provider ||
|
|
726
|
-
(resolvedBackend === "polly" || resolvedBackend === "google"
|
|
727
|
-
? resolvedBackend
|
|
728
|
-
: undefined);
|
|
729
|
-
const transportMode = resolvedToolConfig.transportMode ||
|
|
730
|
-
(runtimeProvider === "custom"
|
|
731
|
-
? "custom"
|
|
732
|
-
: "pie");
|
|
733
|
-
const runtimeTTSConfig = {
|
|
734
|
-
voice: resolvedToolConfig.defaultVoice,
|
|
735
|
-
rate: resolvedToolConfig.rate,
|
|
736
|
-
pitch: resolvedToolConfig.pitch,
|
|
737
|
-
providerOptions: {
|
|
738
|
-
...(resolvedToolConfig.language
|
|
739
|
-
? {
|
|
740
|
-
locale: resolvedToolConfig.language,
|
|
741
|
-
}
|
|
742
|
-
: {}),
|
|
743
|
-
...(resolvedBackend === "polly" && resolvedToolConfig.engine
|
|
744
|
-
? {
|
|
745
|
-
engine: resolvedToolConfig.engine,
|
|
746
|
-
}
|
|
747
|
-
: {}),
|
|
748
|
-
...(resolvedBackend === "polly" &&
|
|
749
|
-
typeof resolvedToolConfig.sampleRate === "number"
|
|
750
|
-
? {
|
|
751
|
-
sampleRate: resolvedToolConfig.sampleRate,
|
|
752
|
-
}
|
|
753
|
-
: {}),
|
|
754
|
-
...(resolvedBackend === "polly" && resolvedToolConfig.format
|
|
755
|
-
? {
|
|
756
|
-
format: resolvedToolConfig.format,
|
|
757
|
-
}
|
|
758
|
-
: {}),
|
|
759
|
-
...(resolvedBackend === "polly"
|
|
760
|
-
? {
|
|
761
|
-
speechMarkTypes: resolvedToolConfig.speechMarksMode === "word+sentence"
|
|
762
|
-
? ["word", "sentence"]
|
|
763
|
-
: ["word"],
|
|
764
|
-
}
|
|
765
|
-
: {}),
|
|
766
|
-
...(transportMode === "custom" &&
|
|
767
|
-
typeof resolvedToolConfig.cache === "boolean"
|
|
768
|
-
? { cache: resolvedToolConfig.cache }
|
|
769
|
-
: {}),
|
|
770
|
-
...(transportMode === "custom" && resolvedToolConfig.speedRate
|
|
771
|
-
? { speedRate: resolvedToolConfig.speedRate }
|
|
772
|
-
: {}),
|
|
773
|
-
...(transportMode === "custom" && resolvedToolConfig.lang_id
|
|
774
|
-
? { lang_id: resolvedToolConfig.lang_id }
|
|
775
|
-
: {}),
|
|
776
|
-
},
|
|
777
|
-
apiEndpoint: resolvedToolConfig.apiEndpoint,
|
|
778
|
-
provider: runtimeProvider,
|
|
779
|
-
language: resolvedToolConfig.language,
|
|
780
|
-
transportMode,
|
|
781
|
-
endpointMode: resolvedToolConfig.endpointMode,
|
|
782
|
-
endpointValidationMode: resolvedToolConfig.endpointValidationMode,
|
|
783
|
-
includeAuthOnAssetFetch: resolvedToolConfig.includeAuthOnAssetFetch,
|
|
784
|
-
validateEndpoint: resolvedToolConfig.validateEndpoint,
|
|
785
|
-
};
|
|
786
796
|
// Try to use TTS provider from registry if available
|
|
787
797
|
if (this.toolProviderRegistry.has("tts")) {
|
|
788
798
|
try {
|
|
789
799
|
const ttsProvider = await this.ensureProviderReady("tts");
|
|
790
800
|
const providerInstance = await ttsProvider.createInstance();
|
|
791
|
-
await this.
|
|
792
|
-
this.ttsService.setCatalogResolver(this.catalogResolver);
|
|
793
|
-
this.ttsInitialized = true;
|
|
794
|
-
await this.hooks.onTTSReady?.();
|
|
801
|
+
await this.initializeTTSService(providerInstance, runtimeTTSConfig);
|
|
795
802
|
await this.emitTelemetry("tts-init-success", {
|
|
796
803
|
provider: "registry",
|
|
797
804
|
});
|
|
@@ -805,10 +812,7 @@ export class ToolkitCoordinator {
|
|
|
805
812
|
// Fallback to browser provider
|
|
806
813
|
const provider = new BrowserTTSProvider();
|
|
807
814
|
try {
|
|
808
|
-
await this.
|
|
809
|
-
this.ttsService.setCatalogResolver(this.catalogResolver);
|
|
810
|
-
this.ttsInitialized = true;
|
|
811
|
-
await this.hooks.onTTSReady?.();
|
|
815
|
+
await this.initializeTTSService(provider, runtimeTTSConfig);
|
|
812
816
|
await this.emitTelemetry("tts-init-success", {
|
|
813
817
|
provider: "browser-fallback",
|
|
814
818
|
});
|
|
@@ -823,6 +827,16 @@ export class ToolkitCoordinator {
|
|
|
823
827
|
throw normalized;
|
|
824
828
|
}
|
|
825
829
|
}
|
|
830
|
+
resolveTTSToolConfig(config) {
|
|
831
|
+
return (config ||
|
|
832
|
+
(this.config.tools?.providers?.tts || {}));
|
|
833
|
+
}
|
|
834
|
+
async initializeTTSService(provider, config) {
|
|
835
|
+
await this.ttsService.initialize(provider, config);
|
|
836
|
+
this.ttsService.setCatalogResolver(this.catalogResolver);
|
|
837
|
+
this.ttsInitialized = true;
|
|
838
|
+
await this.hooks.onTTSReady?.();
|
|
839
|
+
}
|
|
826
840
|
/**
|
|
827
841
|
* Get all services as a bundle for section player convenience.
|
|
828
842
|
* Section player can extract and pass services to child components.
|