@pie-players/pie-assessment-toolkit 0.3.47 → 0.3.49
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/dist/attempt/AssessmentSession.js +2 -1
- package/dist/attempt/AssessmentSession.js.map +1 -1
- package/dist/attempt/adapters/activity-to-test-attempt-session.js +12 -8
- package/dist/attempt/adapters/activity-to-test-attempt-session.js.map +1 -1
- package/dist/components/ItemToolBar.custom-element.js +56 -17
- package/dist/components/PieAssessmentToolkit.custom-element.js +84 -29
- package/dist/components/SectionToolBar.custom-element.js +55 -17
- package/dist/index.d.ts +1 -1
- package/dist/index.js.map +1 -1
- package/dist/policy/core/pnp-policy-inputs.js +1 -2
- package/dist/policy/core/pnp-policy-inputs.js.map +1 -1
- package/dist/policy/core/provenance.js.map +1 -1
- package/dist/runtime/SectionRuntimeEngine.js +1 -1
- package/dist/runtime/SectionRuntimeEngine.js.map +1 -1
- package/dist/runtime/core/cohort.js.map +1 -1
- package/dist/runtime/core/engine-input.js.map +1 -1
- package/dist/runtime/core/engine-readiness.js.map +1 -1
- package/dist/runtime/core/engine-resolver.js.map +1 -1
- package/dist/runtime/core/engine-transition.js +1 -3
- package/dist/runtime/core/engine-transition.js.map +1 -1
- package/dist/runtime/registration-events.js.map +1 -1
- package/dist/runtime/session-event-emitter-policy.js.map +1 -1
- package/dist/services/HighlightCoordinator.js +2 -1
- package/dist/services/HighlightCoordinator.js.map +1 -1
- package/dist/services/I18nService.d.ts +1 -1
- package/dist/services/I18nService.js.map +1 -1
- package/dist/services/SSMLExtractor.js.map +1 -1
- package/dist/services/TTSService.js +5 -2
- package/dist/services/TTSService.js.map +1 -1
- package/dist/services/ThemeProvider.js.map +1 -1
- package/dist/services/ToolRegistry.d.ts +1 -1
- package/dist/services/ToolRegistry.js.map +1 -1
- package/dist/services/ToolkitCoordinator.js +21 -11
- package/dist/services/ToolkitCoordinator.js.map +1 -1
- package/dist/services/createDefaultToolRegistry.js +4 -2
- package/dist/services/createDefaultToolRegistry.js.map +1 -1
- package/dist/services/framework-error-bus.js.map +1 -1
- package/dist/services/framework-error.js.map +1 -1
- package/dist/services/interfaces.js.map +1 -1
- package/dist/services/section-controller-types.js.map +1 -1
- package/dist/services/tool-config-defaults.js +7 -1
- package/dist/services/tool-config-defaults.js.map +1 -1
- package/dist/services/tool-config-validation.js +3 -1
- package/dist/services/tool-config-validation.js.map +1 -1
- package/dist/services/tool-instance-id.js.map +1 -1
- package/dist/services/tool-providers/DesmosToolProvider.js.map +1 -1
- package/dist/services/tool-providers/TTSToolProvider.js.map +1 -1
- package/dist/services/toolbar-items.js.map +1 -1
- package/dist/services/tools-config-normalizer.js +1 -2
- package/dist/services/tools-config-normalizer.js.map +1 -1
- package/dist/services/tts/browser-provider.js +60 -18
- package/dist/services/tts/browser-provider.js.map +1 -1
- package/dist/services/tts/catalog-span-alignment.js +8 -3
- package/dist/services/tts/catalog-span-alignment.js.map +1 -1
- package/dist/services/tts/generated-speech/assemble-plan.js.map +1 -1
- package/dist/services/tts/generated-speech/index.d.ts +1 -1
- package/dist/services/tts/generated-speech/index.js +1 -1
- package/dist/services/tts/generated-speech/index.js.map +1 -1
- package/dist/services/tts/generated-speech/math-speech-cache.js +3 -1
- package/dist/services/tts/generated-speech/math-speech-cache.js.map +1 -1
- package/dist/services/tts/highlight-pipeline/boundary-normalizer.js.map +1 -1
- package/dist/services/tts/highlight-pipeline/create-highlight-plan.js.map +1 -1
- package/dist/services/tts/math-alignment/mathml-tokenizer.js.map +1 -1
- package/dist/services/tts/math-alignment/sequence-aligner.js.map +1 -1
- package/dist/services/tts/math-alignment/speech-tokenizer.js.map +1 -1
- package/dist/services/tts/math-aware-text-processing.js +6 -3
- package/dist/services/tts/math-aware-text-processing.js.map +1 -1
- package/dist/services/tts/math-speech.js +3 -1
- package/dist/services/tts/math-speech.js.map +1 -1
- package/dist/services/tts/visual-line-ranges.js.map +1 -1
- package/dist/services/tts-runtime-config.js +6 -2
- package/dist/services/tts-runtime-config.js.map +1 -1
- package/dist/tools/calculators/desmos-provider.js.map +1 -1
- package/dist/tools/default-tool-module-loaders.js.map +1 -1
- package/dist/tools/registrations/accessibility-tools.js.map +1 -1
- package/dist/tools/registrations/calculator.js +1 -1
- package/dist/tools/registrations/calculator.js.map +1 -1
- package/dist/tools/registrations/interaction-tools.js.map +1 -1
- package/dist/tools/registrations/measurement-tools.js.map +1 -1
- package/dist/tools/registrations/subject-specific-tools.js +1 -1
- package/dist/tools/registrations/subject-specific-tools.js.map +1 -1
- package/dist/tools/registrations/tts.js +4 -2
- package/dist/tools/registrations/tts.js.map +1 -1
- package/dist/tools/types.js.map +1 -1
- package/dist/tools/variant-resolver.js.map +1 -1
- package/package.json +7 -7
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TTSToolProvider.js","sourceRoot":"","sources":["../../../src/services/tool-providers/TTSToolProvider.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * TTS Tool Provider\n *\n * Unified provider for all TTS backends:\n * - Browser Web Speech API (no auth)\n * - AWS Polly (requires auth via server proxy)\n * - Google Cloud TTS (requires auth via server proxy)\n *\n * Part of PIE Assessment Toolkit.\n */\n\nimport type { ITTSProvider, TTSConfig } from \"@pie-players/pie-tts\";\nimport { BrowserTTSProvider } from \"../../services/tts/browser-provider.js\";\nimport type { ToolProviderApi, ToolProviderCapabilities } from \"./ToolProviderApi.js\";\n\n/**\n * TTS backend type\n */\nexport type TTSBackend =\n\t| \"browser\"\n\t| \"polly\"\n\t| \"google\"\n\t| \"server\";\n\n/**\n * TTS tool provider configuration\n */\nexport interface TTSToolProviderConfig extends Partial<TTSConfig> {\n\t/**\n\t * TTS backend to use\n\t */\n\tbackend: TTSBackend;\n\n\t/**\n\t * Server API endpoint (for server backends)\n\t * @example '/api/tts/synthesize'\n\t * @example 'https://api.example.com/tts/synthesize'\n\t */\n\tapiEndpoint?: string;\n\n\t/**\n\t * Provider to use on server ('polly', 'google')\n\t * Only used when backend is 'server', 'polly', or 'google'\n\t */\n\tserverProvider?: \"polly\" | \"google\" | \"custom\";\n\n\t/**\n\t * Explicit transport mode for server provider payload translation.\n\t */\n\ttransportMode?: \"pie\" | \"custom\";\n\n\t/**\n\t * Endpoint style used by the server-backed provider.\n\t */\n\tendpointMode?: \"synthesizePath\" | \"rootPost\";\n\n\t/**\n\t * Endpoint validation strategy when validateEndpoint=true.\n\t */\n\tendpointValidationMode?: \"voices\" | \"endpoint\" | \"none\";\n\n\t/**\n\t * Include auth header when fetching remote audio/speech-mark assets.\n\t */\n\tincludeAuthOnAssetFetch?: boolean;\n\n\t/**\n\t * Auth token (if required)\n\t * Typically fetched via authFetcher in ToolProviderRegistry\n\t */\n\tauthToken?: string;\n\n\t/**\n\t * Organization ID for multi-tenant applications\n\t */\n\torganizationId?: string;\n\n\t/**\n\t * Default voice to use\n\t */\n\tvoice?: string;\n\n\t/**\n\t * Speech rate (0.25 to 4.0, default 1.0)\n\t */\n\trate?: number;\n\n\t/**\n\t * Speech pitch (0 to 2, default 1.0)\n\t * Note: Only browser backend supports pitch\n\t */\n\tpitch?: number;\n\n\t/**\n\t * Optional telemetry callback for tool/backend instrumentation.\n\t */\n\tonTelemetry?: (\n\t\teventName: string,\n\t\tpayload?: Record<string, unknown>,\n\t) => void | Promise<void>;\n}\n\n/**\n * TTS Tool Provider\n *\n * Wraps TTS providers (Browser, Polly, Google) with the ToolProviderApi interface\n * for use in the ToolProviderRegistry.\n *\n * @example Browser TTS (no auth)\n * ```typescript\n * const provider = new TTSToolProvider();\n * await provider.initialize({ backend: 'browser' });\n * const ttsProvider = await provider.createInstance();\n * ```\n *\n * @example Server TTS (with auth)\n * ```typescript\n * const provider = new TTSToolProvider();\n * await provider.initialize({\n * backend: 'polly',\n * apiEndpoint: '/api/tts/synthesize',\n * authToken: 'bearer-token', // Fetched via authFetcher\n * });\n * const ttsProvider = await provider.createInstance();\n * ```\n */\nexport class TTSToolProvider\n\timplements ToolProviderApi<TTSToolProviderConfig, ITTSProvider>\n{\n\treadonly providerId = \"tts-service\";\n\treadonly providerName = \"Text-to-Speech\";\n\treadonly category = \"tts\" as const;\n\treadonly version = \"1.0\";\n\treadonly requiresAuth: boolean;\n\n\tprivate ttsProvider: ITTSProvider | null = null;\n\tprivate config: TTSToolProviderConfig | null = null;\n\n\tprivate async emitTelemetry(\n\t\teventName: string,\n\t\tpayload?: Record<string, unknown>,\n\t): Promise<void> {\n\t\ttry {\n\t\t\tawait this.config?.onTelemetry?.(eventName, payload);\n\t\t} catch (error) {\n\t\t\tconsole.warn(\"[TTSToolProvider] telemetry callback failed:\", error);\n\t\t}\n\t}\n\n\t/**\n\t * Create TTS tool provider\n\t *\n\t * @param backend TTS backend to use (default: 'browser')\n\t */\n\tconstructor(backend: TTSBackend = \"browser\") {\n\t\tthis.requiresAuth = backend !== \"browser\";\n\t}\n\n\t/**\n\t * Initialize TTS provider\n\t *\n\t * Sets up the appropriate TTS backend.\n\t *\n\t * @param config Configuration with backend type and credentials\n\t * @throws Error if initialization fails or required config missing\n\t */\n\tasync initialize(config: TTSToolProviderConfig): Promise<void> {\n\t\tif (this.ttsProvider) {\n\t\t\tconsole.warn(\n\t\t\t\t\"[TTSToolProvider] Already initialized, skipping reinitialization\",\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tthis.config = config;\n\n\t\tswitch (config.backend) {\n\t\t\tcase \"browser\":\n\t\t\t\tawait this._initializeBrowserTTS(config);\n\t\t\t\tbreak;\n\n\t\t\tcase \"polly\":\n\t\t\tcase \"google\":\n\t\t\tcase \"server\":\n\t\t\t\tawait this._initializeServerTTS(config);\n\t\t\t\tbreak;\n\n\t\t\tdefault:\n\t\t\t\tthrow new Error(`[TTSToolProvider] Unknown backend: ${config.backend}`);\n\t\t}\n\n\t\tconsole.log(\n\t\t\t`[TTSToolProvider] Initialized successfully (backend: ${config.backend})`,\n\t\t);\n\t}\n\n\t/**\n\t * Initialize browser TTS (Web Speech API)\n\t */\n\tprivate async _initializeBrowserTTS(\n\t\tconfig: TTSToolProviderConfig,\n\t): Promise<void> {\n\t\t// Check if Web Speech API is available\n\t\tif (typeof window === \"undefined\" || !(\"speechSynthesis\" in window)) {\n\t\t\tthrow new Error(\n\t\t\t\t\"[TTSToolProvider] Browser TTS not supported (Web Speech API not available)\",\n\t\t\t);\n\t\t}\n\n\t\tthis.ttsProvider = new BrowserTTSProvider();\n\t\tconsole.log(\"[TTSToolProvider] Browser TTS initialized (Web Speech API)\");\n\t}\n\n\t/**\n\t * Initialize server-based TTS (Polly, Google)\n\t */\n\tprivate async _initializeServerTTS(\n\t\tconfig: TTSToolProviderConfig,\n\t): Promise<void> {\n\t\tif (!config.apiEndpoint) {\n\t\t\tthrow new Error(\n\t\t\t\t\"[TTSToolProvider] apiEndpoint required for server-based TTS backends\",\n\t\t\t);\n\t\t}\n\n\t\t// Server backends are optional; load implementation only when requested.\n\t\tconst moduleLoadStartedAt = Date.now();\n\t\tawait this.emitTelemetry(\"pie-tool-library-load-start\", {\n\t\t\ttoolId: \"textToSpeech\",\n\t\t\toperation: \"server-provider-module-import\",\n\t\t\tbackend: config.serverProvider || config.backend,\n\t\t});\n\t\tconst serverModule = await (async () => {\n\t\t\ttry {\n\t\t\t\tconst loaded = (await import(\"@pie-players/tts-client-server\")) as {\n\t\t\t\t\tServerTTSProvider: new () => ITTSProvider;\n\t\t\t\t};\n\t\t\t\tawait this.emitTelemetry(\"pie-tool-library-load-success\", {\n\t\t\t\t\ttoolId: \"textToSpeech\",\n\t\t\t\t\toperation: \"server-provider-module-import\",\n\t\t\t\t\tbackend: config.serverProvider || config.backend,\n\t\t\t\t\tduration: Date.now() - moduleLoadStartedAt,\n\t\t\t\t});\n\t\t\t\treturn loaded;\n\t\t\t} catch (error) {\n\t\t\t\tawait this.emitTelemetry(\"pie-tool-library-load-error\", {\n\t\t\t\ttoolId: \"textToSpeech\",\n\t\t\t\t\toperation: \"server-provider-module-import\",\n\t\t\t\t\tbackend: config.serverProvider || config.backend,\n\t\t\t\t\tduration: Date.now() - moduleLoadStartedAt,\n\t\t\t\t\terrorType: \"ToolLibraryLoadError\",\n\t\t\t\t\tmessage: error instanceof Error ? error.message : String(error),\n\t\t\t\t});\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t})();\n\t\tthis.ttsProvider = new serverModule.ServerTTSProvider();\n\n\t\tconsole.log(\n\t\t\t`[TTSToolProvider] Server TTS initialized (provider: ${config.serverProvider || config.backend})`,\n\t\t);\n\t}\n\n\t/**\n\t * Create a TTS provider instance\n\t *\n\t * Returns the initialized TTS provider.\n\t *\n\t * @param config Optional instance-specific configuration (currently unused)\n\t * @returns TTS provider\n\t * @throws Error if provider not initialized\n\t */\n\tasync createInstance(\n\t\tconfig?: Partial<TTSToolProviderConfig>,\n\t): Promise<ITTSProvider> {\n\t\tif (!this.ttsProvider) {\n\t\t\tthrow new Error(\n\t\t\t\t\"[TTSToolProvider] Provider not initialized. Call initialize() first.\",\n\t\t\t);\n\t\t}\n\n\t\treturn this.ttsProvider;\n\t}\n\n\t/**\n\t * Get provider capabilities\n\t *\n\t * @returns TTS capabilities based on backend\n\t */\n\tgetCapabilities(): ToolProviderCapabilities {\n\t\tconst isBrowser = this.config?.backend === \"browser\";\n\n\t\treturn {\n\t\t\tsupportsOffline: isBrowser,\n\t\t\trequiresAuth: !isBrowser,\n\t\t\tmaxInstances: 1, // Single TTS instance (playback is sequential)\n\t\t\tfeatures: {\n\t\t\t\twordBoundary: true, // All backends support word highlighting\n\t\t\t\tpause: true,\n\t\t\t\tresume: true,\n\t\t\t\trateControl: true,\n\t\t\t\tpitchControl: isBrowser, // Only browser supports pitch\n\t\t\t\tvoiceSelection: true,\n\t\t\t},\n\t\t};\n\t}\n\n\t/**\n\t * Check if provider is ready\n\t *\n\t * @returns true if provider is initialized\n\t */\n\tisReady(): boolean {\n\t\treturn this.ttsProvider !== null;\n\t}\n\n\t/**\n\t * Clean up provider resources\n\t *\n\t * Destroys the TTS provider and releases resources.\n\t */\n\tdestroy(): void {\n\t\tif (this.ttsProvider) {\n\t\t\tthis.ttsProvider.destroy();\n\t\t\tthis.ttsProvider = null;\n\t\t}\n\t\tthis.config = null;\n\t\tconsole.log(\"[TTSToolProvider] Destroyed\");\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"TTSToolProvider.js","sourceRoot":"","sources":["../../../src/services/tool-providers/TTSToolProvider.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * TTS Tool Provider\n *\n * Unified provider for all TTS backends:\n * - Browser Web Speech API (no auth)\n * - AWS Polly (requires auth via server proxy)\n * - Google Cloud TTS (requires auth via server proxy)\n *\n * Part of PIE Assessment Toolkit.\n */\n\nimport type { ITTSProvider, TTSConfig } from \"@pie-players/pie-tts\";\nimport { BrowserTTSProvider } from \"../../services/tts/browser-provider.js\";\nimport type {\n\tToolProviderApi,\n\tToolProviderCapabilities,\n} from \"./ToolProviderApi.js\";\n\n/**\n * TTS backend type\n */\nexport type TTSBackend = \"browser\" | \"polly\" | \"google\" | \"server\";\n\n/**\n * TTS tool provider configuration\n */\nexport interface TTSToolProviderConfig extends Partial<TTSConfig> {\n\t/**\n\t * TTS backend to use\n\t */\n\tbackend: TTSBackend;\n\n\t/**\n\t * Server API endpoint (for server backends)\n\t * @example '/api/tts/synthesize'\n\t * @example 'https://api.example.com/tts/synthesize'\n\t */\n\tapiEndpoint?: string;\n\n\t/**\n\t * Provider to use on server ('polly', 'google')\n\t * Only used when backend is 'server', 'polly', or 'google'\n\t */\n\tserverProvider?: \"polly\" | \"google\" | \"custom\";\n\n\t/**\n\t * Explicit transport mode for server provider payload translation.\n\t */\n\ttransportMode?: \"pie\" | \"custom\";\n\n\t/**\n\t * Endpoint style used by the server-backed provider.\n\t */\n\tendpointMode?: \"synthesizePath\" | \"rootPost\";\n\n\t/**\n\t * Endpoint validation strategy when validateEndpoint=true.\n\t */\n\tendpointValidationMode?: \"voices\" | \"endpoint\" | \"none\";\n\n\t/**\n\t * Include auth header when fetching remote audio/speech-mark assets.\n\t */\n\tincludeAuthOnAssetFetch?: boolean;\n\n\t/**\n\t * Auth token (if required)\n\t * Typically fetched via authFetcher in ToolProviderRegistry\n\t */\n\tauthToken?: string;\n\n\t/**\n\t * Organization ID for multi-tenant applications\n\t */\n\torganizationId?: string;\n\n\t/**\n\t * Default voice to use\n\t */\n\tvoice?: string;\n\n\t/**\n\t * Speech rate (0.25 to 4.0, default 1.0)\n\t */\n\trate?: number;\n\n\t/**\n\t * Speech pitch (0 to 2, default 1.0)\n\t * Note: Only browser backend supports pitch\n\t */\n\tpitch?: number;\n\n\t/**\n\t * Optional telemetry callback for tool/backend instrumentation.\n\t */\n\tonTelemetry?: (\n\t\teventName: string,\n\t\tpayload?: Record<string, unknown>,\n\t) => void | Promise<void>;\n}\n\n/**\n * TTS Tool Provider\n *\n * Wraps TTS providers (Browser, Polly, Google) with the ToolProviderApi interface\n * for use in the ToolProviderRegistry.\n *\n * @example Browser TTS (no auth)\n * ```typescript\n * const provider = new TTSToolProvider();\n * await provider.initialize({ backend: 'browser' });\n * const ttsProvider = await provider.createInstance();\n * ```\n *\n * @example Server TTS (with auth)\n * ```typescript\n * const provider = new TTSToolProvider();\n * await provider.initialize({\n * backend: 'polly',\n * apiEndpoint: '/api/tts/synthesize',\n * authToken: 'bearer-token', // Fetched via authFetcher\n * });\n * const ttsProvider = await provider.createInstance();\n * ```\n */\nexport class TTSToolProvider\n\timplements ToolProviderApi<TTSToolProviderConfig, ITTSProvider>\n{\n\treadonly providerId = \"tts-service\";\n\treadonly providerName = \"Text-to-Speech\";\n\treadonly category = \"tts\" as const;\n\treadonly version = \"1.0\";\n\treadonly requiresAuth: boolean;\n\n\tprivate ttsProvider: ITTSProvider | null = null;\n\tprivate config: TTSToolProviderConfig | null = null;\n\n\tprivate async emitTelemetry(\n\t\teventName: string,\n\t\tpayload?: Record<string, unknown>,\n\t): Promise<void> {\n\t\ttry {\n\t\t\tawait this.config?.onTelemetry?.(eventName, payload);\n\t\t} catch (error) {\n\t\t\tconsole.warn(\"[TTSToolProvider] telemetry callback failed:\", error);\n\t\t}\n\t}\n\n\t/**\n\t * Create TTS tool provider\n\t *\n\t * @param backend TTS backend to use (default: 'browser')\n\t */\n\tconstructor(backend: TTSBackend = \"browser\") {\n\t\tthis.requiresAuth = backend !== \"browser\";\n\t}\n\n\t/**\n\t * Initialize TTS provider\n\t *\n\t * Sets up the appropriate TTS backend.\n\t *\n\t * @param config Configuration with backend type and credentials\n\t * @throws Error if initialization fails or required config missing\n\t */\n\tasync initialize(config: TTSToolProviderConfig): Promise<void> {\n\t\tif (this.ttsProvider) {\n\t\t\tconsole.warn(\n\t\t\t\t\"[TTSToolProvider] Already initialized, skipping reinitialization\",\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tthis.config = config;\n\n\t\tswitch (config.backend) {\n\t\t\tcase \"browser\":\n\t\t\t\tawait this._initializeBrowserTTS(config);\n\t\t\t\tbreak;\n\n\t\t\tcase \"polly\":\n\t\t\tcase \"google\":\n\t\t\tcase \"server\":\n\t\t\t\tawait this._initializeServerTTS(config);\n\t\t\t\tbreak;\n\n\t\t\tdefault:\n\t\t\t\tthrow new Error(`[TTSToolProvider] Unknown backend: ${config.backend}`);\n\t\t}\n\n\t\tconsole.log(\n\t\t\t`[TTSToolProvider] Initialized successfully (backend: ${config.backend})`,\n\t\t);\n\t}\n\n\t/**\n\t * Initialize browser TTS (Web Speech API)\n\t */\n\tprivate async _initializeBrowserTTS(\n\t\tconfig: TTSToolProviderConfig,\n\t): Promise<void> {\n\t\t// Check if Web Speech API is available\n\t\tif (typeof window === \"undefined\" || !(\"speechSynthesis\" in window)) {\n\t\t\tthrow new Error(\n\t\t\t\t\"[TTSToolProvider] Browser TTS not supported (Web Speech API not available)\",\n\t\t\t);\n\t\t}\n\n\t\tthis.ttsProvider = new BrowserTTSProvider();\n\t\tconsole.log(\"[TTSToolProvider] Browser TTS initialized (Web Speech API)\");\n\t}\n\n\t/**\n\t * Initialize server-based TTS (Polly, Google)\n\t */\n\tprivate async _initializeServerTTS(\n\t\tconfig: TTSToolProviderConfig,\n\t): Promise<void> {\n\t\tif (!config.apiEndpoint) {\n\t\t\tthrow new Error(\n\t\t\t\t\"[TTSToolProvider] apiEndpoint required for server-based TTS backends\",\n\t\t\t);\n\t\t}\n\n\t\t// Server backends are optional; load implementation only when requested.\n\t\tconst moduleLoadStartedAt = Date.now();\n\t\tawait this.emitTelemetry(\"pie-tool-library-load-start\", {\n\t\t\ttoolId: \"textToSpeech\",\n\t\t\toperation: \"server-provider-module-import\",\n\t\t\tbackend: config.serverProvider || config.backend,\n\t\t});\n\t\tconst serverModule = await (async () => {\n\t\t\ttry {\n\t\t\t\tconst loaded = (await import(\"@pie-players/tts-client-server\")) as {\n\t\t\t\t\tServerTTSProvider: new () => ITTSProvider;\n\t\t\t\t};\n\t\t\t\tawait this.emitTelemetry(\"pie-tool-library-load-success\", {\n\t\t\t\t\ttoolId: \"textToSpeech\",\n\t\t\t\t\toperation: \"server-provider-module-import\",\n\t\t\t\t\tbackend: config.serverProvider || config.backend,\n\t\t\t\t\tduration: Date.now() - moduleLoadStartedAt,\n\t\t\t\t});\n\t\t\t\treturn loaded;\n\t\t\t} catch (error) {\n\t\t\t\tawait this.emitTelemetry(\"pie-tool-library-load-error\", {\n\t\t\t\t\ttoolId: \"textToSpeech\",\n\t\t\t\t\toperation: \"server-provider-module-import\",\n\t\t\t\t\tbackend: config.serverProvider || config.backend,\n\t\t\t\t\tduration: Date.now() - moduleLoadStartedAt,\n\t\t\t\t\terrorType: \"ToolLibraryLoadError\",\n\t\t\t\t\tmessage: error instanceof Error ? error.message : String(error),\n\t\t\t\t});\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t})();\n\t\tthis.ttsProvider = new serverModule.ServerTTSProvider();\n\n\t\tconsole.log(\n\t\t\t`[TTSToolProvider] Server TTS initialized (provider: ${config.serverProvider || config.backend})`,\n\t\t);\n\t}\n\n\t/**\n\t * Create a TTS provider instance\n\t *\n\t * Returns the initialized TTS provider.\n\t *\n\t * @param config Optional instance-specific configuration (currently unused)\n\t * @returns TTS provider\n\t * @throws Error if provider not initialized\n\t */\n\tasync createInstance(\n\t\tconfig?: Partial<TTSToolProviderConfig>,\n\t): Promise<ITTSProvider> {\n\t\tif (!this.ttsProvider) {\n\t\t\tthrow new Error(\n\t\t\t\t\"[TTSToolProvider] Provider not initialized. Call initialize() first.\",\n\t\t\t);\n\t\t}\n\n\t\treturn this.ttsProvider;\n\t}\n\n\t/**\n\t * Get provider capabilities\n\t *\n\t * @returns TTS capabilities based on backend\n\t */\n\tgetCapabilities(): ToolProviderCapabilities {\n\t\tconst isBrowser = this.config?.backend === \"browser\";\n\n\t\treturn {\n\t\t\tsupportsOffline: isBrowser,\n\t\t\trequiresAuth: !isBrowser,\n\t\t\tmaxInstances: 1, // Single TTS instance (playback is sequential)\n\t\t\tfeatures: {\n\t\t\t\twordBoundary: true, // All backends support word highlighting\n\t\t\t\tpause: true,\n\t\t\t\tresume: true,\n\t\t\t\trateControl: true,\n\t\t\t\tpitchControl: isBrowser, // Only browser supports pitch\n\t\t\t\tvoiceSelection: true,\n\t\t\t},\n\t\t};\n\t}\n\n\t/**\n\t * Check if provider is ready\n\t *\n\t * @returns true if provider is initialized\n\t */\n\tisReady(): boolean {\n\t\treturn this.ttsProvider !== null;\n\t}\n\n\t/**\n\t * Clean up provider resources\n\t *\n\t * Destroys the TTS provider and releases resources.\n\t */\n\tdestroy(): void {\n\t\tif (this.ttsProvider) {\n\t\t\tthis.ttsProvider.destroy();\n\t\t\tthis.ttsProvider = null;\n\t\t}\n\t\tthis.config = null;\n\t\tconsole.log(\"[TTSToolProvider] Destroyed\");\n\t}\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"toolbar-items.js","sourceRoot":"","sources":["../../src/services/toolbar-items.ts"],"names":[],"mappings":"AA0BA,MAAM,UAAU,iBAAiB,CAAC,IAAiB;IAClD,OAAO,OAAQ,IAAwB,CAAC,IAAI,KAAK,QAAQ,CAAC;AAC3D,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,KAAc;IACrD,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAC9E,MAAM,IAAI,GAAG,KAAgC,CAAC;IAC9C,IAAI,OAAO,IAAI,CAAC,EAAE,KAAK,QAAQ,IAAI,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAC7E,IAAI,OAAO,IAAI,CAAC,KAAK,KAAK,QAAQ,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"toolbar-items.js","sourceRoot":"","sources":["../../src/services/toolbar-items.ts"],"names":[],"mappings":"AA0BA,MAAM,UAAU,iBAAiB,CAAC,IAAiB;IAClD,OAAO,OAAQ,IAAwB,CAAC,IAAI,KAAK,QAAQ,CAAC;AAC3D,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,KAAc;IACrD,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAC9E,MAAM,IAAI,GAAG,KAAgC,CAAC;IAC9C,IAAI,OAAO,IAAI,CAAC,EAAE,KAAK,QAAQ,IAAI,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAC7E,IAAI,OAAO,IAAI,CAAC,KAAK,KAAK,QAAQ,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC;QACnE,OAAO,KAAK,CAAC;IACd,IAAI,IAAI,CAAC,SAAS,KAAK,SAAS,IAAI,OAAO,IAAI,CAAC,SAAS,KAAK,QAAQ;QACrE,OAAO,KAAK,CAAC;IACd,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,IAAI,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC3E,IAAI,IAAI,CAAC,OAAO,KAAK,SAAS,IAAI,OAAO,IAAI,CAAC,OAAO,KAAK,QAAQ;QACjE,OAAO,KAAK,CAAC;IACd,IAAI,IAAI,CAAC,MAAM,KAAK,SAAS,IAAI,OAAO,IAAI,CAAC,MAAM,KAAK,SAAS;QAChE,OAAO,KAAK,CAAC;IACd,IAAI,IAAI,CAAC,QAAQ,KAAK,SAAS,IAAI,OAAO,IAAI,CAAC,QAAQ,KAAK,SAAS;QACpE,OAAO,KAAK,CAAC;IAEd,MAAM,OAAO,GAAG,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ,CAAC;IAC9C,MAAM,UAAU,GAAG,OAAO,IAAI,CAAC,OAAO,KAAK,UAAU,CAAC;IACtD,IAAI,OAAO,KAAK,UAAU;QAAE,OAAO,KAAK,CAAC;IACzC,IAAI,OAAO;QAAE,OAAO,IAAI,CAAC;IACzB,OAAO,UAAU,CAAC;AACnB,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,IAAwB;IACvD,OAAO,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;AACxE,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,IAAwB;IACzD,OAAO,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;AACxE,CAAC","sourcesContent":["export interface ToolbarItemBase {\n\tid: string;\n\tlabel: string;\n\tariaLabel?: string;\n\ticon?: string;\n\ttooltip?: string;\n\tactive?: boolean;\n\tdisabled?: boolean;\n}\n\nexport interface ToolbarButtonItem extends ToolbarItemBase {\n\tonClick: () => void;\n\thref?: never;\n\ttarget?: never;\n\trel?: never;\n}\n\nexport interface ToolbarLinkItem extends ToolbarItemBase {\n\thref: string;\n\ttarget?: \"_self\" | \"_blank\" | \"_parent\" | \"_top\" | string;\n\trel?: string;\n\tonClick?: never;\n}\n\nexport type ToolbarItem = ToolbarButtonItem | ToolbarLinkItem;\n\nexport function isToolbarLinkItem(item: ToolbarItem): item is ToolbarLinkItem {\n\treturn typeof (item as ToolbarLinkItem).href === \"string\";\n}\n\nexport function isValidToolbarItemShape(value: unknown): value is ToolbarItem {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) return false;\n\tconst item = value as Record<string, unknown>;\n\tif (typeof item.id !== \"string\" || item.id.trim().length === 0) return false;\n\tif (typeof item.label !== \"string\" || item.label.trim().length === 0)\n\t\treturn false;\n\tif (item.ariaLabel !== undefined && typeof item.ariaLabel !== \"string\")\n\t\treturn false;\n\tif (item.icon !== undefined && typeof item.icon !== \"string\") return false;\n\tif (item.tooltip !== undefined && typeof item.tooltip !== \"string\")\n\t\treturn false;\n\tif (item.active !== undefined && typeof item.active !== \"boolean\")\n\t\treturn false;\n\tif (item.disabled !== undefined && typeof item.disabled !== \"boolean\")\n\t\treturn false;\n\n\tconst hasHref = typeof item.href === \"string\";\n\tconst hasOnClick = typeof item.onClick === \"function\";\n\tif (hasHref === hasOnClick) return false;\n\tif (hasHref) return true;\n\treturn hasOnClick;\n}\n\nexport function isInlineSvgIcon(icon: string | undefined): boolean {\n\treturn typeof icon === \"string\" && icon.trimStart().startsWith(\"<svg\");\n}\n\nexport function isExternalIconUrl(icon: string | undefined): boolean {\n\treturn typeof icon === \"string\" && icon.trimStart().startsWith(\"http\");\n}\n"]}
|
|
@@ -126,8 +126,7 @@ export function normalizeToolsConfig(input) {
|
|
|
126
126
|
section: normalizeToolList(assertStringArray(placement?.section, "placement.section")).length
|
|
127
127
|
? normalizeToolList(assertStringArray(placement?.section, "placement.section"))
|
|
128
128
|
: [...DEFAULT_PLACEMENT.section],
|
|
129
|
-
item: normalizeToolList(assertStringArray(placement?.item, "placement.item"))
|
|
130
|
-
.length
|
|
129
|
+
item: normalizeToolList(assertStringArray(placement?.item, "placement.item")).length
|
|
131
130
|
? normalizeToolList(assertStringArray(placement?.item, "placement.item"))
|
|
132
131
|
: [...DEFAULT_PLACEMENT.item],
|
|
133
132
|
passage: normalizeToolList(assertStringArray(placement?.passage, "placement.passage")).length
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tools-config-normalizer.js","sourceRoot":"","sources":["../../src/services/tools-config-normalizer.ts"],"names":[],"mappings":"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","sourcesContent":["import { DEFAULT_TOOL_PLACEMENT } from \"./tool-config-defaults.js\";\nimport type { SREMathSpeechOptions } from \"./tts/math-speech.js\";\n\nexport type ToolPlacementLevel = \"section\" | \"item\" | \"passage\";\n\nexport interface ToolPolicyConfig {\n\tallowed?: string[];\n\tblocked?: string[];\n}\n\nexport interface ToolPlacementConfig {\n\tsection?: string[];\n\titem?: string[];\n\tpassage?: string[];\n}\n\nexport interface ToolRuntimeBackendRequest {\n\tpath: string;\n\tmethod?: \"GET\" | \"POST\" | \"PUT\" | \"PATCH\" | \"DELETE\";\n\theaders?: Record<string, string>;\n\tquery?: Record<string, string | number | boolean>;\n\tbody?: unknown;\n}\n\nexport interface ToolRuntimeProviderBridge {\n\t/**\n\t * Optional auth fetch hook for provider initialization.\n\t * Useful for retrieving temporary credentials from a host backend.\n\t */\n\tauthFetcher?: () => Promise<Record<string, unknown>>;\n\t/**\n\t * Optional backend request bridge for rich tool interactions\n\t * beyond one-time auth fetching.\n\t */\n\trequest?: (request: ToolRuntimeBackendRequest) => Promise<unknown>;\n\t/**\n\t * Optional host event emitter.\n\t */\n\temit?: (eventName: string, payload?: Record<string, unknown>) => void | Promise<void>;\n\t/**\n\t * Optional host event subscription bridge.\n\t */\n\tsubscribe?: (\n\t\teventName: string,\n\t\thandler: (payload: unknown) => void,\n\t) => (() => void) | undefined;\n\t[key: string]: unknown;\n}\n\nexport interface ToolRuntimeProviderConfig {\n\tid?: string;\n\tinit?: Record<string, unknown>;\n\truntime?: ToolRuntimeProviderBridge;\n}\n\nexport type TextToSpeechRuntimeProviderId = \"polly\" | \"google\" | \"custom\";\n\nexport interface ToolProviderConfig {\n\tenabled?: boolean;\n\tprovider?: ToolRuntimeProviderConfig;\n\tsettings?: Record<string, unknown>;\n\t[key: string]: unknown;\n}\n\nexport interface TextToSpeechToolProviderSettings\n\textends Record<string, unknown> {\n\tmathSpeech?: SREMathSpeechOptions;\n}\n\nexport interface TextToSpeechToolProviderConfig\n\textends Omit<ToolProviderConfig, \"provider\" | \"settings\"> {\n\tprovider?: ToolRuntimeProviderConfig | TextToSpeechRuntimeProviderId;\n\tserverProvider?: TextToSpeechRuntimeProviderId;\n\tmathSpeech?: SREMathSpeechOptions;\n\tsettings?: TextToSpeechToolProviderSettings;\n}\n\nexport interface ToolProvidersConfig {\n\ttextToSpeech?: TextToSpeechToolProviderConfig;\n\t[key: string]: ToolProviderConfig | TextToSpeechToolProviderConfig | undefined;\n}\n\n/**\n * PNP/profile enforcement mode mirrored on `runtime.tools.pnpEnforcement`\n * (M5 mirror rule). Carried alongside `policy` / `placement` /\n * `providers` so hosts can pin or opt out of PNP/profile precedence via\n * the runtime config without reaching for the toolkit prop directly.\n *\n * - `\"on\"` — force enforcement (engine applies PNP/profile gates).\n * - `\"off\"` — opt out (engine ignores PNP/profile inputs).\n * - omitted / `undefined` — auto-mode (the default). The toolkit\n * coordinator computes the effective mode from PNP/profile policy material on the\n * bound `AssessmentEntity` / `AssessmentItemRef`. See\n * `resolveDefaultPnpEnforcement` in `policy/core/pnp-policy-inputs.ts`.\n */\nexport type ToolsPnpEnforcement = \"on\" | \"off\";\n\nexport interface CanonicalToolsConfig {\n\tpolicy: ToolPolicyConfig;\n\tplacement: Required<ToolPlacementConfig>;\n\tproviders: ToolProvidersConfig;\n\t/**\n\t * Optional PNP/profile enforcement override. Mirrored on\n\t * `runtime.tools.pnpEnforcement`; consumed by\n\t * `<pie-assessment-toolkit>` and `ToolkitCoordinator` as the\n\t * embedded path's equivalent of the standalone `pnp-enforcement`\n\t * attribute. See {@link ToolsPnpEnforcement} for semantics.\n\t */\n\tpnpEnforcement?: ToolsPnpEnforcement;\n}\n\nconst DEFAULT_PLACEMENT: Required<ToolPlacementConfig> = {\n\tsection: [...DEFAULT_TOOL_PLACEMENT.section],\n\titem: [...DEFAULT_TOOL_PLACEMENT.item],\n\tpassage: [...DEFAULT_TOOL_PLACEMENT.passage],\n};\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction assertStringArray(value: unknown, fieldPath: string): string[] {\n\tif (value == null) return [];\n\tif (!Array.isArray(value)) {\n\t\tthrow new Error(\n\t\t\t`Invalid tools config at \"${fieldPath}\": expected an array of tool ids.`,\n\t\t);\n\t}\n\tconst invalidEntry = value.find((entry) => typeof entry !== \"string\");\n\tif (invalidEntry !== undefined) {\n\t\tthrow new Error(\n\t\t\t`Invalid tools config at \"${fieldPath}\": all entries must be strings.`,\n\t\t);\n\t}\n\treturn value as string[];\n}\n\nfunction assertPlacementConfig(value: unknown): ToolPlacementConfig | undefined {\n\tif (value == null) return undefined;\n\tif (!isPlainObject(value)) {\n\t\tthrow new Error(\n\t\t\t'Invalid tools config at \"placement\": expected an object with section/item/passage arrays.',\n\t\t);\n\t}\n\treturn value as ToolPlacementConfig;\n}\n\nfunction assertPolicyConfig(value: unknown): ToolPolicyConfig | undefined {\n\tif (value == null) return undefined;\n\tif (!isPlainObject(value)) {\n\t\tthrow new Error(\n\t\t\t'Invalid tools config at \"policy\": expected an object with allowed/blocked arrays.',\n\t\t);\n\t}\n\treturn value as ToolPolicyConfig;\n}\n\nfunction assertProviderConfig(\n\tproviderId: string,\n\tvalue: unknown,\n): ToolProviderConfig | TextToSpeechToolProviderConfig | undefined {\n\tif (value == null) return undefined;\n\tif (!isPlainObject(value)) {\n\t\tthrow new Error(\n\t\t\t`Invalid tools config at \"providers.${providerId}\": expected an object.`,\n\t\t);\n\t}\n\tconst config = value as ToolProviderConfig;\n\tconst isTTSRuntimeProviderId = (provider: unknown): boolean =>\n\t\tprovider === \"polly\" || provider === \"google\" || provider === \"custom\";\n\tif (\n\t\t\"enabled\" in config &&\n\t\tconfig.enabled !== undefined &&\n\t\ttypeof config.enabled !== \"boolean\"\n\t) {\n\t\tthrow new Error(\n\t\t\t`Invalid tools config at \"providers.${providerId}.enabled\": expected a boolean.`,\n\t\t);\n\t}\n\tif (\n\t\t\"settings\" in config &&\n\t\tconfig.settings !== undefined &&\n\t\t!isPlainObject(config.settings)\n\t) {\n\t\tthrow new Error(\n\t\t\t`Invalid tools config at \"providers.${providerId}.settings\": expected an object.`,\n\t\t);\n\t}\n\tif (\n\t\t\"provider\" in config &&\n\t\tconfig.provider !== undefined &&\n\t\t!isPlainObject(config.provider) &&\n\t\t!(providerId === \"textToSpeech\" && isTTSRuntimeProviderId(config.provider))\n\t) {\n\t\tthrow new Error(\n\t\t\t`Invalid tools config at \"providers.${providerId}.provider\": expected an object.`,\n\t\t);\n\t}\n\treturn config;\n}\n\nfunction assertProvidersConfig(value: unknown): ToolProvidersConfig {\n\tif (value == null) return {};\n\tif (!isPlainObject(value)) {\n\t\tthrow new Error(\n\t\t\t'Invalid tools config at \"providers\": expected an object keyed by tool id.',\n\t\t);\n\t}\n\tconst normalized: ToolProvidersConfig = {};\n\tfor (const [providerId, providerConfig] of Object.entries(value)) {\n\t\tnormalized[providerId] = assertProviderConfig(providerId, providerConfig);\n\t}\n\treturn normalized;\n}\n\nexport function normalizeToolAlias(toolId: string): string {\n\tconst trimmed = toolId.trim();\n\tif (!trimmed) return \"\";\n\treturn trimmed;\n}\n\nexport function normalizeToolList(toolIds: string[] | undefined | null): string[] {\n\tif (!toolIds || toolIds.length === 0) return [];\n\tconst deduped = new Set<string>();\n\tfor (const rawToolId of toolIds) {\n\t\tif (typeof rawToolId !== \"string\") {\n\t\t\tthrow new Error(\n\t\t\t\t\"Invalid tools config: tool list entries must be strings.\",\n\t\t\t);\n\t\t}\n\t\tconst normalized = normalizeToolAlias(rawToolId);\n\t\tif (!normalized) continue;\n\t\tdeduped.add(normalized);\n\t}\n\treturn Array.from(deduped);\n}\n\nexport function parseToolList(input: string | undefined | null): string[] {\n\tif (!input) return [];\n\treturn normalizeToolList(\n\t\tinput\n\t\t\t.split(\",\")\n\t\t\t.map((entry) => entry.trim())\n\t\t\t.filter(Boolean),\n\t);\n}\n\nfunction assertPnpEnforcement(\n\tvalue: unknown,\n\tfieldPath: \"pnpEnforcement\",\n): ToolsPnpEnforcement | undefined {\n\tif (value == null) return undefined;\n\tif (value === \"on\" || value === \"off\") return value;\n\tthrow new Error(\n\t\t`Invalid tools config at \"${fieldPath}\": expected \"on\" or \"off\", got ${JSON.stringify(value)}.`,\n\t);\n}\n\nexport function normalizeToolsConfig(\n\tinput?: Partial<CanonicalToolsConfig> | null,\n): CanonicalToolsConfig {\n\tif (input != null && !isPlainObject(input)) {\n\t\tthrow new Error(\n\t\t\t'Invalid tools config: expected an object with \"policy\", \"placement\", and \"providers\".',\n\t\t);\n\t}\n\tconst policy = assertPolicyConfig(input?.policy);\n\tconst placement = assertPlacementConfig(input?.placement);\n\tconst providers = assertProvidersConfig(input?.providers);\n\tconst pnpEnforcement = assertPnpEnforcement(\n\t\tinput?.pnpEnforcement,\n\t\t\"pnpEnforcement\",\n\t);\n\n\tconst config: CanonicalToolsConfig = {\n\t\tpolicy: {\n\t\t\tallowed: normalizeToolList(assertStringArray(policy?.allowed, \"policy.allowed\")),\n\t\t\tblocked: normalizeToolList(assertStringArray(policy?.blocked, \"policy.blocked\")),\n\t\t},\n\t\tplacement: {\n\t\t\tsection: normalizeToolList(\n\t\t\t\tassertStringArray(placement?.section, \"placement.section\"),\n\t\t\t).length\n\t\t\t\t? normalizeToolList(assertStringArray(placement?.section, \"placement.section\"))\n\t\t\t\t: [...DEFAULT_PLACEMENT.section],\n\t\t\titem: normalizeToolList(assertStringArray(placement?.item, \"placement.item\"))\n\t\t\t\t.length\n\t\t\t\t? normalizeToolList(assertStringArray(placement?.item, \"placement.item\"))\n\t\t\t\t: [...DEFAULT_PLACEMENT.item],\n\t\t\tpassage: normalizeToolList(\n\t\t\t\tassertStringArray(placement?.passage, \"placement.passage\"),\n\t\t\t).length\n\t\t\t\t? normalizeToolList(assertStringArray(placement?.passage, \"placement.passage\"))\n\t\t\t\t: [...DEFAULT_PLACEMENT.passage],\n\t\t},\n\t\tproviders,\n\t};\n\tif (pnpEnforcement) {\n\t\tconfig.pnpEnforcement = pnpEnforcement;\n\t}\n\treturn config;\n}\n\n"]}
|
|
1
|
+
{"version":3,"file":"tools-config-normalizer.js","sourceRoot":"","sources":["../../src/services/tools-config-normalizer.ts"],"names":[],"mappings":"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","sourcesContent":["import { DEFAULT_TOOL_PLACEMENT } from \"./tool-config-defaults.js\";\nimport type { SREMathSpeechOptions } from \"./tts/math-speech.js\";\n\nexport type ToolPlacementLevel = \"section\" | \"item\" | \"passage\";\n\nexport interface ToolPolicyConfig {\n\tallowed?: string[];\n\tblocked?: string[];\n}\n\nexport interface ToolPlacementConfig {\n\tsection?: string[];\n\titem?: string[];\n\tpassage?: string[];\n}\n\nexport interface ToolRuntimeBackendRequest {\n\tpath: string;\n\tmethod?: \"GET\" | \"POST\" | \"PUT\" | \"PATCH\" | \"DELETE\";\n\theaders?: Record<string, string>;\n\tquery?: Record<string, string | number | boolean>;\n\tbody?: unknown;\n}\n\nexport interface ToolRuntimeProviderBridge {\n\t/**\n\t * Optional auth fetch hook for provider initialization.\n\t * Useful for retrieving temporary credentials from a host backend.\n\t */\n\tauthFetcher?: () => Promise<Record<string, unknown>>;\n\t/**\n\t * Optional backend request bridge for rich tool interactions\n\t * beyond one-time auth fetching.\n\t */\n\trequest?: (request: ToolRuntimeBackendRequest) => Promise<unknown>;\n\t/**\n\t * Optional host event emitter.\n\t */\n\temit?: (\n\t\teventName: string,\n\t\tpayload?: Record<string, unknown>,\n\t) => void | Promise<void>;\n\t/**\n\t * Optional host event subscription bridge.\n\t */\n\tsubscribe?: (\n\t\teventName: string,\n\t\thandler: (payload: unknown) => void,\n\t) => (() => void) | undefined;\n\t[key: string]: unknown;\n}\n\nexport interface ToolRuntimeProviderConfig {\n\tid?: string;\n\tinit?: Record<string, unknown>;\n\truntime?: ToolRuntimeProviderBridge;\n}\n\nexport type TextToSpeechRuntimeProviderId = \"polly\" | \"google\" | \"custom\";\n\nexport interface ToolProviderConfig {\n\tenabled?: boolean;\n\tprovider?: ToolRuntimeProviderConfig;\n\tsettings?: Record<string, unknown>;\n\t[key: string]: unknown;\n}\n\nexport interface TextToSpeechToolProviderSettings\n\textends Record<string, unknown> {\n\tmathSpeech?: SREMathSpeechOptions;\n}\n\nexport interface TextToSpeechToolProviderConfig\n\textends Omit<ToolProviderConfig, \"provider\" | \"settings\"> {\n\tprovider?: ToolRuntimeProviderConfig | TextToSpeechRuntimeProviderId;\n\tserverProvider?: TextToSpeechRuntimeProviderId;\n\tmathSpeech?: SREMathSpeechOptions;\n\tsettings?: TextToSpeechToolProviderSettings;\n}\n\nexport interface ToolProvidersConfig {\n\ttextToSpeech?: TextToSpeechToolProviderConfig;\n\t[key: string]:\n\t\t| ToolProviderConfig\n\t\t| TextToSpeechToolProviderConfig\n\t\t| undefined;\n}\n\n/**\n * PNP/profile enforcement mode mirrored on `runtime.tools.pnpEnforcement`\n * (M5 mirror rule). Carried alongside `policy` / `placement` /\n * `providers` so hosts can pin or opt out of PNP/profile precedence via\n * the runtime config without reaching for the toolkit prop directly.\n *\n * - `\"on\"` — force enforcement (engine applies PNP/profile gates).\n * - `\"off\"` — opt out (engine ignores PNP/profile inputs).\n * - omitted / `undefined` — auto-mode (the default). The toolkit\n * coordinator computes the effective mode from PNP/profile policy material on the\n * bound `AssessmentEntity` / `AssessmentItemRef`. See\n * `resolveDefaultPnpEnforcement` in `policy/core/pnp-policy-inputs.ts`.\n */\nexport type ToolsPnpEnforcement = \"on\" | \"off\";\n\nexport interface CanonicalToolsConfig {\n\tpolicy: ToolPolicyConfig;\n\tplacement: Required<ToolPlacementConfig>;\n\tproviders: ToolProvidersConfig;\n\t/**\n\t * Optional PNP/profile enforcement override. Mirrored on\n\t * `runtime.tools.pnpEnforcement`; consumed by\n\t * `<pie-assessment-toolkit>` and `ToolkitCoordinator` as the\n\t * embedded path's equivalent of the standalone `pnp-enforcement`\n\t * attribute. See {@link ToolsPnpEnforcement} for semantics.\n\t */\n\tpnpEnforcement?: ToolsPnpEnforcement;\n}\n\nconst DEFAULT_PLACEMENT: Required<ToolPlacementConfig> = {\n\tsection: [...DEFAULT_TOOL_PLACEMENT.section],\n\titem: [...DEFAULT_TOOL_PLACEMENT.item],\n\tpassage: [...DEFAULT_TOOL_PLACEMENT.passage],\n};\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction assertStringArray(value: unknown, fieldPath: string): string[] {\n\tif (value == null) return [];\n\tif (!Array.isArray(value)) {\n\t\tthrow new Error(\n\t\t\t`Invalid tools config at \"${fieldPath}\": expected an array of tool ids.`,\n\t\t);\n\t}\n\tconst invalidEntry = value.find((entry) => typeof entry !== \"string\");\n\tif (invalidEntry !== undefined) {\n\t\tthrow new Error(\n\t\t\t`Invalid tools config at \"${fieldPath}\": all entries must be strings.`,\n\t\t);\n\t}\n\treturn value as string[];\n}\n\nfunction assertPlacementConfig(\n\tvalue: unknown,\n): ToolPlacementConfig | undefined {\n\tif (value == null) return undefined;\n\tif (!isPlainObject(value)) {\n\t\tthrow new Error(\n\t\t\t'Invalid tools config at \"placement\": expected an object with section/item/passage arrays.',\n\t\t);\n\t}\n\treturn value as ToolPlacementConfig;\n}\n\nfunction assertPolicyConfig(value: unknown): ToolPolicyConfig | undefined {\n\tif (value == null) return undefined;\n\tif (!isPlainObject(value)) {\n\t\tthrow new Error(\n\t\t\t'Invalid tools config at \"policy\": expected an object with allowed/blocked arrays.',\n\t\t);\n\t}\n\treturn value as ToolPolicyConfig;\n}\n\nfunction assertProviderConfig(\n\tproviderId: string,\n\tvalue: unknown,\n): ToolProviderConfig | TextToSpeechToolProviderConfig | undefined {\n\tif (value == null) return undefined;\n\tif (!isPlainObject(value)) {\n\t\tthrow new Error(\n\t\t\t`Invalid tools config at \"providers.${providerId}\": expected an object.`,\n\t\t);\n\t}\n\tconst config = value as ToolProviderConfig;\n\tconst isTTSRuntimeProviderId = (provider: unknown): boolean =>\n\t\tprovider === \"polly\" || provider === \"google\" || provider === \"custom\";\n\tif (\n\t\t\"enabled\" in config &&\n\t\tconfig.enabled !== undefined &&\n\t\ttypeof config.enabled !== \"boolean\"\n\t) {\n\t\tthrow new Error(\n\t\t\t`Invalid tools config at \"providers.${providerId}.enabled\": expected a boolean.`,\n\t\t);\n\t}\n\tif (\n\t\t\"settings\" in config &&\n\t\tconfig.settings !== undefined &&\n\t\t!isPlainObject(config.settings)\n\t) {\n\t\tthrow new Error(\n\t\t\t`Invalid tools config at \"providers.${providerId}.settings\": expected an object.`,\n\t\t);\n\t}\n\tif (\n\t\t\"provider\" in config &&\n\t\tconfig.provider !== undefined &&\n\t\t!isPlainObject(config.provider) &&\n\t\t!(providerId === \"textToSpeech\" && isTTSRuntimeProviderId(config.provider))\n\t) {\n\t\tthrow new Error(\n\t\t\t`Invalid tools config at \"providers.${providerId}.provider\": expected an object.`,\n\t\t);\n\t}\n\treturn config;\n}\n\nfunction assertProvidersConfig(value: unknown): ToolProvidersConfig {\n\tif (value == null) return {};\n\tif (!isPlainObject(value)) {\n\t\tthrow new Error(\n\t\t\t'Invalid tools config at \"providers\": expected an object keyed by tool id.',\n\t\t);\n\t}\n\tconst normalized: ToolProvidersConfig = {};\n\tfor (const [providerId, providerConfig] of Object.entries(value)) {\n\t\tnormalized[providerId] = assertProviderConfig(providerId, providerConfig);\n\t}\n\treturn normalized;\n}\n\nexport function normalizeToolAlias(toolId: string): string {\n\tconst trimmed = toolId.trim();\n\tif (!trimmed) return \"\";\n\treturn trimmed;\n}\n\nexport function normalizeToolList(\n\ttoolIds: string[] | undefined | null,\n): string[] {\n\tif (!toolIds || toolIds.length === 0) return [];\n\tconst deduped = new Set<string>();\n\tfor (const rawToolId of toolIds) {\n\t\tif (typeof rawToolId !== \"string\") {\n\t\t\tthrow new Error(\n\t\t\t\t\"Invalid tools config: tool list entries must be strings.\",\n\t\t\t);\n\t\t}\n\t\tconst normalized = normalizeToolAlias(rawToolId);\n\t\tif (!normalized) continue;\n\t\tdeduped.add(normalized);\n\t}\n\treturn Array.from(deduped);\n}\n\nexport function parseToolList(input: string | undefined | null): string[] {\n\tif (!input) return [];\n\treturn normalizeToolList(\n\t\tinput\n\t\t\t.split(\",\")\n\t\t\t.map((entry) => entry.trim())\n\t\t\t.filter(Boolean),\n\t);\n}\n\nfunction assertPnpEnforcement(\n\tvalue: unknown,\n\tfieldPath: \"pnpEnforcement\",\n): ToolsPnpEnforcement | undefined {\n\tif (value == null) return undefined;\n\tif (value === \"on\" || value === \"off\") return value;\n\tthrow new Error(\n\t\t`Invalid tools config at \"${fieldPath}\": expected \"on\" or \"off\", got ${JSON.stringify(value)}.`,\n\t);\n}\n\nexport function normalizeToolsConfig(\n\tinput?: Partial<CanonicalToolsConfig> | null,\n): CanonicalToolsConfig {\n\tif (input != null && !isPlainObject(input)) {\n\t\tthrow new Error(\n\t\t\t'Invalid tools config: expected an object with \"policy\", \"placement\", and \"providers\".',\n\t\t);\n\t}\n\tconst policy = assertPolicyConfig(input?.policy);\n\tconst placement = assertPlacementConfig(input?.placement);\n\tconst providers = assertProvidersConfig(input?.providers);\n\tconst pnpEnforcement = assertPnpEnforcement(\n\t\tinput?.pnpEnforcement,\n\t\t\"pnpEnforcement\",\n\t);\n\n\tconst config: CanonicalToolsConfig = {\n\t\tpolicy: {\n\t\t\tallowed: normalizeToolList(\n\t\t\t\tassertStringArray(policy?.allowed, \"policy.allowed\"),\n\t\t\t),\n\t\t\tblocked: normalizeToolList(\n\t\t\t\tassertStringArray(policy?.blocked, \"policy.blocked\"),\n\t\t\t),\n\t\t},\n\t\tplacement: {\n\t\t\tsection: normalizeToolList(\n\t\t\t\tassertStringArray(placement?.section, \"placement.section\"),\n\t\t\t).length\n\t\t\t\t? normalizeToolList(\n\t\t\t\t\t\tassertStringArray(placement?.section, \"placement.section\"),\n\t\t\t\t\t)\n\t\t\t\t: [...DEFAULT_PLACEMENT.section],\n\t\t\titem: normalizeToolList(\n\t\t\t\tassertStringArray(placement?.item, \"placement.item\"),\n\t\t\t).length\n\t\t\t\t? normalizeToolList(\n\t\t\t\t\t\tassertStringArray(placement?.item, \"placement.item\"),\n\t\t\t\t\t)\n\t\t\t\t: [...DEFAULT_PLACEMENT.item],\n\t\t\tpassage: normalizeToolList(\n\t\t\t\tassertStringArray(placement?.passage, \"placement.passage\"),\n\t\t\t).length\n\t\t\t\t? normalizeToolList(\n\t\t\t\t\t\tassertStringArray(placement?.passage, \"placement.passage\"),\n\t\t\t\t\t)\n\t\t\t\t: [...DEFAULT_PLACEMENT.passage],\n\t\t},\n\t\tproviders,\n\t};\n\tif (pnpEnforcement) {\n\t\tconfig.pnpEnforcement = pnpEnforcement;\n\t}\n\treturn config;\n}\n"]}
|
|
@@ -6,7 +6,43 @@
|
|
|
6
6
|
*
|
|
7
7
|
* Part of PIE Assessment Toolkit.
|
|
8
8
|
*/
|
|
9
|
-
import { segmentSentences as segmentTextToSentences
|
|
9
|
+
import { segmentSentences as segmentTextToSentences } from "./text-segmentation.js";
|
|
10
|
+
const normalizeLanguageCode = (value) => String(value || "")
|
|
11
|
+
.trim()
|
|
12
|
+
.toLowerCase();
|
|
13
|
+
const browserLanguage = () => {
|
|
14
|
+
const navigatorLanguage = typeof navigator !== "undefined"
|
|
15
|
+
? navigator.language || navigator.languages?.[0]
|
|
16
|
+
: "";
|
|
17
|
+
return normalizeLanguageCode(navigatorLanguage || "en-US");
|
|
18
|
+
};
|
|
19
|
+
const findBrowserVoice = (voices, preferredName) => {
|
|
20
|
+
if (preferredName) {
|
|
21
|
+
const explicit = voices.find((voice) => voice.name === preferredName);
|
|
22
|
+
if (explicit)
|
|
23
|
+
return explicit;
|
|
24
|
+
}
|
|
25
|
+
const language = browserLanguage();
|
|
26
|
+
const languagePrefix = language.split("-")[0] || "en";
|
|
27
|
+
const matchesLanguage = (voice) => {
|
|
28
|
+
const voiceLanguage = normalizeLanguageCode(voice.lang);
|
|
29
|
+
return voiceLanguage === language || voiceLanguage.startsWith(`${languagePrefix}-`);
|
|
30
|
+
};
|
|
31
|
+
const ranked = [
|
|
32
|
+
(voice) => voice.localService && matchesLanguage(voice),
|
|
33
|
+
(voice) => voice.default && matchesLanguage(voice),
|
|
34
|
+
(voice) => matchesLanguage(voice),
|
|
35
|
+
(voice) => voice.localService,
|
|
36
|
+
(voice) => voice.default,
|
|
37
|
+
];
|
|
38
|
+
for (const predicate of ranked) {
|
|
39
|
+
const voice = voices.find(predicate);
|
|
40
|
+
if (voice)
|
|
41
|
+
return voice;
|
|
42
|
+
}
|
|
43
|
+
return voices[0] || null;
|
|
44
|
+
};
|
|
45
|
+
const shouldAssignBrowserVoice = (voice) => !voice.default;
|
|
10
46
|
/**
|
|
11
47
|
* Browser TTS Provider
|
|
12
48
|
*
|
|
@@ -183,25 +219,29 @@ class BrowserTTSProviderImpl {
|
|
|
183
219
|
resolve(false);
|
|
184
220
|
return;
|
|
185
221
|
}
|
|
186
|
-
|
|
222
|
+
const utterance = new SpeechSynthesisUtterance(chunkText);
|
|
223
|
+
this.utterance = utterance;
|
|
187
224
|
// Apply config
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
if (voice)
|
|
192
|
-
this.utterance.voice = voice;
|
|
193
|
-
}
|
|
225
|
+
const voice = findBrowserVoice(speechSynthesis.getVoices(), this.config?.voice);
|
|
226
|
+
if (voice && shouldAssignBrowserVoice(voice))
|
|
227
|
+
utterance.voice = voice;
|
|
194
228
|
if (this.config?.rate)
|
|
195
|
-
|
|
229
|
+
utterance.rate = this.config.rate;
|
|
196
230
|
if (this.config?.pitch)
|
|
197
|
-
|
|
198
|
-
|
|
231
|
+
utterance.pitch = this.config.pitch;
|
|
232
|
+
const clearOwnedUtterance = () => {
|
|
233
|
+
if (this.utterance === utterance) {
|
|
234
|
+
this.utterance = null;
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
utterance.onstart = () => {
|
|
199
238
|
if (runId !== this.speakRunId)
|
|
200
239
|
return;
|
|
201
240
|
this._isPlaying = true;
|
|
202
241
|
this._isPaused = false;
|
|
203
242
|
};
|
|
204
|
-
|
|
243
|
+
utterance.onend = () => {
|
|
244
|
+
clearOwnedUtterance();
|
|
205
245
|
if (runId !== this.speakRunId) {
|
|
206
246
|
resolve(false);
|
|
207
247
|
return;
|
|
@@ -210,7 +250,8 @@ class BrowserTTSProviderImpl {
|
|
|
210
250
|
this._isPaused = false;
|
|
211
251
|
resolve(true);
|
|
212
252
|
};
|
|
213
|
-
|
|
253
|
+
utterance.onerror = (event) => {
|
|
254
|
+
clearOwnedUtterance();
|
|
214
255
|
if (runId !== this.speakRunId) {
|
|
215
256
|
resolve(false);
|
|
216
257
|
return;
|
|
@@ -223,17 +264,17 @@ class BrowserTTSProviderImpl {
|
|
|
223
264
|
}
|
|
224
265
|
reject(new Error(`Speech synthesis error: ${event.error}`));
|
|
225
266
|
};
|
|
226
|
-
|
|
267
|
+
utterance.onpause = () => {
|
|
227
268
|
if (runId !== this.speakRunId)
|
|
228
269
|
return;
|
|
229
270
|
this._isPaused = true;
|
|
230
271
|
};
|
|
231
|
-
|
|
272
|
+
utterance.onresume = () => {
|
|
232
273
|
if (runId !== this.speakRunId)
|
|
233
274
|
return;
|
|
234
275
|
this._isPaused = false;
|
|
235
276
|
};
|
|
236
|
-
|
|
277
|
+
utterance.onboundary = (event) => {
|
|
237
278
|
if (runId !== this.speakRunId)
|
|
238
279
|
return;
|
|
239
280
|
console.log("[BrowserProvider] Boundary event:", event.name, "charIndex:", event.charIndex, "charLength:", event.charLength);
|
|
@@ -258,7 +299,7 @@ class BrowserTTSProviderImpl {
|
|
|
258
299
|
console.log("[BrowserProvider] Calling onWordBoundary with word:", word, "at position:", absoluteBoundaryStart);
|
|
259
300
|
this.onWordBoundary(word, absoluteBoundaryStart, wordLength);
|
|
260
301
|
};
|
|
261
|
-
speechSynthesis.speak(
|
|
302
|
+
speechSynthesis.speak(utterance);
|
|
262
303
|
});
|
|
263
304
|
}
|
|
264
305
|
pause() {
|
|
@@ -273,9 +314,10 @@ class BrowserTTSProviderImpl {
|
|
|
273
314
|
}
|
|
274
315
|
stop() {
|
|
275
316
|
this.speakRunId += 1;
|
|
276
|
-
if (this._isPlaying) {
|
|
317
|
+
if (this._isPlaying || this.utterance) {
|
|
277
318
|
speechSynthesis.cancel();
|
|
278
319
|
}
|
|
320
|
+
this.utterance = null;
|
|
279
321
|
this._isPlaying = false;
|
|
280
322
|
this._isPaused = false;
|
|
281
323
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"browser-provider.js","sourceRoot":"","sources":["../../../src/services/tts/browser-provider.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Browser TTS Provider\n *\n * Text-to-speech provider using the browser's Web Speech API.\n * Works in all modern browsers that support SpeechSynthesis.\n *\n * Part of PIE Assessment Toolkit.\n */\n\nimport type {\n\tITTSProvider,\n\tITTSProviderImplementation,\n\tTTSConfig,\n\tTTSFeature,\n\tTTSProviderCapabilities,\n} from \"@pie-players/pie-tts\";\nimport {\n\tsegmentSentences as segmentTextToSentences,\n} from \"./text-segmentation.js\";\n\ninterface TTSSpeechSegment {\n\ttext: string;\n\tstartOffset: number;\n\tpauseMsAfter?: number;\n}\n\n/**\n * Browser TTS Provider\n *\n * Stateless factory for creating browser-based TTS implementations.\n */\nexport class BrowserTTSProvider implements ITTSProvider {\n\treadonly providerId = \"browser\";\n\treadonly providerName = \"Browser Speech Synthesis\";\n\treadonly version = \"1.0.0\";\n\n\tasync initialize(config: TTSConfig): Promise<ITTSProviderImplementation> {\n\t\t// SSR guard\n\t\tif (typeof window === \"undefined\") {\n\t\t\tthrow new Error(\"BrowserTTSProvider requires browser environment\");\n\t\t}\n\n\t\t// Check browser support\n\t\tif (!(\"speechSynthesis\" in window)) {\n\t\t\tthrow new Error(\"Browser does not support Speech Synthesis API\");\n\t\t}\n\n\t\treturn new BrowserTTSProviderImpl(config);\n\t}\n\n\tsupportsFeature(feature: TTSFeature): boolean {\n\t\t// Browser supports all TTS features\n\t\treturn true;\n\t}\n\n\tgetCapabilities(): TTSProviderCapabilities {\n\t\treturn {\n\t\t\tsupportsPause: true,\n\t\t\tsupportsResume: true,\n\t\t\tsupportsWordBoundary: true,\n\t\t\tsupportsVoiceSelection: true,\n\t\t\tsupportsRateControl: true,\n\t\t\tsupportsPitchControl: true,\n\t\t\t// The Web Speech API voices plain text only; SSML tags are read aloud.\n\t\t\tsupportsSSML: false,\n\t\t};\n\t}\n\n\tdestroy(): void {\n\t\t// No cleanup needed for browser provider\n\t}\n}\n\n/**\n * Browser TTS Provider Implementation\n *\n * Handles actual speech synthesis using the Web Speech API.\n */\nclass BrowserTTSProviderImpl implements ITTSProviderImplementation {\n\tprivate utterance: SpeechSynthesisUtterance | null = null;\n\tprivate config: TTSConfig | null = null;\n\tprivate _isPlaying = false;\n\tprivate _isPaused = false;\n\tprivate speakRunId = 0;\n\n\tconstructor(config: TTSConfig) {\n\t\tthis.config = config;\n\t}\n\n\tasync speak(text: string): Promise<void> {\n\t\tif (!this.config) {\n\t\t\tthrow new Error(\"TTS not initialized\");\n\t\t}\n\t\t// Invalidate any in-flight run and cancel current utterance.\n\t\tthis.stop();\n\t\tconst runId = this.speakRunId;\n\n\t\tconst chunks = this.splitIntoChunks(text);\n\t\tfor (const chunk of chunks) {\n\t\t\tif (runId !== this.speakRunId) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tconst shouldContinue = await this.speakChunk(chunk.text, chunk.offset, runId);\n\t\t\tif (!shouldContinue) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\tasync speakSegments(segments: TTSSpeechSegment[]): Promise<void> {\n\t\tif (!this.config) {\n\t\t\tthrow new Error(\"TTS not initialized\");\n\t\t}\n\t\tthis.stop();\n\t\tconst runId = this.speakRunId;\n\t\tfor (const segment of segments) {\n\t\t\tif (runId !== this.speakRunId) break;\n\t\t\tconst chunks = this.splitIntoChunks(segment.text);\n\t\t\tfor (const chunk of chunks) {\n\t\t\t\tif (runId !== this.speakRunId) break;\n\t\t\t\tconst shouldContinue = await this.speakChunk(\n\t\t\t\t\tchunk.text,\n\t\t\t\t\tsegment.startOffset + chunk.offset,\n\t\t\t\t\trunId,\n\t\t\t\t);\n\t\t\t\tif (!shouldContinue) break;\n\t\t\t}\n\t\t\tconst pauseMsAfter = Math.max(0, Number(segment.pauseMsAfter || 0));\n\t\t\tif (pauseMsAfter > 0 && runId === this.speakRunId) {\n\t\t\t\tconst shouldContinue = await this.waitForPause(pauseMsAfter, runId);\n\t\t\t\tif (!shouldContinue) break;\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate async waitForPause(pauseMs: number, runId: number): Promise<boolean> {\n\t\tawait new Promise((resolve) => setTimeout(resolve, pauseMs));\n\t\treturn runId === this.speakRunId;\n\t}\n\n\tprivate splitIntoChunks(text: string): Array<{ text: string; offset: number }> {\n\t\tconst MAX_CHUNK_LENGTH = 260;\n\t\tif (text.length <= MAX_CHUNK_LENGTH) {\n\t\t\treturn [{ text, offset: 0 }];\n\t\t}\n\n\t\tconst sentences = this.segmentSentences(text);\n\t\tconst chunks: Array<{ text: string; offset: number }> = [];\n\t\tlet currentText = \"\";\n\t\tlet currentOffset = 0;\n\n\t\tfor (const sentence of sentences) {\n\t\t\tconst trimmed = sentence.text.trim();\n\t\t\tif (!trimmed) continue;\n\t\t\tconst firstNonWhitespace = sentence.text.search(/\\S/);\n\t\t\tconst trimmedStart =\n\t\t\t\tsentence.offset + (firstNonWhitespace === -1 ? 0 : firstNonWhitespace);\n\n\t\t\tif (!currentText) {\n\t\t\t\tcurrentText = trimmed;\n\t\t\t\tcurrentOffset = trimmedStart;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst candidate = `${currentText} ${trimmed}`;\n\t\t\tif (candidate.length <= MAX_CHUNK_LENGTH) {\n\t\t\t\tcurrentText = candidate;\n\t\t\t} else {\n\t\t\t\tchunks.push({ text: currentText, offset: currentOffset });\n\t\t\t\tcurrentText = trimmed;\n\t\t\t\tcurrentOffset = trimmedStart;\n\t\t\t}\n\t\t}\n\n\t\tif (currentText) {\n\t\t\tchunks.push({ text: currentText, offset: currentOffset });\n\t\t}\n\t\treturn chunks.length ? chunks : [{ text, offset: 0 }];\n\t}\n\n\tprivate getHighlightMode(): \"word\" | \"sentence\" {\n\t\tconst providerOptions = (this.config?.providerOptions || {}) as Record<\n\t\t\tstring,\n\t\t\tunknown\n\t\t>;\n\t\treturn providerOptions.highlightMode === \"word\" ? \"word\" : \"sentence\";\n\t}\n\n\tprivate getSegmentationPolicy(): {\n\t\tuseSentenceSegmenter: boolean;\n\t\tuseWordSegmenter: boolean;\n\t\tlocale?: string;\n\t} {\n\t\tconst providerOptions = (this.config?.providerOptions || {}) as Record<\n\t\t\tstring,\n\t\t\tunknown\n\t\t>;\n\t\tconst segmenter = (providerOptions.segmenter || {}) as Record<string, unknown>;\n\t\tconst mode = segmenter.mode;\n\t\tconst useSegmenter = mode !== \"regexOnly\";\n\t\tconst locale =\n\t\t\ttypeof segmenter.locale === \"string\" && segmenter.locale.trim().length > 0\n\t\t\t\t? segmenter.locale\n\t\t\t\t: typeof providerOptions.locale === \"string\" &&\n\t\t\t\t\t providerOptions.locale.trim().length > 0\n\t\t\t\t\t? providerOptions.locale\n\t\t\t\t\t: undefined;\n\t\treturn {\n\t\t\tuseSentenceSegmenter: useSegmenter,\n\t\t\tuseWordSegmenter: useSegmenter,\n\t\t\tlocale,\n\t\t};\n\t}\n\n\tprivate segmentSentences(text: string): Array<{ text: string; offset: number }> {\n\t\tconst policy = this.getSegmentationPolicy();\n\t\treturn segmentTextToSentences(text, {\n\t\t\tlocale: policy.locale,\n\t\t\tuseSentenceSegmenter: policy.useSentenceSegmenter,\n\t\t});\n\t}\n\n\tprivate inferWordLength(text: string, index: number): number {\n\t\tconst safeIndex = Math.max(0, Math.min(index, Math.max(0, text.length - 1)));\n\t\tconst slice = text.slice(safeIndex);\n\t\tconst match = slice.match(/^\\s*([^\\s]+)/);\n\t\treturn match?.[1]?.length || 1;\n\t}\n\n\tprivate async speakChunk(\n\t\tchunkText: string,\n\t\tchunkOffset: number,\n\t\trunId: number,\n\t): Promise<boolean> {\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tif (runId !== this.speakRunId) {\n\t\t\t\tresolve(false);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tthis.utterance = new SpeechSynthesisUtterance(chunkText);\n\n\t\t\t// Apply config\n\t\t\tif (this.config?.voice) {\n\t\t\t\tconst voices = speechSynthesis.getVoices();\n\t\t\t\tconst voice = voices.find((v) => v.name === this.config!.voice);\n\t\t\t\tif (voice) this.utterance!.voice = voice;\n\t\t\t}\n\n\t\t\tif (this.config?.rate) this.utterance.rate = this.config.rate;\n\t\t\tif (this.config?.pitch) this.utterance.pitch = this.config.pitch;\n\n\t\t\tthis.utterance.onstart = () => {\n\t\t\t\tif (runId !== this.speakRunId) return;\n\t\t\t\tthis._isPlaying = true;\n\t\t\t\tthis._isPaused = false;\n\t\t\t};\n\n\t\t\tthis.utterance.onend = () => {\n\t\t\t\tif (runId !== this.speakRunId) {\n\t\t\t\t\tresolve(false);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis._isPlaying = false;\n\t\t\t\tthis._isPaused = false;\n\t\t\t\tresolve(true);\n\t\t\t};\n\n\t\t\tthis.utterance.onerror = (event) => {\n\t\t\t\tif (runId !== this.speakRunId) {\n\t\t\t\t\tresolve(false);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis._isPlaying = false;\n\t\t\t\tthis._isPaused = false;\n\t\t\t\tif (event.error === \"interrupted\" || event.error === \"canceled\") {\n\t\t\t\t\tresolve(false);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\treject(new Error(`Speech synthesis error: ${event.error}`));\n\t\t\t};\n\n\t\t\tthis.utterance.onpause = () => {\n\t\t\t\tif (runId !== this.speakRunId) return;\n\t\t\t\tthis._isPaused = true;\n\t\t\t};\n\n\t\t\tthis.utterance.onresume = () => {\n\t\t\t\tif (runId !== this.speakRunId) return;\n\t\t\t\tthis._isPaused = false;\n\t\t\t};\n\n\t\t\tthis.utterance.onboundary = (event) => {\n\t\t\t\tif (runId !== this.speakRunId) return;\n\t\t\t\tconsole.log(\n\t\t\t\t\t\"[BrowserProvider] Boundary event:\",\n\t\t\t\t\tevent.name,\n\t\t\t\t\t\"charIndex:\",\n\t\t\t\t\tevent.charIndex,\n\t\t\t\t\t\"charLength:\",\n\t\t\t\t\tevent.charLength,\n\t\t\t\t);\n\t\t\t\tif (event.name !== \"word\" || !this.onWordBoundary) return;\n\t\t\t\tif (this.getHighlightMode() === \"sentence\") {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst charIndex = Math.max(\n\t\t\t\t\t0,\n\t\t\t\t\tMath.min(event.charIndex, Math.max(0, chunkText.length - 1)),\n\t\t\t\t);\n\t\t\t\tconst reportedLength = Number(event.charLength || 0);\n\t\t\t\tconst inferredLength = this.inferWordLength(chunkText, charIndex);\n\t\t\t\tconst wordLength =\n\t\t\t\t\tNumber.isFinite(reportedLength) &&\n\t\t\t\t\treportedLength > 0 &&\n\t\t\t\t\treportedLength <= 80 &&\n\t\t\t\t\tcharIndex + reportedLength <= chunkText.length\n\t\t\t\t\t\t? reportedLength\n\t\t\t\t\t\t: inferredLength;\n\t\t\t\tconst word = chunkText\n\t\t\t\t\t.substring(charIndex, Math.min(chunkText.length, charIndex + wordLength))\n\t\t\t\t\t.trim();\n\t\t\t\tconst absoluteBoundaryStart = chunkOffset + charIndex;\n\t\t\t\tconsole.log(\n\t\t\t\t\t\"[BrowserProvider] Calling onWordBoundary with word:\",\n\t\t\t\t\tword,\n\t\t\t\t\t\"at position:\",\n\t\t\t\t\tabsoluteBoundaryStart,\n\t\t\t\t);\n\t\t\t\tthis.onWordBoundary(word, absoluteBoundaryStart, wordLength);\n\t\t\t};\n\n\t\t\tspeechSynthesis.speak(this.utterance);\n\t\t});\n\t}\n\n\tpause(): void {\n\t\tif (this._isPlaying && !this._isPaused) {\n\t\t\tspeechSynthesis.pause();\n\t\t}\n\t}\n\n\tresume(): void {\n\t\tif (this._isPlaying && this._isPaused) {\n\t\t\tspeechSynthesis.resume();\n\t\t}\n\t}\n\n\tstop(): void {\n\t\tthis.speakRunId += 1;\n\t\tif (this._isPlaying) {\n\t\t\tspeechSynthesis.cancel();\n\t\t}\n\t\tthis._isPlaying = false;\n\t\tthis._isPaused = false;\n\t}\n\n\tisPlaying(): boolean {\n\t\treturn this._isPlaying && !this._isPaused;\n\t}\n\n\tisPaused(): boolean {\n\t\treturn this._isPaused;\n\t}\n\n\t/**\n\t * Update settings dynamically (rate, pitch, voice)\n\t * Changes take effect on the next speak() call\n\t */\n\tupdateSettings(settings: Partial<TTSConfig>): void {\n\t\tif (!this.config) {\n\t\t\tthis.config = {} as TTSConfig;\n\t\t}\n\n\t\t// Update config with new settings\n\t\tif (settings.rate !== undefined) {\n\t\t\tthis.config.rate = settings.rate;\n\t\t}\n\t\tif (settings.pitch !== undefined) {\n\t\t\tthis.config.pitch = settings.pitch;\n\t\t}\n\t\tif (settings.voice !== undefined) {\n\t\t\tthis.config.voice = settings.voice;\n\t\t}\n\t\tif (settings.providerOptions !== undefined) {\n\t\t\tthis.config.providerOptions = {\n\t\t\t\t...(this.config.providerOptions || {}),\n\t\t\t\t...(settings.providerOptions || {}),\n\t\t\t};\n\t\t}\n\t}\n\n\tonWordBoundary?: (word: string, position: number, length?: number) => void;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"browser-provider.js","sourceRoot":"","sources":["../../../src/services/tts/browser-provider.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Browser TTS Provider\n *\n * Text-to-speech provider using the browser's Web Speech API.\n * Works in all modern browsers that support SpeechSynthesis.\n *\n * Part of PIE Assessment Toolkit.\n */\n\nimport type {\n\tITTSProvider,\n\tITTSProviderImplementation,\n\tTTSConfig,\n\tTTSFeature,\n\tTTSProviderCapabilities,\n} from \"@pie-players/pie-tts\";\nimport { segmentSentences as segmentTextToSentences } from \"./text-segmentation.js\";\n\ninterface TTSSpeechSegment {\n\ttext: string;\n\tstartOffset: number;\n\tpauseMsAfter?: number;\n}\n\nconst normalizeLanguageCode = (value: unknown): string =>\n\tString(value || \"\")\n\t\t.trim()\n\t\t.toLowerCase();\n\nconst browserLanguage = (): string => {\n\tconst navigatorLanguage =\n\t\ttypeof navigator !== \"undefined\"\n\t\t\t? navigator.language || navigator.languages?.[0]\n\t\t\t: \"\";\n\treturn normalizeLanguageCode(navigatorLanguage || \"en-US\");\n};\n\nconst findBrowserVoice = (\n\tvoices: SpeechSynthesisVoice[],\n\tpreferredName?: string,\n): SpeechSynthesisVoice | null => {\n\tif (preferredName) {\n\t\tconst explicit = voices.find((voice) => voice.name === preferredName);\n\t\tif (explicit) return explicit;\n\t}\n\tconst language = browserLanguage();\n\tconst languagePrefix = language.split(\"-\")[0] || \"en\";\n\tconst matchesLanguage = (voice: SpeechSynthesisVoice) => {\n\t\tconst voiceLanguage = normalizeLanguageCode(voice.lang);\n\t\treturn voiceLanguage === language || voiceLanguage.startsWith(`${languagePrefix}-`);\n\t};\n\tconst ranked = [\n\t\t(voice: SpeechSynthesisVoice) => voice.localService && matchesLanguage(voice),\n\t\t(voice: SpeechSynthesisVoice) => voice.default && matchesLanguage(voice),\n\t\t(voice: SpeechSynthesisVoice) => matchesLanguage(voice),\n\t\t(voice: SpeechSynthesisVoice) => voice.localService,\n\t\t(voice: SpeechSynthesisVoice) => voice.default,\n\t];\n\tfor (const predicate of ranked) {\n\t\tconst voice = voices.find(predicate);\n\t\tif (voice) return voice;\n\t}\n\treturn voices[0] || null;\n};\n\nconst shouldAssignBrowserVoice = (voice: SpeechSynthesisVoice): boolean => !voice.default;\n\n/**\n * Browser TTS Provider\n *\n * Stateless factory for creating browser-based TTS implementations.\n */\nexport class BrowserTTSProvider implements ITTSProvider {\n\treadonly providerId = \"browser\";\n\treadonly providerName = \"Browser Speech Synthesis\";\n\treadonly version = \"1.0.0\";\n\n\tasync initialize(config: TTSConfig): Promise<ITTSProviderImplementation> {\n\t\t// SSR guard\n\t\tif (typeof window === \"undefined\") {\n\t\t\tthrow new Error(\"BrowserTTSProvider requires browser environment\");\n\t\t}\n\n\t\t// Check browser support\n\t\tif (!(\"speechSynthesis\" in window)) {\n\t\t\tthrow new Error(\"Browser does not support Speech Synthesis API\");\n\t\t}\n\n\t\treturn new BrowserTTSProviderImpl(config);\n\t}\n\n\tsupportsFeature(feature: TTSFeature): boolean {\n\t\t// Browser supports all TTS features\n\t\treturn true;\n\t}\n\n\tgetCapabilities(): TTSProviderCapabilities {\n\t\treturn {\n\t\t\tsupportsPause: true,\n\t\t\tsupportsResume: true,\n\t\t\tsupportsWordBoundary: true,\n\t\t\tsupportsVoiceSelection: true,\n\t\t\tsupportsRateControl: true,\n\t\t\tsupportsPitchControl: true,\n\t\t\t// The Web Speech API voices plain text only; SSML tags are read aloud.\n\t\t\tsupportsSSML: false,\n\t\t};\n\t}\n\n\tdestroy(): void {\n\t\t// No cleanup needed for browser provider\n\t}\n}\n\n/**\n * Browser TTS Provider Implementation\n *\n * Handles actual speech synthesis using the Web Speech API.\n */\nclass BrowserTTSProviderImpl implements ITTSProviderImplementation {\n\tprivate utterance: SpeechSynthesisUtterance | null = null;\n\tprivate config: TTSConfig | null = null;\n\tprivate _isPlaying = false;\n\tprivate _isPaused = false;\n\tprivate speakRunId = 0;\n\n\tconstructor(config: TTSConfig) {\n\t\tthis.config = config;\n\t}\n\n\tasync speak(text: string): Promise<void> {\n\t\tif (!this.config) {\n\t\t\tthrow new Error(\"TTS not initialized\");\n\t\t}\n\t\t// Invalidate any in-flight run and cancel current utterance.\n\t\tthis.stop();\n\t\tconst runId = this.speakRunId;\n\n\t\tconst chunks = this.splitIntoChunks(text);\n\t\tfor (const chunk of chunks) {\n\t\t\tif (runId !== this.speakRunId) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tconst shouldContinue = await this.speakChunk(\n\t\t\t\tchunk.text,\n\t\t\t\tchunk.offset,\n\t\t\t\trunId,\n\t\t\t);\n\t\t\tif (!shouldContinue) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\tasync speakSegments(segments: TTSSpeechSegment[]): Promise<void> {\n\t\tif (!this.config) {\n\t\t\tthrow new Error(\"TTS not initialized\");\n\t\t}\n\t\tthis.stop();\n\t\tconst runId = this.speakRunId;\n\t\tfor (const segment of segments) {\n\t\t\tif (runId !== this.speakRunId) break;\n\t\t\tconst chunks = this.splitIntoChunks(segment.text);\n\t\t\tfor (const chunk of chunks) {\n\t\t\t\tif (runId !== this.speakRunId) break;\n\t\t\t\tconst shouldContinue = await this.speakChunk(\n\t\t\t\t\tchunk.text,\n\t\t\t\t\tsegment.startOffset + chunk.offset,\n\t\t\t\t\trunId,\n\t\t\t\t);\n\t\t\t\tif (!shouldContinue) break;\n\t\t\t}\n\t\t\tconst pauseMsAfter = Math.max(0, Number(segment.pauseMsAfter || 0));\n\t\t\tif (pauseMsAfter > 0 && runId === this.speakRunId) {\n\t\t\t\tconst shouldContinue = await this.waitForPause(pauseMsAfter, runId);\n\t\t\t\tif (!shouldContinue) break;\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate async waitForPause(pauseMs: number, runId: number): Promise<boolean> {\n\t\tawait new Promise((resolve) => setTimeout(resolve, pauseMs));\n\t\treturn runId === this.speakRunId;\n\t}\n\n\tprivate splitIntoChunks(\n\t\ttext: string,\n\t): Array<{ text: string; offset: number }> {\n\t\tconst MAX_CHUNK_LENGTH = 260;\n\t\tif (text.length <= MAX_CHUNK_LENGTH) {\n\t\t\treturn [{ text, offset: 0 }];\n\t\t}\n\n\t\tconst sentences = this.segmentSentences(text);\n\t\tconst chunks: Array<{ text: string; offset: number }> = [];\n\t\tlet currentText = \"\";\n\t\tlet currentOffset = 0;\n\n\t\tfor (const sentence of sentences) {\n\t\t\tconst trimmed = sentence.text.trim();\n\t\t\tif (!trimmed) continue;\n\t\t\tconst firstNonWhitespace = sentence.text.search(/\\S/);\n\t\t\tconst trimmedStart =\n\t\t\t\tsentence.offset + (firstNonWhitespace === -1 ? 0 : firstNonWhitespace);\n\n\t\t\tif (!currentText) {\n\t\t\t\tcurrentText = trimmed;\n\t\t\t\tcurrentOffset = trimmedStart;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst candidate = `${currentText} ${trimmed}`;\n\t\t\tif (candidate.length <= MAX_CHUNK_LENGTH) {\n\t\t\t\tcurrentText = candidate;\n\t\t\t} else {\n\t\t\t\tchunks.push({ text: currentText, offset: currentOffset });\n\t\t\t\tcurrentText = trimmed;\n\t\t\t\tcurrentOffset = trimmedStart;\n\t\t\t}\n\t\t}\n\n\t\tif (currentText) {\n\t\t\tchunks.push({ text: currentText, offset: currentOffset });\n\t\t}\n\t\treturn chunks.length ? chunks : [{ text, offset: 0 }];\n\t}\n\n\tprivate getHighlightMode(): \"word\" | \"sentence\" {\n\t\tconst providerOptions = (this.config?.providerOptions || {}) as Record<\n\t\t\tstring,\n\t\t\tunknown\n\t\t>;\n\t\treturn providerOptions.highlightMode === \"word\" ? \"word\" : \"sentence\";\n\t}\n\n\tprivate getSegmentationPolicy(): {\n\t\tuseSentenceSegmenter: boolean;\n\t\tuseWordSegmenter: boolean;\n\t\tlocale?: string;\n\t} {\n\t\tconst providerOptions = (this.config?.providerOptions || {}) as Record<\n\t\t\tstring,\n\t\t\tunknown\n\t\t>;\n\t\tconst segmenter = (providerOptions.segmenter || {}) as Record<\n\t\t\tstring,\n\t\t\tunknown\n\t\t>;\n\t\tconst mode = segmenter.mode;\n\t\tconst useSegmenter = mode !== \"regexOnly\";\n\t\tconst locale =\n\t\t\ttypeof segmenter.locale === \"string\" && segmenter.locale.trim().length > 0\n\t\t\t\t? segmenter.locale\n\t\t\t\t: typeof providerOptions.locale === \"string\" &&\n\t\t\t\t\t\tproviderOptions.locale.trim().length > 0\n\t\t\t\t\t? providerOptions.locale\n\t\t\t\t\t: undefined;\n\t\treturn {\n\t\t\tuseSentenceSegmenter: useSegmenter,\n\t\t\tuseWordSegmenter: useSegmenter,\n\t\t\tlocale,\n\t\t};\n\t}\n\n\tprivate segmentSentences(\n\t\ttext: string,\n\t): Array<{ text: string; offset: number }> {\n\t\tconst policy = this.getSegmentationPolicy();\n\t\treturn segmentTextToSentences(text, {\n\t\t\tlocale: policy.locale,\n\t\t\tuseSentenceSegmenter: policy.useSentenceSegmenter,\n\t\t});\n\t}\n\n\tprivate inferWordLength(text: string, index: number): number {\n\t\tconst safeIndex = Math.max(\n\t\t\t0,\n\t\t\tMath.min(index, Math.max(0, text.length - 1)),\n\t\t);\n\t\tconst slice = text.slice(safeIndex);\n\t\tconst match = slice.match(/^\\s*([^\\s]+)/);\n\t\treturn match?.[1]?.length || 1;\n\t}\n\n\tprivate async speakChunk(\n\t\tchunkText: string,\n\t\tchunkOffset: number,\n\t\trunId: number,\n\t): Promise<boolean> {\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tif (runId !== this.speakRunId) {\n\t\t\t\tresolve(false);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst utterance = new SpeechSynthesisUtterance(chunkText);\n\t\t\tthis.utterance = utterance;\n\n\t\t\t// Apply config\n\t\t\tconst voice = findBrowserVoice(\n\t\t\t\tspeechSynthesis.getVoices(),\n\t\t\t\tthis.config?.voice,\n\t\t\t);\n\t\t\tif (voice && shouldAssignBrowserVoice(voice)) utterance.voice = voice;\n\n\t\t\tif (this.config?.rate) utterance.rate = this.config.rate;\n\t\t\tif (this.config?.pitch) utterance.pitch = this.config.pitch;\n\n\t\t\tconst clearOwnedUtterance = () => {\n\t\t\t\tif (this.utterance === utterance) {\n\t\t\t\t\tthis.utterance = null;\n\t\t\t\t}\n\t\t\t};\n\n\t\t\tutterance.onstart = () => {\n\t\t\t\tif (runId !== this.speakRunId) return;\n\t\t\t\tthis._isPlaying = true;\n\t\t\t\tthis._isPaused = false;\n\t\t\t};\n\n\t\t\tutterance.onend = () => {\n\t\t\t\tclearOwnedUtterance();\n\t\t\t\tif (runId !== this.speakRunId) {\n\t\t\t\t\tresolve(false);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis._isPlaying = false;\n\t\t\t\tthis._isPaused = false;\n\t\t\t\tresolve(true);\n\t\t\t};\n\n\t\t\tutterance.onerror = (event) => {\n\t\t\t\tclearOwnedUtterance();\n\t\t\t\tif (runId !== this.speakRunId) {\n\t\t\t\t\tresolve(false);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis._isPlaying = false;\n\t\t\t\tthis._isPaused = false;\n\t\t\t\tif (event.error === \"interrupted\" || event.error === \"canceled\") {\n\t\t\t\t\tresolve(false);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\treject(new Error(`Speech synthesis error: ${event.error}`));\n\t\t\t};\n\n\t\t\tutterance.onpause = () => {\n\t\t\t\tif (runId !== this.speakRunId) return;\n\t\t\t\tthis._isPaused = true;\n\t\t\t};\n\n\t\t\tutterance.onresume = () => {\n\t\t\t\tif (runId !== this.speakRunId) return;\n\t\t\t\tthis._isPaused = false;\n\t\t\t};\n\n\t\t\tutterance.onboundary = (event) => {\n\t\t\t\tif (runId !== this.speakRunId) return;\n\t\t\t\tconsole.log(\n\t\t\t\t\t\"[BrowserProvider] Boundary event:\",\n\t\t\t\t\tevent.name,\n\t\t\t\t\t\"charIndex:\",\n\t\t\t\t\tevent.charIndex,\n\t\t\t\t\t\"charLength:\",\n\t\t\t\t\tevent.charLength,\n\t\t\t\t);\n\t\t\t\tif (event.name !== \"word\" || !this.onWordBoundary) return;\n\t\t\t\tif (this.getHighlightMode() === \"sentence\") {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst charIndex = Math.max(\n\t\t\t\t\t0,\n\t\t\t\t\tMath.min(event.charIndex, Math.max(0, chunkText.length - 1)),\n\t\t\t\t);\n\t\t\t\tconst reportedLength = Number(event.charLength || 0);\n\t\t\t\tconst inferredLength = this.inferWordLength(chunkText, charIndex);\n\t\t\t\tconst wordLength =\n\t\t\t\t\tNumber.isFinite(reportedLength) &&\n\t\t\t\t\treportedLength > 0 &&\n\t\t\t\t\treportedLength <= 80 &&\n\t\t\t\t\tcharIndex + reportedLength <= chunkText.length\n\t\t\t\t\t\t? reportedLength\n\t\t\t\t\t\t: inferredLength;\n\t\t\t\tconst word = chunkText\n\t\t\t\t\t.substring(\n\t\t\t\t\t\tcharIndex,\n\t\t\t\t\t\tMath.min(chunkText.length, charIndex + wordLength),\n\t\t\t\t\t)\n\t\t\t\t\t.trim();\n\t\t\t\tconst absoluteBoundaryStart = chunkOffset + charIndex;\n\t\t\t\tconsole.log(\n\t\t\t\t\t\"[BrowserProvider] Calling onWordBoundary with word:\",\n\t\t\t\t\tword,\n\t\t\t\t\t\"at position:\",\n\t\t\t\t\tabsoluteBoundaryStart,\n\t\t\t\t);\n\t\t\t\tthis.onWordBoundary(word, absoluteBoundaryStart, wordLength);\n\t\t\t};\n\n\t\t\tspeechSynthesis.speak(utterance);\n\t\t});\n\t}\n\n\tpause(): void {\n\t\tif (this._isPlaying && !this._isPaused) {\n\t\t\tspeechSynthesis.pause();\n\t\t}\n\t}\n\n\tresume(): void {\n\t\tif (this._isPlaying && this._isPaused) {\n\t\t\tspeechSynthesis.resume();\n\t\t}\n\t}\n\n\tstop(): void {\n\t\tthis.speakRunId += 1;\n\t\tif (this._isPlaying || this.utterance) {\n\t\t\tspeechSynthesis.cancel();\n\t\t}\n\t\tthis.utterance = null;\n\t\tthis._isPlaying = false;\n\t\tthis._isPaused = false;\n\t}\n\n\tisPlaying(): boolean {\n\t\treturn this._isPlaying && !this._isPaused;\n\t}\n\n\tisPaused(): boolean {\n\t\treturn this._isPaused;\n\t}\n\n\t/**\n\t * Update settings dynamically (rate, pitch, voice)\n\t * Changes take effect on the next speak() call\n\t */\n\tupdateSettings(settings: Partial<TTSConfig>): void {\n\t\tif (!this.config) {\n\t\t\tthis.config = {} as TTSConfig;\n\t\t}\n\n\t\t// Update config with new settings\n\t\tif (settings.rate !== undefined) {\n\t\t\tthis.config.rate = settings.rate;\n\t\t}\n\t\tif (settings.pitch !== undefined) {\n\t\t\tthis.config.pitch = settings.pitch;\n\t\t}\n\t\tif (settings.voice !== undefined) {\n\t\t\tthis.config.voice = settings.voice;\n\t\t}\n\t\tif (settings.providerOptions !== undefined) {\n\t\t\tthis.config.providerOptions = {\n\t\t\t\t...(this.config.providerOptions || {}),\n\t\t\t\t...(settings.providerOptions || {}),\n\t\t\t};\n\t\t}\n\t}\n\n\tonWordBoundary?: (word: string, position: number, length?: number) => void;\n}\n"]}
|
|
@@ -91,7 +91,8 @@ const createCandidates = (spokenTokens, visibleTokens) => {
|
|
|
91
91
|
const computeAnchors = (spokenTokens, visibleTokens) => {
|
|
92
92
|
const candidates = createCandidates(spokenTokens, visibleTokens).sort((left, right) => spokenTokens[left.spokenStartToken].start -
|
|
93
93
|
spokenTokens[right.spokenStartToken].start ||
|
|
94
|
-
visibleTokens[left.visibleToken].start -
|
|
94
|
+
visibleTokens[left.visibleToken].start -
|
|
95
|
+
visibleTokens[right.visibleToken].start);
|
|
95
96
|
const bestScore = new Array(candidates.length).fill(0);
|
|
96
97
|
const previous = new Array(candidates.length).fill(-1);
|
|
97
98
|
for (let i = 0; i < candidates.length; i++) {
|
|
@@ -142,7 +143,8 @@ const computeAnchors = (spokenTokens, visibleTokens) => {
|
|
|
142
143
|
const scoreConfidence = (anchors, spokenTokens, visibleTokens) => {
|
|
143
144
|
if (anchors.length === 0)
|
|
144
145
|
return 0;
|
|
145
|
-
const tokenCoverage = anchors.length /
|
|
146
|
+
const tokenCoverage = anchors.length /
|
|
147
|
+
Math.max(1, Math.min(spokenTokens.length, visibleTokens.length));
|
|
146
148
|
const averageScore = anchors.reduce((total, anchor) => total + anchor.score, 0) /
|
|
147
149
|
anchors.length /
|
|
148
150
|
10;
|
|
@@ -242,7 +244,10 @@ const resolvePlainBoundaryOffset = (alignment, position, safeLength) => {
|
|
|
242
244
|
const token = findTokenAtOffset(alignment.spokenTokens, position);
|
|
243
245
|
if (!token)
|
|
244
246
|
return null;
|
|
245
|
-
return {
|
|
247
|
+
return {
|
|
248
|
+
start: position,
|
|
249
|
+
length: Math.min(safeLength, token.end - position),
|
|
250
|
+
};
|
|
246
251
|
};
|
|
247
252
|
export const resolveSpokenBoundaryOffset = (alignment, position, length = 1, boundaryWord) => {
|
|
248
253
|
if (alignment.boundaryOffsetMode === "unsupported" ||
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"catalog-span-alignment.js","sourceRoot":"","sources":["../../../src/services/tts/catalog-span-alignment.ts"],"names":[],"mappings":"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","sourcesContent":["import { extractSpokenText } from \"./ssml/spoken-text.js\";\nimport {\n\tcreateSpeechAlignmentTokenPattern,\n\tnormalizeTextForSpeech,\n} from \"./text-processing.js\";\n\nexport type CatalogChunkPlaybackMode =\n\t| \"exact-word\"\n\t| \"anchor-span\"\n\t| \"region-fallback\";\n\nexport type BoundaryOffsetMode =\n\t| \"plain-spoken-text\"\n\t| \"raw-ssml\"\n\t| \"unsupported\";\n\nexport interface CatalogTextToken {\n\ttext: string;\n\tnormalized: string;\n\tstart: number;\n\tend: number;\n}\n\nexport interface CatalogSpanAnchor {\n\tspokenStart: number;\n\tspokenEnd: number;\n\tvisibleStart: number;\n\tvisibleEnd: number;\n\tscore: number;\n}\n\nexport interface CatalogSpanAlignment {\n\tspeechText: string;\n\tspokenText: string;\n\tvisibleText: string;\n\tplaybackMode: CatalogChunkPlaybackMode;\n\tboundaryOffsetMode: BoundaryOffsetMode;\n\tanchors: CatalogSpanAnchor[];\n\tconfidence: number;\n\tspokenTokens: CatalogTextToken[];\n\tvisibleTokens: CatalogTextToken[];\n\trawToSpokenOffsetMap: Map<number, number>;\n}\n\ninterface MatchCandidate {\n\tspokenStartToken: number;\n\tspokenEndToken: number;\n\tvisibleToken: number;\n\tscore: number;\n}\n\n// Shared Unicode-aware tokenizer (see `createSpeechAlignmentTokenPattern`), so\n// accented Latin and non-Latin words tokenize the same way here as in the math\n// speech tokenizer. LIMITATION (i18n): the phrase table below is still English,\n// so multi-word operator phrases (\"plus or minus\", \"divided by\") only align for\n// English speech; other locales degrade to coarse region highlighting. A fresh\n// pattern per call because of the global flag's mutable `lastIndex`.\nconst TOKEN_PATTERN = createSpeechAlignmentTokenPattern();\n\nconst NUMERIC_WORDS = new Map<string, string>([\n\t[\"zero\", \"0\"],\n\t[\"one\", \"1\"],\n\t[\"two\", \"2\"],\n\t[\"three\", \"3\"],\n\t[\"four\", \"4\"],\n\t[\"five\", \"5\"],\n\t[\"six\", \"6\"],\n\t[\"seven\", \"7\"],\n\t[\"eight\", \"8\"],\n\t[\"nine\", \"9\"],\n\t[\"ten\", \"10\"],\n]);\n\nconst SPOKEN_PHRASE_TARGETS: Array<{\n\tphrase: string[];\n\tvisible: string;\n\tscore: number;\n}> = [\n\t{ phrase: [\"plus\", \"or\", \"minus\"], visible: \"±\", score: 8 },\n\t{ phrase: [\"square\", \"root\"], visible: \"√\", score: 8 },\n\t{ phrase: [\"divided\", \"by\"], visible: \"/\", score: 7 },\n\t{ phrase: [\"all\", \"over\"], visible: \"/\", score: 7 },\n\t{ phrase: [\"equals\"], visible: \"=\", score: 7 },\n\t{ phrase: [\"equal\"], visible: \"=\", score: 7 },\n\t{ phrase: [\"times\"], visible: \"\\u2062\", score: 7 },\n\t{ phrase: [\"multiplied\", \"by\"], visible: \"\\u2062\", score: 7 },\n\t{ phrase: [\"over\"], visible: \"/\", score: 6 },\n\t{ phrase: [\"squared\"], visible: \"²\", score: 6 },\n\t{ phrase: [\"squared\"], visible: \"2\", score: 4 },\n\t{ phrase: [\"negative\"], visible: \"-\", score: 5 },\n\t{ phrase: [\"minus\"], visible: \"-\", score: 5 },\n\t{ phrase: [\"plus\"], visible: \"+\", score: 5 },\n];\n\nconst tokenize = (text: string): CatalogTextToken[] => {\n\tconst tokens: CatalogTextToken[] = [];\n\tfor (const match of text.matchAll(TOKEN_PATTERN)) {\n\t\tconst value = match[0];\n\t\tconst start = match.index ?? 0;\n\t\ttokens.push({\n\t\t\ttext: value,\n\t\t\tnormalized: value.toLowerCase(),\n\t\t\tstart,\n\t\t\tend: start + value.length,\n\t\t});\n\t}\n\treturn tokens;\n};\n\nconst normalizedTokenValue = (token: CatalogTextToken): string =>\n\tNUMERIC_WORDS.get(token.normalized) || token.normalized;\n\nconst createCandidates = (\n\tspokenTokens: CatalogTextToken[],\n\tvisibleTokens: CatalogTextToken[],\n): MatchCandidate[] => {\n\tconst candidates: MatchCandidate[] = [];\n\tfor (let spokenIndex = 0; spokenIndex < spokenTokens.length; spokenIndex++) {\n\t\tfor (let visibleIndex = 0; visibleIndex < visibleTokens.length; visibleIndex++) {\n\t\t\tconst spokenValue = normalizedTokenValue(spokenTokens[spokenIndex]);\n\t\t\tconst visibleValue = normalizedTokenValue(visibleTokens[visibleIndex]);\n\t\t\tif (spokenValue === visibleValue) {\n\t\t\t\tcandidates.push({\n\t\t\t\t\tspokenStartToken: spokenIndex,\n\t\t\t\t\tspokenEndToken: spokenIndex + 1,\n\t\t\t\t\tvisibleToken: visibleIndex,\n\t\t\t\t\tscore: 10,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\tfor (const phraseTarget of SPOKEN_PHRASE_TARGETS) {\n\t\t\tconst phraseTokens = spokenTokens.slice(\n\t\t\t\tspokenIndex,\n\t\t\t\tspokenIndex + phraseTarget.phrase.length,\n\t\t\t);\n\t\t\tif (phraseTokens.length !== phraseTarget.phrase.length) continue;\n\t\t\tif (\n\t\t\t\t!phraseTokens.every(\n\t\t\t\t\t(token, tokenIndex) =>\n\t\t\t\t\t\ttoken.normalized === phraseTarget.phrase[tokenIndex],\n\t\t\t\t)\n\t\t\t) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tfor (\n\t\t\t\tlet visibleIndex = 0;\n\t\t\t\tvisibleIndex < visibleTokens.length;\n\t\t\t\tvisibleIndex++\n\t\t\t) {\n\t\t\t\tif (visibleTokens[visibleIndex].normalized !== phraseTarget.visible) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tcandidates.push({\n\t\t\t\t\tspokenStartToken: spokenIndex,\n\t\t\t\t\tspokenEndToken: spokenIndex + phraseTarget.phrase.length,\n\t\t\t\t\tvisibleToken: visibleIndex,\n\t\t\t\t\tscore: phraseTarget.score,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\treturn candidates;\n};\n\nconst computeAnchors = (\n\tspokenTokens: CatalogTextToken[],\n\tvisibleTokens: CatalogTextToken[],\n): CatalogSpanAnchor[] => {\n\tconst candidates = createCandidates(spokenTokens, visibleTokens).sort(\n\t\t(left, right) =>\n\t\t\tspokenTokens[left.spokenStartToken].start -\n\t\t\t\tspokenTokens[right.spokenStartToken].start ||\n\t\t\tvisibleTokens[left.visibleToken].start - visibleTokens[right.visibleToken].start,\n\t);\n\tconst bestScore = new Array(candidates.length).fill(0) as number[];\n\tconst previous = new Array(candidates.length).fill(-1) as number[];\n\n\tfor (let i = 0; i < candidates.length; i++) {\n\t\tconst candidate = candidates[i];\n\t\tbestScore[i] = candidate.score;\n\t\tfor (let j = 0; j < i; j++) {\n\t\t\tconst prior = candidates[j];\n\t\t\tif (\n\t\t\t\tprior.spokenEndToken <= candidate.spokenStartToken &&\n\t\t\t\tprior.visibleToken < candidate.visibleToken\n\t\t\t) {\n\t\t\t\tconst spokenGap = candidate.spokenStartToken - prior.spokenEndToken;\n\t\t\t\tconst visibleGap = candidate.visibleToken - prior.visibleToken - 1;\n\t\t\t\tconst gapPenalty = Math.max(0, Math.abs(spokenGap - visibleGap) - 2);\n\t\t\t\tconst score = bestScore[j] + candidate.score - gapPenalty;\n\t\t\t\tif (score > bestScore[i]) {\n\t\t\t\t\tbestScore[i] = score;\n\t\t\t\t\tprevious[i] = j;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tlet bestIndex = -1;\n\tfor (let i = 0; i < bestScore.length; i++) {\n\t\tif (bestIndex < 0 || bestScore[i] > bestScore[bestIndex]) {\n\t\t\tbestIndex = i;\n\t\t}\n\t}\n\tif (bestIndex < 0) return [];\n\n\tconst chosen: MatchCandidate[] = [];\n\tlet current = bestIndex;\n\twhile (current >= 0) {\n\t\tchosen.unshift(candidates[current]);\n\t\tcurrent = previous[current];\n\t}\n\n\treturn chosen.map((candidate) => {\n\t\tconst firstSpoken = spokenTokens[candidate.spokenStartToken];\n\t\tconst lastSpoken = spokenTokens[candidate.spokenEndToken - 1];\n\t\tconst visible = visibleTokens[candidate.visibleToken];\n\t\treturn {\n\t\t\tspokenStart: firstSpoken.start,\n\t\t\tspokenEnd: lastSpoken.end,\n\t\t\tvisibleStart: visible.start,\n\t\t\tvisibleEnd: visible.end,\n\t\t\tscore: candidate.score,\n\t\t};\n\t});\n};\n\nconst scoreConfidence = (\n\tanchors: CatalogSpanAnchor[],\n\tspokenTokens: CatalogTextToken[],\n\tvisibleTokens: CatalogTextToken[],\n): number => {\n\tif (anchors.length === 0) return 0;\n\tconst tokenCoverage =\n\t\tanchors.length / Math.max(1, Math.min(spokenTokens.length, visibleTokens.length));\n\tconst averageScore =\n\t\tanchors.reduce((total, anchor) => total + anchor.score, 0) /\n\t\tanchors.length /\n\t\t10;\n\treturn Math.min(1, tokenCoverage * averageScore);\n};\n\nexport const createCatalogSpanAlignment = (args: {\n\tspeechText: string;\n\tvisibleText: string;\n}): CatalogSpanAlignment => {\n\tconst extracted = extractSpokenText(args.speechText);\n\tconst visibleText = normalizeTextForSpeech(args.visibleText);\n\tconst spokenText = extracted.spokenText;\n\tconst spokenTokens = tokenize(spokenText);\n\tconst visibleTokens = tokenize(visibleText);\n\tconst anchors = extracted.unsupportedSemantic\n\t\t? []\n\t\t: computeAnchors(spokenTokens, visibleTokens);\n\tconst confidence = scoreConfidence(anchors, spokenTokens, visibleTokens);\n\tconst isExact = spokenText === visibleText && spokenText.length > 0;\n\tconst hasShortSingleExactAnchor =\n\t\tanchors.length === 1 &&\n\t\tanchors[0].score === 10 &&\n\t\tspokenTokens.length <= 2 &&\n\t\tvisibleTokens.length <= 2;\n\tconst playbackMode: CatalogChunkPlaybackMode = extracted.unsupportedSemantic\n\t\t? \"region-fallback\"\n\t\t: isExact\n\t\t\t? \"exact-word\"\n\t\t\t: anchors.length >= 2 || hasShortSingleExactAnchor\n\t\t\t\t? confidence >= 0.12\n\t\t\t\t\t? \"anchor-span\"\n\t\t\t\t\t: \"region-fallback\"\n\t\t\t\t: \"region-fallback\";\n\tconst boundaryOffsetMode: BoundaryOffsetMode = extracted.unsupportedSemantic\n\t\t? \"unsupported\"\n\t\t: extracted.hasMarkup\n\t\t\t? \"raw-ssml\"\n\t\t\t: \"plain-spoken-text\";\n\n\treturn {\n\t\tspeechText: args.speechText,\n\t\tspokenText,\n\t\tvisibleText,\n\t\tplaybackMode,\n\t\tboundaryOffsetMode,\n\t\tanchors,\n\t\tconfidence,\n\t\tspokenTokens,\n\t\tvisibleTokens,\n\t\trawToSpokenOffsetMap: extracted.rawToSpokenOffsetMap,\n\t};\n};\n\nconst findTokenAtOffset = (\n\ttokens: CatalogTextToken[],\n\toffset: number,\n): CatalogTextToken | null =>\n\ttokens.find((token) => token.start <= offset && offset < token.end) || null;\n\nconst normalizeBoundaryWord = (word?: string): string | null => {\n\tif (!word) return null;\n\tif (/^<[^>]+>$/.test(word.trim())) return null;\n\tconst token = tokenize(word)[0];\n\tif (!token) return null;\n\treturn NUMERIC_WORDS.get(token.normalized) || token.normalized;\n};\n\nconst candidateMatchesBoundaryWord = (\n\talignment: CatalogSpanAlignment,\n\tcandidate: { start: number; length: number } | null,\n\tboundaryWord?: string,\n): boolean => {\n\tconst normalizedBoundary = normalizeBoundaryWord(boundaryWord);\n\tif (!candidate || !normalizedBoundary) return false;\n\tconst token = findTokenAtOffset(alignment.spokenTokens, candidate.start);\n\tif (!token) return false;\n\treturn normalizedTokenValue(token) === normalizedBoundary;\n};\n\nconst mapRawOffsetToSpokenOffset = (\n\talignment: CatalogSpanAlignment,\n\tposition: number,\n): number | null => {\n\tconst direct = alignment.rawToSpokenOffsetMap.get(position);\n\tif (direct !== undefined) return direct;\n\tfor (let offset = position; offset < alignment.speechText.length; offset++) {\n\t\tconst mapped = alignment.rawToSpokenOffsetMap.get(offset);\n\t\tif (mapped !== undefined) return mapped;\n\t\tif (alignment.speechText[offset] === \">\") break;\n\t}\n\treturn null;\n};\n\nconst resolveRawBoundaryOffset = (\n\talignment: CatalogSpanAlignment,\n\tposition: number,\n\tsafeLength: number,\n): { start: number; length: number } | null => {\n\tconst mappedStart = mapRawOffsetToSpokenOffset(alignment, position);\n\tif (mappedStart === null) return null;\n\tconst mappedEnd = mapRawOffsetToSpokenOffset(\n\t\talignment,\n\t\tMath.max(position, position + safeLength - 1),\n\t);\n\tconst token = findTokenAtOffset(alignment.spokenTokens, mappedStart);\n\tconst mappedLength =\n\t\tmappedEnd !== null && mappedEnd >= mappedStart\n\t\t\t? mappedEnd - mappedStart + 1\n\t\t\t: token\n\t\t\t\t? token.end - mappedStart\n\t\t\t\t: safeLength;\n\treturn { start: mappedStart, length: Math.max(1, mappedLength) };\n};\n\nconst resolvePlainBoundaryOffset = (\n\talignment: CatalogSpanAlignment,\n\tposition: number,\n\tsafeLength: number,\n): { start: number; length: number } | null => {\n\tif (position < 0 || position >= alignment.spokenText.length) return null;\n\tconst token = findTokenAtOffset(alignment.spokenTokens, position);\n\tif (!token) return null;\n\treturn { start: position, length: Math.min(safeLength, token.end - position) };\n};\n\nexport const resolveSpokenBoundaryOffset = (\n\talignment: CatalogSpanAlignment,\n\tposition: number,\n\tlength = 1,\n\tboundaryWord?: string,\n): { start: number; length: number } | null => {\n\tif (\n\t\talignment.boundaryOffsetMode === \"unsupported\" ||\n\t\t!Number.isFinite(position)\n\t) {\n\t\treturn null;\n\t}\n\tconst safeLength = Math.max(1, Number.isFinite(length) ? length : 1);\n\tif (boundaryWord && /^<[^>]+>$/.test(boundaryWord.trim())) return null;\n\tif (alignment.boundaryOffsetMode === \"raw-ssml\") {\n\t\tconst rawCandidate = resolveRawBoundaryOffset(\n\t\t\talignment,\n\t\t\tposition,\n\t\t\tsafeLength,\n\t\t);\n\t\tconst plainCandidate = resolvePlainBoundaryOffset(\n\t\t\talignment,\n\t\t\tposition,\n\t\t\tsafeLength,\n\t\t);\n\t\tif (boundaryWord) {\n\t\t\tif (\n\t\t\t\tcandidateMatchesBoundaryWord(alignment, rawCandidate, boundaryWord)\n\t\t\t) {\n\t\t\t\treturn rawCandidate;\n\t\t\t}\n\t\t\tif (\n\t\t\t\tcandidateMatchesBoundaryWord(alignment, plainCandidate, boundaryWord)\n\t\t\t) {\n\t\t\t\treturn plainCandidate;\n\t\t\t}\n\t\t\tif (normalizeBoundaryWord(boundaryWord)) return null;\n\t\t}\n\t\treturn rawCandidate || plainCandidate;\n\t}\n\treturn (\n\t\tresolvePlainBoundaryOffset(alignment, position, safeLength) ||\n\t\tresolveRawBoundaryOffset(alignment, position, safeLength)\n\t);\n};\n\nexport const resolveVisibleSpanForBoundary = (\n\talignment: CatalogSpanAlignment,\n\tspokenOffset: number,\n): { start: number; end: number } | null => {\n\tif (alignment.playbackMode === \"region-fallback\") return null;\n\tconst exactAnchor = alignment.anchors.find(\n\t\t(anchor) => anchor.spokenStart <= spokenOffset && spokenOffset < anchor.spokenEnd,\n\t);\n\tif (exactAnchor) {\n\t\treturn { start: exactAnchor.visibleStart, end: exactAnchor.visibleEnd };\n\t}\n\n\tlet previous: CatalogSpanAnchor | null = null;\n\tlet next: CatalogSpanAnchor | null = null;\n\tfor (const anchor of alignment.anchors) {\n\t\tif (anchor.spokenEnd <= spokenOffset) {\n\t\t\tprevious = anchor;\n\t\t\tcontinue;\n\t\t}\n\t\tif (anchor.spokenStart > spokenOffset) {\n\t\t\tnext = anchor;\n\t\t\tbreak;\n\t\t}\n\t}\n\tif (previous && next) {\n\t\treturn {\n\t\t\tstart: Math.min(previous.visibleStart, next.visibleStart),\n\t\t\tend: Math.max(previous.visibleEnd, next.visibleEnd),\n\t\t};\n\t}\n\treturn null;\n};\n"]}
|
|
1
|
+
{"version":3,"file":"catalog-span-alignment.js","sourceRoot":"","sources":["../../../src/services/tts/catalog-span-alignment.ts"],"names":[],"mappings":"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","sourcesContent":["import { extractSpokenText } from \"./ssml/spoken-text.js\";\nimport {\n\tcreateSpeechAlignmentTokenPattern,\n\tnormalizeTextForSpeech,\n} from \"./text-processing.js\";\n\nexport type CatalogChunkPlaybackMode =\n\t| \"exact-word\"\n\t| \"anchor-span\"\n\t| \"region-fallback\";\n\nexport type BoundaryOffsetMode =\n\t| \"plain-spoken-text\"\n\t| \"raw-ssml\"\n\t| \"unsupported\";\n\nexport interface CatalogTextToken {\n\ttext: string;\n\tnormalized: string;\n\tstart: number;\n\tend: number;\n}\n\nexport interface CatalogSpanAnchor {\n\tspokenStart: number;\n\tspokenEnd: number;\n\tvisibleStart: number;\n\tvisibleEnd: number;\n\tscore: number;\n}\n\nexport interface CatalogSpanAlignment {\n\tspeechText: string;\n\tspokenText: string;\n\tvisibleText: string;\n\tplaybackMode: CatalogChunkPlaybackMode;\n\tboundaryOffsetMode: BoundaryOffsetMode;\n\tanchors: CatalogSpanAnchor[];\n\tconfidence: number;\n\tspokenTokens: CatalogTextToken[];\n\tvisibleTokens: CatalogTextToken[];\n\trawToSpokenOffsetMap: Map<number, number>;\n}\n\ninterface MatchCandidate {\n\tspokenStartToken: number;\n\tspokenEndToken: number;\n\tvisibleToken: number;\n\tscore: number;\n}\n\n// Shared Unicode-aware tokenizer (see `createSpeechAlignmentTokenPattern`), so\n// accented Latin and non-Latin words tokenize the same way here as in the math\n// speech tokenizer. LIMITATION (i18n): the phrase table below is still English,\n// so multi-word operator phrases (\"plus or minus\", \"divided by\") only align for\n// English speech; other locales degrade to coarse region highlighting. A fresh\n// pattern per call because of the global flag's mutable `lastIndex`.\nconst TOKEN_PATTERN = createSpeechAlignmentTokenPattern();\n\nconst NUMERIC_WORDS = new Map<string, string>([\n\t[\"zero\", \"0\"],\n\t[\"one\", \"1\"],\n\t[\"two\", \"2\"],\n\t[\"three\", \"3\"],\n\t[\"four\", \"4\"],\n\t[\"five\", \"5\"],\n\t[\"six\", \"6\"],\n\t[\"seven\", \"7\"],\n\t[\"eight\", \"8\"],\n\t[\"nine\", \"9\"],\n\t[\"ten\", \"10\"],\n]);\n\nconst SPOKEN_PHRASE_TARGETS: Array<{\n\tphrase: string[];\n\tvisible: string;\n\tscore: number;\n}> = [\n\t{ phrase: [\"plus\", \"or\", \"minus\"], visible: \"±\", score: 8 },\n\t{ phrase: [\"square\", \"root\"], visible: \"√\", score: 8 },\n\t{ phrase: [\"divided\", \"by\"], visible: \"/\", score: 7 },\n\t{ phrase: [\"all\", \"over\"], visible: \"/\", score: 7 },\n\t{ phrase: [\"equals\"], visible: \"=\", score: 7 },\n\t{ phrase: [\"equal\"], visible: \"=\", score: 7 },\n\t{ phrase: [\"times\"], visible: \"\\u2062\", score: 7 },\n\t{ phrase: [\"multiplied\", \"by\"], visible: \"\\u2062\", score: 7 },\n\t{ phrase: [\"over\"], visible: \"/\", score: 6 },\n\t{ phrase: [\"squared\"], visible: \"²\", score: 6 },\n\t{ phrase: [\"squared\"], visible: \"2\", score: 4 },\n\t{ phrase: [\"negative\"], visible: \"-\", score: 5 },\n\t{ phrase: [\"minus\"], visible: \"-\", score: 5 },\n\t{ phrase: [\"plus\"], visible: \"+\", score: 5 },\n];\n\nconst tokenize = (text: string): CatalogTextToken[] => {\n\tconst tokens: CatalogTextToken[] = [];\n\tfor (const match of text.matchAll(TOKEN_PATTERN)) {\n\t\tconst value = match[0];\n\t\tconst start = match.index ?? 0;\n\t\ttokens.push({\n\t\t\ttext: value,\n\t\t\tnormalized: value.toLowerCase(),\n\t\t\tstart,\n\t\t\tend: start + value.length,\n\t\t});\n\t}\n\treturn tokens;\n};\n\nconst normalizedTokenValue = (token: CatalogTextToken): string =>\n\tNUMERIC_WORDS.get(token.normalized) || token.normalized;\n\nconst createCandidates = (\n\tspokenTokens: CatalogTextToken[],\n\tvisibleTokens: CatalogTextToken[],\n): MatchCandidate[] => {\n\tconst candidates: MatchCandidate[] = [];\n\tfor (let spokenIndex = 0; spokenIndex < spokenTokens.length; spokenIndex++) {\n\t\tfor (\n\t\t\tlet visibleIndex = 0;\n\t\t\tvisibleIndex < visibleTokens.length;\n\t\t\tvisibleIndex++\n\t\t) {\n\t\t\tconst spokenValue = normalizedTokenValue(spokenTokens[spokenIndex]);\n\t\t\tconst visibleValue = normalizedTokenValue(visibleTokens[visibleIndex]);\n\t\t\tif (spokenValue === visibleValue) {\n\t\t\t\tcandidates.push({\n\t\t\t\t\tspokenStartToken: spokenIndex,\n\t\t\t\t\tspokenEndToken: spokenIndex + 1,\n\t\t\t\t\tvisibleToken: visibleIndex,\n\t\t\t\t\tscore: 10,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\tfor (const phraseTarget of SPOKEN_PHRASE_TARGETS) {\n\t\t\tconst phraseTokens = spokenTokens.slice(\n\t\t\t\tspokenIndex,\n\t\t\t\tspokenIndex + phraseTarget.phrase.length,\n\t\t\t);\n\t\t\tif (phraseTokens.length !== phraseTarget.phrase.length) continue;\n\t\t\tif (\n\t\t\t\t!phraseTokens.every(\n\t\t\t\t\t(token, tokenIndex) =>\n\t\t\t\t\t\ttoken.normalized === phraseTarget.phrase[tokenIndex],\n\t\t\t\t)\n\t\t\t) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tfor (\n\t\t\t\tlet visibleIndex = 0;\n\t\t\t\tvisibleIndex < visibleTokens.length;\n\t\t\t\tvisibleIndex++\n\t\t\t) {\n\t\t\t\tif (visibleTokens[visibleIndex].normalized !== phraseTarget.visible) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tcandidates.push({\n\t\t\t\t\tspokenStartToken: spokenIndex,\n\t\t\t\t\tspokenEndToken: spokenIndex + phraseTarget.phrase.length,\n\t\t\t\t\tvisibleToken: visibleIndex,\n\t\t\t\t\tscore: phraseTarget.score,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\treturn candidates;\n};\n\nconst computeAnchors = (\n\tspokenTokens: CatalogTextToken[],\n\tvisibleTokens: CatalogTextToken[],\n): CatalogSpanAnchor[] => {\n\tconst candidates = createCandidates(spokenTokens, visibleTokens).sort(\n\t\t(left, right) =>\n\t\t\tspokenTokens[left.spokenStartToken].start -\n\t\t\t\tspokenTokens[right.spokenStartToken].start ||\n\t\t\tvisibleTokens[left.visibleToken].start -\n\t\t\t\tvisibleTokens[right.visibleToken].start,\n\t);\n\tconst bestScore = new Array(candidates.length).fill(0) as number[];\n\tconst previous = new Array(candidates.length).fill(-1) as number[];\n\n\tfor (let i = 0; i < candidates.length; i++) {\n\t\tconst candidate = candidates[i];\n\t\tbestScore[i] = candidate.score;\n\t\tfor (let j = 0; j < i; j++) {\n\t\t\tconst prior = candidates[j];\n\t\t\tif (\n\t\t\t\tprior.spokenEndToken <= candidate.spokenStartToken &&\n\t\t\t\tprior.visibleToken < candidate.visibleToken\n\t\t\t) {\n\t\t\t\tconst spokenGap = candidate.spokenStartToken - prior.spokenEndToken;\n\t\t\t\tconst visibleGap = candidate.visibleToken - prior.visibleToken - 1;\n\t\t\t\tconst gapPenalty = Math.max(0, Math.abs(spokenGap - visibleGap) - 2);\n\t\t\t\tconst score = bestScore[j] + candidate.score - gapPenalty;\n\t\t\t\tif (score > bestScore[i]) {\n\t\t\t\t\tbestScore[i] = score;\n\t\t\t\t\tprevious[i] = j;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tlet bestIndex = -1;\n\tfor (let i = 0; i < bestScore.length; i++) {\n\t\tif (bestIndex < 0 || bestScore[i] > bestScore[bestIndex]) {\n\t\t\tbestIndex = i;\n\t\t}\n\t}\n\tif (bestIndex < 0) return [];\n\n\tconst chosen: MatchCandidate[] = [];\n\tlet current = bestIndex;\n\twhile (current >= 0) {\n\t\tchosen.unshift(candidates[current]);\n\t\tcurrent = previous[current];\n\t}\n\n\treturn chosen.map((candidate) => {\n\t\tconst firstSpoken = spokenTokens[candidate.spokenStartToken];\n\t\tconst lastSpoken = spokenTokens[candidate.spokenEndToken - 1];\n\t\tconst visible = visibleTokens[candidate.visibleToken];\n\t\treturn {\n\t\t\tspokenStart: firstSpoken.start,\n\t\t\tspokenEnd: lastSpoken.end,\n\t\t\tvisibleStart: visible.start,\n\t\t\tvisibleEnd: visible.end,\n\t\t\tscore: candidate.score,\n\t\t};\n\t});\n};\n\nconst scoreConfidence = (\n\tanchors: CatalogSpanAnchor[],\n\tspokenTokens: CatalogTextToken[],\n\tvisibleTokens: CatalogTextToken[],\n): number => {\n\tif (anchors.length === 0) return 0;\n\tconst tokenCoverage =\n\t\tanchors.length /\n\t\tMath.max(1, Math.min(spokenTokens.length, visibleTokens.length));\n\tconst averageScore =\n\t\tanchors.reduce((total, anchor) => total + anchor.score, 0) /\n\t\tanchors.length /\n\t\t10;\n\treturn Math.min(1, tokenCoverage * averageScore);\n};\n\nexport const createCatalogSpanAlignment = (args: {\n\tspeechText: string;\n\tvisibleText: string;\n}): CatalogSpanAlignment => {\n\tconst extracted = extractSpokenText(args.speechText);\n\tconst visibleText = normalizeTextForSpeech(args.visibleText);\n\tconst spokenText = extracted.spokenText;\n\tconst spokenTokens = tokenize(spokenText);\n\tconst visibleTokens = tokenize(visibleText);\n\tconst anchors = extracted.unsupportedSemantic\n\t\t? []\n\t\t: computeAnchors(spokenTokens, visibleTokens);\n\tconst confidence = scoreConfidence(anchors, spokenTokens, visibleTokens);\n\tconst isExact = spokenText === visibleText && spokenText.length > 0;\n\tconst hasShortSingleExactAnchor =\n\t\tanchors.length === 1 &&\n\t\tanchors[0].score === 10 &&\n\t\tspokenTokens.length <= 2 &&\n\t\tvisibleTokens.length <= 2;\n\tconst playbackMode: CatalogChunkPlaybackMode = extracted.unsupportedSemantic\n\t\t? \"region-fallback\"\n\t\t: isExact\n\t\t\t? \"exact-word\"\n\t\t\t: anchors.length >= 2 || hasShortSingleExactAnchor\n\t\t\t\t? confidence >= 0.12\n\t\t\t\t\t? \"anchor-span\"\n\t\t\t\t\t: \"region-fallback\"\n\t\t\t\t: \"region-fallback\";\n\tconst boundaryOffsetMode: BoundaryOffsetMode = extracted.unsupportedSemantic\n\t\t? \"unsupported\"\n\t\t: extracted.hasMarkup\n\t\t\t? \"raw-ssml\"\n\t\t\t: \"plain-spoken-text\";\n\n\treturn {\n\t\tspeechText: args.speechText,\n\t\tspokenText,\n\t\tvisibleText,\n\t\tplaybackMode,\n\t\tboundaryOffsetMode,\n\t\tanchors,\n\t\tconfidence,\n\t\tspokenTokens,\n\t\tvisibleTokens,\n\t\trawToSpokenOffsetMap: extracted.rawToSpokenOffsetMap,\n\t};\n};\n\nconst findTokenAtOffset = (\n\ttokens: CatalogTextToken[],\n\toffset: number,\n): CatalogTextToken | null =>\n\ttokens.find((token) => token.start <= offset && offset < token.end) || null;\n\nconst normalizeBoundaryWord = (word?: string): string | null => {\n\tif (!word) return null;\n\tif (/^<[^>]+>$/.test(word.trim())) return null;\n\tconst token = tokenize(word)[0];\n\tif (!token) return null;\n\treturn NUMERIC_WORDS.get(token.normalized) || token.normalized;\n};\n\nconst candidateMatchesBoundaryWord = (\n\talignment: CatalogSpanAlignment,\n\tcandidate: { start: number; length: number } | null,\n\tboundaryWord?: string,\n): boolean => {\n\tconst normalizedBoundary = normalizeBoundaryWord(boundaryWord);\n\tif (!candidate || !normalizedBoundary) return false;\n\tconst token = findTokenAtOffset(alignment.spokenTokens, candidate.start);\n\tif (!token) return false;\n\treturn normalizedTokenValue(token) === normalizedBoundary;\n};\n\nconst mapRawOffsetToSpokenOffset = (\n\talignment: CatalogSpanAlignment,\n\tposition: number,\n): number | null => {\n\tconst direct = alignment.rawToSpokenOffsetMap.get(position);\n\tif (direct !== undefined) return direct;\n\tfor (let offset = position; offset < alignment.speechText.length; offset++) {\n\t\tconst mapped = alignment.rawToSpokenOffsetMap.get(offset);\n\t\tif (mapped !== undefined) return mapped;\n\t\tif (alignment.speechText[offset] === \">\") break;\n\t}\n\treturn null;\n};\n\nconst resolveRawBoundaryOffset = (\n\talignment: CatalogSpanAlignment,\n\tposition: number,\n\tsafeLength: number,\n): { start: number; length: number } | null => {\n\tconst mappedStart = mapRawOffsetToSpokenOffset(alignment, position);\n\tif (mappedStart === null) return null;\n\tconst mappedEnd = mapRawOffsetToSpokenOffset(\n\t\talignment,\n\t\tMath.max(position, position + safeLength - 1),\n\t);\n\tconst token = findTokenAtOffset(alignment.spokenTokens, mappedStart);\n\tconst mappedLength =\n\t\tmappedEnd !== null && mappedEnd >= mappedStart\n\t\t\t? mappedEnd - mappedStart + 1\n\t\t\t: token\n\t\t\t\t? token.end - mappedStart\n\t\t\t\t: safeLength;\n\treturn { start: mappedStart, length: Math.max(1, mappedLength) };\n};\n\nconst resolvePlainBoundaryOffset = (\n\talignment: CatalogSpanAlignment,\n\tposition: number,\n\tsafeLength: number,\n): { start: number; length: number } | null => {\n\tif (position < 0 || position >= alignment.spokenText.length) return null;\n\tconst token = findTokenAtOffset(alignment.spokenTokens, position);\n\tif (!token) return null;\n\treturn {\n\t\tstart: position,\n\t\tlength: Math.min(safeLength, token.end - position),\n\t};\n};\n\nexport const resolveSpokenBoundaryOffset = (\n\talignment: CatalogSpanAlignment,\n\tposition: number,\n\tlength = 1,\n\tboundaryWord?: string,\n): { start: number; length: number } | null => {\n\tif (\n\t\talignment.boundaryOffsetMode === \"unsupported\" ||\n\t\t!Number.isFinite(position)\n\t) {\n\t\treturn null;\n\t}\n\tconst safeLength = Math.max(1, Number.isFinite(length) ? length : 1);\n\tif (boundaryWord && /^<[^>]+>$/.test(boundaryWord.trim())) return null;\n\tif (alignment.boundaryOffsetMode === \"raw-ssml\") {\n\t\tconst rawCandidate = resolveRawBoundaryOffset(\n\t\t\talignment,\n\t\t\tposition,\n\t\t\tsafeLength,\n\t\t);\n\t\tconst plainCandidate = resolvePlainBoundaryOffset(\n\t\t\talignment,\n\t\t\tposition,\n\t\t\tsafeLength,\n\t\t);\n\t\tif (boundaryWord) {\n\t\t\tif (candidateMatchesBoundaryWord(alignment, rawCandidate, boundaryWord)) {\n\t\t\t\treturn rawCandidate;\n\t\t\t}\n\t\t\tif (\n\t\t\t\tcandidateMatchesBoundaryWord(alignment, plainCandidate, boundaryWord)\n\t\t\t) {\n\t\t\t\treturn plainCandidate;\n\t\t\t}\n\t\t\tif (normalizeBoundaryWord(boundaryWord)) return null;\n\t\t}\n\t\treturn rawCandidate || plainCandidate;\n\t}\n\treturn (\n\t\tresolvePlainBoundaryOffset(alignment, position, safeLength) ||\n\t\tresolveRawBoundaryOffset(alignment, position, safeLength)\n\t);\n};\n\nexport const resolveVisibleSpanForBoundary = (\n\talignment: CatalogSpanAlignment,\n\tspokenOffset: number,\n): { start: number; end: number } | null => {\n\tif (alignment.playbackMode === \"region-fallback\") return null;\n\tconst exactAnchor = alignment.anchors.find(\n\t\t(anchor) =>\n\t\t\tanchor.spokenStart <= spokenOffset && spokenOffset < anchor.spokenEnd,\n\t);\n\tif (exactAnchor) {\n\t\treturn { start: exactAnchor.visibleStart, end: exactAnchor.visibleEnd };\n\t}\n\n\tlet previous: CatalogSpanAnchor | null = null;\n\tlet next: CatalogSpanAnchor | null = null;\n\tfor (const anchor of alignment.anchors) {\n\t\tif (anchor.spokenEnd <= spokenOffset) {\n\t\t\tprevious = anchor;\n\t\t\tcontinue;\n\t\t}\n\t\tif (anchor.spokenStart > spokenOffset) {\n\t\t\tnext = anchor;\n\t\t\tbreak;\n\t\t}\n\t}\n\tif (previous && next) {\n\t\treturn {\n\t\t\tstart: Math.min(previous.visibleStart, next.visibleStart),\n\t\t\tend: Math.max(previous.visibleEnd, next.visibleEnd),\n\t\t};\n\t}\n\treturn null;\n};\n"]}
|