@pie-players/pie-assessment-toolkit 0.3.49 → 0.3.51
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 +25 -0
- package/dist/components/ItemToolBar.custom-element.js +99 -30
- package/dist/components/PieAssessmentToolkit.custom-element.js +120 -30
- package/dist/components/SectionToolBar.custom-element.js +99 -28
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/services/TTSService.d.ts +4 -0
- package/dist/services/TTSService.js +54 -20
- package/dist/services/TTSService.js.map +1 -1
- package/dist/services/ToolkitCoordinator.d.ts +3 -2
- package/dist/services/ToolkitCoordinator.js.map +1 -1
- package/dist/services/interfaces.d.ts +4 -0
- package/dist/services/interfaces.js.map +1 -1
- package/dist/services/tts-runtime-config.d.ts +15 -1
- package/dist/services/tts-runtime-config.js +76 -10
- package/dist/services/tts-runtime-config.js.map +1 -1
- package/dist/tools/registrations/tts.js +6 -8
- package/dist/tools/registrations/tts.js.map +1 -1
- package/package.json +7 -7
- package/dist/tools/default-tool-module-loaders.d.ts +0 -2
- package/dist/tools/default-tool-module-loaders.js +0 -28
- package/dist/tools/default-tool-module-loaders.js.map +0 -1
|
@@ -243,6 +243,10 @@ export interface TtsServiceApi {
|
|
|
243
243
|
* Update TTS settings dynamically (rate, pitch, voice)
|
|
244
244
|
*/
|
|
245
245
|
updateSettings(settings: Partial<TTSConfig>): Promise<void>;
|
|
246
|
+
/**
|
|
247
|
+
* Update playback speed and apply it to active planned playback.
|
|
248
|
+
*/
|
|
249
|
+
setPlaybackRate(rate: number): Promise<void>;
|
|
246
250
|
/**
|
|
247
251
|
* Set highlight coordinator for word highlighting
|
|
248
252
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"interfaces.js","sourceRoot":"","sources":["../../src/services/interfaces.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;;AA60BH,0EAA0E","sourcesContent":["/**\n * Service Interfaces\n *\n * Public interfaces for all core assessment toolkit services.\n * These interfaces define the contracts that service implementations must fulfill,\n * enabling dependency injection, testing, and custom implementations.\n *\n * Part of PIE Assessment Toolkit.\n */\n\nimport type { I18nServiceApi } from \"@pie-players/pie-players-shared/i18n\";\nimport type {\n\tAccessibilityCatalogResolver,\n\tCatalogLookupContext,\n\tCatalogLookupOptions,\n\tCatalogOwnerContext,\n\tCatalogStatistics,\n\tCatalogType,\n\tResolvedCatalog,\n} from \"./AccessibilityCatalogResolver.js\";\nimport type { FrameworkErrorListener } from \"./framework-error-bus.js\";\nimport type { HighlightColor, HighlightType } from \"./HighlightCoordinator.js\";\nimport type {\n\tSectionControllerHandle,\n\tSectionItemEventSubscriptionArgs,\n\tSectionScopedEventSubscriptionArgs,\n\tSectionEventSubscriptionArgs,\n\tToolkitCoordinatorHooks,\n\tToolkitInitStatus,\n} from \"./ToolkitCoordinator.js\";\nimport type { FontSize, ThemeConfig } from \"./ThemeProvider.js\";\nimport type { ZIndexLayer } from \"./ToolCoordinator.js\";\nimport type { PlaybackState, TTSConfig } from \"./TTSService.js\";\nimport type {\n\tToolPlacementConfig,\n\tToolPlacementLevel,\n\tToolProviderConfig,\n} from \"./tools-config-normalizer.js\";\nimport type { ToolProviderRegistry } from \"./tool-providers/ToolProviderRegistry.js\";\nimport type {\n\tPolicySource,\n\tPnpEnforcementMode,\n\tResolvedEngineInputs,\n\tToolPolicyChangeListener,\n\tToolPolicyDecision,\n\tToolPolicyDecisionRequest,\n} from \"../policy/engine.js\";\nimport type {\n\tAssessmentEntity,\n\tAssessmentItemRef,\n} from \"@pie-players/pie-players-shared/types\";\nimport type {\n\tITTSProvider,\n\tTTSProviderCapabilities,\n} from \"@pie-players/pie-tts\";\nimport type {\n\tResolvedToolContext,\n\tToolContextResolver,\n\tToolContextResolverContext,\n\tToolContextResolverMap,\n} from \"./ToolRegistry.js\";\n\n// Re-export I18nServiceApi from players-shared\nexport type { I18nServiceApi };\n\n/**\n * Theme provider interface\n *\n * Applies consistent accessibility theming across items and tools.\n */\nexport interface ThemeProviderApi {\n\t/**\n\t * Apply theme configuration\n\t */\n\tapplyTheme(config: ThemeConfig): void;\n\n\t/**\n\t * Get current theme configuration\n\t */\n\tgetCurrentTheme(): Required<ThemeConfig>;\n\n\t/**\n\t * Reset to default theme\n\t */\n\treset(): void;\n\n\t/**\n\t * Clean up and remove theme styles\n\t */\n\tdestroy(): void;\n}\n\n/**\n * Highlight coordinator interface\n *\n * Manages content highlighting for TTS, annotations, and selections.\n */\nexport interface HighlightCoordinatorApi {\n\t/**\n\t * Highlight a text range\n\t */\n\thighlightRange(\n\t\trange: Range,\n\t\ttype: HighlightType,\n\t\tcolor: HighlightColor,\n\t): void;\n\n\t/**\n\t * Highlight a word for TTS (temporary)\n\t */\n\thighlightTTSWord(\n\t\ttextNode: Text,\n\t\tstartOffset: number,\n\t\tendOffset: number,\n\t): void;\n\n\t/**\n\t * Highlight a single element as the active TTS word (temporary).\n\t *\n\t * Marks exactly the supplied element (via `data-pie-tts-word-element`)\n\t * without walking up to a containing ancestor. Used for atomic targets that\n\t * have no direct text node to range over — most notably MathJax CHTML tokens\n\t * (e.g. `<mjx-mi><mjx-c/></mjx-mi>`) and whole-expression fallbacks. Painting\n\t * the element itself is what lets a resolved math token highlight as a token\n\t * rather than escalating to the full `<math>` / `<mjx-container>`.\n\t *\n\t * Optional so lightweight coordinator mocks can omit it.\n\t */\n\thighlightTTSWordElement?(element: Element): void;\n\n\t/**\n\t * Highlight sentence(s) for TTS (background layer)\n\t */\n\thighlightTTSSentence(ranges: Range[]): void;\n\n\t/**\n\t * Clear all TTS highlights (word and sentence)\n\t */\n\tclearTTS(): void;\n\n\t/**\n\t * Clear highlights of a specific type\n\t */\n\tclearHighlights(type: HighlightType): void;\n\n\t/**\n\t * Clear all highlights\n\t */\n\tclearAll(): void;\n\n\t/**\n\t * Check if highlighting is supported in current environment\n\t */\n\tisSupported(): boolean;\n\n\t/**\n\t * Update TTS highlight style dynamically\n\t */\n\tupdateTTSHighlightStyle(color: string, opacity: number): void;\n}\n\n/**\n * Tool state interface\n */\nexport interface ToolState {\n\tid: string;\n\tname: string;\n\tisVisible: boolean;\n\telement: HTMLElement | null;\n\tlayer: ZIndexLayer;\n}\n\n/**\n * Tool coordinator interface\n *\n * Manages z-index layering and visibility for floating tools.\n */\nexport interface ToolCoordinatorApi {\n\t/**\n\t * Register a tool with the coordinator\n\t */\n\tregisterTool(\n\t\tid: string,\n\t\tname: string,\n\t\telement?: HTMLElement,\n\t\tlayer?: ZIndexLayer,\n\t): void;\n\n\t/**\n\t * Unregister a tool\n\t */\n\tunregisterTool(id: string): void;\n\n\t/**\n\t * Show a tool\n\t */\n\tshowTool(id: string): void;\n\n\t/**\n\t * Hide a tool\n\t */\n\thideTool(id: string): void;\n\n\t/**\n\t * Toggle tool visibility\n\t */\n\ttoggleTool(id: string): void;\n\n\t/**\n\t * Check if tool is visible\n\t */\n\tisToolVisible(id: string): boolean;\n\n\t/**\n\t * Bring an element to the front of its layer\n\t */\n\tbringToFront(element: HTMLElement): void;\n\n\t/**\n\t * Update the element reference for a tool\n\t */\n\tupdateToolElement(id: string, element: HTMLElement): void;\n\n\t/**\n\t * Get state for a specific tool\n\t */\n\tgetToolState(id: string): ToolState | undefined;\n\n\t/**\n\t * Get all visible tools\n\t */\n\tgetVisibleTools(): ToolState[];\n\n\t/**\n\t * Subscribe to tool state changes\n\t */\n\tsubscribe(listener: () => void): () => void;\n}\n\n/**\n * TTS service interface\n *\n * Provides text-to-speech functionality with provider-based architecture.\n * Supports QTI 3.0 accessibility catalogs for pre-authored spoken content.\n */\nexport interface TtsServiceApi {\n\t/**\n\t * Initialize TTS with a provider\n\t */\n\tinitialize(\n\t\tprovider: ITTSProvider,\n\t\tconfig?: Partial<TTSConfig>,\n\t): Promise<void>;\n\n\t/**\n\t * Speak text with optional catalog support\n\t */\n\tspeak(\n\t\ttext: string,\n\t\toptions?: {\n\t\t\tcatalogId?: string;\n\t\t\tcatalogContext?: CatalogLookupContext;\n\t\t\tlanguage?: string;\n\t\t\tcontentElement?: Element;\n\t\t},\n\t): Promise<void>;\n\n\t/**\n\t * Speak a text range\n\t */\n\tspeakRange(\n\t\trange: Range,\n\t\toptions?: { contentRoot?: Element | null },\n\t): Promise<void>;\n\n\t/**\n\t * Pause playback\n\t */\n\tpause(): void;\n\n\t/**\n\t * Resume playback\n\t */\n\tresume(): void;\n\n\t/**\n\t * Stop playback\n\t */\n\tstop(): void;\n\n\t/**\n\t * Request active TTS controls to hand off/deactivate their UI state.\n\t *\n\t * This is an orchestration hint for TTS tool chrome and does not replace\n\t * playback controls such as stop/pause/resume.\n\t */\n\trequestControlHandoff(): void;\n\n\t/**\n\t * Seek forward by sentence units\n\t */\n\tseekForward(units?: number): Promise<void>;\n\n\t/**\n\t * Seek backward by sentence units\n\t */\n\tseekBackward(units?: number): Promise<void>;\n\n\t/**\n\t * Check if currently playing\n\t */\n\tisPlaying(): boolean;\n\n\t/**\n\t * Check if paused\n\t */\n\tisPaused(): boolean;\n\n\t/**\n\t * Get current playback state\n\t */\n\tgetState(): PlaybackState;\n\n\t/**\n\t * Get currently speaking text\n\t */\n\tgetCurrentText(): string | null;\n\n\t/**\n\t * Subscribe to state changes\n\t */\n\tonStateChange(id: string, callback: (state: PlaybackState) => void): void;\n\n\t/**\n\t * Unsubscribe from state changes\n\t */\n\toffStateChange(id: string, callback: (state: PlaybackState) => void): void;\n\n\t/**\n\t * Get capabilities of current provider\n\t */\n\tgetCapabilities(): TTSProviderCapabilities | null;\n\n\t/**\n\t * Update TTS settings dynamically (rate, pitch, voice)\n\t */\n\tupdateSettings(settings: Partial<TTSConfig>): Promise<void>;\n\n\t/**\n\t * Set highlight coordinator for word highlighting\n\t */\n\tsetHighlightCoordinator(coordinator: HighlightCoordinatorApi): void;\n\n\t/**\n\t * Set accessibility catalog resolver for spoken content\n\t */\n\tsetCatalogResolver(resolver: AccessibilityCatalogResolver): void;\n}\n\n/**\n * Accessibility catalog resolver interface\n *\n * Manages QTI 3.0 accessibility catalogs at assessment and item levels.\n * Provides lookup and resolution services for alternative content representations.\n */\nexport interface AccessibilityCatalogResolverApi {\n\t/**\n\t * Set the default language for fallback resolution\n\t */\n\tsetDefaultLanguage(language: string): void;\n\n\t/**\n\t * Get the default language\n\t */\n\tgetDefaultLanguage(): string;\n\n\t/**\n\t * Add item-level catalogs (called when rendering a new item)\n\t */\n\taddItemCatalogs(catalogs: any[]): void;\n\n\t/**\n\t * Register catalogs scoped to a mounted content owner.\n\t */\n\tregisterCatalogs?(context: CatalogOwnerContext, catalogs: any[]): () => void;\n\n\t/**\n\t * Clear item-level catalogs (called when leaving an item)\n\t */\n\tclearItemCatalogs(): void;\n\n\t/**\n\t * Check if a catalog exists\n\t */\n\thasCatalog(catalogId: string): boolean;\n\n\t/**\n\t * Get alternative content for a catalog identifier\n\t */\n\tgetAlternative(\n\t\tcatalogId: string,\n\t\toptions: CatalogLookupOptions,\n\t): ResolvedCatalog | null;\n\n\t/**\n\t * Get all available alternatives for a catalog identifier\n\t */\n\tgetAllAlternatives(catalogId: string): ResolvedCatalog[];\n\n\t/**\n\t * Get all catalog identifiers available\n\t */\n\tgetAllCatalogIds(): string[];\n\n\t/**\n\t * Get statistics about available catalogs\n\t */\n\tgetStatistics(): CatalogStatistics;\n\n\t/**\n\t * Check if a specific catalog type is available\n\t */\n\thasAlternativeType(catalogId: string, type: CatalogType): boolean;\n\n\t/**\n\t * Get all catalog IDs that have a specific type of alternative\n\t */\n\tgetCatalogsByType(type: CatalogType): string[];\n\n\t/**\n\t * Reset all catalogs\n\t */\n\treset(): void;\n\n\t/**\n\t * Destroy and cleanup\n\t */\n\tdestroy(): void;\n}\n\n/**\n * Element tool state store interface\n *\n * Manages element-level ephemeral tool state using composite keys for global uniqueness.\n * Tool state is client-only and separate from PIE session data (which is sent to server for scoring).\n */\nexport interface ElementToolStateStoreApi {\n\t/**\n\t * Generate a globally unique element ID from components\n\t */\n\tgetGlobalElementId(\n\t\tassessmentId: string,\n\t\tsectionId: string,\n\t\titemId: string,\n\t\telementId: string,\n\t): string;\n\n\t/**\n\t * Parse a global element ID into its components\n\t */\n\tparseGlobalElementId(globalElementId: string): {\n\t\tassessmentId: string;\n\t\tsectionId: string;\n\t\titemId: string;\n\t\telementId: string;\n\t} | null;\n\n\t/**\n\t * Set state for a specific tool on an element\n\t */\n\tsetState(globalElementId: string, toolId: string, state: any): void;\n\n\t/**\n\t * Get state for a specific tool on an element\n\t */\n\tgetState(globalElementId: string, toolId: string): any | undefined;\n\n\t/**\n\t * Get all tool states for a specific element\n\t */\n\tgetElementState(globalElementId: string): Record<string, any>;\n\n\t/**\n\t * Get all element states\n\t */\n\tgetAllState(): Record<string, Record<string, any>>;\n\n\t/**\n\t * Subscribe to state changes\n\t */\n\tsubscribe(\n\t\tcallback: (state: Map<string, Map<string, any>>) => void,\n\t): () => void;\n\n\t/**\n\t * Set callback for persistence integration\n\t */\n\tsetOnStateChange(\n\t\tcallback: (state: Record<string, Record<string, any>>) => void,\n\t): void;\n\n\t/**\n\t * Load state from persistence\n\t */\n\tloadState(state: Record<string, Record<string, any>>): void;\n\n\t/**\n\t * Clear state for a specific element\n\t */\n\tclearElement(globalElementId: string): void;\n\n\t/**\n\t * Clear state for a specific tool across all elements\n\t */\n\tclearTool(toolId: string): void;\n\n\t/**\n\t * Clear all elements in a specific section\n\t */\n\tclearSection(assessmentId: string, sectionId: string): void;\n\n\t/**\n\t * Clear all state\n\t */\n\tclearAll(): void;\n}\n\n/**\n * Toolkit coordinator interface\n *\n * Orchestrates all toolkit services (TTS, tools, accessibility, state management) from a single entry point.\n * Provides centralized configuration for tool availability and settings.\n */\nexport interface ToolkitCoordinatorApi {\n\t/**\n\t * Assessment identifier\n\t */\n\treadonly assessmentId: string;\n\n\t/**\n\t * Configuration\n\t */\n\treadonly config: {\n\t\ttools?: {\n\t\t\tproviders?: Record<string, ToolProviderConfig | undefined>;\n\t\t};\n\t};\n\n\t/**\n\t * TTS service\n\t */\n\treadonly ttsService: TtsServiceApi;\n\n\t/**\n\t * Tool coordinator\n\t */\n\treadonly toolCoordinator: ToolCoordinatorApi;\n\n\t/**\n\t * Highlight coordinator\n\t */\n\treadonly highlightCoordinator: HighlightCoordinatorApi;\n\n\t/**\n\t * Element tool state store\n\t */\n\treadonly elementToolStateStore: ElementToolStateStoreApi;\n\n\t/**\n\t * Catalog resolver\n\t */\n\treadonly catalogResolver: AccessibilityCatalogResolverApi;\n\n\t/**\n\t * Tool provider registry\n\t */\n\treadonly toolProviderRegistry: ToolProviderRegistry;\n\n\t/**\n\t * Get all services as a bundle\n\t */\n\tgetServiceBundle(): {\n\t\tttsService: TtsServiceApi;\n\t\ttoolCoordinator: ToolCoordinatorApi;\n\t\thighlightCoordinator: HighlightCoordinatorApi;\n\t\telementToolStateStore: ElementToolStateStoreApi;\n\t\tcatalogResolver: AccessibilityCatalogResolverApi;\n\t\ttoolProviderRegistry: ToolProviderRegistry;\n\t};\n\n\t/**\n\t * Ensure TTS service is initialized and ready.\n\t */\n\tensureTTSReady(config?: Record<string, unknown>): Promise<void>;\n\n\t/**\n\t * Ensure a provider is initialized and ready.\n\t */\n\tensureProviderReady(providerId: string): Promise<unknown>;\n\n\t/**\n\t * Wait until coordinator initialization is complete.\n\t */\n\twaitUntilReady(): Promise<void>;\n\n\t/**\n\t * Check if coordinator has completed initialization.\n\t */\n\tisReady(): boolean;\n\n\t/**\n\t * Read current initialization status.\n\t */\n\tgetInitStatus(): ToolkitInitStatus;\n\n\t/**\n\t * Check if a tool is enabled\n\t */\n\tisToolEnabled(toolId: string): boolean;\n\n\t/**\n\t * Get tool configuration\n\t */\n\tgetToolConfig(toolId: string): ToolProviderConfig | null;\n\n\t/**\n\t * Update tool configuration\n\t */\n\tupdateToolConfig(toolId: string, updates: Partial<ToolProviderConfig>): void;\n\n\t/**\n\t * Update the enabled tool list for one placement level.\n\t */\n\tupdateToolPlacement(level: ToolPlacementLevel, toolIds: string[]): void;\n\n\t/**\n\t * Patch one or more placement levels in the canonical tools config.\n\t */\n\tupdateToolsPlacement(partial: ToolPlacementConfig): void;\n\n\t/**\n\t * Register or update lifecycle hooks at runtime.\n\t */\n\tsetHooks(hooks: ToolkitCoordinatorHooks): void;\n\n\t/**\n\t * Return a section controller if already created.\n\t */\n\tgetSectionController(args: {\n\t\tsectionId: string;\n\t\tattemptId?: string;\n\t}): SectionControllerHandle | undefined;\n\n\t/**\n\t * Subscribe to section controller events.\n\t *\n\t * The listener is bound to the toolkit's *active section cohort* and\n\t * automatically migrates across cohort transitions\n\t * (`getOrCreateSectionController` for a different cohort). On every\n\t * migration the listener receives a snapshot replay of the new\n\t * cohort's already-loaded `content-loaded` events followed by the\n\t * aggregate `section-loading-complete`, in the canonical order a\n\t * fresh subscriber would have observed.\n\t *\n\t * Throws if no active section cohort exists; host code must call\n\t * `getOrCreateSectionController(...)` at least once before\n\t * subscribing. (`toolkit-ready` alone is not sufficient — it fires\n\t * once toolkit state has loaded but before any section controller\n\t * has been created.) The typical pattern is to subscribe once\n\t * immediately after the first `getOrCreateSectionController(...)`\n\t * resolves; the subscription then follows the active cohort across\n\t * all subsequent navigation without further wiring.\n\t *\n\t * Subscribing the same `listener` reference twice replaces the prior\n\t * subscription with the new one (filter args from the second call\n\t * win); calling the returned disposer twice is a no-op.\n\t *\n\t * A listener that throws is caught and `console.warn`-logged; the\n\t * throw does not interrupt fan-out to the remaining listeners.\n\t */\n\tsubscribeSectionEvents(args: SectionEventSubscriptionArgs): () => void;\n\n\t/**\n\t * Subscribe to item-scoped section controller events.\n\t *\n\t * Same active-cohort binding contract as {@link subscribeSectionEvents};\n\t * defaults `eventTypes` to the item-scoped subset.\n\t */\n\tsubscribeItemEvents(args: SectionItemEventSubscriptionArgs): () => void;\n\n\t/**\n\t * Subscribe to section-scoped lifecycle/loading/completion/error events.\n\t *\n\t * Same active-cohort binding contract as {@link subscribeSectionEvents};\n\t * defaults `eventTypes` to the section-scoped subset.\n\t */\n\tsubscribeSectionLifecycleEvents(\n\t\targs: SectionScopedEventSubscriptionArgs,\n\t): () => void;\n\n\t/**\n\t * Create or reuse a section controller with single-flight deduplication.\n\t */\n\tgetOrCreateSectionController(args: {\n\t\tsectionId: string;\n\t\tattemptId?: string;\n\t\tinput?: unknown;\n\t\tupdateExisting?: boolean;\n\t\tcreateDefaultController: () =>\n\t\t\t| SectionControllerHandle\n\t\t\t| Promise<SectionControllerHandle>;\n\t}): Promise<SectionControllerHandle>;\n\n\t/**\n\t * Dispose an existing section controller.\n\t */\n\tdisposeSectionController(args: {\n\t\tsectionId: string;\n\t\tattemptId?: string;\n\t\tpersistBeforeDispose?: boolean;\n\t\tclearPersistence?: boolean;\n\t}): Promise<void>;\n\n\t/**\n\t * Subscribe to framework-error events emitted by the coordinator.\n\t *\n\t * Mirrors the shape of {@link subscribeTelemetry} (see\n\t * `ToolkitCoordinator.subscribeTelemetry`): a synchronous,\n\t * multi-subscriber stream where each call to the internal bus's\n\t * `reportFrameworkError` fans out to every active listener exactly once.\n\t *\n\t * The returned function detaches the listener; calling it twice is a\n\t * no-op. A listener that throws is caught and logged; the throw does\n\t * not break fan-out to the remaining listeners.\n\t *\n\t * Use this to wire framework errors into Sentry / Datadog / a custom\n\t * banner without listening on a DOM event. The DOM event\n\t * (`framework-error`) and the canonical `onFrameworkError` prop on\n\t * `<pie-assessment-toolkit>` consume the same bus.\n\t */\n\tsubscribeFrameworkErrors(listener: FrameworkErrorListener): () => void;\n\n\t// ----------------------------------------------------------------\n\t// Tool Policy Engine — public surface (M8 PR 2 / PR 3).\n\t//\n\t// The coordinator owns a single `ToolPolicyEngine` instance and\n\t// exposes its decision and subscription surface through the API\n\t// so that toolbar custom elements (`pie-item-toolbar`,\n\t// `pie-section-toolbar`), the base section player, and bespoke\n\t// host instrumentation (PNP debugger, etc.) all flow through the\n\t// same engine. Hosts that want to drive PNP/profile inputs imperatively\n\t// (instead of binding props on `<pie-assessment-toolkit>`) call\n\t// `updateAssessment` / `updateCurrentItemRef` /\n\t// `setPnpEnforcement` directly.\n\t// ----------------------------------------------------------------\n\n\t/**\n\t * Resolve the visible tool set for a given placement level + scope.\n\t * Returns the engine's full decision (visible tools, diagnostics,\n\t * provenance). Hosts that only need the IDs may map\n\t * `decision.visibleTools` themselves.\n\t */\n\tdecideToolPolicy(request: ToolPolicyDecisionRequest): ToolPolicyDecision;\n\n\t/**\n\t * Subscribe to policy-engine change events. Fires whenever the\n\t * coordinator's bound policy inputs change (`updateToolConfig`,\n\t * `updateToolPlacement`, `updateAssessment`, `updateCurrentItemRef`,\n\t * `setPnpEnforcement`) or a custom `PolicySource` is registered /\n\t * removed. Listeners that need the new visible tool set should\n\t * call `decideToolPolicy(...)` with their level / scope.\n\t */\n\tonPolicyChange(listener: ToolPolicyChangeListener): () => void;\n\n\t/**\n\t * Bind (or clear) the active assessment for PNP/profile policy decisions.\n\t *\n\t * Under auto-mode (no host override via {@link setPnpEnforcement}),\n\t * the engine flips to `pnpEnforcement: \"on\"` iff the assessment\n\t * carries profile precedence material (`personalNeedsProfile`,\n\t * `settings.districtPolicy`, `settings.testAdministration`) or the\n\t * currently-bound item ref carries item-level profile inputs. A bare\n\t * assessment record (just `id` / `name`) keeps `\"off\"`.\n\t *\n\t * The host override set via {@link setPnpEnforcement} is sticky\n\t * across assessment swaps.\n\t */\n\tupdateAssessment(assessment: AssessmentEntity | null): void;\n\n\t/**\n\t * Bind (or clear) the current item reference for policy decisions.\n\t * Used by item-level profile gates (item `requiredTools` /\n\t * `restrictedTools` / `toolParameters`). Item-level profile material\n\t * also feeds the auto-mode helper — navigating to an item with\n\t * profile settings can flip auto-mode to `\"on\"` even when the parent\n\t * assessment carries no profile block of its own.\n\t */\n\tupdateCurrentItemRef(itemRef: AssessmentItemRef | null): void;\n\n\t/**\n\t * Override the auto-mode PNP/profile enforcement decision. Pass `\"on\"` /\n\t * `\"off\"` to pin the mode, or `null` to clear the override and\n\t * return to auto-mode (`\"on\"` iff the bound assessment / item ref\n\t * carries profile material, otherwise `\"off\"`).\n\t */\n\tsetPnpEnforcement(mode: PnpEnforcementMode | null): void;\n\n\t/**\n\t * Read the engine inputs currently driving decisions. Useful for\n\t * debugging / instrumentation; do not mutate.\n\t */\n\tgetPolicyInputs(): Readonly<ResolvedEngineInputs>;\n\n\t/**\n\t * Register a custom `PolicySource`. The source participates in\n\t * every subsequent `decideToolPolicy(...)` call until disposed\n\t * (the returned function detaches).\n\t */\n\tregisterPolicySource(source: PolicySource): () => void;\n\n\t/**\n\t * Register a host-owned resolver for scoped tool render context.\n\t */\n\tregisterToolContextResolver(\n\t\ttoolId: string,\n\t\tresolver: ToolContextResolver,\n\t): () => void;\n\n\t/**\n\t * Replace all host-owned render-context resolvers.\n\t */\n\tsetToolContextResolvers(\n\t\tresolvers: ToolContextResolverMap | null | undefined,\n\t): void;\n\n\t/**\n\t * Whether a host resolver is registered for this tool.\n\t */\n\thasToolContextResolver(toolId: string): boolean;\n\n\t/**\n\t * Resolve render visibility/params for a tool that already survived policy gates.\n\t */\n\tresolveToolContext(\n\t\tcontext: ToolContextResolverContext,\n\t): ResolvedToolContext | null;\n\n\t/**\n\t * Subscribe to resolver registration/removal changes.\n\t */\n\tonToolContextResolverChange(listener: () => void): () => void;\n}\n\n// I18nServiceApi is re-exported from @pie-players/pie-players-shared/i18n\n"]}
|
|
1
|
+
{"version":3,"file":"interfaces.js","sourceRoot":"","sources":["../../src/services/interfaces.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;;AAk1BH,0EAA0E","sourcesContent":["/**\n * Service Interfaces\n *\n * Public interfaces for all core assessment toolkit services.\n * These interfaces define the contracts that service implementations must fulfill,\n * enabling dependency injection, testing, and custom implementations.\n *\n * Part of PIE Assessment Toolkit.\n */\n\nimport type { I18nServiceApi } from \"@pie-players/pie-players-shared/i18n\";\nimport type {\n\tAccessibilityCatalogResolver,\n\tCatalogLookupContext,\n\tCatalogLookupOptions,\n\tCatalogOwnerContext,\n\tCatalogStatistics,\n\tCatalogType,\n\tResolvedCatalog,\n} from \"./AccessibilityCatalogResolver.js\";\nimport type { FrameworkErrorListener } from \"./framework-error-bus.js\";\nimport type { HighlightColor, HighlightType } from \"./HighlightCoordinator.js\";\nimport type {\n\tSectionControllerHandle,\n\tSectionItemEventSubscriptionArgs,\n\tSectionScopedEventSubscriptionArgs,\n\tSectionEventSubscriptionArgs,\n\tToolkitCoordinatorHooks,\n\tToolkitInitStatus,\n} from \"./ToolkitCoordinator.js\";\nimport type { FontSize, ThemeConfig } from \"./ThemeProvider.js\";\nimport type { ZIndexLayer } from \"./ToolCoordinator.js\";\nimport type { PlaybackState, TTSConfig } from \"./TTSService.js\";\nimport type {\n\tToolPlacementConfig,\n\tToolPlacementLevel,\n\tToolProviderConfig,\n} from \"./tools-config-normalizer.js\";\nimport type { ToolProviderRegistry } from \"./tool-providers/ToolProviderRegistry.js\";\nimport type {\n\tPolicySource,\n\tPnpEnforcementMode,\n\tResolvedEngineInputs,\n\tToolPolicyChangeListener,\n\tToolPolicyDecision,\n\tToolPolicyDecisionRequest,\n} from \"../policy/engine.js\";\nimport type {\n\tAssessmentEntity,\n\tAssessmentItemRef,\n} from \"@pie-players/pie-players-shared/types\";\nimport type {\n\tITTSProvider,\n\tTTSProviderCapabilities,\n} from \"@pie-players/pie-tts\";\nimport type {\n\tResolvedToolContext,\n\tToolContextResolver,\n\tToolContextResolverContext,\n\tToolContextResolverMap,\n} from \"./ToolRegistry.js\";\n\n// Re-export I18nServiceApi from players-shared\nexport type { I18nServiceApi };\n\n/**\n * Theme provider interface\n *\n * Applies consistent accessibility theming across items and tools.\n */\nexport interface ThemeProviderApi {\n\t/**\n\t * Apply theme configuration\n\t */\n\tapplyTheme(config: ThemeConfig): void;\n\n\t/**\n\t * Get current theme configuration\n\t */\n\tgetCurrentTheme(): Required<ThemeConfig>;\n\n\t/**\n\t * Reset to default theme\n\t */\n\treset(): void;\n\n\t/**\n\t * Clean up and remove theme styles\n\t */\n\tdestroy(): void;\n}\n\n/**\n * Highlight coordinator interface\n *\n * Manages content highlighting for TTS, annotations, and selections.\n */\nexport interface HighlightCoordinatorApi {\n\t/**\n\t * Highlight a text range\n\t */\n\thighlightRange(\n\t\trange: Range,\n\t\ttype: HighlightType,\n\t\tcolor: HighlightColor,\n\t): void;\n\n\t/**\n\t * Highlight a word for TTS (temporary)\n\t */\n\thighlightTTSWord(\n\t\ttextNode: Text,\n\t\tstartOffset: number,\n\t\tendOffset: number,\n\t): void;\n\n\t/**\n\t * Highlight a single element as the active TTS word (temporary).\n\t *\n\t * Marks exactly the supplied element (via `data-pie-tts-word-element`)\n\t * without walking up to a containing ancestor. Used for atomic targets that\n\t * have no direct text node to range over — most notably MathJax CHTML tokens\n\t * (e.g. `<mjx-mi><mjx-c/></mjx-mi>`) and whole-expression fallbacks. Painting\n\t * the element itself is what lets a resolved math token highlight as a token\n\t * rather than escalating to the full `<math>` / `<mjx-container>`.\n\t *\n\t * Optional so lightweight coordinator mocks can omit it.\n\t */\n\thighlightTTSWordElement?(element: Element): void;\n\n\t/**\n\t * Highlight sentence(s) for TTS (background layer)\n\t */\n\thighlightTTSSentence(ranges: Range[]): void;\n\n\t/**\n\t * Clear all TTS highlights (word and sentence)\n\t */\n\tclearTTS(): void;\n\n\t/**\n\t * Clear highlights of a specific type\n\t */\n\tclearHighlights(type: HighlightType): void;\n\n\t/**\n\t * Clear all highlights\n\t */\n\tclearAll(): void;\n\n\t/**\n\t * Check if highlighting is supported in current environment\n\t */\n\tisSupported(): boolean;\n\n\t/**\n\t * Update TTS highlight style dynamically\n\t */\n\tupdateTTSHighlightStyle(color: string, opacity: number): void;\n}\n\n/**\n * Tool state interface\n */\nexport interface ToolState {\n\tid: string;\n\tname: string;\n\tisVisible: boolean;\n\telement: HTMLElement | null;\n\tlayer: ZIndexLayer;\n}\n\n/**\n * Tool coordinator interface\n *\n * Manages z-index layering and visibility for floating tools.\n */\nexport interface ToolCoordinatorApi {\n\t/**\n\t * Register a tool with the coordinator\n\t */\n\tregisterTool(\n\t\tid: string,\n\t\tname: string,\n\t\telement?: HTMLElement,\n\t\tlayer?: ZIndexLayer,\n\t): void;\n\n\t/**\n\t * Unregister a tool\n\t */\n\tunregisterTool(id: string): void;\n\n\t/**\n\t * Show a tool\n\t */\n\tshowTool(id: string): void;\n\n\t/**\n\t * Hide a tool\n\t */\n\thideTool(id: string): void;\n\n\t/**\n\t * Toggle tool visibility\n\t */\n\ttoggleTool(id: string): void;\n\n\t/**\n\t * Check if tool is visible\n\t */\n\tisToolVisible(id: string): boolean;\n\n\t/**\n\t * Bring an element to the front of its layer\n\t */\n\tbringToFront(element: HTMLElement): void;\n\n\t/**\n\t * Update the element reference for a tool\n\t */\n\tupdateToolElement(id: string, element: HTMLElement): void;\n\n\t/**\n\t * Get state for a specific tool\n\t */\n\tgetToolState(id: string): ToolState | undefined;\n\n\t/**\n\t * Get all visible tools\n\t */\n\tgetVisibleTools(): ToolState[];\n\n\t/**\n\t * Subscribe to tool state changes\n\t */\n\tsubscribe(listener: () => void): () => void;\n}\n\n/**\n * TTS service interface\n *\n * Provides text-to-speech functionality with provider-based architecture.\n * Supports QTI 3.0 accessibility catalogs for pre-authored spoken content.\n */\nexport interface TtsServiceApi {\n\t/**\n\t * Initialize TTS with a provider\n\t */\n\tinitialize(\n\t\tprovider: ITTSProvider,\n\t\tconfig?: Partial<TTSConfig>,\n\t): Promise<void>;\n\n\t/**\n\t * Speak text with optional catalog support\n\t */\n\tspeak(\n\t\ttext: string,\n\t\toptions?: {\n\t\t\tcatalogId?: string;\n\t\t\tcatalogContext?: CatalogLookupContext;\n\t\t\tlanguage?: string;\n\t\t\tcontentElement?: Element;\n\t\t},\n\t): Promise<void>;\n\n\t/**\n\t * Speak a text range\n\t */\n\tspeakRange(\n\t\trange: Range,\n\t\toptions?: { contentRoot?: Element | null },\n\t): Promise<void>;\n\n\t/**\n\t * Pause playback\n\t */\n\tpause(): void;\n\n\t/**\n\t * Resume playback\n\t */\n\tresume(): void;\n\n\t/**\n\t * Stop playback\n\t */\n\tstop(): void;\n\n\t/**\n\t * Request active TTS controls to hand off/deactivate their UI state.\n\t *\n\t * This is an orchestration hint for TTS tool chrome and does not replace\n\t * playback controls such as stop/pause/resume.\n\t */\n\trequestControlHandoff(): void;\n\n\t/**\n\t * Seek forward by sentence units\n\t */\n\tseekForward(units?: number): Promise<void>;\n\n\t/**\n\t * Seek backward by sentence units\n\t */\n\tseekBackward(units?: number): Promise<void>;\n\n\t/**\n\t * Check if currently playing\n\t */\n\tisPlaying(): boolean;\n\n\t/**\n\t * Check if paused\n\t */\n\tisPaused(): boolean;\n\n\t/**\n\t * Get current playback state\n\t */\n\tgetState(): PlaybackState;\n\n\t/**\n\t * Get currently speaking text\n\t */\n\tgetCurrentText(): string | null;\n\n\t/**\n\t * Subscribe to state changes\n\t */\n\tonStateChange(id: string, callback: (state: PlaybackState) => void): void;\n\n\t/**\n\t * Unsubscribe from state changes\n\t */\n\toffStateChange(id: string, callback: (state: PlaybackState) => void): void;\n\n\t/**\n\t * Get capabilities of current provider\n\t */\n\tgetCapabilities(): TTSProviderCapabilities | null;\n\n\t/**\n\t * Update TTS settings dynamically (rate, pitch, voice)\n\t */\n\tupdateSettings(settings: Partial<TTSConfig>): Promise<void>;\n\n\t/**\n\t * Update playback speed and apply it to active planned playback.\n\t */\n\tsetPlaybackRate(rate: number): Promise<void>;\n\n\t/**\n\t * Set highlight coordinator for word highlighting\n\t */\n\tsetHighlightCoordinator(coordinator: HighlightCoordinatorApi): void;\n\n\t/**\n\t * Set accessibility catalog resolver for spoken content\n\t */\n\tsetCatalogResolver(resolver: AccessibilityCatalogResolver): void;\n}\n\n/**\n * Accessibility catalog resolver interface\n *\n * Manages QTI 3.0 accessibility catalogs at assessment and item levels.\n * Provides lookup and resolution services for alternative content representations.\n */\nexport interface AccessibilityCatalogResolverApi {\n\t/**\n\t * Set the default language for fallback resolution\n\t */\n\tsetDefaultLanguage(language: string): void;\n\n\t/**\n\t * Get the default language\n\t */\n\tgetDefaultLanguage(): string;\n\n\t/**\n\t * Add item-level catalogs (called when rendering a new item)\n\t */\n\taddItemCatalogs(catalogs: any[]): void;\n\n\t/**\n\t * Register catalogs scoped to a mounted content owner.\n\t */\n\tregisterCatalogs?(context: CatalogOwnerContext, catalogs: any[]): () => void;\n\n\t/**\n\t * Clear item-level catalogs (called when leaving an item)\n\t */\n\tclearItemCatalogs(): void;\n\n\t/**\n\t * Check if a catalog exists\n\t */\n\thasCatalog(catalogId: string): boolean;\n\n\t/**\n\t * Get alternative content for a catalog identifier\n\t */\n\tgetAlternative(\n\t\tcatalogId: string,\n\t\toptions: CatalogLookupOptions,\n\t): ResolvedCatalog | null;\n\n\t/**\n\t * Get all available alternatives for a catalog identifier\n\t */\n\tgetAllAlternatives(catalogId: string): ResolvedCatalog[];\n\n\t/**\n\t * Get all catalog identifiers available\n\t */\n\tgetAllCatalogIds(): string[];\n\n\t/**\n\t * Get statistics about available catalogs\n\t */\n\tgetStatistics(): CatalogStatistics;\n\n\t/**\n\t * Check if a specific catalog type is available\n\t */\n\thasAlternativeType(catalogId: string, type: CatalogType): boolean;\n\n\t/**\n\t * Get all catalog IDs that have a specific type of alternative\n\t */\n\tgetCatalogsByType(type: CatalogType): string[];\n\n\t/**\n\t * Reset all catalogs\n\t */\n\treset(): void;\n\n\t/**\n\t * Destroy and cleanup\n\t */\n\tdestroy(): void;\n}\n\n/**\n * Element tool state store interface\n *\n * Manages element-level ephemeral tool state using composite keys for global uniqueness.\n * Tool state is client-only and separate from PIE session data (which is sent to server for scoring).\n */\nexport interface ElementToolStateStoreApi {\n\t/**\n\t * Generate a globally unique element ID from components\n\t */\n\tgetGlobalElementId(\n\t\tassessmentId: string,\n\t\tsectionId: string,\n\t\titemId: string,\n\t\telementId: string,\n\t): string;\n\n\t/**\n\t * Parse a global element ID into its components\n\t */\n\tparseGlobalElementId(globalElementId: string): {\n\t\tassessmentId: string;\n\t\tsectionId: string;\n\t\titemId: string;\n\t\telementId: string;\n\t} | null;\n\n\t/**\n\t * Set state for a specific tool on an element\n\t */\n\tsetState(globalElementId: string, toolId: string, state: any): void;\n\n\t/**\n\t * Get state for a specific tool on an element\n\t */\n\tgetState(globalElementId: string, toolId: string): any | undefined;\n\n\t/**\n\t * Get all tool states for a specific element\n\t */\n\tgetElementState(globalElementId: string): Record<string, any>;\n\n\t/**\n\t * Get all element states\n\t */\n\tgetAllState(): Record<string, Record<string, any>>;\n\n\t/**\n\t * Subscribe to state changes\n\t */\n\tsubscribe(\n\t\tcallback: (state: Map<string, Map<string, any>>) => void,\n\t): () => void;\n\n\t/**\n\t * Set callback for persistence integration\n\t */\n\tsetOnStateChange(\n\t\tcallback: (state: Record<string, Record<string, any>>) => void,\n\t): void;\n\n\t/**\n\t * Load state from persistence\n\t */\n\tloadState(state: Record<string, Record<string, any>>): void;\n\n\t/**\n\t * Clear state for a specific element\n\t */\n\tclearElement(globalElementId: string): void;\n\n\t/**\n\t * Clear state for a specific tool across all elements\n\t */\n\tclearTool(toolId: string): void;\n\n\t/**\n\t * Clear all elements in a specific section\n\t */\n\tclearSection(assessmentId: string, sectionId: string): void;\n\n\t/**\n\t * Clear all state\n\t */\n\tclearAll(): void;\n}\n\n/**\n * Toolkit coordinator interface\n *\n * Orchestrates all toolkit services (TTS, tools, accessibility, state management) from a single entry point.\n * Provides centralized configuration for tool availability and settings.\n */\nexport interface ToolkitCoordinatorApi {\n\t/**\n\t * Assessment identifier\n\t */\n\treadonly assessmentId: string;\n\n\t/**\n\t * Configuration\n\t */\n\treadonly config: {\n\t\ttools?: {\n\t\t\tproviders?: Record<string, ToolProviderConfig | undefined>;\n\t\t};\n\t};\n\n\t/**\n\t * TTS service\n\t */\n\treadonly ttsService: TtsServiceApi;\n\n\t/**\n\t * Tool coordinator\n\t */\n\treadonly toolCoordinator: ToolCoordinatorApi;\n\n\t/**\n\t * Highlight coordinator\n\t */\n\treadonly highlightCoordinator: HighlightCoordinatorApi;\n\n\t/**\n\t * Element tool state store\n\t */\n\treadonly elementToolStateStore: ElementToolStateStoreApi;\n\n\t/**\n\t * Catalog resolver\n\t */\n\treadonly catalogResolver: AccessibilityCatalogResolverApi;\n\n\t/**\n\t * Tool provider registry\n\t */\n\treadonly toolProviderRegistry: ToolProviderRegistry;\n\n\t/**\n\t * Get all services as a bundle\n\t */\n\tgetServiceBundle(): {\n\t\tttsService: TtsServiceApi;\n\t\ttoolCoordinator: ToolCoordinatorApi;\n\t\thighlightCoordinator: HighlightCoordinatorApi;\n\t\telementToolStateStore: ElementToolStateStoreApi;\n\t\tcatalogResolver: AccessibilityCatalogResolverApi;\n\t\ttoolProviderRegistry: ToolProviderRegistry;\n\t};\n\n\t/**\n\t * Ensure TTS service is initialized and ready.\n\t */\n\tensureTTSReady(config?: Record<string, unknown>): Promise<void>;\n\n\t/**\n\t * Ensure a provider is initialized and ready.\n\t */\n\tensureProviderReady(providerId: string): Promise<unknown>;\n\n\t/**\n\t * Wait until coordinator initialization is complete.\n\t */\n\twaitUntilReady(): Promise<void>;\n\n\t/**\n\t * Check if coordinator has completed initialization.\n\t */\n\tisReady(): boolean;\n\n\t/**\n\t * Read current initialization status.\n\t */\n\tgetInitStatus(): ToolkitInitStatus;\n\n\t/**\n\t * Check if a tool is enabled\n\t */\n\tisToolEnabled(toolId: string): boolean;\n\n\t/**\n\t * Get tool configuration\n\t */\n\tgetToolConfig(toolId: string): ToolProviderConfig | null;\n\n\t/**\n\t * Update tool configuration\n\t */\n\tupdateToolConfig(toolId: string, updates: Partial<ToolProviderConfig>): void;\n\n\t/**\n\t * Update the enabled tool list for one placement level.\n\t */\n\tupdateToolPlacement(level: ToolPlacementLevel, toolIds: string[]): void;\n\n\t/**\n\t * Patch one or more placement levels in the canonical tools config.\n\t */\n\tupdateToolsPlacement(partial: ToolPlacementConfig): void;\n\n\t/**\n\t * Register or update lifecycle hooks at runtime.\n\t */\n\tsetHooks(hooks: ToolkitCoordinatorHooks): void;\n\n\t/**\n\t * Return a section controller if already created.\n\t */\n\tgetSectionController(args: {\n\t\tsectionId: string;\n\t\tattemptId?: string;\n\t}): SectionControllerHandle | undefined;\n\n\t/**\n\t * Subscribe to section controller events.\n\t *\n\t * The listener is bound to the toolkit's *active section cohort* and\n\t * automatically migrates across cohort transitions\n\t * (`getOrCreateSectionController` for a different cohort). On every\n\t * migration the listener receives a snapshot replay of the new\n\t * cohort's already-loaded `content-loaded` events followed by the\n\t * aggregate `section-loading-complete`, in the canonical order a\n\t * fresh subscriber would have observed.\n\t *\n\t * Throws if no active section cohort exists; host code must call\n\t * `getOrCreateSectionController(...)` at least once before\n\t * subscribing. (`toolkit-ready` alone is not sufficient — it fires\n\t * once toolkit state has loaded but before any section controller\n\t * has been created.) The typical pattern is to subscribe once\n\t * immediately after the first `getOrCreateSectionController(...)`\n\t * resolves; the subscription then follows the active cohort across\n\t * all subsequent navigation without further wiring.\n\t *\n\t * Subscribing the same `listener` reference twice replaces the prior\n\t * subscription with the new one (filter args from the second call\n\t * win); calling the returned disposer twice is a no-op.\n\t *\n\t * A listener that throws is caught and `console.warn`-logged; the\n\t * throw does not interrupt fan-out to the remaining listeners.\n\t */\n\tsubscribeSectionEvents(args: SectionEventSubscriptionArgs): () => void;\n\n\t/**\n\t * Subscribe to item-scoped section controller events.\n\t *\n\t * Same active-cohort binding contract as {@link subscribeSectionEvents};\n\t * defaults `eventTypes` to the item-scoped subset.\n\t */\n\tsubscribeItemEvents(args: SectionItemEventSubscriptionArgs): () => void;\n\n\t/**\n\t * Subscribe to section-scoped lifecycle/loading/completion/error events.\n\t *\n\t * Same active-cohort binding contract as {@link subscribeSectionEvents};\n\t * defaults `eventTypes` to the section-scoped subset.\n\t */\n\tsubscribeSectionLifecycleEvents(\n\t\targs: SectionScopedEventSubscriptionArgs,\n\t): () => void;\n\n\t/**\n\t * Create or reuse a section controller with single-flight deduplication.\n\t */\n\tgetOrCreateSectionController(args: {\n\t\tsectionId: string;\n\t\tattemptId?: string;\n\t\tinput?: unknown;\n\t\tupdateExisting?: boolean;\n\t\tcreateDefaultController: () =>\n\t\t\t| SectionControllerHandle\n\t\t\t| Promise<SectionControllerHandle>;\n\t}): Promise<SectionControllerHandle>;\n\n\t/**\n\t * Dispose an existing section controller.\n\t */\n\tdisposeSectionController(args: {\n\t\tsectionId: string;\n\t\tattemptId?: string;\n\t\tpersistBeforeDispose?: boolean;\n\t\tclearPersistence?: boolean;\n\t}): Promise<void>;\n\n\t/**\n\t * Subscribe to framework-error events emitted by the coordinator.\n\t *\n\t * Mirrors the shape of {@link subscribeTelemetry} (see\n\t * `ToolkitCoordinator.subscribeTelemetry`): a synchronous,\n\t * multi-subscriber stream where each call to the internal bus's\n\t * `reportFrameworkError` fans out to every active listener exactly once.\n\t *\n\t * The returned function detaches the listener; calling it twice is a\n\t * no-op. A listener that throws is caught and logged; the throw does\n\t * not break fan-out to the remaining listeners.\n\t *\n\t * Use this to wire framework errors into Sentry / Datadog / a custom\n\t * banner without listening on a DOM event. The DOM event\n\t * (`framework-error`) and the canonical `onFrameworkError` prop on\n\t * `<pie-assessment-toolkit>` consume the same bus.\n\t */\n\tsubscribeFrameworkErrors(listener: FrameworkErrorListener): () => void;\n\n\t// ----------------------------------------------------------------\n\t// Tool Policy Engine — public surface (M8 PR 2 / PR 3).\n\t//\n\t// The coordinator owns a single `ToolPolicyEngine` instance and\n\t// exposes its decision and subscription surface through the API\n\t// so that toolbar custom elements (`pie-item-toolbar`,\n\t// `pie-section-toolbar`), the base section player, and bespoke\n\t// host instrumentation (PNP debugger, etc.) all flow through the\n\t// same engine. Hosts that want to drive PNP/profile inputs imperatively\n\t// (instead of binding props on `<pie-assessment-toolkit>`) call\n\t// `updateAssessment` / `updateCurrentItemRef` /\n\t// `setPnpEnforcement` directly.\n\t// ----------------------------------------------------------------\n\n\t/**\n\t * Resolve the visible tool set for a given placement level + scope.\n\t * Returns the engine's full decision (visible tools, diagnostics,\n\t * provenance). Hosts that only need the IDs may map\n\t * `decision.visibleTools` themselves.\n\t */\n\tdecideToolPolicy(request: ToolPolicyDecisionRequest): ToolPolicyDecision;\n\n\t/**\n\t * Subscribe to policy-engine change events. Fires whenever the\n\t * coordinator's bound policy inputs change (`updateToolConfig`,\n\t * `updateToolPlacement`, `updateAssessment`, `updateCurrentItemRef`,\n\t * `setPnpEnforcement`) or a custom `PolicySource` is registered /\n\t * removed. Listeners that need the new visible tool set should\n\t * call `decideToolPolicy(...)` with their level / scope.\n\t */\n\tonPolicyChange(listener: ToolPolicyChangeListener): () => void;\n\n\t/**\n\t * Bind (or clear) the active assessment for PNP/profile policy decisions.\n\t *\n\t * Under auto-mode (no host override via {@link setPnpEnforcement}),\n\t * the engine flips to `pnpEnforcement: \"on\"` iff the assessment\n\t * carries profile precedence material (`personalNeedsProfile`,\n\t * `settings.districtPolicy`, `settings.testAdministration`) or the\n\t * currently-bound item ref carries item-level profile inputs. A bare\n\t * assessment record (just `id` / `name`) keeps `\"off\"`.\n\t *\n\t * The host override set via {@link setPnpEnforcement} is sticky\n\t * across assessment swaps.\n\t */\n\tupdateAssessment(assessment: AssessmentEntity | null): void;\n\n\t/**\n\t * Bind (or clear) the current item reference for policy decisions.\n\t * Used by item-level profile gates (item `requiredTools` /\n\t * `restrictedTools` / `toolParameters`). Item-level profile material\n\t * also feeds the auto-mode helper — navigating to an item with\n\t * profile settings can flip auto-mode to `\"on\"` even when the parent\n\t * assessment carries no profile block of its own.\n\t */\n\tupdateCurrentItemRef(itemRef: AssessmentItemRef | null): void;\n\n\t/**\n\t * Override the auto-mode PNP/profile enforcement decision. Pass `\"on\"` /\n\t * `\"off\"` to pin the mode, or `null` to clear the override and\n\t * return to auto-mode (`\"on\"` iff the bound assessment / item ref\n\t * carries profile material, otherwise `\"off\"`).\n\t */\n\tsetPnpEnforcement(mode: PnpEnforcementMode | null): void;\n\n\t/**\n\t * Read the engine inputs currently driving decisions. Useful for\n\t * debugging / instrumentation; do not mutate.\n\t */\n\tgetPolicyInputs(): Readonly<ResolvedEngineInputs>;\n\n\t/**\n\t * Register a custom `PolicySource`. The source participates in\n\t * every subsequent `decideToolPolicy(...)` call until disposed\n\t * (the returned function detaches).\n\t */\n\tregisterPolicySource(source: PolicySource): () => void;\n\n\t/**\n\t * Register a host-owned resolver for scoped tool render context.\n\t */\n\tregisterToolContextResolver(\n\t\ttoolId: string,\n\t\tresolver: ToolContextResolver,\n\t): () => void;\n\n\t/**\n\t * Replace all host-owned render-context resolvers.\n\t */\n\tsetToolContextResolvers(\n\t\tresolvers: ToolContextResolverMap | null | undefined,\n\t): void;\n\n\t/**\n\t * Whether a host resolver is registered for this tool.\n\t */\n\thasToolContextResolver(toolId: string): boolean;\n\n\t/**\n\t * Resolve render visibility/params for a tool that already survived policy gates.\n\t */\n\tresolveToolContext(\n\t\tcontext: ToolContextResolverContext,\n\t): ResolvedToolContext | null;\n\n\t/**\n\t * Subscribe to resolver registration/removal changes.\n\t */\n\tonToolContextResolverChange(listener: () => void): () => void;\n}\n\n// I18nServiceApi is re-exported from @pie-players/pie-players-shared/i18n\n"]}
|
|
@@ -9,6 +9,17 @@ export interface TTSHostToolbarLayout {
|
|
|
9
9
|
expandWhenToolActive: boolean;
|
|
10
10
|
};
|
|
11
11
|
}
|
|
12
|
+
export interface TTSSpeedOptionConfig {
|
|
13
|
+
rate: number;
|
|
14
|
+
label?: string;
|
|
15
|
+
ariaLabel?: string;
|
|
16
|
+
}
|
|
17
|
+
export type TTSSpeedOption = number | TTSSpeedOptionConfig;
|
|
18
|
+
export interface NormalizedTTSSpeedOption {
|
|
19
|
+
rate: number;
|
|
20
|
+
label: string;
|
|
21
|
+
ariaLabel: string;
|
|
22
|
+
}
|
|
12
23
|
export interface TTSRuntimeSettings {
|
|
13
24
|
backend?: "browser" | "polly" | "google" | "server";
|
|
14
25
|
serverProvider?: "polly" | "google" | "custom";
|
|
@@ -35,8 +46,9 @@ export interface TTSRuntimeSettings {
|
|
|
35
46
|
* - Omitted/non-array: default speed buttons are shown.
|
|
36
47
|
* - Empty array: hide speed buttons.
|
|
37
48
|
* - Arrays that sanitize to no valid values: default speed buttons are shown.
|
|
49
|
+
* - Object entries can customize button text while preserving numeric rates.
|
|
38
50
|
*/
|
|
39
|
-
speedOptions?:
|
|
51
|
+
speedOptions?: TTSSpeedOption[];
|
|
40
52
|
layoutMode?: TTSLayoutMode;
|
|
41
53
|
/**
|
|
42
54
|
* Per-token highlighting of math expressions.
|
|
@@ -57,6 +69,7 @@ export interface TTSRuntimeSettings {
|
|
|
57
69
|
export declare const normalizeTTSLayoutMode: (value: unknown, fallback?: TTSLayoutMode) => TTSLayoutMode;
|
|
58
70
|
/** Default inline TTS speed button multipliers (excluding 1.0×). */
|
|
59
71
|
export declare const DEFAULT_TTS_SPEED_OPTIONS: readonly number[];
|
|
72
|
+
export declare const normalizeTTSSpeedOptionConfigs: (value: unknown) => TTSSpeedOption[];
|
|
60
73
|
/**
|
|
61
74
|
* Resolves inline toolbar speed options from host config.
|
|
62
75
|
* - Omitted/non-array: default speed buttons.
|
|
@@ -65,6 +78,7 @@ export declare const DEFAULT_TTS_SPEED_OPTIONS: readonly number[];
|
|
|
65
78
|
* - 1.0× is never shown as a discrete speed button.
|
|
66
79
|
*/
|
|
67
80
|
export declare const normalizeTTSSpeedOptions: (value: unknown) => number[];
|
|
81
|
+
export declare const normalizeTTSSpeedControlOptions: (value: unknown) => NormalizedTTSSpeedOption[];
|
|
68
82
|
export declare const formatTTSSpeedOptionsAsText: (values: number[]) => string;
|
|
69
83
|
/**
|
|
70
84
|
* Parse comma/semicolon-separated multipliers from settings UI text.
|
|
@@ -14,6 +14,64 @@ export const normalizeTTSLayoutMode = (value, fallback = "left-aligned") => type
|
|
|
14
14
|
: fallback;
|
|
15
15
|
/** Default inline TTS speed button multipliers (excluding 1.0×). */
|
|
16
16
|
export const DEFAULT_TTS_SPEED_OPTIONS = Object.freeze([0.8, 1.25]);
|
|
17
|
+
const normalizeSpeedRate = (entry) => {
|
|
18
|
+
if (typeof entry !== "number" || !Number.isFinite(entry) || entry <= 0) {
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
const rounded = Math.round(entry * 100) / 100;
|
|
22
|
+
return rounded === 1 ? undefined : rounded;
|
|
23
|
+
};
|
|
24
|
+
const trimOptionalText = (value) => {
|
|
25
|
+
if (typeof value !== "string")
|
|
26
|
+
return undefined;
|
|
27
|
+
const trimmed = value.trim();
|
|
28
|
+
return trimmed.length ? trimmed : undefined;
|
|
29
|
+
};
|
|
30
|
+
const formatSpeedLabel = (rate) => `${rate}x`;
|
|
31
|
+
const formatSpeedAriaLabel = (label, usedDefaultLabel) => usedDefaultLabel ? `Speed ${label}` : `${label} speed`;
|
|
32
|
+
const normalizeSpeedAriaLabel = (label, ariaLabel, usedDefaultLabel) => {
|
|
33
|
+
if (!ariaLabel)
|
|
34
|
+
return formatSpeedAriaLabel(label, usedDefaultLabel);
|
|
35
|
+
if (ariaLabel.toLowerCase().includes(label.toLowerCase()))
|
|
36
|
+
return ariaLabel;
|
|
37
|
+
return `${label} ${ariaLabel}`;
|
|
38
|
+
};
|
|
39
|
+
const normalizeTTSSpeedOptionConfig = (entry) => {
|
|
40
|
+
if (typeof entry === "number")
|
|
41
|
+
return normalizeSpeedRate(entry);
|
|
42
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry))
|
|
43
|
+
return undefined;
|
|
44
|
+
const record = entry;
|
|
45
|
+
const rate = normalizeSpeedRate(record.rate);
|
|
46
|
+
if (rate === undefined)
|
|
47
|
+
return undefined;
|
|
48
|
+
const label = trimOptionalText(record.label);
|
|
49
|
+
const ariaLabel = trimOptionalText(record.ariaLabel);
|
|
50
|
+
return {
|
|
51
|
+
rate,
|
|
52
|
+
...(label ? { label } : {}),
|
|
53
|
+
...(ariaLabel ? { ariaLabel } : {}),
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
export const normalizeTTSSpeedOptionConfigs = (value) => {
|
|
57
|
+
if (!Array.isArray(value))
|
|
58
|
+
return [...DEFAULT_TTS_SPEED_OPTIONS];
|
|
59
|
+
if (value.length === 0)
|
|
60
|
+
return [];
|
|
61
|
+
const dedupedRates = new Set();
|
|
62
|
+
const normalized = [];
|
|
63
|
+
for (const entry of value) {
|
|
64
|
+
const option = normalizeTTSSpeedOptionConfig(entry);
|
|
65
|
+
if (option === undefined)
|
|
66
|
+
continue;
|
|
67
|
+
const rate = typeof option === "number" ? option : option.rate;
|
|
68
|
+
if (dedupedRates.has(rate))
|
|
69
|
+
continue;
|
|
70
|
+
dedupedRates.add(rate);
|
|
71
|
+
normalized.push(option);
|
|
72
|
+
}
|
|
73
|
+
return normalized.length ? normalized : [...DEFAULT_TTS_SPEED_OPTIONS];
|
|
74
|
+
};
|
|
17
75
|
/**
|
|
18
76
|
* Resolves inline toolbar speed options from host config.
|
|
19
77
|
* - Omitted/non-array: default speed buttons.
|
|
@@ -26,17 +84,25 @@ export const normalizeTTSSpeedOptions = (value) => {
|
|
|
26
84
|
return [...DEFAULT_TTS_SPEED_OPTIONS];
|
|
27
85
|
if (value.length === 0)
|
|
28
86
|
return [];
|
|
29
|
-
|
|
30
|
-
for (const entry of value) {
|
|
31
|
-
if (typeof entry !== "number" || !Number.isFinite(entry) || entry <= 0)
|
|
32
|
-
continue;
|
|
33
|
-
const rounded = Math.round(entry * 100) / 100;
|
|
34
|
-
if (rounded === 1)
|
|
35
|
-
continue;
|
|
36
|
-
deduped.add(rounded);
|
|
37
|
-
}
|
|
38
|
-
return deduped.size ? Array.from(deduped) : [...DEFAULT_TTS_SPEED_OPTIONS];
|
|
87
|
+
return normalizeTTSSpeedOptionConfigs(value).map((option) => typeof option === "number" ? option : option.rate);
|
|
39
88
|
};
|
|
89
|
+
export const normalizeTTSSpeedControlOptions = (value) => normalizeTTSSpeedOptionConfigs(value).map((option) => {
|
|
90
|
+
if (typeof option === "number") {
|
|
91
|
+
const label = formatSpeedLabel(option);
|
|
92
|
+
return {
|
|
93
|
+
rate: option,
|
|
94
|
+
label,
|
|
95
|
+
ariaLabel: formatSpeedAriaLabel(label, true),
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
const defaultLabel = formatSpeedLabel(option.rate);
|
|
99
|
+
const label = option.label || defaultLabel;
|
|
100
|
+
return {
|
|
101
|
+
rate: option.rate,
|
|
102
|
+
label,
|
|
103
|
+
ariaLabel: normalizeSpeedAriaLabel(label, option.ariaLabel, label === defaultLabel),
|
|
104
|
+
};
|
|
105
|
+
});
|
|
40
106
|
export const formatTTSSpeedOptionsAsText = (values) => values.join(", ");
|
|
41
107
|
/**
|
|
42
108
|
* Parse comma/semicolon-separated multipliers from settings UI text.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tts-runtime-config.js","sourceRoot":"","sources":["../../src/services/tts-runtime-config.ts"],"names":[],"mappings":"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","sourcesContent":["import type { TTSConfig } from \"./TTSService.js\";\nimport type { ToolProviderConfig } from \"./tools-config-normalizer.js\";\nimport {\n\tnormalizeSREMathSpeechOptions,\n\ttype SREMathSpeechOptions,\n} from \"./tts/math-speech.js\";\n\nexport type TTSLayoutMode =\n\t| \"reserved-row\"\n\t| \"expanding-row\"\n\t| \"floating-overlay\"\n\t| \"left-aligned\";\n\nconst VALID_TTS_LAYOUT_MODES = new Set<TTSLayoutMode>([\n\t\"reserved-row\",\n\t\"expanding-row\",\n\t\"floating-overlay\",\n\t\"left-aligned\",\n]);\n\nexport interface TTSHostToolbarLayout {\n\tmount: \"before-buttons\";\n\tcontrolsRow: {\n\t\treserveSpace: boolean;\n\t\texpandWhenToolActive: boolean;\n\t};\n}\n\nexport interface TTSRuntimeSettings {\n\tbackend?: \"browser\" | \"polly\" | \"google\" | \"server\";\n\tserverProvider?: \"polly\" | \"google\" | \"custom\";\n\tprovider?: \"polly\" | \"google\" | \"custom\";\n\tengine?: \"standard\" | \"neural\";\n\tsampleRate?: number;\n\tformat?: \"mp3\" | \"ogg\" | \"pcm\";\n\tspeechMarksMode?: \"word\" | \"word+sentence\";\n\tdefaultVoice?: string;\n\trate?: number;\n\tpitch?: number;\n\tapiEndpoint?: string;\n\tlanguage?: string;\n\ttransportMode?: \"pie\" | \"custom\";\n\tendpointMode?: \"synthesizePath\" | \"rootPost\";\n\tendpointValidationMode?: \"voices\" | \"endpoint\" | \"none\";\n\tincludeAuthOnAssetFetch?: boolean;\n\tvalidateEndpoint?: boolean;\n\tcache?: boolean;\n\tspeedRate?: \"slow\" | \"medium\" | \"fast\";\n\tlang_id?: string;\n\t/**\n\t * Optional inline TTS speed buttons.\n\t * - Omitted/non-array: default speed buttons are shown.\n\t * - Empty array: hide speed buttons.\n\t * - Arrays that sanitize to no valid values: default speed buttons are shown.\n\t */\n\tspeedOptions?: number[];\n\tlayoutMode?: TTSLayoutMode;\n\t/**\n\t * Per-token highlighting of math expressions.\n\t * - `true` / omitted (default): when the spoken math aligns confidently to the\n\t * rendered MathML, the formula is highlighted glyph by glyph; otherwise it\n\t * safely falls back to a whole-formula block.\n\t * - `false`: every formula is highlighted as a single block (the fallback),\n\t * never broken into per-token highlights. Prose word tracking is unaffected.\n\t */\n\tmathTokenHighlighting?: boolean;\n\t/**\n\t * Speech Rule Engine options for generated MathML speech. Hosts can use these\n\t * to tune SRE itself (for example ClearSpeak ImpliedTimes/Paren preferences)\n\t * instead of relying on toolkit-specific speech rewrites.\n\t */\n\tmathSpeech?: SREMathSpeechOptions;\n}\n\nconst toRecord = (value: unknown): Record<string, unknown> =>\n\tvalue && typeof value === \"object\" ? (value as Record<string, unknown>) : {};\n\nconst isServerBackend = (\n\tbackend: TTSRuntimeSettings[\"backend\"],\n): backend is \"polly\" | \"google\" | \"server\" =>\n\tbackend === \"polly\" || backend === \"google\" || backend === \"server\";\n\nconst withDefault = <T>(value: T | undefined, fallback: T): T =>\n\tvalue === undefined ? fallback : value;\n\nexport const normalizeTTSLayoutMode = (\n\tvalue: unknown,\n\tfallback: TTSLayoutMode = \"left-aligned\",\n): TTSLayoutMode =>\n\ttypeof value === \"string\" &&\n\tVALID_TTS_LAYOUT_MODES.has(value as TTSLayoutMode)\n\t\t? (value as TTSLayoutMode)\n\t\t: fallback;\n\n/** Default inline TTS speed button multipliers (excluding 1.0×). */\nexport const DEFAULT_TTS_SPEED_OPTIONS = Object.freeze([0.8, 1.25]);\n\n/**\n * Resolves inline toolbar speed options from host config.\n * - Omitted/non-array: default speed buttons.\n * - Empty array: hide speed buttons.\n * - Arrays that sanitize to no valid values: default speed buttons.\n * - 1.0× is never shown as a discrete speed button.\n */\nexport const normalizeTTSSpeedOptions = (value: unknown): number[] => {\n\tif (!Array.isArray(value)) return [...DEFAULT_TTS_SPEED_OPTIONS];\n\tif (value.length === 0) return [];\n\tconst deduped = new Set<number>();\n\tfor (const entry of value) {\n\t\tif (typeof entry !== \"number\" || !Number.isFinite(entry) || entry <= 0)\n\t\t\tcontinue;\n\t\tconst rounded = Math.round(entry * 100) / 100;\n\t\tif (rounded === 1) continue;\n\t\tdeduped.add(rounded);\n\t}\n\treturn deduped.size ? Array.from(deduped) : [...DEFAULT_TTS_SPEED_OPTIONS];\n};\n\nexport const formatTTSSpeedOptionsAsText = (values: number[]): string =>\n\tvalues.join(\", \");\n\n/**\n * Parse comma/semicolon-separated multipliers from settings UI text.\n * - Empty or whitespace-only string → hide speed buttons (`[]`).\n * - Non-empty text with no parseable finite numbers → same as invalid-only array config: defaults.\n */\nexport const parseTTSSpeedOptionsFromText = (text: string): number[] => {\n\tconst trimmed = text.trim();\n\tif (!trimmed) return [];\n\tconst parts = trimmed\n\t\t.split(/[,;]+/)\n\t\t.map((p) => p.trim())\n\t\t.filter(Boolean);\n\tconst candidates = parts\n\t\t.map((p) => Number.parseFloat(p))\n\t\t.filter((n) => Number.isFinite(n));\n\tif (!candidates.length) return [...DEFAULT_TTS_SPEED_OPTIONS];\n\treturn normalizeTTSSpeedOptions(candidates);\n};\n\nconst applyRuntimeDefaults = (\n\tconfig: TTSRuntimeSettings,\n): TTSRuntimeSettings => {\n\tconst withLayoutDefaults: TTSRuntimeSettings = {\n\t\t...config,\n\t\tlayoutMode: normalizeTTSLayoutMode(config.layoutMode),\n\t};\n\tconst backend = config.backend || \"browser\";\n\tif (!isServerBackend(backend)) return withLayoutDefaults;\n\n\tconst withServerDefaults: TTSRuntimeSettings = {\n\t\t...withLayoutDefaults,\n\t\tapiEndpoint: withDefault(withLayoutDefaults.apiEndpoint, \"/api/tts\"),\n\t\ttransportMode: withDefault(withLayoutDefaults.transportMode, \"pie\"),\n\t\tendpointValidationMode: withDefault(\n\t\t\twithLayoutDefaults.endpointValidationMode,\n\t\t\t\"voices\",\n\t\t),\n\t\tvalidateEndpoint: withDefault(withLayoutDefaults.validateEndpoint, true),\n\t\tincludeAuthOnAssetFetch: withDefault(\n\t\t\twithLayoutDefaults.includeAuthOnAssetFetch,\n\t\t\tfalse,\n\t\t),\n\t\trate: withDefault(withLayoutDefaults.rate, 1.0),\n\t\tlanguage: withDefault(withLayoutDefaults.language, \"en-US\"),\n\t};\n\n\tif (backend === \"polly\") {\n\t\treturn {\n\t\t\t...withServerDefaults,\n\t\t\tdefaultVoice: withDefault(withServerDefaults.defaultVoice, \"Joanna\"),\n\t\t\tengine: withDefault(withServerDefaults.engine, \"neural\"),\n\t\t\tformat: withDefault(withServerDefaults.format, \"mp3\"),\n\t\t\tspeechMarksMode: withDefault(\n\t\t\t\twithServerDefaults.speechMarksMode,\n\t\t\t\t\"word+sentence\",\n\t\t\t),\n\t\t};\n\t}\n\n\tif (backend === \"google\") {\n\t\treturn {\n\t\t\t...withServerDefaults,\n\t\t\tdefaultVoice: withDefault(\n\t\t\t\twithServerDefaults.defaultVoice,\n\t\t\t\t\"en-US-Wavenet-A\",\n\t\t\t),\n\t\t};\n\t}\n\n\treturn withServerDefaults;\n};\n\nexport const resolveTTSRuntimeSettings = (\n\tconfig: ToolProviderConfig | TTSRuntimeSettings | undefined,\n): TTSRuntimeSettings => {\n\tconst configRecord = toRecord(config);\n\tconst settingsRecord = toRecord(configRecord.settings);\n\treturn applyRuntimeDefaults({\n\t\t...configRecord,\n\t\t...settingsRecord,\n\t} as TTSRuntimeSettings);\n};\n\nexport const resolveTTSBackend = (\n\tconfig: TTSRuntimeSettings,\n): NonNullable<TTSRuntimeSettings[\"backend\"]> => config.backend || \"browser\";\n\nexport const resolveRuntimeProvider = (\n\tconfig: TTSRuntimeSettings,\n\tbackend: NonNullable<TTSRuntimeSettings[\"backend\"]>,\n): TTSRuntimeSettings[\"serverProvider\"] => {\n\tif (backend === \"polly\" || backend === \"google\") return backend;\n\tif (backend === \"server\") {\n\t\treturn config.serverProvider || config.provider;\n\t}\n\treturn config.serverProvider || config.provider;\n};\n\nexport const resolveTransportMode = (\n\tconfig: TTSRuntimeSettings,\n\truntimeProvider: TTSRuntimeSettings[\"serverProvider\"],\n): NonNullable<TTSRuntimeSettings[\"transportMode\"]> =>\n\tconfig.transportMode || (runtimeProvider === \"custom\" ? \"custom\" : \"pie\");\n\nexport const buildRuntimeTTSConfig = (\n\tconfig: TTSRuntimeSettings,\n): Partial<TTSConfig> => {\n\tconst backend = resolveTTSBackend(config);\n\tconst runtimeProvider = resolveRuntimeProvider(config, backend);\n\tconst transportMode = resolveTransportMode(config, runtimeProvider);\n\tconst mathSpeech = normalizeSREMathSpeechOptions(config.mathSpeech);\n\treturn {\n\t\tvoice: config.defaultVoice,\n\t\trate: config.rate,\n\t\tpitch: config.pitch,\n\t\tproviderOptions: {\n\t\t\t...(config.language ? { locale: config.language } : {}),\n\t\t\t...(backend === \"polly\" && config.engine\n\t\t\t\t? { engine: config.engine }\n\t\t\t\t: {}),\n\t\t\t...(backend === \"polly\" && typeof config.sampleRate === \"number\"\n\t\t\t\t? { sampleRate: config.sampleRate }\n\t\t\t\t: {}),\n\t\t\t...(backend === \"polly\" && config.format\n\t\t\t\t? { format: config.format }\n\t\t\t\t: {}),\n\t\t\t...(backend === \"polly\"\n\t\t\t\t? {\n\t\t\t\t\t\tspeechMarkTypes:\n\t\t\t\t\t\t\tconfig.speechMarksMode === \"word+sentence\"\n\t\t\t\t\t\t\t\t? [\"word\", \"sentence\"]\n\t\t\t\t\t\t\t\t: [\"word\"],\n\t\t\t\t\t}\n\t\t\t\t: {}),\n\t\t\t...(transportMode === \"custom\" && typeof config.cache === \"boolean\"\n\t\t\t\t? { cache: config.cache }\n\t\t\t\t: {}),\n\t\t\t...(transportMode === \"custom\" && config.speedRate\n\t\t\t\t? { speedRate: config.speedRate }\n\t\t\t\t: {}),\n\t\t\t...(transportMode === \"custom\" && config.lang_id\n\t\t\t\t? { lang_id: config.lang_id }\n\t\t\t\t: {}),\n\t\t\t...(mathSpeech ? { mathSpeech } : {}),\n\t\t},\n\t\tapiEndpoint: config.apiEndpoint,\n\t\tprovider: runtimeProvider,\n\t\tlanguage: config.language,\n\t\ttransportMode,\n\t\tendpointMode: config.endpointMode,\n\t\tendpointValidationMode: config.endpointValidationMode,\n\t\tincludeAuthOnAssetFetch: config.includeAuthOnAssetFetch,\n\t\tvalidateEndpoint: config.validateEndpoint,\n\t\t// Toolkit-level highlight setting carried through the config channel\n\t\t// (like apiEndpoint/transportMode); consumed by the highlight pipeline,\n\t\t// ignored by providers. Only forwarded when set so the pipeline default\n\t\t// (per-token enabled) applies otherwise.\n\t\t...(typeof config.mathTokenHighlighting === \"boolean\"\n\t\t\t? { mathTokenHighlighting: config.mathTokenHighlighting }\n\t\t\t: {}),\n\t} as Partial<TTSConfig>;\n};\n\nexport const resolveTTSLayoutMode = (\n\tconfig: TTSRuntimeSettings,\n): TTSLayoutMode => normalizeTTSLayoutMode(config.layoutMode);\n\nexport const resolveTTSHostToolbarLayout = (\n\tconfig: TTSRuntimeSettings,\n): TTSHostToolbarLayout => {\n\tconst layoutMode = resolveTTSLayoutMode(config);\n\tswitch (layoutMode) {\n\t\tcase \"reserved-row\":\n\t\t\treturn {\n\t\t\t\tmount: \"before-buttons\",\n\t\t\t\tcontrolsRow: {\n\t\t\t\t\treserveSpace: true,\n\t\t\t\t\texpandWhenToolActive: false,\n\t\t\t\t},\n\t\t\t};\n\t\tcase \"expanding-row\":\n\t\t\treturn {\n\t\t\t\tmount: \"before-buttons\",\n\t\t\t\tcontrolsRow: {\n\t\t\t\t\treserveSpace: false,\n\t\t\t\t\texpandWhenToolActive: true,\n\t\t\t\t},\n\t\t\t};\n\t\tcase \"floating-overlay\":\n\t\tcase \"left-aligned\":\n\t\tdefault:\n\t\t\treturn {\n\t\t\t\tmount: \"before-buttons\",\n\t\t\t\tcontrolsRow: {\n\t\t\t\t\treserveSpace: false,\n\t\t\t\t\texpandWhenToolActive: false,\n\t\t\t\t},\n\t\t\t};\n\t}\n};\n"]}
|
|
1
|
+
{"version":3,"file":"tts-runtime-config.js","sourceRoot":"","sources":["../../src/services/tts-runtime-config.ts"],"names":[],"mappings":"AAEA,OAAO,EACN,6BAA6B,GAE7B,MAAM,sBAAsB,CAAC;AAQ9B,MAAM,sBAAsB,GAAG,IAAI,GAAG,CAAgB;IACrD,cAAc;IACd,eAAe;IACf,kBAAkB;IAClB,cAAc;CACd,CAAC,CAAC;AAuEH,MAAM,QAAQ,GAAG,CAAC,KAAc,EAA2B,EAAE,CAC5D,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAE,KAAiC,CAAC,CAAC,CAAC,EAAE,CAAC;AAE9E,MAAM,eAAe,GAAG,CACvB,OAAsC,EACK,EAAE,CAC7C,OAAO,KAAK,OAAO,IAAI,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAK,QAAQ,CAAC;AAErE,MAAM,WAAW,GAAG,CAAI,KAAoB,EAAE,QAAW,EAAK,EAAE,CAC/D,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC;AAExC,MAAM,CAAC,MAAM,sBAAsB,GAAG,CACrC,KAAc,EACd,WAA0B,cAAc,EACxB,EAAE,CAClB,OAAO,KAAK,KAAK,QAAQ;IACzB,sBAAsB,CAAC,GAAG,CAAC,KAAsB,CAAC;IACjD,CAAC,CAAE,KAAuB;IAC1B,CAAC,CAAC,QAAQ,CAAC;AAEb,oEAAoE;AACpE,MAAM,CAAC,MAAM,yBAAyB,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC;AAEpE,MAAM,kBAAkB,GAAG,CAAC,KAAc,EAAsB,EAAE;IACjE,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,EAAE,CAAC;QACxE,OAAO,SAAS,CAAC;IAClB,CAAC;IACD,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC;IAC9C,OAAO,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC;AAC5C,CAAC,CAAC;AAEF,MAAM,gBAAgB,GAAG,CAAC,KAAc,EAAsB,EAAE;IAC/D,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAC;IAChD,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IAC7B,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC;AAC7C,CAAC,CAAC;AAEF,MAAM,gBAAgB,GAAG,CAAC,IAAY,EAAU,EAAE,CAAC,GAAG,IAAI,GAAG,CAAC;AAE9D,MAAM,oBAAoB,GAAG,CAAC,KAAa,EAAE,gBAAyB,EAAU,EAAE,CACjF,gBAAgB,CAAC,CAAC,CAAC,SAAS,KAAK,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,QAAQ,CAAC;AAExD,MAAM,uBAAuB,GAAG,CAC/B,KAAa,EACb,SAA6B,EAC7B,gBAAyB,EAChB,EAAE;IACX,IAAI,CAAC,SAAS;QAAE,OAAO,oBAAoB,CAAC,KAAK,EAAE,gBAAgB,CAAC,CAAC;IACrE,IAAI,SAAS,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC;QAAE,OAAO,SAAS,CAAC;IAC5E,OAAO,GAAG,KAAK,IAAI,SAAS,EAAE,CAAC;AAChC,CAAC,CAAC;AAEF,MAAM,6BAA6B,GAAG,CACrC,KAAc,EACe,EAAE;IAC/B,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,kBAAkB,CAAC,KAAK,CAAC,CAAC;IAChE,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IAClF,MAAM,MAAM,GAAG,KAAgC,CAAC;IAChD,MAAM,IAAI,GAAG,kBAAkB,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAC7C,IAAI,IAAI,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IACzC,MAAM,KAAK,GAAG,gBAAgB,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC7C,MAAM,SAAS,GAAG,gBAAgB,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IACrD,OAAO;QACN,IAAI;QACJ,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC3B,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACnC,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,8BAA8B,GAAG,CAC7C,KAAc,EACK,EAAE;IACrB,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,CAAC,GAAG,yBAAyB,CAAC,CAAC;IACjE,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAClC,MAAM,YAAY,GAAG,IAAI,GAAG,EAAU,CAAC;IACvC,MAAM,UAAU,GAAqB,EAAE,CAAC;IACxC,KAAK,MAAM,KAAK,IAAI,KAAK,EAAE,CAAC;QAC3B,MAAM,MAAM,GAAG,6BAA6B,CAAC,KAAK,CAAC,CAAC;QACpD,IAAI,MAAM,KAAK,SAAS;YAAE,SAAS;QACnC,MAAM,IAAI,GAAG,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC;QAC/D,IAAI,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,SAAS;QACrC,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACvB,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACzB,CAAC;IACD,OAAO,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,GAAG,yBAAyB,CAAC,CAAC;AACxE,CAAC,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAC,KAAc,EAAY,EAAE;IACpE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,CAAC,GAAG,yBAAyB,CAAC,CAAC;IACjE,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAClC,OAAO,8BAA8B,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAC3D,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CACjD,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,+BAA+B,GAAG,CAC9C,KAAc,EACe,EAAE,CAC/B,8BAA8B,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE;IACpD,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;QAChC,MAAM,KAAK,GAAG,gBAAgB,CAAC,MAAM,CAAC,CAAC;QACvC,OAAO;YACN,IAAI,EAAE,MAAM;YACZ,KAAK;YACL,SAAS,EAAE,oBAAoB,CAAC,KAAK,EAAE,IAAI,CAAC;SAC5C,CAAC;IACH,CAAC;IACD,MAAM,YAAY,GAAG,gBAAgB,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACnD,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,IAAI,YAAY,CAAC;IAC3C,OAAO;QACN,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,KAAK;QACL,SAAS,EAAE,uBAAuB,CACjC,KAAK,EACL,MAAM,CAAC,SAAS,EAChB,KAAK,KAAK,YAAY,CACtB;KACD,CAAC;AACH,CAAC,CAAC,CAAC;AAEJ,MAAM,CAAC,MAAM,2BAA2B,GAAG,CAAC,MAAgB,EAAU,EAAE,CACvE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAEnB;;;;GAIG;AACH,MAAM,CAAC,MAAM,4BAA4B,GAAG,CAAC,IAAY,EAAY,EAAE;IACtE,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;IAC5B,IAAI,CAAC,OAAO;QAAE,OAAO,EAAE,CAAC;IACxB,MAAM,KAAK,GAAG,OAAO;SACnB,KAAK,CAAC,OAAO,CAAC;SACd,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;SACpB,MAAM,CAAC,OAAO,CAAC,CAAC;IAClB,MAAM,UAAU,GAAG,KAAK;SACtB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;SAChC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;IACpC,IAAI,CAAC,UAAU,CAAC,MAAM;QAAE,OAAO,CAAC,GAAG,yBAAyB,CAAC,CAAC;IAC9D,OAAO,wBAAwB,CAAC,UAAU,CAAC,CAAC;AAC7C,CAAC,CAAC;AAEF,MAAM,oBAAoB,GAAG,CAC5B,MAA0B,EACL,EAAE;IACvB,MAAM,kBAAkB,GAAuB;QAC9C,GAAG,MAAM;QACT,UAAU,EAAE,sBAAsB,CAAC,MAAM,CAAC,UAAU,CAAC;KACrD,CAAC;IACF,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,IAAI,SAAS,CAAC;IAC5C,IAAI,CAAC,eAAe,CAAC,OAAO,CAAC;QAAE,OAAO,kBAAkB,CAAC;IAEzD,MAAM,kBAAkB,GAAuB;QAC9C,GAAG,kBAAkB;QACrB,WAAW,EAAE,WAAW,CAAC,kBAAkB,CAAC,WAAW,EAAE,UAAU,CAAC;QACpE,aAAa,EAAE,WAAW,CAAC,kBAAkB,CAAC,aAAa,EAAE,KAAK,CAAC;QACnE,sBAAsB,EAAE,WAAW,CAClC,kBAAkB,CAAC,sBAAsB,EACzC,QAAQ,CACR;QACD,gBAAgB,EAAE,WAAW,CAAC,kBAAkB,CAAC,gBAAgB,EAAE,IAAI,CAAC;QACxE,uBAAuB,EAAE,WAAW,CACnC,kBAAkB,CAAC,uBAAuB,EAC1C,KAAK,CACL;QACD,IAAI,EAAE,WAAW,CAAC,kBAAkB,CAAC,IAAI,EAAE,GAAG,CAAC;QAC/C,QAAQ,EAAE,WAAW,CAAC,kBAAkB,CAAC,QAAQ,EAAE,OAAO,CAAC;KAC3D,CAAC;IAEF,IAAI,OAAO,KAAK,OAAO,EAAE,CAAC;QACzB,OAAO;YACN,GAAG,kBAAkB;YACrB,YAAY,EAAE,WAAW,CAAC,kBAAkB,CAAC,YAAY,EAAE,QAAQ,CAAC;YACpE,MAAM,EAAE,WAAW,CAAC,kBAAkB,CAAC,MAAM,EAAE,QAAQ,CAAC;YACxD,MAAM,EAAE,WAAW,CAAC,kBAAkB,CAAC,MAAM,EAAE,KAAK,CAAC;YACrD,eAAe,EAAE,WAAW,CAC3B,kBAAkB,CAAC,eAAe,EAClC,eAAe,CACf;SACD,CAAC;IACH,CAAC;IAED,IAAI,OAAO,KAAK,QAAQ,EAAE,CAAC;QAC1B,OAAO;YACN,GAAG,kBAAkB;YACrB,YAAY,EAAE,WAAW,CACxB,kBAAkB,CAAC,YAAY,EAC/B,iBAAiB,CACjB;SACD,CAAC;IACH,CAAC;IAED,OAAO,kBAAkB,CAAC;AAC3B,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,yBAAyB,GAAG,CACxC,MAA2D,EACtC,EAAE;IACvB,MAAM,YAAY,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC;IACtC,MAAM,cAAc,GAAG,QAAQ,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC;IACvD,OAAO,oBAAoB,CAAC;QAC3B,GAAG,YAAY;QACf,GAAG,cAAc;KACK,CAAC,CAAC;AAC1B,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAChC,MAA0B,EACmB,EAAE,CAAC,MAAM,CAAC,OAAO,IAAI,SAAS,CAAC;AAE7E,MAAM,CAAC,MAAM,sBAAsB,GAAG,CACrC,MAA0B,EAC1B,OAAmD,EACZ,EAAE;IACzC,IAAI,OAAO,KAAK,OAAO,IAAI,OAAO,KAAK,QAAQ;QAAE,OAAO,OAAO,CAAC;IAChE,IAAI,OAAO,KAAK,QAAQ,EAAE,CAAC;QAC1B,OAAO,MAAM,CAAC,cAAc,IAAI,MAAM,CAAC,QAAQ,CAAC;IACjD,CAAC;IACD,OAAO,MAAM,CAAC,cAAc,IAAI,MAAM,CAAC,QAAQ,CAAC;AACjD,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,oBAAoB,GAAG,CACnC,MAA0B,EAC1B,eAAqD,EACF,EAAE,CACrD,MAAM,CAAC,aAAa,IAAI,CAAC,eAAe,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;AAE3E,MAAM,CAAC,MAAM,qBAAqB,GAAG,CACpC,MAA0B,EACL,EAAE;IACvB,MAAM,OAAO,GAAG,iBAAiB,CAAC,MAAM,CAAC,CAAC;IAC1C,MAAM,eAAe,GAAG,sBAAsB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChE,MAAM,aAAa,GAAG,oBAAoB,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IACpE,MAAM,UAAU,GAAG,6BAA6B,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IACpE,OAAO;QACN,KAAK,EAAE,MAAM,CAAC,YAAY;QAC1B,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,KAAK,EAAE,MAAM,CAAC,KAAK;QACnB,eAAe,EAAE;YAChB,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACvD,GAAG,CAAC,OAAO,KAAK,OAAO,IAAI,MAAM,CAAC,MAAM;gBACvC,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE;gBAC3B,CAAC,CAAC,EAAE,CAAC;YACN,GAAG,CAAC,OAAO,KAAK,OAAO,IAAI,OAAO,MAAM,CAAC,UAAU,KAAK,QAAQ;gBAC/D,CAAC,CAAC,EAAE,UAAU,EAAE,MAAM,CAAC,UAAU,EAAE;gBACnC,CAAC,CAAC,EAAE,CAAC;YACN,GAAG,CAAC,OAAO,KAAK,OAAO,IAAI,MAAM,CAAC,MAAM;gBACvC,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE;gBAC3B,CAAC,CAAC,EAAE,CAAC;YACN,GAAG,CAAC,OAAO,KAAK,OAAO;gBACtB,CAAC,CAAC;oBACA,eAAe,EACd,MAAM,CAAC,eAAe,KAAK,eAAe;wBACzC,CAAC,CAAC,CAAC,MAAM,EAAE,UAAU,CAAC;wBACtB,CAAC,CAAC,CAAC,MAAM,CAAC;iBACZ;gBACF,CAAC,CAAC,EAAE,CAAC;YACN,GAAG,CAAC,aAAa,KAAK,QAAQ,IAAI,OAAO,MAAM,CAAC,KAAK,KAAK,SAAS;gBAClE,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE;gBACzB,CAAC,CAAC,EAAE,CAAC;YACN,GAAG,CAAC,aAAa,KAAK,QAAQ,IAAI,MAAM,CAAC,SAAS;gBACjD,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE;gBACjC,CAAC,CAAC,EAAE,CAAC;YACN,GAAG,CAAC,aAAa,KAAK,QAAQ,IAAI,MAAM,CAAC,OAAO;gBAC/C,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE;gBAC7B,CAAC,CAAC,EAAE,CAAC;YACN,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACrC;QACD,WAAW,EAAE,MAAM,CAAC,WAAW;QAC/B,QAAQ,EAAE,eAAe;QACzB,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,aAAa;QACb,YAAY,EAAE,MAAM,CAAC,YAAY;QACjC,sBAAsB,EAAE,MAAM,CAAC,sBAAsB;QACrD,uBAAuB,EAAE,MAAM,CAAC,uBAAuB;QACvD,gBAAgB,EAAE,MAAM,CAAC,gBAAgB;QACzC,qEAAqE;QACrE,wEAAwE;QACxE,wEAAwE;QACxE,yCAAyC;QACzC,GAAG,CAAC,OAAO,MAAM,CAAC,qBAAqB,KAAK,SAAS;YACpD,CAAC,CAAC,EAAE,qBAAqB,EAAE,MAAM,CAAC,qBAAqB,EAAE;YACzD,CAAC,CAAC,EAAE,CAAC;KACgB,CAAC;AACzB,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,oBAAoB,GAAG,CACnC,MAA0B,EACV,EAAE,CAAC,sBAAsB,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;AAE9D,MAAM,CAAC,MAAM,2BAA2B,GAAG,CAC1C,MAA0B,EACH,EAAE;IACzB,MAAM,UAAU,GAAG,oBAAoB,CAAC,MAAM,CAAC,CAAC;IAChD,QAAQ,UAAU,EAAE,CAAC;QACpB,KAAK,cAAc;YAClB,OAAO;gBACN,KAAK,EAAE,gBAAgB;gBACvB,WAAW,EAAE;oBACZ,YAAY,EAAE,IAAI;oBAClB,oBAAoB,EAAE,KAAK;iBAC3B;aACD,CAAC;QACH,KAAK,eAAe;YACnB,OAAO;gBACN,KAAK,EAAE,gBAAgB;gBACvB,WAAW,EAAE;oBACZ,YAAY,EAAE,KAAK;oBACnB,oBAAoB,EAAE,IAAI;iBAC1B;aACD,CAAC;QACH,KAAK,kBAAkB,CAAC;QACxB,KAAK,cAAc,CAAC;QACpB;YACC,OAAO;gBACN,KAAK,EAAE,gBAAgB;gBACvB,WAAW,EAAE;oBACZ,YAAY,EAAE,KAAK;oBACnB,oBAAoB,EAAE,KAAK;iBAC3B;aACD,CAAC;IACJ,CAAC;AACF,CAAC,CAAC","sourcesContent":["import type { TTSConfig } from \"./TTSService.js\";\nimport type { ToolProviderConfig } from \"./tools-config-normalizer.js\";\nimport {\n\tnormalizeSREMathSpeechOptions,\n\ttype SREMathSpeechOptions,\n} from \"./tts/math-speech.js\";\n\nexport type TTSLayoutMode =\n\t| \"reserved-row\"\n\t| \"expanding-row\"\n\t| \"floating-overlay\"\n\t| \"left-aligned\";\n\nconst VALID_TTS_LAYOUT_MODES = new Set<TTSLayoutMode>([\n\t\"reserved-row\",\n\t\"expanding-row\",\n\t\"floating-overlay\",\n\t\"left-aligned\",\n]);\n\nexport interface TTSHostToolbarLayout {\n\tmount: \"before-buttons\";\n\tcontrolsRow: {\n\t\treserveSpace: boolean;\n\t\texpandWhenToolActive: boolean;\n\t};\n}\n\nexport interface TTSSpeedOptionConfig {\n\trate: number;\n\tlabel?: string;\n\tariaLabel?: string;\n}\n\nexport type TTSSpeedOption = number | TTSSpeedOptionConfig;\n\nexport interface NormalizedTTSSpeedOption {\n\trate: number;\n\tlabel: string;\n\tariaLabel: string;\n}\n\nexport interface TTSRuntimeSettings {\n\tbackend?: \"browser\" | \"polly\" | \"google\" | \"server\";\n\tserverProvider?: \"polly\" | \"google\" | \"custom\";\n\tprovider?: \"polly\" | \"google\" | \"custom\";\n\tengine?: \"standard\" | \"neural\";\n\tsampleRate?: number;\n\tformat?: \"mp3\" | \"ogg\" | \"pcm\";\n\tspeechMarksMode?: \"word\" | \"word+sentence\";\n\tdefaultVoice?: string;\n\trate?: number;\n\tpitch?: number;\n\tapiEndpoint?: string;\n\tlanguage?: string;\n\ttransportMode?: \"pie\" | \"custom\";\n\tendpointMode?: \"synthesizePath\" | \"rootPost\";\n\tendpointValidationMode?: \"voices\" | \"endpoint\" | \"none\";\n\tincludeAuthOnAssetFetch?: boolean;\n\tvalidateEndpoint?: boolean;\n\tcache?: boolean;\n\tspeedRate?: \"slow\" | \"medium\" | \"fast\";\n\tlang_id?: string;\n\t/**\n\t * Optional inline TTS speed buttons.\n\t * - Omitted/non-array: default speed buttons are shown.\n\t * - Empty array: hide speed buttons.\n\t * - Arrays that sanitize to no valid values: default speed buttons are shown.\n\t * - Object entries can customize button text while preserving numeric rates.\n\t */\n\tspeedOptions?: TTSSpeedOption[];\n\tlayoutMode?: TTSLayoutMode;\n\t/**\n\t * Per-token highlighting of math expressions.\n\t * - `true` / omitted (default): when the spoken math aligns confidently to the\n\t * rendered MathML, the formula is highlighted glyph by glyph; otherwise it\n\t * safely falls back to a whole-formula block.\n\t * - `false`: every formula is highlighted as a single block (the fallback),\n\t * never broken into per-token highlights. Prose word tracking is unaffected.\n\t */\n\tmathTokenHighlighting?: boolean;\n\t/**\n\t * Speech Rule Engine options for generated MathML speech. Hosts can use these\n\t * to tune SRE itself (for example ClearSpeak ImpliedTimes/Paren preferences)\n\t * instead of relying on toolkit-specific speech rewrites.\n\t */\n\tmathSpeech?: SREMathSpeechOptions;\n}\n\nconst toRecord = (value: unknown): Record<string, unknown> =>\n\tvalue && typeof value === \"object\" ? (value as Record<string, unknown>) : {};\n\nconst isServerBackend = (\n\tbackend: TTSRuntimeSettings[\"backend\"],\n): backend is \"polly\" | \"google\" | \"server\" =>\n\tbackend === \"polly\" || backend === \"google\" || backend === \"server\";\n\nconst withDefault = <T>(value: T | undefined, fallback: T): T =>\n\tvalue === undefined ? fallback : value;\n\nexport const normalizeTTSLayoutMode = (\n\tvalue: unknown,\n\tfallback: TTSLayoutMode = \"left-aligned\",\n): TTSLayoutMode =>\n\ttypeof value === \"string\" &&\n\tVALID_TTS_LAYOUT_MODES.has(value as TTSLayoutMode)\n\t\t? (value as TTSLayoutMode)\n\t\t: fallback;\n\n/** Default inline TTS speed button multipliers (excluding 1.0×). */\nexport const DEFAULT_TTS_SPEED_OPTIONS = Object.freeze([0.8, 1.25]);\n\nconst normalizeSpeedRate = (entry: unknown): number | undefined => {\n\tif (typeof entry !== \"number\" || !Number.isFinite(entry) || entry <= 0) {\n\t\treturn undefined;\n\t}\n\tconst rounded = Math.round(entry * 100) / 100;\n\treturn rounded === 1 ? undefined : rounded;\n};\n\nconst trimOptionalText = (value: unknown): string | undefined => {\n\tif (typeof value !== \"string\") return undefined;\n\tconst trimmed = value.trim();\n\treturn trimmed.length ? trimmed : undefined;\n};\n\nconst formatSpeedLabel = (rate: number): string => `${rate}x`;\n\nconst formatSpeedAriaLabel = (label: string, usedDefaultLabel: boolean): string =>\n\tusedDefaultLabel ? `Speed ${label}` : `${label} speed`;\n\nconst normalizeSpeedAriaLabel = (\n\tlabel: string,\n\tariaLabel: string | undefined,\n\tusedDefaultLabel: boolean,\n): string => {\n\tif (!ariaLabel) return formatSpeedAriaLabel(label, usedDefaultLabel);\n\tif (ariaLabel.toLowerCase().includes(label.toLowerCase())) return ariaLabel;\n\treturn `${label} ${ariaLabel}`;\n};\n\nconst normalizeTTSSpeedOptionConfig = (\n\tentry: unknown,\n): TTSSpeedOption | undefined => {\n\tif (typeof entry === \"number\") return normalizeSpeedRate(entry);\n\tif (!entry || typeof entry !== \"object\" || Array.isArray(entry)) return undefined;\n\tconst record = entry as Record<string, unknown>;\n\tconst rate = normalizeSpeedRate(record.rate);\n\tif (rate === undefined) return undefined;\n\tconst label = trimOptionalText(record.label);\n\tconst ariaLabel = trimOptionalText(record.ariaLabel);\n\treturn {\n\t\trate,\n\t\t...(label ? { label } : {}),\n\t\t...(ariaLabel ? { ariaLabel } : {}),\n\t};\n};\n\nexport const normalizeTTSSpeedOptionConfigs = (\n\tvalue: unknown,\n): TTSSpeedOption[] => {\n\tif (!Array.isArray(value)) return [...DEFAULT_TTS_SPEED_OPTIONS];\n\tif (value.length === 0) return [];\n\tconst dedupedRates = new Set<number>();\n\tconst normalized: TTSSpeedOption[] = [];\n\tfor (const entry of value) {\n\t\tconst option = normalizeTTSSpeedOptionConfig(entry);\n\t\tif (option === undefined) continue;\n\t\tconst rate = typeof option === \"number\" ? option : option.rate;\n\t\tif (dedupedRates.has(rate)) continue;\n\t\tdedupedRates.add(rate);\n\t\tnormalized.push(option);\n\t}\n\treturn normalized.length ? normalized : [...DEFAULT_TTS_SPEED_OPTIONS];\n};\n\n/**\n * Resolves inline toolbar speed options from host config.\n * - Omitted/non-array: default speed buttons.\n * - Empty array: hide speed buttons.\n * - Arrays that sanitize to no valid values: default speed buttons.\n * - 1.0× is never shown as a discrete speed button.\n */\nexport const normalizeTTSSpeedOptions = (value: unknown): number[] => {\n\tif (!Array.isArray(value)) return [...DEFAULT_TTS_SPEED_OPTIONS];\n\tif (value.length === 0) return [];\n\treturn normalizeTTSSpeedOptionConfigs(value).map((option) =>\n\t\ttypeof option === \"number\" ? option : option.rate,\n\t);\n};\n\nexport const normalizeTTSSpeedControlOptions = (\n\tvalue: unknown,\n): NormalizedTTSSpeedOption[] =>\n\tnormalizeTTSSpeedOptionConfigs(value).map((option) => {\n\t\tif (typeof option === \"number\") {\n\t\t\tconst label = formatSpeedLabel(option);\n\t\t\treturn {\n\t\t\t\trate: option,\n\t\t\t\tlabel,\n\t\t\t\tariaLabel: formatSpeedAriaLabel(label, true),\n\t\t\t};\n\t\t}\n\t\tconst defaultLabel = formatSpeedLabel(option.rate);\n\t\tconst label = option.label || defaultLabel;\n\t\treturn {\n\t\t\trate: option.rate,\n\t\t\tlabel,\n\t\t\tariaLabel: normalizeSpeedAriaLabel(\n\t\t\t\tlabel,\n\t\t\t\toption.ariaLabel,\n\t\t\t\tlabel === defaultLabel,\n\t\t\t),\n\t\t};\n\t});\n\nexport const formatTTSSpeedOptionsAsText = (values: number[]): string =>\n\tvalues.join(\", \");\n\n/**\n * Parse comma/semicolon-separated multipliers from settings UI text.\n * - Empty or whitespace-only string → hide speed buttons (`[]`).\n * - Non-empty text with no parseable finite numbers → same as invalid-only array config: defaults.\n */\nexport const parseTTSSpeedOptionsFromText = (text: string): number[] => {\n\tconst trimmed = text.trim();\n\tif (!trimmed) return [];\n\tconst parts = trimmed\n\t\t.split(/[,;]+/)\n\t\t.map((p) => p.trim())\n\t\t.filter(Boolean);\n\tconst candidates = parts\n\t\t.map((p) => Number.parseFloat(p))\n\t\t.filter((n) => Number.isFinite(n));\n\tif (!candidates.length) return [...DEFAULT_TTS_SPEED_OPTIONS];\n\treturn normalizeTTSSpeedOptions(candidates);\n};\n\nconst applyRuntimeDefaults = (\n\tconfig: TTSRuntimeSettings,\n): TTSRuntimeSettings => {\n\tconst withLayoutDefaults: TTSRuntimeSettings = {\n\t\t...config,\n\t\tlayoutMode: normalizeTTSLayoutMode(config.layoutMode),\n\t};\n\tconst backend = config.backend || \"browser\";\n\tif (!isServerBackend(backend)) return withLayoutDefaults;\n\n\tconst withServerDefaults: TTSRuntimeSettings = {\n\t\t...withLayoutDefaults,\n\t\tapiEndpoint: withDefault(withLayoutDefaults.apiEndpoint, \"/api/tts\"),\n\t\ttransportMode: withDefault(withLayoutDefaults.transportMode, \"pie\"),\n\t\tendpointValidationMode: withDefault(\n\t\t\twithLayoutDefaults.endpointValidationMode,\n\t\t\t\"voices\",\n\t\t),\n\t\tvalidateEndpoint: withDefault(withLayoutDefaults.validateEndpoint, true),\n\t\tincludeAuthOnAssetFetch: withDefault(\n\t\t\twithLayoutDefaults.includeAuthOnAssetFetch,\n\t\t\tfalse,\n\t\t),\n\t\trate: withDefault(withLayoutDefaults.rate, 1.0),\n\t\tlanguage: withDefault(withLayoutDefaults.language, \"en-US\"),\n\t};\n\n\tif (backend === \"polly\") {\n\t\treturn {\n\t\t\t...withServerDefaults,\n\t\t\tdefaultVoice: withDefault(withServerDefaults.defaultVoice, \"Joanna\"),\n\t\t\tengine: withDefault(withServerDefaults.engine, \"neural\"),\n\t\t\tformat: withDefault(withServerDefaults.format, \"mp3\"),\n\t\t\tspeechMarksMode: withDefault(\n\t\t\t\twithServerDefaults.speechMarksMode,\n\t\t\t\t\"word+sentence\",\n\t\t\t),\n\t\t};\n\t}\n\n\tif (backend === \"google\") {\n\t\treturn {\n\t\t\t...withServerDefaults,\n\t\t\tdefaultVoice: withDefault(\n\t\t\t\twithServerDefaults.defaultVoice,\n\t\t\t\t\"en-US-Wavenet-A\",\n\t\t\t),\n\t\t};\n\t}\n\n\treturn withServerDefaults;\n};\n\nexport const resolveTTSRuntimeSettings = (\n\tconfig: ToolProviderConfig | TTSRuntimeSettings | undefined,\n): TTSRuntimeSettings => {\n\tconst configRecord = toRecord(config);\n\tconst settingsRecord = toRecord(configRecord.settings);\n\treturn applyRuntimeDefaults({\n\t\t...configRecord,\n\t\t...settingsRecord,\n\t} as TTSRuntimeSettings);\n};\n\nexport const resolveTTSBackend = (\n\tconfig: TTSRuntimeSettings,\n): NonNullable<TTSRuntimeSettings[\"backend\"]> => config.backend || \"browser\";\n\nexport const resolveRuntimeProvider = (\n\tconfig: TTSRuntimeSettings,\n\tbackend: NonNullable<TTSRuntimeSettings[\"backend\"]>,\n): TTSRuntimeSettings[\"serverProvider\"] => {\n\tif (backend === \"polly\" || backend === \"google\") return backend;\n\tif (backend === \"server\") {\n\t\treturn config.serverProvider || config.provider;\n\t}\n\treturn config.serverProvider || config.provider;\n};\n\nexport const resolveTransportMode = (\n\tconfig: TTSRuntimeSettings,\n\truntimeProvider: TTSRuntimeSettings[\"serverProvider\"],\n): NonNullable<TTSRuntimeSettings[\"transportMode\"]> =>\n\tconfig.transportMode || (runtimeProvider === \"custom\" ? \"custom\" : \"pie\");\n\nexport const buildRuntimeTTSConfig = (\n\tconfig: TTSRuntimeSettings,\n): Partial<TTSConfig> => {\n\tconst backend = resolveTTSBackend(config);\n\tconst runtimeProvider = resolveRuntimeProvider(config, backend);\n\tconst transportMode = resolveTransportMode(config, runtimeProvider);\n\tconst mathSpeech = normalizeSREMathSpeechOptions(config.mathSpeech);\n\treturn {\n\t\tvoice: config.defaultVoice,\n\t\trate: config.rate,\n\t\tpitch: config.pitch,\n\t\tproviderOptions: {\n\t\t\t...(config.language ? { locale: config.language } : {}),\n\t\t\t...(backend === \"polly\" && config.engine\n\t\t\t\t? { engine: config.engine }\n\t\t\t\t: {}),\n\t\t\t...(backend === \"polly\" && typeof config.sampleRate === \"number\"\n\t\t\t\t? { sampleRate: config.sampleRate }\n\t\t\t\t: {}),\n\t\t\t...(backend === \"polly\" && config.format\n\t\t\t\t? { format: config.format }\n\t\t\t\t: {}),\n\t\t\t...(backend === \"polly\"\n\t\t\t\t? {\n\t\t\t\t\t\tspeechMarkTypes:\n\t\t\t\t\t\t\tconfig.speechMarksMode === \"word+sentence\"\n\t\t\t\t\t\t\t\t? [\"word\", \"sentence\"]\n\t\t\t\t\t\t\t\t: [\"word\"],\n\t\t\t\t\t}\n\t\t\t\t: {}),\n\t\t\t...(transportMode === \"custom\" && typeof config.cache === \"boolean\"\n\t\t\t\t? { cache: config.cache }\n\t\t\t\t: {}),\n\t\t\t...(transportMode === \"custom\" && config.speedRate\n\t\t\t\t? { speedRate: config.speedRate }\n\t\t\t\t: {}),\n\t\t\t...(transportMode === \"custom\" && config.lang_id\n\t\t\t\t? { lang_id: config.lang_id }\n\t\t\t\t: {}),\n\t\t\t...(mathSpeech ? { mathSpeech } : {}),\n\t\t},\n\t\tapiEndpoint: config.apiEndpoint,\n\t\tprovider: runtimeProvider,\n\t\tlanguage: config.language,\n\t\ttransportMode,\n\t\tendpointMode: config.endpointMode,\n\t\tendpointValidationMode: config.endpointValidationMode,\n\t\tincludeAuthOnAssetFetch: config.includeAuthOnAssetFetch,\n\t\tvalidateEndpoint: config.validateEndpoint,\n\t\t// Toolkit-level highlight setting carried through the config channel\n\t\t// (like apiEndpoint/transportMode); consumed by the highlight pipeline,\n\t\t// ignored by providers. Only forwarded when set so the pipeline default\n\t\t// (per-token enabled) applies otherwise.\n\t\t...(typeof config.mathTokenHighlighting === \"boolean\"\n\t\t\t? { mathTokenHighlighting: config.mathTokenHighlighting }\n\t\t\t: {}),\n\t} as Partial<TTSConfig>;\n};\n\nexport const resolveTTSLayoutMode = (\n\tconfig: TTSRuntimeSettings,\n): TTSLayoutMode => normalizeTTSLayoutMode(config.layoutMode);\n\nexport const resolveTTSHostToolbarLayout = (\n\tconfig: TTSRuntimeSettings,\n): TTSHostToolbarLayout => {\n\tconst layoutMode = resolveTTSLayoutMode(config);\n\tswitch (layoutMode) {\n\t\tcase \"reserved-row\":\n\t\t\treturn {\n\t\t\t\tmount: \"before-buttons\",\n\t\t\t\tcontrolsRow: {\n\t\t\t\t\treserveSpace: true,\n\t\t\t\t\texpandWhenToolActive: false,\n\t\t\t\t},\n\t\t\t};\n\t\tcase \"expanding-row\":\n\t\t\treturn {\n\t\t\t\tmount: \"before-buttons\",\n\t\t\t\tcontrolsRow: {\n\t\t\t\t\treserveSpace: false,\n\t\t\t\t\texpandWhenToolActive: true,\n\t\t\t\t},\n\t\t\t};\n\t\tcase \"floating-overlay\":\n\t\tcase \"left-aligned\":\n\t\tdefault:\n\t\t\treturn {\n\t\t\t\tmount: \"before-buttons\",\n\t\t\t\tcontrolsRow: {\n\t\t\t\t\treserveSpace: false,\n\t\t\t\t\texpandWhenToolActive: false,\n\t\t\t\t},\n\t\t\t};\n\t}\n};\n"]}
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { hasReadableText } from "../../services/tool-context.js";
|
|
11
11
|
import { createScopedToolId } from "../../services/tool-instance-id.js";
|
|
12
|
-
import { buildRuntimeTTSConfig, normalizeTTSLayoutMode,
|
|
12
|
+
import { buildRuntimeTTSConfig, normalizeTTSLayoutMode, normalizeTTSSpeedControlOptions, normalizeTTSSpeedOptionConfigs, resolveTTSHostToolbarLayout, resolveTTSLayoutMode, resolveTTSBackend, resolveTTSRuntimeSettings, resolveRuntimeProvider, resolveTransportMode, } from "../../services/tts-runtime-config.js";
|
|
13
13
|
import { TTSToolProvider } from "../../services/tool-providers/index.js";
|
|
14
14
|
const inlineTTSControls = new Map();
|
|
15
15
|
export const TOOL_ELEMENT_UNMOUNT_CALLBACK_PROP = "__pieToolElementUnmount";
|
|
@@ -59,7 +59,7 @@ export const ttsToolRegistration = {
|
|
|
59
59
|
settings.layoutMode = normalizeTTSLayoutMode(settings.layoutMode);
|
|
60
60
|
}
|
|
61
61
|
if (settings && "speedOptions" in settings) {
|
|
62
|
-
settings.speedOptions =
|
|
62
|
+
settings.speedOptions = normalizeTTSSpeedOptionConfigs(settings.speedOptions);
|
|
63
63
|
}
|
|
64
64
|
const normalizedConfig = {
|
|
65
65
|
...config,
|
|
@@ -68,7 +68,7 @@ export const ttsToolRegistration = {
|
|
|
68
68
|
normalizedConfig.layoutMode = normalizeTTSLayoutMode(normalizedConfig.layoutMode);
|
|
69
69
|
}
|
|
70
70
|
if ("speedOptions" in normalizedConfig) {
|
|
71
|
-
normalizedConfig.speedOptions =
|
|
71
|
+
normalizedConfig.speedOptions = normalizeTTSSpeedOptionConfigs(normalizedConfig.speedOptions);
|
|
72
72
|
}
|
|
73
73
|
if (settings) {
|
|
74
74
|
normalizedConfig.settings = settings;
|
|
@@ -101,7 +101,7 @@ export const ttsToolRegistration = {
|
|
|
101
101
|
undefined);
|
|
102
102
|
const resolveElementSpeedOptions = () => {
|
|
103
103
|
const runtimeSettings = resolveRuntimeSettings();
|
|
104
|
-
return
|
|
104
|
+
return normalizeTTSSpeedControlOptions(runtimeSettings.speedOptions);
|
|
105
105
|
};
|
|
106
106
|
const resolveLayoutMode = () => resolveTTSLayoutMode(resolveRuntimeSettings());
|
|
107
107
|
const resolveHostLayout = () => resolveTTSHostToolbarLayout(resolveRuntimeSettings());
|
|
@@ -133,8 +133,7 @@ export const ttsToolRegistration = {
|
|
|
133
133
|
element.setAttribute("language", toolbarContext.language || "en-US");
|
|
134
134
|
element.setAttribute("size", resolveControlSize());
|
|
135
135
|
element.setAttribute("layout-mode", resolveLayoutMode());
|
|
136
|
-
element.speedOptions =
|
|
137
|
-
resolveElementSpeedOptions();
|
|
136
|
+
element.speedOptions = resolveElementSpeedOptions();
|
|
138
137
|
return element;
|
|
139
138
|
};
|
|
140
139
|
const hostLayout = resolveHostLayout();
|
|
@@ -171,8 +170,7 @@ export const ttsToolRegistration = {
|
|
|
171
170
|
element.setAttribute("language", toolbarContext.language || "en-US");
|
|
172
171
|
element.setAttribute("size", resolveControlSize());
|
|
173
172
|
element.setAttribute("layout-mode", resolveLayoutMode());
|
|
174
|
-
element.speedOptions =
|
|
175
|
-
resolveElementSpeedOptions();
|
|
173
|
+
element.speedOptions = resolveElementSpeedOptions();
|
|
176
174
|
},
|
|
177
175
|
};
|
|
178
176
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tts.js","sourceRoot":"","sources":["../../../src/tools/registrations/tts.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Text-to-Speech (TTS) Tool Registration\n *\n * Registers the TTS tool for reading content aloud.\n *\n * Maps to QTI 3.0 standard access features:\n * - textToSpeech (auditory support)\n * - readAloud (auditory support)\n */\n\nimport type {\n\tToolRegistration,\n\tToolToolbarRenderResult,\n\tToolbarContext,\n} from \"../../services/ToolRegistry.js\";\nimport type { ToolContext } from \"../../services/tool-context.js\";\nimport { hasReadableText } from \"../../services/tool-context.js\";\nimport { createScopedToolId } from \"../../services/tool-instance-id.js\";\nimport {\n\tbuildRuntimeTTSConfig,\n\tnormalizeTTSLayoutMode,\n\tnormalizeTTSSpeedOptions,\n\tresolveTTSHostToolbarLayout,\n\tresolveTTSLayoutMode,\n\tresolveTTSBackend,\n\tresolveTTSRuntimeSettings,\n\tresolveRuntimeProvider,\n\tresolveTransportMode,\n} from \"../../services/tts-runtime-config.js\";\nimport { TTSToolProvider } from \"../../services/tool-providers/index.js\";\n\nconst inlineTTSControls = new Map<string, HTMLElement>();\nexport const TOOL_ELEMENT_UNMOUNT_CALLBACK_PROP = \"__pieToolElementUnmount\";\nexport const TOOL_ACTIVE_CHANGE_EVENT = \"pie-tool-active-change\";\n\n/**\n * Text-to-Speech tool registration\n *\n * Supports:\n * - Reading content aloud using browser TTS or external providers\n * - Context-aware visibility (shows when readable text is available)\n * - All levels except assessment and element\n */\nexport const ttsToolRegistration: ToolRegistration = {\n\ttoolId: \"textToSpeech\",\n\tname: \"Text to Speech\",\n\tdescription: \"Read content aloud\",\n\ticon: \"volume-up\",\n\tprovider: {\n\t\tgetProviderId: () => \"tts\",\n\t\tcreateProvider: (config) => {\n\t\t\tconst settings = resolveTTSRuntimeSettings(config);\n\t\t\treturn new TTSToolProvider(resolveTTSBackend(settings));\n\t\t},\n\t\tgetInitConfig: (config) => {\n\t\t\tconst settings = resolveTTSRuntimeSettings(config);\n\t\t\tconst backend = resolveTTSBackend(settings);\n\t\t\tconst serverProvider = resolveRuntimeProvider(settings, backend);\n\t\t\tconst transportMode = resolveTransportMode(settings, serverProvider);\n\t\t\treturn {\n\t\t\t\tbackend,\n\t\t\t\tserverProvider,\n\t\t\t\ttransportMode,\n\t\t\t\t...buildRuntimeTTSConfig(settings),\n\t\t\t};\n\t\t},\n\t\tgetAuthFetcher: (config) => {\n\t\t\tconst runtimeAuthFetcher = config?.provider?.runtime?.authFetcher;\n\t\t\treturn typeof runtimeAuthFetcher === \"function\"\n\t\t\t\t? runtimeAuthFetcher\n\t\t\t\t: undefined;\n\t\t},\n\t\tsanitizeConfig: (config) => {\n\t\t\tconst settings =\n\t\t\t\tconfig.settings && typeof config.settings === \"object\"\n\t\t\t\t\t? { ...(config.settings as Record<string, unknown>) }\n\t\t\t\t\t: undefined;\n\t\t\tif (settings && \"layoutMode\" in settings) {\n\t\t\t\tsettings.layoutMode = normalizeTTSLayoutMode(settings.layoutMode);\n\t\t\t}\n\t\t\tif (settings && \"speedOptions\" in settings) {\n\t\t\t\tsettings.speedOptions = normalizeTTSSpeedOptions(settings.speedOptions);\n\t\t\t}\n\t\t\tconst normalizedConfig: Record<string, unknown> = {\n\t\t\t\t...(config as Record<string, unknown>),\n\t\t\t};\n\t\t\tif (\"layoutMode\" in normalizedConfig) {\n\t\t\t\tnormalizedConfig.layoutMode = normalizeTTSLayoutMode(\n\t\t\t\t\tnormalizedConfig.layoutMode,\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (\"speedOptions\" in normalizedConfig) {\n\t\t\t\tnormalizedConfig.speedOptions = normalizeTTSSpeedOptions(\n\t\t\t\t\tnormalizedConfig.speedOptions,\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (settings) {\n\t\t\t\tnormalizedConfig.settings = settings;\n\t\t\t}\n\t\t\treturn normalizedConfig as typeof config;\n\t\t},\n\t\tlazy: true,\n\t},\n\n\t// TTS is inline-only and scoped to item/passage contexts.\n\tsupportedLevels: [\"item\", \"passage\"],\n\n\t// PNP support IDs that enable this tool\n\t// Maps to QTI 3.0 standard features: textToSpeech, readAloud\n\tpnpSupportIds: [\n\t\t\"textToSpeech\", // QTI 3.0 standard (auditory.textToSpeech)\n\t\t\"readAloud\", // QTI 3.0 standard (auditory.readAloud)\n\t\t\"tts\", // Common abbreviation\n\t\t\"speechOutput\", // Common variant\n\t],\n\n\t/**\n\t * Pass 2: Determine if TTS is relevant in this context\n\t *\n\t * TTS is relevant when:\n\t * - Context contains readable text (at least 10 characters)\n\t */\n\tisVisibleInContext(context: ToolContext): boolean {\n\t\treturn hasReadableText(context);\n\t},\n\n\trenderToolbar(\n\t\t_context: ToolContext,\n\t\ttoolbarContext: ToolbarContext,\n\t): ToolToolbarRenderResult {\n\t\tconst resolveRuntimeSettings = () =>\n\t\t\tresolveTTSRuntimeSettings(\n\t\t\t\ttoolbarContext.toolkitCoordinator?.getToolConfig(this.toolId) ||\n\t\t\t\t\tundefined,\n\t\t\t);\n\t\tconst resolveElementSpeedOptions = (): number[] => {\n\t\t\tconst runtimeSettings = resolveRuntimeSettings();\n\t\t\treturn normalizeTTSSpeedOptions(runtimeSettings.speedOptions);\n\t\t};\n\t\tconst resolveLayoutMode = () =>\n\t\t\tresolveTTSLayoutMode(resolveRuntimeSettings());\n\t\tconst resolveHostLayout = () =>\n\t\t\tresolveTTSHostToolbarLayout(resolveRuntimeSettings());\n\t\tconst fullToolId = createScopedToolId(\n\t\t\tthis.toolId,\n\t\t\ttoolbarContext.scope.level,\n\t\t\ttoolbarContext.scope.scopeId,\n\t\t);\n\t\tconst resolveControlSize = (): \"sm\" | \"md\" | \"lg\" => {\n\t\t\tconst raw = toolbarContext.ui?.size;\n\t\t\treturn raw === \"sm\" || raw === \"lg\" ? raw : \"md\";\n\t\t};\n\t\tconst ensureElement = (): HTMLElement => {\n\t\t\tlet element = inlineTTSControls.get(fullToolId);\n\t\t\tif (\n\t\t\t\telement &&\n\t\t\t\ttypeof (element as { isConnected?: boolean }).isConnected ===\n\t\t\t\t\t\"boolean\" &&\n\t\t\t\t!(element as { isConnected?: boolean }).isConnected\n\t\t\t) {\n\t\t\t\tinlineTTSControls.delete(fullToolId);\n\t\t\t\telement = undefined;\n\t\t\t}\n\t\t\tif (!element) {\n\t\t\t\telement = document.createElement(\"pie-tool-tts-inline\");\n\t\t\t\t(\n\t\t\t\t\telement as HTMLElement & {\n\t\t\t\t\t\t[key: string]: unknown;\n\t\t\t\t\t}\n\t\t\t\t)[TOOL_ELEMENT_UNMOUNT_CALLBACK_PROP] = () => {\n\t\t\t\t\tif (inlineTTSControls.get(fullToolId) === element) {\n\t\t\t\t\t\tinlineTTSControls.delete(fullToolId);\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\tinlineTTSControls.set(fullToolId, element);\n\t\t\t}\n\t\t\telement.setAttribute(\"tool-id\", fullToolId);\n\t\t\telement.setAttribute(\n\t\t\t\t\"catalog-id\",\n\t\t\t\ttoolbarContext.catalogId || toolbarContext.itemId,\n\t\t\t);\n\t\t\telement.setAttribute(\"language\", toolbarContext.language || \"en-US\");\n\t\t\telement.setAttribute(\"size\", resolveControlSize());\n\t\t\telement.setAttribute(\"layout-mode\", resolveLayoutMode());\n\t\t\t(element as HTMLElement & { speedOptions?: number[] }).speedOptions =\n\t\t\t\tresolveElementSpeedOptions();\n\t\t\treturn element;\n\t\t};\n\t\tconst hostLayout = resolveHostLayout();\n\n\t\treturn {\n\t\t\ttoolId: this.toolId,\n\t\t\tbutton: null,\n\t\t\telements: [\n\t\t\t\t{\n\t\t\t\t\telement: ensureElement(),\n\t\t\t\t\tmount: hostLayout.mount,\n\t\t\t\t\tlayoutHints: {\n\t\t\t\t\t\tcontrolsRow: {\n\t\t\t\t\t\t\treserveSpace: hostLayout.controlsRow.reserveSpace,\n\t\t\t\t\t\t\tshowWhenToolActive: hostLayout.controlsRow.expandWhenToolActive,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t],\n\t\t\tsubscribeActive: (callback) => {\n\t\t\t\tconst element = ensureElement();\n\t\t\t\tconst handler = (event: Event) => {\n\t\t\t\t\tconst detail = (event as CustomEvent<{ active?: boolean }>).detail;\n\t\t\t\t\tcallback(detail?.active === true);\n\t\t\t\t};\n\t\t\t\telement.addEventListener(TOOL_ACTIVE_CHANGE_EVENT, handler);\n\t\t\t\treturn () => {\n\t\t\t\t\telement.removeEventListener(TOOL_ACTIVE_CHANGE_EVENT, handler);\n\t\t\t\t};\n\t\t\t},\n\t\t\tsync: () => {\n\t\t\t\tconst element = ensureElement();\n\t\t\t\telement.setAttribute(\"tool-id\", fullToolId);\n\t\t\t\telement.setAttribute(\n\t\t\t\t\t\"catalog-id\",\n\t\t\t\t\ttoolbarContext.catalogId || toolbarContext.itemId,\n\t\t\t\t);\n\t\t\t\telement.setAttribute(\"language\", toolbarContext.language || \"en-US\");\n\t\t\t\telement.setAttribute(\"size\", resolveControlSize());\n\t\t\t\telement.setAttribute(\"layout-mode\", resolveLayoutMode());\n\t\t\t\t(element as HTMLElement & { speedOptions?: number[] }).speedOptions =\n\t\t\t\t\tresolveElementSpeedOptions();\n\t\t\t},\n\t\t};\n\t},\n};\n"]}
|
|
1
|
+
{"version":3,"file":"tts.js","sourceRoot":"","sources":["../../../src/tools/registrations/tts.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Text-to-Speech (TTS) Tool Registration\n *\n * Registers the TTS tool for reading content aloud.\n *\n * Maps to QTI 3.0 standard access features:\n * - textToSpeech (auditory support)\n * - readAloud (auditory support)\n */\n\nimport type {\n\tToolRegistration,\n\tToolToolbarRenderResult,\n\tToolbarContext,\n} from \"../../services/ToolRegistry.js\";\nimport type { ToolContext } from \"../../services/tool-context.js\";\nimport { hasReadableText } from \"../../services/tool-context.js\";\nimport { createScopedToolId } from \"../../services/tool-instance-id.js\";\nimport {\n\tbuildRuntimeTTSConfig,\n\tnormalizeTTSLayoutMode,\n\tnormalizeTTSSpeedControlOptions,\n\tnormalizeTTSSpeedOptionConfigs,\n\ttype NormalizedTTSSpeedOption,\n\tresolveTTSHostToolbarLayout,\n\tresolveTTSLayoutMode,\n\tresolveTTSBackend,\n\tresolveTTSRuntimeSettings,\n\tresolveRuntimeProvider,\n\tresolveTransportMode,\n} from \"../../services/tts-runtime-config.js\";\nimport { TTSToolProvider } from \"../../services/tool-providers/index.js\";\n\nconst inlineTTSControls = new Map<string, HTMLElement>();\nexport const TOOL_ELEMENT_UNMOUNT_CALLBACK_PROP = \"__pieToolElementUnmount\";\nexport const TOOL_ACTIVE_CHANGE_EVENT = \"pie-tool-active-change\";\n\n/**\n * Text-to-Speech tool registration\n *\n * Supports:\n * - Reading content aloud using browser TTS or external providers\n * - Context-aware visibility (shows when readable text is available)\n * - All levels except assessment and element\n */\nexport const ttsToolRegistration: ToolRegistration = {\n\ttoolId: \"textToSpeech\",\n\tname: \"Text to Speech\",\n\tdescription: \"Read content aloud\",\n\ticon: \"volume-up\",\n\tprovider: {\n\t\tgetProviderId: () => \"tts\",\n\t\tcreateProvider: (config) => {\n\t\t\tconst settings = resolveTTSRuntimeSettings(config);\n\t\t\treturn new TTSToolProvider(resolveTTSBackend(settings));\n\t\t},\n\t\tgetInitConfig: (config) => {\n\t\t\tconst settings = resolveTTSRuntimeSettings(config);\n\t\t\tconst backend = resolveTTSBackend(settings);\n\t\t\tconst serverProvider = resolveRuntimeProvider(settings, backend);\n\t\t\tconst transportMode = resolveTransportMode(settings, serverProvider);\n\t\t\treturn {\n\t\t\t\tbackend,\n\t\t\t\tserverProvider,\n\t\t\t\ttransportMode,\n\t\t\t\t...buildRuntimeTTSConfig(settings),\n\t\t\t};\n\t\t},\n\t\tgetAuthFetcher: (config) => {\n\t\t\tconst runtimeAuthFetcher = config?.provider?.runtime?.authFetcher;\n\t\t\treturn typeof runtimeAuthFetcher === \"function\"\n\t\t\t\t? runtimeAuthFetcher\n\t\t\t\t: undefined;\n\t\t},\n\t\tsanitizeConfig: (config) => {\n\t\t\tconst settings =\n\t\t\t\tconfig.settings && typeof config.settings === \"object\"\n\t\t\t\t\t? { ...(config.settings as Record<string, unknown>) }\n\t\t\t\t\t: undefined;\n\t\t\tif (settings && \"layoutMode\" in settings) {\n\t\t\t\tsettings.layoutMode = normalizeTTSLayoutMode(settings.layoutMode);\n\t\t\t}\n\t\t\tif (settings && \"speedOptions\" in settings) {\n\t\t\t\tsettings.speedOptions = normalizeTTSSpeedOptionConfigs(\n\t\t\t\t\tsettings.speedOptions,\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst normalizedConfig: Record<string, unknown> = {\n\t\t\t\t...(config as Record<string, unknown>),\n\t\t\t};\n\t\t\tif (\"layoutMode\" in normalizedConfig) {\n\t\t\t\tnormalizedConfig.layoutMode = normalizeTTSLayoutMode(\n\t\t\t\t\tnormalizedConfig.layoutMode,\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (\"speedOptions\" in normalizedConfig) {\n\t\t\t\tnormalizedConfig.speedOptions = normalizeTTSSpeedOptionConfigs(\n\t\t\t\t\tnormalizedConfig.speedOptions,\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (settings) {\n\t\t\t\tnormalizedConfig.settings = settings;\n\t\t\t}\n\t\t\treturn normalizedConfig as typeof config;\n\t\t},\n\t\tlazy: true,\n\t},\n\n\t// TTS is inline-only and scoped to item/passage contexts.\n\tsupportedLevels: [\"item\", \"passage\"],\n\n\t// PNP support IDs that enable this tool\n\t// Maps to QTI 3.0 standard features: textToSpeech, readAloud\n\tpnpSupportIds: [\n\t\t\"textToSpeech\", // QTI 3.0 standard (auditory.textToSpeech)\n\t\t\"readAloud\", // QTI 3.0 standard (auditory.readAloud)\n\t\t\"tts\", // Common abbreviation\n\t\t\"speechOutput\", // Common variant\n\t],\n\n\t/**\n\t * Pass 2: Determine if TTS is relevant in this context\n\t *\n\t * TTS is relevant when:\n\t * - Context contains readable text (at least 10 characters)\n\t */\n\tisVisibleInContext(context: ToolContext): boolean {\n\t\treturn hasReadableText(context);\n\t},\n\n\trenderToolbar(\n\t\t_context: ToolContext,\n\t\ttoolbarContext: ToolbarContext,\n\t): ToolToolbarRenderResult {\n\t\tconst resolveRuntimeSettings = () =>\n\t\t\tresolveTTSRuntimeSettings(\n\t\t\t\ttoolbarContext.toolkitCoordinator?.getToolConfig(this.toolId) ||\n\t\t\t\t\tundefined,\n\t\t\t);\n\t\tconst resolveElementSpeedOptions = (): NormalizedTTSSpeedOption[] => {\n\t\t\tconst runtimeSettings = resolveRuntimeSettings();\n\t\t\treturn normalizeTTSSpeedControlOptions(runtimeSettings.speedOptions);\n\t\t};\n\t\tconst resolveLayoutMode = () =>\n\t\t\tresolveTTSLayoutMode(resolveRuntimeSettings());\n\t\tconst resolveHostLayout = () =>\n\t\t\tresolveTTSHostToolbarLayout(resolveRuntimeSettings());\n\t\tconst fullToolId = createScopedToolId(\n\t\t\tthis.toolId,\n\t\t\ttoolbarContext.scope.level,\n\t\t\ttoolbarContext.scope.scopeId,\n\t\t);\n\t\tconst resolveControlSize = (): \"sm\" | \"md\" | \"lg\" => {\n\t\t\tconst raw = toolbarContext.ui?.size;\n\t\t\treturn raw === \"sm\" || raw === \"lg\" ? raw : \"md\";\n\t\t};\n\t\tconst ensureElement = (): HTMLElement => {\n\t\t\tlet element = inlineTTSControls.get(fullToolId);\n\t\t\tif (\n\t\t\t\telement &&\n\t\t\t\ttypeof (element as { isConnected?: boolean }).isConnected ===\n\t\t\t\t\t\"boolean\" &&\n\t\t\t\t!(element as { isConnected?: boolean }).isConnected\n\t\t\t) {\n\t\t\t\tinlineTTSControls.delete(fullToolId);\n\t\t\t\telement = undefined;\n\t\t\t}\n\t\t\tif (!element) {\n\t\t\t\telement = document.createElement(\"pie-tool-tts-inline\");\n\t\t\t\t(\n\t\t\t\t\telement as HTMLElement & {\n\t\t\t\t\t\t[key: string]: unknown;\n\t\t\t\t\t}\n\t\t\t\t)[TOOL_ELEMENT_UNMOUNT_CALLBACK_PROP] = () => {\n\t\t\t\t\tif (inlineTTSControls.get(fullToolId) === element) {\n\t\t\t\t\t\tinlineTTSControls.delete(fullToolId);\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\tinlineTTSControls.set(fullToolId, element);\n\t\t\t}\n\t\t\telement.setAttribute(\"tool-id\", fullToolId);\n\t\t\telement.setAttribute(\n\t\t\t\t\"catalog-id\",\n\t\t\t\ttoolbarContext.catalogId || toolbarContext.itemId,\n\t\t\t);\n\t\t\telement.setAttribute(\"language\", toolbarContext.language || \"en-US\");\n\t\t\telement.setAttribute(\"size\", resolveControlSize());\n\t\t\telement.setAttribute(\"layout-mode\", resolveLayoutMode());\n\t\t\t(\n\t\t\t\telement as HTMLElement & { speedOptions?: NormalizedTTSSpeedOption[] }\n\t\t\t).speedOptions = resolveElementSpeedOptions();\n\t\t\treturn element;\n\t\t};\n\t\tconst hostLayout = resolveHostLayout();\n\n\t\treturn {\n\t\t\ttoolId: this.toolId,\n\t\t\tbutton: null,\n\t\t\telements: [\n\t\t\t\t{\n\t\t\t\t\telement: ensureElement(),\n\t\t\t\t\tmount: hostLayout.mount,\n\t\t\t\t\tlayoutHints: {\n\t\t\t\t\t\tcontrolsRow: {\n\t\t\t\t\t\t\treserveSpace: hostLayout.controlsRow.reserveSpace,\n\t\t\t\t\t\t\tshowWhenToolActive: hostLayout.controlsRow.expandWhenToolActive,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t],\n\t\t\tsubscribeActive: (callback) => {\n\t\t\t\tconst element = ensureElement();\n\t\t\t\tconst handler = (event: Event) => {\n\t\t\t\t\tconst detail = (event as CustomEvent<{ active?: boolean }>).detail;\n\t\t\t\t\tcallback(detail?.active === true);\n\t\t\t\t};\n\t\t\t\telement.addEventListener(TOOL_ACTIVE_CHANGE_EVENT, handler);\n\t\t\t\treturn () => {\n\t\t\t\t\telement.removeEventListener(TOOL_ACTIVE_CHANGE_EVENT, handler);\n\t\t\t\t};\n\t\t\t},\n\t\t\tsync: () => {\n\t\t\t\tconst element = ensureElement();\n\t\t\t\telement.setAttribute(\"tool-id\", fullToolId);\n\t\t\t\telement.setAttribute(\n\t\t\t\t\t\"catalog-id\",\n\t\t\t\t\ttoolbarContext.catalogId || toolbarContext.itemId,\n\t\t\t\t);\n\t\t\t\telement.setAttribute(\"language\", toolbarContext.language || \"en-US\");\n\t\t\t\telement.setAttribute(\"size\", resolveControlSize());\n\t\t\t\telement.setAttribute(\"layout-mode\", resolveLayoutMode());\n\t\t\t\t(\n\t\t\t\t\telement as HTMLElement & { speedOptions?: NormalizedTTSSpeedOption[] }\n\t\t\t\t).speedOptions = resolveElementSpeedOptions();\n\t\t\t},\n\t\t};\n\t},\n};\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pie-players/pie-assessment-toolkit",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.51",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "PIE assessment toolkit: composable services + reference implementation for assessment players and tool coordination",
|
|
6
6
|
"license": "MIT",
|
|
@@ -75,15 +75,15 @@
|
|
|
75
75
|
"test": "bun test"
|
|
76
76
|
},
|
|
77
77
|
"dependencies": {
|
|
78
|
-
"@pie-players/pie-calculator": "0.3.
|
|
79
|
-
"@pie-players/pie-context": "0.3.
|
|
80
|
-
"@pie-players/pie-players-shared": "0.3.
|
|
81
|
-
"@pie-players/pie-tts": "0.3.
|
|
78
|
+
"@pie-players/pie-calculator": "0.3.51",
|
|
79
|
+
"@pie-players/pie-context": "0.3.51",
|
|
80
|
+
"@pie-players/pie-players-shared": "0.3.51",
|
|
81
|
+
"@pie-players/pie-tts": "0.3.51",
|
|
82
82
|
"speech-rule-engine": "^5.0.0-rc.1"
|
|
83
83
|
},
|
|
84
84
|
"peerDependencies": {
|
|
85
|
-
"@pie-players/pie-calculator-desmos": "0.3.
|
|
86
|
-
"@pie-players/tts-client-server": "0.3.
|
|
85
|
+
"@pie-players/pie-calculator-desmos": "0.3.51",
|
|
86
|
+
"@pie-players/tts-client-server": "0.3.51"
|
|
87
87
|
},
|
|
88
88
|
"peerDependenciesMeta": {
|
|
89
89
|
"@pie-players/pie-calculator-desmos": {
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
const loadSideEffectModule = (load) => load().then(() => undefined);
|
|
2
|
-
/**
|
|
3
|
-
* Default lazy module loaders for built-in tools.
|
|
4
|
-
*
|
|
5
|
-
* Keeping this map near tool registrations avoids hard-coding module imports
|
|
6
|
-
* in runtime components.
|
|
7
|
-
*/
|
|
8
|
-
function loadCalculatorModule() {
|
|
9
|
-
if (typeof globalThis !== "undefined" &&
|
|
10
|
-
"customElements" in globalThis &&
|
|
11
|
-
globalThis.customElements?.get("pie-tool-calculator")) {
|
|
12
|
-
return Promise.resolve();
|
|
13
|
-
}
|
|
14
|
-
return loadSideEffectModule(() =>
|
|
15
|
-
// biome-ignore lint/suspicious/noTsIgnore: module is optional; @ts-expect-error would fail in workspace builds where the symlink resolves
|
|
16
|
-
// @ts-ignore Optional host module may be absent from toolkit package deps.
|
|
17
|
-
import("@pie-players/pie-tool-calculator-desmos"));
|
|
18
|
-
}
|
|
19
|
-
export const DEFAULT_TOOL_MODULE_LOADERS = {
|
|
20
|
-
// Keep toolkit defaults limited to broadly expected modules.
|
|
21
|
-
// Hosts can register additional optional tool loaders as needed.
|
|
22
|
-
calculator: loadCalculatorModule,
|
|
23
|
-
textToSpeech: () => loadSideEffectModule(() =>
|
|
24
|
-
// biome-ignore lint/suspicious/noTsIgnore: module is optional; @ts-expect-error would fail in workspace builds where the symlink resolves
|
|
25
|
-
// @ts-ignore Optional host module may be absent from toolkit package deps.
|
|
26
|
-
import("@pie-players/pie-tool-tts-inline")),
|
|
27
|
-
};
|
|
28
|
-
//# sourceMappingURL=default-tool-module-loaders.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"default-tool-module-loaders.js","sourceRoot":"","sources":["../../src/tools/default-tool-module-loaders.ts"],"names":[],"mappings":"AAEA,MAAM,oBAAoB,GAAG,CAAC,IAA4B,EAAiB,EAAE,CAC5E,IAAI,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;AAE9B;;;;;GAKG;AACH,SAAS,oBAAoB;IAC5B,IACC,OAAO,UAAU,KAAK,WAAW;QACjC,gBAAgB,IAAI,UAAU;QAC9B,UAAU,CAAC,cAAc,EAAE,GAAG,CAAC,qBAAqB,CAAC,EACpD,CAAC;QACF,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;IAC1B,CAAC;IACD,OAAO,oBAAoB,CAC1B,GAAG,EAAE;IACJ,0IAA0I;IAC1I,2EAA2E;IAC3E,MAAM,CAAC,yCAAyC,CAAC,CAClD,CAAC;AACH,CAAC;AAED,MAAM,CAAC,MAAM,2BAA2B,GAAqC;IAC5E,6DAA6D;IAC7D,iEAAiE;IACjE,UAAU,EAAE,oBAAoB;IAChC,YAAY,EAAE,GAAG,EAAE,CAClB,oBAAoB,CACnB,GAAG,EAAE;IACJ,0IAA0I;IAC1I,2EAA2E;IAC3E,MAAM,CAAC,kCAAkC,CAAC,CAC3C;CACF,CAAC","sourcesContent":["import type { ToolModuleLoader } from \"../services/ToolRegistry.js\";\n\nconst loadSideEffectModule = (load: () => Promise<unknown>): Promise<void> =>\n\tload().then(() => undefined);\n\n/**\n * Default lazy module loaders for built-in tools.\n *\n * Keeping this map near tool registrations avoids hard-coding module imports\n * in runtime components.\n */\nfunction loadCalculatorModule(): Promise<unknown> {\n\tif (\n\t\ttypeof globalThis !== \"undefined\" &&\n\t\t\"customElements\" in globalThis &&\n\t\tglobalThis.customElements?.get(\"pie-tool-calculator\")\n\t) {\n\t\treturn Promise.resolve();\n\t}\n\treturn loadSideEffectModule(\n\t\t() =>\n\t\t\t// biome-ignore lint/suspicious/noTsIgnore: module is optional; @ts-expect-error would fail in workspace builds where the symlink resolves\n\t\t\t// @ts-ignore Optional host module may be absent from toolkit package deps.\n\t\t\timport(\"@pie-players/pie-tool-calculator-desmos\"),\n\t);\n}\n\nexport const DEFAULT_TOOL_MODULE_LOADERS: Record<string, ToolModuleLoader> = {\n\t// Keep toolkit defaults limited to broadly expected modules.\n\t// Hosts can register additional optional tool loaders as needed.\n\tcalculator: loadCalculatorModule,\n\ttextToSpeech: () =>\n\t\tloadSideEffectModule(\n\t\t\t() =>\n\t\t\t\t// biome-ignore lint/suspicious/noTsIgnore: module is optional; @ts-expect-error would fail in workspace builds where the symlink resolves\n\t\t\t\t// @ts-ignore Optional host module may be absent from toolkit package deps.\n\t\t\t\timport(\"@pie-players/pie-tool-tts-inline\"),\n\t\t),\n};\n"]}
|