@omote/core 0.4.4 → 0.4.6

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/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/events/EventEmitter.ts","../src/audio/MicrophoneCapture.ts","../src/audio/RingBuffer.ts","../src/audio/AudioScheduler.ts","../src/audio/AudioChunkCoalescer.ts","../src/audio/LAMPipeline.ts","../src/audio/SyncedAudioPipeline.ts","../src/animation/EmotionToBlendshapeMapper.ts","../src/animation/audioEnergy.ts","../src/telemetry/exporters/console.ts","../src/telemetry/exporters/otlp.ts","../src/telemetry/OmoteTelemetry.ts","../src/telemetry/types.ts","../src/cache/ModelCache.ts","../src/logging/types.ts","../src/logging/formatters.ts","../src/logging/Logger.ts","../src/utils/runtime.ts","../src/inference/onnxLoader.ts","../src/inference/blendshapeUtils.ts","../src/inference/Wav2Vec2Inference.ts","../src/audio/FullFacePipeline.ts","../src/inference/kaldiFbank.ts","../src/inference/ctcDecoder.ts","../src/inference/SenseVoiceInference.ts","../src/inference/Wav2ArkitCpuInference.ts","../src/inference/createLipSync.ts","../src/inference/SileroVADInference.ts","../src/inference/SileroVADWorker.ts","../src/inference/createSileroVAD.ts","../src/inference/SafariSpeechRecognition.ts","../src/emotion/Emotion.ts","../src/ai/adapters/AgentCoreAdapter.ts","../src/ai/orchestration/ConversationOrchestrator.ts","../src/ai/tenancy/TenantManager.ts","../src/ai/utils/AudioSyncManager.ts","../src/ai/utils/InterruptionHandler.ts","../src/animation/types.ts","../src/animation/AnimationGraph.ts","../src/animation/simplex2d.ts","../src/animation/ProceduralLifeLayer.ts","../../types/src/protocol.ts"],"sourcesContent":["/**\r\n * @omote/core - WebGPU-accelerated AI character SDK\r\n *\r\n * Real-time lip sync, speech recognition, voice activity detection, and avatar animation\r\n * that runs entirely in the browser using ONNX Runtime Web.\r\n *\r\n * @packageDocumentation\r\n *\r\n * ## Main Components\r\n *\r\n * - **{@link Wav2Vec2Inference}** - LAM lip sync (52 ARKit blendshapes)\r\n * - **{@link SenseVoiceInference}** - SenseVoice ASR + emotion + language ID\r\n * - **{@link SileroVADInference}** - Voice activity detection\r\n * - **{@link EmotionController}** - Emotion state with smooth transitions\r\n *\r\n * ## Quick Start\r\n *\r\n * ```typescript\r\n * import { Wav2Vec2Inference, SenseVoiceInference } from '@omote/core';\r\n *\r\n * // Lip sync\r\n * const lam = new Wav2Vec2Inference({ modelUrl: '/models/lam-wav2vec2.onnx' });\r\n * await lam.load();\r\n * const { blendshapes } = await lam.infer(audioSamples);\r\n *\r\n * // Speech-to-text\r\n * const asr = new SenseVoiceInference({ modelUrl: '/models/sensevoice/model.int8.onnx' });\r\n * await asr.load();\r\n * const { text, emotion, language } = await asr.transcribe(audioSamples);\r\n * ```\r\n */\r\n\r\n// Events\r\nexport { EventEmitter } from './events';\r\nexport type {\r\n OmoteEvents,\r\n AnimationEvent,\r\n VisemeEvent,\r\n EmotionEvent,\r\n GazeEvent,\r\n TTSStartEvent,\r\n TTSMarkEvent,\r\n TTSEndEvent,\r\n STTPartialEvent,\r\n STTFinalEvent,\r\n SessionStateEvent,\r\n BackendEvent,\r\n} from './events';\r\n\r\n// Audio\r\nexport {\r\n MicrophoneCapture,\r\n RingBuffer,\r\n AudioScheduler,\r\n AudioChunkCoalescer,\r\n LAMPipeline,\r\n SyncedAudioPipeline,\r\n FullFacePipeline,\r\n} from './audio';\r\nexport type {\r\n MicrophoneCaptureConfig,\r\n AudioSchedulerOptions,\r\n AudioChunkCoalescerOptions,\r\n LAMPipelineOptions,\r\n LAMFrame,\r\n SyncedAudioPipelineOptions,\r\n SyncedAudioPipelineEvents,\r\n FullFacePipelineOptions,\r\n FullFaceFrame,\r\n FullFacePipelineEvents,\r\n} from './audio';\r\n\r\n// Inference - SenseVoice ASR (Speech Recognition + Emotion + Language ID + Audio Events)\r\nexport { SenseVoiceInference } from './inference';\r\nexport type {\r\n SenseVoiceConfig,\r\n SenseVoiceLanguage,\r\n SenseVoiceResult,\r\n SenseVoiceModelInfo,\r\n} from './inference';\r\n\r\n// Inference - SenseVoice Preprocessing\r\nexport { computeKaldiFbank, applyLFR, applyCMVN, parseCMVNFromMetadata } from './inference';\r\nexport type { KaldiFbankOptions } from './inference';\r\n\r\n// Inference - SenseVoice CTC Decoder\r\nexport { ctcGreedyDecode, parseTokensFile, resolveLanguageId, resolveTextNormId } from './inference';\r\nexport type { CTCDecodeResult } from './inference';\r\n\r\n// Inference - Unified Wav2Vec2 (ASR + A2E)\r\nexport { Wav2Vec2Inference, LAM_BLENDSHAPES, ARKIT_BLENDSHAPES, CTC_VOCAB } from './inference';\r\nexport type {\r\n Wav2Vec2InferenceConfig,\r\n Wav2Vec2Result,\r\n} from './inference';\r\n\r\n// Inference - Wav2ARKit CPU (Safari/iOS lip sync)\r\nexport { Wav2ArkitCpuInference } from './inference';\r\nexport type { Wav2ArkitCpuConfig } from './inference';\r\n\r\n// Inference - Lip Sync Factory (auto GPU/CPU selection)\r\nexport { createLipSync } from './inference';\r\nexport type {\r\n CreateLipSyncConfig,\r\n LipSyncBackend,\r\n LipSyncResult,\r\n LipSyncModelInfo,\r\n} from './inference';\r\n\r\n// Inference - Blendshape Utilities\r\nexport { symmetrizeBlendshapes, remapWav2ArkitToLam, WAV2ARKIT_BLENDSHAPES } from './inference';\r\n\r\n// Inference - Silero VAD (Voice Activity Detection)\r\nexport { SileroVADInference } from './inference';\r\nexport type {\r\n SileroVADConfig,\r\n VADModelInfo,\r\n VADResult,\r\n SpeechSegment,\r\n VADBackend,\r\n} from './inference';\r\n\r\n// Inference - Silero VAD Worker (Off-main-thread)\r\nexport { SileroVADWorker } from './inference';\r\nexport type {\r\n VADWorkerConfig,\r\n VADWorkerModelInfo,\r\n} from './inference';\r\n\r\n// Inference - Silero VAD Factory (Recommended API)\r\nexport { createSileroVAD, supportsVADWorker } from './inference';\r\nexport type {\r\n SileroVADFactoryConfig,\r\n SileroVADBackend,\r\n} from './inference';\r\n\r\n// Inference - Safari Web Speech API (iOS native ASR)\r\nexport { SafariSpeechRecognition } from './inference';\r\nexport type {\r\n SafariSpeechConfig,\r\n SpeechRecognitionResult,\r\n SpeechResultCallback,\r\n SpeechErrorCallback,\r\n} from './inference';\r\n\r\n// Emotion\r\nexport {\r\n EMOTION_NAMES,\r\n EMOTION_VECTOR_SIZE,\r\n EmotionPresets,\r\n EmotionController,\r\n createEmotionVector,\r\n getEmotionPreset,\r\n blendEmotions,\r\n lerpEmotion,\r\n} from './emotion';\r\nexport type {\r\n EmotionName,\r\n EmotionWeights,\r\n EmotionPresetName,\r\n} from './emotion';\r\n\r\n// AI Adapters\r\nexport {\r\n AgentCoreAdapter,\r\n ConversationOrchestrator,\r\n TenantManager,\r\n AudioSyncManager,\r\n InterruptionHandler,\r\n} from './ai';\r\n\r\n// Cache\r\nexport {\r\n ModelCache,\r\n getModelCache,\r\n fetchWithCache,\r\n preloadModels,\r\n formatBytes,\r\n getCacheKey,\r\n configureCacheLimit,\r\n getCacheConfig,\r\n} from './cache/ModelCache';\r\nexport type { ValidationResult, FetchWithCacheOptions, CacheConfig, QuotaInfo } from './cache/ModelCache';\r\n\r\n// Utils - Runtime Detection & Backend Selection\r\nexport {\r\n isIOS,\r\n isAndroid,\r\n isMobile,\r\n isIOSSafari,\r\n isSafari,\r\n hasWebGPUApi,\r\n getRecommendedBackend,\r\n resolveBackend,\r\n getOptimalWasmThreads,\r\n shouldEnableWasmProxy,\r\n // Platform-specific utilities\r\n isSpeechRecognitionAvailable,\r\n shouldUseNativeASR,\r\n shouldUseServerLipSync,\r\n shouldUseCpuLipSync,\r\n} from './utils/runtime';\r\nexport type { RuntimeBackend, BackendPreference } from './utils/runtime';\r\n\r\n// ONNX Runtime Lazy Loader\r\nexport {\r\n getOnnxRuntime,\r\n getOnnxRuntimeForPreference,\r\n isWebGPUAvailable,\r\n getSessionOptions,\r\n createSessionWithFallback,\r\n getLoadedBackend,\r\n isOnnxRuntimeLoaded,\r\n preloadOnnxRuntime,\r\n} from './inference/onnxLoader';\r\nexport type { SessionOptions } from './inference/onnxLoader';\r\n\r\n// Logging\r\nexport {\r\n configureLogging,\r\n getLoggingConfig,\r\n resetLoggingConfig,\r\n setLogLevel,\r\n setLoggingEnabled,\r\n createLogger,\r\n noopLogger,\r\n LOG_LEVEL_PRIORITY,\r\n DEFAULT_LOGGING_CONFIG,\r\n} from './logging';\r\nexport type {\r\n LogLevel,\r\n LogEntry,\r\n LogSink,\r\n LogFormatter,\r\n LoggingConfig,\r\n ILogger,\r\n} from './logging';\r\n\r\n// Telemetry\r\nexport {\r\n OmoteTelemetry,\r\n configureTelemetry,\r\n getTelemetry,\r\n ConsoleExporter,\r\n OTLPExporter,\r\n MetricNames,\r\n INFERENCE_LATENCY_BUCKETS,\r\n MODEL_LOAD_TIME_BUCKETS,\r\n} from './telemetry';\r\nexport type {\r\n ActiveSpan,\r\n TelemetryConfig,\r\n TelemetryExporter,\r\n OTLPExporterConfig,\r\n SamplingConfig,\r\n SpanAttributes,\r\n ModelSpanAttributes,\r\n InferenceSpanAttributes,\r\n CacheSpanAttributes,\r\n TelemetryExporterInterface,\r\n SpanData,\r\n MetricData,\r\n} from './telemetry';\r\n\r\nexport type {\r\n AIAdapter,\r\n AIAdapterEvents,\r\n AISessionState,\r\n SessionConfig,\r\n TenantConfig,\r\n VoiceConfig,\r\n ConversationMessage,\r\n MessageRole,\r\n ConversationSession,\r\n SessionSnapshot,\r\n AgentCoreConfig,\r\n OrchestratorConfig,\r\n OrchestratorEvents,\r\n TenantQuota,\r\n TenantUsage,\r\n TokenRefreshCallback,\r\n AudioSyncConfig,\r\n AudioSyncEvents,\r\n InterruptionConfig,\r\n InterruptionEvents,\r\n} from './ai';\r\n\r\n// Animation\r\nexport {\r\n AnimationGraph,\r\n AudioEnergyAnalyzer,\r\n EmphasisDetector,\r\n EmotionToBlendshapeMapper,\r\n ProceduralLifeLayer,\r\n UPPER_FACE_BLENDSHAPES,\r\n EMOTION_ARKIT_MAP,\r\n calculateRMS,\r\n calculatePeak,\r\n DEFAULT_ANIMATION_CONFIG,\r\n} from './animation';\r\nexport type {\r\n EmotionLabel,\r\n AnimationStateName,\r\n AnimationTrigger,\r\n AnimationLayer,\r\n AnimationClip,\r\n BlendWeight,\r\n AnimationState,\r\n Transition,\r\n EmotionAnimationMap,\r\n AnimationGraphConfig,\r\n AnimationOutput,\r\n AnimationGraphEvents,\r\n EmotionFrame,\r\n Emotion2VecLabel,\r\n UpperFaceBlendshapeName,\r\n UpperFaceBlendshapes,\r\n EmotionBlendMode,\r\n EmotionBlendshapeConfig,\r\n LifeLayerConfig,\r\n LifeLayerInput,\r\n LifeLayerOutput,\r\n} from './animation';\r\n\r\n// Platform types (shared with backend via @omote/types)\r\nexport type {\r\n AvatarFormat,\r\n GSplatConfig,\r\n CharacterAvatar,\r\n CharacterVoice,\r\n CharacterPersonality,\r\n CharacterMemory,\r\n Character,\r\n CharacterSpec,\r\n} from '@omote/types';\r\nexport type {\r\n SessionStatus,\r\n SessionMessage,\r\n PlatformSession,\r\n} from '@omote/types';\r\nexport type {\r\n LAMJobStatus,\r\n LAMJob,\r\n ARKitToFLAMEMapping,\r\n} from '@omote/types';\r\nexport type {\r\n ResponseStartEvent,\r\n ResponseChunkEvent,\r\n AudioChunkEvent,\r\n ResponseEndEvent,\r\n ErrorEvent as ProtocolErrorEvent,\r\n ProtocolEvent,\r\n} from '@omote/types';\r\nexport { PROTOCOL_VERSION, isProtocolEvent } from '@omote/types';\r\nexport type {\r\n PaginatedResponse,\r\n ApiError,\r\n CreateCharacterRequest,\r\n CreateCharacterResponse,\r\n CreateSessionRequest,\r\n CreateSessionResponse,\r\n CreateLAMJobRequest,\r\n CreateLAMJobResponse,\r\n} from '@omote/types';\r\n","/**\n * Type-safe event emitter for Omote core events\n *\n * @category Events\n */\n\nexport type EventCallback<T = unknown> = (data: T) => void;\n\nexport class EventEmitter<TEvents extends { [key: string]: unknown }> {\n private listeners = new Map<keyof TEvents, Set<EventCallback<unknown>>>();\n\n on<K extends keyof TEvents>(event: K, callback: EventCallback<TEvents[K]>): () => void {\n if (!this.listeners.has(event)) {\n this.listeners.set(event, new Set());\n }\n this.listeners.get(event)!.add(callback as EventCallback<unknown>);\n\n // Return unsubscribe function\n return () => this.off(event, callback);\n }\n\n off<K extends keyof TEvents>(event: K, callback: EventCallback<TEvents[K]>): void {\n this.listeners.get(event)?.delete(callback as EventCallback<unknown>);\n }\n\n emit<K extends keyof TEvents>(event: K, data: TEvents[K]): void {\n this.listeners.get(event)?.forEach((cb) => cb(data));\n }\n\n once<K extends keyof TEvents>(event: K, callback: EventCallback<TEvents[K]>): () => void {\n const wrapper: EventCallback<TEvents[K]> = (data) => {\n this.off(event, wrapper);\n callback(data);\n };\n return this.on(event, wrapper);\n }\n\n removeAllListeners(event?: keyof TEvents): void {\n if (event) {\n this.listeners.delete(event);\n } else {\n this.listeners.clear();\n }\n }\n}\n","/**\n * Microphone capture - renderer-agnostic audio input\n *\n * Captures audio from the microphone and emits PCM chunks.\n * Works in any JavaScript environment with Web Audio API.\n *\n * @category Audio\n */\n\nimport { EventEmitter, type OmoteEvents } from '../events';\n\nexport interface MicrophoneCaptureConfig {\n /** Target sample rate (default: 16000 for speech processing) */\n sampleRate?: number;\n /** Chunk size in samples (default: 1600 = 100ms at 16kHz) */\n chunkSize?: number;\n}\n\nexport class MicrophoneCapture {\n private config: Required<MicrophoneCaptureConfig>;\n private stream: MediaStream | null = null;\n private context: AudioContext | null = null;\n private processor: ScriptProcessorNode | null = null;\n private buffer: Float32Array = new Float32Array(0);\n private _isRecording = false;\n private _loggedFirstChunk = false;\n /** Actual AudioContext sample rate (may differ from target on Firefox) */\n private _nativeSampleRate = 0;\n\n constructor(\n private events: EventEmitter<OmoteEvents>,\n config: MicrophoneCaptureConfig = {}\n ) {\n this.config = {\n sampleRate: config.sampleRate ?? 16000,\n chunkSize: config.chunkSize ?? 1600,\n };\n }\n\n get isRecording(): boolean {\n return this._isRecording;\n }\n\n get isSupported(): boolean {\n return typeof navigator !== 'undefined' && !!navigator.mediaDevices?.getUserMedia;\n }\n\n async start(): Promise<void> {\n if (!this.isSupported) {\n this.events.emit('error', {\n code: 'MICROPHONE_NOT_SUPPORTED',\n message: 'Microphone not supported in this browser',\n });\n return;\n }\n\n if (this._isRecording) return;\n\n try {\n this.stream = await navigator.mediaDevices.getUserMedia({\n audio: {\n sampleRate: { ideal: this.config.sampleRate },\n channelCount: 1,\n echoCancellation: true,\n noiseSuppression: true,\n autoGainControl: true,\n },\n });\n\n // Create AudioContext and connect mic stream.\n // Firefox throws NotSupportedError when connecting a MediaStreamSource\n // to an AudioContext with a different sample rate than the stream's native\n // rate. Fall back to native rate + JS resampling when this happens.\n this.context = new AudioContext({ sampleRate: this.config.sampleRate });\n\n // Resume AudioContext if suspended (browser autoplay policy)\n if (this.context.state === 'suspended') {\n await this.context.resume();\n }\n\n let source: MediaStreamAudioSourceNode;\n try {\n source = this.context.createMediaStreamSource(this.stream);\n this._nativeSampleRate = this.context.sampleRate;\n } catch (sourceErr) {\n // Firefox: \"Connecting AudioNodes from AudioContexts with different\n // sample-rate is currently not supported\"\n console.warn(\n '[MicrophoneCapture] Cannot connect stream at',\n this.config.sampleRate + 'Hz, falling back to native rate:',\n (sourceErr as Error).message\n );\n await this.context.close();\n this.context = new AudioContext(); // native rate (typically 48kHz)\n if (this.context.state === 'suspended') {\n await this.context.resume();\n }\n source = this.context.createMediaStreamSource(this.stream);\n this._nativeSampleRate = this.context.sampleRate;\n console.log('[MicrophoneCapture] Using native rate:', this._nativeSampleRate, 'Hz → resampling to', this.config.sampleRate, 'Hz');\n }\n\n // Use ScriptProcessor for broad compatibility\n this.processor = this.context.createScriptProcessor(4096, 1, 1);\n\n this.processor.onaudioprocess = (e) => {\n const raw = e.inputBuffer.getChannelData(0);\n\n // Resample if AudioContext is running at a different rate than target\n const input = this._nativeSampleRate !== this.config.sampleRate\n ? this.resample(raw, this._nativeSampleRate, this.config.sampleRate)\n : raw;\n\n // Calculate audio level\n let rms = 0;\n let peak = 0;\n for (let i = 0; i < input.length; i++) {\n const abs = Math.abs(input[i]);\n rms += input[i] * input[i];\n if (abs > peak) peak = abs;\n }\n rms = Math.sqrt(rms / input.length);\n\n this.events.emit('audio.level', { rms, peak });\n\n // Accumulate samples\n const newBuffer = new Float32Array(this.buffer.length + input.length);\n newBuffer.set(this.buffer);\n newBuffer.set(input, this.buffer.length);\n this.buffer = newBuffer;\n\n // Emit chunks\n let chunkCount = 0;\n while (this.buffer.length >= this.config.chunkSize) {\n const chunk = this.buffer.slice(0, this.config.chunkSize);\n this.buffer = this.buffer.slice(this.config.chunkSize);\n\n const pcm = this.floatToPCM16(chunk);\n this.events.emit('audio.chunk', {\n pcm,\n timestamp: performance.now(),\n });\n chunkCount++;\n }\n // Log first emission for debugging\n if (chunkCount > 0 && !this._loggedFirstChunk) {\n console.log('[MicrophoneCapture] Emitting audio chunks:', chunkCount);\n this._loggedFirstChunk = true;\n }\n };\n\n source.connect(this.processor);\n this.processor.connect(this.context.destination);\n\n this._isRecording = true;\n console.log('[MicrophoneCapture] Started recording, context state:', this.context.state);\n } catch (err) {\n this.events.emit('error', {\n code: 'MICROPHONE_ERROR',\n message: (err as Error).message,\n details: err,\n });\n }\n }\n\n stop(): void {\n if (this.processor) {\n this.processor.disconnect();\n this.processor = null;\n }\n\n if (this.context) {\n this.context.close();\n this.context = null;\n }\n\n if (this.stream) {\n this.stream.getTracks().forEach((t) => t.stop());\n this.stream = null;\n }\n\n this.buffer = new Float32Array(0);\n this._isRecording = false;\n }\n\n /**\n * Resample audio using linear interpolation.\n * Used when the AudioContext runs at the device's native rate (e.g. 48kHz)\n * and we need to downsample to the target rate (e.g. 16kHz).\n */\n private resample(input: Float32Array, fromRate: number, toRate: number): Float32Array {\n if (fromRate === toRate) return input;\n const ratio = fromRate / toRate;\n const outputLength = Math.floor(input.length / ratio);\n const output = new Float32Array(outputLength);\n for (let i = 0; i < outputLength; i++) {\n const srcIdx = i * ratio;\n const lo = Math.floor(srcIdx);\n const hi = Math.min(lo + 1, input.length - 1);\n const frac = srcIdx - lo;\n output[i] = input[lo] * (1 - frac) + input[hi] * frac;\n }\n return output;\n }\n\n private floatToPCM16(float32: Float32Array): Int16Array {\n const pcm = new Int16Array(float32.length);\n for (let i = 0; i < float32.length; i++) {\n const s = Math.max(-1, Math.min(1, float32[i]));\n pcm[i] = s < 0 ? s * 0x8000 : s * 0x7fff;\n }\n return pcm;\n }\n}\n","/**\n * Ring buffer for audio sample accumulation\n *\n * Efficiently accumulates audio samples and provides\n * contiguous buffers for inference without memory allocation churn.\n *\n * @category Audio\n */\n\nexport class RingBuffer {\n private buffer: Float32Array;\n private writeIndex = 0;\n private isFull = false;\n\n constructor(private readonly size: number) {\n this.buffer = new Float32Array(size);\n }\n\n /**\n * Write samples to the ring buffer\n * Converts Int16Array PCM to Float32\n */\n write(pcm: Int16Array): void {\n for (let i = 0; i < pcm.length; i++) {\n this.buffer[this.writeIndex] = pcm[i] / 32768.0;\n this.writeIndex = (this.writeIndex + 1) % this.size;\n\n if (this.writeIndex === 0) {\n this.isFull = true;\n }\n }\n }\n\n /**\n * Write float samples directly\n */\n writeFloat(samples: Float32Array): void {\n for (let i = 0; i < samples.length; i++) {\n this.buffer[this.writeIndex] = samples[i];\n this.writeIndex = (this.writeIndex + 1) % this.size;\n\n if (this.writeIndex === 0) {\n this.isFull = true;\n }\n }\n }\n\n /**\n * Get a contiguous copy of the buffer contents in chronological order\n * Returns null if buffer isn't full yet\n */\n read(): Float32Array | null {\n if (!this.isFull) return null;\n\n const output = new Float32Array(this.size);\n\n // Copy from writeIndex to end (oldest samples)\n const firstPart = this.buffer.subarray(this.writeIndex);\n output.set(firstPart, 0);\n\n // Copy from 0 to writeIndex (newest samples)\n const secondPart = this.buffer.subarray(0, this.writeIndex);\n output.set(secondPart, firstPart.length);\n\n return output;\n }\n\n /**\n * Check if buffer has enough samples\n */\n get hasData(): boolean {\n return this.isFull;\n }\n\n /**\n * Get current fill level (0-1)\n */\n get fillLevel(): number {\n if (this.isFull) return 1;\n return this.writeIndex / this.size;\n }\n\n /**\n * Reset the buffer\n */\n reset(): void {\n this.buffer.fill(0);\n this.writeIndex = 0;\n this.isFull = false;\n }\n}\n","/**\r\n * AudioScheduler - Enterprise-grade Web Audio API scheduling\r\n *\r\n * Implements the lookahead scheduling pattern from Chris Wilson's\r\n * \"A Tale of Two Clocks\" - the authoritative guide on Web Audio timing.\r\n *\r\n * Key Features:\r\n * - Uses AudioContext.currentTime (hardware clock) for sample-accurate timing\r\n * - Pre-schedules audio chunks for gapless playback\r\n * - Tracks scheduled sources for cleanup\r\n * - Provides playback state monitoring\r\n *\r\n * @see https://web.dev/articles/audio-scheduling\r\n * @category Audio\r\n */\r\n\r\nexport interface AudioSchedulerOptions {\r\n /** Sample rate in Hz (default: 16000 for speech) */\r\n sampleRate?: number\r\n /** Number of audio channels (default: 1 for mono) */\r\n channels?: number\r\n /**\r\n * Initial lookahead delay in seconds before first audio plays.\r\n * Gives LAM inference time to compute blendshapes before audio starts.\r\n * Default: 0.05 (50ms) for WebGPU, increase to 0.3-0.5 for WASM on iOS.\r\n */\r\n initialLookaheadSec?: number\r\n}\r\n\r\nexport class AudioScheduler {\r\n private context: AudioContext | null = null\r\n private nextPlayTime = 0\r\n private scheduledSources: Array<{ source: AudioBufferSourceNode; gainNode: GainNode }> = []\r\n private isPlaying = false\r\n\r\n constructor(private readonly options: AudioSchedulerOptions = {}) {}\r\n\r\n /**\r\n * Initialize AudioContext with specified sample rate\r\n *\r\n * Note: This is now a no-op. AudioContext is created lazily on first schedule()\r\n * to avoid browser autoplay policy issues (requires user gesture).\r\n */\r\n async initialize(): Promise<void> {\r\n // No-op - context will be created lazily in ensureContext()\r\n console.log('[AudioScheduler] Ready for lazy initialization')\r\n }\r\n\r\n /**\r\n * Eagerly create and warm up the AudioContext\r\n *\r\n * Call this when a playback session starts (e.g., when AI response begins).\r\n * The AudioContext needs time to initialize the audio hardware — on Windows\r\n * this can take 50-100ms. By warming up early (before audio data arrives),\r\n * the context is fully ready when schedule() is first called.\r\n *\r\n * Must be called after a user gesture (click/tap) for autoplay policy.\r\n */\r\n async warmup(): Promise<void> {\r\n await this.ensureContext()\r\n }\r\n\r\n /**\r\n * Ensure AudioContext is created and ready\r\n * Called lazily on first schedule() - requires user gesture\r\n */\r\n private async ensureContext(): Promise<AudioContext> {\r\n if (this.context && this.context.state !== 'closed') {\r\n return this.context\r\n }\r\n\r\n const sampleRate = this.options.sampleRate ?? 16000\r\n this.context = new AudioContext({ sampleRate })\r\n\r\n // Resume if suspended (browser autoplay policy)\r\n if (this.context.state === 'suspended') {\r\n await this.context.resume()\r\n }\r\n\r\n console.log(`[AudioScheduler] AudioContext initialized at ${sampleRate}Hz`)\r\n return this.context\r\n }\r\n\r\n /**\r\n * Schedule an audio chunk for playback\r\n *\r\n * Uses Web Audio's hardware-accurate clock for sample-perfect timing.\r\n * Chunks are scheduled immediately, not when they should play - this\r\n * ensures gapless playback even if main thread stalls.\r\n *\r\n * @param audioData - Float32Array of audio samples\r\n * @returns Scheduled playback time in AudioContext seconds\r\n */\r\n async schedule(audioData: Float32Array): Promise<number> {\r\n // Lazy initialization (requires user gesture)\r\n const ctx = await this.ensureContext()\r\n const channels = this.options.channels ?? 1\r\n\r\n // Initialize playback timing on first chunk\r\n // Lookahead gives AudioContext time to initialize and LAM time to pre-compute.\r\n // 50ms default is fine for WebGPU; WASM on iOS needs 300-500ms.\r\n if (!this.isPlaying) {\r\n const lookahead = this.options.initialLookaheadSec ?? 0.05\r\n this.nextPlayTime = ctx.currentTime + lookahead\r\n this.isPlaying = true\r\n }\r\n\r\n // Create audio buffer\r\n const audioBuffer = ctx.createBuffer(channels, audioData.length, ctx.sampleRate)\r\n audioBuffer.getChannelData(0).set(audioData)\r\n\r\n // Create gain node for fade control\r\n const gainNode = ctx.createGain()\r\n gainNode.gain.value = 1.0\r\n gainNode.connect(ctx.destination)\r\n\r\n // Create and schedule source\r\n const source = ctx.createBufferSource()\r\n source.buffer = audioBuffer\r\n source.connect(gainNode) // Route through gain node for fade control\r\n\r\n // Schedule at precise time for gapless playback\r\n const scheduleTime = this.nextPlayTime\r\n source.start(scheduleTime)\r\n\r\n // Track scheduled source with its gain node\r\n this.scheduledSources.push({ source, gainNode })\r\n\r\n // Update next play time\r\n const duration = audioData.length / ctx.sampleRate\r\n this.nextPlayTime = scheduleTime + duration\r\n\r\n return scheduleTime\r\n }\r\n\r\n /**\r\n * Get current audio clock time\r\n *\r\n * This is the hardware-accurate time, NOT JavaScript time.\r\n * Use this for synchronizing visual animations to audio.\r\n *\r\n * @returns Current time in AudioContext seconds\r\n */\r\n getCurrentTime(): number {\r\n if (!this.context) return 0\r\n return this.context.currentTime\r\n }\r\n\r\n /**\r\n * Get scheduled playback end time\r\n */\r\n getPlaybackEndTime(): number {\r\n return this.nextPlayTime\r\n }\r\n\r\n /**\r\n * Check if all scheduled audio has finished playing\r\n */\r\n isComplete(): boolean {\r\n if (!this.context || !this.isPlaying) return false\r\n return this.context.currentTime >= this.nextPlayTime\r\n }\r\n\r\n /**\r\n * Cancel all scheduled audio with smooth fade-out\r\n *\r\n * Applies a linear fade-out to all playing sources and stops them gracefully.\r\n * Prevents audio clicks/pops by ramping gain to zero before stopping.\r\n *\r\n * @param fadeOutMs - Fade-out duration in milliseconds (default: 50ms)\r\n * @returns Promise that resolves when fade-out completes\r\n */\r\n async cancelAll(fadeOutMs: number = 50): Promise<void> {\r\n if (!this.context || this.scheduledSources.length === 0) {\r\n return\r\n }\r\n\r\n const ctx = this.context\r\n const currentTime = ctx.currentTime\r\n const fadeOutSec = fadeOutMs / 1000\r\n\r\n // Apply fade-out to all scheduled sources\r\n for (const { source, gainNode } of this.scheduledSources) {\r\n try {\r\n // Ramp gain from current value to zero\r\n gainNode.gain.setValueAtTime(gainNode.gain.value, currentTime)\r\n gainNode.gain.linearRampToValueAtTime(0.0, currentTime + fadeOutSec)\r\n\r\n // Stop source after fade completes\r\n source.stop(currentTime + fadeOutSec)\r\n } catch (err) {\r\n // Source may have already stopped naturally - ignore error\r\n }\r\n }\r\n\r\n // Clear tracking arrays\r\n this.scheduledSources = []\r\n this.isPlaying = false\r\n this.nextPlayTime = 0\r\n\r\n // Wait for fade-out to complete\r\n await new Promise(resolve => setTimeout(resolve, fadeOutMs))\r\n }\r\n\r\n /**\r\n * Reset scheduler state for new playback session\r\n * Stops any orphaned sources that weren't cleaned up by cancelAll()\r\n */\r\n reset(): void {\r\n // Stop any still-playing sources before clearing\r\n if (this.context) {\r\n const now = this.context.currentTime\r\n for (const { source, gainNode } of this.scheduledSources) {\r\n try {\r\n gainNode.gain.setValueAtTime(0, now)\r\n source.stop(now)\r\n } catch {\r\n // Already stopped\r\n }\r\n }\r\n }\r\n this.nextPlayTime = 0\r\n this.isPlaying = false\r\n this.scheduledSources = []\r\n }\r\n\r\n /**\r\n * Cleanup resources\r\n */\r\n dispose(): void {\r\n if (this.context) {\r\n this.context.close()\r\n this.context = null\r\n }\r\n this.scheduledSources = []\r\n this.isPlaying = false\r\n }\r\n}\r\n","/**\n * AudioChunkCoalescer - Combine small network chunks into optimal buffers\n *\n * Network streaming often delivers audio in small chunks (e.g., 32ms from TTS APIs).\n * Creating an AudioBufferSourceNode for each tiny chunk is inefficient and can cause\n * overhead from object creation/GC.\n *\n * This class implements a double-buffering pattern: accumulate small chunks in a\n * temporary buffer, then flush to playback queue when threshold is reached.\n *\n * Benefits:\n * - Reduces AudioBufferSourceNode overhead (fewer nodes = less GC pressure)\n * - Configurable buffer size for optimal playback chunk duration\n * - Maintains sample-accurate timing despite buffering\n *\n * Based on patterns from HLS.js and production streaming implementations.\n *\n * @category Audio\n */\n\nexport interface AudioChunkCoalescerOptions {\n /**\n * Target duration in milliseconds for combined chunks\n * Default: 200ms (balances latency vs overhead)\n *\n * Smaller values = lower latency, more overhead\n * Larger values = higher latency, less overhead\n */\n targetDurationMs?: number\n\n /**\n * Sample rate in Hz\n * Default: 16000 (speech quality)\n */\n sampleRate?: number\n}\n\nexport class AudioChunkCoalescer {\n private tempBuffer: Uint8Array[] = []\n private readonly targetBytes: number\n\n constructor(private readonly options: AudioChunkCoalescerOptions = {}) {\n const targetMs = options.targetDurationMs ?? 200\n const sampleRate = options.sampleRate ?? 16000\n\n // Calculate target bytes: (duration_s) * (samples/s) * (2 bytes per Int16 sample)\n this.targetBytes = (targetMs / 1000) * sampleRate * 2\n }\n\n /**\n * Add a chunk to the temporary buffer\n *\n * @param chunk - Uint8Array containing Int16 PCM audio\n * @returns Combined buffer if threshold reached, null otherwise\n */\n add(chunk: Uint8Array): ArrayBuffer | null {\n // Add to temporary buffer\n this.tempBuffer.push(chunk)\n\n // Calculate total bytes buffered\n const totalBytes = this.tempBuffer.reduce((sum, c) => sum + c.length, 0)\n\n // If we've reached the threshold, combine and return\n if (totalBytes >= this.targetBytes) {\n return this.flush()\n }\n\n return null\n }\n\n /**\n * Flush remaining buffered data\n *\n * Call this when the stream ends to ensure all audio is processed,\n * even if it doesn't reach the target threshold.\n *\n * @returns Combined buffer, or null if buffer is empty\n */\n flush(): ArrayBuffer | null {\n if (this.tempBuffer.length === 0) {\n return null\n }\n\n // Calculate total size\n const totalBytes = this.tempBuffer.reduce((sum, c) => sum + c.length, 0)\n\n // Combine all chunks into single buffer\n const combined = new Uint8Array(totalBytes)\n let offset = 0\n for (const chunk of this.tempBuffer) {\n combined.set(chunk, offset)\n offset += chunk.length\n }\n\n // Clear temp buffer\n this.tempBuffer = []\n\n return combined.buffer\n }\n\n /**\n * Get current buffer fill level (0-1)\n */\n get fillLevel(): number {\n const totalBytes = this.tempBuffer.reduce((sum, c) => sum + c.length, 0)\n return Math.min(1, totalBytes / this.targetBytes)\n }\n\n /**\n * Get current buffered duration in milliseconds\n */\n getBufferedDurationMs(): number {\n const sampleRate = this.options.sampleRate ?? 16000\n const totalBytes = this.tempBuffer.reduce((sum, c) => sum + c.length, 0)\n const samples = totalBytes / 2 // Int16 = 2 bytes per sample\n return (samples / sampleRate) * 1000\n }\n\n /**\n * Get number of chunks currently buffered\n */\n get chunkCount(): number {\n return this.tempBuffer.length\n }\n\n /**\n * Reset the coalescer\n */\n reset(): void {\n this.tempBuffer = []\n }\n}\n","/**\r\n * LAMPipeline - Coordinate LAM (Wav2Vec2) inference with frame synchronization\r\n *\r\n * Manages the buffering and processing pipeline for LAM lip sync:\r\n * 1. Accumulates audio samples in a ring buffer\r\n * 2. Triggers LAM inference when buffer reaches required size (16000 samples @ 16kHz = 1.0s)\r\n * 3. Queues resulting blendshape frames with precise timestamps\r\n * 4. Provides frames synchronized to AudioContext clock\r\n *\r\n * Key Design Decisions:\r\n * - Ring buffer pattern for efficient sample accumulation (no allocation churn)\r\n * - Frame queue with timestamps for deterministic playback\r\n * - Timestamp-based frame retrieval (not callback) for renderer flexibility\r\n *\r\n * Based on patterns from Chrome Audio Worklet design and Web Audio clock management.\r\n *\r\n * @see https://developer.chrome.com/blog/audio-worklet-design-pattern\r\n * @category Audio\r\n */\r\n\r\nimport { RingBuffer } from './RingBuffer'\r\nimport type { LipSyncBackend } from '../inference/LipSyncBackend'\r\n\r\nexport interface LAMFrame {\r\n /** 52 ARKit blendshape weights */\r\n frame: Float32Array\r\n /** AudioContext time when this frame should be displayed */\r\n timestamp: number\r\n}\r\n\r\nexport interface LAMPipelineOptions {\r\n /**\r\n * Sample rate in Hz (must match audio playback)\r\n * Default: 16000\r\n */\r\n sampleRate?: number\r\n\r\n /**\r\n * LAM inference callback\r\n * Called each time LAM processes a buffer\r\n */\r\n onInference?: (frameCount: number) => void\r\n\r\n /**\r\n * Error callback for inference failures\r\n */\r\n onError?: (error: Error) => void\r\n}\r\n\r\nexport class LAMPipeline {\r\n private readonly REQUIRED_SAMPLES = 16000 // 1.0s at 16kHz (LAM requirement)\r\n private readonly FRAME_RATE = 30 // LAM outputs 30fps\r\n\r\n private buffer: Float32Array = new Float32Array(0)\r\n private bufferStartTime = 0\r\n private frameQueue: LAMFrame[] = []\r\n\r\n /**\r\n * Last successfully retrieved frame\r\n * Used as fallback when no new frame is available to prevent avatar freezing\r\n */\r\n private lastFrame: Float32Array | null = null\r\n\r\n constructor(private readonly options: LAMPipelineOptions = {}) {}\r\n\r\n /**\r\n * Push audio samples into the pipeline\r\n *\r\n * Accumulates samples and triggers LAM inference when buffer is full.\r\n * Multiple calls may be needed to accumulate enough samples.\r\n *\r\n * @param samples - Float32Array of audio samples\r\n * @param timestamp - AudioContext time when these samples start playing\r\n * @param lam - LAM inference engine\r\n */\r\n async push(samples: Float32Array, timestamp: number, lam: LipSyncBackend): Promise<void> {\r\n // Track buffer start time when empty\r\n if (this.buffer.length === 0) {\r\n this.bufferStartTime = timestamp\r\n }\r\n\r\n // Accumulate samples\r\n const newBuffer = new Float32Array(this.buffer.length + samples.length)\r\n newBuffer.set(this.buffer, 0)\r\n newBuffer.set(samples, this.buffer.length)\r\n this.buffer = newBuffer\r\n\r\n // Process ALL complete chunks (not just one)\r\n // Critical for AgentCore which delivers entire sentences at once (30-50K+ samples)\r\n // Without the while loop, samples pile up and LAM falls behind audio playback\r\n while (this.buffer.length >= this.REQUIRED_SAMPLES) {\r\n await this.processBuffer(lam)\r\n\r\n // Yield to main thread between inference batches so RAF can run.\r\n // On iOS WASM, each inference blocks 100-500ms — without yielding,\r\n // the renderer freezes for the entire while-loop duration.\r\n if (this.buffer.length >= this.REQUIRED_SAMPLES) {\r\n await new Promise<void>(r => setTimeout(r, 0))\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Process accumulated buffer through LAM inference\r\n */\r\n private async processBuffer(lam: LipSyncBackend): Promise<void> {\r\n try {\r\n // Extract exactly REQUIRED_SAMPLES for inference\r\n const toProcess = this.buffer.slice(0, this.REQUIRED_SAMPLES)\r\n const processedStartTime = this.bufferStartTime\r\n\r\n // Keep remaining samples for next inference\r\n this.buffer = this.buffer.slice(this.REQUIRED_SAMPLES)\r\n\r\n // Update start time for remaining buffer\r\n const processedDuration = this.REQUIRED_SAMPLES / (this.options.sampleRate ?? 16000)\r\n this.bufferStartTime = processedStartTime + processedDuration\r\n\r\n // Run LAM inference\r\n const result = await lam.infer(toProcess)\r\n\r\n // Queue frames with timestamps\r\n const frameDuration = 1 / this.FRAME_RATE\r\n for (let i = 0; i < result.blendshapes.length; i++) {\r\n const frame = result.blendshapes[i]\r\n const timestamp = processedStartTime + (i * frameDuration)\r\n this.frameQueue.push({ frame, timestamp })\r\n }\r\n\r\n // Notify callback\r\n this.options.onInference?.(result.blendshapes.length)\r\n } catch (error) {\r\n this.options.onError?.(error as Error)\r\n\r\n // Clear buffer on error to prevent repeated failures\r\n this.buffer = new Float32Array(0)\r\n this.bufferStartTime = 0\r\n }\r\n }\r\n\r\n /**\r\n * Get the frame that should be displayed at the current time\r\n *\r\n * Automatically removes frames that have already been displayed.\r\n * This prevents memory leaks from accumulating old frames.\r\n *\r\n * Discard Window (prevents premature frame discarding):\r\n * - WebGPU: 0.5s (LAM inference 20-100ms + RAF jitter + React stalls)\r\n * - WASM: 1.0s (LAM inference 50-500ms + higher variability)\r\n *\r\n * Last-Frame-Hold: Returns last valid frame instead of null to prevent\r\n * avatar freezing when between frames (RAF at 60fps vs LAM at 30fps).\r\n *\r\n * @param currentTime - Current AudioContext time\r\n * @param lam - LAM inference engine (optional, for backend detection)\r\n * @returns Current frame, or last frame as fallback, or null if no frames yet\r\n */\r\n getFrameForTime(currentTime: number, lam?: { backend: 'webgpu' | 'wasm' | null }): Float32Array | null {\r\n // Dynamic discard window based on backend performance characteristics\r\n const discardWindow = lam?.backend === 'wasm' ? 1.0 : 0.5\r\n\r\n // Remove frames that are too old (already displayed)\r\n let discardedCount = 0\r\n while (this.frameQueue.length > 0 && this.frameQueue[0].timestamp < currentTime - discardWindow) {\r\n const discarded = this.frameQueue.shift()!\r\n discardedCount++\r\n\r\n // Log frame discards for debugging sync issues\r\n if (discardedCount === 1) {\r\n const ageMs = ((currentTime - discarded.timestamp) * 1000).toFixed(0)\r\n console.warn('[LAM] Frame(s) discarded as too old', {\r\n ageMs,\r\n discardWindowMs: discardWindow * 1000,\r\n queueLength: this.frameQueue.length,\r\n backend: lam?.backend ?? 'unknown'\r\n })\r\n }\r\n }\r\n\r\n // Return the frame that should be playing now\r\n if (this.frameQueue.length > 0 && this.frameQueue[0].timestamp <= currentTime) {\r\n const { frame } = this.frameQueue.shift()!\r\n this.lastFrame = frame // Cache for fallback\r\n return frame\r\n }\r\n\r\n // Last-frame-hold: Return cached frame instead of null to prevent freezing\r\n // This handles RAF running at 60fps while LAM produces 30fps\r\n return this.lastFrame\r\n }\r\n\r\n /**\r\n * Get all frames in the queue (for debugging/monitoring)\r\n */\r\n getQueuedFrames(): LAMFrame[] {\r\n return [...this.frameQueue]\r\n }\r\n\r\n /**\r\n * Get current buffer fill level (0-1)\r\n */\r\n get fillLevel(): number {\r\n return Math.min(1, this.buffer.length / this.REQUIRED_SAMPLES)\r\n }\r\n\r\n /**\r\n * Get number of frames queued\r\n */\r\n get queuedFrameCount(): number {\r\n return this.frameQueue.length\r\n }\r\n\r\n /**\r\n * Get buffered audio duration in seconds\r\n */\r\n get bufferedDuration(): number {\r\n return this.buffer.length / (this.options.sampleRate ?? 16000)\r\n }\r\n\r\n /**\r\n * Flush remaining buffered audio\r\n *\r\n * Processes any remaining audio in the buffer, even if less than REQUIRED_SAMPLES.\r\n * This ensures the final audio chunk generates blendshape frames.\r\n *\r\n * Should be called when audio stream ends to prevent losing the last 0-1 seconds.\r\n *\r\n * @param lam - LAM inference engine\r\n */\r\n async flush(lam: LipSyncBackend): Promise<void> {\r\n if (this.buffer.length === 0) {\r\n return // Nothing to flush\r\n }\r\n\r\n // Pad buffer to REQUIRED_SAMPLES (LAM expects exactly 16000 samples)\r\n const padded = new Float32Array(this.REQUIRED_SAMPLES)\r\n padded.set(this.buffer, 0)\r\n // Remaining samples are already zero (Float32Array default)\r\n\r\n // Process the padded buffer\r\n const processedStartTime = this.bufferStartTime\r\n\r\n try {\r\n // Run LAM inference\r\n const result = await lam.infer(padded)\r\n\r\n // Queue frames with timestamps\r\n // Only queue frames that correspond to actual audio (not padding)\r\n const actualDuration = this.buffer.length / (this.options.sampleRate ?? 16000)\r\n const frameDuration = 1 / this.FRAME_RATE\r\n const actualFrameCount = Math.ceil(actualDuration * this.FRAME_RATE)\r\n\r\n for (let i = 0; i < Math.min(actualFrameCount, result.blendshapes.length); i++) {\r\n const frame = result.blendshapes[i]\r\n const timestamp = processedStartTime + (i * frameDuration)\r\n this.frameQueue.push({ frame, timestamp })\r\n }\r\n\r\n // Clear buffer after flushing\r\n this.buffer = new Float32Array(0)\r\n this.bufferStartTime = 0\r\n\r\n // Notify callback\r\n this.options.onInference?.(Math.min(actualFrameCount, result.blendshapes.length))\r\n } catch (error) {\r\n this.options.onError?.(error as Error)\r\n\r\n // Clear buffer on error\r\n this.buffer = new Float32Array(0)\r\n this.bufferStartTime = 0\r\n }\r\n }\r\n\r\n /**\r\n * Adjust all queued frame timestamps by an offset\r\n *\r\n * Used for synchronization when audio scheduling time differs from\r\n * the estimated time used during LAM processing.\r\n *\r\n * @param offset - Time offset in seconds to add to all timestamps\r\n */\r\n adjustTimestamps(offset: number): void {\r\n for (const frame of this.frameQueue) {\r\n frame.timestamp += offset\r\n }\r\n }\r\n\r\n /**\r\n * Reset the pipeline\r\n */\r\n reset(): void {\r\n this.buffer = new Float32Array(0)\r\n this.bufferStartTime = 0\r\n this.frameQueue = []\r\n this.lastFrame = null // Clear last-frame-hold cache\r\n }\r\n}\r\n","/**\r\n * SyncedAudioPipeline - Audio playback + LAM lip sync coordinator\r\n *\r\n * Orchestrates the complete pipeline for synchronized audio playback and lip sync:\r\n * 1. Network chunks → Coalescer → Optimized buffers\r\n * 2. Audio buffers → Scheduler → Gapless playback (immediate, never blocks)\r\n * 3. Audio buffers → LAM Pipeline → Blendshape frames (background, fire-and-forget)\r\n * 4. Frames synchronized to AudioContext clock → Renderer\r\n *\r\n * Key Architecture Pattern: Audio-First, LAM-Background\r\n * - Audio chunks are scheduled for playback immediately (never waits for LAM)\r\n * - LAM inference runs in background without blocking the audio path\r\n * - Lip sync starts ~1 second after audio (LAM needs 16000 samples to infer)\r\n * - Once LAM catches up, frames stay synchronized to AudioContext clock\r\n *\r\n * This decoupled design prevents LAM inference (50-300ms) from blocking audio\r\n * scheduling, which caused audible stuttering when audio arrived as a continuous\r\n * stream (e.g., single-call TTS from ElevenLabs via AgentCore).\r\n *\r\n * @see https://web.dev/articles/audio-scheduling (Web Audio clock patterns)\r\n * @category Audio\r\n */\r\n\r\nimport { AudioScheduler } from './AudioScheduler'\r\nimport { AudioChunkCoalescer } from './AudioChunkCoalescer'\r\nimport { LAMPipeline } from './LAMPipeline'\r\nimport { EventEmitter } from '../events/EventEmitter'\r\nimport type { LipSyncBackend } from '../inference/LipSyncBackend'\r\n\r\n/**\r\n * Safely convert an ArrayBuffer of PCM16 bytes to Float32 samples.\r\n * Handles odd-length buffers by truncating to the nearest even byte boundary.\r\n */\r\nfunction pcm16ToFloat32(buffer: ArrayBuffer): Float32Array {\r\n // Int16Array requires even byte length — truncate if odd\r\n const byteLen = buffer.byteLength & ~1\r\n const int16 = byteLen === buffer.byteLength\r\n ? new Int16Array(buffer)\r\n : new Int16Array(buffer, 0, byteLen / 2)\r\n const float32 = new Float32Array(int16.length)\r\n for (let i = 0; i < int16.length; i++) {\r\n float32[i] = int16[i] / 32768\r\n }\r\n return float32\r\n}\r\n\r\nexport interface SyncedAudioPipelineOptions {\r\n /** Sample rate in Hz (default: 16000) */\r\n sampleRate?: number\r\n /** Target chunk duration in ms for coalescing (default: 200) */\r\n chunkTargetMs?: number\r\n /** LAM inference engine */\r\n lam: LipSyncBackend\r\n /**\r\n * Audio playback delay in ms before first audio plays.\r\n * Gives LAM inference time to pre-compute blendshapes.\r\n * Default: auto-detected from lam.backend (50ms WebGPU, 350ms WASM).\r\n */\r\n audioDelayMs?: number\r\n}\r\n\r\nexport interface SyncedAudioPipelineEvents {\r\n /** New frame ready for display */\r\n frame_ready: Float32Array\r\n /** Playback has completed */\r\n playback_complete: void\r\n /** First audio chunk scheduled, playback starting */\r\n playback_start: number\r\n /** Error occurred */\r\n error: Error\r\n /** Index signature for EventEmitter compatibility */\r\n [key: string]: unknown\r\n}\r\n\r\nexport class SyncedAudioPipeline extends EventEmitter<SyncedAudioPipelineEvents> {\r\n private scheduler: AudioScheduler\r\n private coalescer: AudioChunkCoalescer\r\n private lamPipeline: LAMPipeline\r\n\r\n private playbackStarted = false\r\n private monitorInterval: number | null = null\r\n private frameAnimationId: number | null = null\r\n\r\n constructor(private readonly options: SyncedAudioPipelineOptions) {\r\n super()\r\n\r\n const sampleRate = options.sampleRate ?? 16000\r\n\r\n // Auto-detect audio delay from model + backend:\r\n // - wav2arkit_cpu on iOS: 750ms (single-threaded WASM, 100-500ms inference per chunk)\r\n // - Wav2Vec2 WASM fallback: 350ms (multi-threaded, faster inference)\r\n // - Wav2Vec2 WebGPU: 50ms (GPU-accelerated, near-instant)\r\n const autoDelay = options.lam.modelId === 'wav2arkit_cpu' ? 750\r\n : options.lam.backend === 'wasm' ? 350\r\n : 50\r\n const audioDelayMs = options.audioDelayMs ?? autoDelay\r\n\r\n this.scheduler = new AudioScheduler({\r\n sampleRate,\r\n initialLookaheadSec: audioDelayMs / 1000,\r\n })\r\n this.coalescer = new AudioChunkCoalescer({\r\n sampleRate,\r\n targetDurationMs: options.chunkTargetMs ?? 200,\r\n })\r\n this.lamPipeline = new LAMPipeline({\r\n sampleRate,\r\n onError: (error) => {\r\n this.emit('error', error)\r\n },\r\n })\r\n }\r\n\r\n /**\r\n * Initialize the pipeline\r\n */\r\n async initialize(): Promise<void> {\r\n await this.scheduler.initialize()\r\n }\r\n\r\n /**\r\n * Start a new playback session\r\n *\r\n * Resets all state and prepares for incoming audio chunks.\r\n * Audio will be scheduled immediately as chunks arrive (no buffering).\r\n */\r\n start(): void {\r\n // Stop any active session first (prevents duplicate frame loops/monitors)\r\n this.stopMonitoring()\r\n\r\n this.scheduler.reset()\r\n this.coalescer.reset()\r\n this.lamPipeline.reset()\r\n this.playbackStarted = false\r\n\r\n // Eagerly warm up AudioContext so audio hardware is ready when\r\n // first audio chunk arrives. Without this, AudioContext creation\r\n // happens at schedule time and the first 50-100ms of audio stutters\r\n // while Windows WASAPI initializes.\r\n this.scheduler.warmup()\r\n\r\n // Start frame animation loop\r\n this.startFrameLoop()\r\n\r\n // Start playback monitoring\r\n this.startMonitoring()\r\n }\r\n\r\n /**\r\n * Receive audio chunk from network\r\n *\r\n * Audio-first design: schedules audio immediately, LAM runs in background.\r\n * This prevents LAM inference (50-300ms) from blocking audio scheduling,\r\n * which caused audible stuttering with continuous audio streams.\r\n *\r\n * @param chunk - Uint8Array containing Int16 PCM audio\r\n */\r\n async onAudioChunk(chunk: Uint8Array): Promise<void> {\r\n // Coalesce small chunks into optimal buffers\r\n const combined = this.coalescer.add(chunk)\r\n if (!combined) {\r\n return // Not enough data yet\r\n }\r\n\r\n // Convert PCM16 bytes to Float32 samples (handles odd-length buffers safely)\r\n const float32 = pcm16ToFloat32(combined)\r\n\r\n // Schedule audio immediately — never wait for LAM\r\n const scheduleTime = await this.scheduler.schedule(float32)\r\n\r\n // Emit playback_start on first scheduled chunk\r\n if (!this.playbackStarted) {\r\n this.playbackStarted = true\r\n this.emit('playback_start', scheduleTime)\r\n }\r\n\r\n // LAM runs in background — never blocks audio scheduling.\r\n // lam.infer() takes 50-300ms when it triggers (every 16000 samples).\r\n // If we awaited here, the NDJSON processing loop in useVoice.tsx would\r\n // stall, preventing new audio chunks from being scheduled. The already-\r\n // scheduled audio plays out and runs dry → gap → audible stutter.\r\n this.lamPipeline.push(float32, scheduleTime, this.options.lam).catch(err => {\r\n this.emit('error', err)\r\n })\r\n }\r\n\r\n /**\r\n * End of audio stream\r\n *\r\n * Flushes any remaining buffered data.\r\n */\r\n async end(): Promise<void> {\r\n // Flush remaining coalesced data\r\n const remaining = this.coalescer.flush()\r\n if (remaining) {\r\n const chunk = new Uint8Array(remaining)\r\n await this.onAudioChunk(chunk)\r\n }\r\n\r\n // Flush remaining LAM buffer to process final audio chunk\r\n // This ensures blendshapes are generated for the last 0-1 seconds of audio\r\n await this.lamPipeline.flush(this.options.lam)\r\n }\r\n\r\n /**\r\n * Stop playback immediately with smooth fade-out\r\n *\r\n * Gracefully cancels all audio playback and LAM processing:\r\n * - Fades out audio over specified duration (default: 50ms)\r\n * - Cancels pending LAM inferences\r\n * - Clears all buffers and queues\r\n * - Emits 'playback_complete' event\r\n *\r\n * Use this for interruptions (e.g., user barge-in during AI speech).\r\n *\r\n * @param fadeOutMs - Fade-out duration in milliseconds (default: 50ms)\r\n * @returns Promise that resolves when fade-out completes\r\n */\r\n async stop(fadeOutMs: number = 50): Promise<void> {\r\n // Stop monitoring and frame loop\r\n this.stopMonitoring()\r\n\r\n // Cancel audio playback with fade-out\r\n await this.scheduler.cancelAll(fadeOutMs)\r\n\r\n // Clear all buffers\r\n this.coalescer.reset()\r\n this.lamPipeline.reset()\r\n this.playbackStarted = false\r\n\r\n // Emit completion event\r\n this.emit('playback_complete', undefined as any)\r\n }\r\n\r\n /**\r\n * Start frame animation loop\r\n *\r\n * Uses requestAnimationFrame to check for new LAM frames.\r\n * Synchronized to AudioContext clock (not visual refresh rate).\r\n *\r\n * Frame Emission Strategy:\r\n * - LAMPipeline uses last-frame-hold to prevent null returns\r\n * - Always emit frames (even repeated frames) to maintain smooth animation\r\n * - Renderer is responsible for detecting duplicate frames if needed\r\n */\r\n private startFrameLoop(): void {\r\n const updateFrame = () => {\r\n const currentTime = this.scheduler.getCurrentTime()\r\n const frame = this.lamPipeline.getFrameForTime(currentTime, this.options.lam)\r\n\r\n if (frame) {\r\n this.emit('frame_ready', frame)\r\n }\r\n\r\n this.frameAnimationId = requestAnimationFrame(updateFrame)\r\n }\r\n\r\n this.frameAnimationId = requestAnimationFrame(updateFrame)\r\n }\r\n\r\n /**\r\n * Start monitoring for playback completion\r\n */\r\n private startMonitoring(): void {\r\n if (this.monitorInterval) {\r\n clearInterval(this.monitorInterval)\r\n }\r\n\r\n this.monitorInterval = window.setInterval(() => {\r\n if (this.scheduler.isComplete() && this.lamPipeline.queuedFrameCount === 0) {\r\n this.emit('playback_complete', undefined as any)\r\n this.stopMonitoring()\r\n }\r\n }, 100)\r\n }\r\n\r\n /**\r\n * Stop monitoring\r\n */\r\n private stopMonitoring(): void {\r\n if (this.monitorInterval) {\r\n clearInterval(this.monitorInterval)\r\n this.monitorInterval = null\r\n }\r\n\r\n if (this.frameAnimationId) {\r\n cancelAnimationFrame(this.frameAnimationId)\r\n this.frameAnimationId = null\r\n }\r\n }\r\n\r\n /**\r\n * Get current pipeline state (for debugging/monitoring)\r\n */\r\n getState() {\r\n return {\r\n playbackStarted: this.playbackStarted,\r\n coalescerFill: this.coalescer.fillLevel,\r\n lamFill: this.lamPipeline.fillLevel,\r\n queuedFrames: this.lamPipeline.queuedFrameCount,\r\n currentTime: this.scheduler.getCurrentTime(),\r\n playbackEndTime: this.scheduler.getPlaybackEndTime(),\r\n }\r\n }\r\n\r\n /**\r\n * Cleanup resources\r\n */\r\n dispose(): void {\r\n this.stopMonitoring()\r\n this.scheduler.dispose()\r\n this.coalescer.reset()\r\n this.lamPipeline.reset()\r\n }\r\n}\r\n","/**\n * Emotion to ARKit Blendshape Mapper\n *\n * Converts Emotion2VecInference output to upper face ARKit blendshapes for\n * expressive avatar animation. Maps 4 emotion categories (neutral, happy, angry, sad)\n * to 11 upper face blendshapes (brows, eyes, cheeks).\n *\n * Supports two blend modes:\n * - 'dominant': Uses only the strongest emotion (simpler, more stable)\n * - 'weighted': Blends all emotions by probability (more nuanced, e.g., bittersweet)\n *\n * Also supports energy modulation to scale emotion intensity by audio energy,\n * making expressions stronger during emphasized speech.\n *\n * @example Basic usage\n * ```typescript\n * import { EmotionToBlendshapeMapper } from '@omote/core';\n * import { Emotion2VecInference } from '@omote/core';\n *\n * const emotion = new Emotion2VecInference({ modelUrl: '/models/emotion.onnx' });\n * const mapper = new EmotionToBlendshapeMapper();\n *\n * // Process emotion frame\n * const result = await emotion.infer(audioSamples);\n * const blendshapes = mapper.mapFrame(result.dominant);\n *\n * // Apply to avatar\n * for (const [name, value] of Object.entries(blendshapes)) {\n * avatar.setBlendshape(name, value);\n * }\n * ```\n *\n * @example Weighted blending for nuanced expressions\n * ```typescript\n * const mapper = new EmotionToBlendshapeMapper({\n * blendMode: 'weighted',\n * minBlendProbability: 0.1,\n * });\n *\n * // Frame with mixed emotions: { happy: 0.6, sad: 0.3, neutral: 0.1 }\n * // Result: bittersweet expression (smiling but worried brow)\n * const blendshapes = mapper.mapFrame(emotionFrame);\n * ```\n *\n * @example Energy-modulated emotion\n * ```typescript\n * import { AudioEnergyAnalyzer } from '@omote/core';\n *\n * const energyAnalyzer = new AudioEnergyAnalyzer();\n * const mapper = new EmotionToBlendshapeMapper({ energyModulation: true });\n *\n * // In animation loop\n * function animate(audioChunk: Float32Array, emotionFrame: EmotionFrame) {\n * const { energy } = energyAnalyzer.analyze(audioChunk);\n * mapper.mapFrame(emotionFrame, energy); // Louder = stronger emotion\n * mapper.update(16);\n * applyToAvatar(mapper.getCurrentBlendshapes());\n * }\n * ```\n *\n * @module animation\n */\n\n// ─── Emotion types (formerly in Emotion2VecInference, inlined here) ──────────\n\nexport const EMOTION2VEC_LABELS = ['neutral', 'happy', 'angry', 'sad'] as const;\nexport type Emotion2VecLabel = (typeof EMOTION2VEC_LABELS)[number];\n\nexport interface EmotionFrame {\n /** Primary emotion label */\n emotion: Emotion2VecLabel;\n /** Confidence for primary emotion (0-1) */\n confidence: number;\n /** All emotion probabilities */\n probabilities: Record<Emotion2VecLabel, number>;\n}\n\n/**\n * Upper face ARKit blendshape names (11 total)\n *\n * These blendshapes control the upper face (brows, eyes, cheeks) and are\n * driven by emotion detection, complementing the mouth blendshapes from\n * LAM lip sync.\n */\nexport const UPPER_FACE_BLENDSHAPES = [\n // Brows (5)\n 'browDownLeft',\n 'browDownRight',\n 'browInnerUp',\n 'browOuterUpLeft',\n 'browOuterUpRight',\n // Eyes (4)\n 'eyeSquintLeft',\n 'eyeSquintRight',\n 'eyeWideLeft',\n 'eyeWideRight',\n // Cheeks (2)\n 'cheekSquintLeft',\n 'cheekSquintRight',\n] as const;\n\nexport type UpperFaceBlendshapeName = (typeof UPPER_FACE_BLENDSHAPES)[number];\n\n/**\n * Upper face blendshape values (0-1 for each)\n */\nexport type UpperFaceBlendshapes = Record<UpperFaceBlendshapeName, number>;\n\n/**\n * Blend mode for combining emotions\n * - 'dominant': Use only the strongest emotion (default, more stable)\n * - 'weighted': Blend all emotions by probability (more nuanced)\n */\nexport type EmotionBlendMode = 'dominant' | 'weighted';\n\n/**\n * Emotion to ARKit blendshape mapping\n *\n * Based on Paul Ekman's FACS (Facial Action Coding System) research:\n *\n * - Happy (AU6+AU12): Cheek raise + lip corner pull (Duchenne smile)\n * Upper face: cheekSquint (AU6) + slight eyeSquint from genuine smile\n *\n * - Angry (AU4+AU5+AU7+AU23): Brow lower + eye wide + lid tighten + lip press\n * Upper face: browDown (AU4) + eyeWide (AU5) + eyeSquint (AU7) creates the \"glare\"\n *\n * - Sad (AU1+AU4+AU15): Inner brow raise + brow furrow + lip corner depress\n * Upper face: browInnerUp (AU1) + browDown (AU4) creates the worried/sad brow\n *\n * - Neutral: All zeros (no expression overlay)\n *\n * @see https://imotions.com/blog/learning/research-fundamentals/facial-action-coding-system/\n * @see https://melindaozel.com/arkit-to-facs-cheat-sheet/\n */\nexport const EMOTION_ARKIT_MAP: Record<Emotion2VecLabel, Partial<UpperFaceBlendshapes>> = {\n happy: {\n // AU6 - Cheek raiser (primary Duchenne smile marker)\n cheekSquintLeft: 0.5,\n cheekSquintRight: 0.5,\n // Slight eye squint from genuine smile (orbicularis oculi activation)\n eyeSquintLeft: 0.2,\n eyeSquintRight: 0.2,\n },\n angry: {\n // AU4 - Brow lowerer (intense, primary anger marker)\n browDownLeft: 0.7,\n browDownRight: 0.7,\n // AU5 - Upper lid raiser (wide eyes, part of the \"glare\")\n eyeWideLeft: 0.4,\n eyeWideRight: 0.4,\n // AU7 - Lid tightener (tense stare, combines with AU5 for angry glare)\n eyeSquintLeft: 0.3,\n eyeSquintRight: 0.3,\n },\n sad: {\n // AU1 - Inner brow raiser (primary sadness marker)\n browInnerUp: 0.6,\n // AU4 - Brow lowerer (brows drawn together)\n browDownLeft: 0.3,\n browDownRight: 0.3,\n },\n neutral: {}, // All zeros - no expression overlay\n};\n\n/**\n * Configuration for EmotionToBlendshapeMapper\n */\nexport interface EmotionBlendshapeConfig {\n /**\n * Smoothing factor for exponential moving average (0-1)\n * Lower = slower, smoother transitions\n * Higher = faster, more responsive\n * @default 0.15\n */\n smoothingFactor?: number;\n\n /**\n * Minimum confidence threshold for emotion to take effect\n * Emotions below this confidence are treated as neutral\n * @default 0.3\n */\n confidenceThreshold?: number;\n\n /**\n * Global intensity multiplier for all blendshapes (0-2)\n * @default 1.0\n */\n intensity?: number;\n\n /**\n * Blend mode for combining emotions\n * - 'dominant': Use only the strongest emotion (default)\n * - 'weighted': Blend all emotions by probability\n * @default 'dominant'\n */\n blendMode?: EmotionBlendMode;\n\n /**\n * Minimum probability for an emotion to contribute in weighted blend mode\n * Emotions with probability below this are ignored\n * @default 0.1\n */\n minBlendProbability?: number;\n\n /**\n * Enable energy modulation - scale emotion intensity by audio energy\n * When enabled, louder speech produces stronger expressions\n * @default false\n */\n energyModulation?: boolean;\n\n /**\n * Minimum energy scale when energy modulation is enabled (0-1)\n * At zero audio energy, emotion intensity is scaled by this factor\n * @default 0.3\n */\n minEnergyScale?: number;\n\n /**\n * Maximum energy scale when energy modulation is enabled (0-2)\n * At maximum audio energy, emotion intensity is scaled by this factor\n * @default 1.0\n */\n maxEnergyScale?: number;\n}\n\nconst DEFAULT_CONFIG: Required<EmotionBlendshapeConfig> = {\n smoothingFactor: 0.15,\n confidenceThreshold: 0.3,\n intensity: 1.0,\n blendMode: 'dominant',\n minBlendProbability: 0.1,\n energyModulation: false,\n minEnergyScale: 0.3,\n maxEnergyScale: 1.0,\n};\n\n/**\n * Creates a zeroed UpperFaceBlendshapes object\n */\nfunction createZeroBlendshapes(): UpperFaceBlendshapes {\n const result = {} as UpperFaceBlendshapes;\n for (const name of UPPER_FACE_BLENDSHAPES) {\n result[name] = 0;\n }\n return result;\n}\n\n/**\n * Clamp value between 0 and 1\n */\nfunction clamp01(value: number): number {\n return Math.max(0, Math.min(1, value));\n}\n\n/**\n * EmotionToBlendshapeMapper\n *\n * Converts emotion detection output to upper face ARKit blendshapes.\n * Provides smooth transitions between emotion states using exponential\n * moving average interpolation.\n *\n * Supports two blend modes:\n * - 'dominant': Uses only the strongest emotion\n * - 'weighted': Blends all emotions by probability for nuanced expressions\n *\n * Also supports energy modulation to scale emotion intensity by audio energy.\n */\nexport class EmotionToBlendshapeMapper {\n private config: Required<EmotionBlendshapeConfig>;\n private targetBlendshapes: UpperFaceBlendshapes;\n private currentBlendshapes: UpperFaceBlendshapes;\n private currentEnergy: number = 1.0;\n\n /**\n * Create a new EmotionToBlendshapeMapper\n *\n * @param config - Optional configuration\n */\n constructor(config?: EmotionBlendshapeConfig) {\n this.config = {\n ...DEFAULT_CONFIG,\n ...config,\n };\n this.targetBlendshapes = createZeroBlendshapes();\n this.currentBlendshapes = createZeroBlendshapes();\n }\n\n /**\n * Map an emotion frame to target blendshapes\n *\n * This sets the target values that the mapper will smoothly interpolate\n * towards. Call update() each frame to apply smoothing.\n *\n * @param frame - Emotion frame from Emotion2VecInference\n * @param audioEnergy - Optional audio energy (0-1) for energy modulation\n * @returns Target upper face blendshapes (before smoothing)\n */\n mapFrame(frame: EmotionFrame, audioEnergy?: number): UpperFaceBlendshapes {\n // Reset target to zeros\n this.targetBlendshapes = createZeroBlendshapes();\n\n // Store energy for modulation\n if (audioEnergy !== undefined) {\n this.currentEnergy = clamp01(audioEnergy);\n }\n\n // Check for valid frame\n if (!frame) {\n return { ...this.targetBlendshapes };\n }\n\n // Route to appropriate blend method\n if (this.config.blendMode === 'weighted') {\n this.mapFrameWeighted(frame);\n } else {\n this.mapFrameDominant(frame);\n }\n\n // Apply energy modulation if enabled\n if (this.config.energyModulation) {\n this.applyEnergyModulation();\n }\n\n return { ...this.targetBlendshapes };\n }\n\n /**\n * Map using dominant emotion only (original behavior)\n */\n private mapFrameDominant(frame: EmotionFrame): void {\n // Check confidence threshold\n if (frame.confidence < this.config.confidenceThreshold) {\n return;\n }\n\n // Get emotion mapping\n const emotion = frame.emotion as Emotion2VecLabel;\n const mapping = EMOTION_ARKIT_MAP[emotion];\n\n if (!mapping) {\n return;\n }\n\n // Apply mapping with intensity and confidence scaling\n const scale = this.config.intensity * frame.confidence;\n\n for (const [name, value] of Object.entries(mapping)) {\n const blendshapeName = name as UpperFaceBlendshapeName;\n if (value !== undefined) {\n this.targetBlendshapes[blendshapeName] = clamp01(value * scale);\n }\n }\n }\n\n /**\n * Map using weighted blend of all emotions by probability\n * Creates more nuanced expressions (e.g., bittersweet = happy + sad)\n */\n private mapFrameWeighted(frame: EmotionFrame): void {\n if (!frame.probabilities) {\n // Fall back to dominant if no probabilities\n this.mapFrameDominant(frame);\n return;\n }\n\n // Blend all emotions by their probability\n for (const [emotion, probability] of Object.entries(frame.probabilities)) {\n // Skip emotions below minimum probability\n if (probability < this.config.minBlendProbability) {\n continue;\n }\n\n const mapping = EMOTION_ARKIT_MAP[emotion as Emotion2VecLabel];\n if (!mapping) {\n continue;\n }\n\n // Add this emotion's contribution weighted by probability\n const scale = this.config.intensity * probability;\n\n for (const [name, value] of Object.entries(mapping)) {\n const blendshapeName = name as UpperFaceBlendshapeName;\n if (value !== undefined) {\n // Additive blending - sum contributions\n this.targetBlendshapes[blendshapeName] += value * scale;\n }\n }\n }\n\n // Clamp all values to 0-1 after blending\n for (const name of UPPER_FACE_BLENDSHAPES) {\n this.targetBlendshapes[name] = clamp01(this.targetBlendshapes[name]);\n }\n }\n\n /**\n * Apply energy modulation to scale emotion intensity by audio energy\n * Louder speech = stronger expressions\n */\n private applyEnergyModulation(): void {\n const { minEnergyScale, maxEnergyScale } = this.config;\n\n // Linear interpolation: energy 0 -> minScale, energy 1 -> maxScale\n const energyScale = minEnergyScale + this.currentEnergy * (maxEnergyScale - minEnergyScale);\n\n for (const name of UPPER_FACE_BLENDSHAPES) {\n this.targetBlendshapes[name] = clamp01(this.targetBlendshapes[name] * energyScale);\n }\n }\n\n /**\n * Apply smoothing to interpolate current values towards target\n *\n * Uses exponential moving average:\n * current = current + smoothingFactor * (target - current)\n *\n * @param _deltaMs - Delta time in milliseconds (reserved for future time-based smoothing)\n */\n update(_deltaMs: number): void {\n const factor = this.config.smoothingFactor;\n\n for (const name of UPPER_FACE_BLENDSHAPES) {\n const target = this.targetBlendshapes[name];\n const current = this.currentBlendshapes[name];\n this.currentBlendshapes[name] = clamp01(current + factor * (target - current));\n }\n }\n\n /**\n * Get current smoothed blendshape values\n *\n * @returns Current upper face blendshapes (after smoothing)\n */\n getCurrentBlendshapes(): UpperFaceBlendshapes {\n return { ...this.currentBlendshapes };\n }\n\n /**\n * Reset mapper to neutral state\n *\n * Sets both target and current blendshapes to zero.\n */\n reset(): void {\n this.targetBlendshapes = createZeroBlendshapes();\n this.currentBlendshapes = createZeroBlendshapes();\n this.currentEnergy = 1.0;\n }\n\n /**\n * Get current configuration\n */\n getConfig(): Required<EmotionBlendshapeConfig> {\n return { ...this.config };\n }\n\n /**\n * Update configuration\n *\n * @param config - Partial configuration to update\n */\n setConfig(config: Partial<EmotionBlendshapeConfig>): void {\n this.config = {\n ...this.config,\n ...config,\n };\n }\n}\n","/**\n * Audio Energy Analysis\n *\n * Utilities for extracting energy/loudness from audio for gesture animation.\n *\n * @module animation\n */\n\n/**\n * Calculate RMS (Root Mean Square) energy from audio samples\n * @param samples Audio samples (Float32Array, normalized -1 to 1)\n * @returns RMS energy value (0 to 1)\n */\nexport function calculateRMS(samples: Float32Array): number {\n if (samples.length === 0) return 0;\n\n let sumSquares = 0;\n for (let i = 0; i < samples.length; i++) {\n sumSquares += samples[i] * samples[i];\n }\n\n return Math.sqrt(sumSquares / samples.length);\n}\n\n/**\n * Calculate peak amplitude from audio samples\n * @param samples Audio samples (Float32Array, normalized -1 to 1)\n * @returns Peak amplitude (0 to 1)\n */\nexport function calculatePeak(samples: Float32Array): number {\n let peak = 0;\n for (let i = 0; i < samples.length; i++) {\n const abs = Math.abs(samples[i]);\n if (abs > peak) peak = abs;\n }\n return peak;\n}\n\n/**\n * Smoothed energy analyzer for gesture animation\n */\nexport class AudioEnergyAnalyzer {\n private smoothedRMS: number = 0;\n private smoothedPeak: number = 0;\n private readonly smoothingFactor: number;\n private readonly noiseFloor: number;\n\n /**\n * @param smoothingFactor How much to smooth (0 = no smoothing, 1 = infinite smoothing). Default 0.85\n * @param noiseFloor Minimum energy threshold to consider as signal. Default 0.01\n */\n constructor(smoothingFactor: number = 0.85, noiseFloor: number = 0.01) {\n this.smoothingFactor = Math.max(0, Math.min(0.99, smoothingFactor));\n this.noiseFloor = noiseFloor;\n }\n\n /**\n * Process audio samples and return smoothed energy values\n * @param samples Audio samples (Float32Array)\n * @returns Object with rms and peak values\n */\n process(samples: Float32Array): { rms: number; peak: number; energy: number } {\n const instantRMS = calculateRMS(samples);\n const instantPeak = calculatePeak(samples);\n\n // Apply noise gate\n const gatedRMS = instantRMS > this.noiseFloor ? instantRMS : 0;\n const gatedPeak = instantPeak > this.noiseFloor ? instantPeak : 0;\n\n // Smooth the values (exponential moving average)\n // Attack fast (when getting louder), release slower\n if (gatedRMS > this.smoothedRMS) {\n // Fast attack\n this.smoothedRMS =\n this.smoothedRMS * 0.5 + gatedRMS * 0.5;\n } else {\n // Slow release\n this.smoothedRMS =\n this.smoothedRMS * this.smoothingFactor +\n gatedRMS * (1 - this.smoothingFactor);\n }\n\n if (gatedPeak > this.smoothedPeak) {\n this.smoothedPeak = this.smoothedPeak * 0.3 + gatedPeak * 0.7;\n } else {\n this.smoothedPeak =\n this.smoothedPeak * this.smoothingFactor +\n gatedPeak * (1 - this.smoothingFactor);\n }\n\n // Combined energy (weighted average of RMS and peak)\n // RMS is more stable, peak catches transients\n const energy = this.smoothedRMS * 0.7 + this.smoothedPeak * 0.3;\n\n return {\n rms: this.smoothedRMS,\n peak: this.smoothedPeak,\n energy: Math.min(1, energy * 2), // Scale up and clamp\n };\n }\n\n /**\n * Reset analyzer state\n */\n reset(): void {\n this.smoothedRMS = 0;\n this.smoothedPeak = 0;\n }\n\n /**\n * Get current smoothed RMS value\n */\n get rms(): number {\n return this.smoothedRMS;\n }\n\n /**\n * Get current smoothed peak value\n */\n get peak(): number {\n return this.smoothedPeak;\n }\n}\n\n/**\n * Extract emphasis points from audio (for gesture timing)\n *\n * Detects sudden increases in energy that correspond to speech emphasis.\n */\nexport class EmphasisDetector {\n private energyHistory: number[] = [];\n private readonly historySize: number;\n private readonly emphasisThreshold: number;\n\n /**\n * @param historySize Number of frames to track. Default 10\n * @param emphasisThreshold Minimum energy increase to count as emphasis. Default 0.15\n */\n constructor(historySize: number = 10, emphasisThreshold: number = 0.15) {\n this.historySize = historySize;\n this.emphasisThreshold = emphasisThreshold;\n }\n\n /**\n * Process energy value and detect emphasis\n * @param energy Current energy value (0-1)\n * @returns Object with isEmphasis flag and emphasisStrength\n */\n process(energy: number): { isEmphasis: boolean; emphasisStrength: number } {\n this.energyHistory.push(energy);\n if (this.energyHistory.length > this.historySize) {\n this.energyHistory.shift();\n }\n\n if (this.energyHistory.length < 3) {\n return { isEmphasis: false, emphasisStrength: 0 };\n }\n\n // Calculate average of previous frames (excluding current)\n const prevFrames = this.energyHistory.slice(0, -1);\n const avgPrev = prevFrames.reduce((a, b) => a + b, 0) / prevFrames.length;\n\n // Compare current to average\n const increase = energy - avgPrev;\n const isEmphasis = increase > this.emphasisThreshold;\n\n return {\n isEmphasis,\n emphasisStrength: isEmphasis ? Math.min(1, increase / 0.3) : 0,\n };\n }\n\n /**\n * Reset detector state\n */\n reset(): void {\n this.energyHistory = [];\n }\n}\n","/**\n * Console Exporter\n *\n * Exports telemetry data to the browser console for development/debugging.\n *\n * @category Telemetry\n */\n\nimport type { SpanAttributes } from '../types';\n\n/**\n * Span data structure for export\n */\nexport interface SpanData {\n name: string;\n traceId: string;\n spanId: string;\n parentSpanId?: string;\n startTime: number;\n endTime: number;\n durationMs: number;\n status: 'ok' | 'error';\n attributes: SpanAttributes;\n error?: Error;\n}\n\n/**\n * Metric data structure for export\n */\nexport interface MetricData {\n name: string;\n type: 'counter' | 'histogram';\n value: number;\n attributes: Record<string, string | number | boolean>;\n timestamp: number;\n}\n\n/**\n * Exporter interface that all exporters must implement\n */\nexport interface TelemetryExporterInterface {\n /** Export a completed span */\n exportSpan(span: SpanData): void;\n /** Export a metric */\n exportMetric(metric: MetricData): void;\n /** Flush any buffered data */\n flush(): Promise<void>;\n /** Shutdown the exporter */\n shutdown(): Promise<void>;\n}\n\n/**\n * Console exporter for development/debugging\n *\n * Outputs spans and metrics to the browser console with formatting.\n */\nexport class ConsoleExporter implements TelemetryExporterInterface {\n private enabled: boolean;\n private prefix: string;\n\n constructor(options: { enabled?: boolean; prefix?: string } = {}) {\n this.enabled = options.enabled ?? true;\n this.prefix = options.prefix ?? '[Omote Telemetry]';\n }\n\n exportSpan(span: SpanData): void {\n if (!this.enabled) return;\n\n const statusIcon = span.status === 'ok' ? '✓' : '✗';\n const statusColor = span.status === 'ok' ? 'color: green' : 'color: red';\n\n console.groupCollapsed(\n `%c${this.prefix} %c${statusIcon} ${span.name} %c(${span.durationMs.toFixed(2)}ms)`,\n 'color: gray',\n statusColor,\n 'color: gray'\n );\n\n console.log('Trace ID:', span.traceId);\n console.log('Span ID:', span.spanId);\n if (span.parentSpanId) {\n console.log('Parent Span ID:', span.parentSpanId);\n }\n console.log('Duration:', `${span.durationMs.toFixed(2)}ms`);\n console.log('Status:', span.status);\n\n if (Object.keys(span.attributes).length > 0) {\n console.log('Attributes:', span.attributes);\n }\n\n if (span.error) {\n console.error('Error:', span.error);\n }\n\n console.groupEnd();\n }\n\n exportMetric(metric: MetricData): void {\n if (!this.enabled) return;\n\n const typeIcon = metric.type === 'counter' ? '↑' : '📊';\n\n console.log(\n `%c${this.prefix} %c${typeIcon} ${metric.name}: %c${metric.value}`,\n 'color: gray',\n 'color: blue',\n 'color: black; font-weight: bold',\n metric.attributes\n );\n }\n\n async flush(): Promise<void> {\n // Console exporter doesn't buffer, nothing to flush\n }\n\n async shutdown(): Promise<void> {\n this.enabled = false;\n }\n}\n","/**\n * OTLP Exporter\n *\n * Exports telemetry data to OTLP-compatible backends (Jaeger, Tempo, etc.)\n * using the OTLP/HTTP JSON protocol.\n *\n * @category Telemetry\n */\n\nimport type { OTLPExporterConfig } from '../types';\nimport type { SpanData, MetricData, TelemetryExporterInterface } from './console';\n\n/**\n * OTLP span status codes\n */\nconst StatusCode = {\n UNSET: 0,\n OK: 1,\n ERROR: 2,\n} as const;\n\n/**\n * Convert internal span to OTLP format\n */\nfunction spanToOTLP(span: SpanData, serviceName: string, serviceVersion: string) {\n const attributes = Object.entries(span.attributes)\n .filter(([, v]) => v !== undefined)\n .map(([key, value]) => ({\n key,\n value: typeof value === 'string'\n ? { stringValue: value }\n : typeof value === 'number'\n ? Number.isInteger(value)\n ? { intValue: value }\n : { doubleValue: value }\n : { boolValue: value },\n }));\n\n return {\n resourceSpans: [{\n resource: {\n attributes: [\n { key: 'service.name', value: { stringValue: serviceName } },\n { key: 'service.version', value: { stringValue: serviceVersion } },\n { key: 'telemetry.sdk.name', value: { stringValue: 'omote-sdk' } },\n { key: 'telemetry.sdk.language', value: { stringValue: 'javascript' } },\n ],\n },\n scopeSpans: [{\n scope: {\n name: 'omote-sdk',\n version: serviceVersion,\n },\n spans: [{\n traceId: span.traceId,\n spanId: span.spanId,\n parentSpanId: span.parentSpanId || '',\n name: span.name,\n kind: 1, // INTERNAL\n startTimeUnixNano: String(span.startTime * 1_000_000),\n endTimeUnixNano: String(span.endTime * 1_000_000),\n attributes,\n status: {\n code: span.status === 'ok' ? StatusCode.OK : StatusCode.ERROR,\n message: span.error?.message || '',\n },\n }],\n }],\n }],\n };\n}\n\n/**\n * Convert internal metric to OTLP format\n */\nfunction metricToOTLP(metric: MetricData, serviceName: string, serviceVersion: string) {\n const attributes = Object.entries(metric.attributes)\n .filter(([, v]) => v !== undefined)\n .map(([key, value]) => ({\n key,\n value: typeof value === 'string'\n ? { stringValue: value }\n : typeof value === 'number'\n ? Number.isInteger(value)\n ? { intValue: value }\n : { doubleValue: value }\n : { boolValue: value },\n }));\n\n const dataPoint = {\n attributes,\n timeUnixNano: String(metric.timestamp * 1_000_000),\n ...(metric.type === 'counter'\n ? { asInt: metric.value }\n : { asDouble: metric.value }),\n };\n\n return {\n resourceMetrics: [{\n resource: {\n attributes: [\n { key: 'service.name', value: { stringValue: serviceName } },\n { key: 'service.version', value: { stringValue: serviceVersion } },\n ],\n },\n scopeMetrics: [{\n scope: {\n name: 'omote-sdk',\n version: serviceVersion,\n },\n metrics: [{\n name: metric.name,\n ...(metric.type === 'counter'\n ? {\n sum: {\n dataPoints: [dataPoint],\n aggregationTemporality: 2, // CUMULATIVE\n isMonotonic: true,\n },\n }\n : {\n gauge: {\n dataPoints: [dataPoint],\n },\n }),\n }],\n }],\n }],\n };\n}\n\n/**\n * OTLP exporter for production telemetry\n *\n * Sends spans and metrics to OTLP-compatible backends like:\n * - Jaeger\n * - Grafana Tempo\n * - Honeycomb\n * - Datadog\n * - AWS X-Ray (with collector)\n */\nexport class OTLPExporter implements TelemetryExporterInterface {\n private config: Required<OTLPExporterConfig>;\n private serviceName: string;\n private serviceVersion: string;\n private spanBuffer: SpanData[] = [];\n private metricBuffer: MetricData[] = [];\n private flushIntervalId: ReturnType<typeof setInterval> | null = null;\n private readonly BUFFER_SIZE = 100;\n private readonly FLUSH_INTERVAL_MS = 5000;\n private isShutdown = false;\n\n constructor(\n config: OTLPExporterConfig,\n serviceName: string = 'omote-sdk',\n serviceVersion: string = '0.1.0'\n ) {\n this.config = {\n timeoutMs: 10000,\n headers: {},\n ...config,\n };\n this.serviceName = serviceName;\n this.serviceVersion = serviceVersion;\n\n // Start periodic flush\n this.flushIntervalId = setInterval(() => {\n this.flush().catch(console.error);\n }, this.FLUSH_INTERVAL_MS);\n }\n\n exportSpan(span: SpanData): void {\n if (this.isShutdown) return;\n\n this.spanBuffer.push(span);\n\n if (this.spanBuffer.length >= this.BUFFER_SIZE) {\n this.flush().catch(console.error);\n }\n }\n\n exportMetric(metric: MetricData): void {\n if (this.isShutdown) return;\n\n this.metricBuffer.push(metric);\n\n if (this.metricBuffer.length >= this.BUFFER_SIZE) {\n this.flush().catch(console.error);\n }\n }\n\n async flush(): Promise<void> {\n if (this.isShutdown) return;\n\n const spans = this.spanBuffer.splice(0);\n const metrics = this.metricBuffer.splice(0);\n\n const promises: Promise<void>[] = [];\n\n // Export spans\n if (spans.length > 0) {\n promises.push(this.exportSpans(spans));\n }\n\n // Export metrics\n if (metrics.length > 0) {\n promises.push(this.exportMetrics(metrics));\n }\n\n await Promise.all(promises);\n }\n\n async shutdown(): Promise<void> {\n if (this.flushIntervalId) {\n clearInterval(this.flushIntervalId);\n this.flushIntervalId = null;\n }\n\n // Final flush before marking shutdown\n await this.flush();\n\n this.isShutdown = true;\n }\n\n private async exportSpans(spans: SpanData[]): Promise<void> {\n // Combine all spans into a single request\n const resourceSpans = spans.map(span =>\n spanToOTLP(span, this.serviceName, this.serviceVersion).resourceSpans[0]\n );\n\n const body = { resourceSpans };\n const endpoint = this.config.endpoint.replace(/\\/$/, '') + '/v1/traces';\n\n await this.sendRequest(endpoint, body);\n }\n\n private async exportMetrics(metrics: MetricData[]): Promise<void> {\n // Combine all metrics into a single request\n const resourceMetrics = metrics.map(metric =>\n metricToOTLP(metric, this.serviceName, this.serviceVersion).resourceMetrics[0]\n );\n\n const body = { resourceMetrics };\n const endpoint = this.config.endpoint.replace(/\\/$/, '') + '/v1/metrics';\n\n await this.sendRequest(endpoint, body);\n }\n\n private async sendRequest(endpoint: string, body: unknown): Promise<void> {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.config.timeoutMs);\n\n try {\n const response = await fetch(endpoint, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...this.config.headers,\n },\n body: JSON.stringify(body),\n signal: controller.signal,\n });\n\n if (!response.ok) {\n console.warn(`[OTLP] Export failed: ${response.status} ${response.statusText}`);\n }\n } catch (error) {\n if ((error as Error).name === 'AbortError') {\n console.warn('[OTLP] Export timed out');\n } else {\n console.warn('[OTLP] Export error:', error);\n }\n } finally {\n clearTimeout(timeoutId);\n }\n }\n}\n","/**\n * Muse Telemetry\n *\n * Main orchestrator for SDK telemetry. Manages spans, metrics, and exporters.\n *\n * @category Telemetry\n */\n\nimport type { TelemetryConfig, SpanAttributes, SamplingConfig } from './types';\nimport type { SpanData, MetricData, TelemetryExporterInterface } from './exporters/console';\nimport { ConsoleExporter } from './exporters/console';\nimport { OTLPExporter } from './exporters/otlp';\n\n/**\n * Generate a random hex ID\n */\nfunction generateId(length: number = 16): string {\n const bytes = new Uint8Array(length);\n crypto.getRandomValues(bytes);\n return Array.from(bytes)\n .map(b => b.toString(16).padStart(2, '0'))\n .join('');\n}\n\n/**\n * Span context for tracing\n */\ninterface SpanContext {\n traceId: string;\n spanId: string;\n parentSpanId?: string;\n}\n\n/**\n * Active span handle returned by startSpan\n */\nexport interface ActiveSpan {\n /** End the span with success status */\n end(): void;\n /** End the span with error status */\n endWithError(error: Error): void;\n /** Add attributes to the span */\n setAttributes(attrs: Partial<SpanAttributes>): void;\n /** Get the span context */\n getContext(): SpanContext;\n}\n\n/**\n * Global telemetry instance\n */\nlet globalTelemetry: OmoteTelemetry | null = null;\n\n/**\n * Configure global telemetry\n *\n * @example\n * ```typescript\n * // Development\n * configureTelemetry({\n * enabled: true,\n * serviceName: 'omote-dev',\n * exporter: 'console',\n * });\n *\n * // Production\n * configureTelemetry({\n * enabled: true,\n * serviceName: 'omote-prod',\n * exporter: 'otlp',\n * exporterConfig: {\n * endpoint: 'https://tempo.example.com',\n * },\n * sampling: { ratio: 0.1 },\n * });\n * ```\n */\nexport function configureTelemetry(config: TelemetryConfig): OmoteTelemetry {\n if (globalTelemetry) {\n globalTelemetry.shutdown();\n }\n globalTelemetry = new OmoteTelemetry(config);\n return globalTelemetry;\n}\n\n/**\n * Get the global telemetry instance\n */\nexport function getTelemetry(): OmoteTelemetry | null {\n return globalTelemetry;\n}\n\n/**\n * Main telemetry class\n *\n * Manages spans, metrics, and exports to configured backends.\n */\nexport class OmoteTelemetry {\n private config: Required<Omit<TelemetryConfig, 'exporterConfig'>> & { exporterConfig?: TelemetryConfig['exporterConfig'] };\n private exporter: TelemetryExporterInterface | null = null;\n private activeTraceId: string | null = null;\n private metricsIntervalId: ReturnType<typeof setInterval> | null = null;\n\n // Metric accumulators\n private counters: Map<string, { value: number; attributes: Record<string, string | number | boolean> }> = new Map();\n private histograms: Map<string, { values: number[]; attributes: Record<string, string | number | boolean> }> = new Map();\n\n constructor(config: TelemetryConfig) {\n this.config = {\n enabled: config.enabled ?? false,\n serviceName: config.serviceName ?? 'omote-sdk',\n serviceVersion: config.serviceVersion ?? '0.1.0',\n exporter: config.exporter ?? 'none',\n exporterConfig: config.exporterConfig,\n sampling: config.sampling ?? { ratio: 1.0, alwaysSampleErrors: true },\n metricsEnabled: config.metricsEnabled ?? true,\n metricsIntervalMs: config.metricsIntervalMs ?? 60000,\n };\n\n if (this.config.enabled) {\n this.initExporter();\n this.startMetricsCollection();\n }\n }\n\n /**\n * Initialize the configured exporter\n */\n private initExporter(): void {\n switch (this.config.exporter) {\n case 'console':\n this.exporter = new ConsoleExporter({ enabled: true });\n break;\n case 'otlp':\n if (!this.config.exporterConfig) {\n console.warn('[Telemetry] OTLP exporter requires exporterConfig with endpoint');\n return;\n }\n this.exporter = new OTLPExporter(\n this.config.exporterConfig,\n this.config.serviceName,\n this.config.serviceVersion\n );\n break;\n case 'none':\n default:\n this.exporter = null;\n }\n }\n\n /**\n * Start periodic metrics collection\n */\n private startMetricsCollection(): void {\n if (!this.config.metricsEnabled || !this.exporter) return;\n\n this.metricsIntervalId = setInterval(() => {\n this.flushMetrics();\n }, this.config.metricsIntervalMs);\n }\n\n /**\n * Check if this operation should be sampled\n */\n private shouldSample(isError: boolean = false): boolean {\n if (!this.config.enabled) return false;\n\n const sampling = this.config.sampling as SamplingConfig;\n if (isError && sampling.alwaysSampleErrors) return true;\n\n const ratio = sampling.ratio ?? 1.0;\n return Math.random() < ratio;\n }\n\n /**\n * Start a new span\n *\n * @example\n * ```typescript\n * const span = telemetry.startSpan('Wav2Vec2.infer', {\n * 'inference.input_samples': samples.length,\n * 'model.backend': 'webgpu',\n * });\n *\n * try {\n * const result = await doInference();\n * span.setAttributes({ 'inference.output_frames': result.frames });\n * span.end();\n * } catch (error) {\n * span.endWithError(error);\n * }\n * ```\n */\n startSpan(name: string, attributes: Partial<SpanAttributes> = {}, parentContext?: SpanContext): ActiveSpan {\n const traceId = parentContext?.traceId ?? this.activeTraceId ?? generateId(16);\n const spanId = generateId(8);\n const parentSpanId = parentContext?.spanId;\n const startTime = performance.now();\n\n // Set active trace if this is a root span\n if (!parentContext && !this.activeTraceId) {\n this.activeTraceId = traceId;\n }\n\n let spanAttributes = { ...attributes };\n let ended = false;\n let sampled = this.shouldSample();\n\n const context: SpanContext = { traceId, spanId, parentSpanId };\n\n const endSpan = (status: 'ok' | 'error', error?: Error): void => {\n if (ended) return;\n ended = true;\n\n const endTime = performance.now();\n const durationMs = endTime - startTime;\n\n // Re-check sampling for errors\n if (status === 'error' && !sampled) {\n sampled = this.shouldSample(true);\n }\n\n if (!sampled || !this.exporter) return;\n\n const spanData: SpanData = {\n name,\n traceId,\n spanId,\n parentSpanId,\n startTime,\n endTime,\n durationMs,\n status,\n attributes: spanAttributes as SpanAttributes,\n error,\n };\n\n this.exporter.exportSpan(spanData);\n\n // Clear active trace if this was the root span\n if (!parentSpanId && this.activeTraceId === traceId) {\n this.activeTraceId = null;\n }\n };\n\n return {\n end: () => endSpan('ok'),\n endWithError: (error: Error) => endSpan('error', error),\n setAttributes: (attrs: Partial<SpanAttributes>) => {\n spanAttributes = { ...spanAttributes, ...attrs };\n },\n getContext: () => context,\n };\n }\n\n /**\n * Wrap an async function with a span\n *\n * @example\n * ```typescript\n * const result = await telemetry.withSpan('Model.load', async (span) => {\n * const model = await loadModel();\n * span.setAttributes({ 'model.size_bytes': model.size });\n * return model;\n * });\n * ```\n */\n async withSpan<T>(\n name: string,\n fn: (span: ActiveSpan) => Promise<T>,\n attributes: Partial<SpanAttributes> = {},\n parentContext?: SpanContext\n ): Promise<T> {\n const span = this.startSpan(name, attributes, parentContext);\n\n try {\n const result = await fn(span);\n span.end();\n return result;\n } catch (error) {\n span.endWithError(error as Error);\n throw error;\n }\n }\n\n /**\n * Increment a counter metric\n *\n * @example\n * ```typescript\n * telemetry.incrementCounter('omote.inference.total', 1, {\n * model: 'wav2vec2',\n * backend: 'webgpu',\n * status: 'success',\n * });\n * ```\n */\n incrementCounter(\n name: string,\n value: number = 1,\n attributes: Record<string, string | number | boolean> = {}\n ): void {\n if (!this.config.enabled || !this.config.metricsEnabled) return;\n\n const key = this.getMetricKey(name, attributes);\n const existing = this.counters.get(key);\n\n if (existing) {\n existing.value += value;\n } else {\n this.counters.set(key, { value, attributes });\n }\n }\n\n /**\n * Record a histogram value\n *\n * @example\n * ```typescript\n * telemetry.recordHistogram('omote.inference.latency', durationMs, {\n * model: 'wav2vec2',\n * backend: 'webgpu',\n * });\n * ```\n */\n recordHistogram(\n name: string,\n value: number,\n attributes: Record<string, string | number | boolean> = {}\n ): void {\n if (!this.config.enabled || !this.config.metricsEnabled) return;\n\n const key = this.getMetricKey(name, attributes);\n const existing = this.histograms.get(key);\n\n if (existing) {\n existing.values.push(value);\n } else {\n this.histograms.set(key, { values: [value], attributes });\n }\n }\n\n /**\n * Generate unique key for metric with attributes\n */\n private getMetricKey(name: string, attributes: Record<string, string | number | boolean>): string {\n const sortedAttrs = Object.entries(attributes)\n .sort(([a], [b]) => a.localeCompare(b))\n .map(([k, v]) => `${k}=${v}`)\n .join(',');\n return `${name}|${sortedAttrs}`;\n }\n\n /**\n * Flush accumulated metrics to exporter\n */\n private flushMetrics(): void {\n if (!this.exporter) return;\n\n const timestamp = performance.now();\n\n // Export counters\n for (const [key, data] of this.counters) {\n const name = key.split('|')[0];\n const metric: MetricData = {\n name,\n type: 'counter',\n value: data.value,\n attributes: data.attributes,\n timestamp,\n };\n this.exporter.exportMetric(metric);\n }\n\n // Export histogram aggregates\n for (const [key, data] of this.histograms) {\n const name = key.split('|')[0];\n if (data.values.length === 0) continue;\n\n // Calculate average for histogram\n const sum = data.values.reduce((a, b) => a + b, 0);\n const avg = sum / data.values.length;\n\n const metric: MetricData = {\n name,\n type: 'histogram',\n value: avg,\n attributes: {\n ...data.attributes,\n count: data.values.length,\n sum,\n min: Math.min(...data.values),\n max: Math.max(...data.values),\n },\n timestamp,\n };\n this.exporter.exportMetric(metric);\n\n // Clear values after export\n data.values = [];\n }\n }\n\n /**\n * Force flush all pending data\n */\n async flush(): Promise<void> {\n this.flushMetrics();\n await this.exporter?.flush();\n }\n\n /**\n * Shutdown telemetry\n */\n async shutdown(): Promise<void> {\n if (this.metricsIntervalId) {\n clearInterval(this.metricsIntervalId);\n this.metricsIntervalId = null;\n }\n\n await this.flush();\n await this.exporter?.shutdown();\n this.exporter = null;\n }\n\n /**\n * Check if telemetry is enabled\n */\n isEnabled(): boolean {\n return this.config.enabled;\n }\n\n /**\n * Get current configuration\n */\n getConfig(): TelemetryConfig {\n return { ...this.config };\n }\n}\n","/**\n * Telemetry Types\n *\n * Configuration and type definitions for OpenTelemetry instrumentation.\n *\n * @category Telemetry\n */\n\n/**\n * Supported telemetry exporters\n */\nexport type TelemetryExporter = 'console' | 'otlp' | 'none';\n\n/**\n * Sampling configuration\n */\nexport interface SamplingConfig {\n /** Sampling ratio (0.0 - 1.0). Default: 1.0 (sample everything) */\n ratio?: number;\n /** Always sample errors regardless of ratio */\n alwaysSampleErrors?: boolean;\n}\n\n/**\n * OTLP exporter configuration\n */\nexport interface OTLPExporterConfig {\n /** OTLP endpoint URL (e.g., 'https://tempo.example.com/v1/traces') */\n endpoint: string;\n /** Optional headers for authentication */\n headers?: Record<string, string>;\n /** Request timeout in ms. Default: 10000 */\n timeoutMs?: number;\n}\n\n/**\n * Main telemetry configuration\n */\nexport interface TelemetryConfig {\n /** Enable/disable telemetry. Default: false */\n enabled?: boolean;\n /** Service name for spans. Default: 'omote-sdk' */\n serviceName?: string;\n /** Service version. Default: SDK version */\n serviceVersion?: string;\n /** Exporter type. Default: 'none' */\n exporter?: TelemetryExporter;\n /** OTLP exporter config (required if exporter is 'otlp') */\n exporterConfig?: OTLPExporterConfig;\n /** Sampling configuration */\n sampling?: SamplingConfig;\n /** Enable metrics collection. Default: true when telemetry enabled */\n metricsEnabled?: boolean;\n /** Metrics export interval in ms. Default: 60000 */\n metricsIntervalMs?: number;\n}\n\n/**\n * Span attributes for model operations\n */\nexport interface ModelSpanAttributes {\n /** Model URL or identifier */\n 'model.url'?: string;\n /** Model name (e.g., 'whisper', 'lam', 'silero-vad') */\n 'model.name'?: string;\n /** Inference backend used */\n 'model.backend'?: 'webgpu' | 'wasm';\n /** Whether model was loaded from cache */\n 'model.cached'?: boolean;\n /** Model size in bytes */\n 'model.size_bytes'?: number;\n}\n\n/**\n * Span attributes for inference operations\n */\nexport interface InferenceSpanAttributes extends ModelSpanAttributes {\n /** Number of input audio samples */\n 'inference.input_samples'?: number;\n /** Input duration in ms */\n 'inference.input_duration_ms'?: number;\n /** Number of output frames (for LAM) */\n 'inference.output_frames'?: number;\n /** Inference duration in ms */\n 'inference.duration_ms'?: number;\n /** Whether inference succeeded */\n 'inference.success'?: boolean;\n /** Error type if failed */\n 'inference.error_type'?: string;\n}\n\n/**\n * Span attributes for cache operations\n */\nexport interface CacheSpanAttributes {\n /** Cache key (URL) */\n 'cache.key'?: string;\n /** Whether it was a cache hit */\n 'cache.hit'?: boolean;\n /** Size of cached item in bytes */\n 'cache.size_bytes'?: number;\n /** Cache operation type */\n 'cache.operation'?: 'get' | 'set' | 'delete';\n}\n\n/**\n * Combined span attributes type\n */\nexport type SpanAttributes =\n | ModelSpanAttributes\n | InferenceSpanAttributes\n | CacheSpanAttributes\n | Record<string, string | number | boolean | undefined>;\n\n/**\n * Metric names used by the SDK\n */\nexport const MetricNames = {\n /** Histogram: Inference latency in ms */\n INFERENCE_LATENCY: 'omote.inference.latency',\n /** Histogram: Model load time in ms */\n MODEL_LOAD_TIME: 'omote.model.load_time',\n /** Counter: Total inference operations */\n INFERENCE_TOTAL: 'omote.inference.total',\n /** Counter: Total errors */\n ERRORS_TOTAL: 'omote.errors.total',\n /** Counter: Cache hits */\n CACHE_HITS: 'omote.cache.hits',\n /** Counter: Cache misses */\n CACHE_MISSES: 'omote.cache.misses',\n} as const;\n\n/**\n * Histogram buckets for inference latency (ms)\n */\nexport const INFERENCE_LATENCY_BUCKETS = [1, 5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000];\n\n/**\n * Histogram buckets for model load time (ms)\n */\nexport const MODEL_LOAD_TIME_BUCKETS = [100, 500, 1000, 2500, 5000, 10000, 30000, 60000];\n","/**\n * Model Cache\n *\n * Caches ONNX models in IndexedDB for faster subsequent loads.\n * IndexedDB can handle large files (100s of MBs) unlike localStorage.\n *\n * @category Cache\n */\n\nimport { getTelemetry } from '../telemetry';\n\nconst DB_NAME = 'omote-model-cache';\nconst DB_VERSION = 2;\nconst STORE_NAME = 'models';\n\n/** Default cache size limit: 1GB */\nconst DEFAULT_MAX_SIZE_BYTES = 1024 * 1024 * 1024;\n\n/**\n * Configuration for cache size limits and eviction behavior\n */\nexport interface CacheConfig {\n /** Maximum total cache size in bytes (default: 1GB) */\n maxSizeBytes?: number;\n /** Maximum age in milliseconds before eviction (default: none) */\n maxAgeMs?: number;\n /** Callback when storage quota exceeds warning threshold */\n onQuotaWarning?: (info: QuotaInfo) => void;\n}\n\n/**\n * Storage quota information\n */\nexport interface QuotaInfo {\n /** Total bytes used across all origins */\n usedBytes: number;\n /** Total available quota in bytes */\n quotaBytes: number;\n /** Percentage of quota used (0-100) */\n percentUsed: number;\n /** Bytes used by omote cache specifically */\n cacheBytes: number;\n}\n\n/** Global cache configuration */\nlet globalCacheConfig: CacheConfig = {\n maxSizeBytes: DEFAULT_MAX_SIZE_BYTES,\n};\n\n/**\n * Configure cache size limits and eviction behavior\n *\n * @param config - Cache configuration options\n *\n * @example\n * ```typescript\n * import { configureCacheLimit } from '@omote/core';\n *\n * // Set 500MB limit with 24-hour max age\n * configureCacheLimit({\n * maxSizeBytes: 500 * 1024 * 1024,\n * maxAgeMs: 24 * 60 * 60 * 1000,\n * onQuotaWarning: (info) => {\n * console.warn(`Storage ${info.percentUsed.toFixed(1)}% used`);\n * }\n * });\n * ```\n */\nexport function configureCacheLimit(config: CacheConfig): void {\n globalCacheConfig = {\n ...globalCacheConfig,\n ...config,\n };\n\n // Trigger immediate cleanup if over limit\n const cache = getModelCache();\n cache.enforceLimit().catch((err) => {\n console.warn('[ModelCache] Failed to enforce limit after config change:', err);\n });\n}\n\n/**\n * Get current cache configuration\n */\nexport function getCacheConfig(): CacheConfig {\n return { ...globalCacheConfig };\n}\n\ninterface CachedModel {\n url: string;\n data: ArrayBuffer;\n size: number;\n cachedAt: number;\n /** Last time this model was accessed (for LRU eviction) */\n lastAccessedAt: number;\n etag?: string;\n version?: string;\n}\n\n/**\n * Result from getWithValidation() method\n */\nexport interface ValidationResult {\n /** The cached data, or null if not found */\n data: ArrayBuffer | null;\n /** True if the cached data is stale (etag mismatch) */\n stale: boolean;\n}\n\n/**\n * Generate a version-aware cache key\n *\n * @param url - The model URL\n * @param version - Optional version string\n * @returns The cache key (url#vX.X.X if version provided, url otherwise)\n *\n * @example\n * ```typescript\n * getCacheKey('http://example.com/model.onnx', '1.0.0')\n * // Returns: 'http://example.com/model.onnx#v1.0.0'\n *\n * getCacheKey('http://example.com/model.onnx')\n * // Returns: 'http://example.com/model.onnx'\n * ```\n */\nexport function getCacheKey(url: string, version?: string): string {\n if (version) {\n return `${url}#v${version}`;\n }\n return url;\n}\n\ninterface CacheStats {\n totalSize: number;\n modelCount: number;\n models: { url: string; size: number; cachedAt: Date }[];\n}\n\n/**\n * ModelCache - IndexedDB-based cache for ONNX models\n */\nexport class ModelCache {\n private db: IDBDatabase | null = null;\n private dbPromise: Promise<IDBDatabase> | null = null;\n\n /**\n * Initialize the cache database\n */\n private async getDB(): Promise<IDBDatabase> {\n if (this.db) return this.db;\n if (this.dbPromise) return this.dbPromise;\n\n // Request persistent storage for more generous quota on iOS/mobile browsers\n // This increases available storage from ~50MB to potentially GBs\n if (navigator.storage && navigator.storage.persist) {\n try {\n const isPersisted = await navigator.storage.persist();\n if (isPersisted) {\n console.log('[ModelCache] Persistent storage granted - increased quota available');\n } else {\n console.log('[ModelCache] Persistent storage denied - using default quota');\n }\n\n // Log current quota usage (helpful for debugging iOS limits)\n if (navigator.storage.estimate) {\n const estimate = await navigator.storage.estimate();\n const usedMB = ((estimate.usage || 0) / 1024 / 1024).toFixed(2);\n const quotaMB = ((estimate.quota || 0) / 1024 / 1024).toFixed(2);\n console.log(`[ModelCache] Storage: ${usedMB}MB / ${quotaMB}MB quota`);\n }\n } catch (err) {\n console.warn('[ModelCache] Failed to request persistent storage:', err);\n }\n }\n\n this.dbPromise = new Promise((resolve, reject) => {\n const request = indexedDB.open(DB_NAME, DB_VERSION);\n\n request.onerror = () => {\n console.error('[ModelCache] Failed to open IndexedDB:', request.error);\n reject(request.error);\n };\n\n request.onsuccess = () => {\n this.db = request.result;\n resolve(this.db);\n };\n\n request.onupgradeneeded = (event) => {\n const db = (event.target as IDBOpenDBRequest).result;\n const oldVersion = (event as IDBVersionChangeEvent).oldVersion;\n const tx = (event.target as IDBOpenDBRequest).transaction;\n\n if (oldVersion < 1) {\n // Initial schema: create store with url as key\n const store = db.createObjectStore(STORE_NAME, { keyPath: 'url' });\n store.createIndex('lastAccessedAt', 'lastAccessedAt', { unique: false });\n } else if (oldVersion < 2 && tx) {\n // Migrate from v1 to v2: add lastAccessedAt index and backfill existing entries\n const store = tx.objectStore(STORE_NAME);\n\n // Create index if it doesn't exist\n if (!store.indexNames.contains('lastAccessedAt')) {\n store.createIndex('lastAccessedAt', 'lastAccessedAt', { unique: false });\n }\n\n // Migrate existing entries: set lastAccessedAt = cachedAt\n const cursorRequest = store.openCursor();\n cursorRequest.onsuccess = (cursorEvent) => {\n const cursor = (cursorEvent.target as IDBRequest<IDBCursorWithValue>).result;\n if (cursor) {\n const value = cursor.value;\n if (value.lastAccessedAt === undefined) {\n value.lastAccessedAt = value.cachedAt || Date.now();\n cursor.update(value);\n }\n cursor.continue();\n }\n };\n }\n };\n });\n\n return this.dbPromise;\n }\n\n /**\n * Check if a model is cached\n */\n async has(url: string): Promise<boolean> {\n try {\n const db = await this.getDB();\n return new Promise((resolve) => {\n const tx = db.transaction(STORE_NAME, 'readonly');\n const store = tx.objectStore(STORE_NAME);\n const request = store.count(url);\n request.onsuccess = () => resolve(request.result > 0);\n request.onerror = () => resolve(false);\n });\n } catch {\n return false;\n }\n }\n\n /**\n * Get a cached model\n *\n * Updates lastAccessedAt timestamp for LRU tracking on cache hit.\n */\n async get(url: string): Promise<ArrayBuffer | null> {\n const telemetry = getTelemetry();\n const span = telemetry?.startSpan('ModelCache.get', { 'cache.url': url });\n try {\n const db = await this.getDB();\n return new Promise((resolve) => {\n // Use readwrite to update lastAccessedAt on hit\n const tx = db.transaction(STORE_NAME, 'readwrite');\n const store = tx.objectStore(STORE_NAME);\n const request = store.get(url);\n request.onsuccess = () => {\n const cached = request.result as CachedModel | undefined;\n const hit = cached?.data != null;\n span?.setAttributes({ 'cache.hit': hit });\n if (cached) {\n span?.setAttributes({ 'cache.size_bytes': cached.size });\n // Update lastAccessedAt for LRU tracking\n cached.lastAccessedAt = Date.now();\n store.put(cached);\n }\n span?.end();\n if (hit) {\n telemetry?.incrementCounter('omote.cache.hits', 1, {});\n } else {\n telemetry?.incrementCounter('omote.cache.misses', 1, {});\n }\n resolve(cached?.data ?? null);\n };\n request.onerror = () => {\n span?.setAttributes({ 'cache.hit': false });\n span?.end();\n telemetry?.incrementCounter('omote.cache.misses', 1, {});\n resolve(null);\n };\n });\n } catch {\n span?.endWithError(new Error('Cache get failed'));\n return null;\n }\n }\n\n /**\n * Get a cached model with ETag validation\n *\n * Validates the cached data against the server's current ETag.\n * If the cached ETag differs from the server's, the data is marked as stale.\n *\n * @param url - The cache key\n * @param originalUrl - The original URL for HEAD request (if different from cache key)\n * @returns ValidationResult with data and stale flag\n *\n * @example\n * ```typescript\n * const result = await cache.getWithValidation('http://example.com/model.onnx');\n * if (result.data && !result.stale) {\n * // Use cached data\n * } else if (result.stale) {\n * // Refetch and update cache\n * }\n * ```\n */\n async getWithValidation(url: string, originalUrl?: string): Promise<ValidationResult> {\n const telemetry = getTelemetry();\n const span = telemetry?.startSpan('ModelCache.getWithValidation', { 'cache.url': url });\n\n try {\n const db = await this.getDB();\n const cached = await new Promise<CachedModel | undefined>((resolve) => {\n const tx = db.transaction(STORE_NAME, 'readonly');\n const store = tx.objectStore(STORE_NAME);\n const request = store.get(url);\n request.onsuccess = () => resolve(request.result as CachedModel | undefined);\n request.onerror = () => resolve(undefined);\n });\n\n // Cache miss\n if (!cached?.data) {\n span?.setAttributes({ 'cache.hit': false });\n span?.end();\n telemetry?.incrementCounter('omote.cache.misses', 1, {});\n return { data: null, stale: false };\n }\n\n span?.setAttributes({ 'cache.hit': true, 'cache.size_bytes': cached.size });\n\n // No etag stored - can't validate, return as fresh\n if (!cached.etag) {\n span?.setAttributes({ 'cache.validated': false, 'cache.stale': false });\n span?.end();\n telemetry?.incrementCounter('omote.cache.hits', 1, {});\n return { data: cached.data, stale: false };\n }\n\n // Validate via HEAD request\n const fetchUrl = originalUrl || url;\n try {\n const response = await fetch(fetchUrl, { method: 'HEAD' });\n if (!response.ok) {\n // Server error - assume cache is still valid\n span?.setAttributes({ 'cache.validated': false, 'cache.stale': false });\n span?.end();\n telemetry?.incrementCounter('omote.cache.hits', 1, {});\n return { data: cached.data, stale: false };\n }\n\n const serverEtag = response.headers.get('etag');\n const isStale = serverEtag !== null && serverEtag !== cached.etag;\n\n span?.setAttributes({\n 'cache.validated': true,\n 'cache.stale': isStale,\n 'cache.server_etag': serverEtag || 'none',\n 'cache.cached_etag': cached.etag,\n });\n span?.end();\n\n if (isStale) {\n telemetry?.incrementCounter('omote.cache.stale', 1, {});\n console.log(`[ModelCache] Stale cache detected for ${url}`);\n } else {\n telemetry?.incrementCounter('omote.cache.hits', 1, {});\n }\n\n return { data: cached.data, stale: isStale };\n } catch (fetchError) {\n // HEAD request failed (network error, CORS, etc.)\n // Return cached data as non-stale - better than failing completely\n console.warn('[ModelCache] HEAD validation failed, using cached data:', fetchError);\n span?.setAttributes({ 'cache.validated': false, 'cache.stale': false });\n span?.end();\n telemetry?.incrementCounter('omote.cache.hits', 1, {});\n return { data: cached.data, stale: false };\n }\n } catch {\n span?.endWithError(new Error('Cache getWithValidation failed'));\n return { data: null, stale: false };\n }\n }\n\n /**\n * Store a model in cache\n *\n * After storing, triggers LRU eviction if cache exceeds size limit.\n *\n * @param url - The cache key (use getCacheKey() for versioned keys)\n * @param data - The model data\n * @param etag - Optional ETag for staleness validation\n * @param version - Optional version string for metadata\n */\n async set(url: string, data: ArrayBuffer, etag?: string, version?: string): Promise<void> {\n const telemetry = getTelemetry();\n const span = telemetry?.startSpan('ModelCache.set', {\n 'cache.url': url,\n 'cache.size_bytes': data.byteLength,\n ...(version && { 'cache.version': version }),\n });\n try {\n // Check quota before caching (best effort, don't block write)\n this.checkQuota().catch((err) => {\n console.warn('[ModelCache] Quota check failed:', err);\n });\n\n const db = await this.getDB();\n await new Promise<void>((resolve, reject) => {\n const tx = db.transaction(STORE_NAME, 'readwrite');\n const store = tx.objectStore(STORE_NAME);\n const now = Date.now();\n const cached: CachedModel = {\n url,\n data,\n size: data.byteLength,\n cachedAt: now,\n lastAccessedAt: now,\n etag,\n version,\n };\n const request = store.put(cached);\n request.onsuccess = () => {\n span?.end();\n resolve();\n };\n request.onerror = () => {\n span?.endWithError(request.error || new Error('Cache set failed'));\n reject(request.error);\n };\n });\n\n // Trigger LRU cleanup after write (don't block)\n this.enforceLimit().catch((err) => {\n console.warn('[ModelCache] Failed to enforce limit after set:', err);\n });\n } catch (err) {\n console.warn('[ModelCache] Failed to cache model:', err);\n span?.endWithError(err instanceof Error ? err : new Error(String(err)));\n }\n }\n\n /**\n * Check storage quota and trigger warnings/cleanup as needed\n *\n * - Logs warning if quota > 90% used\n * - Triggers LRU cleanup if quota > 95% used\n * - Calls onQuotaWarning callback if configured\n */\n private async checkQuota(): Promise<void> {\n const quota = await this.getQuotaInfo();\n if (!quota) {\n return; // API unavailable\n }\n\n const config = globalCacheConfig;\n const telemetry = getTelemetry();\n\n if (quota.percentUsed > 90) {\n console.warn(`[ModelCache] Storage quota ${quota.percentUsed.toFixed(1)}% used (${formatBytes(quota.usedBytes)} / ${formatBytes(quota.quotaBytes)})`);\n\n // Emit telemetry counter\n telemetry?.incrementCounter('omote.cache.quota_warning', 1, {\n percent_used: String(Math.round(quota.percentUsed)),\n });\n\n // Call user callback if configured\n if (config.onQuotaWarning) {\n try {\n config.onQuotaWarning(quota);\n } catch (err) {\n console.warn('[ModelCache] onQuotaWarning callback error:', err);\n }\n }\n }\n\n if (quota.percentUsed > 95) {\n console.warn('[ModelCache] Storage quota critical (>95%), triggering LRU cleanup');\n // Free at least 10% of cache to make room\n const bytesToFree = Math.max(quota.cacheBytes * 0.1, 10 * 1024 * 1024);\n await this.evictOldest(bytesToFree);\n }\n }\n\n /**\n * Delete a cached model\n */\n async delete(url: string): Promise<void> {\n try {\n const db = await this.getDB();\n return new Promise((resolve) => {\n const tx = db.transaction(STORE_NAME, 'readwrite');\n const store = tx.objectStore(STORE_NAME);\n store.delete(url);\n tx.oncomplete = () => resolve();\n });\n } catch {\n // Ignore errors\n }\n }\n\n /**\n * Clear all cached models\n */\n async clear(): Promise<void> {\n try {\n const db = await this.getDB();\n return new Promise((resolve) => {\n const tx = db.transaction(STORE_NAME, 'readwrite');\n const store = tx.objectStore(STORE_NAME);\n store.clear();\n tx.oncomplete = () => resolve();\n });\n } catch {\n // Ignore errors\n }\n }\n\n /**\n * Get cache statistics\n */\n async getStats(): Promise<CacheStats> {\n try {\n const db = await this.getDB();\n return new Promise((resolve) => {\n const tx = db.transaction(STORE_NAME, 'readonly');\n const store = tx.objectStore(STORE_NAME);\n const request = store.getAll();\n request.onsuccess = () => {\n const models = (request.result as CachedModel[]) || [];\n resolve({\n totalSize: models.reduce((sum, m) => sum + m.size, 0),\n modelCount: models.length,\n models: models.map((m) => ({\n url: m.url,\n size: m.size,\n cachedAt: new Date(m.cachedAt),\n })),\n });\n };\n request.onerror = () => resolve({ totalSize: 0, modelCount: 0, models: [] });\n });\n } catch {\n return { totalSize: 0, modelCount: 0, models: [] };\n }\n }\n\n /**\n * Enforce cache size limit by evicting oldest entries (LRU)\n *\n * Called automatically after each set() operation.\n * Can also be called manually to trigger cleanup.\n */\n async enforceLimit(): Promise<void> {\n const config = globalCacheConfig;\n const maxSize = config.maxSizeBytes ?? DEFAULT_MAX_SIZE_BYTES;\n\n const stats = await this.getStats();\n if (stats.totalSize <= maxSize) {\n return; // Under limit, nothing to do\n }\n\n const bytesToFree = stats.totalSize - maxSize;\n const evictedUrls = await this.evictOldest(bytesToFree);\n\n if (evictedUrls.length > 0) {\n console.log(`[ModelCache] LRU eviction: removed ${evictedUrls.length} models to free ${formatBytes(bytesToFree)}`);\n }\n }\n\n /**\n * Evict oldest entries (by lastAccessedAt) to free space\n *\n * @param bytesToFree - Minimum bytes to free\n * @returns List of evicted URLs\n *\n * @example\n * ```typescript\n * const cache = getModelCache();\n * const evicted = await cache.evictOldest(100 * 1024 * 1024); // Free 100MB\n * console.log('Evicted:', evicted);\n * ```\n */\n async evictOldest(bytesToFree: number): Promise<string[]> {\n const telemetry = getTelemetry();\n const span = telemetry?.startSpan('ModelCache.evictOldest', {\n 'eviction.bytes_requested': bytesToFree,\n });\n\n try {\n const db = await this.getDB();\n\n // Get all models sorted by lastAccessedAt (oldest first)\n const models = await new Promise<CachedModel[]>((resolve) => {\n const tx = db.transaction(STORE_NAME, 'readonly');\n const store = tx.objectStore(STORE_NAME);\n const request = store.getAll();\n request.onsuccess = () => {\n const all = (request.result as CachedModel[]) || [];\n // Sort by lastAccessedAt ascending (oldest first)\n all.sort((a, b) => (a.lastAccessedAt || a.cachedAt || 0) - (b.lastAccessedAt || b.cachedAt || 0));\n resolve(all);\n };\n request.onerror = () => resolve([]);\n });\n\n const evictedUrls: string[] = [];\n let freedBytes = 0;\n\n // Evict models until we've freed enough space\n for (const model of models) {\n if (freedBytes >= bytesToFree) {\n break;\n }\n\n await this.delete(model.url);\n evictedUrls.push(model.url);\n freedBytes += model.size;\n\n console.log(`[ModelCache] Evicted: ${model.url} (${formatBytes(model.size)})`);\n }\n\n span?.setAttributes({\n 'eviction.bytes_freed': freedBytes,\n 'eviction.models_evicted': evictedUrls.length,\n });\n span?.end();\n\n // Emit telemetry counter\n if (freedBytes > 0) {\n telemetry?.incrementCounter('omote.cache.eviction', evictedUrls.length, {\n bytes_freed: String(freedBytes),\n });\n }\n\n return evictedUrls;\n } catch (err) {\n span?.endWithError(err instanceof Error ? err : new Error(String(err)));\n console.warn('[ModelCache] Eviction failed:', err);\n return [];\n }\n }\n\n /**\n * Get storage quota information\n *\n * Uses navigator.storage.estimate() to get quota details.\n * Returns null if the API is unavailable.\n *\n * @returns Quota info or null if unavailable\n *\n * @example\n * ```typescript\n * const cache = getModelCache();\n * const quota = await cache.getQuotaInfo();\n * if (quota) {\n * console.log(`Using ${quota.percentUsed.toFixed(1)}% of quota`);\n * }\n * ```\n */\n async getQuotaInfo(): Promise<QuotaInfo | null> {\n if (!navigator?.storage?.estimate) {\n return null;\n }\n\n try {\n const estimate = await navigator.storage.estimate();\n const usedBytes = estimate.usage || 0;\n const quotaBytes = estimate.quota || 0;\n const percentUsed = quotaBytes > 0 ? (usedBytes / quotaBytes) * 100 : 0;\n\n const stats = await this.getStats();\n\n return {\n usedBytes,\n quotaBytes,\n percentUsed,\n cacheBytes: stats.totalSize,\n };\n } catch {\n return null;\n }\n }\n}\n\n// Singleton instance\nlet cacheInstance: ModelCache | null = null;\n\n/**\n * Get the global ModelCache instance\n */\nexport function getModelCache(): ModelCache {\n if (!cacheInstance) {\n cacheInstance = new ModelCache();\n }\n return cacheInstance;\n}\n\n// Max size for IndexedDB caching\n// When storing ArrayBuffer in IndexedDB, browser does structured clone which\n// temporarily doubles memory usage. To avoid STATUS_BREAKPOINT crashes:\n// - Files < 500MB: Cache as ArrayBuffer (safe, fast retrieval)\n// - Files >= 500MB: Skip IndexedDB, rely on HTTP cache\n// See: https://bugs.chromium.org/p/chromium/issues/detail?id=170845\nconst MAX_CACHE_SIZE_BYTES = 500 * 1024 * 1024;\n\n/**\n * Options for fetchWithCache\n */\nexport interface FetchWithCacheOptions {\n /** Optional version string for versioned caching */\n version?: string;\n /** If true, validates cached data against server ETag and refetches if stale */\n validateStale?: boolean;\n /** Progress callback during download */\n onProgress?: (loaded: number, total: number) => void;\n}\n\n/**\n * Fetch a model with caching\n * Uses IndexedDB cache with network fallback\n * Files larger than 500MB are not cached to IndexedDB to avoid memory pressure\n * (structured clone during IndexedDB write temporarily doubles memory usage)\n *\n * @param url - The URL to fetch\n * @param onProgress - Optional progress callback (legacy signature)\n * @returns The fetched ArrayBuffer\n *\n * @example\n * ```typescript\n * // Simple usage (backwards compatible)\n * const data = await fetchWithCache('http://example.com/model.onnx');\n *\n * // With progress callback (backwards compatible)\n * const data = await fetchWithCache('http://example.com/model.onnx', (loaded, total) => {\n * console.log(`${loaded}/${total} bytes`);\n * });\n *\n * // With options (new API)\n * const data = await fetchWithCache('http://example.com/model.onnx', {\n * version: '1.0.0',\n * validateStale: true,\n * onProgress: (loaded, total) => console.log(`${loaded}/${total}`)\n * });\n * ```\n */\nexport async function fetchWithCache(\n url: string,\n optionsOrProgress?: FetchWithCacheOptions | ((loaded: number, total: number) => void)\n): Promise<ArrayBuffer> {\n // Normalize arguments - support both old and new signatures\n let options: FetchWithCacheOptions = {};\n if (typeof optionsOrProgress === 'function') {\n // Legacy signature: fetchWithCache(url, onProgress)\n options = { onProgress: optionsOrProgress };\n } else if (optionsOrProgress) {\n // New signature: fetchWithCache(url, options)\n options = optionsOrProgress;\n }\n\n const { version, validateStale = false, onProgress } = options;\n\n const cache = getModelCache();\n const cacheKey = version ? getCacheKey(url, version) : url;\n const telemetry = getTelemetry();\n const span = telemetry?.startSpan('fetchWithCache', {\n 'fetch.url': url,\n ...(version && { 'fetch.version': version }),\n 'fetch.validate_stale': validateStale,\n });\n\n // Check cache with optional staleness validation\n if (validateStale) {\n const validation = await cache.getWithValidation(cacheKey, url);\n\n if (validation.data && !validation.stale) {\n console.log(`[ModelCache] Cache hit (validated): ${url} (${(validation.data.byteLength / 1024 / 1024).toFixed(1)}MB)`);\n onProgress?.(validation.data.byteLength, validation.data.byteLength);\n span?.setAttributes({\n 'fetch.cache_hit': true,\n 'fetch.cache_validated': true,\n 'fetch.cache_stale': false,\n 'fetch.size_bytes': validation.data.byteLength,\n });\n span?.end();\n return validation.data;\n }\n\n if (validation.stale) {\n console.log(`[ModelCache] Cache stale, refetching: ${url}`);\n span?.setAttributes({\n 'fetch.cache_hit': true,\n 'fetch.cache_validated': true,\n 'fetch.cache_stale': true,\n });\n // Continue to fetch fresh data\n }\n // If data is null, continue to fetch\n } else {\n // Simple cache check without validation (backwards compatible behavior)\n const cached = await cache.get(cacheKey);\n if (cached) {\n console.log(`[ModelCache] Cache hit: ${url} (${(cached.byteLength / 1024 / 1024).toFixed(1)}MB)`);\n onProgress?.(cached.byteLength, cached.byteLength);\n span?.setAttributes({\n 'fetch.cache_hit': true,\n 'fetch.size_bytes': cached.byteLength,\n });\n span?.end();\n return cached;\n }\n }\n\n span?.setAttributes({ 'fetch.cache_hit': false });\n console.log(`[ModelCache] Cache miss, fetching: ${url}`);\n\n try {\n // Fetch with progress\n const response = await fetch(url);\n if (!response.ok) {\n throw new Error(`Failed to fetch ${url}: ${response.status}`);\n }\n\n const contentLength = response.headers.get('content-length');\n const total = contentLength ? parseInt(contentLength, 10) : 0;\n const etag = response.headers.get('etag') ?? undefined;\n\n // Check if file is too large for IndexedDB (avoid memory pressure during structured clone)\n const tooLargeForCache = total > MAX_CACHE_SIZE_BYTES;\n if (tooLargeForCache) {\n console.log(`[ModelCache] File too large for IndexedDB (${(total / 1024 / 1024).toFixed(0)}MB > 500MB), using HTTP cache only`);\n }\n\n if (!response.body) {\n const data = await response.arrayBuffer();\n if (!tooLargeForCache) {\n await cache.set(cacheKey, data, etag, version);\n }\n span?.setAttributes({\n 'fetch.size_bytes': data.byteLength,\n 'fetch.cached_to_indexeddb': !tooLargeForCache,\n });\n span?.end();\n return data;\n }\n\n // Stream with progress\n const reader = response.body.getReader();\n const chunks: Uint8Array[] = [];\n let loaded = 0;\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n chunks.push(value);\n loaded += value.length;\n onProgress?.(loaded, total || loaded);\n }\n\n // Combine chunks\n const data = new Uint8Array(loaded);\n let offset = 0;\n for (const chunk of chunks) {\n data.set(chunk, offset);\n offset += chunk.length;\n }\n\n const buffer = data.buffer;\n\n // Cache for next time (if not too large)\n if (!tooLargeForCache) {\n await cache.set(cacheKey, buffer, etag, version);\n console.log(`[ModelCache] Cached: ${url} (${(buffer.byteLength / 1024 / 1024).toFixed(1)}MB)`);\n }\n\n span?.setAttributes({\n 'fetch.size_bytes': buffer.byteLength,\n 'fetch.cached_to_indexeddb': !tooLargeForCache,\n });\n span?.end();\n\n return buffer;\n } catch (error) {\n span?.endWithError(error instanceof Error ? error : new Error(String(error)));\n throw error;\n }\n}\n\n/**\n * Preload models into cache without creating sessions\n */\nexport async function preloadModels(\n urls: string[],\n onProgress?: (current: number, total: number, url: string) => void\n): Promise<void> {\n const cache = getModelCache();\n\n for (let i = 0; i < urls.length; i++) {\n const url = urls[i];\n onProgress?.(i, urls.length, url);\n\n if (await cache.has(url)) {\n console.log(`[ModelCache] Already cached: ${url}`);\n continue;\n }\n\n await fetchWithCache(url);\n }\n\n onProgress?.(urls.length, urls.length, 'done');\n}\n\n/**\n * Format bytes as human readable string\n */\nexport function formatBytes(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n if (bytes < 1024 * 1024 * 1024) return `${(bytes / 1024 / 1024).toFixed(1)} MB`;\n return `${(bytes / 1024 / 1024 / 1024).toFixed(1)} GB`;\n}\n","/**\n * Logging types for Omote SDK\n *\n * 6-level logging system with structured output:\n * - error: Critical failures that prevent operation\n * - warn: Recoverable issues or degraded performance\n * - info: Key lifecycle events (model loaded, inference complete)\n * - debug: Detailed operational info for development\n * - trace: Fine-grained tracing for performance analysis\n * - verbose: Extremely detailed output (tensor shapes, intermediate values)\n */\n\nexport type LogLevel = 'error' | 'warn' | 'info' | 'debug' | 'trace' | 'verbose';\n\n/**\n * Numeric priority for log levels (lower = more severe)\n */\nexport const LOG_LEVEL_PRIORITY: Record<LogLevel, number> = {\n error: 0,\n warn: 1,\n info: 2,\n debug: 3,\n trace: 4,\n verbose: 5,\n};\n\n/**\n * Structured log entry\n */\nexport interface LogEntry {\n /** Unix timestamp in milliseconds */\n timestamp: number;\n /** Log level */\n level: LogLevel;\n /** Module name (e.g., 'LocalInference', 'ModelCache') */\n module: string;\n /** Human-readable message */\n message: string;\n /** Optional structured data */\n data?: Record<string, unknown>;\n /** Optional error object */\n error?: Error;\n}\n\n/**\n * Log output sink interface\n */\nexport interface LogSink {\n (entry: LogEntry): void;\n}\n\n/**\n * Log formatter interface\n */\nexport interface LogFormatter {\n (entry: LogEntry): string;\n}\n\n/**\n * Global logging configuration\n */\nexport interface LoggingConfig {\n /** Minimum log level to output (default: 'info') */\n level: LogLevel;\n /** Enable/disable logging globally (default: true) */\n enabled: boolean;\n /** Output format: 'json' for structured, 'pretty' for human-readable */\n format: 'json' | 'pretty';\n /** Custom output sink (default: console) */\n sink?: LogSink;\n /** Include timestamps in output (default: true) */\n timestamps?: boolean;\n /** Include module name in output (default: true) */\n includeModule?: boolean;\n}\n\n/**\n * Logger interface for module-specific logging\n */\nexport interface ILogger {\n error(message: string, data?: Record<string, unknown>): void;\n warn(message: string, data?: Record<string, unknown>): void;\n info(message: string, data?: Record<string, unknown>): void;\n debug(message: string, data?: Record<string, unknown>): void;\n trace(message: string, data?: Record<string, unknown>): void;\n verbose(message: string, data?: Record<string, unknown>): void;\n\n /** Create a child logger with a sub-module name */\n child(subModule: string): ILogger;\n\n /** Get the module name for this logger */\n readonly module: string;\n}\n\n/**\n * Default configuration\n */\nexport const DEFAULT_LOGGING_CONFIG: LoggingConfig = {\n level: 'info',\n enabled: true,\n format: 'pretty',\n timestamps: true,\n includeModule: true,\n};\n","/**\n * Log formatters for different output formats\n */\n\nimport type { LogEntry, LogFormatter, LogLevel } from './types';\n\n/**\n * ANSI color codes for terminal output\n */\nconst COLORS = {\n reset: '\\x1b[0m',\n red: '\\x1b[31m',\n yellow: '\\x1b[33m',\n blue: '\\x1b[34m',\n cyan: '\\x1b[36m',\n gray: '\\x1b[90m',\n white: '\\x1b[37m',\n magenta: '\\x1b[35m',\n};\n\n/**\n * Level-specific colors\n */\nconst LEVEL_COLORS: Record<LogLevel, string> = {\n error: COLORS.red,\n warn: COLORS.yellow,\n info: COLORS.blue,\n debug: COLORS.cyan,\n trace: COLORS.magenta,\n verbose: COLORS.gray,\n};\n\n/**\n * Level display names (padded for alignment)\n */\nconst LEVEL_NAMES: Record<LogLevel, string> = {\n error: 'ERROR ',\n warn: 'WARN ',\n info: 'INFO ',\n debug: 'DEBUG ',\n trace: 'TRACE ',\n verbose: 'VERBOSE',\n};\n\n/**\n * Check if we're in a browser environment\n */\nconst isBrowser = typeof window !== 'undefined';\n\n/**\n * Format timestamp as ISO string or relative time\n */\nfunction formatTimestamp(timestamp: number): string {\n const date = new Date(timestamp);\n return date.toISOString().substring(11, 23); // HH:mm:ss.SSS\n}\n\n/**\n * Safely serialize data to JSON, handling circular references\n */\nfunction safeStringify(data: unknown): string {\n const seen = new WeakSet();\n return JSON.stringify(data, (key, value) => {\n if (typeof value === 'object' && value !== null) {\n if (seen.has(value)) {\n return '[Circular]';\n }\n seen.add(value);\n }\n // Handle special types\n if (value instanceof Error) {\n return {\n name: value.name,\n message: value.message,\n stack: value.stack,\n };\n }\n if (value instanceof Float32Array || value instanceof Int16Array) {\n return `${value.constructor.name}(${value.length})`;\n }\n if (ArrayBuffer.isView(value)) {\n return `${value.constructor.name}(${value.byteLength})`;\n }\n return value;\n });\n}\n\n/**\n * JSON formatter - structured output for machine parsing\n */\nexport const jsonFormatter: LogFormatter = (entry: LogEntry): string => {\n const output: Record<string, unknown> = {\n timestamp: entry.timestamp,\n level: entry.level,\n module: entry.module,\n message: entry.message,\n };\n\n if (entry.data && Object.keys(entry.data).length > 0) {\n output.data = entry.data;\n }\n\n if (entry.error) {\n output.error = {\n name: entry.error.name,\n message: entry.error.message,\n stack: entry.error.stack,\n };\n }\n\n return safeStringify(output);\n};\n\n/**\n * Pretty formatter - human-readable output with colors\n */\nexport const prettyFormatter: LogFormatter = (entry: LogEntry): string => {\n const time = formatTimestamp(entry.timestamp);\n const level = LEVEL_NAMES[entry.level];\n const module = entry.module;\n const message = entry.message;\n\n // Build the base message\n let output: string;\n\n if (isBrowser) {\n // Browser: no ANSI colors, use plain text\n output = `${time} ${level} [${module}] ${message}`;\n } else {\n // Terminal: use ANSI colors\n const color = LEVEL_COLORS[entry.level];\n output = `${COLORS.gray}${time}${COLORS.reset} ${color}${level}${COLORS.reset} ${COLORS.cyan}[${module}]${COLORS.reset} ${message}`;\n }\n\n // Append data if present\n if (entry.data && Object.keys(entry.data).length > 0) {\n const dataStr = safeStringify(entry.data);\n // For pretty format, indent multi-line data\n if (dataStr.length > 80) {\n output += '\\n ' + JSON.stringify(entry.data, null, 2).replace(/\\n/g, '\\n ');\n } else {\n output += ' ' + dataStr;\n }\n }\n\n // Append error if present\n if (entry.error) {\n output += `\\n ${entry.error.name}: ${entry.error.message}`;\n if (entry.error.stack) {\n const stackLines = entry.error.stack.split('\\n').slice(1, 4);\n output += '\\n ' + stackLines.join('\\n ');\n }\n }\n\n return output;\n};\n\n/**\n * Get formatter by name\n */\nexport function getFormatter(format: 'json' | 'pretty'): LogFormatter {\n return format === 'json' ? jsonFormatter : prettyFormatter;\n}\n\n/**\n * Create browser console arguments for styled output\n * Returns [formatString, ...styleArgs] for console.log\n */\nexport function createBrowserConsoleArgs(entry: LogEntry): [string, ...string[]] {\n const time = formatTimestamp(entry.timestamp);\n const level = entry.level.toUpperCase().padEnd(7);\n const module = entry.module;\n const message = entry.message;\n\n // CSS styles for browser console\n const styles = {\n time: 'color: gray;',\n error: 'color: red; font-weight: bold;',\n warn: 'color: orange; font-weight: bold;',\n info: 'color: blue;',\n debug: 'color: cyan;',\n trace: 'color: magenta;',\n verbose: 'color: gray;',\n module: 'color: teal; font-weight: bold;',\n message: 'color: inherit;',\n };\n\n let formatStr = '%c%s %c%s %c[%s]%c %s';\n const args: string[] = [\n styles.time,\n time,\n styles[entry.level],\n level,\n styles.module,\n module,\n styles.message,\n message,\n ];\n\n // Append data\n if (entry.data && Object.keys(entry.data).length > 0) {\n formatStr += ' %o';\n args.push(entry.data as unknown as string);\n }\n\n return [formatStr, ...args];\n}\n","/**\n * Omote SDK Logger\n *\n * Unified logging system with:\n * - 6 log levels (error, warn, info, debug, trace, verbose)\n * - Structured JSON output for machine parsing\n * - Pretty output for human readability\n * - Module-based child loggers\n * - Runtime configuration\n * - Browser and Node.js compatible\n */\n\nimport type {\n LogLevel,\n LogEntry,\n LoggingConfig,\n LogSink,\n ILogger,\n} from './types';\nimport { LOG_LEVEL_PRIORITY, DEFAULT_LOGGING_CONFIG } from './types';\nimport { getFormatter, createBrowserConsoleArgs } from './formatters';\n\n/**\n * Check if running in browser\n */\nconst isBrowser = typeof window !== 'undefined';\n\n/**\n * Global logging configuration\n */\nlet globalConfig: LoggingConfig = { ...DEFAULT_LOGGING_CONFIG };\n\n/**\n * Configure global logging settings\n */\nexport function configureLogging(config: Partial<LoggingConfig>): void {\n globalConfig = { ...globalConfig, ...config };\n}\n\n/**\n * Get current logging configuration\n */\nexport function getLoggingConfig(): LoggingConfig {\n return { ...globalConfig };\n}\n\n/**\n * Reset logging configuration to defaults\n */\nexport function resetLoggingConfig(): void {\n globalConfig = { ...DEFAULT_LOGGING_CONFIG };\n}\n\n/**\n * Set log level at runtime\n */\nexport function setLogLevel(level: LogLevel): void {\n globalConfig.level = level;\n}\n\n/**\n * Enable or disable logging\n */\nexport function setLoggingEnabled(enabled: boolean): void {\n globalConfig.enabled = enabled;\n}\n\n/**\n * Default console sink with browser-optimized output\n */\nconst consoleSink: LogSink = (entry: LogEntry): void => {\n const consoleMethod = entry.level === 'error' ? 'error'\n : entry.level === 'warn' ? 'warn'\n : 'log';\n\n if (globalConfig.format === 'pretty' && isBrowser) {\n // Use styled console output in browser\n const args = createBrowserConsoleArgs(entry);\n (console as any)[consoleMethod](...args);\n } else {\n // Use formatter for terminal or JSON output\n const formatter = getFormatter(globalConfig.format);\n const formatted = formatter(entry);\n (console as any)[consoleMethod](formatted);\n }\n};\n\n/**\n * Get the active sink (custom or default console)\n */\nfunction getActiveSink(): LogSink {\n return globalConfig.sink || consoleSink;\n}\n\n/**\n * Check if a log level should be output given current config\n */\nfunction shouldLog(level: LogLevel): boolean {\n if (!globalConfig.enabled) return false;\n return LOG_LEVEL_PRIORITY[level] <= LOG_LEVEL_PRIORITY[globalConfig.level];\n}\n\n/**\n * Logger implementation\n */\nclass Logger implements ILogger {\n readonly module: string;\n\n constructor(module: string) {\n this.module = module;\n }\n\n private log(level: LogLevel, message: string, data?: Record<string, unknown>): void {\n if (!shouldLog(level)) return;\n\n const entry: LogEntry = {\n timestamp: Date.now(),\n level,\n module: this.module,\n message,\n data,\n };\n\n // Extract error from data if present\n if (data?.error instanceof Error) {\n entry.error = data.error;\n // Remove from data to avoid duplication\n const { error, ...rest } = data;\n entry.data = Object.keys(rest).length > 0 ? rest : undefined;\n }\n\n getActiveSink()(entry);\n }\n\n error(message: string, data?: Record<string, unknown>): void {\n this.log('error', message, data);\n }\n\n warn(message: string, data?: Record<string, unknown>): void {\n this.log('warn', message, data);\n }\n\n info(message: string, data?: Record<string, unknown>): void {\n this.log('info', message, data);\n }\n\n debug(message: string, data?: Record<string, unknown>): void {\n this.log('debug', message, data);\n }\n\n trace(message: string, data?: Record<string, unknown>): void {\n this.log('trace', message, data);\n }\n\n verbose(message: string, data?: Record<string, unknown>): void {\n this.log('verbose', message, data);\n }\n\n child(subModule: string): ILogger {\n return new Logger(`${this.module}.${subModule}`);\n }\n}\n\n/**\n * Logger cache for reusing instances\n */\nconst loggerCache = new Map<string, Logger>();\n\n/**\n * Create a logger for a specific module\n *\n * @param module - Module name (e.g., 'LocalInference', 'ModelCache')\n * @returns Logger instance\n *\n * @example\n * ```typescript\n * const logger = createLogger('LocalInference');\n * logger.info('Model loaded', { backend: 'webgpu', loadTimeMs: 1234 });\n * ```\n */\nexport function createLogger(module: string): ILogger {\n let logger = loggerCache.get(module);\n if (!logger) {\n logger = new Logger(module);\n loggerCache.set(module, logger);\n }\n return logger;\n}\n\n/**\n * Clear logger cache (useful for testing)\n */\nexport function clearLoggerCache(): void {\n loggerCache.clear();\n}\n\n/**\n * No-op logger for when logging is completely disabled\n */\nexport const noopLogger: ILogger = {\n module: 'noop',\n error: () => {},\n warn: () => {},\n info: () => {},\n debug: () => {},\n trace: () => {},\n verbose: () => {},\n child: () => noopLogger,\n};\n\n/**\n * Get a no-op logger (for production builds that tree-shake logging)\n */\nexport function getNoopLogger(): ILogger {\n return noopLogger;\n}\n","/**\r\n * Runtime detection utilities for platform-specific inference configuration\r\n *\r\n * These utilities help determine the optimal backend (WebGPU vs WASM) based on\r\n * the current platform's capabilities and known limitations.\r\n *\r\n * Key considerations:\r\n * - iOS Safari: WebGPU crashes due to JSEP bugs (GitHub #22776, #26827)\r\n * - Android Chrome: WebGPU works well (Chrome 121+)\r\n * - Desktop: WebGPU preferred for performance\r\n *\r\n * @module utils/runtime\r\n */\r\n\r\n/**\r\n * Supported inference backends\r\n */\r\nexport type RuntimeBackend = 'webgpu' | 'wasm';\r\n\r\n/**\r\n * User-configurable backend preference\r\n */\r\nexport type BackendPreference =\r\n | 'auto' // iOS→WASM, else→WebGPU with fallback\r\n | 'webgpu' // Prefer WebGPU, fallback to WASM on error\r\n | 'wasm' // Prefer WASM, no WebGPU attempt\r\n | 'webgpu-only' // Force WebGPU, throw on failure (for debugging)\r\n | 'wasm-only'; // Force WASM, never load WebGPU bundle (smallest bundle)\r\n\r\n/**\r\n * Detect iOS Safari browser\r\n *\r\n * iOS Safari has severe WebGPU issues:\r\n * - JSEP compilation bugs cause OOM during session creation\r\n * - Threading bugs require numThreads=1\r\n * - Proxy mode triggers memory leaks\r\n *\r\n * @returns true if running in iOS Safari\r\n */\r\nexport function isIOSSafari(): boolean {\r\n if (typeof navigator === 'undefined') return false;\r\n const ua = navigator.userAgent.toLowerCase();\r\n // Must be on iOS device AND using Safari (not Chrome/CriOS/Firefox/Edge)\r\n return /iphone|ipad|ipod/.test(ua) && /safari/.test(ua) && !/chrome|crios|fxios|chromium|edg/.test(ua);\r\n}\r\n\r\n/**\r\n * Detect any iOS device (regardless of browser)\r\n *\r\n * On iOS, all browsers use WebKit, so Chrome/Firefox on iOS\r\n * have the same limitations as Safari.\r\n *\r\n * @returns true if running on any iOS device\r\n */\r\nexport function isIOS(): boolean {\r\n if (typeof navigator === 'undefined') return false;\r\n const ua = navigator.userAgent.toLowerCase();\r\n return /iphone|ipad|ipod/.test(ua);\r\n}\r\n\r\n/**\r\n * Detect Android device\r\n *\r\n * Android Chrome 121+ has good WebGPU support with Qualcomm/ARM GPUs.\r\n *\r\n * @returns true if running on Android\r\n */\r\nexport function isAndroid(): boolean {\r\n if (typeof navigator === 'undefined') return false;\r\n return /android/i.test(navigator.userAgent);\r\n}\r\n\r\n/**\r\n * Detect any mobile device (iOS or Android)\r\n *\r\n * Mobile devices have different performance characteristics:\r\n * - Lower memory limits\r\n * - Thermal throttling\r\n * - Different GPU architectures\r\n *\r\n * @returns true if running on mobile\r\n */\r\nexport function isMobile(): boolean {\r\n return isIOS() || isAndroid();\r\n}\r\n\r\n/**\r\n * Check if WebGPU API is available in the browser\r\n *\r\n * Note: This only checks if the API exists, not if it works reliably.\r\n * iOS has navigator.gpu but ONNX Runtime's WebGPU backend crashes.\r\n *\r\n * @returns true if navigator.gpu exists\r\n */\r\nexport function hasWebGPUApi(): boolean {\r\n if (typeof navigator === 'undefined') return false;\r\n return 'gpu' in navigator && navigator.gpu !== undefined;\r\n}\r\n\r\n/**\r\n * Get the recommended backend for the current platform\r\n *\r\n * Decision tree:\r\n * 1. iOS (any browser): Force WASM (WebGPU crashes)\r\n * 2. Android: WebGPU preferred (works in Chrome 121+)\r\n * 3. Desktop: WebGPU preferred (best performance)\r\n *\r\n * @returns 'wasm' for iOS, 'webgpu' for everything else\r\n */\r\nexport function getRecommendedBackend(): RuntimeBackend {\r\n // Safari (all platforms): Always WASM - WebGPU crashes due to JSEP bugs\r\n // iOS: All browsers use WebKit, so all have the same issue\r\n // macOS Safari: Same multithreaded JSEP build bug\r\n if (isSafari() || isIOS()) {\r\n return 'wasm';\r\n }\r\n\r\n // Android/Desktop (non-Safari): WebGPU preferred\r\n return 'webgpu';\r\n}\r\n\r\n/**\r\n * Resolve user preference to actual backend\r\n *\r\n * @param preference User's backend preference\r\n * @param webgpuAvailable Whether WebGPU is available and working\r\n * @returns The backend to use\r\n */\r\nexport function resolveBackend(\r\n preference: BackendPreference,\r\n webgpuAvailable: boolean\r\n): RuntimeBackend {\r\n switch (preference) {\r\n case 'wasm-only':\r\n return 'wasm';\r\n\r\n case 'webgpu-only':\r\n if (!webgpuAvailable) {\r\n throw new Error(\r\n 'WebGPU requested but not available. Use \"webgpu\" or \"auto\" for fallback.'\r\n );\r\n }\r\n return 'webgpu';\r\n\r\n case 'wasm':\r\n return 'wasm';\r\n\r\n case 'webgpu':\r\n return webgpuAvailable ? 'webgpu' : 'wasm';\r\n\r\n case 'auto':\r\n default:\r\n // Auto: Use platform recommendation, with WebGPU availability check\r\n const recommended = getRecommendedBackend();\r\n if (recommended === 'webgpu' && !webgpuAvailable) {\r\n return 'wasm';\r\n }\r\n return recommended;\r\n }\r\n}\r\n\r\n/**\r\n * Get optimal WASM thread count for current platform\r\n *\r\n * @returns Recommended number of WASM threads\r\n */\r\nexport function getOptimalWasmThreads(): number {\r\n if (isIOS()) {\r\n // iOS: Must be 1 to avoid shared memory bugs (GitHub #22086)\r\n return 1;\r\n }\r\n\r\n if (isAndroid()) {\r\n // Android: Conservative threading (2 threads)\r\n return 2;\r\n }\r\n\r\n // Desktop: Full threading (4 threads)\r\n return 4;\r\n}\r\n\r\n/**\r\n * Check if WASM proxy mode should be enabled\r\n *\r\n * Proxy mode offloads inference to a Web Worker, but has issues:\r\n * - iOS: Triggers Safari 26 JSEP memory leak\r\n * - Mobile: Generally unstable\r\n *\r\n * @returns true if proxy mode is safe to enable\r\n */\r\nexport function shouldEnableWasmProxy(): boolean {\r\n // Always disable proxy. When proxy=true, WASM initializes in a Web Worker.\r\n // The WebGPU EP needs WASM on the main thread for model parsing. If a\r\n // consumer Vite-aliases all ORT imports to the same module, proxy=true\r\n // on one session causes \"WebAssembly is not initialized yet\" for WebGPU.\r\n return false;\r\n}\r\n\r\n/**\r\n * Detect Safari browser on any platform (macOS + iOS)\r\n *\r\n * Safari WebKit has bugs with ONNX Runtime's WebGPU multithreaded JSEP build\r\n * that crash session creation. Both iOS and macOS Safari are affected.\r\n *\r\n * @returns true if running in Safari on any platform\r\n */\r\nexport function isSafari(): boolean {\r\n if (typeof navigator === 'undefined') return false;\r\n const ua = navigator.userAgent.toLowerCase();\r\n // Safari: has \"safari\" but not Chrome, Chromium, CriOS, FxiOS, or Edge\r\n return /safari/.test(ua) && !/chrome|crios|fxios|chromium|edg/.test(ua);\r\n}\r\n\r\n/**\r\n * Recommend using CPU-optimized lip sync model (wav2arkit_cpu)\r\n *\r\n * All iOS browsers use WebKit and have tight memory limits — the 384MB\r\n * LAM model causes silent crashes. wav2arkit_cpu uses URL pass-through\r\n * (ORT fetches the 402MB weights directly into WASM, no JS heap copy).\r\n *\r\n * macOS Safari also needs this due to ONNX Runtime JSEP/ASYNCIFY bugs\r\n * that crash WebKit's JIT compiler.\r\n *\r\n * @returns true if iOS (any browser) or Safari (any platform)\r\n */\r\nexport function shouldUseCpuLipSync(): boolean {\r\n return isSafari() || isIOS();\r\n}\r\n\r\n/**\r\n * Check if Web Speech API is available in the browser\r\n *\r\n * The Web Speech API provides native speech recognition in Safari and Chrome.\r\n * On iOS Safari, this is significantly faster than Whisper WASM.\r\n *\r\n * @returns true if SpeechRecognition API is available\r\n */\r\nexport function isSpeechRecognitionAvailable(): boolean {\r\n if (typeof window === 'undefined') return false;\r\n return 'SpeechRecognition' in window || 'webkitSpeechRecognition' in window;\r\n}\r\n\r\n/**\r\n * Recommend using native Safari Speech API over Whisper on iOS\r\n *\r\n * On iOS, Whisper ASR via WASM takes ~1.3s per inference (30% over target).\r\n * Safari's native Web Speech API is:\r\n * - Much faster (native implementation)\r\n * - Battery-efficient (no WASM overhead)\r\n * - No model download needed (saves 30-150MB)\r\n *\r\n * @returns true if on iOS or Safari with Speech API available\r\n */\r\nexport function shouldUseNativeASR(): boolean {\r\n return (isIOS() || isSafari()) && isSpeechRecognitionAvailable();\r\n}\r\n\r\n/**\r\n * Recommend using server-side LAM over client-side on iOS\r\n *\r\n * On iOS, LAM lip sync via WASM takes ~332ms per second of audio (3.3x over target).\r\n * Server-side inference with GPU can achieve ~50ms, providing:\r\n * - Real-time lip sync (under 100ms target)\r\n * - Reduced iOS device thermal/battery impact\r\n * - Better user experience\r\n *\r\n * @returns true if on iOS (should use server-side lip sync)\r\n */\r\nexport function shouldUseServerLipSync(): boolean {\r\n return isIOS();\r\n}\r\n","/**\r\n * Lazy ONNX Runtime loader with conditional WebGPU/WASM bundle loading\r\n *\r\n * This module provides a way to dynamically load the appropriate ONNX Runtime bundle\r\n * based on the platform's capabilities. This is critical for iOS support because:\r\n *\r\n * 1. iOS Safari has WebGPU API but ONNX Runtime's WebGPU backend crashes\r\n * 2. Loading the WebGPU bundle wastes bandwidth and can cause issues\r\n * 3. WASM-only bundle is smaller and more reliable on iOS\r\n *\r\n * Usage:\r\n * ```typescript\r\n * const ort = await getOnnxRuntime('wasm'); // Load WASM-only bundle\r\n * const ort = await getOnnxRuntime('webgpu'); // Load WebGPU bundle (includes WASM)\r\n * ```\r\n *\r\n * @module inference/onnxLoader\r\n */\r\n\r\n// Type-only import for TypeScript (no runtime code loaded at import time)\r\n// At runtime, we dynamically import either 'onnxruntime-web' or 'onnxruntime-web/webgpu'\r\nimport type { InferenceSession, Tensor, Env } from 'onnxruntime-common';\r\n\r\n// Type alias for the ORT module (loaded dynamically)\r\ntype OrtModule = {\r\n InferenceSession: typeof InferenceSession;\r\n Tensor: typeof Tensor;\r\n env: Env;\r\n};\r\n\r\n// Re-export session options type\r\nexport type SessionOptions = InferenceSession.SessionOptions;\r\nimport {\r\n RuntimeBackend,\r\n BackendPreference,\r\n isIOS,\r\n isIOSSafari,\r\n isSafari,\r\n isMobile,\r\n getOptimalWasmThreads,\r\n shouldEnableWasmProxy,\r\n resolveBackend,\r\n hasWebGPUApi,\r\n} from '../utils/runtime';\r\n\r\n// Re-export RuntimeBackend for consumers\r\nexport type { RuntimeBackend } from '../utils/runtime';\r\nimport { createLogger } from '../logging';\r\n\r\nconst logger = createLogger('OnnxLoader');\r\n\r\n// Cached ONNX Runtime instance\r\nlet ortInstance: OrtModule | null = null;\r\nlet loadedBackend: RuntimeBackend | null = null;\r\n\r\n// CDN path for WASM files\r\nconst WASM_CDN_PATH = 'https://cdn.jsdelivr.net/npm/onnxruntime-web@1.23.2/dist/';\r\n\r\n/**\r\n * Check if WebGPU is available and likely to work\r\n *\r\n * This is more thorough than just checking navigator.gpu exists.\r\n * It actually requests an adapter to verify the GPU is accessible.\r\n *\r\n * @returns true if WebGPU is available and working\r\n */\r\nexport async function isWebGPUAvailable(): Promise<boolean> {\r\n // iOS 18+ exposes navigator.gpu, but the ORT WebGPU bundle uses the\r\n // asyncify WASM binary (ort-wasm-simd-threaded.asyncify.wasm) which\r\n // crashes WebKit's JIT on all iOS browsers. Return false early to\r\n // prevent probing the GPU adapter (which itself consumes memory).\r\n if (isIOS()) {\r\n logger.debug('WebGPU check: disabled on iOS (asyncify bundle crashes WebKit)');\r\n return false;\r\n }\r\n\r\n if (!hasWebGPUApi()) {\r\n logger.debug('WebGPU check: navigator.gpu not available', {\r\n isSecureContext: typeof window !== 'undefined' ? (window as any).isSecureContext : 'N/A',\r\n });\r\n return false;\r\n }\r\n\r\n try {\r\n const adapter = await navigator.gpu.requestAdapter();\r\n if (!adapter) {\r\n logger.debug('WebGPU check: No adapter available');\r\n return false;\r\n }\r\n\r\n // Check for minimum required features\r\n const device = await adapter.requestDevice();\r\n if (!device) {\r\n logger.debug('WebGPU check: Could not create device');\r\n return false;\r\n }\r\n\r\n // Clean up\r\n device.destroy();\r\n\r\n logger.debug('WebGPU check: Available and working');\r\n return true;\r\n } catch (err) {\r\n logger.debug('WebGPU check: Error during availability check', { error: err });\r\n return false;\r\n }\r\n}\r\n\r\n/**\r\n * iOS WASM memory patch\r\n *\r\n * ONNX Runtime's ort-wasm-simd-threaded.wasm creates\r\n * WebAssembly.Memory({initial:256, maximum:65536, shared:true}).\r\n * iOS Safari rejects this with \"RangeError: Out of memory\" because\r\n * shared WebAssembly.Memory with a 4GB maximum exceeds iOS limits.\r\n *\r\n * This patch intercepts WebAssembly.Memory construction on iOS to:\r\n * 1. Keep `shared` flag (required by ORT v1.23.2 threaded WASM)\r\n * 2. Cap `maximum` to 32768 pages (2GB)\r\n *\r\n * The 2GB cap balances memory headroom for the 404MB wav2arkit_cpu model\r\n * (needs ~500MB WASM for weights + activations) while staying within\r\n * iOS WebKit's process limits on modern iPhones (6-8GB RAM).\r\n *\r\n * Applied once on first ORT load, stays active for the page lifetime.\r\n */\r\nlet iosWasmPatched = false;\r\n\r\nfunction applyIOSWasmMemoryPatch(): void {\r\n // Only patch on iOS Safari. iOS Chrome/Firefox (all use WebKit) may handle\r\n // the default 4GB maximum without the constructor wrapper interfering.\r\n // Safari specifically throws \"RangeError: Out of memory\" on 4GB shared memory.\r\n if (iosWasmPatched || !isIOSSafari()) return;\r\n iosWasmPatched = true;\r\n\r\n const OrigMemory = WebAssembly.Memory;\r\n const MAX_IOS_PAGES = 32768; // 32768 × 64KB = 2GB\r\n\r\n logger.info('Applying iOS WASM memory patch (max→2GB, shared preserved)');\r\n\r\n // Replace WebAssembly.Memory constructor to cap maximum pages.\r\n // ORT v1.23.2 threaded WASM binaries ALL declare shared:true in their\r\n // memory import. Setting shared:false causes a LinkError on instantiation.\r\n // Instead, keep shared:true and only cap maximum from 65536 (4GB) to\r\n // 32768 (2GB) to allow sufficient headroom for model weights + inference\r\n // activations. Log the actual descriptor for debugging.\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n (WebAssembly as any).Memory = function IOSPatchedMemory(\r\n descriptor: WebAssembly.MemoryDescriptor\r\n ): WebAssembly.Memory {\r\n const patched = { ...descriptor };\r\n\r\n if (patched.maximum !== undefined && patched.maximum > MAX_IOS_PAGES) {\r\n logger.info('iOS memory patch: capping maximum', {\r\n original: patched.maximum,\r\n capped: MAX_IOS_PAGES,\r\n shared: patched.shared,\r\n initial: patched.initial,\r\n });\r\n patched.maximum = MAX_IOS_PAGES;\r\n }\r\n\r\n return new OrigMemory(patched);\r\n };\r\n\r\n // Preserve prototype chain so instanceof checks still work\r\n (WebAssembly as any).Memory.prototype = OrigMemory.prototype;\r\n}\r\n\r\n/**\r\n * Configure WASM environment settings based on platform\r\n *\r\n * This must be called before creating any inference sessions.\r\n */\r\nfunction configureWasm(ort: OrtModule): void {\r\n // Set CDN path for WASM files\r\n ort.env.wasm.wasmPaths = WASM_CDN_PATH;\r\n\r\n // Platform-specific threading configuration\r\n const numThreads = getOptimalWasmThreads();\r\n const enableProxy = shouldEnableWasmProxy();\r\n\r\n ort.env.wasm.numThreads = numThreads;\r\n ort.env.wasm.simd = true; // SIMD always helps\r\n ort.env.wasm.proxy = enableProxy;\r\n\r\n logger.info('WASM configured', {\r\n numThreads,\r\n simd: true,\r\n proxy: enableProxy,\r\n platform: isIOS() ? 'iOS' : isMobile() ? 'Android' : 'Desktop',\r\n });\r\n}\r\n\r\n/**\r\n * Load ONNX Runtime with the specified backend\r\n *\r\n * This lazily loads the appropriate bundle:\r\n * - 'wasm': Loads onnxruntime-web (WASM-only, smaller)\r\n * - 'webgpu': Loads onnxruntime-web/webgpu (includes WebGPU + WASM fallback)\r\n *\r\n * Once loaded, the same instance is reused for all subsequent calls.\r\n * If you need to switch backends, you must reload the page.\r\n *\r\n * @param backend The backend to load ('webgpu' or 'wasm')\r\n * @returns The ONNX Runtime module\r\n */\r\nexport async function getOnnxRuntime(\r\n backend: RuntimeBackend\r\n): Promise<OrtModule> {\r\n // Return cached instance if same backend\r\n if (ortInstance && loadedBackend === backend) {\r\n return ortInstance;\r\n }\r\n\r\n // Warn if trying to switch backends (not supported without page reload)\r\n if (ortInstance && loadedBackend !== backend) {\r\n logger.warn(\r\n `ONNX Runtime already loaded with ${loadedBackend} backend. ` +\r\n `Cannot switch to ${backend}. Returning existing instance.`\r\n );\r\n return ortInstance;\r\n }\r\n\r\n logger.info(`Loading ONNX Runtime with ${backend} backend...`);\r\n\r\n // iOS: Patch WebAssembly.Memory before ORT loads its WASM binary.\r\n // Must be applied before any InferenceSession.create() call.\r\n applyIOSWasmMemoryPatch();\r\n\r\n try {\r\n if (backend === 'wasm' && (isIOS() || isSafari())) {\r\n // Safari (all platforms) + iOS (all browsers): Import via\r\n // 'onnxruntime-web/wasm' sub-path. This uses the plain WASM binaries\r\n // (ort-wasm-simd / ort-wasm-simd-threaded) WITHOUT JSEP/ASYNCIFY.\r\n //\r\n // The JSEP build (ort-wasm-simd-threaded.jsep) crashes WebKit's JIT\r\n // on iOS and macOS Safari due to ASYNCIFY code generation issues.\r\n // See: https://bugs.webkit.org/show_bug.cgi?id=262475\r\n //\r\n // With COEP/COOP headers removed for iOS (no SharedArrayBuffer),\r\n // ORT picks ort-wasm-simd.wasm (single-threaded, Case A = works).\r\n // On macOS Safari (COEP/COOP present), ORT picks\r\n // ort-wasm-simd-threaded.wasm (threaded but non-JSEP, Case B = works).\r\n const module = await import('onnxruntime-web/wasm');\r\n ortInstance = module.default || module;\r\n } else if (backend === 'wasm') {\r\n // Desktop WASM: load bare 'onnxruntime-web'\r\n const module = await import('onnxruntime-web');\r\n ortInstance = module.default || module;\r\n } else {\r\n // Load WebGPU bundle (includes WASM fallback)\r\n const module = await import('onnxruntime-web/webgpu');\r\n ortInstance = module.default || module;\r\n }\r\n\r\n loadedBackend = backend;\r\n\r\n // Configure WASM settings (applies to both bundles)\r\n configureWasm(ortInstance);\r\n\r\n logger.info(`ONNX Runtime loaded successfully`, { backend });\r\n\r\n return ortInstance;\r\n } catch (err) {\r\n logger.error(`Failed to load ONNX Runtime with ${backend} backend`, {\r\n error: err,\r\n });\r\n throw new Error(\r\n `Failed to load ONNX Runtime: ${err instanceof Error ? err.message : String(err)}`\r\n );\r\n }\r\n}\r\n\r\n/**\r\n * Get the appropriate ONNX Runtime based on user preference\r\n *\r\n * This resolves the user's preference against platform capabilities\r\n * and loads the appropriate bundle.\r\n *\r\n * @param preference User's backend preference\r\n * @returns The ONNX Runtime module and the resolved backend\r\n */\r\nexport async function getOnnxRuntimeForPreference(\r\n preference: BackendPreference = 'auto'\r\n): Promise<{ ort: OrtModule; backend: RuntimeBackend }> {\r\n // Check WebGPU availability (skip for iOS)\r\n const webgpuAvailable = await isWebGPUAvailable();\r\n\r\n // Resolve preference to actual backend\r\n const backend = resolveBackend(preference, webgpuAvailable);\r\n\r\n logger.info('Resolved backend preference', {\r\n preference,\r\n webgpuAvailable,\r\n resolvedBackend: backend,\r\n });\r\n\r\n // Load the appropriate bundle\r\n const ort = await getOnnxRuntime(backend);\r\n\r\n return { ort, backend };\r\n}\r\n\r\n/**\r\n * Get session options for creating an inference session\r\n *\r\n * This returns optimized session options based on the backend and platform.\r\n *\r\n * @param backend The backend being used\r\n * @returns Session options for InferenceSession.create()\r\n */\r\nexport function getSessionOptions(\r\n backend: RuntimeBackend\r\n): SessionOptions {\r\n if (backend === 'webgpu') {\r\n return {\r\n executionProviders: [\r\n {\r\n name: 'webgpu',\r\n preferredLayout: 'NHWC', // Reduces memory overhead for layout conversions\r\n } as const,\r\n ],\r\n graphOptimizationLevel: 'all',\r\n };\r\n }\r\n\r\n // WASM backend\r\n // iOS: Use 'basic' graph optimization to reduce peak WASM memory during\r\n // session creation. 'all' causes ORT to create temporary copies of graph\r\n // nodes for complex transformers (attention fusion, constant folding),\r\n // pushing peak memory to ~750-950MB which exceeds iOS WebKit's per-tab\r\n // process limit (~1-1.5GB). Also disable memory arenas to avoid upfront\r\n // allocation. See: https://github.com/microsoft/onnxruntime/issues/13408\r\n if (isIOS()) {\r\n return {\r\n executionProviders: ['wasm'],\r\n graphOptimizationLevel: 'basic',\r\n enableCpuMemArena: false,\r\n enableMemPattern: false,\r\n };\r\n }\r\n\r\n return {\r\n executionProviders: ['wasm'],\r\n graphOptimizationLevel: 'all',\r\n };\r\n}\r\n\r\n/**\r\n * Create an inference session with automatic fallback\r\n *\r\n * If WebGPU session creation fails, automatically falls back to WASM.\r\n *\r\n * @param modelBuffer The model data as ArrayBuffer\r\n * @param preferredBackend The preferred backend\r\n * @returns The created session and the backend used\r\n */\r\nexport async function createSessionWithFallback(\r\n modelBuffer: ArrayBuffer,\r\n preferredBackend: RuntimeBackend\r\n): Promise<{\r\n session: InferenceSession;\r\n backend: RuntimeBackend;\r\n}> {\r\n const ort = await getOnnxRuntime(preferredBackend);\r\n\r\n // Convert ArrayBuffer to Uint8Array for onnxruntime-common types\r\n const modelData = new Uint8Array(modelBuffer);\r\n\r\n if (preferredBackend === 'webgpu') {\r\n try {\r\n const options = getSessionOptions('webgpu');\r\n const session = await ort.InferenceSession.create(modelData, options);\r\n\r\n logger.info('Session created with WebGPU backend');\r\n return { session, backend: 'webgpu' };\r\n } catch (err) {\r\n logger.warn('WebGPU session creation failed, falling back to WASM', {\r\n error: err instanceof Error ? err.message : String(err),\r\n });\r\n // Fall through to WASM\r\n }\r\n }\r\n\r\n // WASM (primary or fallback)\r\n const options = getSessionOptions('wasm');\r\n const session = await ort.InferenceSession.create(modelData, options);\r\n\r\n logger.info('Session created with WASM backend');\r\n return { session, backend: 'wasm' };\r\n}\r\n\r\n/**\r\n * Get the currently loaded backend (if any)\r\n */\r\nexport function getLoadedBackend(): RuntimeBackend | null {\r\n return loadedBackend;\r\n}\r\n\r\n/**\r\n * Check if ONNX Runtime has been loaded\r\n */\r\nexport function isOnnxRuntimeLoaded(): boolean {\r\n return ortInstance !== null;\r\n}\r\n\r\n/**\r\n * Preload ONNX Runtime and compile the WASM binary early\r\n *\r\n * Call this before loading heavy resources (Three.js, VRM models) to ensure\r\n * WASM memory is allocated in a clean JS heap, reducing iOS memory pressure.\r\n * Uses the singleton pattern — subsequent model loading reuses this instance.\r\n *\r\n * @param preference Backend preference (default: 'auto')\r\n * @returns The resolved backend that was loaded\r\n */\r\nexport async function preloadOnnxRuntime(\r\n preference: BackendPreference = 'auto'\r\n): Promise<RuntimeBackend> {\r\n if (ortInstance) {\r\n logger.info('ONNX Runtime already preloaded', { backend: loadedBackend });\r\n return loadedBackend!;\r\n }\r\n\r\n logger.info('Preloading ONNX Runtime...', { preference });\r\n const { backend } = await getOnnxRuntimeForPreference(preference);\r\n logger.info('ONNX Runtime preloaded', { backend });\r\n return backend;\r\n}\r\n","/**\n * Shared blendshape constants and utilities for lip sync inference\n *\n * Contains LAM_BLENDSHAPES (canonical ordering), symmetrization, and\n * index remapping used by both Wav2Vec2Inference and Wav2ArkitCpuInference.\n *\n * This module is the single source of truth for blendshape ordering to\n * avoid circular dependencies between inference classes.\n *\n * @category Inference\n */\n\n/**\n * LAM model blendshape names in order (52 total)\n * NOTE: This is alphabetical ordering used by LAM, different from standard ARKit order\n */\nexport const LAM_BLENDSHAPES = [\n 'browDownLeft', 'browDownRight', 'browInnerUp', 'browOuterUpLeft', 'browOuterUpRight',\n 'cheekPuff', 'cheekSquintLeft', 'cheekSquintRight',\n 'eyeBlinkLeft', 'eyeBlinkRight', 'eyeLookDownLeft', 'eyeLookDownRight',\n 'eyeLookInLeft', 'eyeLookInRight', 'eyeLookOutLeft', 'eyeLookOutRight',\n 'eyeLookUpLeft', 'eyeLookUpRight', 'eyeSquintLeft', 'eyeSquintRight',\n 'eyeWideLeft', 'eyeWideRight',\n 'jawForward', 'jawLeft', 'jawOpen', 'jawRight',\n 'mouthClose', 'mouthDimpleLeft', 'mouthDimpleRight', 'mouthFrownLeft', 'mouthFrownRight',\n 'mouthFunnel', 'mouthLeft', 'mouthLowerDownLeft', 'mouthLowerDownRight',\n 'mouthPressLeft', 'mouthPressRight', 'mouthPucker', 'mouthRight',\n 'mouthRollLower', 'mouthRollUpper', 'mouthShrugLower', 'mouthShrugUpper',\n 'mouthSmileLeft', 'mouthSmileRight', 'mouthStretchLeft', 'mouthStretchRight',\n 'mouthUpperUpLeft', 'mouthUpperUpRight',\n 'noseSneerLeft', 'noseSneerRight', 'tongueOut'\n] as const;\n\n/** Alias for backwards compatibility */\nexport const ARKIT_BLENDSHAPES = LAM_BLENDSHAPES;\n\n/**\n * ARKit Left/Right symmetric pairs for blendshape symmetrization\n * From LAM official postprocessing (models/utils.py)\n */\nconst ARKIT_SYMMETRIC_PAIRS: [string, string][] = [\n ['jawLeft', 'jawRight'],\n ['mouthLeft', 'mouthRight'],\n ['mouthSmileLeft', 'mouthSmileRight'],\n ['mouthFrownLeft', 'mouthFrownRight'],\n ['mouthDimpleLeft', 'mouthDimpleRight'],\n ['mouthStretchLeft', 'mouthStretchRight'],\n ['mouthPressLeft', 'mouthPressRight'],\n ['mouthUpperUpLeft', 'mouthUpperUpRight'],\n ['mouthLowerDownLeft', 'mouthLowerDownRight'],\n ['noseSneerLeft', 'noseSneerRight'],\n ['cheekSquintLeft', 'cheekSquintRight'],\n ['browDownLeft', 'browDownRight'],\n ['browOuterUpLeft', 'browOuterUpRight'],\n ['eyeBlinkLeft', 'eyeBlinkRight'],\n ['eyeLookUpLeft', 'eyeLookUpRight'],\n ['eyeLookDownLeft', 'eyeLookDownRight'],\n ['eyeLookInLeft', 'eyeLookInRight'],\n ['eyeLookOutLeft', 'eyeLookOutRight'],\n ['eyeSquintLeft', 'eyeSquintRight'],\n ['eyeWideLeft', 'eyeWideRight'],\n];\n\n// Precompute index pairs for fast symmetrization\nconst SYMMETRIC_INDEX_PAIRS: [number, number][] = ARKIT_SYMMETRIC_PAIRS.map(([l, r]) => [\n LAM_BLENDSHAPES.indexOf(l as typeof LAM_BLENDSHAPES[number]),\n LAM_BLENDSHAPES.indexOf(r as typeof LAM_BLENDSHAPES[number]),\n]).filter(([l, r]) => l !== -1 && r !== -1) as [number, number][];\n\n/**\n * Symmetrize blendshapes by averaging left/right pairs\n * From LAM official postprocessing (models/utils.py)\n * This fixes asymmetric output from the raw model\n */\nexport function symmetrizeBlendshapes(frame: Float32Array): Float32Array {\n const result = new Float32Array(frame);\n for (const [lIdx, rIdx] of SYMMETRIC_INDEX_PAIRS) {\n const avg = (frame[lIdx] + frame[rIdx]) / 2;\n result[lIdx] = avg;\n result[rIdx] = avg;\n }\n return result;\n}\n\n/**\n * wav2arkit_cpu model blendshape ordering\n *\n * Indices 0-24 match LAM_BLENDSHAPES, but 25+ diverge:\n * - LAM puts jawRight, mouthClose, mouthDimpleLeft, mouthDimpleRight at 25-28\n * - wav2arkit_cpu puts mouthFrownLeft at 25 and moves those four to 48-51\n */\nexport const WAV2ARKIT_BLENDSHAPES = [\n 'browDownLeft', 'browDownRight', 'browInnerUp', 'browOuterUpLeft', 'browOuterUpRight',\n 'cheekPuff', 'cheekSquintLeft', 'cheekSquintRight',\n 'eyeBlinkLeft', 'eyeBlinkRight', 'eyeLookDownLeft', 'eyeLookDownRight',\n 'eyeLookInLeft', 'eyeLookInRight', 'eyeLookOutLeft', 'eyeLookOutRight',\n 'eyeLookUpLeft', 'eyeLookUpRight', 'eyeSquintLeft', 'eyeSquintRight',\n 'eyeWideLeft', 'eyeWideRight',\n 'jawForward', 'jawLeft', 'jawOpen',\n 'mouthFrownLeft', 'mouthFrownRight', 'mouthFunnel', 'mouthLeft',\n 'mouthLowerDownLeft', 'mouthLowerDownRight',\n 'mouthPressLeft', 'mouthPressRight', 'mouthPucker', 'mouthRight',\n 'mouthRollLower', 'mouthRollUpper', 'mouthShrugLower', 'mouthShrugUpper',\n 'mouthSmileLeft', 'mouthSmileRight', 'mouthStretchLeft', 'mouthStretchRight',\n 'mouthUpperUpLeft', 'mouthUpperUpRight',\n 'noseSneerLeft', 'noseSneerRight', 'tongueOut',\n 'mouthClose', 'mouthDimpleLeft', 'mouthDimpleRight', 'jawRight',\n] as const;\n\n/**\n * Precomputed remap table: wav2arkit_cpu output index → LAM_BLENDSHAPES index\n *\n * For each wav2arkit output index i, REMAP_TO_LAM[i] gives the LAM_BLENDSHAPES\n * index where that value should be placed.\n */\nexport const REMAP_WAV2ARKIT_TO_LAM: number[] = WAV2ARKIT_BLENDSHAPES.map(\n (name) => LAM_BLENDSHAPES.indexOf(name as typeof LAM_BLENDSHAPES[number])\n);\n\n/**\n * Remap a blendshape frame from wav2arkit_cpu ordering to LAM_BLENDSHAPES ordering\n *\n * @param frame - Float32Array of 52 blendshape values in wav2arkit_cpu order\n * @returns Float32Array of 52 blendshape values in LAM_BLENDSHAPES order\n */\nexport function remapWav2ArkitToLam(frame: Float32Array): Float32Array {\n const result = new Float32Array(52);\n for (let i = 0; i < 52; i++) {\n result[REMAP_WAV2ARKIT_TO_LAM[i]] = frame[i];\n }\n return result;\n}\n","/**\r\n * Unified Wav2Vec2 inference engine for Audio-to-Expression + ASR\r\n *\r\n * Runs entirely in the browser using WebGPU or WASM.\r\n * Takes raw 16kHz audio and outputs:\r\n * - 52 ARKit blendshapes (lip sync)\r\n * - 32-token CTC logits (speech recognition)\r\n *\r\n * @category Inference\r\n *\r\n * @example Basic usage\r\n * ```typescript\r\n * import { Wav2Vec2Inference } from '@omote/core';\r\n *\r\n * const wav2vec = new Wav2Vec2Inference({ modelUrl: '/models/unified_wav2vec2_asr_a2e.onnx' });\r\n * await wav2vec.load();\r\n *\r\n * // Process 1 second of audio (16kHz = 16000 samples)\r\n * const result = await wav2vec.infer(audioSamples);\r\n *\r\n * console.log('Blendshapes:', result.blendshapes); // [30, 52] for 30fps\r\n * console.log('ASR text:', result.text); // Decoded transcription\r\n * ```\r\n */\r\n\r\n// Type-only import for TypeScript (no runtime code loaded at import time)\r\n// At runtime, we dynamically import either 'onnxruntime-web' or 'onnxruntime-web/webgpu'\r\nimport type { InferenceSession, Tensor, Env } from 'onnxruntime-common';\r\n\r\nimport { fetchWithCache, getModelCache, formatBytes } from '../cache/ModelCache';\r\nimport { createLogger } from '../logging';\r\nimport { getTelemetry } from '../telemetry';\r\nimport {\r\n getOnnxRuntimeForPreference,\r\n getSessionOptions,\r\n isWebGPUAvailable,\r\n type RuntimeBackend,\r\n} from './onnxLoader';\r\nimport { BackendPreference, isIOS } from '../utils/runtime';\r\nimport { symmetrizeBlendshapes, LAM_BLENDSHAPES, ARKIT_BLENDSHAPES } from './blendshapeUtils';\r\nimport type { LipSyncBackend } from './LipSyncBackend';\r\n\r\n// Type alias for the ORT module (loaded dynamically)\r\ntype OrtModule = {\r\n InferenceSession: typeof InferenceSession;\r\n Tensor: typeof Tensor;\r\n env: Env;\r\n};\r\n\r\nconst logger = createLogger('Wav2Vec2');\r\n\r\n// Re-export for backward compatibility\r\nexport type InferenceBackend = BackendPreference;\r\n\r\nexport interface Wav2Vec2InferenceConfig {\r\n /** Path or URL to the ONNX model */\r\n modelUrl: string;\r\n /**\r\n * Path or URL to external model data file (.onnx.data weights).\r\n * Default: `${modelUrl}.data` (e.g., /models/model.onnx.data)\r\n *\r\n * Set to `false` to skip external data loading (single-file models only).\r\n */\r\n externalDataUrl?: string | false;\r\n /** Preferred backend (auto will try WebGPU first, fallback to WASM) */\r\n backend?: InferenceBackend;\r\n /** Number of identity classes (default: 12 for streaming model) */\r\n numIdentityClasses?: number;\r\n}\r\n\r\nexport interface ModelInfo {\r\n backend: 'webgpu' | 'wasm';\r\n loadTimeMs: number;\r\n inputNames: string[];\r\n outputNames: string[];\r\n}\r\n\r\n// Re-export blendshape constants from shared utils (canonical source)\r\nexport { LAM_BLENDSHAPES, ARKIT_BLENDSHAPES } from './blendshapeUtils';\r\n\r\n/** CTC vocabulary (32 tokens from wav2vec2-base-960h) */\r\nexport const CTC_VOCAB = [\r\n '<pad>', '<s>', '</s>', '<unk>', '|', 'E', 'T', 'A', 'O', 'N',\r\n 'I', 'H', 'S', 'R', 'D', 'L', 'U', 'M', 'W', 'C',\r\n 'F', 'G', 'Y', 'P', 'B', 'V', 'K', \"'\", 'X', 'J', 'Q', 'Z'\r\n];\r\n\r\nexport interface Wav2Vec2Result {\r\n /** Blendshape weights [frames, 52] - 30fps */\r\n blendshapes: Float32Array[];\r\n /** Raw CTC logits [frames, 32] - 50fps */\r\n asrLogits: Float32Array[];\r\n /** Decoded text from CTC */\r\n text: string;\r\n /** Number of blendshape frames (30fps) — alias for numA2EFrames */\r\n numFrames: number;\r\n /** Number of A2E frames (30fps) */\r\n numA2EFrames: number;\r\n /** Number of ASR frames (50fps) */\r\n numASRFrames: number;\r\n /** Inference time in ms */\r\n inferenceTimeMs: number;\r\n}\r\n\r\nexport class Wav2Vec2Inference implements LipSyncBackend {\r\n readonly modelId = 'wav2vec2' as const;\r\n\r\n private session: InferenceSession | null = null;\r\n private ort: OrtModule | null = null; // Lazy-loaded ONNX Runtime module\r\n private config: Wav2Vec2InferenceConfig;\r\n private _backend: RuntimeBackend = 'wasm';\r\n private isLoading = false;\r\n private numIdentityClasses: number;\r\n\r\n // Inference queue for handling concurrent calls\r\n private inferenceQueue: Promise<void> = Promise.resolve();\r\n\r\n // Session health: set to true if session.run() times out.\r\n // A timed-out session may have a zombie GPU/WASM dispatch still running,\r\n // so all future infer() calls reject immediately to prevent concurrent access.\r\n private poisoned = false;\r\n private static readonly INFERENCE_TIMEOUT_MS = 5_000;\r\n\r\n constructor(config: Wav2Vec2InferenceConfig) {\r\n this.config = config;\r\n this.numIdentityClasses = config.numIdentityClasses ?? 12;\r\n }\r\n\r\n /**\r\n * Check if WebGPU is available and working\r\n * (iOS returns false even if navigator.gpu exists due to ONNX Runtime bugs)\r\n */\r\n static isWebGPUAvailable = isWebGPUAvailable;\r\n\r\n get backend(): 'webgpu' | 'wasm' | null {\r\n return this.session ? this._backend : null;\r\n }\r\n\r\n get isLoaded(): boolean {\r\n return this.session !== null;\r\n }\r\n\r\n /** True if inference timed out and the session is permanently unusable */\r\n get isSessionPoisoned(): boolean {\r\n return this.poisoned;\r\n }\r\n\r\n /**\r\n * Load the ONNX model\r\n */\r\n async load(): Promise<ModelInfo> {\r\n if (this.isLoading) {\r\n throw new Error('Model is already loading');\r\n }\r\n\r\n if (this.session) {\r\n throw new Error('Model already loaded. Call dispose() first.');\r\n }\r\n\r\n this.isLoading = true;\r\n const startTime = performance.now();\r\n const telemetry = getTelemetry();\r\n const span = telemetry?.startSpan('Wav2Vec2.load', {\r\n 'model.url': this.config.modelUrl,\r\n 'model.backend_requested': this.config.backend || 'auto',\r\n });\r\n\r\n try {\r\n // Lazy load ONNX Runtime with appropriate backend\r\n // iOS: Loads WASM-only bundle (smaller, no WebGPU code)\r\n // Android/Desktop: Loads WebGPU bundle (with WASM fallback)\r\n logger.info('Loading ONNX Runtime...', { preference: this.config.backend || 'auto' });\r\n\r\n const { ort, backend } = await getOnnxRuntimeForPreference(this.config.backend || 'auto');\r\n this.ort = ort;\r\n this._backend = backend;\r\n\r\n logger.info('ONNX Runtime loaded', { backend: this._backend });\r\n\r\n const modelUrl = this.config.modelUrl;\r\n const dataUrl = this.config.externalDataUrl !== false\r\n ? (typeof this.config.externalDataUrl === 'string'\r\n ? this.config.externalDataUrl\r\n : `${modelUrl}.data`)\r\n : null;\r\n const sessionOptions = getSessionOptions(this._backend);\r\n let isCached = false;\r\n\r\n // iOS: Pass URLs directly to ORT to avoid loading 384MB into JS heap.\r\n // ORT fetches externally into WASM memory, cutting peak JS memory from\r\n // ~768MB (ArrayBuffer + Uint8Array copy) to ~2MB (just the graph URL).\r\n // Desktop uses cached ArrayBuffers for faster reloads via IndexedDB.\r\n if (isIOS()) {\r\n logger.info('iOS: passing model URLs directly to ORT (low-memory path)', {\r\n modelUrl,\r\n dataUrl,\r\n });\r\n\r\n if (dataUrl) {\r\n const dataFilename = dataUrl.split('/').pop()!;\r\n logger.info('iOS: setting externalData', { dataFilename, dataUrl });\r\n (sessionOptions as Record<string, unknown>).externalData = [{\r\n path: dataFilename,\r\n data: dataUrl, // URL string — ORT fetches directly into WASM\r\n }];\r\n }\r\n\r\n logger.info('iOS: calling InferenceSession.create() with URL string', {\r\n modelUrl,\r\n sessionOptions: JSON.stringify(sessionOptions, (_, v) =>\r\n typeof v === 'string' && v.length > 100 ? v.slice(0, 100) + '...' : v\r\n ),\r\n });\r\n\r\n try {\r\n this.session = await this.ort!.InferenceSession.create(modelUrl, sessionOptions);\r\n } catch (sessionErr) {\r\n logger.error('iOS: InferenceSession.create() failed', {\r\n error: sessionErr instanceof Error ? sessionErr.message : String(sessionErr),\r\n errorType: sessionErr?.constructor?.name,\r\n stack: sessionErr instanceof Error ? sessionErr.stack : undefined,\r\n });\r\n throw sessionErr;\r\n }\r\n\r\n logger.info('iOS: session created successfully', {\r\n inputNames: this.session.inputNames,\r\n outputNames: this.session.outputNames,\r\n });\r\n } else {\r\n // Desktop: fetch + cache in IndexedDB for fast reloads\r\n const cache = getModelCache();\r\n isCached = await cache.has(modelUrl);\r\n\r\n let modelBuffer: ArrayBuffer;\r\n if (isCached) {\r\n logger.debug('Loading model from cache', { modelUrl });\r\n modelBuffer = (await cache.get(modelUrl))!;\r\n\r\n if (!modelBuffer) {\r\n logger.warn('Cache corruption detected, clearing and retrying', { modelUrl });\r\n await cache.delete(modelUrl);\r\n modelBuffer = await fetchWithCache(modelUrl);\r\n }\r\n } else {\r\n logger.debug('Fetching and caching model', { modelUrl });\r\n modelBuffer = await fetchWithCache(modelUrl);\r\n }\r\n\r\n if (!modelBuffer) {\r\n throw new Error(`Failed to load model: ${modelUrl}`);\r\n }\r\n\r\n // Load external data file (.onnx.data weights) if present\r\n let externalDataBuffer: ArrayBuffer | null = null;\r\n if (dataUrl) {\r\n try {\r\n const isDataCached = await cache.has(dataUrl);\r\n if (isDataCached) {\r\n logger.debug('Loading external data from cache', { dataUrl });\r\n externalDataBuffer = (await cache.get(dataUrl))!;\r\n if (!externalDataBuffer) {\r\n logger.warn('Cache corruption for external data, retrying', { dataUrl });\r\n await cache.delete(dataUrl);\r\n externalDataBuffer = await fetchWithCache(dataUrl);\r\n }\r\n } else {\r\n logger.info('Fetching external model data', {\r\n dataUrl,\r\n note: 'This may be a large download (383MB+)',\r\n });\r\n externalDataBuffer = await fetchWithCache(dataUrl);\r\n }\r\n logger.info('External data loaded', {\r\n size: formatBytes(externalDataBuffer.byteLength),\r\n });\r\n } catch (err) {\r\n logger.debug('No external data file found (single-file model)', {\r\n dataUrl,\r\n error: (err as Error).message,\r\n });\r\n }\r\n }\r\n\r\n logger.debug('Creating ONNX session', {\r\n graphSize: formatBytes(modelBuffer.byteLength),\r\n externalDataSize: externalDataBuffer ? formatBytes(externalDataBuffer.byteLength) : 'none',\r\n backend: this._backend,\r\n });\r\n\r\n // Pass external data to session if loaded\r\n if (externalDataBuffer) {\r\n const dataFilename = dataUrl!.split('/').pop()!;\r\n (sessionOptions as Record<string, unknown>).externalData = [{\r\n path: dataFilename,\r\n data: new Uint8Array(externalDataBuffer),\r\n }];\r\n }\r\n\r\n const modelData = new Uint8Array(modelBuffer);\r\n this.session = await this.ort!.InferenceSession.create(modelData, sessionOptions);\r\n }\r\n\r\n logger.info('ONNX session created successfully', {\r\n executionProvider: this._backend,\r\n backend: this._backend,\r\n });\r\n\r\n const loadTimeMs = performance.now() - startTime;\r\n\r\n logger.info('Model loaded successfully', {\r\n backend: this._backend,\r\n loadTimeMs: Math.round(loadTimeMs),\r\n inputs: this.session.inputNames,\r\n outputs: this.session.outputNames,\r\n });\r\n\r\n span?.setAttributes({\r\n 'model.backend': this._backend,\r\n 'model.load_time_ms': loadTimeMs,\r\n 'model.cached': !isIOS() && isCached,\r\n });\r\n span?.end();\r\n telemetry?.recordHistogram('omote.model.load_time', loadTimeMs, {\r\n model: 'wav2vec2',\r\n backend: this._backend,\r\n });\r\n\r\n // Warmup inference to initialize GPU kernels and contexts\r\n // This prevents hitching on the first real inference during playback.\r\n // Timeout protects against WebGPU device hangs (Windows 10 + older drivers).\r\n logger.debug('Running warmup inference to initialize GPU context');\r\n const warmupStart = performance.now();\r\n const silentAudio = new Float32Array(16000); // 1 second of silence\r\n const WARMUP_TIMEOUT_MS = 15_000;\r\n const warmupResult = await Promise.race([\r\n this.infer(silentAudio, 0).then(() => 'ok' as const),\r\n new Promise<'timeout'>(r => setTimeout(() => r('timeout'), WARMUP_TIMEOUT_MS)),\r\n ]);\r\n const warmupTimeMs = performance.now() - warmupStart;\r\n if (warmupResult === 'timeout') {\r\n logger.warn('Warmup inference timed out — GPU may be unresponsive. Continuing without warmup.', {\r\n timeoutMs: WARMUP_TIMEOUT_MS,\r\n backend: this._backend,\r\n });\r\n } else {\r\n logger.info('Warmup inference complete', {\r\n warmupTimeMs: Math.round(warmupTimeMs),\r\n backend: this._backend,\r\n });\r\n }\r\n telemetry?.recordHistogram('omote.model.warmup_time', warmupTimeMs, {\r\n model: 'wav2vec2',\r\n backend: this._backend,\r\n });\r\n\r\n return {\r\n backend: this._backend,\r\n loadTimeMs,\r\n inputNames: [...this.session.inputNames],\r\n outputNames: [...this.session.outputNames],\r\n };\r\n } catch (error) {\r\n span?.endWithError(error instanceof Error ? error : new Error(String(error)));\r\n telemetry?.incrementCounter('omote.errors.total', 1, {\r\n model: 'wav2vec2',\r\n error_type: 'load_failed',\r\n });\r\n throw error;\r\n } finally {\r\n this.isLoading = false;\r\n }\r\n }\r\n\r\n /**\r\n * Run inference on raw audio\r\n * @param audioSamples - Float32Array of raw audio at 16kHz (16000 samples = 1 second)\r\n * @param identityIndex - Optional identity index (0-11, default 0 = neutral)\r\n *\r\n * Note: Model expects 1-second chunks (16000 samples) for optimal performance.\r\n * Audio will be zero-padded or truncated to 16000 samples.\r\n */\r\n async infer(\r\n audioSamples: Float32Array,\r\n identityIndex: number = 0\r\n ): Promise<Wav2Vec2Result> {\r\n if (!this.session) {\r\n throw new Error('Model not loaded. Call load() first.');\r\n }\r\n\r\n if (this.poisoned) {\r\n throw new Error('Wav2Vec2 session timed out — inference unavailable until page reload');\r\n }\r\n\r\n // CRITICAL: Force copy IMMEDIATELY to prevent ArrayBuffer detachment\r\n // During interruptions, audioSamples buffer may get detached by ONNX Runtime\r\n // before we process it. Copy synchronously to preserve data.\r\n const audioSamplesCopy = new Float32Array(audioSamples);\r\n\r\n // Ensure audio is exactly 16000 samples (1 second)\r\n let audio: Float32Array;\r\n if (audioSamplesCopy.length === 16000) {\r\n audio = audioSamplesCopy;\r\n } else if (audioSamplesCopy.length < 16000) {\r\n // Zero-pad\r\n audio = new Float32Array(16000);\r\n audio.set(audioSamplesCopy, 0);\r\n } else {\r\n // Truncate\r\n audio = audioSamplesCopy.slice(0, 16000);\r\n }\r\n\r\n // Create identity one-hot vector\r\n const identity = new Float32Array(this.numIdentityClasses);\r\n identity[Math.min(identityIndex, this.numIdentityClasses - 1)] = 1.0;\r\n\r\n // CRITICAL: Force copy to prevent ArrayBuffer detachment by ONNX Runtime Web workers\r\n // Without copy, WASM backend transfers buffers to workers, causing \"memory access out of bounds\" errors\r\n const audioCopy = new Float32Array(audio);\r\n const identityCopy = new Float32Array(identity);\r\n\r\n const feeds = {\r\n 'audio': new this.ort!.Tensor('float32', audioCopy, [1, 16000]),\r\n 'identity': new this.ort!.Tensor('float32', identityCopy, [1, this.numIdentityClasses]),\r\n };\r\n\r\n // Queue the inference\r\n return this.queueInference(feeds);\r\n }\r\n\r\n /**\r\n * Decode CTC logits to text using greedy decoding\r\n */\r\n private decodeCTC(logits: Float32Array[]): string {\r\n const tokens: number[] = [];\r\n let prevToken = -1;\r\n\r\n for (const frame of logits) {\r\n // Find argmax\r\n let maxIdx = 0;\r\n let maxVal = frame[0];\r\n for (let i = 1; i < frame.length; i++) {\r\n if (frame[i] > maxVal) {\r\n maxVal = frame[i];\r\n maxIdx = i;\r\n }\r\n }\r\n\r\n // CTC collapse: skip duplicates and blanks (token 0)\r\n if (maxIdx !== prevToken && maxIdx !== 0) {\r\n tokens.push(maxIdx);\r\n }\r\n prevToken = maxIdx;\r\n }\r\n\r\n // Convert to text (token 4 = '|' = word separator = space)\r\n return tokens.map(t => CTC_VOCAB[t] === '|' ? ' ' : CTC_VOCAB[t]).join('');\r\n }\r\n\r\n /**\r\n * Queue inference to serialize ONNX session calls\r\n */\r\n private queueInference(\r\n feeds: Record<string, Tensor>\r\n ): Promise<Wav2Vec2Result> {\r\n return new Promise((resolve, reject) => {\r\n this.inferenceQueue = this.inferenceQueue.then(async () => {\r\n const telemetry = getTelemetry();\r\n const span = telemetry?.startSpan('Wav2Vec2.infer', {\r\n 'inference.backend': this._backend,\r\n 'inference.input_samples': 16000,\r\n });\r\n try {\r\n const startTime = performance.now();\r\n const results = await Promise.race([\r\n this.session!.run(feeds),\r\n new Promise<never>((_, rej) =>\r\n setTimeout(\r\n () => rej(new Error(`Wav2Vec2 inference timed out after ${Wav2Vec2Inference.INFERENCE_TIMEOUT_MS}ms`)),\r\n Wav2Vec2Inference.INFERENCE_TIMEOUT_MS,\r\n )\r\n ),\r\n ]);\r\n const inferenceTimeMs = performance.now() - startTime;\r\n\r\n const asrOutput = results['asr_logits'];\r\n const blendshapeOutput = results['blendshapes'];\r\n\r\n if (!asrOutput || !blendshapeOutput) {\r\n throw new Error('Missing outputs from model');\r\n }\r\n\r\n const asrData = asrOutput.data as Float32Array;\r\n const blendshapeData = blendshapeOutput.data as Float32Array;\r\n\r\n // Parse shapes: ASR is [1, time_50fps, 32], A2E is [1, time_30fps, 52]\r\n const numASRFrames = asrOutput.dims[1] as number;\r\n const numA2EFrames = blendshapeOutput.dims[1] as number;\r\n const asrVocabSize = asrOutput.dims[2] as number;\r\n const numBlendshapes = blendshapeOutput.dims[2] as number;\r\n\r\n // Split into per-frame arrays\r\n const asrLogits: Float32Array[] = [];\r\n const blendshapes: Float32Array[] = [];\r\n\r\n for (let f = 0; f < numASRFrames; f++) {\r\n asrLogits.push(asrData.slice(f * asrVocabSize, (f + 1) * asrVocabSize));\r\n }\r\n\r\n for (let f = 0; f < numA2EFrames; f++) {\r\n const rawFrame = blendshapeData.slice(f * numBlendshapes, (f + 1) * numBlendshapes);\r\n // Apply symmetrization postprocessing (from LAM official pipeline)\r\n blendshapes.push(symmetrizeBlendshapes(rawFrame));\r\n }\r\n\r\n // Decode CTC\r\n const text = this.decodeCTC(asrLogits);\r\n\r\n logger.trace('Inference completed', {\r\n inferenceTimeMs: Math.round(inferenceTimeMs * 100) / 100,\r\n numA2EFrames,\r\n numASRFrames,\r\n textLength: text.length,\r\n });\r\n\r\n span?.setAttributes({\r\n 'inference.duration_ms': inferenceTimeMs,\r\n 'inference.a2e_frames': numA2EFrames,\r\n 'inference.asr_frames': numASRFrames,\r\n });\r\n span?.end();\r\n telemetry?.recordHistogram('omote.inference.latency', inferenceTimeMs, {\r\n model: 'wav2vec2',\r\n backend: this._backend,\r\n });\r\n telemetry?.incrementCounter('omote.inference.total', 1, {\r\n model: 'wav2vec2',\r\n backend: this._backend,\r\n status: 'success',\r\n });\r\n\r\n resolve({\r\n blendshapes,\r\n asrLogits,\r\n text,\r\n numFrames: numA2EFrames,\r\n numA2EFrames,\r\n numASRFrames,\r\n inferenceTimeMs,\r\n });\r\n } catch (err) {\r\n const errMsg = err instanceof Error ? err.message : String(err);\r\n if (errMsg.includes('timed out')) {\r\n this.poisoned = true;\r\n logger.error('CRITICAL: Inference session timed out — LAM is dead. Page reload required.', {\r\n backend: this._backend,\r\n timeoutMs: Wav2Vec2Inference.INFERENCE_TIMEOUT_MS,\r\n });\r\n } else {\r\n logger.error('Inference failed', { error: errMsg, backend: this._backend });\r\n }\r\n span?.endWithError(err instanceof Error ? err : new Error(String(err)));\r\n telemetry?.incrementCounter('omote.inference.total', 1, {\r\n model: 'wav2vec2',\r\n backend: this._backend,\r\n status: 'error',\r\n });\r\n reject(err);\r\n }\r\n });\r\n });\r\n }\r\n\r\n /**\r\n * Get blendshape value by name for a specific frame\r\n */\r\n getBlendshape(blendshapes: Float32Array, name: typeof LAM_BLENDSHAPES[number]): number {\r\n const index = LAM_BLENDSHAPES.indexOf(name);\r\n if (index === -1) {\r\n throw new Error(`Unknown blendshape: ${name}`);\r\n }\r\n return blendshapes[index];\r\n }\r\n\r\n /**\r\n * Dispose of the model and free resources\r\n */\r\n async dispose(): Promise<void> {\r\n if (this.session) {\r\n await this.session.release();\r\n this.session = null;\r\n }\r\n }\r\n}\r\n","/**\n * FullFacePipeline - Combined LAM lip sync + Emotion upper face pipeline\n *\n * Orchestrates full-face animation by combining:\n * 1. LAM lip sync (52 ARKit blendshapes) via audio-first scheduling\n * 2. Emotion labels (from backend LLM or `setEmotionLabel()`) for upper face\n * 3. AudioEnergyAnalyzer for prosody-driven fallback when no emotion label is set\n *\n * Architecture: Audio-First, LAM-Background (same as SyncedAudioPipeline)\n * - Audio chunks are scheduled for playback immediately (never waits for LAM)\n * - LAM inference runs in background without blocking the audio path\n * - Lip sync starts ~1 second after audio (LAM needs 16000 samples to infer)\n *\n * Merge Strategy:\n * - Lower face (41 blendshapes): 100% from LAM (mouth, jaw, tongue, etc.)\n * - Upper face (11 blendshapes): Emotion overlay with LAM as subtle fallback\n * Formula: emotion * emotionBlendFactor + lam * lamBlendFactor\n *\n * Emotion Sources (in priority order):\n * 1. `setEmotionLabel()` — explicit label from backend LLM (recommended)\n * 2. Prosody fallback — subtle brow movement from audio energy (automatic)\n *\n * @category Audio\n *\n * @example Basic usage\n * ```typescript\n * import { FullFacePipeline } from '@omote/core';\n *\n * const pipeline = new FullFacePipeline({\n * lam,\n * emotionBlendFactor: 0.8,\n * lamBlendFactor: 0.2,\n * });\n * await pipeline.initialize();\n *\n * pipeline.on('full_frame_ready', (frame) => {\n * applyToAvatar(frame.blendshapes);\n * });\n *\n * pipeline.start();\n * pipeline.setEmotionLabel('happy'); // From backend LLM\n * await pipeline.onAudioChunk(audioData);\n * ```\n */\n\nimport { AudioScheduler } from './AudioScheduler';\nimport { AudioChunkCoalescer } from './AudioChunkCoalescer';\nimport { LAMPipeline } from './LAMPipeline';\nimport { EventEmitter } from '../events/EventEmitter';\nimport { EmotionToBlendshapeMapper, UPPER_FACE_BLENDSHAPES } from '../animation/EmotionToBlendshapeMapper';\nimport { AudioEnergyAnalyzer } from '../animation/audioEnergy';\nimport type { UpperFaceBlendshapes } from '../animation/EmotionToBlendshapeMapper';\nimport type { LipSyncBackend } from '../inference/LipSyncBackend';\nimport { LAM_BLENDSHAPES } from '../inference/Wav2Vec2Inference';\nimport type { EmotionFrame, Emotion2VecLabel } from '../animation/EmotionToBlendshapeMapper';\nimport { createLogger } from '../logging';\n\nconst logger = createLogger('FullFacePipeline');\n\n/**\n * Safely convert an ArrayBuffer of PCM16 bytes to Float32 samples.\n * Handles odd-length buffers by truncating to the nearest even byte boundary.\n */\nfunction pcm16ToFloat32(buffer: ArrayBuffer): Float32Array {\n const byteLen = buffer.byteLength & ~1;\n const int16 = byteLen === buffer.byteLength\n ? new Int16Array(buffer)\n : new Int16Array(buffer, 0, byteLen / 2);\n const float32 = new Float32Array(int16.length);\n for (let i = 0; i < int16.length; i++) {\n float32[i] = int16[i] / 32768;\n }\n return float32;\n}\n\n/**\n * Index map for O(1) blendshape name lookup\n */\nconst BLENDSHAPE_INDEX_MAP = new Map<string, number>();\nLAM_BLENDSHAPES.forEach((name, index) => {\n BLENDSHAPE_INDEX_MAP.set(name, index);\n});\n\n/**\n * Set of upper face blendshape names for fast lookup\n */\nconst UPPER_FACE_SET = new Set<string>(UPPER_FACE_BLENDSHAPES);\n\n/**\n * Map of natural language + SenseVoice emotion labels to Emotion2VecLabel\n */\nconst EMOTION_LABEL_MAP: Record<string, Emotion2VecLabel> = {\n // Direct labels\n happy: 'happy',\n sad: 'sad',\n angry: 'angry',\n neutral: 'neutral',\n // Natural language synonyms\n excited: 'happy',\n joyful: 'happy',\n cheerful: 'happy',\n delighted: 'happy',\n amused: 'happy',\n melancholic: 'sad',\n sorrowful: 'sad',\n disappointed: 'sad',\n frustrated: 'angry',\n irritated: 'angry',\n furious: 'angry',\n annoyed: 'angry',\n // SenseVoice labels\n fearful: 'sad',\n disgusted: 'angry',\n surprised: 'happy',\n};\n\n/**\n * Configuration for FullFacePipeline\n */\nexport interface FullFacePipelineOptions {\n /** Sample rate in Hz (default: 16000) */\n sampleRate?: number;\n /** Target chunk duration in ms for coalescing (default: 200) */\n chunkTargetMs?: number;\n /**\n * Audio playback delay in ms before first audio plays.\n * Gives LAM inference time to pre-compute blendshapes.\n * Default: auto-detected from lam.backend (50ms WebGPU, 350ms WASM).\n */\n audioDelayMs?: number;\n /** LAM inference engine */\n lam: LipSyncBackend;\n /**\n * Emotion blend factor for upper face blendshapes (0-1)\n * Higher values give more weight to emotion detection\n * @default 0.8\n */\n emotionBlendFactor?: number;\n /**\n * LAM blend factor for upper face blendshapes (0-1)\n * Provides subtle fallback from LAM when emotion is weak\n * @default 0.2\n */\n lamBlendFactor?: number;\n}\n\n/**\n * Full face frame with merged blendshapes and emotion data\n */\nexport interface FullFaceFrame {\n /** Merged 52 ARKit blendshapes (lower face from LAM + upper face from emotion) */\n blendshapes: Float32Array;\n /** Original LAM blendshapes (52) */\n lamBlendshapes: Float32Array;\n /** Emotion-driven upper face blendshapes (11) */\n emotionBlendshapes: UpperFaceBlendshapes;\n /** Raw emotion frame data */\n emotion: EmotionFrame | null;\n /** AudioContext timestamp for this frame */\n timestamp: number;\n}\n\n/**\n * Events emitted by FullFacePipeline\n */\nexport interface FullFacePipelineEvents {\n /** New merged frame ready for display */\n full_frame_ready: FullFaceFrame;\n /** Raw LAM frame ready (for debugging/monitoring) */\n lam_frame_ready: Float32Array;\n /** Emotion frame ready (for debugging/monitoring) */\n emotion_frame_ready: EmotionFrame;\n /** Playback has completed */\n playback_complete: void;\n /** First frame ready, playback starting */\n playback_start: number;\n /** Error occurred */\n error: Error;\n /** Index signature for EventEmitter compatibility */\n [key: string]: unknown;\n}\n\n/**\n * FullFacePipeline - Unified LAM + Emotion animation pipeline\n *\n * Audio-first design matching SyncedAudioPipeline:\n * - Audio is scheduled immediately (never waits for LAM)\n * - LAM runs in background (fire-and-forget)\n * - Emotion from setEmotionLabel() or prosody fallback\n */\nexport class FullFacePipeline extends EventEmitter<FullFacePipelineEvents> {\n private scheduler: AudioScheduler;\n private coalescer: AudioChunkCoalescer;\n private lamPipeline: LAMPipeline;\n private emotionMapper: EmotionToBlendshapeMapper;\n private energyAnalyzer: AudioEnergyAnalyzer;\n\n private playbackStarted = false;\n private monitorInterval: ReturnType<typeof setInterval> | null = null;\n private frameAnimationId: number | null = null;\n\n // Emotion state\n private lastEmotionFrame: EmotionFrame | null = null;\n private currentAudioEnergy = 0;\n\n // Stale frame detection\n private lastNewFrameTime = 0;\n private lastKnownLamFrame: Float32Array | null = null;\n private staleWarningEmitted = false;\n private static readonly STALE_FRAME_THRESHOLD_MS = 3_000;\n\n // Blend factors\n private emotionBlendFactor: number;\n private lamBlendFactor: number;\n\n constructor(private readonly options: FullFacePipelineOptions) {\n super();\n\n const sampleRate = options.sampleRate ?? 16000;\n this.emotionBlendFactor = options.emotionBlendFactor ?? 0.8;\n this.lamBlendFactor = options.lamBlendFactor ?? 0.2;\n\n // Auto-detect audio delay from model + backend (same logic as SyncedAudioPipeline)\n const autoDelay = options.lam.modelId === 'wav2arkit_cpu' ? 750\n : options.lam.backend === 'wasm' ? 350\n : 50;\n const audioDelayMs = options.audioDelayMs ?? autoDelay;\n\n this.scheduler = new AudioScheduler({\n sampleRate,\n initialLookaheadSec: audioDelayMs / 1000,\n });\n this.coalescer = new AudioChunkCoalescer({\n sampleRate,\n targetDurationMs: options.chunkTargetMs ?? 200,\n });\n this.lamPipeline = new LAMPipeline({\n sampleRate,\n onError: (error) => {\n logger.error('LAM inference error', { message: error.message, stack: error.stack });\n this.emit('error', error);\n },\n });\n this.emotionMapper = new EmotionToBlendshapeMapper({\n smoothingFactor: 0.15,\n confidenceThreshold: 0.3,\n intensity: 1.0,\n energyModulation: true,\n });\n this.energyAnalyzer = new AudioEnergyAnalyzer();\n }\n\n /**\n * Initialize the pipeline\n */\n async initialize(): Promise<void> {\n await this.scheduler.initialize();\n }\n\n /**\n * Set emotion label from backend (e.g., LLM response emotion).\n *\n * Converts a natural language emotion label into an EmotionFrame\n * that drives upper face blendshapes for the duration of the utterance.\n *\n * Supported labels: happy, excited, joyful, sad, melancholic, angry,\n * frustrated, neutral, etc.\n *\n * @param label - Emotion label string (case-insensitive)\n */\n setEmotionLabel(label: string): void {\n const normalized = label.toLowerCase();\n const mapped = EMOTION_LABEL_MAP[normalized] ?? 'neutral';\n\n // Build synthetic probability distribution\n const probabilities: Record<Emotion2VecLabel, number> = {\n neutral: 0.1,\n happy: 0.1,\n angry: 0.1,\n sad: 0.1,\n };\n probabilities[mapped] = 0.7;\n\n const frame: EmotionFrame = {\n emotion: mapped,\n confidence: 0.7,\n probabilities,\n };\n\n this.lastEmotionFrame = frame;\n logger.info('Emotion label set', { label, mapped });\n }\n\n /**\n * Clear any set emotion label.\n * Falls back to prosody-only upper face animation.\n */\n clearEmotionLabel(): void {\n this.lastEmotionFrame = null;\n }\n\n /**\n * Start a new playback session\n *\n * Resets all state and prepares for incoming audio chunks.\n * Audio will be scheduled immediately as chunks arrive (no buffering).\n */\n start(): void {\n // Stop any active session first (prevents duplicate frame loops/monitors)\n this.stopMonitoring();\n\n this.scheduler.reset();\n this.coalescer.reset();\n this.lamPipeline.reset();\n this.playbackStarted = false;\n\n // Reset emotion state\n this.lastEmotionFrame = null;\n this.currentAudioEnergy = 0;\n this.emotionMapper.reset();\n this.energyAnalyzer.reset();\n\n // Reset stale frame tracking\n this.lastNewFrameTime = 0;\n this.lastKnownLamFrame = null;\n this.staleWarningEmitted = false;\n\n // Eagerly warm up AudioContext so audio hardware is ready\n this.scheduler.warmup();\n\n this.startFrameLoop();\n this.startMonitoring();\n }\n\n /**\n * Receive audio chunk from network\n *\n * Audio-first design: schedules audio immediately, LAM runs in background.\n * This prevents LAM inference (50-300ms) from blocking audio scheduling.\n *\n * @param chunk - Uint8Array containing Int16 PCM audio\n */\n async onAudioChunk(chunk: Uint8Array): Promise<void> {\n // Coalesce small chunks into optimal buffers\n const combined = this.coalescer.add(chunk);\n if (!combined) {\n return; // Not enough data yet\n }\n\n // Convert PCM16 bytes to Float32 samples\n const float32 = pcm16ToFloat32(combined);\n\n // Schedule audio immediately — never wait for LAM\n const scheduleTime = await this.scheduler.schedule(float32);\n\n // Emit playback_start on first scheduled chunk\n if (!this.playbackStarted) {\n this.playbackStarted = true;\n this.emit('playback_start', scheduleTime);\n }\n\n // Compute audio energy for prosody fallback\n const { energy } = this.energyAnalyzer.process(float32);\n this.currentAudioEnergy = energy;\n\n // LAM runs in background — never blocks audio scheduling\n this.lamPipeline.push(float32, scheduleTime, this.options.lam).catch(err => {\n this.emit('error', err);\n });\n }\n\n /**\n * Get emotion frame for current animation.\n *\n * Priority:\n * 1. Explicit emotion label from setEmotionLabel()\n * 2. Prosody fallback: subtle brow movement from audio energy\n */\n private getEmotionFrame(): { frame: EmotionFrame | null; energy: number } {\n if (this.lastEmotionFrame) {\n return { frame: this.lastEmotionFrame, energy: this.currentAudioEnergy };\n }\n\n // No explicit emotion label — return null so mergeBlendshapes() uses\n // the direct prosody path (brow movement from audio energy) instead of\n // routing through EmotionToBlendshapeMapper which maps neutral → zero.\n return { frame: null, energy: this.currentAudioEnergy };\n }\n\n /**\n * Merge LAM blendshapes with emotion upper face blendshapes\n */\n mergeBlendshapes(\n lamFrame: Float32Array,\n emotionFrame: EmotionFrame | null,\n audioEnergy?: number,\n ): { merged: Float32Array; emotionBlendshapes: UpperFaceBlendshapes } {\n const merged = new Float32Array(52);\n let emotionBlendshapes: UpperFaceBlendshapes;\n\n if (emotionFrame) {\n // Get emotion-driven blendshapes with energy modulation\n this.emotionMapper.mapFrame(emotionFrame, audioEnergy);\n this.emotionMapper.update(33); // ~30fps\n emotionBlendshapes = this.emotionMapper.getCurrentBlendshapes();\n } else {\n // No emotion — zero upper face from this pipeline.\n // Brow animation is handled by ProceduralLifeLayer (simplex noise + speech modulation).\n emotionBlendshapes = {} as UpperFaceBlendshapes;\n for (const name of UPPER_FACE_BLENDSHAPES) {\n emotionBlendshapes[name] = 0;\n }\n }\n\n // Merge: lower face 100% LAM, upper face emotion + LAM fallback\n for (let i = 0; i < 52; i++) {\n const name = LAM_BLENDSHAPES[i];\n\n if (UPPER_FACE_SET.has(name)) {\n // Upper face: emotion * factor + LAM * factor\n const emotionValue = emotionBlendshapes[name as keyof UpperFaceBlendshapes] ?? 0;\n const lamValue = lamFrame[i];\n merged[i] = emotionValue * this.emotionBlendFactor + lamValue * this.lamBlendFactor;\n } else {\n // Lower face: 100% LAM\n merged[i] = lamFrame[i];\n }\n }\n\n return { merged, emotionBlendshapes };\n }\n\n /**\n * Start frame animation loop\n */\n private startFrameLoop(): void {\n const updateFrame = () => {\n const currentTime = this.scheduler.getCurrentTime();\n const lamFrame = this.lamPipeline.getFrameForTime(currentTime, this.options.lam);\n\n if (lamFrame) {\n // Track stale frame detection\n if (lamFrame !== this.lastKnownLamFrame) {\n this.lastNewFrameTime = performance.now();\n this.lastKnownLamFrame = lamFrame;\n this.staleWarningEmitted = false;\n }\n\n // Get emotion frame (explicit label or prosody fallback)\n const { frame: emotionFrame, energy } = this.getEmotionFrame();\n\n // Merge LAM + emotion\n const { merged, emotionBlendshapes } = this.mergeBlendshapes(lamFrame, emotionFrame, energy);\n\n // Emit merged frame\n const fullFrame: FullFaceFrame = {\n blendshapes: merged,\n lamBlendshapes: lamFrame,\n emotionBlendshapes,\n emotion: emotionFrame,\n timestamp: currentTime,\n };\n\n this.emit('full_frame_ready', fullFrame);\n this.emit('lam_frame_ready', lamFrame);\n\n if (emotionFrame) {\n this.emit('emotion_frame_ready', emotionFrame);\n }\n } else if (this.playbackStarted && !this.lastKnownLamFrame) {\n // Pre-speech window: LAM hasn't produced any frames yet (~first 1s).\n // Emit subtle prosody-only animation to prevent dead face on startup.\n const { frame: emotionFrame, energy } = this.getEmotionFrame();\n if (emotionFrame && energy > 0.05) {\n const startupFrame = new Float32Array(52);\n const { merged, emotionBlendshapes } = this.mergeBlendshapes(startupFrame, emotionFrame, energy);\n this.emit('full_frame_ready', {\n blendshapes: merged,\n lamBlendshapes: startupFrame,\n emotionBlendshapes,\n emotion: emotionFrame,\n timestamp: currentTime,\n });\n }\n }\n\n // Stale frame detection: warn if LAM hasn't produced new frames during playback\n if (\n this.playbackStarted &&\n this.lastNewFrameTime > 0 &&\n !this.staleWarningEmitted &&\n performance.now() - this.lastNewFrameTime > FullFacePipeline.STALE_FRAME_THRESHOLD_MS\n ) {\n this.staleWarningEmitted = true;\n logger.warn('LAM appears stalled — no new frames for 3+ seconds during playback', {\n staleDurationMs: Math.round(performance.now() - this.lastNewFrameTime),\n queuedFrames: this.lamPipeline.queuedFrameCount,\n });\n }\n\n this.frameAnimationId = requestAnimationFrame(updateFrame);\n };\n\n this.frameAnimationId = requestAnimationFrame(updateFrame);\n }\n\n /**\n * End of audio stream\n */\n async end(): Promise<void> {\n // Flush remaining coalesced data\n const remaining = this.coalescer.flush();\n if (remaining) {\n const chunk = new Uint8Array(remaining);\n await this.onAudioChunk(chunk);\n }\n\n // Flush remaining LAM buffer\n await this.lamPipeline.flush(this.options.lam);\n }\n\n /**\n * Stop playback immediately with smooth fade-out\n */\n async stop(fadeOutMs: number = 50): Promise<void> {\n this.stopMonitoring();\n await this.scheduler.cancelAll(fadeOutMs);\n\n this.coalescer.reset();\n this.lamPipeline.reset();\n this.playbackStarted = false;\n\n // Clear emotion state\n this.lastEmotionFrame = null;\n this.currentAudioEnergy = 0;\n this.emotionMapper.reset();\n this.energyAnalyzer.reset();\n\n // Reset stale frame tracking\n this.lastNewFrameTime = 0;\n this.lastKnownLamFrame = null;\n this.staleWarningEmitted = false;\n\n this.emit('playback_complete', undefined as any);\n }\n\n /**\n * Start monitoring for playback completion\n */\n private startMonitoring(): void {\n if (this.monitorInterval) {\n clearInterval(this.monitorInterval);\n }\n\n this.monitorInterval = setInterval(() => {\n if (this.scheduler.isComplete() && this.lamPipeline.queuedFrameCount === 0) {\n this.emit('playback_complete', undefined as any);\n this.stopMonitoring();\n }\n }, 100);\n }\n\n /**\n * Stop monitoring\n */\n private stopMonitoring(): void {\n if (this.monitorInterval) {\n clearInterval(this.monitorInterval);\n this.monitorInterval = null;\n }\n\n if (this.frameAnimationId) {\n cancelAnimationFrame(this.frameAnimationId);\n this.frameAnimationId = null;\n }\n }\n\n /**\n * Get current pipeline state (for debugging/monitoring)\n */\n getState() {\n return {\n playbackStarted: this.playbackStarted,\n coalescerFill: this.coalescer.fillLevel,\n lamFill: this.lamPipeline.fillLevel,\n queuedLAMFrames: this.lamPipeline.queuedFrameCount,\n emotionLabel: this.lastEmotionFrame?.emotion ?? null,\n currentAudioEnergy: this.currentAudioEnergy,\n currentTime: this.scheduler.getCurrentTime(),\n playbackEndTime: this.scheduler.getPlaybackEndTime(),\n };\n }\n\n /**\n * Check if an explicit emotion label is currently set\n */\n get hasEmotionLabel(): boolean {\n return this.lastEmotionFrame !== null;\n }\n\n /**\n * Cleanup resources\n */\n dispose(): void {\n this.stopMonitoring();\n this.scheduler.dispose();\n this.coalescer.reset();\n this.lamPipeline.reset();\n this.lastEmotionFrame = null;\n this.currentAudioEnergy = 0;\n }\n}\n","/**\n * Kaldi-compatible filterbank (fbank) feature extraction\n *\n * Pure TypeScript implementation matching kaldi-native-fbank parameters\n * used by SenseVoice. No external dependencies.\n *\n * Pipeline: audio → framing → windowing → FFT → power spectrum → mel filterbank → log\n *\n * @module inference/kaldiFbank\n */\n\n// ─── FFT ────────────────────────────────────────────────────────────────────\n\n/**\n * In-place Radix-2 Cooley-Tukey FFT\n * @param re Real parts (modified in-place)\n * @param im Imaginary parts (modified in-place)\n */\nfunction fft(re: Float64Array, im: Float64Array): void {\n const n = re.length;\n\n // Bit-reversal permutation\n for (let i = 1, j = 0; i < n; i++) {\n let bit = n >> 1;\n while (j & bit) {\n j ^= bit;\n bit >>= 1;\n }\n j ^= bit;\n if (i < j) {\n let tmp = re[i]; re[i] = re[j]; re[j] = tmp;\n tmp = im[i]; im[i] = im[j]; im[j] = tmp;\n }\n }\n\n // Butterfly passes\n for (let len = 2; len <= n; len *= 2) {\n const halfLen = len / 2;\n const angle = -2 * Math.PI / len;\n const wRe = Math.cos(angle);\n const wIm = Math.sin(angle);\n\n for (let i = 0; i < n; i += len) {\n let curRe = 1;\n let curIm = 0;\n for (let j = 0; j < halfLen; j++) {\n const a = i + j;\n const b = a + halfLen;\n const tRe = curRe * re[b] - curIm * im[b];\n const tIm = curRe * im[b] + curIm * re[b];\n re[b] = re[a] - tRe;\n im[b] = im[a] - tIm;\n re[a] += tRe;\n im[a] += tIm;\n const nextRe = curRe * wRe - curIm * wIm;\n curIm = curRe * wIm + curIm * wRe;\n curRe = nextRe;\n }\n }\n }\n}\n\n// ─── Mel Scale ──────────────────────────────────────────────────────────────\n\n/** HTK mel scale (same as Kaldi default) */\nfunction htkMel(freq: number): number {\n return 1127.0 * Math.log(1.0 + freq / 700.0);\n}\n\nfunction htkMelInverse(mel: number): number {\n return 700.0 * (Math.exp(mel / 1127.0) - 1.0);\n}\n\n// ─── Mel Filterbank ─────────────────────────────────────────────────────────\n\n/**\n * Build triangular mel filterbank matrix\n * @returns Array of numBins filters, each is a sparse {startBin, weights} pair\n */\ninterface MelFilter {\n startBin: number;\n weights: Float32Array;\n}\n\nfunction buildMelFilterbank(\n numBins: number,\n fftSize: number,\n sampleRate: number,\n lowFreq: number,\n highFreq: number,\n): MelFilter[] {\n const numFftBins = fftSize / 2 + 1;\n const lowMel = htkMel(lowFreq);\n const highMel = htkMel(highFreq);\n\n // numBins + 2 equally spaced points in mel space\n const melPoints = new Float64Array(numBins + 2);\n for (let i = 0; i < numBins + 2; i++) {\n melPoints[i] = lowMel + (highMel - lowMel) * i / (numBins + 1);\n }\n\n // Convert mel points to FFT bin indices (float, not rounded)\n const binFreqs = new Float64Array(numBins + 2);\n for (let i = 0; i < numBins + 2; i++) {\n binFreqs[i] = htkMelInverse(melPoints[i]) * fftSize / sampleRate;\n }\n\n const filters: MelFilter[] = [];\n\n for (let m = 0; m < numBins; m++) {\n const left = binFreqs[m];\n const center = binFreqs[m + 1];\n const right = binFreqs[m + 2];\n\n const startBin = Math.max(0, Math.ceil(left));\n const endBin = Math.min(numFftBins - 1, Math.floor(right));\n\n const weights = new Float32Array(endBin - startBin + 1);\n for (let k = startBin; k <= endBin; k++) {\n if (k <= center) {\n weights[k - startBin] = (center - left) > 0 ? (k - left) / (center - left) : 0;\n } else {\n weights[k - startBin] = (right - center) > 0 ? (right - k) / (right - center) : 0;\n }\n }\n\n filters.push({ startBin, weights });\n }\n\n return filters;\n}\n\n// ─── Hamming Window ─────────────────────────────────────────────────────────\n\nfunction createHammingWindow(length: number): Float32Array {\n const window = new Float32Array(length);\n for (let i = 0; i < length; i++) {\n window[i] = 0.54 - 0.46 * Math.cos(2 * Math.PI * i / (length - 1));\n }\n return window;\n}\n\n// ─── Options ────────────────────────────────────────────────────────────────\n\nexport interface KaldiFbankOptions {\n /** Frame length in ms (default: 25) */\n frameLengthMs?: number;\n /** Frame shift in ms (default: 10) */\n frameShiftMs?: number;\n /** Low frequency cutoff in Hz (default: 20) */\n lowFreq?: number;\n /** High frequency cutoff in Hz (default: sampleRate / 2) */\n highFreq?: number;\n /** Dither amount (default: 0 for deterministic output) */\n dither?: number;\n /** Preemphasis coefficient (default: 0.97) */\n preemphasis?: number;\n}\n\n// ─── Main Fbank ─────────────────────────────────────────────────────────────\n\n/**\n * Compute Kaldi-compatible log mel filterbank features\n *\n * @param audio Raw audio samples (float32, [-1, 1] range)\n * @param sampleRate Sample rate in Hz (must be 16000 for SenseVoice)\n * @param numMelBins Number of mel bins (80 for SenseVoice)\n * @param opts Optional parameters\n * @returns Flattened Float32Array of shape [numFrames, numMelBins]\n */\nexport function computeKaldiFbank(\n audio: Float32Array,\n sampleRate: number,\n numMelBins: number,\n opts?: KaldiFbankOptions,\n): Float32Array {\n const frameLengthMs = opts?.frameLengthMs ?? 25;\n const frameShiftMs = opts?.frameShiftMs ?? 10;\n const lowFreq = opts?.lowFreq ?? 20;\n const highFreq = opts?.highFreq ?? (sampleRate / 2);\n const dither = opts?.dither ?? 0;\n const preemphasis = opts?.preemphasis ?? 0.97;\n\n const frameLengthSamples = Math.round(sampleRate * frameLengthMs / 1000);\n const frameShiftSamples = Math.round(sampleRate * frameShiftMs / 1000);\n\n // Kaldi signal scaling: float [-1,1] → int16 range\n const scaled = new Float32Array(audio.length);\n for (let i = 0; i < audio.length; i++) {\n scaled[i] = audio[i] * 32768;\n }\n\n // Optional dithering\n if (dither > 0) {\n for (let i = 0; i < scaled.length; i++) {\n // Box-Muller for Gaussian noise\n const u1 = Math.random();\n const u2 = Math.random();\n scaled[i] += dither * Math.sqrt(-2 * Math.log(u1 + 1e-10)) * Math.cos(2 * Math.PI * u2);\n }\n }\n\n // Number of frames (snip_edges=true: only complete frames)\n const numFrames = Math.max(0, Math.floor((scaled.length - frameLengthSamples) / frameShiftSamples) + 1);\n if (numFrames === 0) {\n return new Float32Array(0);\n }\n\n // FFT size: next power of 2\n let fftSize = 1;\n while (fftSize < frameLengthSamples) fftSize *= 2;\n\n const numFftBins = fftSize / 2 + 1;\n\n // Pre-compute window and filterbank\n const window = createHammingWindow(frameLengthSamples);\n const filters = buildMelFilterbank(numMelBins, fftSize, sampleRate, lowFreq, highFreq);\n\n // Allocate output\n const output = new Float32Array(numFrames * numMelBins);\n\n // FFT buffers (reused per frame)\n const fftRe = new Float64Array(fftSize);\n const fftIm = new Float64Array(fftSize);\n\n for (let f = 0; f < numFrames; f++) {\n const offset = f * frameShiftSamples;\n\n // Clear FFT buffers\n fftRe.fill(0);\n fftIm.fill(0);\n\n // Extract frame with preemphasis and windowing\n for (let i = 0; i < frameLengthSamples; i++) {\n let sample = scaled[offset + i];\n // Preemphasis: y[n] = x[n] - coeff * x[n-1]\n if (preemphasis > 0 && i > 0) {\n sample -= preemphasis * scaled[offset + i - 1];\n } else if (preemphasis > 0 && i === 0 && offset > 0) {\n sample -= preemphasis * scaled[offset - 1];\n }\n // Remove DC offset per frame\n fftRe[i] = sample * window[i];\n }\n\n // FFT\n fft(fftRe, fftIm);\n\n // Power spectrum: |X(k)|^2\n // Apply mel filterbank and take log\n const outOffset = f * numMelBins;\n for (let m = 0; m < numMelBins; m++) {\n const filter = filters[m];\n let energy = 0;\n for (let k = 0; k < filter.weights.length; k++) {\n const bin = filter.startBin + k;\n if (bin < numFftBins) {\n const powerSpec = fftRe[bin] * fftRe[bin] + fftIm[bin] * fftIm[bin];\n energy += filter.weights[k] * powerSpec;\n }\n }\n output[outOffset + m] = Math.log(Math.max(energy, 1e-10));\n }\n }\n\n return output;\n}\n\n// ─── LFR (Low Frame Rate) Stacking ─────────────────────────────────────────\n\n/**\n * Apply Low Frame Rate stacking for SenseVoice\n *\n * Concatenates lfrM consecutive frames with stride lfrN.\n * Left-pads with copies of first frame, right-pads last group.\n *\n * @param features Flattened [numFrames, featureDim]\n * @param featureDim Feature dimension per frame (e.g., 80)\n * @param lfrM Number of frames to stack (default: 7)\n * @param lfrN Stride (default: 6)\n * @returns Flattened [numOutputFrames, featureDim * lfrM]\n */\nexport function applyLFR(\n features: Float32Array,\n featureDim: number,\n lfrM: number = 7,\n lfrN: number = 6,\n): Float32Array {\n const numFrames = features.length / featureDim;\n if (numFrames === 0) return new Float32Array(0);\n\n const leftPad = Math.floor((lfrM - 1) / 2); // 3 for lfrM=7\n const paddedLen = numFrames + leftPad;\n const numOutputFrames = Math.ceil(paddedLen / lfrN);\n const outputDim = featureDim * lfrM;\n\n const output = new Float32Array(numOutputFrames * outputDim);\n\n for (let i = 0; i < numOutputFrames; i++) {\n const startFrame = i * lfrN - leftPad;\n\n for (let j = 0; j < lfrM; j++) {\n let srcFrame = startFrame + j;\n // Clamp to valid range\n if (srcFrame < 0) srcFrame = 0;\n if (srcFrame >= numFrames) srcFrame = numFrames - 1;\n\n const srcOffset = srcFrame * featureDim;\n const dstOffset = i * outputDim + j * featureDim;\n for (let k = 0; k < featureDim; k++) {\n output[dstOffset + k] = features[srcOffset + k];\n }\n }\n }\n\n return output;\n}\n\n// ─── CMVN (Cepstral Mean-Variance Normalization) ───────────────────────────\n\n/**\n * Apply CMVN normalization in-place\n *\n * Formula: normalized[i] = (features[i] + negMean[i % dim]) * invStddev[i % dim]\n *\n * @param features Flattened feature array (modified in-place)\n * @param dim Feature dimension (560 for SenseVoice after LFR)\n * @param negMean Negative mean vector (dim-dimensional)\n * @param invStddev Inverse standard deviation vector (dim-dimensional)\n * @returns The same features array (for chaining)\n */\nexport function applyCMVN(\n features: Float32Array,\n dim: number,\n negMean: Float32Array,\n invStddev: Float32Array,\n): Float32Array {\n for (let i = 0; i < features.length; i++) {\n const d = i % dim;\n features[i] = (features[i] + negMean[d]) * invStddev[d];\n }\n return features;\n}\n\n// ─── CMVN Parsing ───────────────────────────────────────────────────────────\n\n/**\n * Parse CMVN vectors from comma-separated strings (stored in ONNX metadata)\n *\n * The sherpa-onnx SenseVoice export stores neg_mean and inv_stddev\n * as comma-separated float strings in the model's metadata.\n */\nexport function parseCMVNFromMetadata(\n negMeanStr: string,\n invStddevStr: string,\n): { negMean: Float32Array; invStddev: Float32Array } {\n const negMean = new Float32Array(\n negMeanStr.split(',').map(s => parseFloat(s.trim()))\n );\n const invStddev = new Float32Array(\n invStddevStr.split(',').map(s => parseFloat(s.trim()))\n );\n return { negMean, invStddev };\n}\n","/**\n * CTC greedy decoder for SenseVoice\n *\n * Decodes CTC logits into text with structured token parsing\n * for language, emotion, and audio event detection.\n *\n * @module inference/ctcDecoder\n */\n\nexport interface CTCDecodeResult {\n /** Decoded text (speech content only) */\n text: string;\n /** Detected language (e.g., 'zh', 'en', 'ja', 'ko', 'yue') */\n language?: string;\n /** Detected emotion (e.g., 'HAPPY', 'SAD', 'ANGRY', 'NEUTRAL') */\n emotion?: string;\n /** Detected audio event (e.g., 'Speech', 'BGM', 'Laughter') */\n event?: string;\n}\n\n/** SenseVoice language ID → string mapping */\nconst LANGUAGE_IDS: Record<number, string> = {\n 0: 'auto',\n 3: 'zh',\n 4: 'en',\n 7: 'yue',\n 11: 'ja',\n 12: 'ko',\n 13: 'nospeech',\n};\n\n/** SenseVoice text normalization ID → string mapping */\nconst TEXT_NORM_IDS: Record<number, string> = {\n 14: 'with_itn',\n 15: 'without_itn',\n};\n\n/** Resolve language string to SenseVoice language ID */\nexport function resolveLanguageId(language: string): number {\n const map: Record<string, number> = {\n auto: 0,\n zh: 3,\n en: 4,\n yue: 7,\n ja: 11,\n ko: 12,\n };\n return map[language] ?? 0;\n}\n\n/** Resolve text norm string to SenseVoice text norm ID */\nexport function resolveTextNormId(textNorm: string): number {\n return textNorm === 'without_itn' ? 15 : 14;\n}\n\n/**\n * Parse tokens.txt into a token ID → string map\n *\n * Format: each line is \"token_string token_id\"\n * e.g., \"<unk> 0\", \"▁the 3\", \"s 4\"\n */\nexport function parseTokensFile(content: string): Map<number, string> {\n const map = new Map<number, string>();\n const lines = content.split('\\n');\n for (const line of lines) {\n const trimmed = line.trim();\n if (!trimmed) continue;\n // Find the last space — token string may contain spaces\n const lastSpace = trimmed.lastIndexOf(' ');\n if (lastSpace === -1) continue;\n const token = trimmed.substring(0, lastSpace);\n const id = parseInt(trimmed.substring(lastSpace + 1), 10);\n if (!isNaN(id)) {\n map.set(id, token);\n }\n }\n return map;\n}\n\n/**\n * SenseVoice structured token pattern matching\n * Structured tokens appear in the first few decoded tokens as <|tag|>\n */\nfunction parseStructuredToken(token: string): { type: string; value: string } | null {\n const match = token.match(/^<\\|(.+)\\|>$/);\n if (!match) return null;\n\n const value = match[1];\n\n // Language tokens\n if (value === 'zh' || value === 'en' || value === 'ja' || value === 'ko' || value === 'yue' || value === 'nospeech') {\n return { type: 'language', value };\n }\n\n // Emotion tokens\n const emotions = ['HAPPY', 'SAD', 'ANGRY', 'NEUTRAL', 'FEARFUL', 'DISGUSTED', 'SURPRISED', 'EMO_UNKNOWN'];\n if (emotions.includes(value)) {\n return { type: 'emotion', value };\n }\n\n // Audio event tokens\n const events = ['Speech', 'BGM', 'Applause', 'Laughter', 'Crying', 'Coughing', 'Sneezing', 'EVENT_UNKNOWN'];\n if (events.includes(value)) {\n return { type: 'event', value };\n }\n\n // ITN tokens\n if (value === 'withitn' || value === 'woitn' || value === 'with_itn' || value === 'without_itn') {\n return { type: 'textnorm', value };\n }\n\n return null;\n}\n\n/**\n * CTC greedy decode\n *\n * @param logits Raw logits from model output, flattened [seqLen, vocabSize]\n * @param seqLen Sequence length (time steps)\n * @param vocabSize Vocabulary size\n * @param tokenMap Token ID → string map from tokens.txt\n * @returns Decoded text and structured metadata\n */\nexport function ctcGreedyDecode(\n logits: Float32Array,\n seqLen: number,\n vocabSize: number,\n tokenMap: Map<number, string>,\n): CTCDecodeResult {\n // Step 1: Argmax per time step\n const tokenIds: number[] = [];\n for (let t = 0; t < seqLen; t++) {\n const offset = t * vocabSize;\n let maxIdx = 0;\n let maxVal = logits[offset];\n for (let v = 1; v < vocabSize; v++) {\n if (logits[offset + v] > maxVal) {\n maxVal = logits[offset + v];\n maxIdx = v;\n }\n }\n tokenIds.push(maxIdx);\n }\n\n // Step 2: Collapse consecutive duplicates\n const collapsed: number[] = [];\n let prev = -1;\n for (const id of tokenIds) {\n if (id !== prev) {\n collapsed.push(id);\n prev = id;\n }\n }\n\n // Step 3: Remove blank tokens (ID 0) and special tokens (<s>=1, </s>=2)\n const filtered = collapsed.filter(id => id !== 0 && id !== 1 && id !== 2);\n\n // Step 4: Convert to token strings and parse structured tokens\n let language: string | undefined;\n let emotion: string | undefined;\n let event: string | undefined;\n const textTokens: string[] = [];\n\n for (const id of filtered) {\n const token = tokenMap.get(id);\n if (!token) continue;\n\n const structured = parseStructuredToken(token);\n if (structured) {\n if (structured.type === 'language') language = structured.value;\n else if (structured.type === 'emotion') emotion = structured.value;\n else if (structured.type === 'event') event = structured.value;\n // Skip textnorm tokens — not useful in output\n } else {\n textTokens.push(token);\n }\n }\n\n // Step 5: Join tokens, handle SentencePiece boundary marker\n let text = textTokens.join('');\n // Replace SentencePiece word boundary (▁ = U+2581) with space\n text = text.replace(/\\u2581/g, ' ').trim();\n\n return { text, language, emotion, event };\n}\n","/**\n * SenseVoice automatic speech recognition using ONNX Runtime Web\n *\n * Non-autoregressive CTC-based ASR that is 5x faster than Whisper-Small.\n * Runs entirely in browser via WebGPU or WASM. No transformers.js dependency.\n *\n * Uses the sherpa-onnx SenseVoice export (model.int8.onnx, 239MB int8 quantized).\n * Also provides emotion detection, language identification, and audio event detection\n * from the same forward pass.\n *\n * @category Inference\n *\n * @example Basic usage\n * ```typescript\n * import { SenseVoiceInference } from '@omote/core';\n *\n * const asr = new SenseVoiceInference({\n * modelUrl: '/models/sensevoice/model.int8.onnx',\n * tokensUrl: '/models/sensevoice/tokens.txt',\n * });\n * await asr.load();\n *\n * const { text, emotion, language } = await asr.transcribe(audioSamples);\n * console.log(text); // \"Hello world\"\n * console.log(emotion); // \"NEUTRAL\"\n * console.log(language); // \"en\"\n * ```\n *\n * @module inference/SenseVoiceInference\n */\n\nimport type { InferenceSession, Tensor, Env } from 'onnxruntime-common';\nimport { fetchWithCache, getModelCache, formatBytes } from '../cache/ModelCache';\nimport { createLogger } from '../logging';\nimport { getTelemetry } from '../telemetry';\nimport {\n getOnnxRuntimeForPreference,\n getSessionOptions,\n type RuntimeBackend,\n} from './onnxLoader';\nimport { BackendPreference, isIOS } from '../utils/runtime';\nimport { computeKaldiFbank, applyLFR, applyCMVN, parseCMVNFromMetadata } from './kaldiFbank';\nimport { ctcGreedyDecode, parseTokensFile, resolveLanguageId, resolveTextNormId } from './ctcDecoder';\nimport type { CTCDecodeResult } from './ctcDecoder';\n\ntype OrtModule = {\n InferenceSession: typeof InferenceSession;\n Tensor: typeof Tensor;\n env: Env;\n};\n\nconst logger = createLogger('SenseVoice');\n\n// ─── Config ─────────────────────────────────────────────────────────────────\n\nexport type SenseVoiceLanguage = 'auto' | 'zh' | 'en' | 'ja' | 'ko' | 'yue';\n\nexport interface SenseVoiceConfig {\n /** Path or URL to model.int8.onnx (239MB) */\n modelUrl: string;\n /** Path or URL to tokens.txt vocabulary file (default: sibling of modelUrl) */\n tokensUrl?: string;\n /** Language hint (default: 'auto' for auto-detection) */\n language?: SenseVoiceLanguage;\n /** Text normalization: 'with_itn' applies inverse text normalization (default: 'with_itn') */\n textNorm?: 'with_itn' | 'without_itn';\n /** Preferred backend (default: 'auto') */\n backend?: BackendPreference;\n}\n\n// ─── Result Types ───────────────────────────────────────────────────────────\n\nexport interface SenseVoiceResult {\n /** Transcribed text */\n text: string;\n /** Detected language (e.g., 'zh', 'en', 'ja', 'ko', 'yue') */\n language?: string;\n /** Detected emotion (e.g., 'HAPPY', 'SAD', 'ANGRY', 'NEUTRAL') */\n emotion?: string;\n /** Detected audio event (e.g., 'Speech', 'BGM', 'Laughter') */\n event?: string;\n /** Inference time in milliseconds (preprocessing + model + decode) */\n inferenceTimeMs: number;\n /** Preprocessing time in milliseconds (fbank + LFR + CMVN) */\n preprocessTimeMs: number;\n}\n\nexport interface SenseVoiceModelInfo {\n backend: RuntimeBackend;\n loadTimeMs: number;\n inputNames: string[];\n outputNames: string[];\n vocabSize: number;\n}\n\n// ─── Inference Class ────────────────────────────────────────────────────────\n\nexport class SenseVoiceInference {\n private session: InferenceSession | null = null;\n private ort: OrtModule | null = null;\n private config: Required<SenseVoiceConfig>;\n private _backend: RuntimeBackend = 'wasm';\n private isLoading = false;\n private inferenceQueue: Promise<void> = Promise.resolve();\n\n // Preprocessing state (loaded once)\n private tokenMap: Map<number, string> | null = null;\n private negMean: Float32Array | null = null;\n private invStddev: Float32Array | null = null;\n private languageId: number = 0;\n private textNormId: number = 14;\n\n constructor(config: SenseVoiceConfig) {\n // Default tokensUrl to sibling of modelUrl\n const modelDir = config.modelUrl.substring(0, config.modelUrl.lastIndexOf('/'));\n const tokensUrl = config.tokensUrl ?? `${modelDir}/tokens.txt`;\n\n this.config = {\n modelUrl: config.modelUrl,\n tokensUrl,\n language: config.language ?? 'auto',\n textNorm: config.textNorm ?? 'with_itn',\n backend: config.backend ?? 'auto',\n };\n\n this.languageId = resolveLanguageId(this.config.language);\n this.textNormId = resolveTextNormId(this.config.textNorm);\n }\n\n get backend(): RuntimeBackend | null {\n return this.session ? this._backend : null;\n }\n\n get isLoaded(): boolean {\n return this.session !== null;\n }\n\n // ─── Load ───────────────────────────────────────────────────────────────\n\n async load(onProgress?: (loaded: number, total: number) => void): Promise<SenseVoiceModelInfo> {\n if (this.isLoading) {\n throw new Error('Model is already loading');\n }\n if (this.session) {\n throw new Error('Model already loaded. Call dispose() first.');\n }\n\n this.isLoading = true;\n const startTime = performance.now();\n const telemetry = getTelemetry();\n const span = telemetry?.startSpan('SenseVoice.load', {\n 'model.url': this.config.modelUrl,\n 'model.backend_requested': this.config.backend,\n });\n\n try {\n // 1. Load ORT runtime\n logger.info('Loading ONNX Runtime...', { preference: this.config.backend });\n const { ort, backend } = await getOnnxRuntimeForPreference(this.config.backend);\n this.ort = ort;\n this._backend = backend;\n logger.info('ONNX Runtime loaded', { backend: this._backend });\n\n // 2. Fetch tokens.txt (small, safe on all platforms)\n logger.debug('Fetching tokens vocabulary', { tokensUrl: this.config.tokensUrl });\n const tokensResponse = await fetch(this.config.tokensUrl);\n if (!tokensResponse.ok) {\n throw new Error(`Failed to fetch tokens.txt: ${tokensResponse.status} ${tokensResponse.statusText}`);\n }\n const tokensText = await tokensResponse.text();\n this.tokenMap = parseTokensFile(tokensText);\n logger.debug('Tokens loaded', { vocabSize: this.tokenMap.size });\n\n // 3. Create inference session\n const sessionOptions = getSessionOptions(this._backend);\n\n // SenseVoice uses variable-length inputs (progressive transcription sends\n // growing audio buffers where numLfrFrames changes each call: 13 → 26 → 40…).\n // WebGPU 'all' optimization pre-compiles GPU kernels assuming fixed shapes\n // from the first inference, causing index-out-of-bounds on subsequent calls\n // with different numLfrFrames. Use 'basic' to preserve dynamic shape support.\n // Wav2Vec2 is unaffected (always fixed 16000-sample input → constant shape).\n if (this._backend === 'webgpu') {\n sessionOptions.graphOptimizationLevel = 'basic';\n }\n\n let isCached = false;\n\n // iOS: Pass model URL directly to ORT to avoid loading 239MB into JS heap.\n // ORT fetches into WASM memory, keeping JS heap at ~2MB.\n // Desktop: fetch + cache in IndexedDB for fast reloads.\n if (isIOS()) {\n logger.info('iOS: passing model URL directly to ORT (low-memory path)', {\n modelUrl: this.config.modelUrl,\n });\n this.session = await this.ort.InferenceSession.create(\n this.config.modelUrl, sessionOptions\n );\n } else {\n const cache = getModelCache();\n isCached = await cache.has(this.config.modelUrl);\n\n let modelBuffer: ArrayBuffer;\n if (isCached) {\n logger.debug('Loading model from cache', { modelUrl: this.config.modelUrl });\n modelBuffer = (await cache.get(this.config.modelUrl))!;\n onProgress?.(modelBuffer.byteLength, modelBuffer.byteLength);\n } else {\n logger.debug('Fetching and caching model', { modelUrl: this.config.modelUrl });\n modelBuffer = await fetchWithCache(this.config.modelUrl, onProgress);\n }\n\n logger.debug('Creating ONNX session', {\n size: formatBytes(modelBuffer.byteLength),\n backend: this._backend,\n });\n\n const modelData = new Uint8Array(modelBuffer);\n this.session = await this.ort.InferenceSession.create(modelData, sessionOptions);\n }\n\n // 5. Try to read CMVN from model metadata\n try {\n const metadata = (this.session as any).handler?.metadata;\n if (metadata?.neg_mean && metadata?.inv_stddev) {\n const cmvn = parseCMVNFromMetadata(metadata.neg_mean, metadata.inv_stddev);\n this.negMean = cmvn.negMean;\n this.invStddev = cmvn.invStddev;\n logger.debug('CMVN loaded from model metadata', { dim: this.negMean.length });\n } else {\n logger.warn('CMVN not found in model metadata — features will not be normalized');\n }\n } catch (cmvnErr) {\n logger.warn('Failed to read CMVN from model metadata', { error: cmvnErr });\n }\n\n const loadTimeMs = performance.now() - startTime;\n\n logger.info('SenseVoice model loaded', {\n backend: this._backend,\n loadTimeMs: Math.round(loadTimeMs),\n vocabSize: this.tokenMap.size,\n inputs: this.session.inputNames,\n outputs: this.session.outputNames,\n hasCMVN: this.negMean !== null,\n });\n\n span?.setAttributes({\n 'model.backend': this._backend,\n 'model.load_time_ms': loadTimeMs,\n 'model.cached': !isIOS() && isCached,\n 'model.vocab_size': this.tokenMap.size,\n });\n span?.end();\n\n telemetry?.recordHistogram('omote.model.load_time', loadTimeMs, {\n model: 'sensevoice',\n backend: this._backend,\n });\n\n return {\n backend: this._backend,\n loadTimeMs,\n inputNames: [...this.session.inputNames],\n outputNames: [...this.session.outputNames],\n vocabSize: this.tokenMap.size,\n };\n } catch (error) {\n span?.endWithError(error instanceof Error ? error : new Error(String(error)));\n telemetry?.incrementCounter('omote.errors.total', 1, {\n model: 'sensevoice',\n error_type: 'load_failed',\n });\n throw error;\n } finally {\n this.isLoading = false;\n }\n }\n\n // ─── Transcribe ─────────────────────────────────────────────────────────\n\n /**\n * Transcribe audio samples to text\n *\n * @param audioSamples Float32Array of audio samples at 16kHz, [-1, 1] range\n * @returns Transcription result with text, emotion, language, and event\n */\n async transcribe(audioSamples: Float32Array): Promise<SenseVoiceResult> {\n if (!this.session || !this.ort || !this.tokenMap) {\n throw new Error('Model not loaded. Call load() first.');\n }\n\n // Copy to prevent ArrayBuffer detachment\n const audio = new Float32Array(audioSamples);\n\n return this.queueInference(audio);\n }\n\n private queueInference(audio: Float32Array): Promise<SenseVoiceResult> {\n return new Promise((resolve, reject) => {\n this.inferenceQueue = this.inferenceQueue.then(async () => {\n const telemetry = getTelemetry();\n const span = telemetry?.startSpan('SenseVoice.transcribe', {\n 'inference.backend': this._backend,\n 'inference.input_samples': audio.length,\n });\n\n try {\n const startTime = performance.now();\n\n // ── Preprocessing ──────────────────────────────────\n const preprocessStart = performance.now();\n\n // 1. Compute Kaldi fbank features [T, 80]\n const fbank = computeKaldiFbank(audio, 16000, 80);\n const numFrames = fbank.length / 80;\n\n if (numFrames === 0) {\n resolve({\n text: '',\n inferenceTimeMs: performance.now() - startTime,\n preprocessTimeMs: performance.now() - preprocessStart,\n });\n return;\n }\n\n // 2. Apply LFR stacking [T_reduced, 560]\n const lfrFeatures = applyLFR(fbank, 80, 7, 6);\n const numLfrFrames = lfrFeatures.length / 560;\n\n // 3. Apply CMVN normalization (in-place)\n if (this.negMean && this.invStddev) {\n applyCMVN(lfrFeatures, 560, this.negMean, this.invStddev);\n }\n\n const preprocessTimeMs = performance.now() - preprocessStart;\n\n // ── Build feeds ────────────────────────────────────\n const ort = this.ort!;\n const feeds: Record<string, InstanceType<typeof ort.Tensor>> = {\n x: new ort.Tensor('float32', lfrFeatures, [1, numLfrFrames, 560]),\n x_length: new ort.Tensor('int32', new Int32Array([numLfrFrames]), [1]),\n language: new ort.Tensor('int32', new Int32Array([this.languageId]), [1]),\n text_norm: new ort.Tensor('int32', new Int32Array([this.textNormId]), [1]),\n };\n\n // ── Inference ──────────────────────────────────────\n const results = await this.session!.run(feeds);\n\n const logitsOutput = results['logits'];\n if (!logitsOutput) {\n throw new Error('Model output missing \"logits\" tensor');\n }\n\n const logitsData = logitsOutput.data as Float32Array;\n const logitsDims = logitsOutput.dims;\n const seqLen = logitsDims[1] as number;\n const vocabSize = logitsDims[2] as number;\n\n // ── CTC Decode ─────────────────────────────────────\n const decoded = ctcGreedyDecode(logitsData, seqLen, vocabSize, this.tokenMap!);\n\n const inferenceTimeMs = performance.now() - startTime;\n\n logger.trace('Transcription complete', {\n text: decoded.text.substring(0, 50),\n language: decoded.language,\n emotion: decoded.emotion,\n event: decoded.event,\n preprocessTimeMs: Math.round(preprocessTimeMs * 100) / 100,\n inferenceTimeMs: Math.round(inferenceTimeMs * 100) / 100,\n numFrames,\n numLfrFrames,\n });\n\n span?.setAttributes({\n 'inference.duration_ms': inferenceTimeMs,\n 'inference.preprocess_ms': preprocessTimeMs,\n 'inference.num_frames': numFrames,\n 'inference.text_length': decoded.text.length,\n });\n span?.end();\n\n telemetry?.recordHistogram('omote.inference.latency', inferenceTimeMs, {\n model: 'sensevoice',\n backend: this._backend,\n });\n telemetry?.incrementCounter('omote.inference.total', 1, {\n model: 'sensevoice',\n backend: this._backend,\n status: 'success',\n });\n\n resolve({\n text: decoded.text,\n language: decoded.language,\n emotion: decoded.emotion,\n event: decoded.event,\n inferenceTimeMs,\n preprocessTimeMs,\n });\n } catch (err) {\n span?.endWithError(err instanceof Error ? err : new Error(String(err)));\n telemetry?.incrementCounter('omote.inference.total', 1, {\n model: 'sensevoice',\n backend: this._backend,\n status: 'error',\n });\n reject(err);\n }\n });\n });\n }\n\n // ─── Dispose ──────────────────────────────────────────────────────────\n\n async dispose(): Promise<void> {\n if (this.session) {\n await this.session.release();\n this.session = null;\n }\n this.ort = null;\n this.tokenMap = null;\n this.negMean = null;\n this.invStddev = null;\n }\n}\n","/**\r\n * CPU-optimized lip sync inference using wav2arkit_cpu model\r\n *\r\n * A Safari/iOS-compatible alternative to Wav2Vec2Inference (384MB) designed\r\n * for platforms where WebGPU crashes due to ONNX Runtime JSEP bugs.\r\n *\r\n * The model uses ONNX external data format:\r\n * - wav2arkit_cpu.onnx (1.86MB graph structure)\r\n * - wav2arkit_cpu.onnx.data (402MB weights)\r\n * Both files are fetched and cached automatically.\r\n *\r\n * Key differences from Wav2Vec2Inference:\r\n * - WASM-only backend (CPU-optimized, single-threaded, no WebGPU)\r\n * - No identity input (baked to identity 11)\r\n * - No ASR output (lip sync only)\r\n * - Dynamic input length (not fixed to 16000 samples)\r\n * - Different native blendshape ordering (remapped to LAM_BLENDSHAPES)\r\n *\r\n * @category Inference\r\n *\r\n * @example\r\n * ```typescript\r\n * import { Wav2ArkitCpuInference } from '@omote/core';\r\n *\r\n * const lam = new Wav2ArkitCpuInference({\r\n * modelUrl: '/models/wav2arkit_cpu.onnx',\r\n * });\r\n * await lam.load();\r\n *\r\n * const { blendshapes } = await lam.infer(audioSamples);\r\n * // blendshapes: Float32Array[] in LAM_BLENDSHAPES order, 30fps\r\n * ```\r\n */\r\n\r\nimport type { InferenceSession, Tensor, Env } from 'onnxruntime-common';\r\n\r\nimport { fetchWithCache, getModelCache, formatBytes } from '../cache/ModelCache';\r\nimport { createLogger } from '../logging';\r\nimport { getTelemetry } from '../telemetry';\r\nimport {\r\n getOnnxRuntimeForPreference,\r\n getSessionOptions,\r\n type RuntimeBackend,\r\n} from './onnxLoader';\r\nimport { BackendPreference, isIOS } from '../utils/runtime';\r\nimport { symmetrizeBlendshapes } from './blendshapeUtils';\r\nimport type { LipSyncBackend, LipSyncModelInfo, LipSyncResult } from './LipSyncBackend';\r\n\r\ntype OrtModule = {\r\n InferenceSession: typeof InferenceSession;\r\n Tensor: typeof Tensor;\r\n env: Env;\r\n};\r\n\r\nconst logger = createLogger('Wav2ArkitCpu');\r\n\r\nexport interface Wav2ArkitCpuConfig {\r\n /** Path or URL to the wav2arkit_cpu ONNX model (.onnx graph file) */\r\n modelUrl: string;\r\n /**\r\n * Path or URL to external model data file (.onnx.data weights).\r\n * Default: `${modelUrl}.data` (e.g., /models/wav2arkit_cpu.onnx.data)\r\n *\r\n * Set to `false` to skip external data loading (single-file models only).\r\n */\r\n externalDataUrl?: string | false;\r\n /** Preferred backend (default: 'wasm' — this model is CPU-optimized) */\r\n backend?: BackendPreference;\r\n}\r\n\r\nexport class Wav2ArkitCpuInference implements LipSyncBackend {\r\n readonly modelId = 'wav2arkit_cpu' as const;\r\n\r\n private session: InferenceSession | null = null;\r\n private ort: OrtModule | null = null;\r\n private config: Wav2ArkitCpuConfig;\r\n private _backend: RuntimeBackend = 'wasm';\r\n private isLoading = false;\r\n\r\n // Inference queue for handling concurrent calls\r\n private inferenceQueue: Promise<void> = Promise.resolve();\r\n\r\n constructor(config: Wav2ArkitCpuConfig) {\r\n this.config = config;\r\n }\r\n\r\n get backend(): RuntimeBackend | null {\r\n return this.session ? this._backend : null;\r\n }\r\n\r\n get isLoaded(): boolean {\r\n return this.session !== null;\r\n }\r\n\r\n /**\r\n * Load the ONNX model\r\n */\r\n async load(): Promise<LipSyncModelInfo> {\r\n if (this.isLoading) {\r\n throw new Error('Model is already loading');\r\n }\r\n\r\n if (this.session) {\r\n throw new Error('Model already loaded. Call dispose() first.');\r\n }\r\n\r\n this.isLoading = true;\r\n const startTime = performance.now();\r\n const telemetry = getTelemetry();\r\n const span = telemetry?.startSpan('Wav2ArkitCpu.load', {\r\n 'model.url': this.config.modelUrl,\r\n 'model.backend_requested': this.config.backend || 'wasm',\r\n });\r\n\r\n try {\r\n // Default to WASM — this model is CPU-optimized\r\n const preference = this.config.backend || 'wasm';\r\n logger.info('Loading ONNX Runtime...', { preference });\r\n\r\n const { ort, backend } = await getOnnxRuntimeForPreference(preference);\r\n this.ort = ort;\r\n this._backend = backend;\r\n\r\n logger.info('ONNX Runtime loaded', { backend: this._backend });\r\n\r\n const modelUrl = this.config.modelUrl;\r\n const dataUrl = this.config.externalDataUrl !== false\r\n ? (this.config.externalDataUrl || `${modelUrl}.data`)\r\n : null;\r\n const sessionOptions = getSessionOptions(this._backend);\r\n\r\n // iOS: Pass URLs directly to ORT to avoid loading 402MB into JS heap.\r\n // ORT fetches externally into WASM memory, cutting peak JS memory from\r\n // ~800MB to ~2MB (just the graph). Desktop uses cached ArrayBuffers for\r\n // faster reloads via IndexedDB.\r\n if (isIOS()) {\r\n logger.info('iOS: passing model URLs directly to ORT (low-memory path)', {\r\n modelUrl,\r\n dataUrl,\r\n });\r\n\r\n if (dataUrl) {\r\n const dataFilename = dataUrl.split('/').pop()!;\r\n (sessionOptions as Record<string, unknown>).externalData = [{\r\n path: dataFilename,\r\n data: dataUrl, // URL string — ORT fetches directly into WASM\r\n }];\r\n }\r\n\r\n this.session = await this.ort!.InferenceSession.create(modelUrl, sessionOptions);\r\n } else {\r\n // Desktop: fetch + cache in IndexedDB for fast reloads\r\n const cache = getModelCache();\r\n const isCached = await cache.has(modelUrl);\r\n\r\n let modelBuffer: ArrayBuffer;\r\n if (isCached) {\r\n logger.debug('Loading model from cache', { modelUrl });\r\n modelBuffer = (await cache.get(modelUrl))!;\r\n\r\n if (!modelBuffer) {\r\n logger.warn('Cache corruption detected, clearing and retrying', { modelUrl });\r\n await cache.delete(modelUrl);\r\n modelBuffer = await fetchWithCache(modelUrl);\r\n }\r\n } else {\r\n logger.debug('Fetching and caching model graph', { modelUrl });\r\n modelBuffer = await fetchWithCache(modelUrl);\r\n }\r\n\r\n if (!modelBuffer) {\r\n throw new Error(`Failed to load model: ${modelUrl}`);\r\n }\r\n\r\n // Load external data file (.onnx.data weights) if present\r\n let externalDataBuffer: ArrayBuffer | null = null;\r\n if (dataUrl) {\r\n try {\r\n const isDataCached = await cache.has(dataUrl);\r\n if (isDataCached) {\r\n logger.debug('Loading external data from cache', { dataUrl });\r\n externalDataBuffer = (await cache.get(dataUrl))!;\r\n if (!externalDataBuffer) {\r\n logger.warn('Cache corruption for external data, retrying', { dataUrl });\r\n await cache.delete(dataUrl);\r\n externalDataBuffer = await fetchWithCache(dataUrl);\r\n }\r\n } else {\r\n logger.info('Fetching external model data', {\r\n dataUrl,\r\n note: 'This may be a large download (400MB+)',\r\n });\r\n externalDataBuffer = await fetchWithCache(dataUrl);\r\n }\r\n logger.info('External data loaded', {\r\n size: formatBytes(externalDataBuffer.byteLength),\r\n });\r\n } catch (err) {\r\n logger.debug('No external data file found (single-file model)', {\r\n dataUrl,\r\n error: (err as Error).message,\r\n });\r\n }\r\n }\r\n\r\n logger.debug('Creating ONNX session', {\r\n graphSize: formatBytes(modelBuffer.byteLength),\r\n externalDataSize: externalDataBuffer ? formatBytes(externalDataBuffer.byteLength) : 'none',\r\n backend: this._backend,\r\n });\r\n\r\n // Pass external data to session if loaded\r\n if (externalDataBuffer) {\r\n const dataFilename = dataUrl!.split('/').pop()!;\r\n (sessionOptions as Record<string, unknown>).externalData = [{\r\n path: dataFilename,\r\n data: new Uint8Array(externalDataBuffer),\r\n }];\r\n }\r\n\r\n const modelData = new Uint8Array(modelBuffer);\r\n this.session = await this.ort!.InferenceSession.create(modelData, sessionOptions);\r\n }\r\n\r\n const loadTimeMs = performance.now() - startTime;\r\n\r\n logger.info('Model loaded successfully', {\r\n backend: this._backend,\r\n loadTimeMs: Math.round(loadTimeMs),\r\n inputs: this.session.inputNames,\r\n outputs: this.session.outputNames,\r\n });\r\n\r\n span?.setAttributes({\r\n 'model.backend': this._backend,\r\n 'model.load_time_ms': loadTimeMs,\r\n 'model.cached': !isIOS(),\r\n });\r\n span?.end();\r\n telemetry?.recordHistogram('omote.model.load_time', loadTimeMs, {\r\n model: 'wav2arkit_cpu',\r\n backend: this._backend,\r\n });\r\n\r\n // Warmup inference\r\n logger.debug('Running warmup inference');\r\n const warmupStart = performance.now();\r\n const silentAudio = new Float32Array(16000);\r\n await this.infer(silentAudio);\r\n const warmupTimeMs = performance.now() - warmupStart;\r\n logger.info('Warmup inference complete', {\r\n warmupTimeMs: Math.round(warmupTimeMs),\r\n backend: this._backend,\r\n });\r\n telemetry?.recordHistogram('omote.model.warmup_time', warmupTimeMs, {\r\n model: 'wav2arkit_cpu',\r\n backend: this._backend,\r\n });\r\n\r\n return {\r\n backend: this._backend,\r\n loadTimeMs,\r\n inputNames: [...this.session.inputNames],\r\n outputNames: [...this.session.outputNames],\r\n };\r\n } catch (error) {\r\n span?.endWithError(error instanceof Error ? error : new Error(String(error)));\r\n telemetry?.incrementCounter('omote.errors.total', 1, {\r\n model: 'wav2arkit_cpu',\r\n error_type: 'load_failed',\r\n });\r\n throw error;\r\n } finally {\r\n this.isLoading = false;\r\n }\r\n }\r\n\r\n /**\r\n * Run inference on raw audio\r\n *\r\n * Accepts variable-length audio (not fixed to 16000 samples).\r\n * Output frames = ceil(30 * numSamples / 16000).\r\n *\r\n * @param audioSamples - Float32Array of raw audio at 16kHz\r\n * @param _identityIndex - Ignored (identity 11 is baked into the model)\r\n */\r\n async infer(\r\n audioSamples: Float32Array,\r\n _identityIndex?: number\r\n ): Promise<LipSyncResult> {\r\n if (!this.session) {\r\n throw new Error('Model not loaded. Call load() first.');\r\n }\r\n\r\n // Force copy to prevent ArrayBuffer detachment\r\n const audioCopy = new Float32Array(audioSamples);\r\n\r\n const feeds = {\r\n 'audio_waveform': new this.ort!.Tensor('float32', audioCopy, [1, audioCopy.length]),\r\n };\r\n\r\n return this.queueInference(feeds, audioCopy.length);\r\n }\r\n\r\n /**\r\n * Queue inference to serialize ONNX session calls\r\n */\r\n private queueInference(\r\n feeds: Record<string, Tensor>,\r\n inputSamples: number\r\n ): Promise<LipSyncResult> {\r\n return new Promise((resolve, reject) => {\r\n this.inferenceQueue = this.inferenceQueue.then(async () => {\r\n const telemetry = getTelemetry();\r\n const span = telemetry?.startSpan('Wav2ArkitCpu.infer', {\r\n 'inference.backend': this._backend,\r\n 'inference.input_samples': inputSamples,\r\n });\r\n try {\r\n const startTime = performance.now();\r\n const results = await this.session!.run(feeds);\r\n const inferenceTimeMs = performance.now() - startTime;\r\n\r\n const blendshapeOutput = results['blendshapes'];\r\n\r\n if (!blendshapeOutput) {\r\n throw new Error('Missing blendshapes output from model');\r\n }\r\n\r\n const blendshapeData = blendshapeOutput.data as Float32Array;\r\n const numFrames = blendshapeOutput.dims[1] as number;\r\n const numBlendshapes = blendshapeOutput.dims[2] as number;\r\n\r\n // Split into per-frame arrays, symmetrize (no clamp — match Wav2Vec2 behavior)\r\n // wav2arkit_cpu outputs in alphabetical order = LAM_BLENDSHAPES order (no remap needed)\r\n const blendshapes: Float32Array[] = [];\r\n for (let f = 0; f < numFrames; f++) {\r\n const rawFrame = blendshapeData.slice(f * numBlendshapes, (f + 1) * numBlendshapes);\r\n const symmetrized = symmetrizeBlendshapes(rawFrame);\r\n blendshapes.push(symmetrized);\r\n }\r\n\r\n logger.trace('Inference completed', {\r\n inferenceTimeMs: Math.round(inferenceTimeMs * 100) / 100,\r\n numFrames,\r\n inputSamples,\r\n });\r\n\r\n span?.setAttributes({\r\n 'inference.duration_ms': inferenceTimeMs,\r\n 'inference.frames': numFrames,\r\n });\r\n span?.end();\r\n telemetry?.recordHistogram('omote.inference.latency', inferenceTimeMs, {\r\n model: 'wav2arkit_cpu',\r\n backend: this._backend,\r\n });\r\n telemetry?.incrementCounter('omote.inference.total', 1, {\r\n model: 'wav2arkit_cpu',\r\n backend: this._backend,\r\n status: 'success',\r\n });\r\n\r\n resolve({\r\n blendshapes,\r\n numFrames,\r\n inferenceTimeMs,\r\n });\r\n } catch (err) {\r\n span?.endWithError(err instanceof Error ? err : new Error(String(err)));\r\n telemetry?.incrementCounter('omote.inference.total', 1, {\r\n model: 'wav2arkit_cpu',\r\n backend: this._backend,\r\n status: 'error',\r\n });\r\n reject(err);\r\n }\r\n });\r\n });\r\n }\r\n\r\n /**\r\n * Dispose of the model and free resources\r\n */\r\n async dispose(): Promise<void> {\r\n if (this.session) {\r\n await this.session.release();\r\n this.session = null;\r\n }\r\n }\r\n}\r\n","/**\r\n * Factory function for lip sync with automatic GPU/CPU model selection\r\n *\r\n * Provides a unified API that automatically selects the optimal model:\r\n * - Safari (macOS + iOS): Uses Wav2ArkitCpuInference (404MB, WASM)\r\n * - Chrome/Firefox/Edge: Uses Wav2Vec2Inference (384MB, WebGPU)\r\n * - Fallback: Gracefully falls back to CPU model if GPU model fails to load\r\n *\r\n * Why two separate models?\r\n * Wav2Vec2 (LAM) cannot run on Safari/iOS for two reasons:\r\n * 1. Its dual-head transformer graph needs ~750-950MB peak during ORT session\r\n * creation (graph optimization), exceeding iOS WebKit's ~1-1.5GB tab limit.\r\n * 2. It ships as a single 384MB .onnx file that must load into JS heap before\r\n * ORT can consume it. iOS WebKit OOMs on this allocation.\r\n * wav2arkit_cpu solves both: external data format (1.86MB graph + 402MB weights)\r\n * lets ORT load only the tiny graph, then stream weights via URL pass-through\r\n * directly into WASM memory. JS heap stays at ~2MB.\r\n *\r\n * @category Inference\r\n *\r\n * @example Auto-detect (recommended)\r\n * ```typescript\r\n * import { createLipSync } from '@omote/core';\r\n *\r\n * const lam = createLipSync({\r\n * gpuModelUrl: '/models/unified_wav2vec2_asr_a2e.onnx',\r\n * cpuModelUrl: '/models/wav2arkit_cpu.onnx',\r\n * });\r\n *\r\n * await lam.load();\r\n * const { blendshapes } = await lam.infer(audioSamples);\r\n * ```\r\n *\r\n * @example Force CPU model\r\n * ```typescript\r\n * const lam = createLipSync({\r\n * gpuModelUrl: '/models/unified_wav2vec2_asr_a2e.onnx',\r\n * cpuModelUrl: '/models/wav2arkit_cpu.onnx',\r\n * mode: 'cpu',\r\n * });\r\n * ```\r\n */\r\n\r\nimport { createLogger } from '../logging';\r\nimport { shouldUseCpuLipSync, isSafari } from '../utils/runtime';\r\nimport { Wav2Vec2Inference } from './Wav2Vec2Inference';\r\nimport { Wav2ArkitCpuInference } from './Wav2ArkitCpuInference';\r\nimport type { LipSyncBackend, LipSyncModelInfo, LipSyncResult } from './LipSyncBackend';\r\nimport type { RuntimeBackend, BackendPreference } from '../utils/runtime';\r\n\r\nconst logger = createLogger('createLipSync');\r\n\r\n/**\r\n * Configuration for the lip sync factory\r\n */\r\nexport interface CreateLipSyncConfig {\r\n /** URL for the GPU model (Wav2Vec2, used on Chrome/Firefox/Edge) */\r\n gpuModelUrl: string;\r\n /**\r\n * URL for GPU model external data file (.onnx.data weights).\r\n * Default: `${gpuModelUrl}.data`\r\n *\r\n * Set to `false` to skip external data loading (single-file models only).\r\n */\r\n gpuExternalDataUrl?: string | false;\r\n /** URL for the CPU model (wav2arkit_cpu, used on Safari/iOS) */\r\n cpuModelUrl: string;\r\n /**\r\n * Model selection mode:\r\n * - 'auto': Safari/iOS → CPU, everything else → GPU (default)\r\n * - 'gpu': Force GPU model (Wav2Vec2Inference)\r\n * - 'cpu': Force CPU model (Wav2ArkitCpuInference)\r\n */\r\n mode?: 'auto' | 'gpu' | 'cpu';\r\n /** Backend preference for GPU model (default: 'auto') */\r\n gpuBackend?: BackendPreference;\r\n /** Number of identity classes for GPU model (default: 12) */\r\n numIdentityClasses?: number;\r\n /**\r\n * Fall back to CPU model if GPU model fails to load (default: true)\r\n * Only applies when mode is 'auto' or 'gpu'\r\n */\r\n fallbackOnError?: boolean;\r\n}\r\n\r\n/**\r\n * Create a lip sync instance with automatic GPU/CPU model selection\r\n *\r\n * @param config - Factory configuration\r\n * @returns A LipSyncBackend instance (either GPU or CPU model)\r\n */\r\nexport function createLipSync(config: CreateLipSyncConfig): LipSyncBackend {\r\n const mode = config.mode ?? 'auto';\r\n const fallbackOnError = config.fallbackOnError ?? true;\r\n\r\n // Determine which model to use\r\n let useCpu: boolean;\r\n\r\n if (mode === 'cpu') {\r\n useCpu = true;\r\n logger.info('Forcing CPU lip sync model (wav2arkit_cpu)');\r\n } else if (mode === 'gpu') {\r\n useCpu = false;\r\n logger.info('Forcing GPU lip sync model (Wav2Vec2)');\r\n } else {\r\n // Auto-detect: Safari/iOS → CPU, everything else → GPU\r\n useCpu = shouldUseCpuLipSync();\r\n logger.info('Auto-detected lip sync model', {\r\n useCpu,\r\n isSafari: isSafari(),\r\n });\r\n }\r\n\r\n if (useCpu) {\r\n logger.info('Creating Wav2ArkitCpuInference (404MB, WASM)');\r\n return new Wav2ArkitCpuInference({\r\n modelUrl: config.cpuModelUrl,\r\n });\r\n }\r\n\r\n // GPU model, optionally with fallback\r\n const gpuInstance = new Wav2Vec2Inference({\r\n modelUrl: config.gpuModelUrl,\r\n externalDataUrl: config.gpuExternalDataUrl,\r\n backend: config.gpuBackend ?? 'auto',\r\n numIdentityClasses: config.numIdentityClasses,\r\n });\r\n\r\n if (fallbackOnError) {\r\n logger.info('Creating Wav2Vec2Inference with CPU fallback');\r\n return new LipSyncWithFallback(gpuInstance, config);\r\n }\r\n\r\n logger.info('Creating Wav2Vec2Inference (no fallback)');\r\n return gpuInstance;\r\n}\r\n\r\n/**\r\n * Wrapper that provides automatic fallback from GPU to CPU model\r\n *\r\n * If the GPU model fails during load(), this wrapper automatically\r\n * creates a Wav2ArkitCpuInference instance instead.\r\n */\r\nclass LipSyncWithFallback implements LipSyncBackend {\r\n private implementation: LipSyncBackend;\r\n private readonly config: CreateLipSyncConfig;\r\n private hasFallenBack = false;\r\n\r\n constructor(gpuInstance: Wav2Vec2Inference, config: CreateLipSyncConfig) {\r\n this.implementation = gpuInstance;\r\n this.config = config;\r\n }\r\n\r\n get modelId(): 'wav2vec2' | 'wav2arkit_cpu' {\r\n return this.implementation.modelId;\r\n }\r\n\r\n get backend(): RuntimeBackend | null {\r\n return this.implementation.backend;\r\n }\r\n\r\n get isLoaded(): boolean {\r\n return this.implementation.isLoaded;\r\n }\r\n\r\n async load(): Promise<LipSyncModelInfo> {\r\n try {\r\n return await this.implementation.load();\r\n } catch (error) {\r\n return this.fallbackToCpu(error instanceof Error ? error.message : String(error));\r\n }\r\n }\r\n\r\n private async fallbackToCpu(reason: string): Promise<LipSyncModelInfo> {\r\n logger.warn('GPU model load failed, falling back to CPU model', { reason });\r\n\r\n // Clean up failed GPU instance\r\n try {\r\n await this.implementation.dispose();\r\n } catch {\r\n // Ignore dispose errors\r\n }\r\n\r\n // Create CPU fallback\r\n this.implementation = new Wav2ArkitCpuInference({\r\n modelUrl: this.config.cpuModelUrl,\r\n });\r\n this.hasFallenBack = true;\r\n\r\n logger.info('Fallback to Wav2ArkitCpuInference successful');\r\n return await this.implementation.load();\r\n }\r\n\r\n async infer(audioSamples: Float32Array, identityIndex?: number): Promise<LipSyncResult> {\r\n return this.implementation.infer(audioSamples, identityIndex);\r\n }\r\n\r\n async dispose(): Promise<void> {\r\n return this.implementation.dispose();\r\n }\r\n}\r\n","/**\r\n * Silero VAD (Voice Activity Detection) inference\r\n *\r\n * Neural network-based VAD running in browser via ONNX Runtime Web.\r\n * Much more accurate than RMS-based energy detection.\r\n *\r\n * Uses lazy loading to conditionally load WebGPU or WASM-only bundle:\r\n * - iOS: Loads WASM-only bundle (WebGPU crashes due to Safari bugs)\r\n * - Android/Desktop: Loads WebGPU bundle (with WASM fallback)\r\n *\r\n * @category Inference\r\n *\r\n * @example Basic usage\r\n * ```typescript\r\n * import { SileroVADInference } from '@omote/core';\r\n *\r\n * const vad = new SileroVADInference({\r\n * modelUrl: '/models/silero-vad.onnx'\r\n * });\r\n * await vad.load();\r\n *\r\n * // Process 32ms chunks (512 samples at 16kHz)\r\n * const probability = await vad.process(audioChunk);\r\n * if (probability > 0.5) {\r\n * console.log('Speech detected!');\r\n * }\r\n * ```\r\n *\r\n * @example Streaming with state management\r\n * ```typescript\r\n * // State is automatically maintained between process() calls\r\n * // Call reset() when starting a new audio stream\r\n * vad.reset();\r\n *\r\n * for (const chunk of audioChunks) {\r\n * const prob = await vad.process(chunk);\r\n * // prob is speech probability [0, 1]\r\n * }\r\n * ```\r\n */\r\n\r\n// Type-only import for TypeScript (no runtime code loaded at import time)\r\n// At runtime, we dynamically import either 'onnxruntime-web' or 'onnxruntime-web/webgpu'\r\nimport type { InferenceSession, Tensor, Env } from 'onnxruntime-common';\r\n\r\n// Type alias for the ORT module (loaded dynamically)\r\ntype OrtModule = {\r\n InferenceSession: typeof InferenceSession;\r\n Tensor: typeof Tensor;\r\n env: Env;\r\n};\r\nimport { fetchWithCache, getModelCache, formatBytes } from '../cache/ModelCache';\r\nimport { createLogger } from '../logging';\r\nimport { getTelemetry } from '../telemetry';\r\nimport {\r\n getOnnxRuntimeForPreference,\r\n getSessionOptions,\r\n isWebGPUAvailable,\r\n type RuntimeBackend,\r\n} from './onnxLoader';\r\nimport { BackendPreference } from '../utils/runtime';\r\n\r\nconst logger = createLogger('SileroVAD');\r\n\r\nexport type VADBackend = BackendPreference;\r\n\r\n/**\r\n * Configuration for Silero VAD\r\n */\r\nexport interface SileroVADConfig {\r\n /** Path or URL to the ONNX model */\r\n modelUrl: string;\r\n /** Preferred backend (auto will try WebGPU first, fallback to WASM) */\r\n backend?: VADBackend;\r\n /** Sample rate (8000 or 16000, default: 16000) */\r\n sampleRate?: 8000 | 16000;\r\n /** Speech probability threshold (default: 0.5) */\r\n threshold?: number;\r\n /**\r\n * Number of audio chunks to keep in pre-speech buffer.\r\n * When VAD triggers, these chunks are prepended to the speech buffer\r\n * to capture the beginning of speech that occurred before detection.\r\n *\r\n * At 512 samples/chunk and 16kHz:\r\n * - 10 chunks = 320ms of pre-speech audio\r\n * - 15 chunks = 480ms of pre-speech audio\r\n *\r\n * Default: 10 chunks (320ms)\r\n */\r\n preSpeechBufferChunks?: number;\r\n}\r\n\r\n/**\r\n * VAD model loading information\r\n */\r\nexport interface VADModelInfo {\r\n backend: 'webgpu' | 'wasm';\r\n loadTimeMs: number;\r\n inputNames: string[];\r\n outputNames: string[];\r\n sampleRate: number;\r\n chunkSize: number;\r\n}\r\n\r\n/**\r\n * Result from a single VAD inference\r\n */\r\nexport interface VADResult {\r\n /** Speech probability (0-1) */\r\n probability: number;\r\n /** Whether speech is detected (probability > threshold) */\r\n isSpeech: boolean;\r\n /** Inference time in milliseconds */\r\n inferenceTimeMs: number;\r\n /**\r\n * Pre-speech audio chunks (only present on first speech detection).\r\n * These are the N chunks immediately before VAD triggered, useful for\r\n * capturing the beginning of speech that occurred before detection.\r\n *\r\n * Only populated when transitioning from silence to speech.\r\n */\r\n preSpeechChunks?: Float32Array[];\r\n}\r\n\r\n/**\r\n * Speech segment detected by VAD\r\n */\r\nexport interface SpeechSegment {\r\n /** Start time in seconds */\r\n start: number;\r\n /** End time in seconds */\r\n end: number;\r\n /** Average probability during segment */\r\n avgProbability: number;\r\n}\r\n\r\n/**\r\n * Silero VAD - Neural network voice activity detection\r\n *\r\n * Based on snakers4/silero-vad ONNX model.\r\n * Processes 32ms chunks (512 samples at 16kHz) with LSTM state.\r\n *\r\n * @see https://github.com/snakers4/silero-vad\r\n */\r\nexport class SileroVADInference {\r\n private session: InferenceSession | null = null;\r\n private ort: OrtModule | null = null; // Lazy-loaded ONNX Runtime module\r\n private config: Required<SileroVADConfig>;\r\n private _backend: RuntimeBackend = 'wasm';\r\n private isLoading = false;\r\n\r\n // LSTM state tensors [2, batch_size, 128]\r\n private state: Tensor | null = null;\r\n\r\n // Context buffer (prepended to each chunk)\r\n private context: Float32Array;\r\n\r\n // Chunk sizes based on sample rate\r\n private readonly chunkSize: number;\r\n private readonly contextSize: number;\r\n\r\n // Inference queue for serialization\r\n private inferenceQueue: Promise<void> = Promise.resolve();\r\n\r\n // Pre-speech buffer for capturing beginning of speech\r\n private preSpeechBuffer: Float32Array[] = [];\r\n private wasSpeaking = false;\r\n\r\n // Cached sample rate tensor (int64 scalar, never changes per instance)\r\n private srTensor: Tensor | null = null;\r\n\r\n constructor(config: SileroVADConfig) {\r\n const sampleRate = config.sampleRate ?? 16000;\r\n\r\n if (sampleRate !== 8000 && sampleRate !== 16000) {\r\n throw new Error('Silero VAD only supports 8000 or 16000 Hz sample rates');\r\n }\r\n\r\n this.config = {\r\n modelUrl: config.modelUrl,\r\n backend: config.backend ?? 'auto',\r\n sampleRate,\r\n threshold: config.threshold ?? 0.5,\r\n preSpeechBufferChunks: config.preSpeechBufferChunks ?? 10,\r\n };\r\n\r\n // Set chunk sizes based on sample rate\r\n this.chunkSize = sampleRate === 16000 ? 512 : 256;\r\n this.contextSize = sampleRate === 16000 ? 64 : 32;\r\n this.context = new Float32Array(this.contextSize);\r\n }\r\n\r\n get backend(): RuntimeBackend | null {\r\n return this.session ? this._backend : null;\r\n }\r\n\r\n get isLoaded(): boolean {\r\n return this.session !== null;\r\n }\r\n\r\n get sampleRate(): number {\r\n return this.config.sampleRate;\r\n }\r\n\r\n get threshold(): number {\r\n return this.config.threshold;\r\n }\r\n\r\n /**\r\n * Get required chunk size in samples\r\n */\r\n getChunkSize(): number {\r\n return this.chunkSize;\r\n }\r\n\r\n /**\r\n * Get chunk duration in milliseconds\r\n */\r\n getChunkDurationMs(): number {\r\n return (this.chunkSize / this.config.sampleRate) * 1000;\r\n }\r\n\r\n /**\r\n * Check if WebGPU is available and working\r\n * (iOS returns false even if navigator.gpu exists due to ONNX Runtime bugs)\r\n */\r\n static isWebGPUAvailable = isWebGPUAvailable;\r\n\r\n /**\r\n * Load the ONNX model\r\n */\r\n async load(): Promise<VADModelInfo> {\r\n if (this.isLoading) {\r\n throw new Error('Model is already loading');\r\n }\r\n\r\n if (this.session) {\r\n throw new Error('Model already loaded. Call dispose() first.');\r\n }\r\n\r\n this.isLoading = true;\r\n const startTime = performance.now();\r\n const telemetry = getTelemetry();\r\n const span = telemetry?.startSpan('SileroVAD.load', {\r\n 'model.url': this.config.modelUrl,\r\n 'model.backend_requested': this.config.backend,\r\n 'model.sample_rate': this.config.sampleRate,\r\n });\r\n\r\n try {\r\n // Lazy load ONNX Runtime with appropriate backend\r\n // iOS: Loads WASM-only bundle (smaller, no WebGPU code)\r\n // Android/Desktop: Loads WebGPU bundle (with WASM fallback)\r\n logger.info('Loading ONNX Runtime...', { preference: this.config.backend });\r\n\r\n const { ort, backend } = await getOnnxRuntimeForPreference(this.config.backend);\r\n this.ort = ort;\r\n this._backend = backend;\r\n\r\n logger.info('ONNX Runtime loaded', { backend: this._backend });\r\n\r\n // Load model with caching\r\n const cache = getModelCache();\r\n const modelUrl = this.config.modelUrl;\r\n const isCached = await cache.has(modelUrl);\r\n\r\n let modelBuffer: ArrayBuffer;\r\n if (isCached) {\r\n logger.debug('Loading model from cache', { modelUrl });\r\n modelBuffer = (await cache.get(modelUrl))!;\r\n } else {\r\n logger.debug('Fetching and caching model', { modelUrl });\r\n modelBuffer = await fetchWithCache(modelUrl);\r\n }\r\n\r\n logger.debug('Creating ONNX session', {\r\n size: formatBytes(modelBuffer.byteLength),\r\n backend: this._backend,\r\n });\r\n\r\n // Create session with optimized settings for the backend\r\n // Convert ArrayBuffer to Uint8Array for onnxruntime-common types\r\n const sessionOptions = getSessionOptions(this._backend);\r\n const modelData = new Uint8Array(modelBuffer);\r\n this.session = await ort.InferenceSession.create(modelData, sessionOptions);\r\n\r\n // Initialize state\r\n this.reset();\r\n\r\n const loadTimeMs = performance.now() - startTime;\r\n\r\n logger.info('Model loaded successfully', {\r\n backend: this._backend,\r\n loadTimeMs: Math.round(loadTimeMs),\r\n sampleRate: this.config.sampleRate,\r\n chunkSize: this.chunkSize,\r\n threshold: this.config.threshold,\r\n });\r\n\r\n span?.setAttributes({\r\n 'model.backend': this._backend,\r\n 'model.load_time_ms': loadTimeMs,\r\n 'model.cached': isCached,\r\n });\r\n span?.end();\r\n telemetry?.recordHistogram('omote.model.load_time', loadTimeMs, {\r\n model: 'silero-vad',\r\n backend: this._backend,\r\n });\r\n\r\n return {\r\n backend: this._backend,\r\n loadTimeMs,\r\n inputNames: [...this.session.inputNames],\r\n outputNames: [...this.session.outputNames],\r\n sampleRate: this.config.sampleRate,\r\n chunkSize: this.chunkSize,\r\n };\r\n } catch (error) {\r\n span?.endWithError(error instanceof Error ? error : new Error(String(error)));\r\n telemetry?.incrementCounter('omote.errors.total', 1, {\r\n model: 'silero-vad',\r\n error_type: 'load_failed',\r\n });\r\n throw error;\r\n } finally {\r\n this.isLoading = false;\r\n }\r\n }\r\n\r\n /**\r\n * Reset state for new audio stream\r\n */\r\n reset(): void {\r\n if (!this.ort) {\r\n throw new Error('ONNX Runtime not loaded. Call load() first.');\r\n }\r\n // LSTM state: [2, batch_size=1, 128]\r\n this.state = new this.ort.Tensor('float32', new Float32Array(2 * 1 * 128), [2, 1, 128]);\r\n // Reset context buffer\r\n this.context = new Float32Array(this.contextSize);\r\n // Reset pre-speech buffer\r\n this.preSpeechBuffer = [];\r\n this.wasSpeaking = false;\r\n\r\n // Create cached sr tensor once (int64 scalar, never changes)\r\n if (!this.srTensor) {\r\n try {\r\n this.srTensor = new this.ort.Tensor(\r\n 'int64',\r\n new BigInt64Array([BigInt(this.config.sampleRate)]),\r\n []\r\n );\r\n } catch (e) {\r\n // Fallback: some iOS Safari versions may not support BigInt64Array\r\n // ORT also accepts readonly bigint[] for int64 tensors\r\n logger.warn('BigInt64Array not available, using bigint array fallback', {\r\n error: e instanceof Error ? e.message : String(e),\r\n });\r\n this.srTensor = new this.ort.Tensor(\r\n 'int64',\r\n [BigInt(this.config.sampleRate)] as unknown as BigInt64Array,\r\n []\r\n );\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Process a single audio chunk\r\n *\r\n * @param audioChunk - Float32Array of exactly chunkSize samples (512 for 16kHz, 256 for 8kHz)\r\n * @returns VAD result with speech probability\r\n */\r\n async process(audioChunk: Float32Array): Promise<VADResult> {\r\n if (!this.session) {\r\n throw new Error('Model not loaded. Call load() first.');\r\n }\r\n\r\n if (audioChunk.length !== this.chunkSize) {\r\n throw new Error(\r\n `Audio chunk must be exactly ${this.chunkSize} samples (got ${audioChunk.length}). ` +\r\n `Use getChunkSize() to get required size.`\r\n );\r\n }\r\n\r\n return this.queueInference(audioChunk);\r\n }\r\n\r\n /**\r\n * Process audio and detect speech segments\r\n *\r\n * @param audio - Complete audio buffer\r\n * @param options - Detection options\r\n * @returns Array of speech segments\r\n */\r\n async detectSpeech(\r\n audio: Float32Array,\r\n options: {\r\n /** Minimum speech duration in ms (default: 250) */\r\n minSpeechDurationMs?: number;\r\n /** Minimum silence duration to end segment in ms (default: 300) */\r\n minSilenceDurationMs?: number;\r\n /** Padding to add before/after speech in ms (default: 30) */\r\n speechPadMs?: number;\r\n } = {}\r\n ): Promise<SpeechSegment[]> {\r\n const {\r\n minSpeechDurationMs = 250,\r\n minSilenceDurationMs = 300,\r\n speechPadMs = 30,\r\n } = options;\r\n\r\n this.reset();\r\n\r\n const segments: SpeechSegment[] = [];\r\n const chunkDurationMs = this.getChunkDurationMs();\r\n const minSpeechChunks = Math.ceil(minSpeechDurationMs / chunkDurationMs);\r\n const minSilenceChunks = Math.ceil(minSilenceDurationMs / chunkDurationMs);\r\n const padChunks = Math.ceil(speechPadMs / chunkDurationMs);\r\n\r\n let inSpeech = false;\r\n let speechStart = 0;\r\n let silenceCount = 0;\r\n let speechChunks = 0;\r\n let totalProb = 0;\r\n\r\n // Process in chunks\r\n for (let i = 0; i + this.chunkSize <= audio.length; i += this.chunkSize) {\r\n const chunk = audio.slice(i, i + this.chunkSize);\r\n const result = await this.process(chunk);\r\n const chunkIndex = i / this.chunkSize;\r\n const timeMs = chunkIndex * chunkDurationMs;\r\n\r\n if (result.isSpeech) {\r\n if (!inSpeech) {\r\n // Start of speech\r\n inSpeech = true;\r\n speechStart = Math.max(0, timeMs - speechPadMs);\r\n silenceCount = 0;\r\n speechChunks = 0;\r\n totalProb = 0;\r\n }\r\n silenceCount = 0;\r\n speechChunks++;\r\n totalProb += result.probability;\r\n } else if (inSpeech) {\r\n silenceCount++;\r\n if (silenceCount >= minSilenceChunks) {\r\n // End of speech\r\n if (speechChunks >= minSpeechChunks) {\r\n segments.push({\r\n start: speechStart / 1000,\r\n end: (timeMs + speechPadMs) / 1000,\r\n avgProbability: totalProb / speechChunks,\r\n });\r\n }\r\n inSpeech = false;\r\n }\r\n }\r\n }\r\n\r\n // Handle trailing speech\r\n if (inSpeech && speechChunks >= minSpeechChunks) {\r\n const endMs = (audio.length / this.config.sampleRate) * 1000;\r\n segments.push({\r\n start: speechStart / 1000,\r\n end: endMs / 1000,\r\n avgProbability: totalProb / speechChunks,\r\n });\r\n }\r\n\r\n return segments;\r\n }\r\n\r\n /**\r\n * Calculate RMS energy of audio chunk\r\n */\r\n private calculateRMS(samples: Float32Array): number {\r\n let sum = 0;\r\n for (let i = 0; i < samples.length; i++) {\r\n sum += samples[i] * samples[i];\r\n }\r\n return Math.sqrt(sum / samples.length);\r\n }\r\n\r\n /**\r\n * Queue inference to serialize ONNX session calls\r\n */\r\n private queueInference(audioChunk: Float32Array): Promise<VADResult> {\r\n // CRITICAL: Force copy IMMEDIATELY to prevent ArrayBuffer detachment\r\n // During interruptions, audioChunk's buffer may get detached by ONNX Runtime\r\n // before we access it in the async queue. Copy synchronously to preserve data.\r\n const audioChunkCopy = new Float32Array(audioChunk);\r\n\r\n // Energy pre-filter: skip inference on very quiet audio\r\n // This prevents false positives from blank/silent chunks and saves compute\r\n const MIN_ENERGY_THRESHOLD = 0.001; // Very low threshold - only filters near-silence\r\n const rms = this.calculateRMS(audioChunkCopy);\r\n if (rms < MIN_ENERGY_THRESHOLD) {\r\n // Update pre-speech buffer even for silent chunks (ring buffer)\r\n if (!this.wasSpeaking) {\r\n this.preSpeechBuffer.push(new Float32Array(audioChunkCopy));\r\n if (this.preSpeechBuffer.length > this.config.preSpeechBufferChunks) {\r\n this.preSpeechBuffer.shift();\r\n }\r\n }\r\n\r\n logger.trace('Skipping VAD inference - audio too quiet', {\r\n rms: Math.round(rms * 10000) / 10000,\r\n threshold: MIN_ENERGY_THRESHOLD,\r\n });\r\n\r\n return Promise.resolve({\r\n probability: 0,\r\n isSpeech: false,\r\n inferenceTimeMs: 0,\r\n });\r\n }\r\n\r\n return new Promise((resolve, reject) => {\r\n this.inferenceQueue = this.inferenceQueue.then(async () => {\r\n const telemetry = getTelemetry();\r\n const span = telemetry?.startSpan('SileroVAD.process', {\r\n 'inference.backend': this._backend,\r\n 'inference.chunk_size': this.chunkSize,\r\n });\r\n try {\r\n const startTime = performance.now();\r\n\r\n // Prepend context to input\r\n const inputSize = this.contextSize + this.chunkSize;\r\n const inputBuffer = new Float32Array(inputSize);\r\n inputBuffer.set(this.context, 0);\r\n inputBuffer.set(audioChunkCopy, this.contextSize);\r\n\r\n // Create tensors\r\n // CRITICAL: Force copy to prevent ArrayBuffer detachment by ONNX Runtime Web workers\r\n // Without copy, WASM backend transfers buffers to workers, causing \"memory access out of bounds\" errors\r\n const inputBufferCopy = new Float32Array(inputBuffer);\r\n const inputTensor = new this.ort!.Tensor('float32', inputBufferCopy, [1, inputSize]);\r\n // Use cached sr tensor (created once in reset(), handles BigInt64Array compatibility)\r\n const srTensor = this.srTensor!;\r\n\r\n // CRITICAL: Also copy state tensor to prevent detachment\r\n // State tensor is reused across inferences and gets detached during interruptions\r\n const stateCopy = new Float32Array(this.state!.data as Float32Array);\r\n const stateTensor = new this.ort!.Tensor('float32', stateCopy, this.state!.dims as number[]);\r\n\r\n const feeds = {\r\n 'input': inputTensor,\r\n 'state': stateTensor,\r\n 'sr': srTensor,\r\n };\r\n\r\n // Run inference\r\n const results = await this.session!.run(feeds);\r\n\r\n // Extract outputs\r\n const outputTensor = results['output'];\r\n const newStateTensor = results['stateN'] || results['state'];\r\n\r\n if (!outputTensor) {\r\n throw new Error('Missing output tensor from VAD model');\r\n }\r\n\r\n const probability = (outputTensor.data as Float32Array)[0];\r\n\r\n // Update state for next call\r\n if (newStateTensor) {\r\n this.state = new this.ort!.Tensor(\r\n 'float32',\r\n new Float32Array(newStateTensor.data as Float32Array),\r\n [2, 1, 128]\r\n );\r\n }\r\n\r\n // Update context (last contextSize samples of input chunk)\r\n this.context = audioChunk.slice(-this.contextSize);\r\n\r\n const inferenceTimeMs = performance.now() - startTime;\r\n const isSpeech = probability > this.config.threshold;\r\n\r\n // Pre-speech buffer logic\r\n let preSpeechChunks: Float32Array[] | undefined;\r\n\r\n if (isSpeech && !this.wasSpeaking) {\r\n // Silence→Speech transition: populate preSpeechChunks\r\n preSpeechChunks = [...this.preSpeechBuffer];\r\n this.preSpeechBuffer = [];\r\n logger.debug('Speech started with pre-speech buffer', {\r\n preSpeechChunks: preSpeechChunks.length,\r\n durationMs: Math.round(preSpeechChunks.length * this.getChunkDurationMs()),\r\n });\r\n } else if (!isSpeech && !this.wasSpeaking) {\r\n // Still in silence: maintain ring buffer\r\n this.preSpeechBuffer.push(new Float32Array(audioChunk));\r\n if (this.preSpeechBuffer.length > this.config.preSpeechBufferChunks) {\r\n this.preSpeechBuffer.shift();\r\n }\r\n } else if (!isSpeech && this.wasSpeaking) {\r\n // Speech→Silence transition: clear buffer\r\n this.preSpeechBuffer = [];\r\n }\r\n\r\n this.wasSpeaking = isSpeech;\r\n\r\n logger.trace('VAD inference completed', {\r\n probability: Math.round(probability * 1000) / 1000,\r\n isSpeech,\r\n inferenceTimeMs: Math.round(inferenceTimeMs * 100) / 100,\r\n });\r\n\r\n span?.setAttributes({\r\n 'inference.duration_ms': inferenceTimeMs,\r\n 'inference.probability': probability,\r\n 'inference.is_speech': isSpeech,\r\n });\r\n span?.end();\r\n telemetry?.recordHistogram('omote.inference.latency', inferenceTimeMs, {\r\n model: 'silero-vad',\r\n backend: this._backend,\r\n });\r\n telemetry?.incrementCounter('omote.inference.total', 1, {\r\n model: 'silero-vad',\r\n backend: this._backend,\r\n status: 'success',\r\n });\r\n\r\n resolve({\r\n probability,\r\n isSpeech,\r\n inferenceTimeMs,\r\n preSpeechChunks,\r\n });\r\n } catch (err) {\r\n span?.endWithError(err instanceof Error ? err : new Error(String(err)));\r\n telemetry?.incrementCounter('omote.inference.total', 1, {\r\n model: 'silero-vad',\r\n backend: this._backend,\r\n status: 'error',\r\n });\r\n reject(err);\r\n }\r\n });\r\n });\r\n }\r\n\r\n /**\r\n * Dispose of the model and free resources\r\n */\r\n async dispose(): Promise<void> {\r\n if (this.session) {\r\n await this.session.release();\r\n this.session = null;\r\n }\r\n this.state = null;\r\n this.srTensor = null;\r\n }\r\n}\r\n","/**\n * Silero VAD Web Worker implementation\n *\n * Runs Silero VAD inference in a dedicated Web Worker to prevent main thread blocking.\n * Uses inline worker script (Blob URL pattern) to avoid separate file deployment.\n *\n * Key design decisions:\n * - WASM backend only (WebGPU doesn't work in Workers)\n * - LSTM state serialized as Float32Array (Tensors can't cross worker boundary)\n * - Audio copied (not transferred) to retain main thread access for pre-speech buffer\n * - ONNX Runtime loaded from CDN in worker (no bundler complications)\n *\n * @category Inference\n *\n * @example Basic usage\n * ```typescript\n * import { SileroVADWorker } from '@omote/core';\n *\n * const vad = new SileroVADWorker({\n * modelUrl: '/models/silero-vad.onnx'\n * });\n * await vad.load();\n *\n * // Process 32ms chunks (512 samples at 16kHz)\n * const result = await vad.process(audioChunk);\n * if (result.isSpeech) {\n * console.log('Speech detected!', result.probability);\n * }\n * ```\n */\n\nimport { createLogger } from '../logging';\nimport { getTelemetry } from '../telemetry';\n\nconst logger = createLogger('SileroVADWorker');\n\n// ONNX Runtime CDN path (matches onnxLoader.ts)\nconst WASM_CDN_PATH = 'https://cdn.jsdelivr.net/npm/onnxruntime-web@1.23.2/dist/';\n\n// Worker script timeouts\nconst LOAD_TIMEOUT_MS = 10000; // 10 seconds for model load\nconst INFERENCE_TIMEOUT_MS = 1000; // 1 second for inference\n\n/**\n * Messages sent from main thread to worker\n */\nexport type VADWorkerMessage =\n | { type: 'load'; modelUrl: string; sampleRate: 8000 | 16000; wasmPaths: string }\n | { type: 'process'; audio: Float32Array; state: Float32Array; context: Float32Array }\n | { type: 'reset' }\n | { type: 'dispose' };\n\n/**\n * Messages sent from worker to main thread\n */\nexport type VADWorkerResult =\n | { type: 'loaded'; inputNames: string[]; outputNames: string[]; loadTimeMs: number }\n | { type: 'result'; probability: number; state: Float32Array; inferenceTimeMs: number }\n | { type: 'reset'; state: Float32Array }\n | { type: 'error'; error: string }\n | { type: 'disposed' };\n\n/**\n * Configuration for Silero VAD Worker\n */\nexport interface VADWorkerConfig {\n /** Path or URL to the ONNX model */\n modelUrl: string;\n /** Sample rate (8000 or 16000, default: 16000) */\n sampleRate?: 8000 | 16000;\n /** Speech probability threshold (default: 0.5) */\n threshold?: number;\n /**\n * Number of audio chunks to keep in pre-speech buffer.\n * When VAD triggers, these chunks are prepended to the speech buffer\n * to capture the beginning of speech that occurred before detection.\n *\n * At 512 samples/chunk and 16kHz:\n * - 10 chunks = 320ms of pre-speech audio\n * - 15 chunks = 480ms of pre-speech audio\n *\n * Default: 10 chunks (320ms)\n */\n preSpeechBufferChunks?: number;\n}\n\n/**\n * VAD model loading information from worker\n */\nexport interface VADWorkerModelInfo {\n backend: 'wasm'; // Worker always uses WASM (no WebGPU in workers)\n loadTimeMs: number;\n inputNames: string[];\n outputNames: string[];\n sampleRate: number;\n chunkSize: number;\n}\n\n/**\n * Result from a single VAD inference\n */\nexport interface VADResult {\n /** Speech probability (0-1) */\n probability: number;\n /** Whether speech is detected (probability > threshold) */\n isSpeech: boolean;\n /** Inference time in milliseconds */\n inferenceTimeMs: number;\n /**\n * Pre-speech audio chunks (only present on first speech detection).\n * These are the N chunks immediately before VAD triggered, useful for\n * capturing the beginning of speech that occurred before detection.\n *\n * Only populated when transitioning from silence to speech.\n */\n preSpeechChunks?: Float32Array[];\n}\n\n/**\n * Inline worker script for VAD inference\n *\n * This script is embedded as a string and loaded via Blob URL.\n * It loads ONNX Runtime from CDN and runs VAD inference.\n */\nconst WORKER_SCRIPT = `\n// Silero VAD Worker Script\n// Loaded via Blob URL - no separate file needed\n\nlet ort = null;\nlet session = null;\nlet sampleRate = 16000;\nlet chunkSize = 512;\nlet contextSize = 64;\n\n/**\n * Load ONNX Runtime from CDN\n */\nasync function loadOrt(wasmPaths) {\n if (ort) return;\n\n // Import ONNX Runtime from CDN\n // Using dynamic import with full CDN URL\n const ortUrl = wasmPaths + 'ort.wasm.min.js';\n\n // Load the script by fetching and executing it\n const response = await fetch(ortUrl);\n const scriptText = await response.text();\n\n // Create a blob URL for the script\n const blob = new Blob([scriptText], { type: 'application/javascript' });\n const blobUrl = URL.createObjectURL(blob);\n\n // Import the module\n importScripts(blobUrl);\n URL.revokeObjectURL(blobUrl);\n\n // ort is now available as global\n ort = self.ort;\n\n // Configure WASM settings\n ort.env.wasm.wasmPaths = wasmPaths;\n ort.env.wasm.numThreads = 1; // Single thread in worker\n ort.env.wasm.simd = true;\n ort.env.wasm.proxy = false; // No proxy in worker\n}\n\n/**\n * Load the VAD model\n */\nasync function loadModel(modelUrl, sr) {\n sampleRate = sr;\n chunkSize = sr === 16000 ? 512 : 256;\n contextSize = sr === 16000 ? 64 : 32;\n\n // Fetch model data\n const response = await fetch(modelUrl);\n if (!response.ok) {\n throw new Error('Failed to fetch model: ' + response.status + ' ' + response.statusText);\n }\n const modelBuffer = await response.arrayBuffer();\n const modelData = new Uint8Array(modelBuffer);\n\n // Create session with WASM backend\n session = await ort.InferenceSession.create(modelData, {\n executionProviders: ['wasm'],\n graphOptimizationLevel: 'all',\n });\n\n return {\n inputNames: session.inputNames,\n outputNames: session.outputNames,\n };\n}\n\n/**\n * Create initial LSTM state\n */\nfunction createInitialState() {\n return new Float32Array(2 * 1 * 128); // [2, 1, 128]\n}\n\n/**\n * Run VAD inference\n */\nasync function runInference(audio, state, context) {\n const inputSize = contextSize + chunkSize;\n\n // Prepend context to input\n const inputBuffer = new Float32Array(inputSize);\n inputBuffer.set(context, 0);\n inputBuffer.set(audio, contextSize);\n\n // Create tensors\n const inputTensor = new ort.Tensor('float32', new Float32Array(inputBuffer), [1, inputSize]);\n const stateTensor = new ort.Tensor('float32', new Float32Array(state), [2, 1, 128]);\n // Use BigInt64Array constructor (not .from()) for broader compatibility\n let srTensor;\n try {\n srTensor = new ort.Tensor('int64', new BigInt64Array([BigInt(sampleRate)]), []);\n } catch (e) {\n // Fallback for environments without BigInt64Array support\n srTensor = new ort.Tensor('int64', [BigInt(sampleRate)], []);\n }\n\n const feeds = {\n 'input': inputTensor,\n 'state': stateTensor,\n 'sr': srTensor,\n };\n\n // Run inference\n const results = await session.run(feeds);\n\n // Extract outputs\n const outputTensor = results['output'];\n const newStateTensor = results['stateN'] || results['state'];\n\n if (!outputTensor) {\n throw new Error('Missing output tensor from VAD model');\n }\n\n const probability = outputTensor.data[0];\n const newState = new Float32Array(newStateTensor.data);\n\n return { probability, newState };\n}\n\n// Message handler\nself.onmessage = async function(e) {\n const msg = e.data;\n\n try {\n switch (msg.type) {\n case 'load': {\n const startTime = performance.now();\n await loadOrt(msg.wasmPaths);\n const { inputNames, outputNames } = await loadModel(msg.modelUrl, msg.sampleRate);\n const loadTimeMs = performance.now() - startTime;\n\n self.postMessage({\n type: 'loaded',\n inputNames,\n outputNames,\n loadTimeMs,\n });\n break;\n }\n\n case 'process': {\n const startTime = performance.now();\n const { probability, newState } = await runInference(msg.audio, msg.state, msg.context);\n const inferenceTimeMs = performance.now() - startTime;\n\n self.postMessage({\n type: 'result',\n probability,\n state: newState,\n inferenceTimeMs,\n });\n break;\n }\n\n case 'reset': {\n const state = createInitialState();\n self.postMessage({\n type: 'reset',\n state,\n });\n break;\n }\n\n case 'dispose': {\n if (session) {\n await session.release();\n session = null;\n }\n ort = null;\n self.postMessage({ type: 'disposed' });\n break;\n }\n\n default:\n self.postMessage({\n type: 'error',\n error: 'Unknown message type: ' + msg.type,\n });\n }\n } catch (err) {\n self.postMessage({\n type: 'error',\n error: err.message || String(err),\n });\n }\n};\n\n// Error handler\nself.onerror = function(err) {\n self.postMessage({\n type: 'error',\n error: 'Worker error: ' + (err.message || String(err)),\n });\n};\n`;\n\n/**\n * Silero VAD Worker - Voice Activity Detection in a Web Worker\n *\n * Runs Silero VAD inference off the main thread to prevent UI blocking.\n * Feature parity with SileroVADInference but runs in dedicated worker.\n *\n * @see SileroVADInference for main-thread version\n */\nexport class SileroVADWorker {\n private worker: Worker | null = null;\n private config: Required<VADWorkerConfig>;\n private isLoading = false;\n private _isLoaded = false;\n\n // LSTM state (kept in main thread, sent to worker for each inference)\n private state: Float32Array;\n\n // Context buffer (last 64 samples for 16kHz, 32 for 8kHz)\n private context: Float32Array;\n\n // Chunk sizes based on sample rate\n private readonly chunkSize: number;\n private readonly contextSize: number;\n\n // Inference queue for serialization\n private inferenceQueue: Promise<void> = Promise.resolve();\n\n // Pre-speech buffer for capturing beginning of speech\n private preSpeechBuffer: Float32Array[] = [];\n private wasSpeaking = false;\n\n // Pending message handlers\n private pendingResolvers: Map<string, { resolve: (value: unknown) => void; reject: (error: Error) => void }> = new Map();\n private messageId = 0;\n\n constructor(config: VADWorkerConfig) {\n const sampleRate = config.sampleRate ?? 16000;\n\n if (sampleRate !== 8000 && sampleRate !== 16000) {\n throw new Error('Silero VAD only supports 8000 or 16000 Hz sample rates');\n }\n\n this.config = {\n modelUrl: config.modelUrl,\n sampleRate,\n threshold: config.threshold ?? 0.5,\n preSpeechBufferChunks: config.preSpeechBufferChunks ?? 10,\n };\n\n // Set chunk sizes based on sample rate\n this.chunkSize = sampleRate === 16000 ? 512 : 256;\n this.contextSize = sampleRate === 16000 ? 64 : 32;\n\n // Initialize state and context\n this.state = new Float32Array(2 * 1 * 128); // [2, 1, 128]\n this.context = new Float32Array(this.contextSize);\n }\n\n get isLoaded(): boolean {\n return this._isLoaded;\n }\n\n /**\n * Backend type (always 'wasm' for Worker, WebGPU not supported in Workers)\n */\n get backend(): 'wasm' | null {\n return this._isLoaded ? 'wasm' : null;\n }\n\n get sampleRate(): number {\n return this.config.sampleRate;\n }\n\n get threshold(): number {\n return this.config.threshold;\n }\n\n /**\n * Get required chunk size in samples\n */\n getChunkSize(): number {\n return this.chunkSize;\n }\n\n /**\n * Get chunk duration in milliseconds\n */\n getChunkDurationMs(): number {\n return (this.chunkSize / this.config.sampleRate) * 1000;\n }\n\n /**\n * Create the worker from inline script\n */\n private createWorker(): Worker {\n const blob = new Blob([WORKER_SCRIPT], { type: 'application/javascript' });\n const blobUrl = URL.createObjectURL(blob);\n const worker = new Worker(blobUrl);\n\n // Revoke blob URL after worker is created (worker has its own copy)\n URL.revokeObjectURL(blobUrl);\n\n // Set up message handler\n worker.onmessage = (event: MessageEvent<VADWorkerResult>) => {\n this.handleWorkerMessage(event.data);\n };\n\n // Set up error handler\n worker.onerror = (error) => {\n logger.error('Worker error', { error: error.message });\n // Reject any pending operations\n for (const [, resolver] of this.pendingResolvers) {\n resolver.reject(new Error(`Worker error: ${error.message}`));\n }\n this.pendingResolvers.clear();\n };\n\n return worker;\n }\n\n /**\n * Handle messages from worker\n */\n private handleWorkerMessage(result: VADWorkerResult): void {\n // Route to pending resolver based on result type\n const resolver = this.pendingResolvers.get(result.type);\n if (resolver) {\n this.pendingResolvers.delete(result.type);\n if (result.type === 'error') {\n resolver.reject(new Error(result.error));\n } else {\n resolver.resolve(result);\n }\n }\n }\n\n /**\n * Send message to worker and wait for response\n */\n private sendMessage<T>(message: VADWorkerMessage, expectedType: string, timeoutMs: number): Promise<T> {\n return new Promise((resolve, reject) => {\n if (!this.worker) {\n reject(new Error('Worker not initialized'));\n return;\n }\n\n // Set up timeout\n const timeoutId = setTimeout(() => {\n this.pendingResolvers.delete(expectedType);\n reject(new Error(`Worker operation timed out after ${timeoutMs}ms`));\n }, timeoutMs);\n\n // Register resolver\n this.pendingResolvers.set(expectedType, {\n resolve: (value) => {\n clearTimeout(timeoutId);\n resolve(value as T);\n },\n reject: (error) => {\n clearTimeout(timeoutId);\n reject(error);\n },\n });\n\n // Also listen for errors\n this.pendingResolvers.set('error', {\n resolve: () => {}, // Never called for errors\n reject: (error) => {\n clearTimeout(timeoutId);\n this.pendingResolvers.delete(expectedType);\n reject(error);\n },\n });\n\n // Send message\n this.worker.postMessage(message);\n });\n }\n\n /**\n * Load the ONNX model in the worker\n */\n async load(): Promise<VADWorkerModelInfo> {\n if (this.isLoading) {\n throw new Error('Model is already loading');\n }\n\n if (this._isLoaded) {\n throw new Error('Model already loaded. Call dispose() first.');\n }\n\n this.isLoading = true;\n const startTime = performance.now();\n const telemetry = getTelemetry();\n const span = telemetry?.startSpan('SileroVADWorker.load', {\n 'model.url': this.config.modelUrl,\n 'model.sample_rate': this.config.sampleRate,\n });\n\n try {\n logger.info('Creating VAD worker...');\n\n // Create worker\n this.worker = this.createWorker();\n\n logger.info('Loading model in worker...', {\n modelUrl: this.config.modelUrl,\n sampleRate: this.config.sampleRate,\n });\n\n // Send load message to worker\n const result = await this.sendMessage<{\n type: 'loaded';\n inputNames: string[];\n outputNames: string[];\n loadTimeMs: number;\n }>(\n {\n type: 'load',\n modelUrl: this.config.modelUrl,\n sampleRate: this.config.sampleRate,\n wasmPaths: WASM_CDN_PATH,\n },\n 'loaded',\n LOAD_TIMEOUT_MS\n );\n\n this._isLoaded = true;\n\n const loadTimeMs = performance.now() - startTime;\n\n logger.info('VAD worker loaded successfully', {\n backend: 'wasm',\n loadTimeMs: Math.round(loadTimeMs),\n workerLoadTimeMs: Math.round(result.loadTimeMs),\n sampleRate: this.config.sampleRate,\n chunkSize: this.chunkSize,\n threshold: this.config.threshold,\n });\n\n span?.setAttributes({\n 'model.backend': 'wasm',\n 'model.load_time_ms': loadTimeMs,\n 'model.worker_load_time_ms': result.loadTimeMs,\n });\n span?.end();\n telemetry?.recordHistogram('omote.model.load_time', loadTimeMs, {\n model: 'silero-vad-worker',\n backend: 'wasm',\n });\n\n return {\n backend: 'wasm',\n loadTimeMs,\n inputNames: result.inputNames,\n outputNames: result.outputNames,\n sampleRate: this.config.sampleRate,\n chunkSize: this.chunkSize,\n };\n } catch (error) {\n span?.endWithError(error instanceof Error ? error : new Error(String(error)));\n telemetry?.incrementCounter('omote.errors.total', 1, {\n model: 'silero-vad-worker',\n error_type: 'load_failed',\n });\n\n // Clean up on failure\n if (this.worker) {\n this.worker.terminate();\n this.worker = null;\n }\n\n throw error;\n } finally {\n this.isLoading = false;\n }\n }\n\n /**\n * Reset state for new audio stream\n */\n async reset(): Promise<void> {\n if (!this._isLoaded || !this.worker) {\n throw new Error('Worker not loaded. Call load() first.');\n }\n\n // Request reset from worker to get fresh state\n const result = await this.sendMessage<{ type: 'reset'; state: Float32Array }>(\n { type: 'reset' },\n 'reset',\n INFERENCE_TIMEOUT_MS\n );\n\n // Update local state\n this.state = result.state;\n this.context = new Float32Array(this.contextSize);\n this.preSpeechBuffer = [];\n this.wasSpeaking = false;\n }\n\n /**\n * Process a single audio chunk\n *\n * @param audioChunk - Float32Array of exactly chunkSize samples (512 for 16kHz, 256 for 8kHz)\n * @returns VAD result with speech probability\n */\n async process(audioChunk: Float32Array): Promise<VADResult> {\n if (!this._isLoaded || !this.worker) {\n throw new Error('Worker not loaded. Call load() first.');\n }\n\n if (audioChunk.length !== this.chunkSize) {\n throw new Error(\n `Audio chunk must be exactly ${this.chunkSize} samples (got ${audioChunk.length}). ` +\n `Use getChunkSize() to get required size.`\n );\n }\n\n return this.queueInference(audioChunk);\n }\n\n /**\n * Queue inference to serialize worker calls\n */\n private queueInference(audioChunk: Float32Array): Promise<VADResult> {\n // CRITICAL: Force copy IMMEDIATELY to prevent ArrayBuffer detachment\n const audioChunkCopy = new Float32Array(audioChunk);\n\n return new Promise((resolve, reject) => {\n this.inferenceQueue = this.inferenceQueue.then(async () => {\n const telemetry = getTelemetry();\n const span = telemetry?.startSpan('SileroVADWorker.process', {\n 'inference.backend': 'wasm',\n 'inference.chunk_size': this.chunkSize,\n });\n\n try {\n const startTime = performance.now();\n\n // Send process message to worker\n const result = await this.sendMessage<{\n type: 'result';\n probability: number;\n state: Float32Array;\n inferenceTimeMs: number;\n }>(\n {\n type: 'process',\n audio: audioChunkCopy,\n state: this.state,\n context: this.context,\n },\n 'result',\n INFERENCE_TIMEOUT_MS\n );\n\n // Update local state from worker result\n this.state = result.state;\n\n // Update context (last contextSize samples of input chunk)\n this.context = audioChunkCopy.slice(-this.contextSize);\n\n const inferenceTimeMs = performance.now() - startTime;\n const isSpeech = result.probability > this.config.threshold;\n\n // Pre-speech buffer logic (same as SileroVADInference)\n let preSpeechChunks: Float32Array[] | undefined;\n\n if (isSpeech && !this.wasSpeaking) {\n // Silence→Speech transition: populate preSpeechChunks\n preSpeechChunks = [...this.preSpeechBuffer];\n this.preSpeechBuffer = [];\n logger.debug('Speech started with pre-speech buffer', {\n preSpeechChunks: preSpeechChunks.length,\n durationMs: Math.round(preSpeechChunks.length * this.getChunkDurationMs()),\n });\n } else if (!isSpeech && !this.wasSpeaking) {\n // Still in silence: maintain ring buffer\n this.preSpeechBuffer.push(new Float32Array(audioChunkCopy));\n if (this.preSpeechBuffer.length > this.config.preSpeechBufferChunks) {\n this.preSpeechBuffer.shift();\n }\n } else if (!isSpeech && this.wasSpeaking) {\n // Speech→Silence transition: clear buffer\n this.preSpeechBuffer = [];\n }\n\n this.wasSpeaking = isSpeech;\n\n logger.trace('VAD worker inference completed', {\n probability: Math.round(result.probability * 1000) / 1000,\n isSpeech,\n inferenceTimeMs: Math.round(inferenceTimeMs * 100) / 100,\n workerTimeMs: Math.round(result.inferenceTimeMs * 100) / 100,\n });\n\n span?.setAttributes({\n 'inference.duration_ms': inferenceTimeMs,\n 'inference.worker_duration_ms': result.inferenceTimeMs,\n 'inference.probability': result.probability,\n 'inference.is_speech': isSpeech,\n });\n span?.end();\n telemetry?.recordHistogram('omote.inference.latency', inferenceTimeMs, {\n model: 'silero-vad-worker',\n backend: 'wasm',\n });\n telemetry?.incrementCounter('omote.inference.total', 1, {\n model: 'silero-vad-worker',\n backend: 'wasm',\n status: 'success',\n });\n\n resolve({\n probability: result.probability,\n isSpeech,\n inferenceTimeMs,\n preSpeechChunks,\n });\n } catch (err) {\n span?.endWithError(err instanceof Error ? err : new Error(String(err)));\n telemetry?.incrementCounter('omote.inference.total', 1, {\n model: 'silero-vad-worker',\n backend: 'wasm',\n status: 'error',\n });\n reject(err);\n }\n });\n });\n }\n\n /**\n * Dispose of the worker and free resources\n */\n async dispose(): Promise<void> {\n if (this.worker) {\n try {\n // Ask worker to clean up\n await this.sendMessage({ type: 'dispose' }, 'disposed', INFERENCE_TIMEOUT_MS);\n } catch {\n // Ignore errors during dispose\n }\n\n // Terminate worker\n this.worker.terminate();\n this.worker = null;\n }\n\n this._isLoaded = false;\n this.state = new Float32Array(2 * 1 * 128);\n this.context = new Float32Array(this.contextSize);\n this.preSpeechBuffer = [];\n this.wasSpeaking = false;\n this.pendingResolvers.clear();\n }\n\n /**\n * Check if Web Workers are supported\n */\n static isSupported(): boolean {\n return typeof Worker !== 'undefined';\n }\n}\n","/**\n * Factory function for Silero VAD with automatic Worker vs main thread selection\n *\n * Provides a unified API that automatically selects the optimal implementation:\n * - Desktop browsers: Uses SileroVADWorker (off-main-thread inference)\n * - Mobile devices: Uses SileroVADInference (main thread, avoids memory overhead)\n * - Fallback: Gracefully falls back to main thread if Worker fails\n *\n * @category Inference\n *\n * @example Basic usage (auto-detect)\n * ```typescript\n * import { createSileroVAD } from '@omote/core';\n *\n * const vad = createSileroVAD({\n * modelUrl: '/models/silero-vad.onnx',\n * threshold: 0.5,\n * });\n *\n * await vad.load();\n * const result = await vad.process(audioChunk);\n * if (result.isSpeech) {\n * console.log('Speech detected!', result.probability);\n * }\n * ```\n *\n * @example Force worker usage\n * ```typescript\n * const vad = createSileroVAD({\n * modelUrl: '/models/silero-vad.onnx',\n * useWorker: true, // Force Worker even on mobile\n * });\n * ```\n *\n * @example Force main thread\n * ```typescript\n * const vad = createSileroVAD({\n * modelUrl: '/models/silero-vad.onnx',\n * useWorker: false, // Force main thread\n * });\n * ```\n */\n\nimport { createLogger } from '../logging';\nimport { isMobile } from '../utils/runtime';\nimport { SileroVADInference } from './SileroVADInference';\nimport type { SileroVADConfig, VADModelInfo, VADResult } from './SileroVADInference';\nimport { SileroVADWorker } from './SileroVADWorker';\nimport type { VADWorkerModelInfo } from './SileroVADWorker';\nimport type { RuntimeBackend } from '../utils/runtime';\n\nconst logger = createLogger('createSileroVAD');\n\n/**\n * Common interface for both SileroVADInference and SileroVADWorker\n *\n * This interface defines the shared API that both implementations provide,\n * allowing consumers to use either interchangeably.\n */\nexport interface SileroVADBackend {\n /** Current backend type (webgpu, wasm, or null if not loaded) */\n readonly backend: RuntimeBackend | null;\n\n /** Whether the model is loaded and ready for inference */\n readonly isLoaded: boolean;\n\n /** Audio sample rate (8000 or 16000 Hz) */\n readonly sampleRate: number;\n\n /** Speech detection threshold (0-1) */\n readonly threshold: number;\n\n /**\n * Load the ONNX model\n * @returns Model loading information\n */\n load(): Promise<VADModelInfo | VADWorkerModelInfo>;\n\n /**\n * Process a single audio chunk\n * @param audioChunk - Float32Array of exactly chunkSize samples\n * @returns VAD result with speech probability\n */\n process(audioChunk: Float32Array): Promise<VADResult>;\n\n /**\n * Reset state for new audio stream\n */\n reset(): void | Promise<void>;\n\n /**\n * Dispose of the model and free resources\n */\n dispose(): Promise<void>;\n\n /**\n * Get required chunk size in samples\n */\n getChunkSize(): number;\n\n /**\n * Get chunk duration in milliseconds\n */\n getChunkDurationMs(): number;\n}\n\n/**\n * Configuration for the Silero VAD factory\n *\n * Extends SileroVADConfig with worker-specific options.\n */\nexport interface SileroVADFactoryConfig extends SileroVADConfig {\n /**\n * Force worker usage (true), main thread (false), or auto-detect (undefined).\n *\n * Auto-detection behavior:\n * - Desktop: Uses Worker (better responsiveness, off-main-thread)\n * - Mobile: Uses main thread (avoids 5MB memory overhead)\n *\n * You can override this to:\n * - `true`: Force Worker even on mobile (if you have memory headroom)\n * - `false`: Force main thread even on desktop (for debugging)\n *\n * Default: undefined (auto-detect)\n */\n useWorker?: boolean;\n\n /**\n * Fallback to main thread on worker errors.\n *\n * When true (default), if the Worker fails to load or encounters an error,\n * the factory will automatically create a main thread instance instead.\n *\n * When false, worker errors will propagate as exceptions.\n *\n * Default: true\n */\n fallbackOnError?: boolean;\n}\n\n/**\n * Check if the current environment supports VAD Web Workers\n *\n * Requirements:\n * - Worker constructor must exist\n * - Blob URL support (for inline worker script)\n *\n * @returns true if VAD Worker is supported\n */\nexport function supportsVADWorker(): boolean {\n // Check Worker constructor exists\n if (typeof Worker === 'undefined') {\n logger.debug('Worker not supported: Worker constructor undefined');\n return false;\n }\n\n // Check Blob URL support (needed for inline worker script)\n if (typeof URL === 'undefined' || typeof URL.createObjectURL === 'undefined') {\n logger.debug('Worker not supported: URL.createObjectURL unavailable');\n return false;\n }\n\n // Check Blob support\n if (typeof Blob === 'undefined') {\n logger.debug('Worker not supported: Blob constructor unavailable');\n return false;\n }\n\n return true;\n}\n\n/**\n * Create a Silero VAD instance with automatic implementation selection\n *\n * This factory function automatically selects between:\n * - **SileroVADWorker**: Off-main-thread inference (better for desktop)\n * - **SileroVADInference**: Main thread inference (better for mobile)\n *\n * The selection is based on:\n * 1. Explicit `useWorker` config (if provided)\n * 2. Platform detection (mobile vs desktop)\n * 3. Worker API availability\n *\n * Both implementations share the same interface (SileroVADBackend),\n * so consumers can use either interchangeably.\n *\n * @param config - Factory configuration\n * @returns A SileroVAD instance (either Worker or main thread)\n *\n * @example\n * ```typescript\n * // Auto-detect (recommended)\n * const vad = createSileroVAD({ modelUrl: '/models/silero-vad.onnx' });\n *\n * // Force Worker\n * const vadWorker = createSileroVAD({ modelUrl: '/models/silero-vad.onnx', useWorker: true });\n *\n * // Force main thread\n * const vadMain = createSileroVAD({ modelUrl: '/models/silero-vad.onnx', useWorker: false });\n * ```\n */\nexport function createSileroVAD(config: SileroVADFactoryConfig): SileroVADBackend {\n const fallbackOnError = config.fallbackOnError ?? true;\n\n // Determine whether to use Worker\n let useWorker: boolean;\n\n if (config.useWorker !== undefined) {\n // Explicit preference\n useWorker = config.useWorker;\n logger.debug('Worker preference explicitly set', { useWorker });\n } else {\n // Auto-detect based on platform and support\n const workerSupported = supportsVADWorker();\n const onMobile = isMobile();\n\n // Desktop with Worker support: use Worker\n // Mobile: use main thread (memory overhead concern)\n useWorker = workerSupported && !onMobile;\n\n logger.debug('Auto-detected Worker preference', {\n useWorker,\n workerSupported,\n onMobile,\n });\n }\n\n // Create the appropriate implementation\n if (useWorker) {\n logger.info('Creating SileroVADWorker (off-main-thread)');\n const worker = new SileroVADWorker({\n modelUrl: config.modelUrl,\n sampleRate: config.sampleRate,\n threshold: config.threshold,\n preSpeechBufferChunks: config.preSpeechBufferChunks,\n });\n\n if (fallbackOnError) {\n // Wrap with fallback behavior\n return new VADWorkerWithFallback(worker, config);\n }\n\n return worker as SileroVADBackend;\n }\n\n logger.info('Creating SileroVADInference (main thread)');\n return new SileroVADInference(config) as SileroVADBackend;\n}\n\n/**\n * Wrapper that provides automatic fallback from Worker to main thread\n *\n * If the Worker fails during load(), this wrapper will automatically\n * create a main thread SileroVADInference instance instead.\n */\nclass VADWorkerWithFallback implements SileroVADBackend {\n private implementation: SileroVADBackend;\n private readonly config: SileroVADFactoryConfig;\n private hasFallenBack = false;\n\n constructor(worker: SileroVADWorker, config: SileroVADFactoryConfig) {\n this.implementation = worker as SileroVADBackend;\n this.config = config;\n }\n\n get backend(): RuntimeBackend | null {\n // Worker always uses WASM, but hasn't loaded yet\n if (!this.isLoaded) return null;\n return this.hasFallenBack ? (this.implementation as SileroVADInference).backend : 'wasm';\n }\n\n get isLoaded(): boolean {\n return this.implementation.isLoaded;\n }\n\n get sampleRate(): number {\n return this.implementation.sampleRate;\n }\n\n get threshold(): number {\n return this.implementation.threshold;\n }\n\n async load(): Promise<VADModelInfo | VADWorkerModelInfo> {\n try {\n return await this.implementation.load();\n } catch (error) {\n logger.warn('Worker load failed, falling back to main thread', {\n error: error instanceof Error ? error.message : String(error),\n });\n\n // Clean up failed worker\n try {\n await this.implementation.dispose();\n } catch {\n // Ignore dispose errors\n }\n\n // Create main thread fallback\n this.implementation = new SileroVADInference(this.config) as SileroVADBackend;\n this.hasFallenBack = true;\n\n logger.info('Fallback to SileroVADInference successful');\n return await this.implementation.load();\n }\n }\n\n async process(audioChunk: Float32Array): Promise<VADResult> {\n return this.implementation.process(audioChunk);\n }\n\n reset(): void | Promise<void> {\n return this.implementation.reset();\n }\n\n async dispose(): Promise<void> {\n return this.implementation.dispose();\n }\n\n getChunkSize(): number {\n return this.implementation.getChunkSize();\n }\n\n getChunkDurationMs(): number {\n return this.implementation.getChunkDurationMs();\n }\n}\n","/**\r\n * Safari Web Speech API wrapper for iOS speech recognition\r\n *\r\n * Provides a similar interface to WhisperInference for easy substitution on iOS.\r\n * Uses the native Web Speech API which is significantly faster than Whisper WASM on iOS.\r\n *\r\n * Key differences from WhisperInference:\r\n * - Real-time streaming (not batch processing)\r\n * - No audio buffer input (microphone handled by browser)\r\n * - transcribe() throws error (use start/stop pattern instead)\r\n *\r\n * @category Inference\r\n *\r\n * @example Basic usage\r\n * ```typescript\r\n * import { SafariSpeechRecognition, shouldUseNativeASR } from '@omote/core';\r\n *\r\n * // Use native ASR on iOS, Whisper elsewhere\r\n * if (shouldUseNativeASR()) {\r\n * const speech = new SafariSpeechRecognition({ language: 'en-US' });\r\n *\r\n * speech.onResult((result) => {\r\n * console.log('Transcript:', result.text);\r\n * });\r\n *\r\n * await speech.start();\r\n * // ... user speaks ...\r\n * const finalResult = await speech.stop();\r\n * }\r\n * ```\r\n *\r\n * @example Platform-aware initialization\r\n * ```typescript\r\n * const asr = shouldUseNativeASR()\r\n * ? new SafariSpeechRecognition({ language: 'en-US' })\r\n * : new WhisperInference({ model: 'tiny' });\r\n * ```\r\n */\r\n\r\nimport { createLogger } from '../logging';\r\nimport { getTelemetry } from '../telemetry';\r\nimport { isSpeechRecognitionAvailable } from '../utils/runtime';\r\n\r\nconst logger = createLogger('SafariSpeech');\r\n\r\n/**\r\n * Configuration for Safari Speech Recognition\r\n */\r\nexport interface SafariSpeechConfig {\r\n /** Language code (default: 'en-US') */\r\n language?: string;\r\n /** Continuous mode for ongoing conversation (default: true) */\r\n continuous?: boolean;\r\n /** Interim results before speech ends (default: true) */\r\n interimResults?: boolean;\r\n /** Max alternatives (default: 1) */\r\n maxAlternatives?: number;\r\n}\r\n\r\n/**\r\n * Result from speech recognition (matches WhisperInference TranscriptionResult)\r\n */\r\nexport interface SpeechRecognitionResult {\r\n /** Transcribed text */\r\n text: string;\r\n /** Detected/used language */\r\n language: string;\r\n /** Time since start in ms (not inference time - native API) */\r\n inferenceTimeMs: number;\r\n /** Whether this is a final result or interim */\r\n isFinal: boolean;\r\n /** Confidence score (0-1) if available */\r\n confidence?: number;\r\n}\r\n\r\n/**\r\n * Callback for receiving recognition results\r\n */\r\nexport type SpeechResultCallback = (result: SpeechRecognitionResult) => void;\r\n\r\n/**\r\n * Callback for receiving recognition errors\r\n */\r\nexport type SpeechErrorCallback = (error: Error) => void;\r\n\r\n// Type declarations for Web Speech API (not in lib.dom.d.ts by default)\r\ninterface SpeechRecognitionEvent extends Event {\r\n resultIndex: number;\r\n results: SpeechRecognitionResultList;\r\n}\r\n\r\ninterface SpeechRecognitionResultList {\r\n length: number;\r\n item(index: number): SpeechRecognitionResult;\r\n [index: number]: SpeechRecognitionResultItem;\r\n}\r\n\r\ninterface SpeechRecognitionResultItem {\r\n isFinal: boolean;\r\n length: number;\r\n item(index: number): SpeechRecognitionAlternative;\r\n [index: number]: SpeechRecognitionAlternative;\r\n}\r\n\r\ninterface SpeechRecognitionAlternative {\r\n transcript: string;\r\n confidence: number;\r\n}\r\n\r\ninterface SpeechRecognitionErrorEvent extends Event {\r\n error: string;\r\n message: string;\r\n}\r\n\r\ninterface SpeechRecognitionInterface extends EventTarget {\r\n continuous: boolean;\r\n interimResults: boolean;\r\n lang: string;\r\n maxAlternatives: number;\r\n start(): void;\r\n stop(): void;\r\n abort(): void;\r\n onresult: ((event: SpeechRecognitionEvent) => void) | null;\r\n onerror: ((event: SpeechRecognitionErrorEvent) => void) | null;\r\n onend: (() => void) | null;\r\n onstart: (() => void) | null;\r\n onaudiostart: (() => void) | null;\r\n onaudioend: (() => void) | null;\r\n onspeechstart: (() => void) | null;\r\n onspeechend: (() => void) | null;\r\n}\r\n\r\ndeclare global {\r\n interface Window {\r\n SpeechRecognition?: new () => SpeechRecognitionInterface;\r\n webkitSpeechRecognition?: new () => SpeechRecognitionInterface;\r\n }\r\n}\r\n\r\n/**\r\n * Safari Web Speech API wrapper\r\n *\r\n * Provides native speech recognition on iOS Safari.\r\n * Much faster than Whisper WASM and more battery-efficient.\r\n */\r\nexport class SafariSpeechRecognition {\r\n private config: Required<SafariSpeechConfig>;\r\n private recognition: SpeechRecognitionInterface | null = null;\r\n private isListening = false;\r\n private startTime = 0;\r\n private accumulatedText = '';\r\n\r\n // Callbacks\r\n private resultCallbacks: SpeechResultCallback[] = [];\r\n private errorCallbacks: SpeechErrorCallback[] = [];\r\n\r\n // Promise resolvers for stop()\r\n private stopResolver: ((result: SpeechRecognitionResult) => void) | null = null;\r\n private stopRejecter: ((error: Error) => void) | null = null;\r\n\r\n constructor(config: SafariSpeechConfig = {}) {\r\n this.config = {\r\n language: config.language ?? 'en-US',\r\n continuous: config.continuous ?? true,\r\n interimResults: config.interimResults ?? true,\r\n maxAlternatives: config.maxAlternatives ?? 1,\r\n };\r\n\r\n logger.debug('SafariSpeechRecognition created', {\r\n language: this.config.language,\r\n continuous: this.config.continuous,\r\n });\r\n }\r\n\r\n /**\r\n * Check if Web Speech API is available\r\n */\r\n static isAvailable(): boolean {\r\n return isSpeechRecognitionAvailable();\r\n }\r\n\r\n /**\r\n * Check if currently listening\r\n */\r\n get listening(): boolean {\r\n return this.isListening;\r\n }\r\n\r\n /**\r\n * Get the language being used\r\n */\r\n get language(): string {\r\n return this.config.language;\r\n }\r\n\r\n /**\r\n * Register a callback for receiving results\r\n */\r\n onResult(callback: SpeechResultCallback): void {\r\n this.resultCallbacks.push(callback);\r\n }\r\n\r\n /**\r\n * Register a callback for receiving errors\r\n */\r\n onError(callback: SpeechErrorCallback): void {\r\n this.errorCallbacks.push(callback);\r\n }\r\n\r\n /**\r\n * Remove a result callback\r\n */\r\n offResult(callback: SpeechResultCallback): void {\r\n const index = this.resultCallbacks.indexOf(callback);\r\n if (index !== -1) {\r\n this.resultCallbacks.splice(index, 1);\r\n }\r\n }\r\n\r\n /**\r\n * Remove an error callback\r\n */\r\n offError(callback: SpeechErrorCallback): void {\r\n const index = this.errorCallbacks.indexOf(callback);\r\n if (index !== -1) {\r\n this.errorCallbacks.splice(index, 1);\r\n }\r\n }\r\n\r\n /**\r\n * Start listening for speech\r\n *\r\n * On iOS Safari, this will trigger the microphone permission prompt\r\n * if not already granted.\r\n */\r\n async start(): Promise<void> {\r\n if (this.isListening) {\r\n logger.warn('Already listening');\r\n return;\r\n }\r\n\r\n if (!SafariSpeechRecognition.isAvailable()) {\r\n const error = new Error(\r\n 'Web Speech API not available. ' +\r\n 'This API is supported in Safari (iOS/macOS) and Chrome. ' +\r\n 'On iOS, use Safari for native speech recognition.'\r\n );\r\n this.emitError(error);\r\n throw error;\r\n }\r\n\r\n const telemetry = getTelemetry();\r\n const span = telemetry?.startSpan('SafariSpeech.start', {\r\n 'speech.language': this.config.language,\r\n 'speech.continuous': this.config.continuous,\r\n });\r\n\r\n try {\r\n // Create recognition instance\r\n const SpeechRecognitionClass = window.SpeechRecognition || window.webkitSpeechRecognition;\r\n if (!SpeechRecognitionClass) {\r\n throw new Error('SpeechRecognition constructor not found');\r\n }\r\n\r\n this.recognition = new SpeechRecognitionClass();\r\n this.recognition.continuous = this.config.continuous;\r\n this.recognition.interimResults = this.config.interimResults;\r\n this.recognition.lang = this.config.language;\r\n this.recognition.maxAlternatives = this.config.maxAlternatives;\r\n\r\n // Set up event handlers\r\n this.setupEventHandlers();\r\n\r\n // Start recognition\r\n this.recognition.start();\r\n this.isListening = true;\r\n this.startTime = performance.now();\r\n this.accumulatedText = '';\r\n\r\n logger.info('Speech recognition started', {\r\n language: this.config.language,\r\n });\r\n\r\n span?.end();\r\n } catch (error) {\r\n span?.endWithError(error instanceof Error ? error : new Error(String(error)));\r\n this.emitError(error instanceof Error ? error : new Error(String(error)));\r\n throw error;\r\n }\r\n }\r\n\r\n /**\r\n * Stop listening and return the final transcript\r\n */\r\n async stop(): Promise<SpeechRecognitionResult> {\r\n if (!this.isListening || !this.recognition) {\r\n logger.warn('Not currently listening');\r\n return {\r\n text: this.accumulatedText,\r\n language: this.config.language,\r\n inferenceTimeMs: 0,\r\n isFinal: true,\r\n };\r\n }\r\n\r\n const telemetry = getTelemetry();\r\n const span = telemetry?.startSpan('SafariSpeech.stop');\r\n\r\n return new Promise((resolve, reject) => {\r\n this.stopResolver = resolve;\r\n this.stopRejecter = reject;\r\n\r\n try {\r\n this.recognition!.stop();\r\n // onend handler will resolve the promise\r\n } catch (error) {\r\n span?.endWithError(error instanceof Error ? error : new Error(String(error)));\r\n this.isListening = false;\r\n reject(error);\r\n }\r\n });\r\n }\r\n\r\n /**\r\n * Abort recognition without waiting for final result\r\n */\r\n abort(): void {\r\n if (this.recognition && this.isListening) {\r\n this.recognition.abort();\r\n this.isListening = false;\r\n logger.info('Speech recognition aborted');\r\n }\r\n }\r\n\r\n /**\r\n * NOT SUPPORTED: Transcribe audio buffer\r\n *\r\n * Safari Speech API does not support transcribing pre-recorded audio.\r\n * It only works with live microphone input.\r\n *\r\n * For batch transcription on iOS, use server-side Whisper or a cloud ASR service.\r\n *\r\n * @throws Error always - this method is not supported\r\n */\r\n async transcribe(_audio: Float32Array): Promise<SpeechRecognitionResult> {\r\n throw new Error(\r\n 'SafariSpeechRecognition does not support transcribe() with audio buffers. ' +\r\n 'The Web Speech API only works with live microphone input. ' +\r\n 'Use start() and stop() for real-time recognition, or use WhisperInference/cloud ASR for batch transcription.'\r\n );\r\n }\r\n\r\n /**\r\n * Dispose of recognition resources\r\n */\r\n dispose(): void {\r\n if (this.recognition) {\r\n if (this.isListening) {\r\n this.recognition.abort();\r\n }\r\n this.recognition = null;\r\n }\r\n this.isListening = false;\r\n this.resultCallbacks = [];\r\n this.errorCallbacks = [];\r\n logger.debug('SafariSpeechRecognition disposed');\r\n }\r\n\r\n /**\r\n * Set up event handlers for the recognition instance\r\n */\r\n private setupEventHandlers(): void {\r\n if (!this.recognition) return;\r\n\r\n this.recognition.onresult = (event: SpeechRecognitionEvent) => {\r\n const telemetry = getTelemetry();\r\n const span = telemetry?.startSpan('SafariSpeech.onresult');\r\n\r\n try {\r\n // Process all new results\r\n for (let i = event.resultIndex; i < event.results.length; i++) {\r\n const result = event.results[i];\r\n const alternative = result[0];\r\n\r\n if (alternative) {\r\n const text = alternative.transcript;\r\n const isFinal = result.isFinal;\r\n\r\n // Accumulate final text\r\n if (isFinal) {\r\n this.accumulatedText += text + ' ';\r\n }\r\n\r\n const speechResult: SpeechRecognitionResult = {\r\n text: isFinal ? this.accumulatedText.trim() : text,\r\n language: this.config.language,\r\n inferenceTimeMs: performance.now() - this.startTime,\r\n isFinal,\r\n confidence: alternative.confidence,\r\n };\r\n\r\n // Emit to callbacks\r\n this.emitResult(speechResult);\r\n\r\n logger.trace('Speech result', {\r\n text: text.substring(0, 50),\r\n isFinal,\r\n confidence: alternative.confidence,\r\n });\r\n }\r\n }\r\n\r\n span?.end();\r\n } catch (error) {\r\n span?.endWithError(error instanceof Error ? error : new Error(String(error)));\r\n logger.error('Error processing speech result', { error });\r\n }\r\n };\r\n\r\n this.recognition.onerror = (event: SpeechRecognitionErrorEvent) => {\r\n const error = new Error(`Speech recognition error: ${event.error} - ${event.message}`);\r\n logger.error('Speech recognition error', { error: event.error, message: event.message });\r\n this.emitError(error);\r\n\r\n if (this.stopRejecter) {\r\n this.stopRejecter(error);\r\n this.stopResolver = null;\r\n this.stopRejecter = null;\r\n }\r\n };\r\n\r\n this.recognition.onend = () => {\r\n this.isListening = false;\r\n logger.info('Speech recognition ended', {\r\n totalText: this.accumulatedText.length,\r\n durationMs: performance.now() - this.startTime,\r\n });\r\n\r\n // Resolve stop() promise if pending\r\n if (this.stopResolver) {\r\n const result: SpeechRecognitionResult = {\r\n text: this.accumulatedText.trim(),\r\n language: this.config.language,\r\n inferenceTimeMs: performance.now() - this.startTime,\r\n isFinal: true,\r\n };\r\n this.stopResolver(result);\r\n this.stopResolver = null;\r\n this.stopRejecter = null;\r\n }\r\n };\r\n\r\n this.recognition.onstart = () => {\r\n logger.debug('Speech recognition started by browser');\r\n };\r\n\r\n this.recognition.onspeechstart = () => {\r\n logger.debug('Speech detected');\r\n };\r\n\r\n this.recognition.onspeechend = () => {\r\n logger.debug('Speech ended');\r\n };\r\n }\r\n\r\n /**\r\n * Emit result to all registered callbacks\r\n */\r\n private emitResult(result: SpeechRecognitionResult): void {\r\n for (const callback of this.resultCallbacks) {\r\n try {\r\n callback(result);\r\n } catch (error) {\r\n logger.error('Error in result callback', { error });\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Emit error to all registered callbacks\r\n */\r\n private emitError(error: Error): void {\r\n for (const callback of this.errorCallbacks) {\r\n try {\r\n callback(error);\r\n } catch (callbackError) {\r\n logger.error('Error in error callback', { error: callbackError });\r\n }\r\n }\r\n }\r\n}\r\n","/**\n * Emotion - Helper for creating emotion vectors for avatar animation\n *\n * Provides 10 explicit emotion channels that can be used to control\n * avatar expressions and emotional states.\n *\n * @category Emotion\n *\n * @example Creating emotion vectors\n * ```typescript\n * import { createEmotionVector, EmotionPresets } from '@omote/core';\n *\n * // Named weights\n * const happy = createEmotionVector({ joy: 0.8, amazement: 0.2 });\n *\n * // Use preset\n * const surprised = EmotionPresets.surprised;\n * ```\n *\n * @example Smooth transitions\n * ```typescript\n * import { EmotionController } from '@omote/core';\n *\n * const controller = new EmotionController();\n * controller.setPreset('happy');\n * controller.transitionTo({ sadness: 0.7 }, 500);\n *\n * // In animation loop\n * controller.update();\n * const emotion = controller.emotion;\n * ```\n */\n\n/** The 10 explicit emotion channels */\nexport const EMOTION_NAMES = [\n 'amazement',\n 'anger',\n 'cheekiness',\n 'disgust',\n 'fear',\n 'grief',\n 'joy',\n 'outofbreath',\n 'pain',\n 'sadness',\n] as const;\n\nexport type EmotionName = typeof EMOTION_NAMES[number];\n\n/** Emotion weights by name */\nexport type EmotionWeights = Partial<Record<EmotionName, number>>;\n\n/** Total emotion vector size */\nexport const EMOTION_VECTOR_SIZE = 26;\n\n/** Number of explicit emotion channels */\nexport const EXPLICIT_EMOTION_COUNT = 10;\n\n/**\n * Create an emotion vector from named weights\n *\n * @param weights - Named emotion weights (0-1)\n * @returns Float32Array of emotion values\n *\n * @example\n * ```ts\n * const emotion = createEmotionVector({ joy: 0.8, amazement: 0.3 });\n * ```\n */\nexport function createEmotionVector(weights: EmotionWeights = {}): Float32Array {\n const vector = new Float32Array(EMOTION_VECTOR_SIZE);\n\n for (const [name, value] of Object.entries(weights)) {\n const idx = EMOTION_NAMES.indexOf(name as EmotionName);\n if (idx >= 0) {\n vector[idx] = Math.max(0, Math.min(1, value));\n }\n }\n\n return vector;\n}\n\n/**\n * Pre-built emotion presets for common expressions\n */\nexport const EmotionPresets = {\n /** Neutral/default - no emotional expression */\n neutral: createEmotionVector({}),\n\n /** Happy - joy with slight amazement */\n happy: createEmotionVector({ joy: 0.7, amazement: 0.2 }),\n\n /** Sad - grief and sadness */\n sad: createEmotionVector({ sadness: 0.7, grief: 0.4 }),\n\n /** Angry - anger with disgust */\n angry: createEmotionVector({ anger: 0.8, disgust: 0.3 }),\n\n /** Surprised - high amazement */\n surprised: createEmotionVector({ amazement: 0.9, fear: 0.2 }),\n\n /** Scared - fear with pain */\n scared: createEmotionVector({ fear: 0.8, pain: 0.3 }),\n\n /** Disgusted - disgust with anger */\n disgusted: createEmotionVector({ disgust: 0.8, anger: 0.2 }),\n\n /** Excited - joy with amazement and cheekiness */\n excited: createEmotionVector({ joy: 0.6, amazement: 0.5, cheekiness: 0.4 }),\n\n /** Tired - out of breath with sadness */\n tired: createEmotionVector({ outofbreath: 0.6, sadness: 0.3 }),\n\n /** Playful - cheekiness with joy */\n playful: createEmotionVector({ cheekiness: 0.7, joy: 0.5 }),\n\n /** Pained - pain with grief */\n pained: createEmotionVector({ pain: 0.8, grief: 0.4 }),\n\n /** Contemplative - slight sadness, calm */\n contemplative: createEmotionVector({ sadness: 0.2, grief: 0.1 }),\n} as const;\n\nexport type EmotionPresetName = keyof typeof EmotionPresets;\n\n/**\n * Get an emotion preset by name\n */\nexport function getEmotionPreset(name: EmotionPresetName): Float32Array {\n return EmotionPresets[name].slice();\n}\n\n/**\n * Blend multiple emotion vectors together\n *\n * @param emotions - Array of { vector, weight } pairs\n * @returns Blended emotion vector\n *\n * @example\n * ```ts\n * const blended = blendEmotions([\n * { vector: EmotionPresets.happy, weight: 0.7 },\n * { vector: EmotionPresets.surprised, weight: 0.3 },\n * ]);\n * ```\n */\nexport function blendEmotions(\n emotions: Array<{ vector: Float32Array; weight: number }>\n): Float32Array {\n const result = new Float32Array(EMOTION_VECTOR_SIZE);\n let totalWeight = 0;\n\n for (const { vector, weight } of emotions) {\n totalWeight += weight;\n for (let i = 0; i < EMOTION_VECTOR_SIZE; i++) {\n result[i] += (vector[i] || 0) * weight;\n }\n }\n\n // Normalize if total weight > 0\n if (totalWeight > 0) {\n for (let i = 0; i < EMOTION_VECTOR_SIZE; i++) {\n result[i] /= totalWeight;\n }\n }\n\n return result;\n}\n\n/**\n * Interpolate between two emotion vectors\n *\n * @param from - Starting emotion\n * @param to - Target emotion\n * @param t - Interpolation factor (0-1)\n * @returns Interpolated emotion vector\n */\nexport function lerpEmotion(\n from: Float32Array,\n to: Float32Array,\n t: number\n): Float32Array {\n const result = new Float32Array(EMOTION_VECTOR_SIZE);\n const clampedT = Math.max(0, Math.min(1, t));\n\n for (let i = 0; i < EMOTION_VECTOR_SIZE; i++) {\n result[i] = (from[i] || 0) * (1 - clampedT) + (to[i] || 0) * clampedT;\n }\n\n return result;\n}\n\n/**\n * EmotionController - Manages emotion state with smooth transitions\n */\nexport class EmotionController {\n private currentEmotion = new Float32Array(EMOTION_VECTOR_SIZE);\n private targetEmotion = new Float32Array(EMOTION_VECTOR_SIZE);\n private transitionProgress = 1.0;\n private transitionDuration = 0;\n private transitionStartTime = 0;\n\n /**\n * Get the current emotion vector\n */\n get emotion(): Float32Array {\n if (this.transitionProgress >= 1.0) {\n return this.targetEmotion;\n }\n\n // Interpolate during transition\n return lerpEmotion(this.currentEmotion, this.targetEmotion, this.transitionProgress);\n }\n\n /**\n * Set emotion immediately (no transition)\n */\n set(weights: EmotionWeights): void {\n const newEmotion = createEmotionVector(weights);\n this.targetEmotion.set(newEmotion);\n this.currentEmotion.set(newEmotion);\n this.transitionProgress = 1.0;\n }\n\n /**\n * Set emotion from preset immediately\n */\n setPreset(preset: EmotionPresetName): void {\n const newEmotion = getEmotionPreset(preset);\n this.targetEmotion.set(newEmotion);\n this.currentEmotion.set(newEmotion);\n this.transitionProgress = 1.0;\n }\n\n /**\n * Transition to new emotion over time\n *\n * @param weights - Target emotion weights\n * @param durationMs - Transition duration in milliseconds\n */\n transitionTo(weights: EmotionWeights, durationMs: number): void {\n this.currentEmotion.set(this.emotion);\n this.targetEmotion.set(createEmotionVector(weights));\n this.transitionDuration = durationMs;\n this.transitionStartTime = performance.now();\n this.transitionProgress = 0;\n }\n\n /**\n * Transition to preset over time\n */\n transitionToPreset(preset: EmotionPresetName, durationMs: number): void {\n this.currentEmotion.set(this.emotion);\n this.targetEmotion.set(getEmotionPreset(preset));\n this.transitionDuration = durationMs;\n this.transitionStartTime = performance.now();\n this.transitionProgress = 0;\n }\n\n /**\n * Update transition progress (call each frame)\n */\n update(): void {\n if (this.transitionProgress >= 1.0) return;\n\n const elapsed = performance.now() - this.transitionStartTime;\n this.transitionProgress = Math.min(1.0, elapsed / this.transitionDuration);\n }\n\n /**\n * Check if currently transitioning\n */\n get isTransitioning(): boolean {\n return this.transitionProgress < 1.0;\n }\n\n /**\n * Reset to neutral\n */\n reset(): void {\n this.currentEmotion.fill(0);\n this.targetEmotion.fill(0);\n this.transitionProgress = 1.0;\n }\n}\n","/**\r\n * AWS AgentCore Adapter\r\n *\r\n * Primary AI adapter for the Omote Platform.\r\n *\r\n * Pipeline:\r\n * User Audio -> Whisper ASR (local) -> Text\r\n * Text -> AgentCore (WebSocket) -> Response Text + Audio chunks (TTS handled backend-side)\r\n * Audio chunks -> LAM (local) -> Blendshapes -> Render\r\n *\r\n * @category AI\r\n */\r\n\r\nimport { EventEmitter } from '../../events/EventEmitter';\r\nimport type {\r\n AIAdapter,\r\n AIAdapterEvents,\r\n SessionConfig,\r\n AISessionState,\r\n ConversationMessage,\r\n TenantConfig,\r\n} from '../interfaces/AIAdapter';\r\nimport { SenseVoiceInference } from '../../inference/SenseVoiceInference';\r\nimport { Wav2Vec2Inference, LAM_BLENDSHAPES } from '../../inference/Wav2Vec2Inference';\r\nimport { SileroVADInference } from '../../inference/SileroVADInference';\r\nimport { EmotionController } from '../../emotion/Emotion';\r\nimport { SyncedAudioPipeline } from '../../audio/SyncedAudioPipeline';\r\n\r\n/**\r\n * AgentCore-specific configuration\r\n */\r\nexport interface AgentCoreConfig {\r\n /** AgentCore WebSocket endpoint */\r\n endpoint: string;\r\n /** AWS region */\r\n region?: string;\r\n /** Model URLs */\r\n models?: {\r\n lamUrl?: string;\r\n };\r\n /** Enable observability */\r\n observability?: {\r\n tracing?: boolean;\r\n metrics?: boolean;\r\n };\r\n}\r\n\r\n/**\r\n * AWS AgentCore Adapter\r\n */\r\nexport class AgentCoreAdapter extends EventEmitter<AIAdapterEvents> implements AIAdapter {\r\n readonly name = 'AgentCore';\r\n\r\n private _state: AISessionState = 'disconnected';\r\n private _sessionId: string | null = null;\r\n private _isConnected = false;\r\n\r\n // Sub-components\r\n private asr: SenseVoiceInference | null = null;\r\n private vad: SileroVADInference | null = null;\r\n private lam: Wav2Vec2Inference | null = null;\r\n private emotionController: EmotionController;\r\n private pipeline: SyncedAudioPipeline | null = null;\r\n\r\n // WebSocket connection to AgentCore\r\n private ws: WebSocket | null = null;\r\n private wsReconnectAttempts = 0;\r\n private readonly maxReconnectAttempts = 5;\r\n\r\n // Audio buffers\r\n private audioBuffer: Float32Array[] = [];\r\n\r\n // Conversation state\r\n private history: ConversationMessage[] = [];\r\n private currentConfig: SessionConfig | null = null;\r\n private agentCoreConfig: AgentCoreConfig;\r\n\r\n // Interruption handling\r\n private isSpeaking = false;\r\n private currentTtsAbortController: AbortController | null = null;\r\n\r\n // Auth token cache per tenant\r\n private tokenCache = new Map<string, { token: string; expiresAt: number }>();\r\n\r\n constructor(config: AgentCoreConfig) {\r\n super();\r\n this.agentCoreConfig = config;\r\n this.emotionController = new EmotionController();\r\n }\r\n\r\n get state(): AISessionState {\r\n return this._state;\r\n }\r\n\r\n get sessionId(): string | null {\r\n return this._sessionId;\r\n }\r\n\r\n get isConnected(): boolean {\r\n return this._isConnected;\r\n }\r\n\r\n /**\r\n * Connect to AgentCore with session configuration\r\n */\r\n async connect(config: SessionConfig): Promise<void> {\r\n this.currentConfig = config;\r\n this._sessionId = config.sessionId;\r\n\r\n try {\r\n // 1. Get/refresh auth token for tenant\r\n const authToken = await this.getAuthToken(config.tenant);\r\n\r\n // 2. Initialize local inference components in parallel\r\n await Promise.all([\r\n this.initASR(),\r\n this.initLAM(),\r\n ]);\r\n\r\n // 3. Connect to AgentCore WebSocket\r\n await this.connectWebSocket(authToken, config);\r\n\r\n this._isConnected = true;\r\n this.setState('idle');\r\n\r\n this.emit('connection.opened', { sessionId: this._sessionId, adapter: this.name });\r\n } catch (error) {\r\n this.setState('error');\r\n this.emit('connection.error', {\r\n error: error as Error,\r\n recoverable: true,\r\n });\r\n throw error;\r\n }\r\n }\r\n\r\n /**\r\n * Disconnect and cleanup\r\n */\r\n async disconnect(): Promise<void> {\r\n // Cancel any ongoing TTS\r\n this.currentTtsAbortController?.abort();\r\n\r\n // Stop pipeline\r\n if (this.pipeline) {\r\n this.pipeline.dispose();\r\n this.pipeline = null;\r\n }\r\n\r\n // Close WebSocket\r\n if (this.ws) {\r\n this.ws.close(1000, 'Client disconnect');\r\n this.ws = null;\r\n }\r\n\r\n // Cleanup local components\r\n await Promise.all([\r\n this.asr?.dispose(),\r\n this.vad?.dispose(),\r\n this.lam?.dispose(),\r\n ]);\r\n\r\n this._isConnected = false;\r\n this.setState('disconnected');\r\n\r\n this.emit('connection.closed', { reason: 'Client disconnect' });\r\n }\r\n\r\n /**\r\n * Push user audio for processing\r\n */\r\n pushAudio(audio: Int16Array | Float32Array): void {\r\n if (!this._isConnected) return;\r\n\r\n // Handle interruption detection (async but fire-and-forget)\r\n if (this.isSpeaking) {\r\n this.detectVoiceActivity(audio).then((hasVoiceActivity) => {\r\n if (hasVoiceActivity) {\r\n this.interrupt();\r\n }\r\n }).catch((error) => {\r\n console.error('[AgentCore] VAD error during interruption detection:', error);\r\n });\r\n // Don't return - still buffer the audio for transcription after interruption\r\n }\r\n\r\n // Convert to Float32 if needed\r\n const float32 = audio instanceof Float32Array\r\n ? audio\r\n : this.int16ToFloat32(audio);\r\n\r\n // Buffer audio chunks\r\n this.audioBuffer.push(float32);\r\n\r\n // Debounce and send to Whisper when we have enough\r\n this.scheduleTranscription();\r\n }\r\n\r\n /**\r\n * Send text directly to AgentCore\r\n */\r\n async sendText(text: string): Promise<void> {\r\n if (!this._isConnected || !this.ws) {\r\n throw new Error('Not connected to AgentCore');\r\n }\r\n\r\n // Add to history\r\n this.addToHistory({\r\n role: 'user',\r\n content: text,\r\n timestamp: Date.now(),\r\n });\r\n\r\n this.setState('thinking');\r\n this.emit('ai.thinking.start', { timestamp: Date.now() });\r\n\r\n // Send to AgentCore\r\n this.ws.send(JSON.stringify({\r\n type: 'user_message',\r\n sessionId: this._sessionId,\r\n content: text,\r\n context: {\r\n history: this.history.slice(-10), // Last 10 messages\r\n emotion: Array.from(this.emotionController.emotion),\r\n },\r\n }));\r\n }\r\n\r\n /**\r\n * Interrupt current AI response\r\n */\r\n interrupt(): void {\r\n if (!this.isSpeaking) return;\r\n\r\n this.emit('interruption.detected', { timestamp: Date.now() });\r\n\r\n // Cancel any pending operations\r\n this.currentTtsAbortController?.abort();\r\n this.currentTtsAbortController = null;\r\n\r\n // Notify AgentCore to stop TTS streaming\r\n if (this.ws?.readyState === WebSocket.OPEN) {\r\n this.ws.send(JSON.stringify({\r\n type: 'interrupt',\r\n sessionId: this._sessionId,\r\n timestamp: Date.now(),\r\n }));\r\n }\r\n\r\n this.isSpeaking = false;\r\n this.setState('listening');\r\n\r\n this.emit('interruption.handled', { timestamp: Date.now(), action: 'stop' });\r\n }\r\n\r\n getHistory(): ConversationMessage[] {\r\n return [...this.history];\r\n }\r\n\r\n clearHistory(): void {\r\n this.history = [];\r\n this.emit('memory.updated', { messageCount: 0 });\r\n }\r\n\r\n async healthCheck(): Promise<boolean> {\r\n if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {\r\n return false;\r\n }\r\n\r\n return new Promise((resolve) => {\r\n const timeout = setTimeout(() => resolve(false), 5000);\r\n\r\n const handler = (event: MessageEvent) => {\r\n const data = JSON.parse(event.data);\r\n if (data.type === 'pong') {\r\n clearTimeout(timeout);\r\n this.ws?.removeEventListener('message', handler);\r\n resolve(true);\r\n }\r\n };\r\n\r\n this.ws?.addEventListener('message', handler);\r\n this.ws?.send(JSON.stringify({ type: 'ping' }));\r\n });\r\n }\r\n\r\n // ==================== Private Methods ====================\r\n\r\n private setState(state: AISessionState): void {\r\n const previousState = this._state;\r\n this._state = state;\r\n this.emit('state.change', { state, previousState });\r\n }\r\n\r\n private async getAuthToken(tenant: TenantConfig): Promise<string> {\r\n const cached = this.tokenCache.get(tenant.tenantId);\r\n if (cached && cached.expiresAt > Date.now() + 60000) {\r\n return cached.token;\r\n }\r\n\r\n // If we have an auth token already, use it\r\n if (tenant.credentials.authToken) {\r\n return tenant.credentials.authToken;\r\n }\r\n\r\n // Skip auth for local dev (ws:// endpoints or localhost)\r\n // The simple voice-agent doesn't have an auth endpoint\r\n const endpoint = this.agentCoreConfig.endpoint;\r\n if (endpoint.startsWith('ws://') || endpoint.includes('localhost')) {\r\n return 'local-dev-token';\r\n }\r\n\r\n // Exchange credentials for token (production)\r\n const httpEndpoint = endpoint.replace('wss://', 'https://').replace('ws://', 'http://');\r\n const response = await fetch(`${httpEndpoint}/auth/token`, {\r\n method: 'POST',\r\n headers: { 'Content-Type': 'application/json' },\r\n body: JSON.stringify({\r\n tenantId: tenant.tenantId,\r\n apiKey: tenant.credentials.apiKey,\r\n }),\r\n });\r\n\r\n if (!response.ok) {\r\n throw new Error(`Auth failed: ${response.statusText}`);\r\n }\r\n\r\n const { token, expiresIn } = await response.json();\r\n\r\n this.tokenCache.set(tenant.tenantId, {\r\n token,\r\n expiresAt: Date.now() + expiresIn * 1000,\r\n });\r\n\r\n return token;\r\n }\r\n\r\n private async initASR(): Promise<void> {\r\n // Initialize SenseVoice and Silero VAD in parallel\r\n await Promise.all([\r\n // SenseVoice ASR\r\n (async () => {\r\n this.asr = new SenseVoiceInference({\r\n modelUrl: '/models/sensevoice/model.int8.onnx',\r\n language: 'auto',\r\n });\r\n await this.asr.load();\r\n })(),\r\n // Silero VAD for accurate voice activity detection\r\n (async () => {\r\n this.vad = new SileroVADInference({\r\n modelUrl: '/models/silero-vad.onnx',\r\n backend: 'webgpu',\r\n sampleRate: 16000,\r\n threshold: 0.5,\r\n });\r\n await this.vad.load();\r\n })(),\r\n ]);\r\n }\r\n\r\n private async initLAM(): Promise<void> {\r\n // LAM (Lip Animation Model) based on wav2vec2\r\n // Outputs 52 ARKit blendshapes directly at 30fps - no PCA solver needed\r\n const lamUrl = this.agentCoreConfig.models?.lamUrl || '/models/unified_wav2vec2_asr_a2e.onnx';\r\n\r\n this.lam = new Wav2Vec2Inference({\r\n modelUrl: lamUrl,\r\n backend: 'auto',\r\n });\r\n\r\n await this.lam.load();\r\n\r\n // Initialize SyncedAudioPipeline for synchronized audio playback + LAM\r\n await this.initPipeline();\r\n }\r\n\r\n private async initPipeline(): Promise<void> {\r\n if (!this.lam) {\r\n throw new Error('LAM must be initialized before pipeline');\r\n }\r\n\r\n this.pipeline = new SyncedAudioPipeline({\r\n lam: this.lam,\r\n sampleRate: 16000,\r\n chunkTargetMs: 200,\r\n });\r\n\r\n await this.pipeline.initialize();\r\n\r\n // Subscribe to pipeline events\r\n this.pipeline.on('frame_ready', (frame: Float32Array) => {\r\n // Emit animation event with synchronized frame\r\n this.emit('animation', {\r\n blendshapes: frame,\r\n get: (name: string) => {\r\n const idx = (LAM_BLENDSHAPES as readonly string[]).indexOf(name);\r\n return idx >= 0 ? frame[idx] : 0;\r\n },\r\n timestamp: Date.now(), // Wall clock for client-side logging only\r\n inferenceMs: 0, // Pipeline handles LAM inference asynchronously\r\n });\r\n });\r\n\r\n this.pipeline.on('playback_complete', () => {\r\n this.isSpeaking = false;\r\n this.setState('idle');\r\n this.emit('audio.output.end', { durationMs: 0 });\r\n });\r\n\r\n this.pipeline.on('error', (error: Error) => {\r\n console.error('[AgentCore] Pipeline error:', error);\r\n this.emit('connection.error', {\r\n error,\r\n recoverable: true,\r\n });\r\n });\r\n }\r\n\r\n private async connectWebSocket(authToken: string, config: SessionConfig): Promise<void> {\r\n return new Promise((resolve, reject) => {\r\n const wsUrl = new URL(`${this.agentCoreConfig.endpoint.replace('http', 'ws')}/ws`);\r\n wsUrl.searchParams.set('sessionId', config.sessionId);\r\n wsUrl.searchParams.set('characterId', config.tenant.characterId);\r\n\r\n this.ws = new WebSocket(wsUrl.toString());\r\n\r\n this.ws.onopen = () => {\r\n // Send auth\r\n this.ws?.send(JSON.stringify({\r\n type: 'auth',\r\n token: authToken,\r\n tenantId: config.tenant.tenantId,\r\n systemPrompt: config.systemPrompt,\r\n }));\r\n };\r\n\r\n this.ws.onmessage = (event) => {\r\n this.handleAgentCoreMessage(JSON.parse(event.data));\r\n };\r\n\r\n this.ws.onerror = () => {\r\n reject(new Error('WebSocket connection failed'));\r\n };\r\n\r\n this.ws.onclose = (event) => {\r\n this.handleDisconnect(event);\r\n };\r\n\r\n // Wait for auth confirmation\r\n const authTimeout = setTimeout(() => {\r\n reject(new Error('Auth timeout'));\r\n }, 10000);\r\n\r\n const authHandler = (event: MessageEvent) => {\r\n const data = JSON.parse(event.data);\r\n if (data.type === 'auth_success') {\r\n clearTimeout(authTimeout);\r\n this.ws?.removeEventListener('message', authHandler);\r\n resolve();\r\n } else if (data.type === 'auth_failed') {\r\n clearTimeout(authTimeout);\r\n reject(new Error(data.message));\r\n }\r\n };\r\n\r\n this.ws.addEventListener('message', authHandler);\r\n });\r\n }\r\n\r\n private handleAgentCoreMessage(data: Record<string, unknown>): void {\r\n switch (data.type) {\r\n case 'response_start':\r\n this.setState('speaking');\r\n this.isSpeaking = true;\r\n this.emit('ai.response.start', {\r\n text: data.text as string | undefined,\r\n emotion: data.emotion as string | undefined,\r\n });\r\n // Update emotion state\r\n if (data.emotion) {\r\n this.emotionController.transitionTo(\r\n { [data.emotion as string]: 0.7 },\r\n 300\r\n );\r\n }\r\n // Start pipeline for synchronized playback\r\n if (this.pipeline) {\r\n this.pipeline.start();\r\n }\r\n break;\r\n\r\n case 'response_chunk':\r\n this.emit('ai.response.chunk', {\r\n text: data.text as string,\r\n isLast: data.isLast as boolean,\r\n });\r\n break;\r\n\r\n case 'audio_chunk':\r\n // TTS audio streamed from backend - feed to synchronized pipeline\r\n if (data.audio && this.pipeline) {\r\n const audioData = this.base64ToArrayBuffer(data.audio as string);\r\n const uint8 = new Uint8Array(audioData);\r\n this.pipeline.onAudioChunk(uint8).catch((error) => {\r\n console.error('[AgentCore] Pipeline chunk error:', error);\r\n });\r\n }\r\n break;\r\n\r\n case 'audio_end':\r\n // Signal end of audio stream to pipeline\r\n if (this.pipeline) {\r\n this.pipeline.end().catch((error) => {\r\n console.error('[AgentCore] Pipeline end error:', error);\r\n });\r\n }\r\n // Note: isSpeaking and state will be set to idle by pipeline.playback_complete event\r\n break;\r\n\r\n case 'response_end':\r\n this.addToHistory({\r\n role: 'assistant',\r\n content: data.fullText as string,\r\n timestamp: Date.now(),\r\n emotion: data.emotion as string | undefined,\r\n });\r\n this.emit('ai.response.end', {\r\n fullText: data.fullText as string,\r\n durationMs: data.durationMs as number || 0,\r\n });\r\n break;\r\n\r\n case 'memory_updated':\r\n this.emit('memory.updated', {\r\n messageCount: data.messageCount as number,\r\n tokenCount: data.tokenCount as number | undefined,\r\n });\r\n break;\r\n\r\n case 'error':\r\n this.emit('connection.error', {\r\n error: new Error(data.message as string),\r\n recoverable: (data.recoverable as boolean) ?? false,\r\n });\r\n break;\r\n }\r\n }\r\n\r\n private scheduleTranscription(): void {\r\n // No debounce - transcribe immediately when we have enough audio\r\n // This reduces latency significantly (was adding 100ms delay)\r\n\r\n if (this.audioBuffer.length === 0) return;\r\n\r\n // Concatenate buffered audio\r\n const totalLength = this.audioBuffer.reduce((sum, buf) => sum + buf.length, 0);\r\n\r\n // Need minimum samples for Whisper (250ms instead of 1 sec)\r\n // Shorter buffer = faster response time\r\n if (totalLength < 4000) return; // 250ms at 16kHz (was 16000 = 1sec)\r\n\r\n const audio = new Float32Array(totalLength);\r\n let offset = 0;\r\n for (const buf of this.audioBuffer) {\r\n audio.set(buf, offset);\r\n offset += buf.length;\r\n }\r\n this.audioBuffer = [];\r\n\r\n // Check for actual audio content (not silence/blank audio)\r\n // This prevents [BLANK_AUDIO] transcriptions\r\n let sum = 0;\r\n for (let i = 0; i < audio.length; i++) {\r\n sum += audio[i] * audio[i];\r\n }\r\n const rms = Math.sqrt(sum / audio.length);\r\n\r\n // Skip silent audio (too low energy)\r\n if (rms < 0.01) {\r\n console.debug('[AgentCore] Skipping silent audio', { rms, samples: audio.length });\r\n return;\r\n }\r\n\r\n // Transcribe with SenseVoice\r\n if (this.asr) {\r\n this.setState('listening');\r\n this.emit('user.speech.start', { timestamp: Date.now() });\r\n\r\n this.asr.transcribe(audio).then((result: { text: string; inferenceTimeMs: number }) => {\r\n this.emit('user.transcript.final', {\r\n text: result.text,\r\n confidence: 1.0,\r\n });\r\n this.emit('user.speech.end', { timestamp: Date.now(), durationMs: result.inferenceTimeMs });\r\n\r\n // Send to AgentCore (skip empty transcriptions)\r\n const cleanText = result.text.trim();\r\n if (cleanText) {\r\n this.sendText(cleanText).catch((error: unknown) => {\r\n console.error('[AgentCore] Send text error:', error);\r\n });\r\n }\r\n }).catch((error: unknown) => {\r\n console.error('[AgentCore] Transcription error:', error);\r\n });\r\n }\r\n }\r\n\r\n // REMOVED: processAudioForAnimation() - now handled by SyncedAudioPipeline\r\n // The pipeline manages audio scheduling, LAM inference, and frame synchronization\r\n // Frames are emitted via pipeline.on('frame_ready') event (see initPipeline())\r\n\r\n /**\r\n * Detect voice activity using Silero VAD\r\n * Falls back to simple RMS if VAD not available\r\n */\r\n private async detectVoiceActivity(audio: Int16Array | Float32Array): Promise<boolean> {\r\n // Convert to Float32 if needed\r\n const float32 = audio instanceof Float32Array\r\n ? audio\r\n : this.int16ToFloat32(audio);\r\n\r\n // Use Silero VAD if available (much more accurate)\r\n if (this.vad) {\r\n // Silero VAD requires 512-sample chunks (32ms at 16kHz)\r\n const chunkSize = this.vad.getChunkSize();\r\n\r\n // Process available chunks\r\n for (let i = 0; i + chunkSize <= float32.length; i += chunkSize) {\r\n const chunk = float32.slice(i, i + chunkSize);\r\n const result = await this.vad.process(chunk);\r\n\r\n // If any chunk has speech, return true\r\n if (result.isSpeech) {\r\n return true;\r\n }\r\n }\r\n\r\n return false;\r\n }\r\n\r\n // Fallback: Simple RMS-based detection (less accurate)\r\n let sum = 0;\r\n for (let i = 0; i < float32.length; i++) {\r\n sum += float32[i] * float32[i];\r\n }\r\n const rms = Math.sqrt(sum / float32.length);\r\n return rms > 0.02;\r\n }\r\n\r\n private int16ToFloat32(int16: Int16Array): Float32Array {\r\n const float32 = new Float32Array(int16.length);\r\n for (let i = 0; i < int16.length; i++) {\r\n float32[i] = int16[i] / 32768;\r\n }\r\n return float32;\r\n }\r\n\r\n private base64ToArrayBuffer(base64: string): ArrayBuffer {\r\n const binaryString = atob(base64);\r\n const bytes = new Uint8Array(binaryString.length);\r\n for (let i = 0; i < binaryString.length; i++) {\r\n bytes[i] = binaryString.charCodeAt(i);\r\n }\r\n return bytes.buffer;\r\n }\r\n\r\n private addToHistory(message: ConversationMessage): void {\r\n this.history.push(message);\r\n this.emit('memory.updated', { messageCount: this.history.length });\r\n }\r\n\r\n private handleDisconnect(event: CloseEvent): void {\r\n this._isConnected = false;\r\n\r\n if (event.code !== 1000) {\r\n // Abnormal close - attempt reconnect\r\n if (this.wsReconnectAttempts < this.maxReconnectAttempts) {\r\n this.wsReconnectAttempts++;\r\n setTimeout(() => {\r\n if (this.currentConfig) {\r\n this.connect(this.currentConfig).catch(() => {\r\n // Will retry if fails\r\n });\r\n }\r\n }, Math.pow(2, this.wsReconnectAttempts) * 1000);\r\n } else {\r\n this.setState('error');\r\n this.emit('connection.error', {\r\n error: new Error('Max reconnection attempts reached'),\r\n recoverable: false,\r\n });\r\n }\r\n }\r\n\r\n this.emit('connection.closed', { reason: event.reason || 'Connection closed' });\r\n }\r\n}\r\n","/**\n * Conversation Orchestrator\n *\n * Manages the conversation pipeline with AgentCore:\n * - Handles session lifecycle and tenant isolation\n * - Manages adapter events and state\n *\n * @category AI\n */\n\nimport { EventEmitter } from '../../events/EventEmitter';\nimport type {\n AIAdapter,\n AIAdapterEvents,\n SessionConfig,\n TenantConfig,\n ConversationMessage,\n AISessionState,\n} from '../interfaces/AIAdapter';\nimport type { ConversationSession, SessionSnapshot } from '../interfaces/ConversationSession';\nimport { AgentCoreAdapter, type AgentCoreConfig } from '../adapters/AgentCoreAdapter';\nimport { EmotionController, type EmotionWeights } from '../../emotion/Emotion';\n\n/**\n * Orchestrator configuration\n */\nexport interface OrchestratorConfig {\n /** AgentCore adapter config */\n adapter: AgentCoreConfig;\n /** Connection timeout in ms */\n connectionTimeoutMs?: number;\n /** Max retry attempts */\n maxRetries?: number;\n}\n\n/**\n * Orchestrator events (extends AI adapter events)\n */\nexport interface OrchestratorEvents extends AIAdapterEvents {\n 'session.created': { sessionId: string; tenantId: string };\n 'session.ended': { sessionId: string; reason: string };\n}\n\n/**\n * Internal session implementation\n */\nclass ConversationSessionImpl implements ConversationSession {\n readonly sessionId: string;\n readonly createdAt: number;\n\n private _adapter: AIAdapter;\n private _config: SessionConfig;\n private _history: ConversationMessage[] = [];\n private _context = new Map<string, string>();\n private _emotionController: EmotionController;\n private _lastActivityAt: number;\n\n constructor(\n config: SessionConfig,\n adapter: AIAdapter,\n ) {\n this.sessionId = config.sessionId;\n this._config = config;\n this._adapter = adapter;\n this.createdAt = Date.now();\n this._lastActivityAt = Date.now();\n this._emotionController = new EmotionController();\n\n if (config.emotion) {\n this._emotionController.setPreset(config.emotion as Parameters<typeof this._emotionController.setPreset>[0]);\n }\n }\n\n get adapter(): AIAdapter {\n return this._adapter;\n }\n\n get config(): SessionConfig {\n return this._config;\n }\n\n get state(): AISessionState {\n return this._adapter.state;\n }\n\n get history(): ConversationMessage[] {\n return [...this._history];\n }\n\n get emotion(): EmotionWeights {\n return {};\n }\n\n get lastActivityAt(): number {\n return this._lastActivityAt;\n }\n\n async start(): Promise<void> {\n await this._adapter.connect(this._config);\n this._lastActivityAt = Date.now();\n }\n\n async end(): Promise<void> {\n await this._adapter.disconnect();\n }\n\n pushAudio(audio: Int16Array | Float32Array): void {\n this._adapter.pushAudio(audio);\n this._lastActivityAt = Date.now();\n }\n\n async sendText(text: string): Promise<void> {\n await this._adapter.sendText(text);\n this._lastActivityAt = Date.now();\n }\n\n interrupt(): void {\n this._adapter.interrupt();\n this._lastActivityAt = Date.now();\n }\n\n setEmotion(emotion: EmotionWeights): void {\n this._emotionController.set(emotion);\n }\n\n addContext(key: string, value: string): void {\n this._context.set(key, value);\n }\n\n removeContext(key: string): void {\n this._context.delete(key);\n }\n\n getContext(): Record<string, string> {\n return Object.fromEntries(this._context);\n }\n\n export(): SessionSnapshot {\n return {\n sessionId: this.sessionId,\n tenantId: this._config.tenant.tenantId,\n characterId: this._config.tenant.characterId,\n history: this._history,\n context: Object.fromEntries(this._context),\n emotion: this.emotion,\n createdAt: this.createdAt,\n lastActivityAt: this._lastActivityAt,\n };\n }\n\n import(snapshot: SessionSnapshot): void {\n this._history = [...snapshot.history];\n this._context = new Map(Object.entries(snapshot.context));\n this._lastActivityAt = snapshot.lastActivityAt;\n }\n\n syncHistory(): void {\n this._history = this._adapter.getHistory();\n }\n}\n\n/**\n * Conversation Orchestrator\n */\nexport class ConversationOrchestrator extends EventEmitter<OrchestratorEvents> {\n private config: Required<OrchestratorConfig>;\n\n // Adapter\n private adapter: AgentCoreAdapter;\n\n // Sessions per tenant\n private sessions = new Map<string, ConversationSessionImpl>();\n\n // Tenant configurations\n private tenants = new Map<string, TenantConfig>();\n\n // Health monitoring\n private healthCheckInterval: ReturnType<typeof setInterval> | null = null;\n private readonly HEALTH_CHECK_INTERVAL_MS = 30000;\n\n constructor(config: OrchestratorConfig) {\n super();\n this.config = {\n connectionTimeoutMs: 5000,\n maxRetries: 3,\n ...config,\n };\n\n // Initialize adapter\n this.adapter = new AgentCoreAdapter(config.adapter);\n }\n\n /**\n * Register a tenant\n */\n registerTenant(tenant: TenantConfig): void {\n this.tenants.set(tenant.tenantId, tenant);\n }\n\n /**\n * Unregister a tenant\n */\n unregisterTenant(tenantId: string): void {\n this.tenants.delete(tenantId);\n }\n\n /**\n * Get tenant config\n */\n getTenant(tenantId: string): TenantConfig | undefined {\n return this.tenants.get(tenantId);\n }\n\n /**\n * Create a new conversation session for a tenant\n */\n async createSession(\n tenantId: string,\n options: Partial<SessionConfig> = {}\n ): Promise<ConversationSession> {\n const tenant = this.tenants.get(tenantId);\n if (!tenant) {\n throw new Error(`Tenant not found: ${tenantId}`);\n }\n\n const sessionId = options.sessionId || this.generateSessionId();\n\n const sessionConfig: SessionConfig = {\n sessionId,\n tenant,\n systemPrompt: options.systemPrompt,\n voice: options.voice,\n emotion: options.emotion,\n language: options.language,\n };\n\n const session = new ConversationSessionImpl(sessionConfig, this.adapter);\n\n this.sessions.set(sessionId, session);\n\n // Forward adapter events\n this.forwardAdapterEvents(this.adapter, sessionId);\n\n // Connect the session\n await session.start();\n\n this.emit('session.created', { sessionId, tenantId });\n\n return session;\n }\n\n /**\n * End a session\n */\n async endSession(sessionId: string): Promise<void> {\n const session = this.sessions.get(sessionId);\n if (session) {\n await session.end();\n this.sessions.delete(sessionId);\n this.emit('session.ended', { sessionId, reason: 'Client requested' });\n }\n }\n\n /**\n * Get session by ID\n */\n getSession(sessionId: string): ConversationSession | undefined {\n return this.sessions.get(sessionId);\n }\n\n /**\n * Get all sessions for a tenant\n */\n getTenantSessions(tenantId: string): ConversationSession[] {\n return Array.from(this.sessions.values())\n .filter(s => s.config.tenant.tenantId === tenantId);\n }\n\n /**\n * Start health monitoring\n */\n startHealthMonitoring(): void {\n if (this.healthCheckInterval) return;\n\n this.healthCheckInterval = setInterval(async () => {\n await this.performHealthCheck();\n }, this.HEALTH_CHECK_INTERVAL_MS);\n }\n\n /**\n * Stop health monitoring\n */\n stopHealthMonitoring(): void {\n if (this.healthCheckInterval) {\n clearInterval(this.healthCheckInterval);\n this.healthCheckInterval = null;\n }\n }\n\n /**\n * Dispose all resources\n */\n async dispose(): Promise<void> {\n this.stopHealthMonitoring();\n\n // End all sessions\n const endPromises = Array.from(this.sessions.values()).map(s => s.end());\n await Promise.all(endPromises);\n this.sessions.clear();\n\n // Disconnect adapter\n await this.adapter.disconnect();\n }\n\n // ==================== Private Methods ====================\n\n private generateSessionId(): string {\n return `sess_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;\n }\n\n private forwardAdapterEvents(adapter: AIAdapter, sessionId: string): void {\n // Forward key events with session context\n const events: (keyof AIAdapterEvents)[] = [\n 'state.change',\n 'user.speech.start',\n 'user.speech.end',\n 'user.transcript.partial',\n 'user.transcript.final',\n 'ai.thinking.start',\n 'ai.response.start',\n 'ai.response.chunk',\n 'ai.response.end',\n 'audio.output.chunk',\n 'audio.output.end',\n 'animation',\n 'memory.updated',\n 'connection.error',\n 'interruption.detected',\n 'interruption.handled',\n ];\n\n for (const event of events) {\n adapter.on(event, (data) => {\n const eventData = data as Record<string, unknown>;\n this.emit(event, { ...eventData, sessionId } as AIAdapterEvents[typeof event]);\n });\n }\n }\n\n private async performHealthCheck(): Promise<void> {\n try {\n await this.adapter.healthCheck();\n } catch {\n // Adapter health check failed\n }\n }\n}\n","/**\n * Tenant Manager\n *\n * Handles multi-tenant isolation for the Omote Platform:\n * - Credential isolation per tenant\n * - Session scoping per tenant\n * - Quota management\n * - Token refresh\n *\n * @category AI\n */\n\nimport type { TenantConfig } from '../interfaces/AIAdapter';\n\n/**\n * Tenant quota configuration\n */\nexport interface TenantQuota {\n /** Max concurrent sessions */\n maxSessions: number;\n /** Requests per minute */\n requestsPerMinute: number;\n /** Max tokens per conversation */\n maxTokensPerConversation: number;\n /** Max audio minutes per day */\n maxAudioMinutesPerDay: number;\n}\n\n/**\n * Tenant usage tracking\n */\nexport interface TenantUsage {\n /** Current active sessions */\n currentSessions: number;\n /** Requests in current minute */\n requestsThisMinute: number;\n /** Total tokens used */\n tokensUsed: number;\n /** Audio minutes used today */\n audioMinutesToday: number;\n /** Last reset timestamp */\n lastMinuteReset: number;\n /** Last daily reset timestamp */\n lastDailyReset: number;\n}\n\n/**\n * Token refresh callback\n */\nexport type TokenRefreshCallback = () => Promise<string>;\n\n/**\n * Tenant Manager\n */\nexport class TenantManager {\n private tenants = new Map<string, TenantConfig>();\n private quotas = new Map<string, TenantQuota>();\n private usage = new Map<string, TenantUsage>();\n private tokenRefreshCallbacks = new Map<string, TokenRefreshCallback>();\n\n /**\n * Default quota for new tenants\n */\n static readonly DEFAULT_QUOTA: TenantQuota = {\n maxSessions: 10,\n requestsPerMinute: 60,\n maxTokensPerConversation: 100000,\n maxAudioMinutesPerDay: 60,\n };\n\n /**\n * Register a tenant with quota\n */\n register(\n tenant: TenantConfig,\n quota: TenantQuota = TenantManager.DEFAULT_QUOTA,\n tokenRefreshCallback?: TokenRefreshCallback\n ): void {\n this.tenants.set(tenant.tenantId, tenant);\n this.quotas.set(tenant.tenantId, quota);\n this.usage.set(tenant.tenantId, {\n currentSessions: 0,\n requestsThisMinute: 0,\n tokensUsed: 0,\n audioMinutesToday: 0,\n lastMinuteReset: Date.now(),\n lastDailyReset: Date.now(),\n });\n\n if (tokenRefreshCallback) {\n this.tokenRefreshCallbacks.set(tenant.tenantId, tokenRefreshCallback);\n }\n }\n\n /**\n * Unregister a tenant\n */\n unregister(tenantId: string): void {\n this.tenants.delete(tenantId);\n this.quotas.delete(tenantId);\n this.usage.delete(tenantId);\n this.tokenRefreshCallbacks.delete(tenantId);\n }\n\n /**\n * Get tenant config\n */\n get(tenantId: string): TenantConfig | undefined {\n return this.tenants.get(tenantId);\n }\n\n /**\n * Check if tenant exists\n */\n has(tenantId: string): boolean {\n return this.tenants.has(tenantId);\n }\n\n /**\n * Get all tenant IDs\n */\n getTenantIds(): string[] {\n return Array.from(this.tenants.keys());\n }\n\n /**\n * Check if tenant can create new session\n */\n canCreateSession(tenantId: string): boolean {\n const quota = this.quotas.get(tenantId);\n const usage = this.usage.get(tenantId);\n\n if (!quota || !usage) return false;\n\n return usage.currentSessions < quota.maxSessions;\n }\n\n /**\n * Check if tenant can make request\n */\n canMakeRequest(tenantId: string): boolean {\n const quota = this.quotas.get(tenantId);\n const usage = this.usage.get(tenantId);\n\n if (!quota || !usage) return false;\n\n // Auto-reset minute counter if needed\n this.checkMinuteReset(tenantId);\n\n return usage.requestsThisMinute < quota.requestsPerMinute;\n }\n\n /**\n * Check if tenant can use audio\n */\n canUseAudio(tenantId: string, minutes: number): boolean {\n const quota = this.quotas.get(tenantId);\n const usage = this.usage.get(tenantId);\n\n if (!quota || !usage) return false;\n\n // Auto-reset daily counter if needed\n this.checkDailyReset(tenantId);\n\n return usage.audioMinutesToday + minutes <= quota.maxAudioMinutesPerDay;\n }\n\n /**\n * Increment session count\n */\n incrementSessions(tenantId: string): void {\n const usage = this.usage.get(tenantId);\n if (usage) {\n usage.currentSessions++;\n }\n }\n\n /**\n * Decrement session count\n */\n decrementSessions(tenantId: string): void {\n const usage = this.usage.get(tenantId);\n if (usage && usage.currentSessions > 0) {\n usage.currentSessions--;\n }\n }\n\n /**\n * Record a request\n */\n recordRequest(tenantId: string): void {\n const usage = this.usage.get(tenantId);\n if (usage) {\n this.checkMinuteReset(tenantId);\n usage.requestsThisMinute++;\n }\n }\n\n /**\n * Record token usage\n */\n recordTokens(tenantId: string, tokens: number): void {\n const usage = this.usage.get(tenantId);\n if (usage) {\n usage.tokensUsed += tokens;\n }\n }\n\n /**\n * Record audio usage\n */\n recordAudioMinutes(tenantId: string, minutes: number): void {\n const usage = this.usage.get(tenantId);\n if (usage) {\n this.checkDailyReset(tenantId);\n usage.audioMinutesToday += minutes;\n }\n }\n\n /**\n * Get fresh auth token for tenant\n */\n async getAuthToken(tenantId: string): Promise<string> {\n const tenant = this.tenants.get(tenantId);\n if (!tenant) {\n throw new Error(`Tenant not found: ${tenantId}`);\n }\n\n // Check if we have a refresh callback\n const callback = this.tokenRefreshCallbacks.get(tenantId);\n if (callback) {\n const token = await callback();\n tenant.credentials.authToken = token;\n return token;\n }\n\n // Return existing token\n if (tenant.credentials.authToken) {\n return tenant.credentials.authToken;\n }\n\n throw new Error(`No auth token available for tenant: ${tenantId}`);\n }\n\n /**\n * Update tenant credentials\n */\n updateCredentials(tenantId: string, credentials: Partial<TenantConfig['credentials']>): void {\n const tenant = this.tenants.get(tenantId);\n if (tenant) {\n tenant.credentials = { ...tenant.credentials, ...credentials };\n }\n }\n\n /**\n * Get usage stats for tenant\n */\n getUsage(tenantId: string): TenantUsage | undefined {\n return this.usage.get(tenantId);\n }\n\n /**\n * Get quota for tenant\n */\n getQuota(tenantId: string): TenantQuota | undefined {\n return this.quotas.get(tenantId);\n }\n\n /**\n * Update quota for tenant\n */\n updateQuota(tenantId: string, quota: Partial<TenantQuota>): void {\n const existing = this.quotas.get(tenantId);\n if (existing) {\n this.quotas.set(tenantId, { ...existing, ...quota });\n }\n }\n\n /**\n * Reset all usage stats for a tenant\n */\n resetUsage(tenantId: string): void {\n const usage = this.usage.get(tenantId);\n if (usage) {\n usage.requestsThisMinute = 0;\n usage.tokensUsed = 0;\n usage.audioMinutesToday = 0;\n usage.lastMinuteReset = Date.now();\n usage.lastDailyReset = Date.now();\n }\n }\n\n // ==================== Private Methods ====================\n\n private checkMinuteReset(tenantId: string): void {\n const usage = this.usage.get(tenantId);\n if (!usage) return;\n\n const now = Date.now();\n if (now - usage.lastMinuteReset >= 60000) {\n usage.requestsThisMinute = 0;\n usage.lastMinuteReset = now;\n }\n }\n\n private checkDailyReset(tenantId: string): void {\n const usage = this.usage.get(tenantId);\n if (!usage) return;\n\n const now = Date.now();\n const MS_PER_DAY = 24 * 60 * 60 * 1000;\n if (now - usage.lastDailyReset >= MS_PER_DAY) {\n usage.audioMinutesToday = 0;\n usage.lastDailyReset = now;\n }\n }\n}\n","/**\n * Audio Sync Manager\n *\n * Synchronizes TTS audio playback with lip sync animation:\n * - Buffers audio for inference\n * - Manages playback timing\n * - Handles audio queue for streaming\n *\n * @category AI\n */\n\nimport { EventEmitter } from '../../events/EventEmitter';\n\n/**\n * Audio sync events\n */\nexport interface AudioSyncEvents {\n [key: string]: unknown;\n 'buffer.ready': { audio: Float32Array };\n 'playback.start': Record<string, never>;\n 'playback.end': Record<string, never>;\n 'sync.drift': { driftMs: number };\n}\n\n/**\n * Audio sync configuration\n */\nexport interface AudioSyncConfig {\n /** Target sample rate (default: 16000) */\n sampleRate?: number;\n /** Buffer size for inference (default: 16640) */\n bufferSize?: number;\n /** Overlap between buffers (default: 4160) */\n overlapSize?: number;\n /** Max drift before correction (default: 100ms) */\n maxDriftMs?: number;\n}\n\n/**\n * Audio Sync Manager\n */\nexport class AudioSyncManager extends EventEmitter<AudioSyncEvents> {\n private config: Required<AudioSyncConfig>;\n private audioBuffer: Float32Array;\n private bufferPosition = 0;\n private playbackQueue: Float32Array[] = [];\n private isPlaying = false;\n private audioContext: AudioContext | null = null;\n private playbackStartTime = 0;\n private samplesPlayed = 0;\n\n constructor(config: AudioSyncConfig = {}) {\n super();\n this.config = {\n sampleRate: 16000,\n bufferSize: 16640,\n overlapSize: 4160,\n maxDriftMs: 100,\n ...config,\n };\n\n this.audioBuffer = new Float32Array(this.config.bufferSize);\n }\n\n /**\n * Initialize audio context\n */\n async initialize(): Promise<void> {\n if (!this.audioContext) {\n this.audioContext = new AudioContext({ sampleRate: this.config.sampleRate });\n }\n\n if (this.audioContext.state === 'suspended') {\n await this.audioContext.resume();\n }\n }\n\n /**\n * Push audio chunk for processing and playback\n */\n pushAudio(audio: Float32Array): void {\n // Add to playback queue\n this.playbackQueue.push(audio);\n\n // Buffer for inference\n this.bufferForInference(audio);\n\n // Start playback if not playing\n if (!this.isPlaying && this.playbackQueue.length > 0) {\n this.startPlayback();\n }\n }\n\n /**\n * Buffer audio for inference\n */\n private bufferForInference(audio: Float32Array): void {\n let offset = 0;\n\n while (offset < audio.length) {\n const remaining = this.config.bufferSize - this.bufferPosition;\n const toCopy = Math.min(remaining, audio.length - offset);\n\n this.audioBuffer.set(audio.subarray(offset, offset + toCopy), this.bufferPosition);\n this.bufferPosition += toCopy;\n offset += toCopy;\n\n // Buffer full - emit for processing\n if (this.bufferPosition >= this.config.bufferSize) {\n this.emit('buffer.ready', { audio: new Float32Array(this.audioBuffer) });\n\n // Shift buffer with overlap for continuity\n const overlapStart = this.config.bufferSize - this.config.overlapSize;\n this.audioBuffer.copyWithin(0, overlapStart);\n this.bufferPosition = this.config.overlapSize;\n }\n }\n }\n\n /**\n * Start audio playback\n */\n private async startPlayback(): Promise<void> {\n if (!this.audioContext || this.isPlaying) return;\n\n this.isPlaying = true;\n this.playbackStartTime = this.audioContext.currentTime;\n this.samplesPlayed = 0;\n\n this.emit('playback.start', {});\n\n await this.processPlaybackQueue();\n }\n\n /**\n * Process playback queue\n */\n private async processPlaybackQueue(): Promise<void> {\n if (!this.audioContext) return;\n\n while (this.playbackQueue.length > 0) {\n const audio = this.playbackQueue.shift()!;\n\n // Create buffer and source\n const buffer = this.audioContext.createBuffer(1, audio.length, this.config.sampleRate);\n buffer.copyToChannel(audio, 0);\n\n const source = this.audioContext.createBufferSource();\n source.buffer = buffer;\n source.connect(this.audioContext.destination);\n\n // Calculate when to play\n const playTime = this.playbackStartTime + this.samplesPlayed / this.config.sampleRate;\n source.start(playTime);\n\n this.samplesPlayed += audio.length;\n\n // Check for drift\n this.checkDrift();\n\n // Wait for chunk to finish before processing next\n await new Promise(resolve => {\n source.onended = resolve;\n });\n }\n\n this.isPlaying = false;\n this.emit('playback.end', {});\n }\n\n /**\n * Check for audio/animation drift\n */\n private checkDrift(): void {\n if (!this.audioContext) return;\n\n const expectedTime = this.playbackStartTime + this.samplesPlayed / this.config.sampleRate;\n const actualTime = this.audioContext.currentTime;\n const driftMs = (actualTime - expectedTime) * 1000;\n\n if (Math.abs(driftMs) > this.config.maxDriftMs) {\n this.emit('sync.drift', { driftMs });\n }\n }\n\n /**\n * Clear playback queue\n */\n clearQueue(): void {\n this.playbackQueue = [];\n this.bufferPosition = 0;\n this.audioBuffer.fill(0);\n }\n\n /**\n * Stop playback\n */\n stop(): void {\n this.clearQueue();\n this.isPlaying = false;\n }\n\n /**\n * Get current playback position in seconds\n */\n getPlaybackPosition(): number {\n if (!this.audioContext) return 0;\n return this.audioContext.currentTime - this.playbackStartTime;\n }\n\n /**\n * Check if currently playing\n */\n getIsPlaying(): boolean {\n return this.isPlaying;\n }\n\n /**\n * Dispose resources\n */\n dispose(): void {\n this.stop();\n this.audioContext?.close();\n this.audioContext = null;\n }\n}\n","/**\n * Interruption Handler\n *\n * VAD-based interruption detection for AI conversations:\n * - Monitors user audio for speech\n * - Detects when user interrupts AI response\n * - Triggers interruption callbacks\n *\n * @category AI\n */\n\nimport { EventEmitter } from '../../events/EventEmitter';\n\n/**\n * Interruption events\n */\nexport interface InterruptionEvents {\n [key: string]: unknown;\n 'speech.detected': { rms: number };\n 'speech.ended': { durationMs: number };\n 'interruption.triggered': { rms: number; durationMs: number };\n}\n\n/**\n * Interruption handler configuration\n *\n * Industry standards applied:\n * - vadThreshold: 0.5 (Silero VAD default)\n * - minSpeechDurationMs: 200ms (Google/Amazon barge-in standard)\n * - silenceTimeoutMs: 500ms (OpenAI Realtime API standard)\n */\nexport interface InterruptionConfig {\n /** VAD probability threshold for speech detection (default: 0.5, Silero standard) */\n vadThreshold?: number;\n /** Minimum speech duration to trigger interruption (default: 200ms, Google/Amazon standard) */\n minSpeechDurationMs?: number;\n /** Silence duration to end speech (default: 500ms, OpenAI standard) */\n silenceTimeoutMs?: number;\n /** Enable interruption detection (default: true) */\n enabled?: boolean;\n}\n\n/**\n * Interruption Handler\n */\nexport class InterruptionHandler extends EventEmitter<InterruptionEvents> {\n private config: Required<InterruptionConfig>;\n private isSpeaking = false;\n private speechStartTime = 0;\n private lastSpeechTime = 0;\n private silenceTimer: ReturnType<typeof setTimeout> | null = null;\n private aiIsSpeaking = false;\n\n // Debouncing: only emit one interruption per speech session\n private interruptionTriggeredThisSession = false;\n\n constructor(config: InterruptionConfig = {}) {\n super();\n this.config = {\n vadThreshold: 0.5, // Silero VAD default\n minSpeechDurationMs: 200, // Google/Amazon barge-in standard\n silenceTimeoutMs: 500, // OpenAI Realtime API standard\n enabled: true,\n ...config,\n };\n }\n\n /**\n * Process VAD result for interruption detection\n * @param vadProbability - Speech probability from VAD (0-1)\n * @param audioEnergy - Optional RMS energy for logging (default: 0)\n */\n processVADResult(vadProbability: number, audioEnergy: number = 0): void {\n if (!this.config.enabled) return;\n\n if (vadProbability > this.config.vadThreshold) {\n this.onSpeechDetected(audioEnergy || vadProbability);\n } else {\n this.onSilenceDetected();\n }\n }\n\n /**\n * @deprecated Use processVADResult() instead. This method uses naive RMS detection.\n * Process audio samples for VAD (legacy - uses simple RMS)\n */\n processAudio(samples: Float32Array | Int16Array): void {\n if (!this.config.enabled) return;\n\n const rms = this.calculateRMS(samples);\n\n // Use RMS as proxy for VAD probability (less accurate)\n // RMS > 0.02 roughly maps to speech probability > 0.5\n const vadProbability = Math.min(rms / 0.02, 1.0);\n\n if (vadProbability > this.config.vadThreshold) {\n this.onSpeechDetected(rms);\n } else {\n this.onSilenceDetected();\n }\n }\n\n /**\n * Notify that AI started speaking\n */\n setAISpeaking(speaking: boolean): void {\n this.aiIsSpeaking = speaking;\n }\n\n /**\n * Enable/disable interruption detection\n */\n setEnabled(enabled: boolean): void {\n this.config.enabled = enabled;\n if (!enabled) {\n this.reset();\n }\n }\n\n /**\n * Update configuration\n */\n updateConfig(config: Partial<InterruptionConfig>): void {\n this.config = { ...this.config, ...config };\n }\n\n /**\n * Reset state\n */\n reset(): void {\n this.isSpeaking = false;\n this.speechStartTime = 0;\n this.lastSpeechTime = 0;\n this.interruptionTriggeredThisSession = false;\n if (this.silenceTimer) {\n clearTimeout(this.silenceTimer);\n this.silenceTimer = null;\n }\n }\n\n /**\n * Get current state\n */\n getState(): { isSpeaking: boolean; speechDurationMs: number } {\n return {\n isSpeaking: this.isSpeaking,\n speechDurationMs: this.isSpeaking ? Date.now() - this.speechStartTime : 0,\n };\n }\n\n // ==================== Private Methods ====================\n\n private calculateRMS(samples: Float32Array | Int16Array): number {\n let sum = 0;\n const scale = samples instanceof Int16Array ? 32768 : 1;\n\n for (let i = 0; i < samples.length; i++) {\n const sample = samples[i] / scale;\n sum += sample * sample;\n }\n\n return Math.sqrt(sum / samples.length);\n }\n\n private onSpeechDetected(rms: number): void {\n const now = Date.now();\n this.lastSpeechTime = now;\n\n // Clear silence timer\n if (this.silenceTimer) {\n clearTimeout(this.silenceTimer);\n this.silenceTimer = null;\n }\n\n // Start of speech\n if (!this.isSpeaking) {\n this.isSpeaking = true;\n this.speechStartTime = now;\n this.emit('speech.detected', { rms });\n }\n\n // Check for interruption (only emit ONCE per speech session)\n if (this.aiIsSpeaking && !this.interruptionTriggeredThisSession) {\n const speechDuration = now - this.speechStartTime;\n if (speechDuration >= this.config.minSpeechDurationMs) {\n this.interruptionTriggeredThisSession = true;\n this.emit('interruption.triggered', { rms, durationMs: speechDuration });\n }\n }\n }\n\n private onSilenceDetected(): void {\n if (!this.isSpeaking) return;\n\n // Start silence timer\n if (!this.silenceTimer) {\n this.silenceTimer = setTimeout(() => {\n const durationMs = this.lastSpeechTime - this.speechStartTime;\n this.isSpeaking = false;\n this.silenceTimer = null;\n // Reset interruption flag for next speech session\n this.interruptionTriggeredThisSession = false;\n this.emit('speech.ended', { durationMs });\n }, this.config.silenceTimeoutMs);\n }\n }\n}\n","/**\n * Animation Graph Types\n *\n * Renderer-agnostic animation state machine with emotion and audio-driven blending.\n *\n * @module animation\n */\n\n/**\n * Emotion labels for animation blending\n * Note: These are the 8 emotion categories used for animation, separate from the\n * internal EmotionName type used by EmotionController.\n */\nexport type EmotionLabel =\n | 'angry'\n | 'calm'\n | 'disgust'\n | 'fearful'\n | 'happy'\n | 'neutral'\n | 'sad'\n | 'surprised';\n\n/**\n * High-level animation states\n */\nexport type AnimationStateName = 'idle' | 'listening' | 'thinking' | 'speaking';\n\n/**\n * Events that trigger state transitions\n */\nexport type AnimationTrigger =\n | 'user_speech_start'\n | 'user_speech_end'\n | 'transcript_ready'\n | 'ai_response_start'\n | 'ai_audio_start'\n | 'ai_response_end'\n | 'timeout'\n | 'interrupt';\n\n/**\n * Animation layer types for blending\n */\nexport type AnimationLayer = 'base' | 'emotion' | 'gesture' | 'additive';\n\n/**\n * A single animation clip reference\n */\nexport interface AnimationClip {\n /** Unique identifier for the clip */\n name: string;\n /** Animation layer this clip belongs to */\n layer: AnimationLayer;\n /** Whether this clip loops */\n loop: boolean;\n /** Default duration in seconds (can be overridden by actual clip) */\n duration?: number;\n}\n\n/**\n * Blend weight for an animation clip\n */\nexport interface BlendWeight {\n /** Clip name */\n clip: string;\n /** Weight 0-1 */\n weight: number;\n /** Playback speed multiplier */\n speed: number;\n /** Current time in the animation (0-1 normalized) */\n time: number;\n}\n\n/**\n * Animation state definition\n */\nexport interface AnimationState {\n /** State name */\n name: AnimationStateName;\n /** Base animation clips for this state */\n baseClips: string[];\n /** Blend weights for base clips */\n baseWeights: number[];\n /** Whether emotion overlay is enabled in this state */\n emotionBlendEnabled: boolean;\n /** Whether gesture layer is enabled in this state */\n gestureBlendEnabled: boolean;\n /** Timeout in ms to auto-transition (0 = no timeout) */\n timeout: number;\n /** State to transition to on timeout */\n timeoutTarget?: AnimationStateName;\n}\n\n/**\n * Transition between states\n */\nexport interface Transition {\n /** Source state */\n from: AnimationStateName;\n /** Target state */\n to: AnimationStateName;\n /** Event that triggers this transition */\n trigger: AnimationTrigger;\n /** Blend duration in ms */\n duration: number;\n /** Optional condition function */\n condition?: () => boolean;\n}\n\n/**\n * Emotion to animation mapping\n */\nexport interface EmotionAnimationMap {\n /** Emotion label */\n emotion: EmotionLabel;\n /** Animation clip to blend */\n clip: string;\n /** Maximum blend weight for this emotion */\n maxWeight: number;\n /** Blend speed (weight change per second) */\n blendSpeed: number;\n}\n\n/**\n * Configuration for AnimationGraph\n */\nexport interface AnimationGraphConfig {\n /** Available animation states */\n states: AnimationState[];\n /** Transitions between states */\n transitions: Transition[];\n /** Emotion to animation mappings */\n emotionMappings: EmotionAnimationMap[];\n /** Gesture clips for audio-driven animation */\n gestureClips: string[];\n /** Initial state */\n initialState: AnimationStateName;\n /** Global blend speed for state transitions (weight/sec) */\n transitionBlendSpeed: number;\n /** Minimum audio energy to trigger gestures (0-1) */\n gestureThreshold: number;\n /** Gesture intensity multiplier */\n gestureIntensity: number;\n}\n\n/**\n * Current output of the animation graph\n */\nexport interface AnimationOutput {\n /** Current state name */\n state: AnimationStateName;\n /** All blend weights to apply */\n blendWeights: BlendWeight[];\n /** Active emotion (if any) */\n activeEmotion: EmotionLabel | null;\n /** Current gesture intensity (0-1) */\n gestureIntensity: number;\n /** Whether currently transitioning between states */\n isTransitioning: boolean;\n /** Transition progress (0-1) if transitioning */\n transitionProgress: number;\n}\n\n/**\n * Events emitted by AnimationGraph\n */\nexport type AnimationGraphEvents = {\n /** State changed */\n 'state.change': {\n from: AnimationStateName;\n to: AnimationStateName;\n trigger: AnimationTrigger;\n };\n /** Transition started */\n 'transition.start': {\n from: AnimationStateName;\n to: AnimationStateName;\n duration: number;\n };\n /** Transition completed */\n 'transition.end': {\n state: AnimationStateName;\n };\n /** Emotion changed */\n 'emotion.change': {\n emotion: EmotionLabel | null;\n confidence: number;\n };\n /** Animation output updated (every frame) */\n 'output.update': AnimationOutput;\n /** Index signature for EventEmitter compatibility */\n [key: string]: unknown;\n};\n\n/**\n * Default animation graph configuration\n */\nexport const DEFAULT_ANIMATION_CONFIG: AnimationGraphConfig = {\n initialState: 'idle',\n transitionBlendSpeed: 4.0, // Full blend in 250ms\n gestureThreshold: 0.1,\n gestureIntensity: 1.0,\n\n states: [\n {\n name: 'idle',\n baseClips: ['idle_breathe'],\n baseWeights: [1.0],\n emotionBlendEnabled: true,\n gestureBlendEnabled: false,\n timeout: 0,\n },\n {\n name: 'listening',\n baseClips: ['idle_attentive'],\n baseWeights: [1.0],\n emotionBlendEnabled: true,\n gestureBlendEnabled: false,\n timeout: 10000, // 10s timeout back to idle\n timeoutTarget: 'idle',\n },\n {\n name: 'thinking',\n baseClips: ['thinking_look_up', 'thinking_hand_chin'],\n baseWeights: [0.6, 0.4],\n emotionBlendEnabled: false,\n gestureBlendEnabled: false,\n timeout: 5000, // 5s max thinking\n timeoutTarget: 'idle',\n },\n {\n name: 'speaking',\n baseClips: ['talking_idle'],\n baseWeights: [1.0],\n emotionBlendEnabled: true,\n gestureBlendEnabled: true,\n timeout: 0,\n },\n ],\n\n transitions: [\n // User starts speaking\n { from: 'idle', to: 'listening', trigger: 'user_speech_start', duration: 300 },\n { from: 'speaking', to: 'listening', trigger: 'user_speech_start', duration: 200 }, // Interrupt\n\n // User stops speaking, processing\n { from: 'listening', to: 'thinking', trigger: 'transcript_ready', duration: 400 },\n\n // AI starts responding\n { from: 'thinking', to: 'speaking', trigger: 'ai_audio_start', duration: 300 },\n { from: 'idle', to: 'speaking', trigger: 'ai_audio_start', duration: 400 },\n\n // AI done\n { from: 'speaking', to: 'idle', trigger: 'ai_response_end', duration: 500 },\n\n // Timeouts\n { from: 'listening', to: 'idle', trigger: 'timeout', duration: 600 },\n { from: 'thinking', to: 'idle', trigger: 'timeout', duration: 400 },\n\n // Interrupts\n { from: 'speaking', to: 'listening', trigger: 'interrupt', duration: 150 },\n ],\n\n emotionMappings: [\n { emotion: 'happy', clip: 'emotion_happy', maxWeight: 0.7, blendSpeed: 2.0 },\n { emotion: 'sad', clip: 'emotion_sad', maxWeight: 0.6, blendSpeed: 1.5 },\n { emotion: 'angry', clip: 'emotion_angry', maxWeight: 0.5, blendSpeed: 2.5 },\n { emotion: 'fearful', clip: 'emotion_fear', maxWeight: 0.5, blendSpeed: 2.0 },\n { emotion: 'surprised', clip: 'emotion_surprised', maxWeight: 0.6, blendSpeed: 2.5 },\n { emotion: 'calm', clip: 'emotion_calm', maxWeight: 0.3, blendSpeed: 1.0 },\n { emotion: 'disgust', clip: 'emotion_disgust', maxWeight: 0.4, blendSpeed: 2.0 },\n { emotion: 'neutral', clip: 'emotion_neutral', maxWeight: 0.0, blendSpeed: 1.0 },\n ],\n\n gestureClips: ['gesture_hand_small', 'gesture_hand_medium', 'gesture_hand_large'],\n};\n","/**\n * Animation Graph\n *\n * State machine for character animation with emotion and audio-driven blending.\n * Renderer-agnostic - outputs blend weights that any 3D engine can consume.\n *\n * @example\n * ```typescript\n * import { AnimationGraph, DEFAULT_ANIMATION_CONFIG } from '@omote/core';\n *\n * const graph = new AnimationGraph(DEFAULT_ANIMATION_CONFIG);\n *\n * // Connect to voice pipeline\n * graph.on('output.update', (output) => {\n * // Apply blend weights to your 3D character\n * for (const { clip, weight } of output.blendWeights) {\n * mixer.getAction(clip).setEffectiveWeight(weight);\n * }\n * });\n *\n * // Drive from voice state\n * voiceState.on('listening', () => graph.trigger('user_speech_start'));\n * voiceState.on('thinking', () => graph.trigger('transcript_ready'));\n * voiceState.on('speaking', () => graph.trigger('ai_audio_start'));\n *\n * // Drive from emotion detection\n * emotion.on('result', ({ emotion, confidence }) => {\n * graph.setEmotion(emotion, confidence);\n * });\n *\n * // Update every frame\n * function animate(deltaTime: number) {\n * graph.update(deltaTime);\n * }\n * ```\n *\n * @module animation\n */\n\nimport { EventEmitter } from '../events';\nimport type {\n EmotionLabel,\n AnimationGraphConfig,\n AnimationGraphEvents,\n AnimationTrigger,\n AnimationOutput,\n AnimationState,\n AnimationStateName,\n BlendWeight,\n Transition,\n} from './types';\nimport { DEFAULT_ANIMATION_CONFIG } from './types';\n\n/**\n * Animation state machine with smooth blending\n */\nexport class AnimationGraph extends EventEmitter<AnimationGraphEvents> {\n private config: AnimationGraphConfig;\n private currentState: AnimationState;\n private previousState: AnimationState | null = null;\n\n // Transition state\n private isTransitioning: boolean = false;\n private transitionProgress: number = 0;\n private transitionDuration: number = 0;\n private transitionStartTime: number = 0;\n\n // Emotion state\n private currentEmotion: EmotionLabel | null = null;\n private emotionConfidence: number = 0;\n private emotionBlendWeight: number = 0;\n private targetEmotionWeight: number = 0;\n\n // Gesture state (audio-driven)\n private audioEnergy: number = 0;\n private gestureWeight: number = 0;\n private currentGestureClip: number = 0;\n\n // Timing\n private stateEnterTime: number = 0;\n private lastUpdateTime: number = 0;\n\n // Blend weights cache\n private cachedOutput: AnimationOutput;\n\n constructor(config: Partial<AnimationGraphConfig> = {}) {\n super();\n this.config = { ...DEFAULT_ANIMATION_CONFIG, ...config };\n\n // Find initial state\n const initialState = this.config.states.find(\n (s) => s.name === this.config.initialState\n );\n if (!initialState) {\n throw new Error(`Initial state '${this.config.initialState}' not found`);\n }\n this.currentState = initialState;\n this.stateEnterTime = Date.now();\n this.lastUpdateTime = Date.now();\n\n // Initialize cached output\n this.cachedOutput = this.computeOutput();\n }\n\n /**\n * Get current state name\n */\n get state(): AnimationStateName {\n return this.currentState.name;\n }\n\n /**\n * Get current animation output\n */\n get output(): AnimationOutput {\n return this.cachedOutput;\n }\n\n /**\n * Trigger an animation event (may cause state transition)\n */\n trigger(event: AnimationTrigger): boolean {\n // Find matching transition\n const transition = this.config.transitions.find(\n (t) =>\n t.from === this.currentState.name &&\n t.trigger === event &&\n (!t.condition || t.condition())\n );\n\n if (!transition) {\n return false;\n }\n\n this.startTransition(transition, event);\n return true;\n }\n\n /**\n * Set current emotion (from DistilHuBERT or manual)\n */\n setEmotion(emotion: EmotionLabel, confidence: number): void {\n const prevEmotion = this.currentEmotion;\n\n this.currentEmotion = emotion;\n this.emotionConfidence = Math.max(0, Math.min(1, confidence));\n\n // Find emotion mapping\n const mapping = this.config.emotionMappings.find(\n (m) => m.emotion === emotion\n );\n if (mapping && this.currentState.emotionBlendEnabled) {\n this.targetEmotionWeight = mapping.maxWeight * this.emotionConfidence;\n } else {\n this.targetEmotionWeight = 0;\n }\n\n if (prevEmotion !== emotion) {\n this.emit('emotion.change', { emotion, confidence });\n }\n }\n\n /**\n * Clear current emotion\n */\n clearEmotion(): void {\n this.currentEmotion = null;\n this.emotionConfidence = 0;\n this.targetEmotionWeight = 0;\n this.emit('emotion.change', { emotion: null, confidence: 0 });\n }\n\n /**\n * Set audio energy for gesture animation (0-1)\n */\n setAudioEnergy(energy: number): void {\n this.audioEnergy = Math.max(0, Math.min(1, energy));\n }\n\n /**\n * Force transition to a specific state\n */\n setState(stateName: AnimationStateName, blendDuration: number = 300): void {\n const targetState = this.config.states.find((s) => s.name === stateName);\n if (!targetState) {\n console.warn(`[AnimationGraph] State '${stateName}' not found`);\n return;\n }\n\n if (targetState.name === this.currentState.name && !this.isTransitioning) {\n return;\n }\n\n // Create a manual transition\n const manualTransition: Transition = {\n from: this.currentState.name,\n to: stateName,\n trigger: 'timeout', // Arbitrary, not used for manual\n duration: blendDuration,\n };\n\n this.startTransition(manualTransition, 'timeout');\n }\n\n /**\n * Update animation graph (call every frame)\n * @param deltaMs Time since last update in milliseconds\n */\n update(deltaMs?: number): AnimationOutput {\n const now = Date.now();\n const dt = deltaMs ?? now - this.lastUpdateTime;\n this.lastUpdateTime = now;\n\n const dtSeconds = dt / 1000;\n\n // Update transition\n if (this.isTransitioning) {\n this.updateTransition(dtSeconds);\n }\n\n // Check timeout\n this.checkTimeout(now);\n\n // Update emotion blend\n this.updateEmotionBlend(dtSeconds);\n\n // Update gesture\n this.updateGesture(dtSeconds);\n\n // Compute and cache output\n this.cachedOutput = this.computeOutput();\n this.emit('output.update', this.cachedOutput);\n\n return this.cachedOutput;\n }\n\n /**\n * Reset to initial state\n */\n reset(): void {\n const initialState = this.config.states.find(\n (s) => s.name === this.config.initialState\n );\n if (initialState) {\n this.currentState = initialState;\n this.previousState = null;\n this.isTransitioning = false;\n this.transitionProgress = 0;\n this.stateEnterTime = Date.now();\n this.emotionBlendWeight = 0;\n this.gestureWeight = 0;\n this.cachedOutput = this.computeOutput();\n }\n }\n\n /**\n * Get all clip names used by this graph\n */\n getRequiredClips(): string[] {\n const clips = new Set<string>();\n\n // Base clips from all states\n for (const state of this.config.states) {\n for (const clip of state.baseClips) {\n clips.add(clip);\n }\n }\n\n // Emotion clips\n for (const mapping of this.config.emotionMappings) {\n clips.add(mapping.clip);\n }\n\n // Gesture clips\n for (const clip of this.config.gestureClips) {\n clips.add(clip);\n }\n\n return Array.from(clips);\n }\n\n // ─────────────────────────────────────────────────────────────────\n // Private methods\n // ─────────────────────────────────────────────────────────────────\n\n private startTransition(transition: Transition, event: AnimationTrigger): void {\n const targetState = this.config.states.find(\n (s) => s.name === transition.to\n );\n if (!targetState) {\n console.warn(`[AnimationGraph] Target state '${transition.to}' not found`);\n return;\n }\n\n const fromState = this.currentState.name;\n\n this.previousState = this.currentState;\n this.currentState = targetState;\n this.isTransitioning = true;\n this.transitionProgress = 0;\n this.transitionDuration = transition.duration;\n this.transitionStartTime = Date.now();\n this.stateEnterTime = Date.now();\n\n // Update emotion target based on new state\n if (!this.currentState.emotionBlendEnabled) {\n this.targetEmotionWeight = 0;\n }\n\n this.emit('state.change', {\n from: fromState,\n to: targetState.name,\n trigger: event,\n });\n\n this.emit('transition.start', {\n from: fromState,\n to: targetState.name,\n duration: transition.duration,\n });\n }\n\n private updateTransition(dtSeconds: number): void {\n if (!this.isTransitioning || this.transitionDuration <= 0) {\n this.isTransitioning = false;\n this.transitionProgress = 1;\n return;\n }\n\n // Linear progress based on time\n const elapsed = Date.now() - this.transitionStartTime;\n this.transitionProgress = Math.min(1, elapsed / this.transitionDuration);\n\n if (this.transitionProgress >= 1) {\n this.isTransitioning = false;\n this.transitionProgress = 1;\n this.previousState = null;\n this.emit('transition.end', { state: this.currentState.name });\n }\n }\n\n private checkTimeout(now: number): void {\n if (this.isTransitioning) return;\n if (this.currentState.timeout <= 0) return;\n\n const elapsed = now - this.stateEnterTime;\n if (elapsed >= this.currentState.timeout) {\n this.trigger('timeout');\n }\n }\n\n private updateEmotionBlend(dtSeconds: number): void {\n if (!this.currentEmotion) {\n // Decay emotion weight\n this.emotionBlendWeight = Math.max(\n 0,\n this.emotionBlendWeight - dtSeconds * 2.0\n );\n return;\n }\n\n const mapping = this.config.emotionMappings.find(\n (m) => m.emotion === this.currentEmotion\n );\n const blendSpeed = mapping?.blendSpeed ?? 2.0;\n\n // Smoothly interpolate to target\n const diff = this.targetEmotionWeight - this.emotionBlendWeight;\n const maxChange = blendSpeed * dtSeconds;\n\n if (Math.abs(diff) <= maxChange) {\n this.emotionBlendWeight = this.targetEmotionWeight;\n } else {\n this.emotionBlendWeight += Math.sign(diff) * maxChange;\n }\n }\n\n private updateGesture(dtSeconds: number): void {\n if (!this.currentState.gestureBlendEnabled) {\n this.gestureWeight = Math.max(0, this.gestureWeight - dtSeconds * 4.0);\n return;\n }\n\n // Map audio energy to gesture weight\n const targetGesture =\n this.audioEnergy > this.config.gestureThreshold\n ? this.audioEnergy * this.config.gestureIntensity\n : 0;\n\n // Smooth the gesture weight\n const diff = targetGesture - this.gestureWeight;\n const blendSpeed = 8.0; // Fast response\n const maxChange = blendSpeed * dtSeconds;\n\n if (Math.abs(diff) <= maxChange) {\n this.gestureWeight = targetGesture;\n } else {\n this.gestureWeight += Math.sign(diff) * maxChange;\n }\n\n // Select gesture clip based on intensity\n const clipCount = this.config.gestureClips.length;\n if (clipCount > 0) {\n this.currentGestureClip = Math.min(\n clipCount - 1,\n Math.floor(this.gestureWeight * clipCount)\n );\n }\n }\n\n private computeOutput(): AnimationOutput {\n const blendWeights: BlendWeight[] = [];\n\n // Smooth transition weight (ease in-out)\n const t = this.transitionProgress;\n const transitionWeight = t * t * (3 - 2 * t); // smoothstep\n\n // Previous state clips (fading out)\n if (this.previousState && this.isTransitioning) {\n const fadeOut = 1 - transitionWeight;\n for (let i = 0; i < this.previousState.baseClips.length; i++) {\n const clip = this.previousState.baseClips[i];\n const baseWeight = this.previousState.baseWeights[i] ?? 1.0;\n blendWeights.push({\n clip,\n weight: baseWeight * fadeOut,\n speed: 1.0,\n time: 0,\n });\n }\n }\n\n // Current state clips (fading in or full)\n const fadeIn = this.isTransitioning ? transitionWeight : 1.0;\n for (let i = 0; i < this.currentState.baseClips.length; i++) {\n const clip = this.currentState.baseClips[i];\n const baseWeight = this.currentState.baseWeights[i] ?? 1.0;\n blendWeights.push({\n clip,\n weight: baseWeight * fadeIn,\n speed: 1.0,\n time: 0,\n });\n }\n\n // Emotion overlay\n if (this.currentEmotion && this.emotionBlendWeight > 0.01) {\n const mapping = this.config.emotionMappings.find(\n (m) => m.emotion === this.currentEmotion\n );\n if (mapping) {\n blendWeights.push({\n clip: mapping.clip,\n weight: this.emotionBlendWeight,\n speed: 1.0,\n time: 0,\n });\n }\n }\n\n // Gesture layer\n if (this.gestureWeight > 0.01 && this.config.gestureClips.length > 0) {\n const gestureClip = this.config.gestureClips[this.currentGestureClip];\n blendWeights.push({\n clip: gestureClip,\n weight: this.gestureWeight,\n speed: 1.0 + this.audioEnergy * 0.5, // Faster with more energy\n time: 0,\n });\n }\n\n return {\n state: this.currentState.name,\n blendWeights,\n activeEmotion: this.emotionBlendWeight > 0.01 ? this.currentEmotion : null,\n gestureIntensity: this.gestureWeight,\n isTransitioning: this.isTransitioning,\n transitionProgress: this.transitionProgress,\n };\n }\n}\n","/**\n * Minimal 2D Simplex Noise\n *\n * Based on Stefan Gustavson's public domain implementation.\n * Returns values in [-1, 1] range. Used by ProceduralLifeLayer\n * for organic, non-repetitive brow drift animation.\n *\n * @module animation\n */\n\n// Permutation table (doubled to avoid wrapping)\nconst perm = new Uint8Array(512);\nconst grad2 = [\n [1, 1], [-1, 1], [1, -1], [-1, -1],\n [1, 0], [-1, 0], [0, 1], [0, -1],\n];\n\n// Seed the permutation table deterministically\nconst p = [\n 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225,\n 140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148,\n 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32,\n 57, 177, 33, 88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175,\n 74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122,\n 60, 211, 133, 230, 220, 105, 92, 41, 55, 46, 245, 40, 244, 102, 143, 54,\n 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169,\n 200, 196, 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64,\n 52, 217, 226, 250, 124, 123, 5, 202, 38, 147, 118, 126, 255, 82, 85, 212,\n 207, 206, 59, 227, 47, 16, 58, 17, 182, 189, 28, 42, 223, 183, 170, 213,\n 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9,\n 129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, 104,\n 218, 246, 97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241,\n 81, 51, 145, 235, 249, 14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157,\n 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93,\n 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180,\n];\nfor (let i = 0; i < 256; i++) {\n perm[i] = p[i];\n perm[i + 256] = p[i];\n}\n\nconst F2 = 0.5 * (Math.sqrt(3) - 1);\nconst G2 = (3 - Math.sqrt(3)) / 6;\n\nfunction dot2(g: number[], x: number, y: number): number {\n return g[0] * x + g[1] * y;\n}\n\n/**\n * 2D Simplex Noise\n *\n * @param x - X coordinate\n * @param y - Y coordinate\n * @returns Noise value in [-1, 1]\n */\nexport function simplex2d(x: number, y: number): number {\n // Skew input space to determine simplex cell\n const s = (x + y) * F2;\n const i = Math.floor(x + s);\n const j = Math.floor(y + s);\n\n const t = (i + j) * G2;\n const X0 = i - t;\n const Y0 = j - t;\n const x0 = x - X0;\n const y0 = y - Y0;\n\n // Determine which simplex we're in\n const i1 = x0 > y0 ? 1 : 0;\n const j1 = x0 > y0 ? 0 : 1;\n\n const x1 = x0 - i1 + G2;\n const y1 = y0 - j1 + G2;\n const x2 = x0 - 1 + 2 * G2;\n const y2 = y0 - 1 + 2 * G2;\n\n const ii = i & 255;\n const jj = j & 255;\n const gi0 = perm[ii + perm[jj]] % 8;\n const gi1 = perm[ii + i1 + perm[jj + j1]] % 8;\n const gi2 = perm[ii + 1 + perm[jj + 1]] % 8;\n\n // Calculate contribution from three corners\n let n0 = 0;\n let t0 = 0.5 - x0 * x0 - y0 * y0;\n if (t0 >= 0) {\n t0 *= t0;\n n0 = t0 * t0 * dot2(grad2[gi0], x0, y0);\n }\n\n let n1 = 0;\n let t1 = 0.5 - x1 * x1 - y1 * y1;\n if (t1 >= 0) {\n t1 *= t1;\n n1 = t1 * t1 * dot2(grad2[gi1], x1, y1);\n }\n\n let n2 = 0;\n let t2 = 0.5 - x2 * x2 - y2 * y2;\n if (t2 >= 0) {\n t2 *= t2;\n n2 = t2 * t2 * dot2(grad2[gi2], x2, y2);\n }\n\n // Scale to [-1, 1]\n return 70 * (n0 + n1 + n2);\n}\n","/**\n * ProceduralLifeLayer - Renderer-agnostic procedural animation system\n *\n * Outputs per-frame blendshape values and head deltas for organic life-like\n * animation. No Three.js, no React, no R3F — just math.\n *\n * Implements research-based eye behavior, blinks, gaze breaks, microsaccades,\n * breathing/postural sway, and simplex noise-driven brow drift.\n *\n * Research sources:\n * - Blink frequency: 15-20/min (every 3-4s), PMC4043155\n * - Saccade latency: ~200ms, duration 20-200ms\n * - Microsaccades: ~1/second, amplitude 0.02-0.05, Scholarpedia\n * - Fixation duration: 200-350ms, Nature Scientific Reports\n * - Brow noise: NVIDIA Audio2Face, Unreal MetaHuman layered procedural animation\n *\n * @category Animation\n *\n * @example\n * ```typescript\n * import { ProceduralLifeLayer } from '@omote/core';\n *\n * const lifeLayer = new ProceduralLifeLayer();\n *\n * // In animation loop:\n * const output = lifeLayer.update(delta, {\n * eyeTargetX: normalizedX, // -1..1 from camera math\n * eyeTargetY: normalizedY,\n * audioEnergy: energy, // 0-1 from AudioEnergyAnalyzer\n * isSpeaking: true,\n * });\n *\n * // Apply blendshapes to mesh\n * for (const [name, value] of Object.entries(output.blendshapes)) {\n * const idx = mesh.morphTargetDictionary?.[name];\n * if (idx !== undefined) mesh.morphTargetInfluences![idx] = value;\n * }\n *\n * // Apply head delta to head bone\n * headBone.rotation.y += output.headDelta.yaw;\n * headBone.rotation.x += output.headDelta.pitch;\n * ```\n */\n\nimport { simplex2d } from './simplex2d';\n\n/**\n * Configuration for ProceduralLifeLayer\n */\nexport interface LifeLayerConfig {\n /** Seconds between blinks [min, max]. Default: [2.5, 6] */\n blinkIntervalRange?: [number, number];\n /** Seconds between gaze breaks [min, max]. Default: [3, 8] */\n gazeBreakIntervalRange?: [number, number];\n /** Gaze break deviation range [min, max]. Default: [0.15, 0.4] */\n gazeBreakAmplitudeRange?: [number, number];\n /** Eye micro-motion noise amplitude (0 to disable). Default: 0.06 */\n eyeNoiseAmplitude?: number;\n /** Base simplex noise amplitude for brow drift. Default: 0.30 */\n browNoiseAmplitude?: number;\n /** Multiply brow noise when speaking. Default: 2.0 */\n browNoiseSpeechMultiplier?: number;\n /** Breathing rate in Hz (0.25 = 15 breaths/min). Default: 0.25 */\n breathingRate?: number;\n /** Postural sway amplitude in radians. Default: 0.002 */\n posturalSwayAmplitude?: number;\n /** Max eye movement from center (0-1). Default: 0.8 */\n eyeMaxDeviation?: number;\n /** Eye smoothing factor (higher = faster response). Default: 15 */\n eyeSmoothing?: number;\n}\n\n/**\n * Per-frame input to the life layer\n */\nexport interface LifeLayerInput {\n /** Normalized eye target X: -1 (left) to 1 (right). Consumer computes from camera. */\n eyeTargetX?: number;\n /** Normalized eye target Y: -1 (down) to 1 (up). Consumer computes from camera. */\n eyeTargetY?: number;\n /** Audio energy 0-1 (from AudioEnergyAnalyzer). Drives brow noise amplitude. */\n audioEnergy?: number;\n /** Whether avatar is speaking. Multiplies brow noise amplitude. */\n isSpeaking?: boolean;\n}\n\n/**\n * Per-frame output from the life layer\n */\nexport interface LifeLayerOutput {\n /** Blendshape values to SET directly on mesh (eyes, brows, cheeks). */\n blendshapes: Record<string, number>;\n /** Head rotation deltas in radians. Consumer adds to head bone rotation. */\n headDelta: { yaw: number; pitch: number };\n}\n\n// Blink phase constants\nconst PHASE_OPEN = 0;\nconst PHASE_CLOSING = 1;\nconst PHASE_CLOSED = 2;\nconst PHASE_OPENING = 3;\n\n// Blink timing (researched: fast close, brief hold, slower open)\nconst BLINK_CLOSE_DURATION = 0.06; // 60ms\nconst BLINK_HOLD_DURATION = 0.04; // 40ms\nconst BLINK_OPEN_DURATION = 0.15; // 150ms\nconst BLINK_ASYMMETRY_DELAY = 0.008; // 8ms offset between eyes\n\n// Gaze break phase timing\nconst GAZE_BREAK_DURATION = 0.12; // 120ms to glance away (~7 frames, smooth onset)\nconst GAZE_BREAK_HOLD_DURATION = 0.3; // 300ms hold\nconst GAZE_BREAK_RETURN_DURATION = 0.15; // 150ms to return\n\n// Eye micro-motion noise frequencies (Hz) — continuous drift, no discrete events\nconst EYE_NOISE_X_FREQ = 0.8;\nconst EYE_NOISE_Y_FREQ = 0.6;\nconst EYE_NOISE_X_PHASE = 73.1; // offset to decorrelate from brow noise\nconst EYE_NOISE_Y_PHASE = 91.7;\n\n// Brow noise frequencies (Hz) — organic drift, fast enough to be visible\nconst BROW_INNER_UP_FREQ = 0.4;\nconst BROW_OUTER_LEFT_FREQ = 0.35;\nconst BROW_OUTER_RIGHT_FREQ = 0.38;\nconst BROW_DOWN_FREQ = 0.3;\n\n// Brow noise phase offsets for each channel\nconst BROW_INNER_UP_PHASE = 0;\nconst BROW_OUTER_LEFT_PHASE = 17.3;\nconst BROW_OUTER_RIGHT_PHASE = 31.7;\nconst BROW_DOWN_LEFT_PHASE = 47.1;\nconst BROW_DOWN_RIGHT_PHASE = 59.3;\n\n// Emphasis detection\nconst EMPHASIS_ENERGY_THRESHOLD = 0.3;\nconst EMPHASIS_DECAY_RATE = 4.0; // per second\n\nfunction clamp(v: number, min: number, max: number): number {\n return v < min ? min : v > max ? max : v;\n}\n\nfunction randomRange(min: number, max: number): number {\n return min + Math.random() * (max - min);\n}\n\nfunction smoothStep(t: number): number {\n return t * t * (3 - 2 * t);\n}\n\nfunction softClamp(v: number, max: number): number {\n return Math.tanh(v / max) * max;\n}\n\n/**\n * ProceduralLifeLayer - Renderer-agnostic procedural animation\n *\n * Generates per-frame blendshape values and head rotation deltas\n * for natural eye behavior, blinks, brow movement, and breathing.\n */\nexport class ProceduralLifeLayer {\n // Config\n private blinkIntervalRange: [number, number];\n private gazeBreakIntervalRange: [number, number];\n private gazeBreakAmplitudeRange: [number, number];\n private eyeNoiseAmplitude: number;\n private browNoiseAmplitude: number;\n private browNoiseSpeechMultiplier: number;\n private breathingRate: number;\n private posturalSwayAmplitude: number;\n private eyeMaxDeviation: number;\n private eyeSmoothing: number;\n\n // Blink state\n private blinkTimer = 0;\n private blinkInterval: number;\n private blinkPhase = PHASE_OPEN;\n private blinkProgress = 0;\n private asymmetryRight = 0.97;\n private smoothedBlinkLeft = 0;\n private smoothedBlinkRight = 0;\n\n // Eye contact (smoothed)\n private smoothedEyeX = 0;\n private smoothedEyeY = 0;\n\n // Eye micro-motion (continuous simplex noise, no discrete events)\n private eyeNoiseTime = 0;\n\n // Gaze break state\n private gazeBreakTimer = 0;\n private gazeBreakInterval: number;\n private gazeBreakPhase = PHASE_OPEN;\n private gazeBreakProgress = 0;\n private gazeBreakTargetX = 0;\n private gazeBreakTargetY = 0;\n private gazeBreakCurrentX = 0;\n private gazeBreakCurrentY = 0;\n\n // Breathing / postural sway\n private microMotionTime = 0;\n private breathingPhase = 0;\n\n // Brow noise\n private noiseTime = 0;\n private previousEnergy = 0;\n private emphasisLevel = 0;\n\n constructor(config?: LifeLayerConfig) {\n this.blinkIntervalRange = config?.blinkIntervalRange ?? [2.5, 6];\n this.gazeBreakIntervalRange = config?.gazeBreakIntervalRange ?? [3, 8];\n this.gazeBreakAmplitudeRange = config?.gazeBreakAmplitudeRange ?? [0.15, 0.4];\n this.eyeNoiseAmplitude = config?.eyeNoiseAmplitude ?? 0.06;\n this.browNoiseAmplitude = config?.browNoiseAmplitude ?? 0.30;\n this.browNoiseSpeechMultiplier = config?.browNoiseSpeechMultiplier ?? 2.0;\n this.breathingRate = config?.breathingRate ?? 0.25;\n this.posturalSwayAmplitude = config?.posturalSwayAmplitude ?? 0.002;\n this.eyeMaxDeviation = config?.eyeMaxDeviation ?? 0.8;\n this.eyeSmoothing = config?.eyeSmoothing ?? 15;\n\n // Initialize with random intervals\n this.blinkInterval = randomRange(...this.blinkIntervalRange);\n this.gazeBreakInterval = randomRange(...this.gazeBreakIntervalRange);\n }\n\n /**\n * Update the life layer and produce output for this frame.\n *\n * @param delta - Time since last frame in seconds\n * @param input - Per-frame input (eye target, audio energy, speaking state)\n * @returns Blendshape values and head rotation deltas\n */\n update(delta: number, input?: LifeLayerInput): LifeLayerOutput {\n const eyeTargetX = input?.eyeTargetX ?? 0;\n const eyeTargetY = input?.eyeTargetY ?? 0;\n const audioEnergy = input?.audioEnergy ?? 0;\n const isSpeaking = input?.isSpeaking ?? false;\n\n // Cap delta to prevent instant snaps when returning from backgrounded tab\n const safeDelta = Math.min(delta, 0.1);\n\n const blendshapes: Record<string, number> = {};\n\n // =====================================================================\n // BLINKS\n // =====================================================================\n this.updateBlinks(delta);\n\n const blinkSmoothing = 45;\n const blinkValues = this.getBlinkValues();\n this.smoothedBlinkLeft += (blinkValues.left - this.smoothedBlinkLeft) * Math.min(1, safeDelta * blinkSmoothing);\n this.smoothedBlinkRight += (blinkValues.right - this.smoothedBlinkRight) * Math.min(1, safeDelta * blinkSmoothing);\n\n blendshapes['eyeBlinkLeft'] = this.smoothedBlinkLeft;\n blendshapes['eyeBlinkRight'] = this.smoothedBlinkRight;\n\n // =====================================================================\n // EYE CONTACT (smooth toward target)\n // =====================================================================\n this.smoothedEyeX += (eyeTargetX - this.smoothedEyeX) * Math.min(1, safeDelta * this.eyeSmoothing);\n this.smoothedEyeY += (eyeTargetY - this.smoothedEyeY) * Math.min(1, safeDelta * this.eyeSmoothing);\n\n // =====================================================================\n // EYE MICRO-MOTION (continuous simplex noise replaces discrete saccades)\n // =====================================================================\n this.eyeNoiseTime += delta;\n const microMotion = this.getEyeMicroMotion();\n\n // =====================================================================\n // GAZE BREAKS\n // =====================================================================\n this.updateGazeBreaks(delta);\n\n // =====================================================================\n // COMBINED EYE DIRECTION\n // =====================================================================\n const finalEyeX = this.smoothedEyeX + this.gazeBreakCurrentX + microMotion.x;\n const finalEyeY = this.smoothedEyeY + this.gazeBreakCurrentY + microMotion.y;\n\n // Soft clamp: tanh compression avoids hard boundary pop\n const clampedX = softClamp(finalEyeX, this.eyeMaxDeviation);\n const clampedY = softClamp(finalEyeY, this.eyeMaxDeviation);\n\n // Convert to blendshape values with smooth zero-crossing\n // Quadratic dead zone prevents hard direction switch when crossing center\n const deadZone = 0.02;\n const lookRight = clampedX > deadZone ? clampedX\n : clampedX > 0 ? clampedX * (clampedX / deadZone) : 0;\n const lookLeft = clampedX < -deadZone ? -clampedX\n : clampedX < 0 ? -clampedX * (-clampedX / deadZone) : 0;\n const lookUp = clampedY > deadZone ? clampedY\n : clampedY > 0 ? clampedY * (clampedY / deadZone) : 0;\n const lookDown = clampedY < -deadZone ? -clampedY\n : clampedY < 0 ? -clampedY * (-clampedY / deadZone) : 0;\n\n // Conjugate pairing: both eyes move together naturally\n blendshapes['eyeLookInLeft'] = lookRight;\n blendshapes['eyeLookOutLeft'] = lookLeft;\n blendshapes['eyeLookInRight'] = lookLeft;\n blendshapes['eyeLookOutRight'] = lookRight;\n blendshapes['eyeLookUpLeft'] = lookUp;\n blendshapes['eyeLookUpRight'] = lookUp;\n blendshapes['eyeLookDownLeft'] = lookDown;\n blendshapes['eyeLookDownRight'] = lookDown;\n\n // =====================================================================\n // BROW NOISE (Simplex-driven organic drift)\n // =====================================================================\n this.updateBrowNoise(delta, audioEnergy, isSpeaking, blendshapes);\n\n // =====================================================================\n // BREATHING + POSTURAL SWAY\n // =====================================================================\n this.microMotionTime += delta;\n this.breathingPhase += delta * this.breathingRate * Math.PI * 2;\n\n const breathingY = Math.sin(this.breathingPhase) * 0.003;\n const swayAmp = this.posturalSwayAmplitude;\n const swayX = Math.sin(this.microMotionTime * 0.7) * swayAmp +\n Math.sin(this.microMotionTime * 1.3) * swayAmp * 0.5;\n const swayY = Math.sin(this.microMotionTime * 0.5) * swayAmp * 0.75 +\n Math.sin(this.microMotionTime * 0.9) * swayAmp * 0.5;\n\n return {\n blendshapes,\n headDelta: {\n yaw: swayX,\n pitch: breathingY + swayY,\n },\n };\n }\n\n /**\n * Reset all internal state to initial values.\n */\n reset(): void {\n // Blink\n this.blinkTimer = 0;\n this.blinkInterval = randomRange(...this.blinkIntervalRange);\n this.blinkPhase = PHASE_OPEN;\n this.blinkProgress = 0;\n this.asymmetryRight = 0.97;\n this.smoothedBlinkLeft = 0;\n this.smoothedBlinkRight = 0;\n\n // Eye contact\n this.smoothedEyeX = 0;\n this.smoothedEyeY = 0;\n\n // Eye micro-motion\n this.eyeNoiseTime = 0;\n\n // Gaze break\n this.gazeBreakTimer = 0;\n this.gazeBreakInterval = randomRange(...this.gazeBreakIntervalRange);\n this.gazeBreakPhase = PHASE_OPEN;\n this.gazeBreakProgress = 0;\n this.gazeBreakTargetX = 0;\n this.gazeBreakTargetY = 0;\n this.gazeBreakCurrentX = 0;\n this.gazeBreakCurrentY = 0;\n\n // Breathing / sway\n this.microMotionTime = 0;\n this.breathingPhase = 0;\n\n // Brow noise\n this.noiseTime = 0;\n this.previousEnergy = 0;\n this.emphasisLevel = 0;\n }\n\n // =====================================================================\n // PRIVATE: Blink system\n // =====================================================================\n\n private updateBlinks(delta: number): void {\n this.blinkTimer += delta;\n\n if (this.blinkTimer >= this.blinkInterval && this.blinkPhase === PHASE_OPEN) {\n this.blinkPhase = PHASE_CLOSING;\n this.blinkProgress = 0;\n this.blinkTimer = 0;\n this.blinkInterval = randomRange(...this.blinkIntervalRange);\n this.asymmetryRight = 0.95 + Math.random() * 0.08;\n }\n\n if (this.blinkPhase > PHASE_OPEN) {\n this.blinkProgress += delta;\n\n if (this.blinkPhase === PHASE_CLOSING) {\n if (this.blinkProgress >= BLINK_CLOSE_DURATION) {\n this.blinkPhase = PHASE_CLOSED;\n this.blinkProgress = 0;\n }\n } else if (this.blinkPhase === PHASE_CLOSED) {\n if (this.blinkProgress >= BLINK_HOLD_DURATION) {\n this.blinkPhase = PHASE_OPENING;\n this.blinkProgress = 0;\n }\n } else if (this.blinkPhase === PHASE_OPENING) {\n if (this.blinkProgress >= BLINK_OPEN_DURATION) {\n this.blinkPhase = PHASE_OPEN;\n this.blinkProgress = 0;\n }\n }\n }\n }\n\n private getBlinkValues(): { left: number; right: number } {\n if (this.blinkPhase === PHASE_OPEN) {\n return { left: 0, right: 0 };\n }\n\n if (this.blinkPhase === PHASE_CLOSING) {\n const t = Math.min(1, this.blinkProgress / BLINK_CLOSE_DURATION);\n const eased = t * t * t; // Cubic ease-in for snappy close\n const tRight = Math.max(0, Math.min(1, (this.blinkProgress - BLINK_ASYMMETRY_DELAY) / BLINK_CLOSE_DURATION));\n return {\n left: eased,\n right: tRight * tRight * tRight * this.asymmetryRight,\n };\n }\n\n if (this.blinkPhase === PHASE_CLOSED) {\n return { left: 1, right: this.asymmetryRight };\n }\n\n // PHASE_OPENING\n const t = Math.min(1, this.blinkProgress / BLINK_OPEN_DURATION);\n const eased = smoothStep(t);\n return {\n left: 1 - eased,\n right: (1 - eased) * this.asymmetryRight,\n };\n }\n\n // =====================================================================\n // PRIVATE: Eye micro-motion (continuous simplex noise)\n // =====================================================================\n\n private getEyeMicroMotion(): { x: number; y: number } {\n const amp = this.eyeNoiseAmplitude;\n const x = simplex2d(this.eyeNoiseTime * EYE_NOISE_X_FREQ, EYE_NOISE_X_PHASE) * amp;\n const y = simplex2d(this.eyeNoiseTime * EYE_NOISE_Y_FREQ, EYE_NOISE_Y_PHASE) * amp * 0.7;\n return { x, y };\n }\n\n // =====================================================================\n // PRIVATE: Gaze breaks\n // =====================================================================\n\n private updateGazeBreaks(delta: number): void {\n this.gazeBreakTimer += delta;\n\n if (this.gazeBreakTimer >= this.gazeBreakInterval && this.gazeBreakPhase === PHASE_OPEN) {\n this.gazeBreakPhase = PHASE_CLOSING; // Reusing: 1 = breaking away\n this.gazeBreakProgress = 0;\n this.gazeBreakTimer = 0;\n\n const amp = randomRange(...this.gazeBreakAmplitudeRange);\n this.gazeBreakTargetX = (Math.random() - 0.5) * 2 * amp;\n this.gazeBreakTargetY = (Math.random() - 0.5) * amp * 0.4;\n this.gazeBreakInterval = randomRange(...this.gazeBreakIntervalRange);\n }\n\n if (this.gazeBreakPhase > PHASE_OPEN) {\n this.gazeBreakProgress += delta;\n\n if (this.gazeBreakPhase === 1) {\n // Breaking away (eased like the return phase)\n const t = Math.min(1, this.gazeBreakProgress / GAZE_BREAK_DURATION);\n const eased = smoothStep(t);\n this.gazeBreakCurrentX = this.gazeBreakTargetX * eased;\n this.gazeBreakCurrentY = this.gazeBreakTargetY * eased;\n if (this.gazeBreakProgress >= GAZE_BREAK_DURATION) {\n this.gazeBreakPhase = 2;\n this.gazeBreakProgress = 0;\n }\n } else if (this.gazeBreakPhase === 2) {\n // Holding glance\n this.gazeBreakCurrentX = this.gazeBreakTargetX;\n this.gazeBreakCurrentY = this.gazeBreakTargetY;\n if (this.gazeBreakProgress >= GAZE_BREAK_HOLD_DURATION) {\n this.gazeBreakPhase = 3;\n this.gazeBreakProgress = 0;\n }\n } else if (this.gazeBreakPhase === 3) {\n // Returning to focus\n const t = Math.min(1, this.gazeBreakProgress / GAZE_BREAK_RETURN_DURATION);\n const eased = smoothStep(t);\n this.gazeBreakCurrentX = this.gazeBreakTargetX * (1 - eased);\n this.gazeBreakCurrentY = this.gazeBreakTargetY * (1 - eased);\n if (this.gazeBreakProgress >= GAZE_BREAK_RETURN_DURATION) {\n this.gazeBreakPhase = PHASE_OPEN;\n this.gazeBreakProgress = 0;\n this.gazeBreakCurrentX = 0;\n this.gazeBreakCurrentY = 0;\n }\n }\n } else {\n this.gazeBreakCurrentX = 0;\n this.gazeBreakCurrentY = 0;\n }\n }\n\n // =====================================================================\n // PRIVATE: Brow noise (simplex-driven organic drift)\n // =====================================================================\n\n private updateBrowNoise(\n delta: number,\n audioEnergy: number,\n isSpeaking: boolean,\n blendshapes: Record<string, number>,\n ): void {\n this.noiseTime += delta;\n\n // Emphasis detection: spike on energy transients\n const energyDelta = audioEnergy - this.previousEnergy;\n if (energyDelta > EMPHASIS_ENERGY_THRESHOLD) {\n this.emphasisLevel = 1.0;\n }\n this.emphasisLevel = Math.max(0, this.emphasisLevel - delta * EMPHASIS_DECAY_RATE);\n this.previousEnergy = audioEnergy;\n\n // Speech multiplier\n const speechMul = (isSpeaking && audioEnergy > 0) ? this.browNoiseSpeechMultiplier : 1.0;\n const amp = this.browNoiseAmplitude * speechMul;\n\n // browInnerUp: slow drift + emphasis spike\n const innerUpNoise = simplex2d(this.noiseTime * BROW_INNER_UP_FREQ, BROW_INNER_UP_PHASE);\n const innerUpBase = (innerUpNoise * 0.5 + 0.5) * amp * 0.83; // 0.10/0.12\n const innerUpEmphasis = this.emphasisLevel * 0.25;\n blendshapes['browInnerUp'] = clamp(innerUpBase + innerUpEmphasis, 0, 1);\n\n // browOuterUpLeft\n const outerLeftNoise = simplex2d(this.noiseTime * BROW_OUTER_LEFT_FREQ, BROW_OUTER_LEFT_PHASE);\n blendshapes['browOuterUpLeft'] = clamp((outerLeftNoise * 0.5 + 0.5) * amp * 0.5, 0, 1);\n\n // browOuterUpRight\n const outerRightNoise = simplex2d(this.noiseTime * BROW_OUTER_RIGHT_FREQ, BROW_OUTER_RIGHT_PHASE);\n blendshapes['browOuterUpRight'] = clamp((outerRightNoise * 0.5 + 0.5) * amp * 0.5, 0, 1);\n\n // browDownLeft (subtle furrow)\n const downLeftNoise = simplex2d(this.noiseTime * BROW_DOWN_FREQ, BROW_DOWN_LEFT_PHASE);\n blendshapes['browDownLeft'] = clamp((downLeftNoise * 0.5 + 0.5) * amp * 0.33, 0, 1);\n\n // browDownRight (subtle furrow)\n const downRightNoise = simplex2d(this.noiseTime * BROW_DOWN_FREQ, BROW_DOWN_RIGHT_PHASE);\n blendshapes['browDownRight'] = clamp((downRightNoise * 0.5 + 0.5) * amp * 0.33, 0, 1);\n }\n}\n","/**\n * Versioned NDJSON streaming protocol for client-server communication.\n *\n * Each line in the NDJSON stream is a JSON object conforming to ProtocolEvent.\n * The `v` field enables backward-compatible protocol evolution.\n *\n * @category Protocol\n */\n\n/** Current protocol version. Increment on breaking changes. */\nexport const PROTOCOL_VERSION = 1;\n\n/** Base shape for all NDJSON protocol events */\ninterface ProtocolEventBase {\n /** Protocol version */\n v: number;\n /** Event type discriminator */\n type: string;\n /** ISO 8601 timestamp */\n ts: string;\n}\n\n// --- Response events (server -> client) ---\n\nexport interface ResponseStartEvent extends ProtocolEventBase {\n type: 'response_start';\n /** Initial emotion for the response */\n emotion?: string;\n}\n\nexport interface ResponseChunkEvent extends ProtocolEventBase {\n type: 'response_chunk';\n /** Text chunk (streamed token-by-token) */\n text: string;\n}\n\nexport interface AudioChunkEvent extends ProtocolEventBase {\n type: 'audio_chunk';\n /** Base64-encoded PCM16 audio at 16kHz */\n audio: string;\n /** Audio format identifier */\n format: 'pcm_16000';\n /** Whether this is the last audio chunk */\n isLast: boolean;\n}\n\nexport interface ResponseEndEvent extends ProtocolEventBase {\n type: 'response_end';\n /** Complete response text */\n fullText: string;\n /** Response generation time in ms */\n durationMs?: number;\n /** Token count for this response */\n tokenCount?: number;\n}\n\nexport interface ErrorEvent extends ProtocolEventBase {\n type: 'error';\n /** Error message */\n message: string;\n /** Error code for programmatic handling */\n code?: string;\n /** Whether the client should retry */\n recoverable?: boolean;\n}\n\n/**\n * Union of all protocol events.\n * Used for type-safe NDJSON parsing on the client.\n */\nexport type ProtocolEvent =\n | ResponseStartEvent\n | ResponseChunkEvent\n | AudioChunkEvent\n | ResponseEndEvent\n | ErrorEvent;\n\n/**\n * Type guard for protocol events.\n */\nexport function isProtocolEvent(obj: unknown): obj is ProtocolEvent {\n return (\n typeof obj === 'object' &&\n obj !== null &&\n 'v' in obj &&\n 'type' in obj &&\n 'ts' in obj\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACQO,IAAM,eAAN,MAA+D;AAAA,EAA/D;AACL,SAAQ,YAAY,oBAAI,IAAgD;AAAA;AAAA,EAExE,GAA4B,OAAU,UAAiD;AACrF,QAAI,CAAC,KAAK,UAAU,IAAI,KAAK,GAAG;AAC9B,WAAK,UAAU,IAAI,OAAO,oBAAI,IAAI,CAAC;AAAA,IACrC;AACA,SAAK,UAAU,IAAI,KAAK,EAAG,IAAI,QAAkC;AAGjE,WAAO,MAAM,KAAK,IAAI,OAAO,QAAQ;AAAA,EACvC;AAAA,EAEA,IAA6B,OAAU,UAA2C;AAChF,SAAK,UAAU,IAAI,KAAK,GAAG,OAAO,QAAkC;AAAA,EACtE;AAAA,EAEA,KAA8B,OAAU,MAAwB;AAC9D,SAAK,UAAU,IAAI,KAAK,GAAG,QAAQ,CAAC,OAAO,GAAG,IAAI,CAAC;AAAA,EACrD;AAAA,EAEA,KAA8B,OAAU,UAAiD;AACvF,UAAM,UAAqC,CAAC,SAAS;AACnD,WAAK,IAAI,OAAO,OAAO;AACvB,eAAS,IAAI;AAAA,IACf;AACA,WAAO,KAAK,GAAG,OAAO,OAAO;AAAA,EAC/B;AAAA,EAEA,mBAAmB,OAA6B;AAC9C,QAAI,OAAO;AACT,WAAK,UAAU,OAAO,KAAK;AAAA,IAC7B,OAAO;AACL,WAAK,UAAU,MAAM;AAAA,IACvB;AAAA,EACF;AACF;;;AC1BO,IAAM,oBAAN,MAAwB;AAAA,EAW7B,YACU,QACR,SAAkC,CAAC,GACnC;AAFQ;AAVV,SAAQ,SAA6B;AACrC,SAAQ,UAA+B;AACvC,SAAQ,YAAwC;AAChD,SAAQ,SAAuB,IAAI,aAAa,CAAC;AACjD,SAAQ,eAAe;AACvB,SAAQ,oBAAoB;AAE5B;AAAA,SAAQ,oBAAoB;AAM1B,SAAK,SAAS;AAAA,MACZ,YAAY,OAAO,cAAc;AAAA,MACjC,WAAW,OAAO,aAAa;AAAA,IACjC;AAAA,EACF;AAAA,EAEA,IAAI,cAAuB;AACzB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,cAAuB;AACzB,WAAO,OAAO,cAAc,eAAe,CAAC,CAAC,UAAU,cAAc;AAAA,EACvE;AAAA,EAEA,MAAM,QAAuB;AAC3B,QAAI,CAAC,KAAK,aAAa;AACrB,WAAK,OAAO,KAAK,SAAS;AAAA,QACxB,MAAM;AAAA,QACN,SAAS;AAAA,MACX,CAAC;AACD;AAAA,IACF;AAEA,QAAI,KAAK,aAAc;AAEvB,QAAI;AACF,WAAK,SAAS,MAAM,UAAU,aAAa,aAAa;AAAA,QACtD,OAAO;AAAA,UACL,YAAY,EAAE,OAAO,KAAK,OAAO,WAAW;AAAA,UAC5C,cAAc;AAAA,UACd,kBAAkB;AAAA,UAClB,kBAAkB;AAAA,UAClB,iBAAiB;AAAA,QACnB;AAAA,MACF,CAAC;AAMD,WAAK,UAAU,IAAI,aAAa,EAAE,YAAY,KAAK,OAAO,WAAW,CAAC;AAGtE,UAAI,KAAK,QAAQ,UAAU,aAAa;AACtC,cAAM,KAAK,QAAQ,OAAO;AAAA,MAC5B;AAEA,UAAI;AACJ,UAAI;AACF,iBAAS,KAAK,QAAQ,wBAAwB,KAAK,MAAM;AACzD,aAAK,oBAAoB,KAAK,QAAQ;AAAA,MACxC,SAAS,WAAW;AAGlB,gBAAQ;AAAA,UACN;AAAA,UACA,KAAK,OAAO,aAAa;AAAA,UACxB,UAAoB;AAAA,QACvB;AACA,cAAM,KAAK,QAAQ,MAAM;AACzB,aAAK,UAAU,IAAI,aAAa;AAChC,YAAI,KAAK,QAAQ,UAAU,aAAa;AACtC,gBAAM,KAAK,QAAQ,OAAO;AAAA,QAC5B;AACA,iBAAS,KAAK,QAAQ,wBAAwB,KAAK,MAAM;AACzD,aAAK,oBAAoB,KAAK,QAAQ;AACtC,gBAAQ,IAAI,0CAA0C,KAAK,mBAAmB,2BAAsB,KAAK,OAAO,YAAY,IAAI;AAAA,MAClI;AAGA,WAAK,YAAY,KAAK,QAAQ,sBAAsB,MAAM,GAAG,CAAC;AAE9D,WAAK,UAAU,iBAAiB,CAAC,MAAM;AACrC,cAAM,MAAM,EAAE,YAAY,eAAe,CAAC;AAG1C,cAAM,QAAQ,KAAK,sBAAsB,KAAK,OAAO,aACjD,KAAK,SAAS,KAAK,KAAK,mBAAmB,KAAK,OAAO,UAAU,IACjE;AAGJ,YAAI,MAAM;AACV,YAAI,OAAO;AACX,iBAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,gBAAM,MAAM,KAAK,IAAI,MAAM,CAAC,CAAC;AAC7B,iBAAO,MAAM,CAAC,IAAI,MAAM,CAAC;AACzB,cAAI,MAAM,KAAM,QAAO;AAAA,QACzB;AACA,cAAM,KAAK,KAAK,MAAM,MAAM,MAAM;AAElC,aAAK,OAAO,KAAK,eAAe,EAAE,KAAK,KAAK,CAAC;AAG7C,cAAM,YAAY,IAAI,aAAa,KAAK,OAAO,SAAS,MAAM,MAAM;AACpE,kBAAU,IAAI,KAAK,MAAM;AACzB,kBAAU,IAAI,OAAO,KAAK,OAAO,MAAM;AACvC,aAAK,SAAS;AAGd,YAAI,aAAa;AACjB,eAAO,KAAK,OAAO,UAAU,KAAK,OAAO,WAAW;AAClD,gBAAM,QAAQ,KAAK,OAAO,MAAM,GAAG,KAAK,OAAO,SAAS;AACxD,eAAK,SAAS,KAAK,OAAO,MAAM,KAAK,OAAO,SAAS;AAErD,gBAAM,MAAM,KAAK,aAAa,KAAK;AACnC,eAAK,OAAO,KAAK,eAAe;AAAA,YAC9B;AAAA,YACA,WAAW,YAAY,IAAI;AAAA,UAC7B,CAAC;AACD;AAAA,QACF;AAEA,YAAI,aAAa,KAAK,CAAC,KAAK,mBAAmB;AAC7C,kBAAQ,IAAI,8CAA8C,UAAU;AACpE,eAAK,oBAAoB;AAAA,QAC3B;AAAA,MACF;AAEA,aAAO,QAAQ,KAAK,SAAS;AAC7B,WAAK,UAAU,QAAQ,KAAK,QAAQ,WAAW;AAE/C,WAAK,eAAe;AACpB,cAAQ,IAAI,yDAAyD,KAAK,QAAQ,KAAK;AAAA,IACzF,SAAS,KAAK;AACZ,WAAK,OAAO,KAAK,SAAS;AAAA,QACxB,MAAM;AAAA,QACN,SAAU,IAAc;AAAA,QACxB,SAAS;AAAA,MACX,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EAEA,OAAa;AACX,QAAI,KAAK,WAAW;AAClB,WAAK,UAAU,WAAW;AAC1B,WAAK,YAAY;AAAA,IACnB;AAEA,QAAI,KAAK,SAAS;AAChB,WAAK,QAAQ,MAAM;AACnB,WAAK,UAAU;AAAA,IACjB;AAEA,QAAI,KAAK,QAAQ;AACf,WAAK,OAAO,UAAU,EAAE,QAAQ,CAAC,MAAM,EAAE,KAAK,CAAC;AAC/C,WAAK,SAAS;AAAA,IAChB;AAEA,SAAK,SAAS,IAAI,aAAa,CAAC;AAChC,SAAK,eAAe;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,SAAS,OAAqB,UAAkB,QAA8B;AACpF,QAAI,aAAa,OAAQ,QAAO;AAChC,UAAM,QAAQ,WAAW;AACzB,UAAM,eAAe,KAAK,MAAM,MAAM,SAAS,KAAK;AACpD,UAAM,SAAS,IAAI,aAAa,YAAY;AAC5C,aAAS,IAAI,GAAG,IAAI,cAAc,KAAK;AACrC,YAAM,SAAS,IAAI;AACnB,YAAM,KAAK,KAAK,MAAM,MAAM;AAC5B,YAAM,KAAK,KAAK,IAAI,KAAK,GAAG,MAAM,SAAS,CAAC;AAC5C,YAAM,OAAO,SAAS;AACtB,aAAO,CAAC,IAAI,MAAM,EAAE,KAAK,IAAI,QAAQ,MAAM,EAAE,IAAI;AAAA,IACnD;AACA,WAAO;AAAA,EACT;AAAA,EAEQ,aAAa,SAAmC;AACtD,UAAM,MAAM,IAAI,WAAW,QAAQ,MAAM;AACzC,aAAS,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;AACvC,YAAM,IAAI,KAAK,IAAI,IAAI,KAAK,IAAI,GAAG,QAAQ,CAAC,CAAC,CAAC;AAC9C,UAAI,CAAC,IAAI,IAAI,IAAI,IAAI,QAAS,IAAI;AAAA,IACpC;AACA,WAAO;AAAA,EACT;AACF;;;AC5MO,IAAM,aAAN,MAAiB;AAAA,EAKtB,YAA6B,MAAc;AAAd;AAH7B,SAAQ,aAAa;AACrB,SAAQ,SAAS;AAGf,SAAK,SAAS,IAAI,aAAa,IAAI;AAAA,EACrC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,KAAuB;AAC3B,aAAS,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK;AACnC,WAAK,OAAO,KAAK,UAAU,IAAI,IAAI,CAAC,IAAI;AACxC,WAAK,cAAc,KAAK,aAAa,KAAK,KAAK;AAE/C,UAAI,KAAK,eAAe,GAAG;AACzB,aAAK,SAAS;AAAA,MAChB;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,WAAW,SAA6B;AACtC,aAAS,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;AACvC,WAAK,OAAO,KAAK,UAAU,IAAI,QAAQ,CAAC;AACxC,WAAK,cAAc,KAAK,aAAa,KAAK,KAAK;AAE/C,UAAI,KAAK,eAAe,GAAG;AACzB,aAAK,SAAS;AAAA,MAChB;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,OAA4B;AAC1B,QAAI,CAAC,KAAK,OAAQ,QAAO;AAEzB,UAAM,SAAS,IAAI,aAAa,KAAK,IAAI;AAGzC,UAAM,YAAY,KAAK,OAAO,SAAS,KAAK,UAAU;AACtD,WAAO,IAAI,WAAW,CAAC;AAGvB,UAAM,aAAa,KAAK,OAAO,SAAS,GAAG,KAAK,UAAU;AAC1D,WAAO,IAAI,YAAY,UAAU,MAAM;AAEvC,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,UAAmB;AACrB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,YAAoB;AACtB,QAAI,KAAK,OAAQ,QAAO;AACxB,WAAO,KAAK,aAAa,KAAK;AAAA,EAChC;AAAA;AAAA;AAAA;AAAA,EAKA,QAAc;AACZ,SAAK,OAAO,KAAK,CAAC;AAClB,SAAK,aAAa;AAClB,SAAK,SAAS;AAAA,EAChB;AACF;;;AC7DO,IAAM,iBAAN,MAAqB;AAAA,EAM1B,YAA6B,UAAiC,CAAC,GAAG;AAArC;AAL7B,SAAQ,UAA+B;AACvC,SAAQ,eAAe;AACvB,SAAQ,mBAAiF,CAAC;AAC1F,SAAQ,YAAY;AAAA,EAE+C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQnE,MAAM,aAA4B;AAEhC,YAAQ,IAAI,gDAAgD;AAAA,EAC9D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,MAAM,SAAwB;AAC5B,UAAM,KAAK,cAAc;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAc,gBAAuC;AACnD,QAAI,KAAK,WAAW,KAAK,QAAQ,UAAU,UAAU;AACnD,aAAO,KAAK;AAAA,IACd;AAEA,UAAM,aAAa,KAAK,QAAQ,cAAc;AAC9C,SAAK,UAAU,IAAI,aAAa,EAAE,WAAW,CAAC;AAG9C,QAAI,KAAK,QAAQ,UAAU,aAAa;AACtC,YAAM,KAAK,QAAQ,OAAO;AAAA,IAC5B;AAEA,YAAQ,IAAI,gDAAgD,UAAU,IAAI;AAC1E,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,MAAM,SAAS,WAA0C;AAEvD,UAAM,MAAM,MAAM,KAAK,cAAc;AACrC,UAAM,WAAW,KAAK,QAAQ,YAAY;AAK1C,QAAI,CAAC,KAAK,WAAW;AACnB,YAAM,YAAY,KAAK,QAAQ,uBAAuB;AACtD,WAAK,eAAe,IAAI,cAAc;AACtC,WAAK,YAAY;AAAA,IACnB;AAGA,UAAM,cAAc,IAAI,aAAa,UAAU,UAAU,QAAQ,IAAI,UAAU;AAC/E,gBAAY,eAAe,CAAC,EAAE,IAAI,SAAS;AAG3C,UAAM,WAAW,IAAI,WAAW;AAChC,aAAS,KAAK,QAAQ;AACtB,aAAS,QAAQ,IAAI,WAAW;AAGhC,UAAM,SAAS,IAAI,mBAAmB;AACtC,WAAO,SAAS;AAChB,WAAO,QAAQ,QAAQ;AAGvB,UAAM,eAAe,KAAK;AAC1B,WAAO,MAAM,YAAY;AAGzB,SAAK,iBAAiB,KAAK,EAAE,QAAQ,SAAS,CAAC;AAG/C,UAAM,WAAW,UAAU,SAAS,IAAI;AACxC,SAAK,eAAe,eAAe;AAEnC,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,iBAAyB;AACvB,QAAI,CAAC,KAAK,QAAS,QAAO;AAC1B,WAAO,KAAK,QAAQ;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA,EAKA,qBAA6B;AAC3B,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,aAAsB;AACpB,QAAI,CAAC,KAAK,WAAW,CAAC,KAAK,UAAW,QAAO;AAC7C,WAAO,KAAK,QAAQ,eAAe,KAAK;AAAA,EAC1C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,MAAM,UAAU,YAAoB,IAAmB;AACrD,QAAI,CAAC,KAAK,WAAW,KAAK,iBAAiB,WAAW,GAAG;AACvD;AAAA,IACF;AAEA,UAAM,MAAM,KAAK;AACjB,UAAM,cAAc,IAAI;AACxB,UAAM,aAAa,YAAY;AAG/B,eAAW,EAAE,QAAQ,SAAS,KAAK,KAAK,kBAAkB;AACxD,UAAI;AAEF,iBAAS,KAAK,eAAe,SAAS,KAAK,OAAO,WAAW;AAC7D,iBAAS,KAAK,wBAAwB,GAAK,cAAc,UAAU;AAGnE,eAAO,KAAK,cAAc,UAAU;AAAA,MACtC,SAAS,KAAK;AAAA,MAEd;AAAA,IACF;AAGA,SAAK,mBAAmB,CAAC;AACzB,SAAK,YAAY;AACjB,SAAK,eAAe;AAGpB,UAAM,IAAI,QAAQ,aAAW,WAAW,SAAS,SAAS,CAAC;AAAA,EAC7D;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,QAAc;AAEZ,QAAI,KAAK,SAAS;AAChB,YAAM,MAAM,KAAK,QAAQ;AACzB,iBAAW,EAAE,QAAQ,SAAS,KAAK,KAAK,kBAAkB;AACxD,YAAI;AACF,mBAAS,KAAK,eAAe,GAAG,GAAG;AACnC,iBAAO,KAAK,GAAG;AAAA,QACjB,QAAQ;AAAA,QAER;AAAA,MACF;AAAA,IACF;AACA,SAAK,eAAe;AACpB,SAAK,YAAY;AACjB,SAAK,mBAAmB,CAAC;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA,EAKA,UAAgB;AACd,QAAI,KAAK,SAAS;AAChB,WAAK,QAAQ,MAAM;AACnB,WAAK,UAAU;AAAA,IACjB;AACA,SAAK,mBAAmB,CAAC;AACzB,SAAK,YAAY;AAAA,EACnB;AACF;;;ACxMO,IAAM,sBAAN,MAA0B;AAAA,EAI/B,YAA6B,UAAsC,CAAC,GAAG;AAA1C;AAH7B,SAAQ,aAA2B,CAAC;AAIlC,UAAM,WAAW,QAAQ,oBAAoB;AAC7C,UAAM,aAAa,QAAQ,cAAc;AAGzC,SAAK,cAAe,WAAW,MAAQ,aAAa;AAAA,EACtD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,IAAI,OAAuC;AAEzC,SAAK,WAAW,KAAK,KAAK;AAG1B,UAAM,aAAa,KAAK,WAAW,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,QAAQ,CAAC;AAGvE,QAAI,cAAc,KAAK,aAAa;AAClC,aAAO,KAAK,MAAM;AAAA,IACpB;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,QAA4B;AAC1B,QAAI,KAAK,WAAW,WAAW,GAAG;AAChC,aAAO;AAAA,IACT;AAGA,UAAM,aAAa,KAAK,WAAW,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,QAAQ,CAAC;AAGvE,UAAM,WAAW,IAAI,WAAW,UAAU;AAC1C,QAAI,SAAS;AACb,eAAW,SAAS,KAAK,YAAY;AACnC,eAAS,IAAI,OAAO,MAAM;AAC1B,gBAAU,MAAM;AAAA,IAClB;AAGA,SAAK,aAAa,CAAC;AAEnB,WAAO,SAAS;AAAA,EAClB;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,YAAoB;AACtB,UAAM,aAAa,KAAK,WAAW,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,QAAQ,CAAC;AACvE,WAAO,KAAK,IAAI,GAAG,aAAa,KAAK,WAAW;AAAA,EAClD;AAAA;AAAA;AAAA;AAAA,EAKA,wBAAgC;AAC9B,UAAM,aAAa,KAAK,QAAQ,cAAc;AAC9C,UAAM,aAAa,KAAK,WAAW,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,QAAQ,CAAC;AACvE,UAAM,UAAU,aAAa;AAC7B,WAAQ,UAAU,aAAc;AAAA,EAClC;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,aAAqB;AACvB,WAAO,KAAK,WAAW;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA,EAKA,QAAc;AACZ,SAAK,aAAa,CAAC;AAAA,EACrB;AACF;;;AClFO,IAAM,cAAN,MAAkB;AAAA,EAcvB,YAA6B,UAA8B,CAAC,GAAG;AAAlC;AAb7B,SAAiB,mBAAmB;AACpC;AAAA,SAAiB,aAAa;AAE9B;AAAA,SAAQ,SAAuB,IAAI,aAAa,CAAC;AACjD,SAAQ,kBAAkB;AAC1B,SAAQ,aAAyB,CAAC;AAMlC;AAAA;AAAA;AAAA;AAAA,SAAQ,YAAiC;AAAA,EAEuB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYhE,MAAM,KAAK,SAAuB,WAAmB,KAAoC;AAEvF,QAAI,KAAK,OAAO,WAAW,GAAG;AAC5B,WAAK,kBAAkB;AAAA,IACzB;AAGA,UAAM,YAAY,IAAI,aAAa,KAAK,OAAO,SAAS,QAAQ,MAAM;AACtE,cAAU,IAAI,KAAK,QAAQ,CAAC;AAC5B,cAAU,IAAI,SAAS,KAAK,OAAO,MAAM;AACzC,SAAK,SAAS;AAKd,WAAO,KAAK,OAAO,UAAU,KAAK,kBAAkB;AAClD,YAAM,KAAK,cAAc,GAAG;AAK5B,UAAI,KAAK,OAAO,UAAU,KAAK,kBAAkB;AAC/C,cAAM,IAAI,QAAc,OAAK,WAAW,GAAG,CAAC,CAAC;AAAA,MAC/C;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,cAAc,KAAoC;AAC9D,QAAI;AAEF,YAAM,YAAY,KAAK,OAAO,MAAM,GAAG,KAAK,gBAAgB;AAC5D,YAAM,qBAAqB,KAAK;AAGhC,WAAK,SAAS,KAAK,OAAO,MAAM,KAAK,gBAAgB;AAGrD,YAAM,oBAAoB,KAAK,oBAAoB,KAAK,QAAQ,cAAc;AAC9E,WAAK,kBAAkB,qBAAqB;AAG5C,YAAM,SAAS,MAAM,IAAI,MAAM,SAAS;AAGxC,YAAM,gBAAgB,IAAI,KAAK;AAC/B,eAAS,IAAI,GAAG,IAAI,OAAO,YAAY,QAAQ,KAAK;AAClD,cAAM,QAAQ,OAAO,YAAY,CAAC;AAClC,cAAM,YAAY,qBAAsB,IAAI;AAC5C,aAAK,WAAW,KAAK,EAAE,OAAO,UAAU,CAAC;AAAA,MAC3C;AAGA,WAAK,QAAQ,cAAc,OAAO,YAAY,MAAM;AAAA,IACtD,SAAS,OAAO;AACd,WAAK,QAAQ,UAAU,KAAc;AAGrC,WAAK,SAAS,IAAI,aAAa,CAAC;AAChC,WAAK,kBAAkB;AAAA,IACzB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAmBA,gBAAgB,aAAqB,KAAkE;AAErG,UAAM,gBAAgB,KAAK,YAAY,SAAS,IAAM;AAGtD,QAAI,iBAAiB;AACrB,WAAO,KAAK,WAAW,SAAS,KAAK,KAAK,WAAW,CAAC,EAAE,YAAY,cAAc,eAAe;AAC/F,YAAM,YAAY,KAAK,WAAW,MAAM;AACxC;AAGA,UAAI,mBAAmB,GAAG;AACxB,cAAM,UAAU,cAAc,UAAU,aAAa,KAAM,QAAQ,CAAC;AACpE,gBAAQ,KAAK,uCAAuC;AAAA,UAClD;AAAA,UACA,iBAAiB,gBAAgB;AAAA,UACjC,aAAa,KAAK,WAAW;AAAA,UAC7B,SAAS,KAAK,WAAW;AAAA,QAC3B,CAAC;AAAA,MACH;AAAA,IACF;AAGA,QAAI,KAAK,WAAW,SAAS,KAAK,KAAK,WAAW,CAAC,EAAE,aAAa,aAAa;AAC7E,YAAM,EAAE,MAAM,IAAI,KAAK,WAAW,MAAM;AACxC,WAAK,YAAY;AACjB,aAAO;AAAA,IACT;AAIA,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,kBAA8B;AAC5B,WAAO,CAAC,GAAG,KAAK,UAAU;AAAA,EAC5B;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,YAAoB;AACtB,WAAO,KAAK,IAAI,GAAG,KAAK,OAAO,SAAS,KAAK,gBAAgB;AAAA,EAC/D;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,mBAA2B;AAC7B,WAAO,KAAK,WAAW;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,mBAA2B;AAC7B,WAAO,KAAK,OAAO,UAAU,KAAK,QAAQ,cAAc;AAAA,EAC1D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,MAAM,MAAM,KAAoC;AAC9C,QAAI,KAAK,OAAO,WAAW,GAAG;AAC5B;AAAA,IACF;AAGA,UAAM,SAAS,IAAI,aAAa,KAAK,gBAAgB;AACrD,WAAO,IAAI,KAAK,QAAQ,CAAC;AAIzB,UAAM,qBAAqB,KAAK;AAEhC,QAAI;AAEF,YAAM,SAAS,MAAM,IAAI,MAAM,MAAM;AAIrC,YAAM,iBAAiB,KAAK,OAAO,UAAU,KAAK,QAAQ,cAAc;AACxE,YAAM,gBAAgB,IAAI,KAAK;AAC/B,YAAM,mBAAmB,KAAK,KAAK,iBAAiB,KAAK,UAAU;AAEnE,eAAS,IAAI,GAAG,IAAI,KAAK,IAAI,kBAAkB,OAAO,YAAY,MAAM,GAAG,KAAK;AAC9E,cAAM,QAAQ,OAAO,YAAY,CAAC;AAClC,cAAM,YAAY,qBAAsB,IAAI;AAC5C,aAAK,WAAW,KAAK,EAAE,OAAO,UAAU,CAAC;AAAA,MAC3C;AAGA,WAAK,SAAS,IAAI,aAAa,CAAC;AAChC,WAAK,kBAAkB;AAGvB,WAAK,QAAQ,cAAc,KAAK,IAAI,kBAAkB,OAAO,YAAY,MAAM,CAAC;AAAA,IAClF,SAAS,OAAO;AACd,WAAK,QAAQ,UAAU,KAAc;AAGrC,WAAK,SAAS,IAAI,aAAa,CAAC;AAChC,WAAK,kBAAkB;AAAA,IACzB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,iBAAiB,QAAsB;AACrC,eAAW,SAAS,KAAK,YAAY;AACnC,YAAM,aAAa;AAAA,IACrB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,QAAc;AACZ,SAAK,SAAS,IAAI,aAAa,CAAC;AAChC,SAAK,kBAAkB;AACvB,SAAK,aAAa,CAAC;AACnB,SAAK,YAAY;AAAA,EACnB;AACF;;;ACvQA,SAAS,eAAe,QAAmC;AAEzD,QAAM,UAAU,OAAO,aAAa,CAAC;AACrC,QAAM,QAAQ,YAAY,OAAO,aAC7B,IAAI,WAAW,MAAM,IACrB,IAAI,WAAW,QAAQ,GAAG,UAAU,CAAC;AACzC,QAAM,UAAU,IAAI,aAAa,MAAM,MAAM;AAC7C,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,YAAQ,CAAC,IAAI,MAAM,CAAC,IAAI;AAAA,EAC1B;AACA,SAAO;AACT;AA8BO,IAAM,sBAAN,cAAkC,aAAwC;AAAA,EAS/E,YAA6B,SAAqC;AAChE,UAAM;AADqB;AAJ7B,SAAQ,kBAAkB;AAC1B,SAAQ,kBAAiC;AACzC,SAAQ,mBAAkC;AAKxC,UAAM,aAAa,QAAQ,cAAc;AAMzC,UAAM,YAAY,QAAQ,IAAI,YAAY,kBAAkB,MACxD,QAAQ,IAAI,YAAY,SAAS,MACjC;AACJ,UAAM,eAAe,QAAQ,gBAAgB;AAE7C,SAAK,YAAY,IAAI,eAAe;AAAA,MAClC;AAAA,MACA,qBAAqB,eAAe;AAAA,IACtC,CAAC;AACD,SAAK,YAAY,IAAI,oBAAoB;AAAA,MACvC;AAAA,MACA,kBAAkB,QAAQ,iBAAiB;AAAA,IAC7C,CAAC;AACD,SAAK,cAAc,IAAI,YAAY;AAAA,MACjC;AAAA,MACA,SAAS,CAAC,UAAU;AAClB,aAAK,KAAK,SAAS,KAAK;AAAA,MAC1B;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,aAA4B;AAChC,UAAM,KAAK,UAAU,WAAW;AAAA,EAClC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,QAAc;AAEZ,SAAK,eAAe;AAEpB,SAAK,UAAU,MAAM;AACrB,SAAK,UAAU,MAAM;AACrB,SAAK,YAAY,MAAM;AACvB,SAAK,kBAAkB;AAMvB,SAAK,UAAU,OAAO;AAGtB,SAAK,eAAe;AAGpB,SAAK,gBAAgB;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,MAAM,aAAa,OAAkC;AAEnD,UAAM,WAAW,KAAK,UAAU,IAAI,KAAK;AACzC,QAAI,CAAC,UAAU;AACb;AAAA,IACF;AAGA,UAAM,UAAU,eAAe,QAAQ;AAGvC,UAAM,eAAe,MAAM,KAAK,UAAU,SAAS,OAAO;AAG1D,QAAI,CAAC,KAAK,iBAAiB;AACzB,WAAK,kBAAkB;AACvB,WAAK,KAAK,kBAAkB,YAAY;AAAA,IAC1C;AAOA,SAAK,YAAY,KAAK,SAAS,cAAc,KAAK,QAAQ,GAAG,EAAE,MAAM,SAAO;AAC1E,WAAK,KAAK,SAAS,GAAG;AAAA,IACxB,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,MAAqB;AAEzB,UAAM,YAAY,KAAK,UAAU,MAAM;AACvC,QAAI,WAAW;AACb,YAAM,QAAQ,IAAI,WAAW,SAAS;AACtC,YAAM,KAAK,aAAa,KAAK;AAAA,IAC/B;AAIA,UAAM,KAAK,YAAY,MAAM,KAAK,QAAQ,GAAG;AAAA,EAC/C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgBA,MAAM,KAAK,YAAoB,IAAmB;AAEhD,SAAK,eAAe;AAGpB,UAAM,KAAK,UAAU,UAAU,SAAS;AAGxC,SAAK,UAAU,MAAM;AACrB,SAAK,YAAY,MAAM;AACvB,SAAK,kBAAkB;AAGvB,SAAK,KAAK,qBAAqB,MAAgB;AAAA,EACjD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaQ,iBAAuB;AAC7B,UAAM,cAAc,MAAM;AACxB,YAAM,cAAc,KAAK,UAAU,eAAe;AAClD,YAAM,QAAQ,KAAK,YAAY,gBAAgB,aAAa,KAAK,QAAQ,GAAG;AAE5E,UAAI,OAAO;AACT,aAAK,KAAK,eAAe,KAAK;AAAA,MAChC;AAEA,WAAK,mBAAmB,sBAAsB,WAAW;AAAA,IAC3D;AAEA,SAAK,mBAAmB,sBAAsB,WAAW;AAAA,EAC3D;AAAA;AAAA;AAAA;AAAA,EAKQ,kBAAwB;AAC9B,QAAI,KAAK,iBAAiB;AACxB,oBAAc,KAAK,eAAe;AAAA,IACpC;AAEA,SAAK,kBAAkB,OAAO,YAAY,MAAM;AAC9C,UAAI,KAAK,UAAU,WAAW,KAAK,KAAK,YAAY,qBAAqB,GAAG;AAC1E,aAAK,KAAK,qBAAqB,MAAgB;AAC/C,aAAK,eAAe;AAAA,MACtB;AAAA,IACF,GAAG,GAAG;AAAA,EACR;AAAA;AAAA;AAAA;AAAA,EAKQ,iBAAuB;AAC7B,QAAI,KAAK,iBAAiB;AACxB,oBAAc,KAAK,eAAe;AAClC,WAAK,kBAAkB;AAAA,IACzB;AAEA,QAAI,KAAK,kBAAkB;AACzB,2BAAqB,KAAK,gBAAgB;AAC1C,WAAK,mBAAmB;AAAA,IAC1B;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,WAAW;AACT,WAAO;AAAA,MACL,iBAAiB,KAAK;AAAA,MACtB,eAAe,KAAK,UAAU;AAAA,MAC9B,SAAS,KAAK,YAAY;AAAA,MAC1B,cAAc,KAAK,YAAY;AAAA,MAC/B,aAAa,KAAK,UAAU,eAAe;AAAA,MAC3C,iBAAiB,KAAK,UAAU,mBAAmB;AAAA,IACrD;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,UAAgB;AACd,SAAK,eAAe;AACpB,SAAK,UAAU,QAAQ;AACvB,SAAK,UAAU,MAAM;AACrB,SAAK,YAAY,MAAM;AAAA,EACzB;AACF;;;ACtOO,IAAM,yBAAyB;AAAA;AAAA,EAEpC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AACF;AAmCO,IAAM,oBAA6E;AAAA,EACxF,OAAO;AAAA;AAAA,IAEL,iBAAiB;AAAA,IACjB,kBAAkB;AAAA;AAAA,IAElB,eAAe;AAAA,IACf,gBAAgB;AAAA,EAClB;AAAA,EACA,OAAO;AAAA;AAAA,IAEL,cAAc;AAAA,IACd,eAAe;AAAA;AAAA,IAEf,aAAa;AAAA,IACb,cAAc;AAAA;AAAA,IAEd,eAAe;AAAA,IACf,gBAAgB;AAAA,EAClB;AAAA,EACA,KAAK;AAAA;AAAA,IAEH,aAAa;AAAA;AAAA,IAEb,cAAc;AAAA,IACd,eAAe;AAAA,EACjB;AAAA,EACA,SAAS,CAAC;AAAA;AACZ;AAgEA,IAAM,iBAAoD;AAAA,EACxD,iBAAiB;AAAA,EACjB,qBAAqB;AAAA,EACrB,WAAW;AAAA,EACX,WAAW;AAAA,EACX,qBAAqB;AAAA,EACrB,kBAAkB;AAAA,EAClB,gBAAgB;AAAA,EAChB,gBAAgB;AAClB;AAKA,SAAS,wBAA8C;AACrD,QAAM,SAAS,CAAC;AAChB,aAAW,QAAQ,wBAAwB;AACzC,WAAO,IAAI,IAAI;AAAA,EACjB;AACA,SAAO;AACT;AAKA,SAAS,QAAQ,OAAuB;AACtC,SAAO,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,KAAK,CAAC;AACvC;AAeO,IAAM,4BAAN,MAAgC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWrC,YAAY,QAAkC;AAP9C,SAAQ,gBAAwB;AAQ9B,SAAK,SAAS;AAAA,MACZ,GAAG;AAAA,MACH,GAAG;AAAA,IACL;AACA,SAAK,oBAAoB,sBAAsB;AAC/C,SAAK,qBAAqB,sBAAsB;AAAA,EAClD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,SAAS,OAAqB,aAA4C;AAExE,SAAK,oBAAoB,sBAAsB;AAG/C,QAAI,gBAAgB,QAAW;AAC7B,WAAK,gBAAgB,QAAQ,WAAW;AAAA,IAC1C;AAGA,QAAI,CAAC,OAAO;AACV,aAAO,EAAE,GAAG,KAAK,kBAAkB;AAAA,IACrC;AAGA,QAAI,KAAK,OAAO,cAAc,YAAY;AACxC,WAAK,iBAAiB,KAAK;AAAA,IAC7B,OAAO;AACL,WAAK,iBAAiB,KAAK;AAAA,IAC7B;AAGA,QAAI,KAAK,OAAO,kBAAkB;AAChC,WAAK,sBAAsB;AAAA,IAC7B;AAEA,WAAO,EAAE,GAAG,KAAK,kBAAkB;AAAA,EACrC;AAAA;AAAA;AAAA;AAAA,EAKQ,iBAAiB,OAA2B;AAElD,QAAI,MAAM,aAAa,KAAK,OAAO,qBAAqB;AACtD;AAAA,IACF;AAGA,UAAM,UAAU,MAAM;AACtB,UAAM,UAAU,kBAAkB,OAAO;AAEzC,QAAI,CAAC,SAAS;AACZ;AAAA,IACF;AAGA,UAAM,QAAQ,KAAK,OAAO,YAAY,MAAM;AAE5C,eAAW,CAAC,MAAM,KAAK,KAAK,OAAO,QAAQ,OAAO,GAAG;AACnD,YAAM,iBAAiB;AACvB,UAAI,UAAU,QAAW;AACvB,aAAK,kBAAkB,cAAc,IAAI,QAAQ,QAAQ,KAAK;AAAA,MAChE;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,iBAAiB,OAA2B;AAClD,QAAI,CAAC,MAAM,eAAe;AAExB,WAAK,iBAAiB,KAAK;AAC3B;AAAA,IACF;AAGA,eAAW,CAAC,SAAS,WAAW,KAAK,OAAO,QAAQ,MAAM,aAAa,GAAG;AAExE,UAAI,cAAc,KAAK,OAAO,qBAAqB;AACjD;AAAA,MACF;AAEA,YAAM,UAAU,kBAAkB,OAA2B;AAC7D,UAAI,CAAC,SAAS;AACZ;AAAA,MACF;AAGA,YAAM,QAAQ,KAAK,OAAO,YAAY;AAEtC,iBAAW,CAAC,MAAM,KAAK,KAAK,OAAO,QAAQ,OAAO,GAAG;AACnD,cAAM,iBAAiB;AACvB,YAAI,UAAU,QAAW;AAEvB,eAAK,kBAAkB,cAAc,KAAK,QAAQ;AAAA,QACpD;AAAA,MACF;AAAA,IACF;AAGA,eAAW,QAAQ,wBAAwB;AACzC,WAAK,kBAAkB,IAAI,IAAI,QAAQ,KAAK,kBAAkB,IAAI,CAAC;AAAA,IACrE;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,wBAA8B;AACpC,UAAM,EAAE,gBAAgB,eAAe,IAAI,KAAK;AAGhD,UAAM,cAAc,iBAAiB,KAAK,iBAAiB,iBAAiB;AAE5E,eAAW,QAAQ,wBAAwB;AACzC,WAAK,kBAAkB,IAAI,IAAI,QAAQ,KAAK,kBAAkB,IAAI,IAAI,WAAW;AAAA,IACnF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,OAAO,UAAwB;AAC7B,UAAM,SAAS,KAAK,OAAO;AAE3B,eAAW,QAAQ,wBAAwB;AACzC,YAAM,SAAS,KAAK,kBAAkB,IAAI;AAC1C,YAAM,UAAU,KAAK,mBAAmB,IAAI;AAC5C,WAAK,mBAAmB,IAAI,IAAI,QAAQ,UAAU,UAAU,SAAS,QAAQ;AAAA,IAC/E;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,wBAA8C;AAC5C,WAAO,EAAE,GAAG,KAAK,mBAAmB;AAAA,EACtC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,QAAc;AACZ,SAAK,oBAAoB,sBAAsB;AAC/C,SAAK,qBAAqB,sBAAsB;AAChD,SAAK,gBAAgB;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA,EAKA,YAA+C;AAC7C,WAAO,EAAE,GAAG,KAAK,OAAO;AAAA,EAC1B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,UAAU,QAAgD;AACxD,SAAK,SAAS;AAAA,MACZ,GAAG,KAAK;AAAA,MACR,GAAG;AAAA,IACL;AAAA,EACF;AACF;;;ACtcO,SAAS,aAAa,SAA+B;AAC1D,MAAI,QAAQ,WAAW,EAAG,QAAO;AAEjC,MAAI,aAAa;AACjB,WAAS,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;AACvC,kBAAc,QAAQ,CAAC,IAAI,QAAQ,CAAC;AAAA,EACtC;AAEA,SAAO,KAAK,KAAK,aAAa,QAAQ,MAAM;AAC9C;AAOO,SAAS,cAAc,SAA+B;AAC3D,MAAI,OAAO;AACX,WAAS,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;AACvC,UAAM,MAAM,KAAK,IAAI,QAAQ,CAAC,CAAC;AAC/B,QAAI,MAAM,KAAM,QAAO;AAAA,EACzB;AACA,SAAO;AACT;AAKO,IAAM,sBAAN,MAA0B;AAAA;AAAA;AAAA;AAAA;AAAA,EAU/B,YAAY,kBAA0B,MAAM,aAAqB,MAAM;AATvE,SAAQ,cAAsB;AAC9B,SAAQ,eAAuB;AAS7B,SAAK,kBAAkB,KAAK,IAAI,GAAG,KAAK,IAAI,MAAM,eAAe,CAAC;AAClE,SAAK,aAAa;AAAA,EACpB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,QAAQ,SAAsE;AAC5E,UAAM,aAAa,aAAa,OAAO;AACvC,UAAM,cAAc,cAAc,OAAO;AAGzC,UAAM,WAAW,aAAa,KAAK,aAAa,aAAa;AAC7D,UAAM,YAAY,cAAc,KAAK,aAAa,cAAc;AAIhE,QAAI,WAAW,KAAK,aAAa;AAE/B,WAAK,cACH,KAAK,cAAc,MAAM,WAAW;AAAA,IACxC,OAAO;AAEL,WAAK,cACH,KAAK,cAAc,KAAK,kBACxB,YAAY,IAAI,KAAK;AAAA,IACzB;AAEA,QAAI,YAAY,KAAK,cAAc;AACjC,WAAK,eAAe,KAAK,eAAe,MAAM,YAAY;AAAA,IAC5D,OAAO;AACL,WAAK,eACH,KAAK,eAAe,KAAK,kBACzB,aAAa,IAAI,KAAK;AAAA,IAC1B;AAIA,UAAM,SAAS,KAAK,cAAc,MAAM,KAAK,eAAe;AAE5D,WAAO;AAAA,MACL,KAAK,KAAK;AAAA,MACV,MAAM,KAAK;AAAA,MACX,QAAQ,KAAK,IAAI,GAAG,SAAS,CAAC;AAAA;AAAA,IAChC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,QAAc;AACZ,SAAK,cAAc;AACnB,SAAK,eAAe;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,MAAc;AAChB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,OAAe;AACjB,WAAO,KAAK;AAAA,EACd;AACF;AAOO,IAAM,mBAAN,MAAuB;AAAA;AAAA;AAAA;AAAA;AAAA,EAS5B,YAAY,cAAsB,IAAI,oBAA4B,MAAM;AARxE,SAAQ,gBAA0B,CAAC;AASjC,SAAK,cAAc;AACnB,SAAK,oBAAoB;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,QAAQ,QAAmE;AACzE,SAAK,cAAc,KAAK,MAAM;AAC9B,QAAI,KAAK,cAAc,SAAS,KAAK,aAAa;AAChD,WAAK,cAAc,MAAM;AAAA,IAC3B;AAEA,QAAI,KAAK,cAAc,SAAS,GAAG;AACjC,aAAO,EAAE,YAAY,OAAO,kBAAkB,EAAE;AAAA,IAClD;AAGA,UAAM,aAAa,KAAK,cAAc,MAAM,GAAG,EAAE;AACjD,UAAM,UAAU,WAAW,OAAO,CAAC,GAAG,MAAM,IAAI,GAAG,CAAC,IAAI,WAAW;AAGnE,UAAM,WAAW,SAAS;AAC1B,UAAM,aAAa,WAAW,KAAK;AAEnC,WAAO;AAAA,MACL;AAAA,MACA,kBAAkB,aAAa,KAAK,IAAI,GAAG,WAAW,GAAG,IAAI;AAAA,IAC/D;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,QAAc;AACZ,SAAK,gBAAgB,CAAC;AAAA,EACxB;AACF;;;AC1HO,IAAM,kBAAN,MAA4D;AAAA,EAIjE,YAAY,UAAkD,CAAC,GAAG;AAChE,SAAK,UAAU,QAAQ,WAAW;AAClC,SAAK,SAAS,QAAQ,UAAU;AAAA,EAClC;AAAA,EAEA,WAAW,MAAsB;AAC/B,QAAI,CAAC,KAAK,QAAS;AAEnB,UAAM,aAAa,KAAK,WAAW,OAAO,WAAM;AAChD,UAAM,cAAc,KAAK,WAAW,OAAO,iBAAiB;AAE5D,YAAQ;AAAA,MACN,KAAK,KAAK,MAAM,MAAM,UAAU,IAAI,KAAK,IAAI,OAAO,KAAK,WAAW,QAAQ,CAAC,CAAC;AAAA,MAC9E;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAEA,YAAQ,IAAI,aAAa,KAAK,OAAO;AACrC,YAAQ,IAAI,YAAY,KAAK,MAAM;AACnC,QAAI,KAAK,cAAc;AACrB,cAAQ,IAAI,mBAAmB,KAAK,YAAY;AAAA,IAClD;AACA,YAAQ,IAAI,aAAa,GAAG,KAAK,WAAW,QAAQ,CAAC,CAAC,IAAI;AAC1D,YAAQ,IAAI,WAAW,KAAK,MAAM;AAElC,QAAI,OAAO,KAAK,KAAK,UAAU,EAAE,SAAS,GAAG;AAC3C,cAAQ,IAAI,eAAe,KAAK,UAAU;AAAA,IAC5C;AAEA,QAAI,KAAK,OAAO;AACd,cAAQ,MAAM,UAAU,KAAK,KAAK;AAAA,IACpC;AAEA,YAAQ,SAAS;AAAA,EACnB;AAAA,EAEA,aAAa,QAA0B;AACrC,QAAI,CAAC,KAAK,QAAS;AAEnB,UAAM,WAAW,OAAO,SAAS,YAAY,WAAM;AAEnD,YAAQ;AAAA,MACN,KAAK,KAAK,MAAM,MAAM,QAAQ,IAAI,OAAO,IAAI,OAAO,OAAO,KAAK;AAAA,MAChE;AAAA,MACA;AAAA,MACA;AAAA,MACA,OAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEA,MAAM,QAAuB;AAAA,EAE7B;AAAA,EAEA,MAAM,WAA0B;AAC9B,SAAK,UAAU;AAAA,EACjB;AACF;;;ACvGA,IAAM,aAAa;AAAA,EACjB,OAAO;AAAA,EACP,IAAI;AAAA,EACJ,OAAO;AACT;AAKA,SAAS,WAAW,MAAgB,aAAqB,gBAAwB;AAC/E,QAAM,aAAa,OAAO,QAAQ,KAAK,UAAU,EAC9C,OAAO,CAAC,CAAC,EAAE,CAAC,MAAM,MAAM,MAAS,EACjC,IAAI,CAAC,CAAC,KAAK,KAAK,OAAO;AAAA,IACtB;AAAA,IACA,OAAO,OAAO,UAAU,WACpB,EAAE,aAAa,MAAM,IACrB,OAAO,UAAU,WACf,OAAO,UAAU,KAAK,IACpB,EAAE,UAAU,MAAM,IAClB,EAAE,aAAa,MAAM,IACvB,EAAE,WAAW,MAAM;AAAA,EAC3B,EAAE;AAEJ,SAAO;AAAA,IACL,eAAe,CAAC;AAAA,MACd,UAAU;AAAA,QACR,YAAY;AAAA,UACV,EAAE,KAAK,gBAAgB,OAAO,EAAE,aAAa,YAAY,EAAE;AAAA,UAC3D,EAAE,KAAK,mBAAmB,OAAO,EAAE,aAAa,eAAe,EAAE;AAAA,UACjE,EAAE,KAAK,sBAAsB,OAAO,EAAE,aAAa,YAAY,EAAE;AAAA,UACjE,EAAE,KAAK,0BAA0B,OAAO,EAAE,aAAa,aAAa,EAAE;AAAA,QACxE;AAAA,MACF;AAAA,MACA,YAAY,CAAC;AAAA,QACX,OAAO;AAAA,UACL,MAAM;AAAA,UACN,SAAS;AAAA,QACX;AAAA,QACA,OAAO,CAAC;AAAA,UACN,SAAS,KAAK;AAAA,UACd,QAAQ,KAAK;AAAA,UACb,cAAc,KAAK,gBAAgB;AAAA,UACnC,MAAM,KAAK;AAAA,UACX,MAAM;AAAA;AAAA,UACN,mBAAmB,OAAO,KAAK,YAAY,GAAS;AAAA,UACpD,iBAAiB,OAAO,KAAK,UAAU,GAAS;AAAA,UAChD;AAAA,UACA,QAAQ;AAAA,YACN,MAAM,KAAK,WAAW,OAAO,WAAW,KAAK,WAAW;AAAA,YACxD,SAAS,KAAK,OAAO,WAAW;AAAA,UAClC;AAAA,QACF,CAAC;AAAA,MACH,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AACF;AAKA,SAAS,aAAa,QAAoB,aAAqB,gBAAwB;AACrF,QAAM,aAAa,OAAO,QAAQ,OAAO,UAAU,EAChD,OAAO,CAAC,CAAC,EAAE,CAAC,MAAM,MAAM,MAAS,EACjC,IAAI,CAAC,CAAC,KAAK,KAAK,OAAO;AAAA,IACtB;AAAA,IACA,OAAO,OAAO,UAAU,WACpB,EAAE,aAAa,MAAM,IACrB,OAAO,UAAU,WACf,OAAO,UAAU,KAAK,IACpB,EAAE,UAAU,MAAM,IAClB,EAAE,aAAa,MAAM,IACvB,EAAE,WAAW,MAAM;AAAA,EAC3B,EAAE;AAEJ,QAAM,YAAY;AAAA,IAChB;AAAA,IACA,cAAc,OAAO,OAAO,YAAY,GAAS;AAAA,IACjD,GAAI,OAAO,SAAS,YAChB,EAAE,OAAO,OAAO,MAAM,IACtB,EAAE,UAAU,OAAO,MAAM;AAAA,EAC/B;AAEA,SAAO;AAAA,IACL,iBAAiB,CAAC;AAAA,MAChB,UAAU;AAAA,QACR,YAAY;AAAA,UACV,EAAE,KAAK,gBAAgB,OAAO,EAAE,aAAa,YAAY,EAAE;AAAA,UAC3D,EAAE,KAAK,mBAAmB,OAAO,EAAE,aAAa,eAAe,EAAE;AAAA,QACnE;AAAA,MACF;AAAA,MACA,cAAc,CAAC;AAAA,QACb,OAAO;AAAA,UACL,MAAM;AAAA,UACN,SAAS;AAAA,QACX;AAAA,QACA,SAAS,CAAC;AAAA,UACR,MAAM,OAAO;AAAA,UACb,GAAI,OAAO,SAAS,YAChB;AAAA,YACA,KAAK;AAAA,cACH,YAAY,CAAC,SAAS;AAAA,cACtB,wBAAwB;AAAA;AAAA,cACxB,aAAa;AAAA,YACf;AAAA,UACF,IACE;AAAA,YACA,OAAO;AAAA,cACL,YAAY,CAAC,SAAS;AAAA,YACxB;AAAA,UACF;AAAA,QACJ,CAAC;AAAA,MACH,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AACF;AAYO,IAAM,eAAN,MAAyD;AAAA,EAW9D,YACE,QACA,cAAsB,aACtB,iBAAyB,SACzB;AAXF,SAAQ,aAAyB,CAAC;AAClC,SAAQ,eAA6B,CAAC;AACtC,SAAQ,kBAAyD;AACjE,SAAiB,cAAc;AAC/B,SAAiB,oBAAoB;AACrC,SAAQ,aAAa;AAOnB,SAAK,SAAS;AAAA,MACZ,WAAW;AAAA,MACX,SAAS,CAAC;AAAA,MACV,GAAG;AAAA,IACL;AACA,SAAK,cAAc;AACnB,SAAK,iBAAiB;AAGtB,SAAK,kBAAkB,YAAY,MAAM;AACvC,WAAK,MAAM,EAAE,MAAM,QAAQ,KAAK;AAAA,IAClC,GAAG,KAAK,iBAAiB;AAAA,EAC3B;AAAA,EAEA,WAAW,MAAsB;AAC/B,QAAI,KAAK,WAAY;AAErB,SAAK,WAAW,KAAK,IAAI;AAEzB,QAAI,KAAK,WAAW,UAAU,KAAK,aAAa;AAC9C,WAAK,MAAM,EAAE,MAAM,QAAQ,KAAK;AAAA,IAClC;AAAA,EACF;AAAA,EAEA,aAAa,QAA0B;AACrC,QAAI,KAAK,WAAY;AAErB,SAAK,aAAa,KAAK,MAAM;AAE7B,QAAI,KAAK,aAAa,UAAU,KAAK,aAAa;AAChD,WAAK,MAAM,EAAE,MAAM,QAAQ,KAAK;AAAA,IAClC;AAAA,EACF;AAAA,EAEA,MAAM,QAAuB;AAC3B,QAAI,KAAK,WAAY;AAErB,UAAM,QAAQ,KAAK,WAAW,OAAO,CAAC;AACtC,UAAM,UAAU,KAAK,aAAa,OAAO,CAAC;AAE1C,UAAM,WAA4B,CAAC;AAGnC,QAAI,MAAM,SAAS,GAAG;AACpB,eAAS,KAAK,KAAK,YAAY,KAAK,CAAC;AAAA,IACvC;AAGA,QAAI,QAAQ,SAAS,GAAG;AACtB,eAAS,KAAK,KAAK,cAAc,OAAO,CAAC;AAAA,IAC3C;AAEA,UAAM,QAAQ,IAAI,QAAQ;AAAA,EAC5B;AAAA,EAEA,MAAM,WAA0B;AAC9B,QAAI,KAAK,iBAAiB;AACxB,oBAAc,KAAK,eAAe;AAClC,WAAK,kBAAkB;AAAA,IACzB;AAGA,UAAM,KAAK,MAAM;AAEjB,SAAK,aAAa;AAAA,EACpB;AAAA,EAEA,MAAc,YAAY,OAAkC;AAE1D,UAAM,gBAAgB,MAAM;AAAA,MAAI,UAC9B,WAAW,MAAM,KAAK,aAAa,KAAK,cAAc,EAAE,cAAc,CAAC;AAAA,IACzE;AAEA,UAAM,OAAO,EAAE,cAAc;AAC7B,UAAM,WAAW,KAAK,OAAO,SAAS,QAAQ,OAAO,EAAE,IAAI;AAE3D,UAAM,KAAK,YAAY,UAAU,IAAI;AAAA,EACvC;AAAA,EAEA,MAAc,cAAc,SAAsC;AAEhE,UAAM,kBAAkB,QAAQ;AAAA,MAAI,YAClC,aAAa,QAAQ,KAAK,aAAa,KAAK,cAAc,EAAE,gBAAgB,CAAC;AAAA,IAC/E;AAEA,UAAM,OAAO,EAAE,gBAAgB;AAC/B,UAAM,WAAW,KAAK,OAAO,SAAS,QAAQ,OAAO,EAAE,IAAI;AAE3D,UAAM,KAAK,YAAY,UAAU,IAAI;AAAA,EACvC;AAAA,EAEA,MAAc,YAAY,UAAkB,MAA8B;AACxE,UAAM,aAAa,IAAI,gBAAgB;AACvC,UAAM,YAAY,WAAW,MAAM,WAAW,MAAM,GAAG,KAAK,OAAO,SAAS;AAE5E,QAAI;AACF,YAAM,WAAW,MAAM,MAAM,UAAU;AAAA,QACrC,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,gBAAgB;AAAA,UAChB,GAAG,KAAK,OAAO;AAAA,QACjB;AAAA,QACA,MAAM,KAAK,UAAU,IAAI;AAAA,QACzB,QAAQ,WAAW;AAAA,MACrB,CAAC;AAED,UAAI,CAAC,SAAS,IAAI;AAChB,gBAAQ,KAAK,yBAAyB,SAAS,MAAM,IAAI,SAAS,UAAU,EAAE;AAAA,MAChF;AAAA,IACF,SAAS,OAAO;AACd,UAAK,MAAgB,SAAS,cAAc;AAC1C,gBAAQ,KAAK,yBAAyB;AAAA,MACxC,OAAO;AACL,gBAAQ,KAAK,wBAAwB,KAAK;AAAA,MAC5C;AAAA,IACF,UAAE;AACA,mBAAa,SAAS;AAAA,IACxB;AAAA,EACF;AACF;;;ACpQA,SAAS,WAAW,SAAiB,IAAY;AAC/C,QAAM,QAAQ,IAAI,WAAW,MAAM;AACnC,SAAO,gBAAgB,KAAK;AAC5B,SAAO,MAAM,KAAK,KAAK,EACpB,IAAI,OAAK,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC,EACxC,KAAK,EAAE;AACZ;AA4BA,IAAI,kBAAyC;AA0BtC,SAAS,mBAAmB,QAAyC;AAC1E,MAAI,iBAAiB;AACnB,oBAAgB,SAAS;AAAA,EAC3B;AACA,oBAAkB,IAAI,eAAe,MAAM;AAC3C,SAAO;AACT;AAKO,SAAS,eAAsC;AACpD,SAAO;AACT;AAOO,IAAM,iBAAN,MAAqB;AAAA,EAU1B,YAAY,QAAyB;AARrC,SAAQ,WAA8C;AACtD,SAAQ,gBAA+B;AACvC,SAAQ,oBAA2D;AAGnE;AAAA,SAAQ,WAAkG,oBAAI,IAAI;AAClH,SAAQ,aAAuG,oBAAI,IAAI;AAGrH,SAAK,SAAS;AAAA,MACZ,SAAS,OAAO,WAAW;AAAA,MAC3B,aAAa,OAAO,eAAe;AAAA,MACnC,gBAAgB,OAAO,kBAAkB;AAAA,MACzC,UAAU,OAAO,YAAY;AAAA,MAC7B,gBAAgB,OAAO;AAAA,MACvB,UAAU,OAAO,YAAY,EAAE,OAAO,GAAK,oBAAoB,KAAK;AAAA,MACpE,gBAAgB,OAAO,kBAAkB;AAAA,MACzC,mBAAmB,OAAO,qBAAqB;AAAA,IACjD;AAEA,QAAI,KAAK,OAAO,SAAS;AACvB,WAAK,aAAa;AAClB,WAAK,uBAAuB;AAAA,IAC9B;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,eAAqB;AAC3B,YAAQ,KAAK,OAAO,UAAU;AAAA,MAC5B,KAAK;AACH,aAAK,WAAW,IAAI,gBAAgB,EAAE,SAAS,KAAK,CAAC;AACrD;AAAA,MACF,KAAK;AACH,YAAI,CAAC,KAAK,OAAO,gBAAgB;AAC/B,kBAAQ,KAAK,iEAAiE;AAC9E;AAAA,QACF;AACA,aAAK,WAAW,IAAI;AAAA,UAClB,KAAK,OAAO;AAAA,UACZ,KAAK,OAAO;AAAA,UACZ,KAAK,OAAO;AAAA,QACd;AACA;AAAA,MACF,KAAK;AAAA,MACL;AACE,aAAK,WAAW;AAAA,IACpB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,yBAA+B;AACrC,QAAI,CAAC,KAAK,OAAO,kBAAkB,CAAC,KAAK,SAAU;AAEnD,SAAK,oBAAoB,YAAY,MAAM;AACzC,WAAK,aAAa;AAAA,IACpB,GAAG,KAAK,OAAO,iBAAiB;AAAA,EAClC;AAAA;AAAA;AAAA;AAAA,EAKQ,aAAa,UAAmB,OAAgB;AACtD,QAAI,CAAC,KAAK,OAAO,QAAS,QAAO;AAEjC,UAAM,WAAW,KAAK,OAAO;AAC7B,QAAI,WAAW,SAAS,mBAAoB,QAAO;AAEnD,UAAM,QAAQ,SAAS,SAAS;AAChC,WAAO,KAAK,OAAO,IAAI;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAqBA,UAAU,MAAc,aAAsC,CAAC,GAAG,eAAyC;AACzG,UAAM,UAAU,eAAe,WAAW,KAAK,iBAAiB,WAAW,EAAE;AAC7E,UAAM,SAAS,WAAW,CAAC;AAC3B,UAAM,eAAe,eAAe;AACpC,UAAM,YAAY,YAAY,IAAI;AAGlC,QAAI,CAAC,iBAAiB,CAAC,KAAK,eAAe;AACzC,WAAK,gBAAgB;AAAA,IACvB;AAEA,QAAI,iBAAiB,EAAE,GAAG,WAAW;AACrC,QAAI,QAAQ;AACZ,QAAI,UAAU,KAAK,aAAa;AAEhC,UAAM,UAAuB,EAAE,SAAS,QAAQ,aAAa;AAE7D,UAAM,UAAU,CAAC,QAAwB,UAAwB;AAC/D,UAAI,MAAO;AACX,cAAQ;AAER,YAAM,UAAU,YAAY,IAAI;AAChC,YAAM,aAAa,UAAU;AAG7B,UAAI,WAAW,WAAW,CAAC,SAAS;AAClC,kBAAU,KAAK,aAAa,IAAI;AAAA,MAClC;AAEA,UAAI,CAAC,WAAW,CAAC,KAAK,SAAU;AAEhC,YAAM,WAAqB;AAAA,QACzB;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,YAAY;AAAA,QACZ;AAAA,MACF;AAEA,WAAK,SAAS,WAAW,QAAQ;AAGjC,UAAI,CAAC,gBAAgB,KAAK,kBAAkB,SAAS;AACnD,aAAK,gBAAgB;AAAA,MACvB;AAAA,IACF;AAEA,WAAO;AAAA,MACL,KAAK,MAAM,QAAQ,IAAI;AAAA,MACvB,cAAc,CAAC,UAAiB,QAAQ,SAAS,KAAK;AAAA,MACtD,eAAe,CAAC,UAAmC;AACjD,yBAAiB,EAAE,GAAG,gBAAgB,GAAG,MAAM;AAAA,MACjD;AAAA,MACA,YAAY,MAAM;AAAA,IACpB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcA,MAAM,SACJ,MACA,IACA,aAAsC,CAAC,GACvC,eACY;AACZ,UAAM,OAAO,KAAK,UAAU,MAAM,YAAY,aAAa;AAE3D,QAAI;AACF,YAAM,SAAS,MAAM,GAAG,IAAI;AAC5B,WAAK,IAAI;AACT,aAAO;AAAA,IACT,SAAS,OAAO;AACd,WAAK,aAAa,KAAc;AAChC,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcA,iBACE,MACA,QAAgB,GAChB,aAAwD,CAAC,GACnD;AACN,QAAI,CAAC,KAAK,OAAO,WAAW,CAAC,KAAK,OAAO,eAAgB;AAEzD,UAAM,MAAM,KAAK,aAAa,MAAM,UAAU;AAC9C,UAAM,WAAW,KAAK,SAAS,IAAI,GAAG;AAEtC,QAAI,UAAU;AACZ,eAAS,SAAS;AAAA,IACpB,OAAO;AACL,WAAK,SAAS,IAAI,KAAK,EAAE,OAAO,WAAW,CAAC;AAAA,IAC9C;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaA,gBACE,MACA,OACA,aAAwD,CAAC,GACnD;AACN,QAAI,CAAC,KAAK,OAAO,WAAW,CAAC,KAAK,OAAO,eAAgB;AAEzD,UAAM,MAAM,KAAK,aAAa,MAAM,UAAU;AAC9C,UAAM,WAAW,KAAK,WAAW,IAAI,GAAG;AAExC,QAAI,UAAU;AACZ,eAAS,OAAO,KAAK,KAAK;AAAA,IAC5B,OAAO;AACL,WAAK,WAAW,IAAI,KAAK,EAAE,QAAQ,CAAC,KAAK,GAAG,WAAW,CAAC;AAAA,IAC1D;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,aAAa,MAAc,YAA+D;AAChG,UAAM,cAAc,OAAO,QAAQ,UAAU,EAC1C,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC,EACrC,IAAI,CAAC,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,EAAE,EAC3B,KAAK,GAAG;AACX,WAAO,GAAG,IAAI,IAAI,WAAW;AAAA,EAC/B;AAAA;AAAA;AAAA;AAAA,EAKQ,eAAqB;AAC3B,QAAI,CAAC,KAAK,SAAU;AAEpB,UAAM,YAAY,YAAY,IAAI;AAGlC,eAAW,CAAC,KAAK,IAAI,KAAK,KAAK,UAAU;AACvC,YAAM,OAAO,IAAI,MAAM,GAAG,EAAE,CAAC;AAC7B,YAAM,SAAqB;AAAA,QACzB;AAAA,QACA,MAAM;AAAA,QACN,OAAO,KAAK;AAAA,QACZ,YAAY,KAAK;AAAA,QACjB;AAAA,MACF;AACA,WAAK,SAAS,aAAa,MAAM;AAAA,IACnC;AAGA,eAAW,CAAC,KAAK,IAAI,KAAK,KAAK,YAAY;AACzC,YAAM,OAAO,IAAI,MAAM,GAAG,EAAE,CAAC;AAC7B,UAAI,KAAK,OAAO,WAAW,EAAG;AAG9B,YAAM,MAAM,KAAK,OAAO,OAAO,CAAC,GAAG,MAAM,IAAI,GAAG,CAAC;AACjD,YAAM,MAAM,MAAM,KAAK,OAAO;AAE9B,YAAM,SAAqB;AAAA,QACzB;AAAA,QACA,MAAM;AAAA,QACN,OAAO;AAAA,QACP,YAAY;AAAA,UACV,GAAG,KAAK;AAAA,UACR,OAAO,KAAK,OAAO;AAAA,UACnB;AAAA,UACA,KAAK,KAAK,IAAI,GAAG,KAAK,MAAM;AAAA,UAC5B,KAAK,KAAK,IAAI,GAAG,KAAK,MAAM;AAAA,QAC9B;AAAA,QACA;AAAA,MACF;AACA,WAAK,SAAS,aAAa,MAAM;AAGjC,WAAK,SAAS,CAAC;AAAA,IACjB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,QAAuB;AAC3B,SAAK,aAAa;AAClB,UAAM,KAAK,UAAU,MAAM;AAAA,EAC7B;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,WAA0B;AAC9B,QAAI,KAAK,mBAAmB;AAC1B,oBAAc,KAAK,iBAAiB;AACpC,WAAK,oBAAoB;AAAA,IAC3B;AAEA,UAAM,KAAK,MAAM;AACjB,UAAM,KAAK,UAAU,SAAS;AAC9B,SAAK,WAAW;AAAA,EAClB;AAAA;AAAA;AAAA;AAAA,EAKA,YAAqB;AACnB,WAAO,KAAK,OAAO;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA,EAKA,YAA6B;AAC3B,WAAO,EAAE,GAAG,KAAK,OAAO;AAAA,EAC1B;AACF;;;AChUO,IAAM,cAAc;AAAA;AAAA,EAEzB,mBAAmB;AAAA;AAAA,EAEnB,iBAAiB;AAAA;AAAA,EAEjB,iBAAiB;AAAA;AAAA,EAEjB,cAAc;AAAA;AAAA,EAEd,YAAY;AAAA;AAAA,EAEZ,cAAc;AAChB;AAKO,IAAM,4BAA4B,CAAC,GAAG,GAAG,IAAI,IAAI,IAAI,KAAK,KAAK,KAAK,KAAM,MAAM,GAAI;AAKpF,IAAM,0BAA0B,CAAC,KAAK,KAAK,KAAM,MAAM,KAAM,KAAO,KAAO,GAAK;;;ACjIvF,IAAM,UAAU;AAChB,IAAM,aAAa;AACnB,IAAM,aAAa;AAGnB,IAAM,yBAAyB,OAAO,OAAO;AA6B7C,IAAI,oBAAiC;AAAA,EACnC,cAAc;AAChB;AAqBO,SAAS,oBAAoB,QAA2B;AAC7D,sBAAoB;AAAA,IAClB,GAAG;AAAA,IACH,GAAG;AAAA,EACL;AAGA,QAAM,QAAQ,cAAc;AAC5B,QAAM,aAAa,EAAE,MAAM,CAAC,QAAQ;AAClC,YAAQ,KAAK,6DAA6D,GAAG;AAAA,EAC/E,CAAC;AACH;AAKO,SAAS,iBAA8B;AAC5C,SAAO,EAAE,GAAG,kBAAkB;AAChC;AAuCO,SAAS,YAAY,KAAa,SAA0B;AACjE,MAAI,SAAS;AACX,WAAO,GAAG,GAAG,KAAK,OAAO;AAAA,EAC3B;AACA,SAAO;AACT;AAWO,IAAM,aAAN,MAAiB;AAAA,EAAjB;AACL,SAAQ,KAAyB;AACjC,SAAQ,YAAyC;AAAA;AAAA;AAAA;AAAA;AAAA,EAKjD,MAAc,QAA8B;AAC1C,QAAI,KAAK,GAAI,QAAO,KAAK;AACzB,QAAI,KAAK,UAAW,QAAO,KAAK;AAIhC,QAAI,UAAU,WAAW,UAAU,QAAQ,SAAS;AAClD,UAAI;AACF,cAAM,cAAc,MAAM,UAAU,QAAQ,QAAQ;AACpD,YAAI,aAAa;AACf,kBAAQ,IAAI,qEAAqE;AAAA,QACnF,OAAO;AACL,kBAAQ,IAAI,8DAA8D;AAAA,QAC5E;AAGA,YAAI,UAAU,QAAQ,UAAU;AAC9B,gBAAM,WAAW,MAAM,UAAU,QAAQ,SAAS;AAClD,gBAAM,WAAW,SAAS,SAAS,KAAK,OAAO,MAAM,QAAQ,CAAC;AAC9D,gBAAM,YAAY,SAAS,SAAS,KAAK,OAAO,MAAM,QAAQ,CAAC;AAC/D,kBAAQ,IAAI,yBAAyB,MAAM,QAAQ,OAAO,UAAU;AAAA,QACtE;AAAA,MACF,SAAS,KAAK;AACZ,gBAAQ,KAAK,sDAAsD,GAAG;AAAA,MACxE;AAAA,IACF;AAEA,SAAK,YAAY,IAAI,QAAQ,CAAC,SAAS,WAAW;AAChD,YAAM,UAAU,UAAU,KAAK,SAAS,UAAU;AAElD,cAAQ,UAAU,MAAM;AACtB,gBAAQ,MAAM,0CAA0C,QAAQ,KAAK;AACrE,eAAO,QAAQ,KAAK;AAAA,MACtB;AAEA,cAAQ,YAAY,MAAM;AACxB,aAAK,KAAK,QAAQ;AAClB,gBAAQ,KAAK,EAAE;AAAA,MACjB;AAEA,cAAQ,kBAAkB,CAAC,UAAU;AACnC,cAAM,KAAM,MAAM,OAA4B;AAC9C,cAAM,aAAc,MAAgC;AACpD,cAAM,KAAM,MAAM,OAA4B;AAE9C,YAAI,aAAa,GAAG;AAElB,gBAAM,QAAQ,GAAG,kBAAkB,YAAY,EAAE,SAAS,MAAM,CAAC;AACjE,gBAAM,YAAY,kBAAkB,kBAAkB,EAAE,QAAQ,MAAM,CAAC;AAAA,QACzE,WAAW,aAAa,KAAK,IAAI;AAE/B,gBAAM,QAAQ,GAAG,YAAY,UAAU;AAGvC,cAAI,CAAC,MAAM,WAAW,SAAS,gBAAgB,GAAG;AAChD,kBAAM,YAAY,kBAAkB,kBAAkB,EAAE,QAAQ,MAAM,CAAC;AAAA,UACzE;AAGA,gBAAM,gBAAgB,MAAM,WAAW;AACvC,wBAAc,YAAY,CAAC,gBAAgB;AACzC,kBAAM,SAAU,YAAY,OAA0C;AACtE,gBAAI,QAAQ;AACV,oBAAM,QAAQ,OAAO;AACrB,kBAAI,MAAM,mBAAmB,QAAW;AACtC,sBAAM,iBAAiB,MAAM,YAAY,KAAK,IAAI;AAClD,uBAAO,OAAO,KAAK;AAAA,cACrB;AACA,qBAAO,SAAS;AAAA,YAClB;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF,CAAC;AAED,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,IAAI,KAA+B;AACvC,QAAI;AACF,YAAM,KAAK,MAAM,KAAK,MAAM;AAC5B,aAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,cAAM,KAAK,GAAG,YAAY,YAAY,UAAU;AAChD,cAAM,QAAQ,GAAG,YAAY,UAAU;AACvC,cAAM,UAAU,MAAM,MAAM,GAAG;AAC/B,gBAAQ,YAAY,MAAM,QAAQ,QAAQ,SAAS,CAAC;AACpD,gBAAQ,UAAU,MAAM,QAAQ,KAAK;AAAA,MACvC,CAAC;AAAA,IACH,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,IAAI,KAA0C;AAClD,UAAM,YAAY,aAAa;AAC/B,UAAM,OAAO,WAAW,UAAU,kBAAkB,EAAE,aAAa,IAAI,CAAC;AACxE,QAAI;AACF,YAAM,KAAK,MAAM,KAAK,MAAM;AAC5B,aAAO,IAAI,QAAQ,CAAC,YAAY;AAE9B,cAAM,KAAK,GAAG,YAAY,YAAY,WAAW;AACjD,cAAM,QAAQ,GAAG,YAAY,UAAU;AACvC,cAAM,UAAU,MAAM,IAAI,GAAG;AAC7B,gBAAQ,YAAY,MAAM;AACxB,gBAAM,SAAS,QAAQ;AACvB,gBAAM,MAAM,QAAQ,QAAQ;AAC5B,gBAAM,cAAc,EAAE,aAAa,IAAI,CAAC;AACxC,cAAI,QAAQ;AACV,kBAAM,cAAc,EAAE,oBAAoB,OAAO,KAAK,CAAC;AAEvD,mBAAO,iBAAiB,KAAK,IAAI;AACjC,kBAAM,IAAI,MAAM;AAAA,UAClB;AACA,gBAAM,IAAI;AACV,cAAI,KAAK;AACP,uBAAW,iBAAiB,oBAAoB,GAAG,CAAC,CAAC;AAAA,UACvD,OAAO;AACL,uBAAW,iBAAiB,sBAAsB,GAAG,CAAC,CAAC;AAAA,UACzD;AACA,kBAAQ,QAAQ,QAAQ,IAAI;AAAA,QAC9B;AACA,gBAAQ,UAAU,MAAM;AACtB,gBAAM,cAAc,EAAE,aAAa,MAAM,CAAC;AAC1C,gBAAM,IAAI;AACV,qBAAW,iBAAiB,sBAAsB,GAAG,CAAC,CAAC;AACvD,kBAAQ,IAAI;AAAA,QACd;AAAA,MACF,CAAC;AAAA,IACH,QAAQ;AACN,YAAM,aAAa,IAAI,MAAM,kBAAkB,CAAC;AAChD,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAsBA,MAAM,kBAAkB,KAAa,aAAiD;AACpF,UAAM,YAAY,aAAa;AAC/B,UAAM,OAAO,WAAW,UAAU,gCAAgC,EAAE,aAAa,IAAI,CAAC;AAEtF,QAAI;AACF,YAAM,KAAK,MAAM,KAAK,MAAM;AAC5B,YAAM,SAAS,MAAM,IAAI,QAAiC,CAAC,YAAY;AACrE,cAAM,KAAK,GAAG,YAAY,YAAY,UAAU;AAChD,cAAM,QAAQ,GAAG,YAAY,UAAU;AACvC,cAAM,UAAU,MAAM,IAAI,GAAG;AAC7B,gBAAQ,YAAY,MAAM,QAAQ,QAAQ,MAAiC;AAC3E,gBAAQ,UAAU,MAAM,QAAQ,MAAS;AAAA,MAC3C,CAAC;AAGD,UAAI,CAAC,QAAQ,MAAM;AACjB,cAAM,cAAc,EAAE,aAAa,MAAM,CAAC;AAC1C,cAAM,IAAI;AACV,mBAAW,iBAAiB,sBAAsB,GAAG,CAAC,CAAC;AACvD,eAAO,EAAE,MAAM,MAAM,OAAO,MAAM;AAAA,MACpC;AAEA,YAAM,cAAc,EAAE,aAAa,MAAM,oBAAoB,OAAO,KAAK,CAAC;AAG1E,UAAI,CAAC,OAAO,MAAM;AAChB,cAAM,cAAc,EAAE,mBAAmB,OAAO,eAAe,MAAM,CAAC;AACtE,cAAM,IAAI;AACV,mBAAW,iBAAiB,oBAAoB,GAAG,CAAC,CAAC;AACrD,eAAO,EAAE,MAAM,OAAO,MAAM,OAAO,MAAM;AAAA,MAC3C;AAGA,YAAM,WAAW,eAAe;AAChC,UAAI;AACF,cAAM,WAAW,MAAM,MAAM,UAAU,EAAE,QAAQ,OAAO,CAAC;AACzD,YAAI,CAAC,SAAS,IAAI;AAEhB,gBAAM,cAAc,EAAE,mBAAmB,OAAO,eAAe,MAAM,CAAC;AACtE,gBAAM,IAAI;AACV,qBAAW,iBAAiB,oBAAoB,GAAG,CAAC,CAAC;AACrD,iBAAO,EAAE,MAAM,OAAO,MAAM,OAAO,MAAM;AAAA,QAC3C;AAEA,cAAM,aAAa,SAAS,QAAQ,IAAI,MAAM;AAC9C,cAAM,UAAU,eAAe,QAAQ,eAAe,OAAO;AAE7D,cAAM,cAAc;AAAA,UAClB,mBAAmB;AAAA,UACnB,eAAe;AAAA,UACf,qBAAqB,cAAc;AAAA,UACnC,qBAAqB,OAAO;AAAA,QAC9B,CAAC;AACD,cAAM,IAAI;AAEV,YAAI,SAAS;AACX,qBAAW,iBAAiB,qBAAqB,GAAG,CAAC,CAAC;AACtD,kBAAQ,IAAI,yCAAyC,GAAG,EAAE;AAAA,QAC5D,OAAO;AACL,qBAAW,iBAAiB,oBAAoB,GAAG,CAAC,CAAC;AAAA,QACvD;AAEA,eAAO,EAAE,MAAM,OAAO,MAAM,OAAO,QAAQ;AAAA,MAC7C,SAAS,YAAY;AAGnB,gBAAQ,KAAK,2DAA2D,UAAU;AAClF,cAAM,cAAc,EAAE,mBAAmB,OAAO,eAAe,MAAM,CAAC;AACtE,cAAM,IAAI;AACV,mBAAW,iBAAiB,oBAAoB,GAAG,CAAC,CAAC;AACrD,eAAO,EAAE,MAAM,OAAO,MAAM,OAAO,MAAM;AAAA,MAC3C;AAAA,IACF,QAAQ;AACN,YAAM,aAAa,IAAI,MAAM,gCAAgC,CAAC;AAC9D,aAAO,EAAE,MAAM,MAAM,OAAO,MAAM;AAAA,IACpC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,MAAM,IAAI,KAAa,MAAmB,MAAe,SAAiC;AACxF,UAAM,YAAY,aAAa;AAC/B,UAAM,OAAO,WAAW,UAAU,kBAAkB;AAAA,MAClD,aAAa;AAAA,MACb,oBAAoB,KAAK;AAAA,MACzB,GAAI,WAAW,EAAE,iBAAiB,QAAQ;AAAA,IAC5C,CAAC;AACD,QAAI;AAEF,WAAK,WAAW,EAAE,MAAM,CAAC,QAAQ;AAC/B,gBAAQ,KAAK,oCAAoC,GAAG;AAAA,MACtD,CAAC;AAED,YAAM,KAAK,MAAM,KAAK,MAAM;AAC5B,YAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAC3C,cAAM,KAAK,GAAG,YAAY,YAAY,WAAW;AACjD,cAAM,QAAQ,GAAG,YAAY,UAAU;AACvC,cAAM,MAAM,KAAK,IAAI;AACrB,cAAM,SAAsB;AAAA,UAC1B;AAAA,UACA;AAAA,UACA,MAAM,KAAK;AAAA,UACX,UAAU;AAAA,UACV,gBAAgB;AAAA,UAChB;AAAA,UACA;AAAA,QACF;AACA,cAAM,UAAU,MAAM,IAAI,MAAM;AAChC,gBAAQ,YAAY,MAAM;AACxB,gBAAM,IAAI;AACV,kBAAQ;AAAA,QACV;AACA,gBAAQ,UAAU,MAAM;AACtB,gBAAM,aAAa,QAAQ,SAAS,IAAI,MAAM,kBAAkB,CAAC;AACjE,iBAAO,QAAQ,KAAK;AAAA,QACtB;AAAA,MACF,CAAC;AAGD,WAAK,aAAa,EAAE,MAAM,CAAC,QAAQ;AACjC,gBAAQ,KAAK,mDAAmD,GAAG;AAAA,MACrE,CAAC;AAAA,IACH,SAAS,KAAK;AACZ,cAAQ,KAAK,uCAAuC,GAAG;AACvD,YAAM,aAAa,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC,CAAC;AAAA,IACxE;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAc,aAA4B;AACxC,UAAM,QAAQ,MAAM,KAAK,aAAa;AACtC,QAAI,CAAC,OAAO;AACV;AAAA,IACF;AAEA,UAAM,SAAS;AACf,UAAM,YAAY,aAAa;AAE/B,QAAI,MAAM,cAAc,IAAI;AAC1B,cAAQ,KAAK,8BAA8B,MAAM,YAAY,QAAQ,CAAC,CAAC,WAAW,YAAY,MAAM,SAAS,CAAC,MAAM,YAAY,MAAM,UAAU,CAAC,GAAG;AAGpJ,iBAAW,iBAAiB,6BAA6B,GAAG;AAAA,QAC1D,cAAc,OAAO,KAAK,MAAM,MAAM,WAAW,CAAC;AAAA,MACpD,CAAC;AAGD,UAAI,OAAO,gBAAgB;AACzB,YAAI;AACF,iBAAO,eAAe,KAAK;AAAA,QAC7B,SAAS,KAAK;AACZ,kBAAQ,KAAK,+CAA+C,GAAG;AAAA,QACjE;AAAA,MACF;AAAA,IACF;AAEA,QAAI,MAAM,cAAc,IAAI;AAC1B,cAAQ,KAAK,oEAAoE;AAEjF,YAAM,cAAc,KAAK,IAAI,MAAM,aAAa,KAAK,KAAK,OAAO,IAAI;AACrE,YAAM,KAAK,YAAY,WAAW;AAAA,IACpC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,OAAO,KAA4B;AACvC,QAAI;AACF,YAAM,KAAK,MAAM,KAAK,MAAM;AAC5B,aAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,cAAM,KAAK,GAAG,YAAY,YAAY,WAAW;AACjD,cAAM,QAAQ,GAAG,YAAY,UAAU;AACvC,cAAM,OAAO,GAAG;AAChB,WAAG,aAAa,MAAM,QAAQ;AAAA,MAChC,CAAC;AAAA,IACH,QAAQ;AAAA,IAER;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,QAAuB;AAC3B,QAAI;AACF,YAAM,KAAK,MAAM,KAAK,MAAM;AAC5B,aAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,cAAM,KAAK,GAAG,YAAY,YAAY,WAAW;AACjD,cAAM,QAAQ,GAAG,YAAY,UAAU;AACvC,cAAM,MAAM;AACZ,WAAG,aAAa,MAAM,QAAQ;AAAA,MAChC,CAAC;AAAA,IACH,QAAQ;AAAA,IAER;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,WAAgC;AACpC,QAAI;AACF,YAAM,KAAK,MAAM,KAAK,MAAM;AAC5B,aAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,cAAM,KAAK,GAAG,YAAY,YAAY,UAAU;AAChD,cAAM,QAAQ,GAAG,YAAY,UAAU;AACvC,cAAM,UAAU,MAAM,OAAO;AAC7B,gBAAQ,YAAY,MAAM;AACxB,gBAAM,SAAU,QAAQ,UAA4B,CAAC;AACrD,kBAAQ;AAAA,YACN,WAAW,OAAO,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,MAAM,CAAC;AAAA,YACpD,YAAY,OAAO;AAAA,YACnB,QAAQ,OAAO,IAAI,CAAC,OAAO;AAAA,cACzB,KAAK,EAAE;AAAA,cACP,MAAM,EAAE;AAAA,cACR,UAAU,IAAI,KAAK,EAAE,QAAQ;AAAA,YAC/B,EAAE;AAAA,UACJ,CAAC;AAAA,QACH;AACA,gBAAQ,UAAU,MAAM,QAAQ,EAAE,WAAW,GAAG,YAAY,GAAG,QAAQ,CAAC,EAAE,CAAC;AAAA,MAC7E,CAAC;AAAA,IACH,QAAQ;AACN,aAAO,EAAE,WAAW,GAAG,YAAY,GAAG,QAAQ,CAAC,EAAE;AAAA,IACnD;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,eAA8B;AAClC,UAAM,SAAS;AACf,UAAM,UAAU,OAAO,gBAAgB;AAEvC,UAAM,QAAQ,MAAM,KAAK,SAAS;AAClC,QAAI,MAAM,aAAa,SAAS;AAC9B;AAAA,IACF;AAEA,UAAM,cAAc,MAAM,YAAY;AACtC,UAAM,cAAc,MAAM,KAAK,YAAY,WAAW;AAEtD,QAAI,YAAY,SAAS,GAAG;AAC1B,cAAQ,IAAI,sCAAsC,YAAY,MAAM,mBAAmB,YAAY,WAAW,CAAC,EAAE;AAAA,IACnH;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAeA,MAAM,YAAY,aAAwC;AACxD,UAAM,YAAY,aAAa;AAC/B,UAAM,OAAO,WAAW,UAAU,0BAA0B;AAAA,MAC1D,4BAA4B;AAAA,IAC9B,CAAC;AAED,QAAI;AACF,YAAM,KAAK,MAAM,KAAK,MAAM;AAG5B,YAAM,SAAS,MAAM,IAAI,QAAuB,CAAC,YAAY;AAC3D,cAAM,KAAK,GAAG,YAAY,YAAY,UAAU;AAChD,cAAM,QAAQ,GAAG,YAAY,UAAU;AACvC,cAAM,UAAU,MAAM,OAAO;AAC7B,gBAAQ,YAAY,MAAM;AACxB,gBAAM,MAAO,QAAQ,UAA4B,CAAC;AAElD,cAAI,KAAK,CAAC,GAAG,OAAO,EAAE,kBAAkB,EAAE,YAAY,MAAM,EAAE,kBAAkB,EAAE,YAAY,EAAE;AAChG,kBAAQ,GAAG;AAAA,QACb;AACA,gBAAQ,UAAU,MAAM,QAAQ,CAAC,CAAC;AAAA,MACpC,CAAC;AAED,YAAM,cAAwB,CAAC;AAC/B,UAAI,aAAa;AAGjB,iBAAW,SAAS,QAAQ;AAC1B,YAAI,cAAc,aAAa;AAC7B;AAAA,QACF;AAEA,cAAM,KAAK,OAAO,MAAM,GAAG;AAC3B,oBAAY,KAAK,MAAM,GAAG;AAC1B,sBAAc,MAAM;AAEpB,gBAAQ,IAAI,yBAAyB,MAAM,GAAG,KAAK,YAAY,MAAM,IAAI,CAAC,GAAG;AAAA,MAC/E;AAEA,YAAM,cAAc;AAAA,QAClB,wBAAwB;AAAA,QACxB,2BAA2B,YAAY;AAAA,MACzC,CAAC;AACD,YAAM,IAAI;AAGV,UAAI,aAAa,GAAG;AAClB,mBAAW,iBAAiB,wBAAwB,YAAY,QAAQ;AAAA,UACtE,aAAa,OAAO,UAAU;AAAA,QAChC,CAAC;AAAA,MACH;AAEA,aAAO;AAAA,IACT,SAAS,KAAK;AACZ,YAAM,aAAa,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC,CAAC;AACtE,cAAQ,KAAK,iCAAiC,GAAG;AACjD,aAAO,CAAC;AAAA,IACV;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAmBA,MAAM,eAA0C;AAC9C,QAAI,CAAC,WAAW,SAAS,UAAU;AACjC,aAAO;AAAA,IACT;AAEA,QAAI;AACF,YAAM,WAAW,MAAM,UAAU,QAAQ,SAAS;AAClD,YAAM,YAAY,SAAS,SAAS;AACpC,YAAM,aAAa,SAAS,SAAS;AACrC,YAAM,cAAc,aAAa,IAAK,YAAY,aAAc,MAAM;AAEtE,YAAM,QAAQ,MAAM,KAAK,SAAS;AAElC,aAAO;AAAA,QACL;AAAA,QACA;AAAA,QACA;AAAA,QACA,YAAY,MAAM;AAAA,MACpB;AAAA,IACF,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AACF;AAGA,IAAI,gBAAmC;AAKhC,SAAS,gBAA4B;AAC1C,MAAI,CAAC,eAAe;AAClB,oBAAgB,IAAI,WAAW;AAAA,EACjC;AACA,SAAO;AACT;AAQA,IAAM,uBAAuB,MAAM,OAAO;AA0C1C,eAAsB,eACpB,KACA,mBACsB;AAEtB,MAAI,UAAiC,CAAC;AACtC,MAAI,OAAO,sBAAsB,YAAY;AAE3C,cAAU,EAAE,YAAY,kBAAkB;AAAA,EAC5C,WAAW,mBAAmB;AAE5B,cAAU;AAAA,EACZ;AAEA,QAAM,EAAE,SAAS,gBAAgB,OAAO,WAAW,IAAI;AAEvD,QAAM,QAAQ,cAAc;AAC5B,QAAM,WAAW,UAAU,YAAY,KAAK,OAAO,IAAI;AACvD,QAAM,YAAY,aAAa;AAC/B,QAAM,OAAO,WAAW,UAAU,kBAAkB;AAAA,IAClD,aAAa;AAAA,IACb,GAAI,WAAW,EAAE,iBAAiB,QAAQ;AAAA,IAC1C,wBAAwB;AAAA,EAC1B,CAAC;AAGD,MAAI,eAAe;AACjB,UAAM,aAAa,MAAM,MAAM,kBAAkB,UAAU,GAAG;AAE9D,QAAI,WAAW,QAAQ,CAAC,WAAW,OAAO;AACxC,cAAQ,IAAI,uCAAuC,GAAG,MAAM,WAAW,KAAK,aAAa,OAAO,MAAM,QAAQ,CAAC,CAAC,KAAK;AACrH,mBAAa,WAAW,KAAK,YAAY,WAAW,KAAK,UAAU;AACnE,YAAM,cAAc;AAAA,QAClB,mBAAmB;AAAA,QACnB,yBAAyB;AAAA,QACzB,qBAAqB;AAAA,QACrB,oBAAoB,WAAW,KAAK;AAAA,MACtC,CAAC;AACD,YAAM,IAAI;AACV,aAAO,WAAW;AAAA,IACpB;AAEA,QAAI,WAAW,OAAO;AACpB,cAAQ,IAAI,yCAAyC,GAAG,EAAE;AAC1D,YAAM,cAAc;AAAA,QAClB,mBAAmB;AAAA,QACnB,yBAAyB;AAAA,QACzB,qBAAqB;AAAA,MACvB,CAAC;AAAA,IAEH;AAAA,EAEF,OAAO;AAEL,UAAM,SAAS,MAAM,MAAM,IAAI,QAAQ;AACvC,QAAI,QAAQ;AACV,cAAQ,IAAI,2BAA2B,GAAG,MAAM,OAAO,aAAa,OAAO,MAAM,QAAQ,CAAC,CAAC,KAAK;AAChG,mBAAa,OAAO,YAAY,OAAO,UAAU;AACjD,YAAM,cAAc;AAAA,QAClB,mBAAmB;AAAA,QACnB,oBAAoB,OAAO;AAAA,MAC7B,CAAC;AACD,YAAM,IAAI;AACV,aAAO;AAAA,IACT;AAAA,EACF;AAEA,QAAM,cAAc,EAAE,mBAAmB,MAAM,CAAC;AAChD,UAAQ,IAAI,sCAAsC,GAAG,EAAE;AAEvD,MAAI;AAEF,UAAM,WAAW,MAAM,MAAM,GAAG;AAChC,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,IAAI,MAAM,mBAAmB,GAAG,KAAK,SAAS,MAAM,EAAE;AAAA,IAC9D;AAEA,UAAM,gBAAgB,SAAS,QAAQ,IAAI,gBAAgB;AAC3D,UAAM,QAAQ,gBAAgB,SAAS,eAAe,EAAE,IAAI;AAC5D,UAAM,OAAO,SAAS,QAAQ,IAAI,MAAM,KAAK;AAG7C,UAAM,mBAAmB,QAAQ;AACjC,QAAI,kBAAkB;AACpB,cAAQ,IAAI,+CAA+C,QAAQ,OAAO,MAAM,QAAQ,CAAC,CAAC,oCAAoC;AAAA,IAChI;AAEA,QAAI,CAAC,SAAS,MAAM;AAClB,YAAMA,QAAO,MAAM,SAAS,YAAY;AACxC,UAAI,CAAC,kBAAkB;AACrB,cAAM,MAAM,IAAI,UAAUA,OAAM,MAAM,OAAO;AAAA,MAC/C;AACA,YAAM,cAAc;AAAA,QAClB,oBAAoBA,MAAK;AAAA,QACzB,6BAA6B,CAAC;AAAA,MAChC,CAAC;AACD,YAAM,IAAI;AACV,aAAOA;AAAA,IACT;AAGA,UAAM,SAAS,SAAS,KAAK,UAAU;AACvC,UAAM,SAAuB,CAAC;AAC9B,QAAI,SAAS;AAEb,WAAO,MAAM;AACX,YAAM,EAAE,MAAM,MAAM,IAAI,MAAM,OAAO,KAAK;AAC1C,UAAI,KAAM;AACV,aAAO,KAAK,KAAK;AACjB,gBAAU,MAAM;AAChB,mBAAa,QAAQ,SAAS,MAAM;AAAA,IACtC;AAGA,UAAM,OAAO,IAAI,WAAW,MAAM;AAClC,QAAI,SAAS;AACb,eAAW,SAAS,QAAQ;AAC1B,WAAK,IAAI,OAAO,MAAM;AACtB,gBAAU,MAAM;AAAA,IAClB;AAEA,UAAM,SAAS,KAAK;AAGpB,QAAI,CAAC,kBAAkB;AACrB,YAAM,MAAM,IAAI,UAAU,QAAQ,MAAM,OAAO;AAC/C,cAAQ,IAAI,wBAAwB,GAAG,MAAM,OAAO,aAAa,OAAO,MAAM,QAAQ,CAAC,CAAC,KAAK;AAAA,IAC/F;AAEA,UAAM,cAAc;AAAA,MAClB,oBAAoB,OAAO;AAAA,MAC3B,6BAA6B,CAAC;AAAA,IAChC,CAAC;AACD,UAAM,IAAI;AAEV,WAAO;AAAA,EACT,SAAS,OAAO;AACd,UAAM,aAAa,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC,CAAC;AAC5E,UAAM;AAAA,EACR;AACF;AAKA,eAAsB,cACpB,MACA,YACe;AACf,QAAM,QAAQ,cAAc;AAE5B,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,UAAM,MAAM,KAAK,CAAC;AAClB,iBAAa,GAAG,KAAK,QAAQ,GAAG;AAEhC,QAAI,MAAM,MAAM,IAAI,GAAG,GAAG;AACxB,cAAQ,IAAI,gCAAgC,GAAG,EAAE;AACjD;AAAA,IACF;AAEA,UAAM,eAAe,GAAG;AAAA,EAC1B;AAEA,eAAa,KAAK,QAAQ,KAAK,QAAQ,MAAM;AAC/C;AAKO,SAAS,YAAY,OAAuB;AACjD,MAAI,QAAQ,KAAM,QAAO,GAAG,KAAK;AACjC,MAAI,QAAQ,OAAO,KAAM,QAAO,IAAI,QAAQ,MAAM,QAAQ,CAAC,CAAC;AAC5D,MAAI,QAAQ,OAAO,OAAO,KAAM,QAAO,IAAI,QAAQ,OAAO,MAAM,QAAQ,CAAC,CAAC;AAC1E,SAAO,IAAI,QAAQ,OAAO,OAAO,MAAM,QAAQ,CAAC,CAAC;AACnD;;;AC34BO,IAAM,qBAA+C;AAAA,EAC1D,OAAO;AAAA,EACP,MAAM;AAAA,EACN,MAAM;AAAA,EACN,OAAO;AAAA,EACP,OAAO;AAAA,EACP,SAAS;AACX;AAyEO,IAAM,yBAAwC;AAAA,EACnD,OAAO;AAAA,EACP,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,YAAY;AAAA,EACZ,eAAe;AACjB;;;AC9FA,IAAM,SAAS;AAAA,EACb,OAAO;AAAA,EACP,KAAK;AAAA,EACL,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,OAAO;AAAA,EACP,SAAS;AACX;AAKA,IAAM,eAAyC;AAAA,EAC7C,OAAO,OAAO;AAAA,EACd,MAAM,OAAO;AAAA,EACb,MAAM,OAAO;AAAA,EACb,OAAO,OAAO;AAAA,EACd,OAAO,OAAO;AAAA,EACd,SAAS,OAAO;AAClB;AAKA,IAAM,cAAwC;AAAA,EAC5C,OAAO;AAAA,EACP,MAAM;AAAA,EACN,MAAM;AAAA,EACN,OAAO;AAAA,EACP,OAAO;AAAA,EACP,SAAS;AACX;AAKA,IAAM,YAAY,OAAO,WAAW;AAKpC,SAAS,gBAAgB,WAA2B;AAClD,QAAM,OAAO,IAAI,KAAK,SAAS;AAC/B,SAAO,KAAK,YAAY,EAAE,UAAU,IAAI,EAAE;AAC5C;AAKA,SAAS,cAAc,MAAuB;AAC5C,QAAM,OAAO,oBAAI,QAAQ;AACzB,SAAO,KAAK,UAAU,MAAM,CAAC,KAAK,UAAU;AAC1C,QAAI,OAAO,UAAU,YAAY,UAAU,MAAM;AAC/C,UAAI,KAAK,IAAI,KAAK,GAAG;AACnB,eAAO;AAAA,MACT;AACA,WAAK,IAAI,KAAK;AAAA,IAChB;AAEA,QAAI,iBAAiB,OAAO;AAC1B,aAAO;AAAA,QACL,MAAM,MAAM;AAAA,QACZ,SAAS,MAAM;AAAA,QACf,OAAO,MAAM;AAAA,MACf;AAAA,IACF;AACA,QAAI,iBAAiB,gBAAgB,iBAAiB,YAAY;AAChE,aAAO,GAAG,MAAM,YAAY,IAAI,IAAI,MAAM,MAAM;AAAA,IAClD;AACA,QAAI,YAAY,OAAO,KAAK,GAAG;AAC7B,aAAO,GAAG,MAAM,YAAY,IAAI,IAAI,MAAM,UAAU;AAAA,IACtD;AACA,WAAO;AAAA,EACT,CAAC;AACH;AAKO,IAAM,gBAA8B,CAAC,UAA4B;AACtE,QAAM,SAAkC;AAAA,IACtC,WAAW,MAAM;AAAA,IACjB,OAAO,MAAM;AAAA,IACb,QAAQ,MAAM;AAAA,IACd,SAAS,MAAM;AAAA,EACjB;AAEA,MAAI,MAAM,QAAQ,OAAO,KAAK,MAAM,IAAI,EAAE,SAAS,GAAG;AACpD,WAAO,OAAO,MAAM;AAAA,EACtB;AAEA,MAAI,MAAM,OAAO;AACf,WAAO,QAAQ;AAAA,MACb,MAAM,MAAM,MAAM;AAAA,MAClB,SAAS,MAAM,MAAM;AAAA,MACrB,OAAO,MAAM,MAAM;AAAA,IACrB;AAAA,EACF;AAEA,SAAO,cAAc,MAAM;AAC7B;AAKO,IAAM,kBAAgC,CAAC,UAA4B;AACxE,QAAM,OAAO,gBAAgB,MAAM,SAAS;AAC5C,QAAM,QAAQ,YAAY,MAAM,KAAK;AACrC,QAAMC,UAAS,MAAM;AACrB,QAAM,UAAU,MAAM;AAGtB,MAAI;AAEJ,MAAI,WAAW;AAEb,aAAS,GAAG,IAAI,IAAI,KAAK,KAAKA,OAAM,KAAK,OAAO;AAAA,EAClD,OAAO;AAEL,UAAM,QAAQ,aAAa,MAAM,KAAK;AACtC,aAAS,GAAG,OAAO,IAAI,GAAG,IAAI,GAAG,OAAO,KAAK,IAAI,KAAK,GAAG,KAAK,GAAG,OAAO,KAAK,IAAI,OAAO,IAAI,IAAIA,OAAM,IAAI,OAAO,KAAK,IAAI,OAAO;AAAA,EACnI;AAGA,MAAI,MAAM,QAAQ,OAAO,KAAK,MAAM,IAAI,EAAE,SAAS,GAAG;AACpD,UAAM,UAAU,cAAc,MAAM,IAAI;AAExC,QAAI,QAAQ,SAAS,IAAI;AACvB,gBAAU,SAAS,KAAK,UAAU,MAAM,MAAM,MAAM,CAAC,EAAE,QAAQ,OAAO,MAAM;AAAA,IAC9E,OAAO;AACL,gBAAU,MAAM;AAAA,IAClB;AAAA,EACF;AAGA,MAAI,MAAM,OAAO;AACf,cAAU;AAAA,IAAO,MAAM,MAAM,IAAI,KAAK,MAAM,MAAM,OAAO;AACzD,QAAI,MAAM,MAAM,OAAO;AACrB,YAAM,aAAa,MAAM,MAAM,MAAM,MAAM,IAAI,EAAE,MAAM,GAAG,CAAC;AAC3D,gBAAU,SAAS,WAAW,KAAK,MAAM;AAAA,IAC3C;AAAA,EACF;AAEA,SAAO;AACT;AAKO,SAAS,aAAa,QAAyC;AACpE,SAAO,WAAW,SAAS,gBAAgB;AAC7C;AAMO,SAAS,yBAAyB,OAAwC;AAC/E,QAAM,OAAO,gBAAgB,MAAM,SAAS;AAC5C,QAAM,QAAQ,MAAM,MAAM,YAAY,EAAE,OAAO,CAAC;AAChD,QAAMA,UAAS,MAAM;AACrB,QAAM,UAAU,MAAM;AAGtB,QAAM,SAAS;AAAA,IACb,MAAM;AAAA,IACN,OAAO;AAAA,IACP,MAAM;AAAA,IACN,MAAM;AAAA,IACN,OAAO;AAAA,IACP,OAAO;AAAA,IACP,SAAS;AAAA,IACT,QAAQ;AAAA,IACR,SAAS;AAAA,EACX;AAEA,MAAI,YAAY;AAChB,QAAM,OAAiB;AAAA,IACrB,OAAO;AAAA,IACP;AAAA,IACA,OAAO,MAAM,KAAK;AAAA,IAClB;AAAA,IACA,OAAO;AAAA,IACPA;AAAA,IACA,OAAO;AAAA,IACP;AAAA,EACF;AAGA,MAAI,MAAM,QAAQ,OAAO,KAAK,MAAM,IAAI,EAAE,SAAS,GAAG;AACpD,iBAAa;AACb,SAAK,KAAK,MAAM,IAAyB;AAAA,EAC3C;AAEA,SAAO,CAAC,WAAW,GAAG,IAAI;AAC5B;;;ACrLA,IAAMC,aAAY,OAAO,WAAW;AAKpC,IAAI,eAA8B,EAAE,GAAG,uBAAuB;AAKvD,SAAS,iBAAiB,QAAsC;AACrE,iBAAe,EAAE,GAAG,cAAc,GAAG,OAAO;AAC9C;AAKO,SAAS,mBAAkC;AAChD,SAAO,EAAE,GAAG,aAAa;AAC3B;AAKO,SAAS,qBAA2B;AACzC,iBAAe,EAAE,GAAG,uBAAuB;AAC7C;AAKO,SAAS,YAAY,OAAuB;AACjD,eAAa,QAAQ;AACvB;AAKO,SAAS,kBAAkB,SAAwB;AACxD,eAAa,UAAU;AACzB;AAKA,IAAM,cAAuB,CAAC,UAA0B;AACtD,QAAM,gBAAgB,MAAM,UAAU,UAAU,UAC5C,MAAM,UAAU,SAAS,SACzB;AAEJ,MAAI,aAAa,WAAW,YAAYA,YAAW;AAEjD,UAAM,OAAO,yBAAyB,KAAK;AAC3C,IAAC,QAAgB,aAAa,EAAE,GAAG,IAAI;AAAA,EACzC,OAAO;AAEL,UAAM,YAAY,aAAa,aAAa,MAAM;AAClD,UAAM,YAAY,UAAU,KAAK;AACjC,IAAC,QAAgB,aAAa,EAAE,SAAS;AAAA,EAC3C;AACF;AAKA,SAAS,gBAAyB;AAChC,SAAO,aAAa,QAAQ;AAC9B;AAKA,SAAS,UAAU,OAA0B;AAC3C,MAAI,CAAC,aAAa,QAAS,QAAO;AAClC,SAAO,mBAAmB,KAAK,KAAK,mBAAmB,aAAa,KAAK;AAC3E;AAKA,IAAM,SAAN,MAAM,QAA0B;AAAA,EAG9B,YAAYC,SAAgB;AAC1B,SAAK,SAASA;AAAA,EAChB;AAAA,EAEQ,IAAI,OAAiB,SAAiB,MAAsC;AAClF,QAAI,CAAC,UAAU,KAAK,EAAG;AAEvB,UAAM,QAAkB;AAAA,MACtB,WAAW,KAAK,IAAI;AAAA,MACpB;AAAA,MACA,QAAQ,KAAK;AAAA,MACb;AAAA,MACA;AAAA,IACF;AAGA,QAAI,MAAM,iBAAiB,OAAO;AAChC,YAAM,QAAQ,KAAK;AAEnB,YAAM,EAAE,OAAO,GAAG,KAAK,IAAI;AAC3B,YAAM,OAAO,OAAO,KAAK,IAAI,EAAE,SAAS,IAAI,OAAO;AAAA,IACrD;AAEA,kBAAc,EAAE,KAAK;AAAA,EACvB;AAAA,EAEA,MAAM,SAAiB,MAAsC;AAC3D,SAAK,IAAI,SAAS,SAAS,IAAI;AAAA,EACjC;AAAA,EAEA,KAAK,SAAiB,MAAsC;AAC1D,SAAK,IAAI,QAAQ,SAAS,IAAI;AAAA,EAChC;AAAA,EAEA,KAAK,SAAiB,MAAsC;AAC1D,SAAK,IAAI,QAAQ,SAAS,IAAI;AAAA,EAChC;AAAA,EAEA,MAAM,SAAiB,MAAsC;AAC3D,SAAK,IAAI,SAAS,SAAS,IAAI;AAAA,EACjC;AAAA,EAEA,MAAM,SAAiB,MAAsC;AAC3D,SAAK,IAAI,SAAS,SAAS,IAAI;AAAA,EACjC;AAAA,EAEA,QAAQ,SAAiB,MAAsC;AAC7D,SAAK,IAAI,WAAW,SAAS,IAAI;AAAA,EACnC;AAAA,EAEA,MAAM,WAA4B;AAChC,WAAO,IAAI,QAAO,GAAG,KAAK,MAAM,IAAI,SAAS,EAAE;AAAA,EACjD;AACF;AAKA,IAAM,cAAc,oBAAI,IAAoB;AAcrC,SAAS,aAAaA,SAAyB;AACpD,MAAIC,WAAS,YAAY,IAAID,OAAM;AACnC,MAAI,CAACC,UAAQ;AACX,IAAAA,WAAS,IAAI,OAAOD,OAAM;AAC1B,gBAAY,IAAIA,SAAQC,QAAM;AAAA,EAChC;AACA,SAAOA;AACT;AAYO,IAAM,aAAsB;AAAA,EACjC,QAAQ;AAAA,EACR,OAAO,MAAM;AAAA,EAAC;AAAA,EACd,MAAM,MAAM;AAAA,EAAC;AAAA,EACb,MAAM,MAAM;AAAA,EAAC;AAAA,EACb,OAAO,MAAM;AAAA,EAAC;AAAA,EACd,OAAO,MAAM;AAAA,EAAC;AAAA,EACd,SAAS,MAAM;AAAA,EAAC;AAAA,EAChB,OAAO,MAAM;AACf;;;ACzKO,SAAS,cAAuB;AACrC,MAAI,OAAO,cAAc,YAAa,QAAO;AAC7C,QAAM,KAAK,UAAU,UAAU,YAAY;AAE3C,SAAO,mBAAmB,KAAK,EAAE,KAAK,SAAS,KAAK,EAAE,KAAK,CAAC,kCAAkC,KAAK,EAAE;AACvG;AAUO,SAAS,QAAiB;AAC/B,MAAI,OAAO,cAAc,YAAa,QAAO;AAC7C,QAAM,KAAK,UAAU,UAAU,YAAY;AAC3C,SAAO,mBAAmB,KAAK,EAAE;AACnC;AASO,SAAS,YAAqB;AACnC,MAAI,OAAO,cAAc,YAAa,QAAO;AAC7C,SAAO,WAAW,KAAK,UAAU,SAAS;AAC5C;AAYO,SAAS,WAAoB;AAClC,SAAO,MAAM,KAAK,UAAU;AAC9B;AAUO,SAAS,eAAwB;AACtC,MAAI,OAAO,cAAc,YAAa,QAAO;AAC7C,SAAO,SAAS,aAAa,UAAU,QAAQ;AACjD;AAYO,SAAS,wBAAwC;AAItD,MAAI,SAAS,KAAK,MAAM,GAAG;AACzB,WAAO;AAAA,EACT;AAGA,SAAO;AACT;AASO,SAAS,eACd,YACA,iBACgB;AAChB,UAAQ,YAAY;AAAA,IAClB,KAAK;AACH,aAAO;AAAA,IAET,KAAK;AACH,UAAI,CAAC,iBAAiB;AACpB,cAAM,IAAI;AAAA,UACR;AAAA,QACF;AAAA,MACF;AACA,aAAO;AAAA,IAET,KAAK;AACH,aAAO;AAAA,IAET,KAAK;AACH,aAAO,kBAAkB,WAAW;AAAA,IAEtC,KAAK;AAAA,IACL;AAEE,YAAM,cAAc,sBAAsB;AAC1C,UAAI,gBAAgB,YAAY,CAAC,iBAAiB;AAChD,eAAO;AAAA,MACT;AACA,aAAO;AAAA,EACX;AACF;AAOO,SAAS,wBAAgC;AAC9C,MAAI,MAAM,GAAG;AAEX,WAAO;AAAA,EACT;AAEA,MAAI,UAAU,GAAG;AAEf,WAAO;AAAA,EACT;AAGA,SAAO;AACT;AAWO,SAAS,wBAAiC;AAK/C,SAAO;AACT;AAUO,SAAS,WAAoB;AAClC,MAAI,OAAO,cAAc,YAAa,QAAO;AAC7C,QAAM,KAAK,UAAU,UAAU,YAAY;AAE3C,SAAO,SAAS,KAAK,EAAE,KAAK,CAAC,kCAAkC,KAAK,EAAE;AACxE;AAcO,SAAS,sBAA+B;AAC7C,SAAO,SAAS,KAAK,MAAM;AAC7B;AAUO,SAAS,+BAAwC;AACtD,MAAI,OAAO,WAAW,YAAa,QAAO;AAC1C,SAAO,uBAAuB,UAAU,6BAA6B;AACvE;AAaO,SAAS,qBAA8B;AAC5C,UAAQ,MAAM,KAAK,SAAS,MAAM,6BAA6B;AACjE;AAaO,SAAS,yBAAkC;AAChD,SAAO,MAAM;AACf;;;AC7NA,IAAM,SAAS,aAAa,YAAY;AAGxC,IAAI,cAAgC;AACpC,IAAI,gBAAuC;AAG3C,IAAM,gBAAgB;AAUtB,eAAsB,oBAAsC;AAK1D,MAAI,MAAM,GAAG;AACX,WAAO,MAAM,gEAAgE;AAC7E,WAAO;AAAA,EACT;AAEA,MAAI,CAAC,aAAa,GAAG;AACnB,WAAO,MAAM,6CAA6C;AAAA,MACxD,iBAAiB,OAAO,WAAW,cAAe,OAAe,kBAAkB;AAAA,IACrF,CAAC;AACD,WAAO;AAAA,EACT;AAEA,MAAI;AACF,UAAM,UAAU,MAAM,UAAU,IAAI,eAAe;AACnD,QAAI,CAAC,SAAS;AACZ,aAAO,MAAM,oCAAoC;AACjD,aAAO;AAAA,IACT;AAGA,UAAM,SAAS,MAAM,QAAQ,cAAc;AAC3C,QAAI,CAAC,QAAQ;AACX,aAAO,MAAM,uCAAuC;AACpD,aAAO;AAAA,IACT;AAGA,WAAO,QAAQ;AAEf,WAAO,MAAM,qCAAqC;AAClD,WAAO;AAAA,EACT,SAAS,KAAK;AACZ,WAAO,MAAM,iDAAiD,EAAE,OAAO,IAAI,CAAC;AAC5E,WAAO;AAAA,EACT;AACF;AAoBA,IAAI,iBAAiB;AAErB,SAAS,0BAAgC;AAIvC,MAAI,kBAAkB,CAAC,YAAY,EAAG;AACtC,mBAAiB;AAEjB,QAAM,aAAa,YAAY;AAC/B,QAAM,gBAAgB;AAEtB,SAAO,KAAK,iEAA4D;AASxE,EAAC,YAAoB,SAAS,SAAS,iBACrC,YACoB;AACpB,UAAM,UAAU,EAAE,GAAG,WAAW;AAEhC,QAAI,QAAQ,YAAY,UAAa,QAAQ,UAAU,eAAe;AACpE,aAAO,KAAK,qCAAqC;AAAA,QAC/C,UAAU,QAAQ;AAAA,QAClB,QAAQ;AAAA,QACR,QAAQ,QAAQ;AAAA,QAChB,SAAS,QAAQ;AAAA,MACnB,CAAC;AACD,cAAQ,UAAU;AAAA,IACpB;AAEA,WAAO,IAAI,WAAW,OAAO;AAAA,EAC/B;AAGA,EAAC,YAAoB,OAAO,YAAY,WAAW;AACrD;AAOA,SAAS,cAAc,KAAsB;AAE3C,MAAI,IAAI,KAAK,YAAY;AAGzB,QAAM,aAAa,sBAAsB;AACzC,QAAM,cAAc,sBAAsB;AAE1C,MAAI,IAAI,KAAK,aAAa;AAC1B,MAAI,IAAI,KAAK,OAAO;AACpB,MAAI,IAAI,KAAK,QAAQ;AAErB,SAAO,KAAK,mBAAmB;AAAA,IAC7B;AAAA,IACA,MAAM;AAAA,IACN,OAAO;AAAA,IACP,UAAU,MAAM,IAAI,QAAQ,SAAS,IAAI,YAAY;AAAA,EACvD,CAAC;AACH;AAeA,eAAsB,eACpB,SACoB;AAEpB,MAAI,eAAe,kBAAkB,SAAS;AAC5C,WAAO;AAAA,EACT;AAGA,MAAI,eAAe,kBAAkB,SAAS;AAC5C,WAAO;AAAA,MACL,oCAAoC,aAAa,8BAC3B,OAAO;AAAA,IAC/B;AACA,WAAO;AAAA,EACT;AAEA,SAAO,KAAK,6BAA6B,OAAO,aAAa;AAI7D,0BAAwB;AAExB,MAAI;AACF,QAAI,YAAY,WAAW,MAAM,KAAK,SAAS,IAAI;AAajD,YAAMC,UAAS,MAAM,OAAO,sBAAsB;AAClD,oBAAcA,QAAO,WAAWA;AAAA,IAClC,WAAW,YAAY,QAAQ;AAE7B,YAAMA,UAAS,MAAM,OAAO,iBAAiB;AAC7C,oBAAcA,QAAO,WAAWA;AAAA,IAClC,OAAO;AAEL,YAAMA,UAAS,MAAM,OAAO,wBAAwB;AACpD,oBAAcA,QAAO,WAAWA;AAAA,IAClC;AAEA,oBAAgB;AAGhB,kBAAc,WAAW;AAEzB,WAAO,KAAK,oCAAoC,EAAE,QAAQ,CAAC;AAE3D,WAAO;AAAA,EACT,SAAS,KAAK;AACZ,WAAO,MAAM,oCAAoC,OAAO,YAAY;AAAA,MAClE,OAAO;AAAA,IACT,CAAC;AACD,UAAM,IAAI;AAAA,MACR,gCAAgC,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,IAClF;AAAA,EACF;AACF;AAWA,eAAsB,4BACpB,aAAgC,QACsB;AAEtD,QAAM,kBAAkB,MAAM,kBAAkB;AAGhD,QAAM,UAAU,eAAe,YAAY,eAAe;AAE1D,SAAO,KAAK,+BAA+B;AAAA,IACzC;AAAA,IACA;AAAA,IACA,iBAAiB;AAAA,EACnB,CAAC;AAGD,QAAM,MAAM,MAAM,eAAe,OAAO;AAExC,SAAO,EAAE,KAAK,QAAQ;AACxB;AAUO,SAAS,kBACd,SACgB;AAChB,MAAI,YAAY,UAAU;AACxB,WAAO;AAAA,MACL,oBAAoB;AAAA,QAClB;AAAA,UACE,MAAM;AAAA,UACN,iBAAiB;AAAA;AAAA,QACnB;AAAA,MACF;AAAA,MACA,wBAAwB;AAAA,IAC1B;AAAA,EACF;AASA,MAAI,MAAM,GAAG;AACX,WAAO;AAAA,MACL,oBAAoB,CAAC,MAAM;AAAA,MAC3B,wBAAwB;AAAA,MACxB,mBAAmB;AAAA,MACnB,kBAAkB;AAAA,IACpB;AAAA,EACF;AAEA,SAAO;AAAA,IACL,oBAAoB,CAAC,MAAM;AAAA,IAC3B,wBAAwB;AAAA,EAC1B;AACF;AAWA,eAAsB,0BACpB,aACA,kBAIC;AACD,QAAM,MAAM,MAAM,eAAe,gBAAgB;AAGjD,QAAM,YAAY,IAAI,WAAW,WAAW;AAE5C,MAAI,qBAAqB,UAAU;AACjC,QAAI;AACF,YAAMC,WAAU,kBAAkB,QAAQ;AAC1C,YAAMC,WAAU,MAAM,IAAI,iBAAiB,OAAO,WAAWD,QAAO;AAEpE,aAAO,KAAK,qCAAqC;AACjD,aAAO,EAAE,SAAAC,UAAS,SAAS,SAAS;AAAA,IACtC,SAAS,KAAK;AACZ,aAAO,KAAK,wDAAwD;AAAA,QAClE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,MACxD,CAAC;AAAA,IAEH;AAAA,EACF;AAGA,QAAM,UAAU,kBAAkB,MAAM;AACxC,QAAM,UAAU,MAAM,IAAI,iBAAiB,OAAO,WAAW,OAAO;AAEpE,SAAO,KAAK,mCAAmC;AAC/C,SAAO,EAAE,SAAS,SAAS,OAAO;AACpC;AAKO,SAAS,mBAA0C;AACxD,SAAO;AACT;AAKO,SAAS,sBAA+B;AAC7C,SAAO,gBAAgB;AACzB;AAYA,eAAsB,mBACpB,aAAgC,QACP;AACzB,MAAI,aAAa;AACf,WAAO,KAAK,kCAAkC,EAAE,SAAS,cAAc,CAAC;AACxE,WAAO;AAAA,EACT;AAEA,SAAO,KAAK,8BAA8B,EAAE,WAAW,CAAC;AACxD,QAAM,EAAE,QAAQ,IAAI,MAAM,4BAA4B,UAAU;AAChE,SAAO,KAAK,0BAA0B,EAAE,QAAQ,CAAC;AACjD,SAAO;AACT;;;AC7ZO,IAAM,kBAAkB;AAAA,EAC7B;AAAA,EAAgB;AAAA,EAAiB;AAAA,EAAe;AAAA,EAAmB;AAAA,EACnE;AAAA,EAAa;AAAA,EAAmB;AAAA,EAChC;AAAA,EAAgB;AAAA,EAAiB;AAAA,EAAmB;AAAA,EACpD;AAAA,EAAiB;AAAA,EAAkB;AAAA,EAAkB;AAAA,EACrD;AAAA,EAAiB;AAAA,EAAkB;AAAA,EAAiB;AAAA,EACpD;AAAA,EAAe;AAAA,EACf;AAAA,EAAc;AAAA,EAAW;AAAA,EAAW;AAAA,EACpC;AAAA,EAAc;AAAA,EAAmB;AAAA,EAAoB;AAAA,EAAkB;AAAA,EACvE;AAAA,EAAe;AAAA,EAAa;AAAA,EAAsB;AAAA,EAClD;AAAA,EAAkB;AAAA,EAAmB;AAAA,EAAe;AAAA,EACpD;AAAA,EAAkB;AAAA,EAAkB;AAAA,EAAmB;AAAA,EACvD;AAAA,EAAkB;AAAA,EAAmB;AAAA,EAAoB;AAAA,EACzD;AAAA,EAAoB;AAAA,EACpB;AAAA,EAAiB;AAAA,EAAkB;AACrC;AAGO,IAAM,oBAAoB;AAMjC,IAAM,wBAA4C;AAAA,EAChD,CAAC,WAAW,UAAU;AAAA,EACtB,CAAC,aAAa,YAAY;AAAA,EAC1B,CAAC,kBAAkB,iBAAiB;AAAA,EACpC,CAAC,kBAAkB,iBAAiB;AAAA,EACpC,CAAC,mBAAmB,kBAAkB;AAAA,EACtC,CAAC,oBAAoB,mBAAmB;AAAA,EACxC,CAAC,kBAAkB,iBAAiB;AAAA,EACpC,CAAC,oBAAoB,mBAAmB;AAAA,EACxC,CAAC,sBAAsB,qBAAqB;AAAA,EAC5C,CAAC,iBAAiB,gBAAgB;AAAA,EAClC,CAAC,mBAAmB,kBAAkB;AAAA,EACtC,CAAC,gBAAgB,eAAe;AAAA,EAChC,CAAC,mBAAmB,kBAAkB;AAAA,EACtC,CAAC,gBAAgB,eAAe;AAAA,EAChC,CAAC,iBAAiB,gBAAgB;AAAA,EAClC,CAAC,mBAAmB,kBAAkB;AAAA,EACtC,CAAC,iBAAiB,gBAAgB;AAAA,EAClC,CAAC,kBAAkB,iBAAiB;AAAA,EACpC,CAAC,iBAAiB,gBAAgB;AAAA,EAClC,CAAC,eAAe,cAAc;AAChC;AAGA,IAAM,wBAA4C,sBAAsB,IAAI,CAAC,CAAC,GAAG,CAAC,MAAM;AAAA,EACtF,gBAAgB,QAAQ,CAAmC;AAAA,EAC3D,gBAAgB,QAAQ,CAAmC;AAC7D,CAAC,EAAE,OAAO,CAAC,CAAC,GAAG,CAAC,MAAM,MAAM,MAAM,MAAM,EAAE;AAOnC,SAAS,sBAAsB,OAAmC;AACvE,QAAM,SAAS,IAAI,aAAa,KAAK;AACrC,aAAW,CAAC,MAAM,IAAI,KAAK,uBAAuB;AAChD,UAAM,OAAO,MAAM,IAAI,IAAI,MAAM,IAAI,KAAK;AAC1C,WAAO,IAAI,IAAI;AACf,WAAO,IAAI,IAAI;AAAA,EACjB;AACA,SAAO;AACT;AASO,IAAM,wBAAwB;AAAA,EACnC;AAAA,EAAgB;AAAA,EAAiB;AAAA,EAAe;AAAA,EAAmB;AAAA,EACnE;AAAA,EAAa;AAAA,EAAmB;AAAA,EAChC;AAAA,EAAgB;AAAA,EAAiB;AAAA,EAAmB;AAAA,EACpD;AAAA,EAAiB;AAAA,EAAkB;AAAA,EAAkB;AAAA,EACrD;AAAA,EAAiB;AAAA,EAAkB;AAAA,EAAiB;AAAA,EACpD;AAAA,EAAe;AAAA,EACf;AAAA,EAAc;AAAA,EAAW;AAAA,EACzB;AAAA,EAAkB;AAAA,EAAmB;AAAA,EAAe;AAAA,EACpD;AAAA,EAAsB;AAAA,EACtB;AAAA,EAAkB;AAAA,EAAmB;AAAA,EAAe;AAAA,EACpD;AAAA,EAAkB;AAAA,EAAkB;AAAA,EAAmB;AAAA,EACvD;AAAA,EAAkB;AAAA,EAAmB;AAAA,EAAoB;AAAA,EACzD;AAAA,EAAoB;AAAA,EACpB;AAAA,EAAiB;AAAA,EAAkB;AAAA,EACnC;AAAA,EAAc;AAAA,EAAmB;AAAA,EAAoB;AACvD;AAQO,IAAM,yBAAmC,sBAAsB;AAAA,EACpE,CAAC,SAAS,gBAAgB,QAAQ,IAAsC;AAC1E;AAQO,SAAS,oBAAoB,OAAmC;AACrE,QAAM,SAAS,IAAI,aAAa,EAAE;AAClC,WAAS,IAAI,GAAG,IAAI,IAAI,KAAK;AAC3B,WAAO,uBAAuB,CAAC,CAAC,IAAI,MAAM,CAAC;AAAA,EAC7C;AACA,SAAO;AACT;;;AClFA,IAAMC,UAAS,aAAa,UAAU;AAgC/B,IAAM,YAAY;AAAA,EACvB;AAAA,EAAS;AAAA,EAAO;AAAA,EAAQ;AAAA,EAAS;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAC1D;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAC7C;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AACzD;AAmBO,IAAM,qBAAN,MAAM,mBAA4C;AAAA,EAmBvD,YAAY,QAAiC;AAlB7C,SAAS,UAAU;AAEnB,SAAQ,UAAmC;AAC3C,SAAQ,MAAwB;AAEhC,SAAQ,WAA2B;AACnC,SAAQ,YAAY;AAIpB;AAAA,SAAQ,iBAAgC,QAAQ,QAAQ;AAKxD;AAAA;AAAA;AAAA,SAAQ,WAAW;AAIjB,SAAK,SAAS;AACd,SAAK,qBAAqB,OAAO,sBAAsB;AAAA,EACzD;AAAA,EAQA,IAAI,UAAoC;AACtC,WAAO,KAAK,UAAU,KAAK,WAAW;AAAA,EACxC;AAAA,EAEA,IAAI,WAAoB;AACtB,WAAO,KAAK,YAAY;AAAA,EAC1B;AAAA;AAAA,EAGA,IAAI,oBAA6B;AAC/B,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,OAA2B;AAC/B,QAAI,KAAK,WAAW;AAClB,YAAM,IAAI,MAAM,0BAA0B;AAAA,IAC5C;AAEA,QAAI,KAAK,SAAS;AAChB,YAAM,IAAI,MAAM,6CAA6C;AAAA,IAC/D;AAEA,SAAK,YAAY;AACjB,UAAM,YAAY,YAAY,IAAI;AAClC,UAAM,YAAY,aAAa;AAC/B,UAAM,OAAO,WAAW,UAAU,iBAAiB;AAAA,MACjD,aAAa,KAAK,OAAO;AAAA,MACzB,2BAA2B,KAAK,OAAO,WAAW;AAAA,IACpD,CAAC;AAED,QAAI;AAIF,MAAAA,QAAO,KAAK,2BAA2B,EAAE,YAAY,KAAK,OAAO,WAAW,OAAO,CAAC;AAEpF,YAAM,EAAE,KAAK,QAAQ,IAAI,MAAM,4BAA4B,KAAK,OAAO,WAAW,MAAM;AACxF,WAAK,MAAM;AACX,WAAK,WAAW;AAEhB,MAAAA,QAAO,KAAK,uBAAuB,EAAE,SAAS,KAAK,SAAS,CAAC;AAE7D,YAAM,WAAW,KAAK,OAAO;AAC7B,YAAM,UAAU,KAAK,OAAO,oBAAoB,QAC3C,OAAO,KAAK,OAAO,oBAAoB,WACtC,KAAK,OAAO,kBACZ,GAAG,QAAQ,UACb;AACJ,YAAM,iBAAiB,kBAAkB,KAAK,QAAQ;AACtD,UAAI,WAAW;AAMf,UAAI,MAAM,GAAG;AACX,QAAAA,QAAO,KAAK,6DAA6D;AAAA,UACvE;AAAA,UACA;AAAA,QACF,CAAC;AAED,YAAI,SAAS;AACX,gBAAM,eAAe,QAAQ,MAAM,GAAG,EAAE,IAAI;AAC5C,UAAAA,QAAO,KAAK,6BAA6B,EAAE,cAAc,QAAQ,CAAC;AAClE,UAAC,eAA2C,eAAe,CAAC;AAAA,YAC1D,MAAM;AAAA,YACN,MAAM;AAAA;AAAA,UACR,CAAC;AAAA,QACH;AAEA,QAAAA,QAAO,KAAK,0DAA0D;AAAA,UACpE;AAAA,UACA,gBAAgB,KAAK;AAAA,YAAU;AAAA,YAAgB,CAAC,GAAG,MACjD,OAAO,MAAM,YAAY,EAAE,SAAS,MAAM,EAAE,MAAM,GAAG,GAAG,IAAI,QAAQ;AAAA,UACtE;AAAA,QACF,CAAC;AAED,YAAI;AACF,eAAK,UAAU,MAAM,KAAK,IAAK,iBAAiB,OAAO,UAAU,cAAc;AAAA,QACjF,SAAS,YAAY;AACnB,UAAAA,QAAO,MAAM,yCAAyC;AAAA,YACpD,OAAO,sBAAsB,QAAQ,WAAW,UAAU,OAAO,UAAU;AAAA,YAC3E,WAAW,YAAY,aAAa;AAAA,YACpC,OAAO,sBAAsB,QAAQ,WAAW,QAAQ;AAAA,UAC1D,CAAC;AACD,gBAAM;AAAA,QACR;AAEA,QAAAA,QAAO,KAAK,qCAAqC;AAAA,UAC/C,YAAY,KAAK,QAAQ;AAAA,UACzB,aAAa,KAAK,QAAQ;AAAA,QAC5B,CAAC;AAAA,MACH,OAAO;AAEL,cAAM,QAAQ,cAAc;AAC5B,mBAAW,MAAM,MAAM,IAAI,QAAQ;AAEnC,YAAI;AACJ,YAAI,UAAU;AACZ,UAAAA,QAAO,MAAM,4BAA4B,EAAE,SAAS,CAAC;AACrD,wBAAe,MAAM,MAAM,IAAI,QAAQ;AAEvC,cAAI,CAAC,aAAa;AAChB,YAAAA,QAAO,KAAK,oDAAoD,EAAE,SAAS,CAAC;AAC5E,kBAAM,MAAM,OAAO,QAAQ;AAC3B,0BAAc,MAAM,eAAe,QAAQ;AAAA,UAC7C;AAAA,QACF,OAAO;AACL,UAAAA,QAAO,MAAM,8BAA8B,EAAE,SAAS,CAAC;AACvD,wBAAc,MAAM,eAAe,QAAQ;AAAA,QAC7C;AAEA,YAAI,CAAC,aAAa;AAChB,gBAAM,IAAI,MAAM,yBAAyB,QAAQ,EAAE;AAAA,QACrD;AAGA,YAAI,qBAAyC;AAC7C,YAAI,SAAS;AACX,cAAI;AACF,kBAAM,eAAe,MAAM,MAAM,IAAI,OAAO;AAC5C,gBAAI,cAAc;AAChB,cAAAA,QAAO,MAAM,oCAAoC,EAAE,QAAQ,CAAC;AAC5D,mCAAsB,MAAM,MAAM,IAAI,OAAO;AAC7C,kBAAI,CAAC,oBAAoB;AACvB,gBAAAA,QAAO,KAAK,gDAAgD,EAAE,QAAQ,CAAC;AACvE,sBAAM,MAAM,OAAO,OAAO;AAC1B,qCAAqB,MAAM,eAAe,OAAO;AAAA,cACnD;AAAA,YACF,OAAO;AACL,cAAAA,QAAO,KAAK,gCAAgC;AAAA,gBAC1C;AAAA,gBACA,MAAM;AAAA,cACR,CAAC;AACD,mCAAqB,MAAM,eAAe,OAAO;AAAA,YACnD;AACA,YAAAA,QAAO,KAAK,wBAAwB;AAAA,cAClC,MAAM,YAAY,mBAAmB,UAAU;AAAA,YACjD,CAAC;AAAA,UACH,SAAS,KAAK;AACZ,YAAAA,QAAO,MAAM,mDAAmD;AAAA,cAC9D;AAAA,cACA,OAAQ,IAAc;AAAA,YACxB,CAAC;AAAA,UACH;AAAA,QACF;AAEA,QAAAA,QAAO,MAAM,yBAAyB;AAAA,UACpC,WAAW,YAAY,YAAY,UAAU;AAAA,UAC7C,kBAAkB,qBAAqB,YAAY,mBAAmB,UAAU,IAAI;AAAA,UACpF,SAAS,KAAK;AAAA,QAChB,CAAC;AAGD,YAAI,oBAAoB;AACtB,gBAAM,eAAe,QAAS,MAAM,GAAG,EAAE,IAAI;AAC7C,UAAC,eAA2C,eAAe,CAAC;AAAA,YAC1D,MAAM;AAAA,YACN,MAAM,IAAI,WAAW,kBAAkB;AAAA,UACzC,CAAC;AAAA,QACH;AAEA,cAAM,YAAY,IAAI,WAAW,WAAW;AAC5C,aAAK,UAAU,MAAM,KAAK,IAAK,iBAAiB,OAAO,WAAW,cAAc;AAAA,MAClF;AAEA,MAAAA,QAAO,KAAK,qCAAqC;AAAA,QAC/C,mBAAmB,KAAK;AAAA,QACxB,SAAS,KAAK;AAAA,MAChB,CAAC;AAED,YAAM,aAAa,YAAY,IAAI,IAAI;AAEvC,MAAAA,QAAO,KAAK,6BAA6B;AAAA,QACvC,SAAS,KAAK;AAAA,QACd,YAAY,KAAK,MAAM,UAAU;AAAA,QACjC,QAAQ,KAAK,QAAQ;AAAA,QACrB,SAAS,KAAK,QAAQ;AAAA,MACxB,CAAC;AAED,YAAM,cAAc;AAAA,QAClB,iBAAiB,KAAK;AAAA,QACtB,sBAAsB;AAAA,QACtB,gBAAgB,CAAC,MAAM,KAAK;AAAA,MAC9B,CAAC;AACD,YAAM,IAAI;AACV,iBAAW,gBAAgB,yBAAyB,YAAY;AAAA,QAC9D,OAAO;AAAA,QACP,SAAS,KAAK;AAAA,MAChB,CAAC;AAKD,MAAAA,QAAO,MAAM,oDAAoD;AACjE,YAAM,cAAc,YAAY,IAAI;AACpC,YAAM,cAAc,IAAI,aAAa,IAAK;AAC1C,YAAM,oBAAoB;AAC1B,YAAM,eAAe,MAAM,QAAQ,KAAK;AAAA,QACtC,KAAK,MAAM,aAAa,CAAC,EAAE,KAAK,MAAM,IAAa;AAAA,QACnD,IAAI,QAAmB,OAAK,WAAW,MAAM,EAAE,SAAS,GAAG,iBAAiB,CAAC;AAAA,MAC/E,CAAC;AACD,YAAM,eAAe,YAAY,IAAI,IAAI;AACzC,UAAI,iBAAiB,WAAW;AAC9B,QAAAA,QAAO,KAAK,yFAAoF;AAAA,UAC9F,WAAW;AAAA,UACX,SAAS,KAAK;AAAA,QAChB,CAAC;AAAA,MACH,OAAO;AACL,QAAAA,QAAO,KAAK,6BAA6B;AAAA,UACvC,cAAc,KAAK,MAAM,YAAY;AAAA,UACrC,SAAS,KAAK;AAAA,QAChB,CAAC;AAAA,MACH;AACA,iBAAW,gBAAgB,2BAA2B,cAAc;AAAA,QAClE,OAAO;AAAA,QACP,SAAS,KAAK;AAAA,MAChB,CAAC;AAED,aAAO;AAAA,QACL,SAAS,KAAK;AAAA,QACd;AAAA,QACA,YAAY,CAAC,GAAG,KAAK,QAAQ,UAAU;AAAA,QACvC,aAAa,CAAC,GAAG,KAAK,QAAQ,WAAW;AAAA,MAC3C;AAAA,IACF,SAAS,OAAO;AACd,YAAM,aAAa,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC,CAAC;AAC5E,iBAAW,iBAAiB,sBAAsB,GAAG;AAAA,QACnD,OAAO;AAAA,QACP,YAAY;AAAA,MACd,CAAC;AACD,YAAM;AAAA,IACR,UAAE;AACA,WAAK,YAAY;AAAA,IACnB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAM,MACJ,cACA,gBAAwB,GACC;AACzB,QAAI,CAAC,KAAK,SAAS;AACjB,YAAM,IAAI,MAAM,sCAAsC;AAAA,IACxD;AAEA,QAAI,KAAK,UAAU;AACjB,YAAM,IAAI,MAAM,2EAAsE;AAAA,IACxF;AAKA,UAAM,mBAAmB,IAAI,aAAa,YAAY;AAGtD,QAAI;AACJ,QAAI,iBAAiB,WAAW,MAAO;AACrC,cAAQ;AAAA,IACV,WAAW,iBAAiB,SAAS,MAAO;AAE1C,cAAQ,IAAI,aAAa,IAAK;AAC9B,YAAM,IAAI,kBAAkB,CAAC;AAAA,IAC/B,OAAO;AAEL,cAAQ,iBAAiB,MAAM,GAAG,IAAK;AAAA,IACzC;AAGA,UAAM,WAAW,IAAI,aAAa,KAAK,kBAAkB;AACzD,aAAS,KAAK,IAAI,eAAe,KAAK,qBAAqB,CAAC,CAAC,IAAI;AAIjE,UAAM,YAAY,IAAI,aAAa,KAAK;AACxC,UAAM,eAAe,IAAI,aAAa,QAAQ;AAE9C,UAAM,QAAQ;AAAA,MACZ,SAAS,IAAI,KAAK,IAAK,OAAO,WAAW,WAAW,CAAC,GAAG,IAAK,CAAC;AAAA,MAC9D,YAAY,IAAI,KAAK,IAAK,OAAO,WAAW,cAAc,CAAC,GAAG,KAAK,kBAAkB,CAAC;AAAA,IACxF;AAGA,WAAO,KAAK,eAAe,KAAK;AAAA,EAClC;AAAA;AAAA;AAAA;AAAA,EAKQ,UAAU,QAAgC;AAChD,UAAM,SAAmB,CAAC;AAC1B,QAAI,YAAY;AAEhB,eAAW,SAAS,QAAQ;AAE1B,UAAI,SAAS;AACb,UAAI,SAAS,MAAM,CAAC;AACpB,eAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,YAAI,MAAM,CAAC,IAAI,QAAQ;AACrB,mBAAS,MAAM,CAAC;AAChB,mBAAS;AAAA,QACX;AAAA,MACF;AAGA,UAAI,WAAW,aAAa,WAAW,GAAG;AACxC,eAAO,KAAK,MAAM;AAAA,MACpB;AACA,kBAAY;AAAA,IACd;AAGA,WAAO,OAAO,IAAI,OAAK,UAAU,CAAC,MAAM,MAAM,MAAM,UAAU,CAAC,CAAC,EAAE,KAAK,EAAE;AAAA,EAC3E;AAAA;AAAA;AAAA;AAAA,EAKQ,eACN,OACyB;AACzB,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,WAAK,iBAAiB,KAAK,eAAe,KAAK,YAAY;AACzD,cAAM,YAAY,aAAa;AAC/B,cAAM,OAAO,WAAW,UAAU,kBAAkB;AAAA,UAClD,qBAAqB,KAAK;AAAA,UAC1B,2BAA2B;AAAA,QAC7B,CAAC;AACD,YAAI;AACF,gBAAM,YAAY,YAAY,IAAI;AAClC,gBAAM,UAAU,MAAM,QAAQ,KAAK;AAAA,YACjC,KAAK,QAAS,IAAI,KAAK;AAAA,YACvB,IAAI;AAAA,cAAe,CAAC,GAAG,QACrB;AAAA,gBACE,MAAM,IAAI,IAAI,MAAM,sCAAsC,mBAAkB,oBAAoB,IAAI,CAAC;AAAA,gBACrG,mBAAkB;AAAA,cACpB;AAAA,YACF;AAAA,UACF,CAAC;AACD,gBAAM,kBAAkB,YAAY,IAAI,IAAI;AAE5C,gBAAM,YAAY,QAAQ,YAAY;AACtC,gBAAM,mBAAmB,QAAQ,aAAa;AAE9C,cAAI,CAAC,aAAa,CAAC,kBAAkB;AACnC,kBAAM,IAAI,MAAM,4BAA4B;AAAA,UAC9C;AAEA,gBAAM,UAAU,UAAU;AAC1B,gBAAM,iBAAiB,iBAAiB;AAGxC,gBAAM,eAAe,UAAU,KAAK,CAAC;AACrC,gBAAM,eAAe,iBAAiB,KAAK,CAAC;AAC5C,gBAAM,eAAe,UAAU,KAAK,CAAC;AACrC,gBAAM,iBAAiB,iBAAiB,KAAK,CAAC;AAG9C,gBAAM,YAA4B,CAAC;AACnC,gBAAM,cAA8B,CAAC;AAErC,mBAAS,IAAI,GAAG,IAAI,cAAc,KAAK;AACrC,sBAAU,KAAK,QAAQ,MAAM,IAAI,eAAe,IAAI,KAAK,YAAY,CAAC;AAAA,UACxE;AAEA,mBAAS,IAAI,GAAG,IAAI,cAAc,KAAK;AACrC,kBAAM,WAAW,eAAe,MAAM,IAAI,iBAAiB,IAAI,KAAK,cAAc;AAElF,wBAAY,KAAK,sBAAsB,QAAQ,CAAC;AAAA,UAClD;AAGA,gBAAM,OAAO,KAAK,UAAU,SAAS;AAErC,UAAAA,QAAO,MAAM,uBAAuB;AAAA,YAClC,iBAAiB,KAAK,MAAM,kBAAkB,GAAG,IAAI;AAAA,YACrD;AAAA,YACA;AAAA,YACA,YAAY,KAAK;AAAA,UACnB,CAAC;AAED,gBAAM,cAAc;AAAA,YAClB,yBAAyB;AAAA,YACzB,wBAAwB;AAAA,YACxB,wBAAwB;AAAA,UAC1B,CAAC;AACD,gBAAM,IAAI;AACV,qBAAW,gBAAgB,2BAA2B,iBAAiB;AAAA,YACrE,OAAO;AAAA,YACP,SAAS,KAAK;AAAA,UAChB,CAAC;AACD,qBAAW,iBAAiB,yBAAyB,GAAG;AAAA,YACtD,OAAO;AAAA,YACP,SAAS,KAAK;AAAA,YACd,QAAQ;AAAA,UACV,CAAC;AAED,kBAAQ;AAAA,YACN;AAAA,YACA;AAAA,YACA;AAAA,YACA,WAAW;AAAA,YACX;AAAA,YACA;AAAA,YACA;AAAA,UACF,CAAC;AAAA,QACH,SAAS,KAAK;AACZ,gBAAM,SAAS,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC9D,cAAI,OAAO,SAAS,WAAW,GAAG;AAChC,iBAAK,WAAW;AAChB,YAAAA,QAAO,MAAM,mFAA8E;AAAA,cACzF,SAAS,KAAK;AAAA,cACd,WAAW,mBAAkB;AAAA,YAC/B,CAAC;AAAA,UACH,OAAO;AACL,YAAAA,QAAO,MAAM,oBAAoB,EAAE,OAAO,QAAQ,SAAS,KAAK,SAAS,CAAC;AAAA,UAC5E;AACA,gBAAM,aAAa,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC,CAAC;AACtE,qBAAW,iBAAiB,yBAAyB,GAAG;AAAA,YACtD,OAAO;AAAA,YACP,SAAS,KAAK;AAAA,YACd,QAAQ;AAAA,UACV,CAAC;AACD,iBAAO,GAAG;AAAA,QACZ;AAAA,MACF,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,cAAc,aAA2B,MAA8C;AACrF,UAAM,QAAQ,gBAAgB,QAAQ,IAAI;AAC1C,QAAI,UAAU,IAAI;AAChB,YAAM,IAAI,MAAM,uBAAuB,IAAI,EAAE;AAAA,IAC/C;AACA,WAAO,YAAY,KAAK;AAAA,EAC1B;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,UAAyB;AAC7B,QAAI,KAAK,SAAS;AAChB,YAAM,KAAK,QAAQ,QAAQ;AAC3B,WAAK,UAAU;AAAA,IACjB;AAAA,EACF;AACF;AAzea,mBAiBa,uBAAuB;AAAA;AAAA;AAAA;AAAA;AAjBpC,mBA4BJ,oBAAoB;AA5BtB,IAAM,oBAAN;;;AC/CP,IAAMC,UAAS,aAAa,kBAAkB;AAM9C,SAASC,gBAAe,QAAmC;AACzD,QAAM,UAAU,OAAO,aAAa,CAAC;AACrC,QAAM,QAAQ,YAAY,OAAO,aAC7B,IAAI,WAAW,MAAM,IACrB,IAAI,WAAW,QAAQ,GAAG,UAAU,CAAC;AACzC,QAAM,UAAU,IAAI,aAAa,MAAM,MAAM;AAC7C,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,YAAQ,CAAC,IAAI,MAAM,CAAC,IAAI;AAAA,EAC1B;AACA,SAAO;AACT;AAKA,IAAM,uBAAuB,oBAAI,IAAoB;AACrD,gBAAgB,QAAQ,CAAC,MAAM,UAAU;AACvC,uBAAqB,IAAI,MAAM,KAAK;AACtC,CAAC;AAKD,IAAM,iBAAiB,IAAI,IAAY,sBAAsB;AAK7D,IAAM,oBAAsD;AAAA;AAAA,EAE1D,OAAO;AAAA,EACP,KAAK;AAAA,EACL,OAAO;AAAA,EACP,SAAS;AAAA;AAAA,EAET,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,UAAU;AAAA,EACV,WAAW;AAAA,EACX,QAAQ;AAAA,EACR,aAAa;AAAA,EACb,WAAW;AAAA,EACX,cAAc;AAAA,EACd,YAAY;AAAA,EACZ,WAAW;AAAA,EACX,SAAS;AAAA,EACT,SAAS;AAAA;AAAA,EAET,SAAS;AAAA,EACT,WAAW;AAAA,EACX,WAAW;AACb;AA4EO,IAAM,oBAAN,MAAM,0BAAyB,aAAqC;AAAA,EAyBzE,YAA6B,SAAkC;AAC7D,UAAM;AADqB;AAlB7B,SAAQ,kBAAkB;AAC1B,SAAQ,kBAAyD;AACjE,SAAQ,mBAAkC;AAG1C;AAAA,SAAQ,mBAAwC;AAChD,SAAQ,qBAAqB;AAG7B;AAAA,SAAQ,mBAAmB;AAC3B,SAAQ,oBAAyC;AACjD,SAAQ,sBAAsB;AAU5B,UAAM,aAAa,QAAQ,cAAc;AACzC,SAAK,qBAAqB,QAAQ,sBAAsB;AACxD,SAAK,iBAAiB,QAAQ,kBAAkB;AAGhD,UAAM,YAAY,QAAQ,IAAI,YAAY,kBAAkB,MACxD,QAAQ,IAAI,YAAY,SAAS,MACjC;AACJ,UAAM,eAAe,QAAQ,gBAAgB;AAE7C,SAAK,YAAY,IAAI,eAAe;AAAA,MAClC;AAAA,MACA,qBAAqB,eAAe;AAAA,IACtC,CAAC;AACD,SAAK,YAAY,IAAI,oBAAoB;AAAA,MACvC;AAAA,MACA,kBAAkB,QAAQ,iBAAiB;AAAA,IAC7C,CAAC;AACD,SAAK,cAAc,IAAI,YAAY;AAAA,MACjC;AAAA,MACA,SAAS,CAAC,UAAU;AAClB,QAAAD,QAAO,MAAM,uBAAuB,EAAE,SAAS,MAAM,SAAS,OAAO,MAAM,MAAM,CAAC;AAClF,aAAK,KAAK,SAAS,KAAK;AAAA,MAC1B;AAAA,IACF,CAAC;AACD,SAAK,gBAAgB,IAAI,0BAA0B;AAAA,MACjD,iBAAiB;AAAA,MACjB,qBAAqB;AAAA,MACrB,WAAW;AAAA,MACX,kBAAkB;AAAA,IACpB,CAAC;AACD,SAAK,iBAAiB,IAAI,oBAAoB;AAAA,EAChD;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,aAA4B;AAChC,UAAM,KAAK,UAAU,WAAW;AAAA,EAClC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaA,gBAAgB,OAAqB;AACnC,UAAM,aAAa,MAAM,YAAY;AACrC,UAAM,SAAS,kBAAkB,UAAU,KAAK;AAGhD,UAAM,gBAAkD;AAAA,MACtD,SAAS;AAAA,MACT,OAAO;AAAA,MACP,OAAO;AAAA,MACP,KAAK;AAAA,IACP;AACA,kBAAc,MAAM,IAAI;AAExB,UAAM,QAAsB;AAAA,MAC1B,SAAS;AAAA,MACT,YAAY;AAAA,MACZ;AAAA,IACF;AAEA,SAAK,mBAAmB;AACxB,IAAAA,QAAO,KAAK,qBAAqB,EAAE,OAAO,OAAO,CAAC;AAAA,EACpD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,oBAA0B;AACxB,SAAK,mBAAmB;AAAA,EAC1B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,QAAc;AAEZ,SAAK,eAAe;AAEpB,SAAK,UAAU,MAAM;AACrB,SAAK,UAAU,MAAM;AACrB,SAAK,YAAY,MAAM;AACvB,SAAK,kBAAkB;AAGvB,SAAK,mBAAmB;AACxB,SAAK,qBAAqB;AAC1B,SAAK,cAAc,MAAM;AACzB,SAAK,eAAe,MAAM;AAG1B,SAAK,mBAAmB;AACxB,SAAK,oBAAoB;AACzB,SAAK,sBAAsB;AAG3B,SAAK,UAAU,OAAO;AAEtB,SAAK,eAAe;AACpB,SAAK,gBAAgB;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAM,aAAa,OAAkC;AAEnD,UAAM,WAAW,KAAK,UAAU,IAAI,KAAK;AACzC,QAAI,CAAC,UAAU;AACb;AAAA,IACF;AAGA,UAAM,UAAUC,gBAAe,QAAQ;AAGvC,UAAM,eAAe,MAAM,KAAK,UAAU,SAAS,OAAO;AAG1D,QAAI,CAAC,KAAK,iBAAiB;AACzB,WAAK,kBAAkB;AACvB,WAAK,KAAK,kBAAkB,YAAY;AAAA,IAC1C;AAGA,UAAM,EAAE,OAAO,IAAI,KAAK,eAAe,QAAQ,OAAO;AACtD,SAAK,qBAAqB;AAG1B,SAAK,YAAY,KAAK,SAAS,cAAc,KAAK,QAAQ,GAAG,EAAE,MAAM,SAAO;AAC1E,WAAK,KAAK,SAAS,GAAG;AAAA,IACxB,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASQ,kBAAkE;AACxE,QAAI,KAAK,kBAAkB;AACzB,aAAO,EAAE,OAAO,KAAK,kBAAkB,QAAQ,KAAK,mBAAmB;AAAA,IACzE;AAKA,WAAO,EAAE,OAAO,MAAM,QAAQ,KAAK,mBAAmB;AAAA,EACxD;AAAA;AAAA;AAAA;AAAA,EAKA,iBACE,UACA,cACA,aACoE;AACpE,UAAM,SAAS,IAAI,aAAa,EAAE;AAClC,QAAI;AAEJ,QAAI,cAAc;AAEhB,WAAK,cAAc,SAAS,cAAc,WAAW;AACrD,WAAK,cAAc,OAAO,EAAE;AAC5B,2BAAqB,KAAK,cAAc,sBAAsB;AAAA,IAChE,OAAO;AAGL,2BAAqB,CAAC;AACtB,iBAAW,QAAQ,wBAAwB;AACzC,2BAAmB,IAAI,IAAI;AAAA,MAC7B;AAAA,IACF;AAGA,aAAS,IAAI,GAAG,IAAI,IAAI,KAAK;AAC3B,YAAM,OAAO,gBAAgB,CAAC;AAE9B,UAAI,eAAe,IAAI,IAAI,GAAG;AAE5B,cAAM,eAAe,mBAAmB,IAAkC,KAAK;AAC/E,cAAM,WAAW,SAAS,CAAC;AAC3B,eAAO,CAAC,IAAI,eAAe,KAAK,qBAAqB,WAAW,KAAK;AAAA,MACvE,OAAO;AAEL,eAAO,CAAC,IAAI,SAAS,CAAC;AAAA,MACxB;AAAA,IACF;AAEA,WAAO,EAAE,QAAQ,mBAAmB;AAAA,EACtC;AAAA;AAAA;AAAA;AAAA,EAKQ,iBAAuB;AAC7B,UAAM,cAAc,MAAM;AACxB,YAAM,cAAc,KAAK,UAAU,eAAe;AAClD,YAAM,WAAW,KAAK,YAAY,gBAAgB,aAAa,KAAK,QAAQ,GAAG;AAE/E,UAAI,UAAU;AAEZ,YAAI,aAAa,KAAK,mBAAmB;AACvC,eAAK,mBAAmB,YAAY,IAAI;AACxC,eAAK,oBAAoB;AACzB,eAAK,sBAAsB;AAAA,QAC7B;AAGA,cAAM,EAAE,OAAO,cAAc,OAAO,IAAI,KAAK,gBAAgB;AAG7D,cAAM,EAAE,QAAQ,mBAAmB,IAAI,KAAK,iBAAiB,UAAU,cAAc,MAAM;AAG3F,cAAM,YAA2B;AAAA,UAC/B,aAAa;AAAA,UACb,gBAAgB;AAAA,UAChB;AAAA,UACA,SAAS;AAAA,UACT,WAAW;AAAA,QACb;AAEA,aAAK,KAAK,oBAAoB,SAAS;AACvC,aAAK,KAAK,mBAAmB,QAAQ;AAErC,YAAI,cAAc;AAChB,eAAK,KAAK,uBAAuB,YAAY;AAAA,QAC/C;AAAA,MACF,WAAW,KAAK,mBAAmB,CAAC,KAAK,mBAAmB;AAG1D,cAAM,EAAE,OAAO,cAAc,OAAO,IAAI,KAAK,gBAAgB;AAC7D,YAAI,gBAAgB,SAAS,MAAM;AACjC,gBAAM,eAAe,IAAI,aAAa,EAAE;AACxC,gBAAM,EAAE,QAAQ,mBAAmB,IAAI,KAAK,iBAAiB,cAAc,cAAc,MAAM;AAC/F,eAAK,KAAK,oBAAoB;AAAA,YAC5B,aAAa;AAAA,YACb,gBAAgB;AAAA,YAChB;AAAA,YACA,SAAS;AAAA,YACT,WAAW;AAAA,UACb,CAAC;AAAA,QACH;AAAA,MACF;AAGA,UACE,KAAK,mBACL,KAAK,mBAAmB,KACxB,CAAC,KAAK,uBACN,YAAY,IAAI,IAAI,KAAK,mBAAmB,kBAAiB,0BAC7D;AACA,aAAK,sBAAsB;AAC3B,QAAAD,QAAO,KAAK,2EAAsE;AAAA,UAChF,iBAAiB,KAAK,MAAM,YAAY,IAAI,IAAI,KAAK,gBAAgB;AAAA,UACrE,cAAc,KAAK,YAAY;AAAA,QACjC,CAAC;AAAA,MACH;AAEA,WAAK,mBAAmB,sBAAsB,WAAW;AAAA,IAC3D;AAEA,SAAK,mBAAmB,sBAAsB,WAAW;AAAA,EAC3D;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,MAAqB;AAEzB,UAAM,YAAY,KAAK,UAAU,MAAM;AACvC,QAAI,WAAW;AACb,YAAM,QAAQ,IAAI,WAAW,SAAS;AACtC,YAAM,KAAK,aAAa,KAAK;AAAA,IAC/B;AAGA,UAAM,KAAK,YAAY,MAAM,KAAK,QAAQ,GAAG;AAAA,EAC/C;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,KAAK,YAAoB,IAAmB;AAChD,SAAK,eAAe;AACpB,UAAM,KAAK,UAAU,UAAU,SAAS;AAExC,SAAK,UAAU,MAAM;AACrB,SAAK,YAAY,MAAM;AACvB,SAAK,kBAAkB;AAGvB,SAAK,mBAAmB;AACxB,SAAK,qBAAqB;AAC1B,SAAK,cAAc,MAAM;AACzB,SAAK,eAAe,MAAM;AAG1B,SAAK,mBAAmB;AACxB,SAAK,oBAAoB;AACzB,SAAK,sBAAsB;AAE3B,SAAK,KAAK,qBAAqB,MAAgB;AAAA,EACjD;AAAA;AAAA;AAAA;AAAA,EAKQ,kBAAwB;AAC9B,QAAI,KAAK,iBAAiB;AACxB,oBAAc,KAAK,eAAe;AAAA,IACpC;AAEA,SAAK,kBAAkB,YAAY,MAAM;AACvC,UAAI,KAAK,UAAU,WAAW,KAAK,KAAK,YAAY,qBAAqB,GAAG;AAC1E,aAAK,KAAK,qBAAqB,MAAgB;AAC/C,aAAK,eAAe;AAAA,MACtB;AAAA,IACF,GAAG,GAAG;AAAA,EACR;AAAA;AAAA;AAAA;AAAA,EAKQ,iBAAuB;AAC7B,QAAI,KAAK,iBAAiB;AACxB,oBAAc,KAAK,eAAe;AAClC,WAAK,kBAAkB;AAAA,IACzB;AAEA,QAAI,KAAK,kBAAkB;AACzB,2BAAqB,KAAK,gBAAgB;AAC1C,WAAK,mBAAmB;AAAA,IAC1B;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,WAAW;AACT,WAAO;AAAA,MACL,iBAAiB,KAAK;AAAA,MACtB,eAAe,KAAK,UAAU;AAAA,MAC9B,SAAS,KAAK,YAAY;AAAA,MAC1B,iBAAiB,KAAK,YAAY;AAAA,MAClC,cAAc,KAAK,kBAAkB,WAAW;AAAA,MAChD,oBAAoB,KAAK;AAAA,MACzB,aAAa,KAAK,UAAU,eAAe;AAAA,MAC3C,iBAAiB,KAAK,UAAU,mBAAmB;AAAA,IACrD;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,kBAA2B;AAC7B,WAAO,KAAK,qBAAqB;AAAA,EACnC;AAAA;AAAA;AAAA;AAAA,EAKA,UAAgB;AACd,SAAK,eAAe;AACpB,SAAK,UAAU,QAAQ;AACvB,SAAK,UAAU,MAAM;AACrB,SAAK,YAAY,MAAM;AACvB,SAAK,mBAAmB;AACxB,SAAK,qBAAqB;AAAA,EAC5B;AACF;AAraa,kBAmBa,2BAA2B;AAnB9C,IAAM,mBAAN;;;AC5KP,SAAS,IAAI,IAAkB,IAAwB;AACrD,QAAM,IAAI,GAAG;AAGb,WAAS,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,KAAK;AACjC,QAAI,MAAM,KAAK;AACf,WAAO,IAAI,KAAK;AACd,WAAK;AACL,cAAQ;AAAA,IACV;AACA,SAAK;AACL,QAAI,IAAI,GAAG;AACT,UAAI,MAAM,GAAG,CAAC;AAAG,SAAG,CAAC,IAAI,GAAG,CAAC;AAAG,SAAG,CAAC,IAAI;AACxC,YAAM,GAAG,CAAC;AAAG,SAAG,CAAC,IAAI,GAAG,CAAC;AAAG,SAAG,CAAC,IAAI;AAAA,IACtC;AAAA,EACF;AAGA,WAAS,MAAM,GAAG,OAAO,GAAG,OAAO,GAAG;AACpC,UAAM,UAAU,MAAM;AACtB,UAAM,QAAQ,KAAK,KAAK,KAAK;AAC7B,UAAM,MAAM,KAAK,IAAI,KAAK;AAC1B,UAAM,MAAM,KAAK,IAAI,KAAK;AAE1B,aAAS,IAAI,GAAG,IAAI,GAAG,KAAK,KAAK;AAC/B,UAAI,QAAQ;AACZ,UAAI,QAAQ;AACZ,eAAS,IAAI,GAAG,IAAI,SAAS,KAAK;AAChC,cAAM,IAAI,IAAI;AACd,cAAM,IAAI,IAAI;AACd,cAAM,MAAM,QAAQ,GAAG,CAAC,IAAI,QAAQ,GAAG,CAAC;AACxC,cAAM,MAAM,QAAQ,GAAG,CAAC,IAAI,QAAQ,GAAG,CAAC;AACxC,WAAG,CAAC,IAAI,GAAG,CAAC,IAAI;AAChB,WAAG,CAAC,IAAI,GAAG,CAAC,IAAI;AAChB,WAAG,CAAC,KAAK;AACT,WAAG,CAAC,KAAK;AACT,cAAM,SAAS,QAAQ,MAAM,QAAQ;AACrC,gBAAQ,QAAQ,MAAM,QAAQ;AAC9B,gBAAQ;AAAA,MACV;AAAA,IACF;AAAA,EACF;AACF;AAKA,SAAS,OAAO,MAAsB;AACpC,SAAO,OAAS,KAAK,IAAI,IAAM,OAAO,GAAK;AAC7C;AAEA,SAAS,cAAc,KAAqB;AAC1C,SAAO,OAAS,KAAK,IAAI,MAAM,IAAM,IAAI;AAC3C;AAaA,SAAS,mBACP,SACA,SACA,YACA,SACA,UACa;AACb,QAAM,aAAa,UAAU,IAAI;AACjC,QAAM,SAAS,OAAO,OAAO;AAC7B,QAAM,UAAU,OAAO,QAAQ;AAG/B,QAAM,YAAY,IAAI,aAAa,UAAU,CAAC;AAC9C,WAAS,IAAI,GAAG,IAAI,UAAU,GAAG,KAAK;AACpC,cAAU,CAAC,IAAI,UAAU,UAAU,UAAU,KAAK,UAAU;AAAA,EAC9D;AAGA,QAAM,WAAW,IAAI,aAAa,UAAU,CAAC;AAC7C,WAAS,IAAI,GAAG,IAAI,UAAU,GAAG,KAAK;AACpC,aAAS,CAAC,IAAI,cAAc,UAAU,CAAC,CAAC,IAAI,UAAU;AAAA,EACxD;AAEA,QAAM,UAAuB,CAAC;AAE9B,WAAS,IAAI,GAAG,IAAI,SAAS,KAAK;AAChC,UAAM,OAAO,SAAS,CAAC;AACvB,UAAM,SAAS,SAAS,IAAI,CAAC;AAC7B,UAAM,QAAQ,SAAS,IAAI,CAAC;AAE5B,UAAM,WAAW,KAAK,IAAI,GAAG,KAAK,KAAK,IAAI,CAAC;AAC5C,UAAM,SAAS,KAAK,IAAI,aAAa,GAAG,KAAK,MAAM,KAAK,CAAC;AAEzD,UAAM,UAAU,IAAI,aAAa,SAAS,WAAW,CAAC;AACtD,aAAS,IAAI,UAAU,KAAK,QAAQ,KAAK;AACvC,UAAI,KAAK,QAAQ;AACf,gBAAQ,IAAI,QAAQ,IAAK,SAAS,OAAQ,KAAK,IAAI,SAAS,SAAS,QAAQ;AAAA,MAC/E,OAAO;AACL,gBAAQ,IAAI,QAAQ,IAAK,QAAQ,SAAU,KAAK,QAAQ,MAAM,QAAQ,UAAU;AAAA,MAClF;AAAA,IACF;AAEA,YAAQ,KAAK,EAAE,UAAU,QAAQ,CAAC;AAAA,EACpC;AAEA,SAAO;AACT;AAIA,SAAS,oBAAoB,QAA8B;AACzD,QAAME,UAAS,IAAI,aAAa,MAAM;AACtC,WAAS,IAAI,GAAG,IAAI,QAAQ,KAAK;AAC/B,IAAAA,QAAO,CAAC,IAAI,OAAO,OAAO,KAAK,IAAI,IAAI,KAAK,KAAK,KAAK,SAAS,EAAE;AAAA,EACnE;AACA,SAAOA;AACT;AA8BO,SAAS,kBACd,OACA,YACA,YACA,MACc;AACd,QAAM,gBAAgB,MAAM,iBAAiB;AAC7C,QAAM,eAAe,MAAM,gBAAgB;AAC3C,QAAM,UAAU,MAAM,WAAW;AACjC,QAAM,WAAW,MAAM,YAAa,aAAa;AACjD,QAAM,SAAS,MAAM,UAAU;AAC/B,QAAM,cAAc,MAAM,eAAe;AAEzC,QAAM,qBAAqB,KAAK,MAAM,aAAa,gBAAgB,GAAI;AACvE,QAAM,oBAAoB,KAAK,MAAM,aAAa,eAAe,GAAI;AAGrE,QAAM,SAAS,IAAI,aAAa,MAAM,MAAM;AAC5C,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,WAAO,CAAC,IAAI,MAAM,CAAC,IAAI;AAAA,EACzB;AAGA,MAAI,SAAS,GAAG;AACd,aAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AAEtC,YAAM,KAAK,KAAK,OAAO;AACvB,YAAM,KAAK,KAAK,OAAO;AACvB,aAAO,CAAC,KAAK,SAAS,KAAK,KAAK,KAAK,KAAK,IAAI,KAAK,KAAK,CAAC,IAAI,KAAK,IAAI,IAAI,KAAK,KAAK,EAAE;AAAA,IACxF;AAAA,EACF;AAGA,QAAM,YAAY,KAAK,IAAI,GAAG,KAAK,OAAO,OAAO,SAAS,sBAAsB,iBAAiB,IAAI,CAAC;AACtG,MAAI,cAAc,GAAG;AACnB,WAAO,IAAI,aAAa,CAAC;AAAA,EAC3B;AAGA,MAAI,UAAU;AACd,SAAO,UAAU,mBAAoB,YAAW;AAEhD,QAAM,aAAa,UAAU,IAAI;AAGjC,QAAMA,UAAS,oBAAoB,kBAAkB;AACrD,QAAM,UAAU,mBAAmB,YAAY,SAAS,YAAY,SAAS,QAAQ;AAGrF,QAAM,SAAS,IAAI,aAAa,YAAY,UAAU;AAGtD,QAAM,QAAQ,IAAI,aAAa,OAAO;AACtC,QAAM,QAAQ,IAAI,aAAa,OAAO;AAEtC,WAAS,IAAI,GAAG,IAAI,WAAW,KAAK;AAClC,UAAM,SAAS,IAAI;AAGnB,UAAM,KAAK,CAAC;AACZ,UAAM,KAAK,CAAC;AAGZ,aAAS,IAAI,GAAG,IAAI,oBAAoB,KAAK;AAC3C,UAAI,SAAS,OAAO,SAAS,CAAC;AAE9B,UAAI,cAAc,KAAK,IAAI,GAAG;AAC5B,kBAAU,cAAc,OAAO,SAAS,IAAI,CAAC;AAAA,MAC/C,WAAW,cAAc,KAAK,MAAM,KAAK,SAAS,GAAG;AACnD,kBAAU,cAAc,OAAO,SAAS,CAAC;AAAA,MAC3C;AAEA,YAAM,CAAC,IAAI,SAASA,QAAO,CAAC;AAAA,IAC9B;AAGA,QAAI,OAAO,KAAK;AAIhB,UAAM,YAAY,IAAI;AACtB,aAAS,IAAI,GAAG,IAAI,YAAY,KAAK;AACnC,YAAM,SAAS,QAAQ,CAAC;AACxB,UAAI,SAAS;AACb,eAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,QAAQ,KAAK;AAC9C,cAAM,MAAM,OAAO,WAAW;AAC9B,YAAI,MAAM,YAAY;AACpB,gBAAM,YAAY,MAAM,GAAG,IAAI,MAAM,GAAG,IAAI,MAAM,GAAG,IAAI,MAAM,GAAG;AAClE,oBAAU,OAAO,QAAQ,CAAC,IAAI;AAAA,QAChC;AAAA,MACF;AACA,aAAO,YAAY,CAAC,IAAI,KAAK,IAAI,KAAK,IAAI,QAAQ,KAAK,CAAC;AAAA,IAC1D;AAAA,EACF;AAEA,SAAO;AACT;AAgBO,SAAS,SACd,UACA,YACA,OAAe,GACf,OAAe,GACD;AACd,QAAM,YAAY,SAAS,SAAS;AACpC,MAAI,cAAc,EAAG,QAAO,IAAI,aAAa,CAAC;AAE9C,QAAM,UAAU,KAAK,OAAO,OAAO,KAAK,CAAC;AACzC,QAAM,YAAY,YAAY;AAC9B,QAAM,kBAAkB,KAAK,KAAK,YAAY,IAAI;AAClD,QAAM,YAAY,aAAa;AAE/B,QAAM,SAAS,IAAI,aAAa,kBAAkB,SAAS;AAE3D,WAAS,IAAI,GAAG,IAAI,iBAAiB,KAAK;AACxC,UAAM,aAAa,IAAI,OAAO;AAE9B,aAAS,IAAI,GAAG,IAAI,MAAM,KAAK;AAC7B,UAAI,WAAW,aAAa;AAE5B,UAAI,WAAW,EAAG,YAAW;AAC7B,UAAI,YAAY,UAAW,YAAW,YAAY;AAElD,YAAM,YAAY,WAAW;AAC7B,YAAM,YAAY,IAAI,YAAY,IAAI;AACtC,eAAS,IAAI,GAAG,IAAI,YAAY,KAAK;AACnC,eAAO,YAAY,CAAC,IAAI,SAAS,YAAY,CAAC;AAAA,MAChD;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAeO,SAAS,UACd,UACA,KACA,SACA,WACc;AACd,WAAS,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;AACxC,UAAM,IAAI,IAAI;AACd,aAAS,CAAC,KAAK,SAAS,CAAC,IAAI,QAAQ,CAAC,KAAK,UAAU,CAAC;AAAA,EACxD;AACA,SAAO;AACT;AAUO,SAAS,sBACd,YACA,cACoD;AACpD,QAAM,UAAU,IAAI;AAAA,IAClB,WAAW,MAAM,GAAG,EAAE,IAAI,OAAK,WAAW,EAAE,KAAK,CAAC,CAAC;AAAA,EACrD;AACA,QAAM,YAAY,IAAI;AAAA,IACpB,aAAa,MAAM,GAAG,EAAE,IAAI,OAAK,WAAW,EAAE,KAAK,CAAC,CAAC;AAAA,EACvD;AACA,SAAO,EAAE,SAAS,UAAU;AAC9B;;;ACrUO,SAAS,kBAAkB,UAA0B;AAC1D,QAAM,MAA8B;AAAA,IAClC,MAAM;AAAA,IACN,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,KAAK;AAAA,IACL,IAAI;AAAA,IACJ,IAAI;AAAA,EACN;AACA,SAAO,IAAI,QAAQ,KAAK;AAC1B;AAGO,SAAS,kBAAkB,UAA0B;AAC1D,SAAO,aAAa,gBAAgB,KAAK;AAC3C;AAQO,SAAS,gBAAgB,SAAsC;AACpE,QAAM,MAAM,oBAAI,IAAoB;AACpC,QAAM,QAAQ,QAAQ,MAAM,IAAI;AAChC,aAAW,QAAQ,OAAO;AACxB,UAAM,UAAU,KAAK,KAAK;AAC1B,QAAI,CAAC,QAAS;AAEd,UAAM,YAAY,QAAQ,YAAY,GAAG;AACzC,QAAI,cAAc,GAAI;AACtB,UAAM,QAAQ,QAAQ,UAAU,GAAG,SAAS;AAC5C,UAAM,KAAK,SAAS,QAAQ,UAAU,YAAY,CAAC,GAAG,EAAE;AACxD,QAAI,CAAC,MAAM,EAAE,GAAG;AACd,UAAI,IAAI,IAAI,KAAK;AAAA,IACnB;AAAA,EACF;AACA,SAAO;AACT;AAMA,SAAS,qBAAqB,OAAuD;AACnF,QAAM,QAAQ,MAAM,MAAM,cAAc;AACxC,MAAI,CAAC,MAAO,QAAO;AAEnB,QAAM,QAAQ,MAAM,CAAC;AAGrB,MAAI,UAAU,QAAQ,UAAU,QAAQ,UAAU,QAAQ,UAAU,QAAQ,UAAU,SAAS,UAAU,YAAY;AACnH,WAAO,EAAE,MAAM,YAAY,MAAM;AAAA,EACnC;AAGA,QAAM,WAAW,CAAC,SAAS,OAAO,SAAS,WAAW,WAAW,aAAa,aAAa,aAAa;AACxG,MAAI,SAAS,SAAS,KAAK,GAAG;AAC5B,WAAO,EAAE,MAAM,WAAW,MAAM;AAAA,EAClC;AAGA,QAAM,SAAS,CAAC,UAAU,OAAO,YAAY,YAAY,UAAU,YAAY,YAAY,eAAe;AAC1G,MAAI,OAAO,SAAS,KAAK,GAAG;AAC1B,WAAO,EAAE,MAAM,SAAS,MAAM;AAAA,EAChC;AAGA,MAAI,UAAU,aAAa,UAAU,WAAW,UAAU,cAAc,UAAU,eAAe;AAC/F,WAAO,EAAE,MAAM,YAAY,MAAM;AAAA,EACnC;AAEA,SAAO;AACT;AAWO,SAAS,gBACd,QACA,QACA,WACA,UACiB;AAEjB,QAAM,WAAqB,CAAC;AAC5B,WAAS,IAAI,GAAG,IAAI,QAAQ,KAAK;AAC/B,UAAM,SAAS,IAAI;AACnB,QAAI,SAAS;AACb,QAAI,SAAS,OAAO,MAAM;AAC1B,aAAS,IAAI,GAAG,IAAI,WAAW,KAAK;AAClC,UAAI,OAAO,SAAS,CAAC,IAAI,QAAQ;AAC/B,iBAAS,OAAO,SAAS,CAAC;AAC1B,iBAAS;AAAA,MACX;AAAA,IACF;AACA,aAAS,KAAK,MAAM;AAAA,EACtB;AAGA,QAAM,YAAsB,CAAC;AAC7B,MAAI,OAAO;AACX,aAAW,MAAM,UAAU;AACzB,QAAI,OAAO,MAAM;AACf,gBAAU,KAAK,EAAE;AACjB,aAAO;AAAA,IACT;AAAA,EACF;AAGA,QAAM,WAAW,UAAU,OAAO,QAAM,OAAO,KAAK,OAAO,KAAK,OAAO,CAAC;AAGxE,MAAI;AACJ,MAAI;AACJ,MAAI;AACJ,QAAM,aAAuB,CAAC;AAE9B,aAAW,MAAM,UAAU;AACzB,UAAM,QAAQ,SAAS,IAAI,EAAE;AAC7B,QAAI,CAAC,MAAO;AAEZ,UAAM,aAAa,qBAAqB,KAAK;AAC7C,QAAI,YAAY;AACd,UAAI,WAAW,SAAS,WAAY,YAAW,WAAW;AAAA,eACjD,WAAW,SAAS,UAAW,WAAU,WAAW;AAAA,eACpD,WAAW,SAAS,QAAS,SAAQ,WAAW;AAAA,IAE3D,OAAO;AACL,iBAAW,KAAK,KAAK;AAAA,IACvB;AAAA,EACF;AAGA,MAAI,OAAO,WAAW,KAAK,EAAE;AAE7B,SAAO,KAAK,QAAQ,WAAW,GAAG,EAAE,KAAK;AAEzC,SAAO,EAAE,MAAM,UAAU,SAAS,MAAM;AAC1C;;;ACrIA,IAAMC,UAAS,aAAa,YAAY;AA8CjC,IAAM,sBAAN,MAA0B;AAAA,EAe/B,YAAY,QAA0B;AAdtC,SAAQ,UAAmC;AAC3C,SAAQ,MAAwB;AAEhC,SAAQ,WAA2B;AACnC,SAAQ,YAAY;AACpB,SAAQ,iBAAgC,QAAQ,QAAQ;AAGxD;AAAA,SAAQ,WAAuC;AAC/C,SAAQ,UAA+B;AACvC,SAAQ,YAAiC;AACzC,SAAQ,aAAqB;AAC7B,SAAQ,aAAqB;AAI3B,UAAM,WAAW,OAAO,SAAS,UAAU,GAAG,OAAO,SAAS,YAAY,GAAG,CAAC;AAC9E,UAAM,YAAY,OAAO,aAAa,GAAG,QAAQ;AAEjD,SAAK,SAAS;AAAA,MACZ,UAAU,OAAO;AAAA,MACjB;AAAA,MACA,UAAU,OAAO,YAAY;AAAA,MAC7B,UAAU,OAAO,YAAY;AAAA,MAC7B,SAAS,OAAO,WAAW;AAAA,IAC7B;AAEA,SAAK,aAAa,kBAAkB,KAAK,OAAO,QAAQ;AACxD,SAAK,aAAa,kBAAkB,KAAK,OAAO,QAAQ;AAAA,EAC1D;AAAA,EAEA,IAAI,UAAiC;AACnC,WAAO,KAAK,UAAU,KAAK,WAAW;AAAA,EACxC;AAAA,EAEA,IAAI,WAAoB;AACtB,WAAO,KAAK,YAAY;AAAA,EAC1B;AAAA;AAAA,EAIA,MAAM,KAAK,YAAoF;AAC7F,QAAI,KAAK,WAAW;AAClB,YAAM,IAAI,MAAM,0BAA0B;AAAA,IAC5C;AACA,QAAI,KAAK,SAAS;AAChB,YAAM,IAAI,MAAM,6CAA6C;AAAA,IAC/D;AAEA,SAAK,YAAY;AACjB,UAAM,YAAY,YAAY,IAAI;AAClC,UAAM,YAAY,aAAa;AAC/B,UAAM,OAAO,WAAW,UAAU,mBAAmB;AAAA,MACnD,aAAa,KAAK,OAAO;AAAA,MACzB,2BAA2B,KAAK,OAAO;AAAA,IACzC,CAAC;AAED,QAAI;AAEF,MAAAA,QAAO,KAAK,2BAA2B,EAAE,YAAY,KAAK,OAAO,QAAQ,CAAC;AAC1E,YAAM,EAAE,KAAK,QAAQ,IAAI,MAAM,4BAA4B,KAAK,OAAO,OAAO;AAC9E,WAAK,MAAM;AACX,WAAK,WAAW;AAChB,MAAAA,QAAO,KAAK,uBAAuB,EAAE,SAAS,KAAK,SAAS,CAAC;AAG7D,MAAAA,QAAO,MAAM,8BAA8B,EAAE,WAAW,KAAK,OAAO,UAAU,CAAC;AAC/E,YAAM,iBAAiB,MAAM,MAAM,KAAK,OAAO,SAAS;AACxD,UAAI,CAAC,eAAe,IAAI;AACtB,cAAM,IAAI,MAAM,+BAA+B,eAAe,MAAM,IAAI,eAAe,UAAU,EAAE;AAAA,MACrG;AACA,YAAM,aAAa,MAAM,eAAe,KAAK;AAC7C,WAAK,WAAW,gBAAgB,UAAU;AAC1C,MAAAA,QAAO,MAAM,iBAAiB,EAAE,WAAW,KAAK,SAAS,KAAK,CAAC;AAG/D,YAAM,iBAAiB,kBAAkB,KAAK,QAAQ;AAQtD,UAAI,KAAK,aAAa,UAAU;AAC9B,uBAAe,yBAAyB;AAAA,MAC1C;AAEA,UAAI,WAAW;AAKf,UAAI,MAAM,GAAG;AACX,QAAAA,QAAO,KAAK,4DAA4D;AAAA,UACtE,UAAU,KAAK,OAAO;AAAA,QACxB,CAAC;AACD,aAAK,UAAU,MAAM,KAAK,IAAI,iBAAiB;AAAA,UAC7C,KAAK,OAAO;AAAA,UAAU;AAAA,QACxB;AAAA,MACF,OAAO;AACL,cAAM,QAAQ,cAAc;AAC5B,mBAAW,MAAM,MAAM,IAAI,KAAK,OAAO,QAAQ;AAE/C,YAAI;AACJ,YAAI,UAAU;AACZ,UAAAA,QAAO,MAAM,4BAA4B,EAAE,UAAU,KAAK,OAAO,SAAS,CAAC;AAC3E,wBAAe,MAAM,MAAM,IAAI,KAAK,OAAO,QAAQ;AACnD,uBAAa,YAAY,YAAY,YAAY,UAAU;AAAA,QAC7D,OAAO;AACL,UAAAA,QAAO,MAAM,8BAA8B,EAAE,UAAU,KAAK,OAAO,SAAS,CAAC;AAC7E,wBAAc,MAAM,eAAe,KAAK,OAAO,UAAU,UAAU;AAAA,QACrE;AAEA,QAAAA,QAAO,MAAM,yBAAyB;AAAA,UACpC,MAAM,YAAY,YAAY,UAAU;AAAA,UACxC,SAAS,KAAK;AAAA,QAChB,CAAC;AAED,cAAM,YAAY,IAAI,WAAW,WAAW;AAC5C,aAAK,UAAU,MAAM,KAAK,IAAI,iBAAiB,OAAO,WAAW,cAAc;AAAA,MACjF;AAGA,UAAI;AACF,cAAM,WAAY,KAAK,QAAgB,SAAS;AAChD,YAAI,UAAU,YAAY,UAAU,YAAY;AAC9C,gBAAM,OAAO,sBAAsB,SAAS,UAAU,SAAS,UAAU;AACzE,eAAK,UAAU,KAAK;AACpB,eAAK,YAAY,KAAK;AACtB,UAAAA,QAAO,MAAM,mCAAmC,EAAE,KAAK,KAAK,QAAQ,OAAO,CAAC;AAAA,QAC9E,OAAO;AACL,UAAAA,QAAO,KAAK,yEAAoE;AAAA,QAClF;AAAA,MACF,SAAS,SAAS;AAChB,QAAAA,QAAO,KAAK,2CAA2C,EAAE,OAAO,QAAQ,CAAC;AAAA,MAC3E;AAEA,YAAM,aAAa,YAAY,IAAI,IAAI;AAEvC,MAAAA,QAAO,KAAK,2BAA2B;AAAA,QACrC,SAAS,KAAK;AAAA,QACd,YAAY,KAAK,MAAM,UAAU;AAAA,QACjC,WAAW,KAAK,SAAS;AAAA,QACzB,QAAQ,KAAK,QAAQ;AAAA,QACrB,SAAS,KAAK,QAAQ;AAAA,QACtB,SAAS,KAAK,YAAY;AAAA,MAC5B,CAAC;AAED,YAAM,cAAc;AAAA,QAClB,iBAAiB,KAAK;AAAA,QACtB,sBAAsB;AAAA,QACtB,gBAAgB,CAAC,MAAM,KAAK;AAAA,QAC5B,oBAAoB,KAAK,SAAS;AAAA,MACpC,CAAC;AACD,YAAM,IAAI;AAEV,iBAAW,gBAAgB,yBAAyB,YAAY;AAAA,QAC9D,OAAO;AAAA,QACP,SAAS,KAAK;AAAA,MAChB,CAAC;AAED,aAAO;AAAA,QACL,SAAS,KAAK;AAAA,QACd;AAAA,QACA,YAAY,CAAC,GAAG,KAAK,QAAQ,UAAU;AAAA,QACvC,aAAa,CAAC,GAAG,KAAK,QAAQ,WAAW;AAAA,QACzC,WAAW,KAAK,SAAS;AAAA,MAC3B;AAAA,IACF,SAAS,OAAO;AACd,YAAM,aAAa,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC,CAAC;AAC5E,iBAAW,iBAAiB,sBAAsB,GAAG;AAAA,QACnD,OAAO;AAAA,QACP,YAAY;AAAA,MACd,CAAC;AACD,YAAM;AAAA,IACR,UAAE;AACA,WAAK,YAAY;AAAA,IACnB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAM,WAAW,cAAuD;AACtE,QAAI,CAAC,KAAK,WAAW,CAAC,KAAK,OAAO,CAAC,KAAK,UAAU;AAChD,YAAM,IAAI,MAAM,sCAAsC;AAAA,IACxD;AAGA,UAAM,QAAQ,IAAI,aAAa,YAAY;AAE3C,WAAO,KAAK,eAAe,KAAK;AAAA,EAClC;AAAA,EAEQ,eAAe,OAAgD;AACrE,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,WAAK,iBAAiB,KAAK,eAAe,KAAK,YAAY;AACzD,cAAM,YAAY,aAAa;AAC/B,cAAM,OAAO,WAAW,UAAU,yBAAyB;AAAA,UACzD,qBAAqB,KAAK;AAAA,UAC1B,2BAA2B,MAAM;AAAA,QACnC,CAAC;AAED,YAAI;AACF,gBAAM,YAAY,YAAY,IAAI;AAGlC,gBAAM,kBAAkB,YAAY,IAAI;AAGxC,gBAAM,QAAQ,kBAAkB,OAAO,MAAO,EAAE;AAChD,gBAAM,YAAY,MAAM,SAAS;AAEjC,cAAI,cAAc,GAAG;AACnB,oBAAQ;AAAA,cACN,MAAM;AAAA,cACN,iBAAiB,YAAY,IAAI,IAAI;AAAA,cACrC,kBAAkB,YAAY,IAAI,IAAI;AAAA,YACxC,CAAC;AACD;AAAA,UACF;AAGA,gBAAM,cAAc,SAAS,OAAO,IAAI,GAAG,CAAC;AAC5C,gBAAM,eAAe,YAAY,SAAS;AAG1C,cAAI,KAAK,WAAW,KAAK,WAAW;AAClC,sBAAU,aAAa,KAAK,KAAK,SAAS,KAAK,SAAS;AAAA,UAC1D;AAEA,gBAAM,mBAAmB,YAAY,IAAI,IAAI;AAG7C,gBAAM,MAAM,KAAK;AACjB,gBAAM,QAAyD;AAAA,YAC7D,GAAG,IAAI,IAAI,OAAO,WAAW,aAAa,CAAC,GAAG,cAAc,GAAG,CAAC;AAAA,YAChE,UAAU,IAAI,IAAI,OAAO,SAAS,IAAI,WAAW,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC;AAAA,YACrE,UAAU,IAAI,IAAI,OAAO,SAAS,IAAI,WAAW,CAAC,KAAK,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;AAAA,YACxE,WAAW,IAAI,IAAI,OAAO,SAAS,IAAI,WAAW,CAAC,KAAK,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;AAAA,UAC3E;AAGA,gBAAM,UAAU,MAAM,KAAK,QAAS,IAAI,KAAK;AAE7C,gBAAM,eAAe,QAAQ,QAAQ;AACrC,cAAI,CAAC,cAAc;AACjB,kBAAM,IAAI,MAAM,sCAAsC;AAAA,UACxD;AAEA,gBAAM,aAAa,aAAa;AAChC,gBAAM,aAAa,aAAa;AAChC,gBAAM,SAAS,WAAW,CAAC;AAC3B,gBAAM,YAAY,WAAW,CAAC;AAG9B,gBAAM,UAAU,gBAAgB,YAAY,QAAQ,WAAW,KAAK,QAAS;AAE7E,gBAAM,kBAAkB,YAAY,IAAI,IAAI;AAE5C,UAAAA,QAAO,MAAM,0BAA0B;AAAA,YACrC,MAAM,QAAQ,KAAK,UAAU,GAAG,EAAE;AAAA,YAClC,UAAU,QAAQ;AAAA,YAClB,SAAS,QAAQ;AAAA,YACjB,OAAO,QAAQ;AAAA,YACf,kBAAkB,KAAK,MAAM,mBAAmB,GAAG,IAAI;AAAA,YACvD,iBAAiB,KAAK,MAAM,kBAAkB,GAAG,IAAI;AAAA,YACrD;AAAA,YACA;AAAA,UACF,CAAC;AAED,gBAAM,cAAc;AAAA,YAClB,yBAAyB;AAAA,YACzB,2BAA2B;AAAA,YAC3B,wBAAwB;AAAA,YACxB,yBAAyB,QAAQ,KAAK;AAAA,UACxC,CAAC;AACD,gBAAM,IAAI;AAEV,qBAAW,gBAAgB,2BAA2B,iBAAiB;AAAA,YACrE,OAAO;AAAA,YACP,SAAS,KAAK;AAAA,UAChB,CAAC;AACD,qBAAW,iBAAiB,yBAAyB,GAAG;AAAA,YACtD,OAAO;AAAA,YACP,SAAS,KAAK;AAAA,YACd,QAAQ;AAAA,UACV,CAAC;AAED,kBAAQ;AAAA,YACN,MAAM,QAAQ;AAAA,YACd,UAAU,QAAQ;AAAA,YAClB,SAAS,QAAQ;AAAA,YACjB,OAAO,QAAQ;AAAA,YACf;AAAA,YACA;AAAA,UACF,CAAC;AAAA,QACH,SAAS,KAAK;AACZ,gBAAM,aAAa,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC,CAAC;AACtE,qBAAW,iBAAiB,yBAAyB,GAAG;AAAA,YACtD,OAAO;AAAA,YACP,SAAS,KAAK;AAAA,YACd,QAAQ;AAAA,UACV,CAAC;AACD,iBAAO,GAAG;AAAA,QACZ;AAAA,MACF,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA;AAAA,EAIA,MAAM,UAAyB;AAC7B,QAAI,KAAK,SAAS;AAChB,YAAM,KAAK,QAAQ,QAAQ;AAC3B,WAAK,UAAU;AAAA,IACjB;AACA,SAAK,MAAM;AACX,SAAK,WAAW;AAChB,SAAK,UAAU;AACf,SAAK,YAAY;AAAA,EACnB;AACF;;;ACpXA,IAAMC,UAAS,aAAa,cAAc;AAgBnC,IAAM,wBAAN,MAAsD;AAAA,EAY3D,YAAY,QAA4B;AAXxC,SAAS,UAAU;AAEnB,SAAQ,UAAmC;AAC3C,SAAQ,MAAwB;AAEhC,SAAQ,WAA2B;AACnC,SAAQ,YAAY;AAGpB;AAAA,SAAQ,iBAAgC,QAAQ,QAAQ;AAGtD,SAAK,SAAS;AAAA,EAChB;AAAA,EAEA,IAAI,UAAiC;AACnC,WAAO,KAAK,UAAU,KAAK,WAAW;AAAA,EACxC;AAAA,EAEA,IAAI,WAAoB;AACtB,WAAO,KAAK,YAAY;AAAA,EAC1B;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,OAAkC;AACtC,QAAI,KAAK,WAAW;AAClB,YAAM,IAAI,MAAM,0BAA0B;AAAA,IAC5C;AAEA,QAAI,KAAK,SAAS;AAChB,YAAM,IAAI,MAAM,6CAA6C;AAAA,IAC/D;AAEA,SAAK,YAAY;AACjB,UAAM,YAAY,YAAY,IAAI;AAClC,UAAM,YAAY,aAAa;AAC/B,UAAM,OAAO,WAAW,UAAU,qBAAqB;AAAA,MACrD,aAAa,KAAK,OAAO;AAAA,MACzB,2BAA2B,KAAK,OAAO,WAAW;AAAA,IACpD,CAAC;AAED,QAAI;AAEF,YAAM,aAAa,KAAK,OAAO,WAAW;AAC1C,MAAAA,QAAO,KAAK,2BAA2B,EAAE,WAAW,CAAC;AAErD,YAAM,EAAE,KAAK,QAAQ,IAAI,MAAM,4BAA4B,UAAU;AACrE,WAAK,MAAM;AACX,WAAK,WAAW;AAEhB,MAAAA,QAAO,KAAK,uBAAuB,EAAE,SAAS,KAAK,SAAS,CAAC;AAE7D,YAAM,WAAW,KAAK,OAAO;AAC7B,YAAM,UAAU,KAAK,OAAO,oBAAoB,QAC3C,KAAK,OAAO,mBAAmB,GAAG,QAAQ,UAC3C;AACJ,YAAM,iBAAiB,kBAAkB,KAAK,QAAQ;AAMtD,UAAI,MAAM,GAAG;AACX,QAAAA,QAAO,KAAK,6DAA6D;AAAA,UACvE;AAAA,UACA;AAAA,QACF,CAAC;AAED,YAAI,SAAS;AACX,gBAAM,eAAe,QAAQ,MAAM,GAAG,EAAE,IAAI;AAC5C,UAAC,eAA2C,eAAe,CAAC;AAAA,YAC1D,MAAM;AAAA,YACN,MAAM;AAAA;AAAA,UACR,CAAC;AAAA,QACH;AAEA,aAAK,UAAU,MAAM,KAAK,IAAK,iBAAiB,OAAO,UAAU,cAAc;AAAA,MACjF,OAAO;AAEL,cAAM,QAAQ,cAAc;AAC5B,cAAM,WAAW,MAAM,MAAM,IAAI,QAAQ;AAEzC,YAAI;AACJ,YAAI,UAAU;AACZ,UAAAA,QAAO,MAAM,4BAA4B,EAAE,SAAS,CAAC;AACrD,wBAAe,MAAM,MAAM,IAAI,QAAQ;AAEvC,cAAI,CAAC,aAAa;AAChB,YAAAA,QAAO,KAAK,oDAAoD,EAAE,SAAS,CAAC;AAC5E,kBAAM,MAAM,OAAO,QAAQ;AAC3B,0BAAc,MAAM,eAAe,QAAQ;AAAA,UAC7C;AAAA,QACF,OAAO;AACL,UAAAA,QAAO,MAAM,oCAAoC,EAAE,SAAS,CAAC;AAC7D,wBAAc,MAAM,eAAe,QAAQ;AAAA,QAC7C;AAEA,YAAI,CAAC,aAAa;AAChB,gBAAM,IAAI,MAAM,yBAAyB,QAAQ,EAAE;AAAA,QACrD;AAGA,YAAI,qBAAyC;AAC7C,YAAI,SAAS;AACX,cAAI;AACF,kBAAM,eAAe,MAAM,MAAM,IAAI,OAAO;AAC5C,gBAAI,cAAc;AAChB,cAAAA,QAAO,MAAM,oCAAoC,EAAE,QAAQ,CAAC;AAC5D,mCAAsB,MAAM,MAAM,IAAI,OAAO;AAC7C,kBAAI,CAAC,oBAAoB;AACvB,gBAAAA,QAAO,KAAK,gDAAgD,EAAE,QAAQ,CAAC;AACvE,sBAAM,MAAM,OAAO,OAAO;AAC1B,qCAAqB,MAAM,eAAe,OAAO;AAAA,cACnD;AAAA,YACF,OAAO;AACL,cAAAA,QAAO,KAAK,gCAAgC;AAAA,gBAC1C;AAAA,gBACA,MAAM;AAAA,cACR,CAAC;AACD,mCAAqB,MAAM,eAAe,OAAO;AAAA,YACnD;AACA,YAAAA,QAAO,KAAK,wBAAwB;AAAA,cAClC,MAAM,YAAY,mBAAmB,UAAU;AAAA,YACjD,CAAC;AAAA,UACH,SAAS,KAAK;AACZ,YAAAA,QAAO,MAAM,mDAAmD;AAAA,cAC9D;AAAA,cACA,OAAQ,IAAc;AAAA,YACxB,CAAC;AAAA,UACH;AAAA,QACF;AAEA,QAAAA,QAAO,MAAM,yBAAyB;AAAA,UACpC,WAAW,YAAY,YAAY,UAAU;AAAA,UAC7C,kBAAkB,qBAAqB,YAAY,mBAAmB,UAAU,IAAI;AAAA,UACpF,SAAS,KAAK;AAAA,QAChB,CAAC;AAGD,YAAI,oBAAoB;AACtB,gBAAM,eAAe,QAAS,MAAM,GAAG,EAAE,IAAI;AAC7C,UAAC,eAA2C,eAAe,CAAC;AAAA,YAC1D,MAAM;AAAA,YACN,MAAM,IAAI,WAAW,kBAAkB;AAAA,UACzC,CAAC;AAAA,QACH;AAEA,cAAM,YAAY,IAAI,WAAW,WAAW;AAC5C,aAAK,UAAU,MAAM,KAAK,IAAK,iBAAiB,OAAO,WAAW,cAAc;AAAA,MAClF;AAEA,YAAM,aAAa,YAAY,IAAI,IAAI;AAEvC,MAAAA,QAAO,KAAK,6BAA6B;AAAA,QACvC,SAAS,KAAK;AAAA,QACd,YAAY,KAAK,MAAM,UAAU;AAAA,QACjC,QAAQ,KAAK,QAAQ;AAAA,QACrB,SAAS,KAAK,QAAQ;AAAA,MACxB,CAAC;AAED,YAAM,cAAc;AAAA,QAClB,iBAAiB,KAAK;AAAA,QACtB,sBAAsB;AAAA,QACtB,gBAAgB,CAAC,MAAM;AAAA,MACzB,CAAC;AACD,YAAM,IAAI;AACV,iBAAW,gBAAgB,yBAAyB,YAAY;AAAA,QAC9D,OAAO;AAAA,QACP,SAAS,KAAK;AAAA,MAChB,CAAC;AAGD,MAAAA,QAAO,MAAM,0BAA0B;AACvC,YAAM,cAAc,YAAY,IAAI;AACpC,YAAM,cAAc,IAAI,aAAa,IAAK;AAC1C,YAAM,KAAK,MAAM,WAAW;AAC5B,YAAM,eAAe,YAAY,IAAI,IAAI;AACzC,MAAAA,QAAO,KAAK,6BAA6B;AAAA,QACvC,cAAc,KAAK,MAAM,YAAY;AAAA,QACrC,SAAS,KAAK;AAAA,MAChB,CAAC;AACD,iBAAW,gBAAgB,2BAA2B,cAAc;AAAA,QAClE,OAAO;AAAA,QACP,SAAS,KAAK;AAAA,MAChB,CAAC;AAED,aAAO;AAAA,QACL,SAAS,KAAK;AAAA,QACd;AAAA,QACA,YAAY,CAAC,GAAG,KAAK,QAAQ,UAAU;AAAA,QACvC,aAAa,CAAC,GAAG,KAAK,QAAQ,WAAW;AAAA,MAC3C;AAAA,IACF,SAAS,OAAO;AACd,YAAM,aAAa,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC,CAAC;AAC5E,iBAAW,iBAAiB,sBAAsB,GAAG;AAAA,QACnD,OAAO;AAAA,QACP,YAAY;AAAA,MACd,CAAC;AACD,YAAM;AAAA,IACR,UAAE;AACA,WAAK,YAAY;AAAA,IACnB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,MAAM,MACJ,cACA,gBACwB;AACxB,QAAI,CAAC,KAAK,SAAS;AACjB,YAAM,IAAI,MAAM,sCAAsC;AAAA,IACxD;AAGA,UAAM,YAAY,IAAI,aAAa,YAAY;AAE/C,UAAM,QAAQ;AAAA,MACZ,kBAAkB,IAAI,KAAK,IAAK,OAAO,WAAW,WAAW,CAAC,GAAG,UAAU,MAAM,CAAC;AAAA,IACpF;AAEA,WAAO,KAAK,eAAe,OAAO,UAAU,MAAM;AAAA,EACpD;AAAA;AAAA;AAAA;AAAA,EAKQ,eACN,OACA,cACwB;AACxB,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,WAAK,iBAAiB,KAAK,eAAe,KAAK,YAAY;AACzD,cAAM,YAAY,aAAa;AAC/B,cAAM,OAAO,WAAW,UAAU,sBAAsB;AAAA,UACtD,qBAAqB,KAAK;AAAA,UAC1B,2BAA2B;AAAA,QAC7B,CAAC;AACD,YAAI;AACF,gBAAM,YAAY,YAAY,IAAI;AAClC,gBAAM,UAAU,MAAM,KAAK,QAAS,IAAI,KAAK;AAC7C,gBAAM,kBAAkB,YAAY,IAAI,IAAI;AAE5C,gBAAM,mBAAmB,QAAQ,aAAa;AAE9C,cAAI,CAAC,kBAAkB;AACrB,kBAAM,IAAI,MAAM,uCAAuC;AAAA,UACzD;AAEA,gBAAM,iBAAiB,iBAAiB;AACxC,gBAAM,YAAY,iBAAiB,KAAK,CAAC;AACzC,gBAAM,iBAAiB,iBAAiB,KAAK,CAAC;AAI9C,gBAAM,cAA8B,CAAC;AACrC,mBAAS,IAAI,GAAG,IAAI,WAAW,KAAK;AAClC,kBAAM,WAAW,eAAe,MAAM,IAAI,iBAAiB,IAAI,KAAK,cAAc;AAClF,kBAAM,cAAc,sBAAsB,QAAQ;AAClD,wBAAY,KAAK,WAAW;AAAA,UAC9B;AAEA,UAAAA,QAAO,MAAM,uBAAuB;AAAA,YAClC,iBAAiB,KAAK,MAAM,kBAAkB,GAAG,IAAI;AAAA,YACrD;AAAA,YACA;AAAA,UACF,CAAC;AAED,gBAAM,cAAc;AAAA,YAClB,yBAAyB;AAAA,YACzB,oBAAoB;AAAA,UACtB,CAAC;AACD,gBAAM,IAAI;AACV,qBAAW,gBAAgB,2BAA2B,iBAAiB;AAAA,YACrE,OAAO;AAAA,YACP,SAAS,KAAK;AAAA,UAChB,CAAC;AACD,qBAAW,iBAAiB,yBAAyB,GAAG;AAAA,YACtD,OAAO;AAAA,YACP,SAAS,KAAK;AAAA,YACd,QAAQ;AAAA,UACV,CAAC;AAED,kBAAQ;AAAA,YACN;AAAA,YACA;AAAA,YACA;AAAA,UACF,CAAC;AAAA,QACH,SAAS,KAAK;AACZ,gBAAM,aAAa,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC,CAAC;AACtE,qBAAW,iBAAiB,yBAAyB,GAAG;AAAA,YACtD,OAAO;AAAA,YACP,SAAS,KAAK;AAAA,YACd,QAAQ;AAAA,UACV,CAAC;AACD,iBAAO,GAAG;AAAA,QACZ;AAAA,MACF,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,UAAyB;AAC7B,QAAI,KAAK,SAAS;AAChB,YAAM,KAAK,QAAQ,QAAQ;AAC3B,WAAK,UAAU;AAAA,IACjB;AAAA,EACF;AACF;;;ACpVA,IAAMC,UAAS,aAAa,eAAe;AAyCpC,SAAS,cAAc,QAA6C;AACzE,QAAM,OAAO,OAAO,QAAQ;AAC5B,QAAM,kBAAkB,OAAO,mBAAmB;AAGlD,MAAI;AAEJ,MAAI,SAAS,OAAO;AAClB,aAAS;AACT,IAAAA,QAAO,KAAK,4CAA4C;AAAA,EAC1D,WAAW,SAAS,OAAO;AACzB,aAAS;AACT,IAAAA,QAAO,KAAK,uCAAuC;AAAA,EACrD,OAAO;AAEL,aAAS,oBAAoB;AAC7B,IAAAA,QAAO,KAAK,gCAAgC;AAAA,MAC1C;AAAA,MACA,UAAU,SAAS;AAAA,IACrB,CAAC;AAAA,EACH;AAEA,MAAI,QAAQ;AACV,IAAAA,QAAO,KAAK,8CAA8C;AAC1D,WAAO,IAAI,sBAAsB;AAAA,MAC/B,UAAU,OAAO;AAAA,IACnB,CAAC;AAAA,EACH;AAGA,QAAM,cAAc,IAAI,kBAAkB;AAAA,IACxC,UAAU,OAAO;AAAA,IACjB,iBAAiB,OAAO;AAAA,IACxB,SAAS,OAAO,cAAc;AAAA,IAC9B,oBAAoB,OAAO;AAAA,EAC7B,CAAC;AAED,MAAI,iBAAiB;AACnB,IAAAA,QAAO,KAAK,8CAA8C;AAC1D,WAAO,IAAI,oBAAoB,aAAa,MAAM;AAAA,EACpD;AAEA,EAAAA,QAAO,KAAK,0CAA0C;AACtD,SAAO;AACT;AAQA,IAAM,sBAAN,MAAoD;AAAA,EAKlD,YAAY,aAAgC,QAA6B;AAFzE,SAAQ,gBAAgB;AAGtB,SAAK,iBAAiB;AACtB,SAAK,SAAS;AAAA,EAChB;AAAA,EAEA,IAAI,UAAwC;AAC1C,WAAO,KAAK,eAAe;AAAA,EAC7B;AAAA,EAEA,IAAI,UAAiC;AACnC,WAAO,KAAK,eAAe;AAAA,EAC7B;AAAA,EAEA,IAAI,WAAoB;AACtB,WAAO,KAAK,eAAe;AAAA,EAC7B;AAAA,EAEA,MAAM,OAAkC;AACtC,QAAI;AACF,aAAO,MAAM,KAAK,eAAe,KAAK;AAAA,IACxC,SAAS,OAAO;AACd,aAAO,KAAK,cAAc,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;AAAA,IAClF;AAAA,EACF;AAAA,EAEA,MAAc,cAAc,QAA2C;AACrE,IAAAA,QAAO,KAAK,oDAAoD,EAAE,OAAO,CAAC;AAG1E,QAAI;AACF,YAAM,KAAK,eAAe,QAAQ;AAAA,IACpC,QAAQ;AAAA,IAER;AAGA,SAAK,iBAAiB,IAAI,sBAAsB;AAAA,MAC9C,UAAU,KAAK,OAAO;AAAA,IACxB,CAAC;AACD,SAAK,gBAAgB;AAErB,IAAAA,QAAO,KAAK,8CAA8C;AAC1D,WAAO,MAAM,KAAK,eAAe,KAAK;AAAA,EACxC;AAAA,EAEA,MAAM,MAAM,cAA4B,eAAgD;AACtF,WAAO,KAAK,eAAe,MAAM,cAAc,aAAa;AAAA,EAC9D;AAAA,EAEA,MAAM,UAAyB;AAC7B,WAAO,KAAK,eAAe,QAAQ;AAAA,EACrC;AACF;;;AC1IA,IAAMC,UAAS,aAAa,WAAW;AAkFhC,IAAM,qBAAN,MAAyB;AAAA,EA2B9B,YAAY,QAAyB;AA1BrC,SAAQ,UAAmC;AAC3C,SAAQ,MAAwB;AAEhC,SAAQ,WAA2B;AACnC,SAAQ,YAAY;AAGpB;AAAA,SAAQ,QAAuB;AAU/B;AAAA,SAAQ,iBAAgC,QAAQ,QAAQ;AAGxD;AAAA,SAAQ,kBAAkC,CAAC;AAC3C,SAAQ,cAAc;AAGtB;AAAA,SAAQ,WAA0B;AAGhC,UAAM,aAAa,OAAO,cAAc;AAExC,QAAI,eAAe,OAAQ,eAAe,MAAO;AAC/C,YAAM,IAAI,MAAM,wDAAwD;AAAA,IAC1E;AAEA,SAAK,SAAS;AAAA,MACZ,UAAU,OAAO;AAAA,MACjB,SAAS,OAAO,WAAW;AAAA,MAC3B;AAAA,MACA,WAAW,OAAO,aAAa;AAAA,MAC/B,uBAAuB,OAAO,yBAAyB;AAAA,IACzD;AAGA,SAAK,YAAY,eAAe,OAAQ,MAAM;AAC9C,SAAK,cAAc,eAAe,OAAQ,KAAK;AAC/C,SAAK,UAAU,IAAI,aAAa,KAAK,WAAW;AAAA,EAClD;AAAA,EAEA,IAAI,UAAiC;AACnC,WAAO,KAAK,UAAU,KAAK,WAAW;AAAA,EACxC;AAAA,EAEA,IAAI,WAAoB;AACtB,WAAO,KAAK,YAAY;AAAA,EAC1B;AAAA,EAEA,IAAI,aAAqB;AACvB,WAAO,KAAK,OAAO;AAAA,EACrB;AAAA,EAEA,IAAI,YAAoB;AACtB,WAAO,KAAK,OAAO;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA,EAKA,eAAuB;AACrB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,qBAA6B;AAC3B,WAAQ,KAAK,YAAY,KAAK,OAAO,aAAc;AAAA,EACrD;AAAA;AAAA;AAAA;AAAA,EAWA,MAAM,OAA8B;AAClC,QAAI,KAAK,WAAW;AAClB,YAAM,IAAI,MAAM,0BAA0B;AAAA,IAC5C;AAEA,QAAI,KAAK,SAAS;AAChB,YAAM,IAAI,MAAM,6CAA6C;AAAA,IAC/D;AAEA,SAAK,YAAY;AACjB,UAAM,YAAY,YAAY,IAAI;AAClC,UAAM,YAAY,aAAa;AAC/B,UAAM,OAAO,WAAW,UAAU,kBAAkB;AAAA,MAClD,aAAa,KAAK,OAAO;AAAA,MACzB,2BAA2B,KAAK,OAAO;AAAA,MACvC,qBAAqB,KAAK,OAAO;AAAA,IACnC,CAAC;AAED,QAAI;AAIF,MAAAA,QAAO,KAAK,2BAA2B,EAAE,YAAY,KAAK,OAAO,QAAQ,CAAC;AAE1E,YAAM,EAAE,KAAK,QAAQ,IAAI,MAAM,4BAA4B,KAAK,OAAO,OAAO;AAC9E,WAAK,MAAM;AACX,WAAK,WAAW;AAEhB,MAAAA,QAAO,KAAK,uBAAuB,EAAE,SAAS,KAAK,SAAS,CAAC;AAG7D,YAAM,QAAQ,cAAc;AAC5B,YAAM,WAAW,KAAK,OAAO;AAC7B,YAAM,WAAW,MAAM,MAAM,IAAI,QAAQ;AAEzC,UAAI;AACJ,UAAI,UAAU;AACZ,QAAAA,QAAO,MAAM,4BAA4B,EAAE,SAAS,CAAC;AACrD,sBAAe,MAAM,MAAM,IAAI,QAAQ;AAAA,MACzC,OAAO;AACL,QAAAA,QAAO,MAAM,8BAA8B,EAAE,SAAS,CAAC;AACvD,sBAAc,MAAM,eAAe,QAAQ;AAAA,MAC7C;AAEA,MAAAA,QAAO,MAAM,yBAAyB;AAAA,QACpC,MAAM,YAAY,YAAY,UAAU;AAAA,QACxC,SAAS,KAAK;AAAA,MAChB,CAAC;AAID,YAAM,iBAAiB,kBAAkB,KAAK,QAAQ;AACtD,YAAM,YAAY,IAAI,WAAW,WAAW;AAC5C,WAAK,UAAU,MAAM,IAAI,iBAAiB,OAAO,WAAW,cAAc;AAG1E,WAAK,MAAM;AAEX,YAAM,aAAa,YAAY,IAAI,IAAI;AAEvC,MAAAA,QAAO,KAAK,6BAA6B;AAAA,QACvC,SAAS,KAAK;AAAA,QACd,YAAY,KAAK,MAAM,UAAU;AAAA,QACjC,YAAY,KAAK,OAAO;AAAA,QACxB,WAAW,KAAK;AAAA,QAChB,WAAW,KAAK,OAAO;AAAA,MACzB,CAAC;AAED,YAAM,cAAc;AAAA,QAClB,iBAAiB,KAAK;AAAA,QACtB,sBAAsB;AAAA,QACtB,gBAAgB;AAAA,MAClB,CAAC;AACD,YAAM,IAAI;AACV,iBAAW,gBAAgB,yBAAyB,YAAY;AAAA,QAC9D,OAAO;AAAA,QACP,SAAS,KAAK;AAAA,MAChB,CAAC;AAED,aAAO;AAAA,QACL,SAAS,KAAK;AAAA,QACd;AAAA,QACA,YAAY,CAAC,GAAG,KAAK,QAAQ,UAAU;AAAA,QACvC,aAAa,CAAC,GAAG,KAAK,QAAQ,WAAW;AAAA,QACzC,YAAY,KAAK,OAAO;AAAA,QACxB,WAAW,KAAK;AAAA,MAClB;AAAA,IACF,SAAS,OAAO;AACd,YAAM,aAAa,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC,CAAC;AAC5E,iBAAW,iBAAiB,sBAAsB,GAAG;AAAA,QACnD,OAAO;AAAA,QACP,YAAY;AAAA,MACd,CAAC;AACD,YAAM;AAAA,IACR,UAAE;AACA,WAAK,YAAY;AAAA,IACnB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,QAAc;AACZ,QAAI,CAAC,KAAK,KAAK;AACb,YAAM,IAAI,MAAM,6CAA6C;AAAA,IAC/D;AAEA,SAAK,QAAQ,IAAI,KAAK,IAAI,OAAO,WAAW,IAAI,aAAa,IAAI,IAAI,GAAG,GAAG,CAAC,GAAG,GAAG,GAAG,CAAC;AAEtF,SAAK,UAAU,IAAI,aAAa,KAAK,WAAW;AAEhD,SAAK,kBAAkB,CAAC;AACxB,SAAK,cAAc;AAGnB,QAAI,CAAC,KAAK,UAAU;AAClB,UAAI;AACF,aAAK,WAAW,IAAI,KAAK,IAAI;AAAA,UAC3B;AAAA,UACA,IAAI,cAAc,CAAC,OAAO,KAAK,OAAO,UAAU,CAAC,CAAC;AAAA,UAClD,CAAC;AAAA,QACH;AAAA,MACF,SAAS,GAAG;AAGV,QAAAA,QAAO,KAAK,4DAA4D;AAAA,UACtE,OAAO,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC;AAAA,QAClD,CAAC;AACD,aAAK,WAAW,IAAI,KAAK,IAAI;AAAA,UAC3B;AAAA,UACA,CAAC,OAAO,KAAK,OAAO,UAAU,CAAC;AAAA,UAC/B,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,QAAQ,YAA8C;AAC1D,QAAI,CAAC,KAAK,SAAS;AACjB,YAAM,IAAI,MAAM,sCAAsC;AAAA,IACxD;AAEA,QAAI,WAAW,WAAW,KAAK,WAAW;AACxC,YAAM,IAAI;AAAA,QACR,+BAA+B,KAAK,SAAS,iBAAiB,WAAW,MAAM;AAAA,MAEjF;AAAA,IACF;AAEA,WAAO,KAAK,eAAe,UAAU;AAAA,EACvC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,aACJ,OACA,UAOI,CAAC,GACqB;AAC1B,UAAM;AAAA,MACJ,sBAAsB;AAAA,MACtB,uBAAuB;AAAA,MACvB,cAAc;AAAA,IAChB,IAAI;AAEJ,SAAK,MAAM;AAEX,UAAM,WAA4B,CAAC;AACnC,UAAM,kBAAkB,KAAK,mBAAmB;AAChD,UAAM,kBAAkB,KAAK,KAAK,sBAAsB,eAAe;AACvE,UAAM,mBAAmB,KAAK,KAAK,uBAAuB,eAAe;AACzE,UAAM,YAAY,KAAK,KAAK,cAAc,eAAe;AAEzD,QAAI,WAAW;AACf,QAAI,cAAc;AAClB,QAAI,eAAe;AACnB,QAAI,eAAe;AACnB,QAAI,YAAY;AAGhB,aAAS,IAAI,GAAG,IAAI,KAAK,aAAa,MAAM,QAAQ,KAAK,KAAK,WAAW;AACvE,YAAM,QAAQ,MAAM,MAAM,GAAG,IAAI,KAAK,SAAS;AAC/C,YAAM,SAAS,MAAM,KAAK,QAAQ,KAAK;AACvC,YAAM,aAAa,IAAI,KAAK;AAC5B,YAAM,SAAS,aAAa;AAE5B,UAAI,OAAO,UAAU;AACnB,YAAI,CAAC,UAAU;AAEb,qBAAW;AACX,wBAAc,KAAK,IAAI,GAAG,SAAS,WAAW;AAC9C,yBAAe;AACf,yBAAe;AACf,sBAAY;AAAA,QACd;AACA,uBAAe;AACf;AACA,qBAAa,OAAO;AAAA,MACtB,WAAW,UAAU;AACnB;AACA,YAAI,gBAAgB,kBAAkB;AAEpC,cAAI,gBAAgB,iBAAiB;AACnC,qBAAS,KAAK;AAAA,cACZ,OAAO,cAAc;AAAA,cACrB,MAAM,SAAS,eAAe;AAAA,cAC9B,gBAAgB,YAAY;AAAA,YAC9B,CAAC;AAAA,UACH;AACA,qBAAW;AAAA,QACb;AAAA,MACF;AAAA,IACF;AAGA,QAAI,YAAY,gBAAgB,iBAAiB;AAC/C,YAAM,QAAS,MAAM,SAAS,KAAK,OAAO,aAAc;AACxD,eAAS,KAAK;AAAA,QACZ,OAAO,cAAc;AAAA,QACrB,KAAK,QAAQ;AAAA,QACb,gBAAgB,YAAY;AAAA,MAC9B,CAAC;AAAA,IACH;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKQ,aAAa,SAA+B;AAClD,QAAI,MAAM;AACV,aAAS,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;AACvC,aAAO,QAAQ,CAAC,IAAI,QAAQ,CAAC;AAAA,IAC/B;AACA,WAAO,KAAK,KAAK,MAAM,QAAQ,MAAM;AAAA,EACvC;AAAA;AAAA;AAAA;AAAA,EAKQ,eAAe,YAA8C;AAInE,UAAM,iBAAiB,IAAI,aAAa,UAAU;AAIlD,UAAM,uBAAuB;AAC7B,UAAM,MAAM,KAAK,aAAa,cAAc;AAC5C,QAAI,MAAM,sBAAsB;AAE9B,UAAI,CAAC,KAAK,aAAa;AACrB,aAAK,gBAAgB,KAAK,IAAI,aAAa,cAAc,CAAC;AAC1D,YAAI,KAAK,gBAAgB,SAAS,KAAK,OAAO,uBAAuB;AACnE,eAAK,gBAAgB,MAAM;AAAA,QAC7B;AAAA,MACF;AAEA,MAAAA,QAAO,MAAM,4CAA4C;AAAA,QACvD,KAAK,KAAK,MAAM,MAAM,GAAK,IAAI;AAAA,QAC/B,WAAW;AAAA,MACb,CAAC;AAED,aAAO,QAAQ,QAAQ;AAAA,QACrB,aAAa;AAAA,QACb,UAAU;AAAA,QACV,iBAAiB;AAAA,MACnB,CAAC;AAAA,IACH;AAEA,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,WAAK,iBAAiB,KAAK,eAAe,KAAK,YAAY;AACzD,cAAM,YAAY,aAAa;AAC/B,cAAM,OAAO,WAAW,UAAU,qBAAqB;AAAA,UACrD,qBAAqB,KAAK;AAAA,UAC1B,wBAAwB,KAAK;AAAA,QAC/B,CAAC;AACD,YAAI;AACF,gBAAM,YAAY,YAAY,IAAI;AAGlC,gBAAM,YAAY,KAAK,cAAc,KAAK;AAC1C,gBAAM,cAAc,IAAI,aAAa,SAAS;AAC9C,sBAAY,IAAI,KAAK,SAAS,CAAC;AAC/B,sBAAY,IAAI,gBAAgB,KAAK,WAAW;AAKhD,gBAAM,kBAAkB,IAAI,aAAa,WAAW;AACpD,gBAAM,cAAc,IAAI,KAAK,IAAK,OAAO,WAAW,iBAAiB,CAAC,GAAG,SAAS,CAAC;AAEnF,gBAAM,WAAW,KAAK;AAItB,gBAAM,YAAY,IAAI,aAAa,KAAK,MAAO,IAAoB;AACnE,gBAAM,cAAc,IAAI,KAAK,IAAK,OAAO,WAAW,WAAW,KAAK,MAAO,IAAgB;AAE3F,gBAAM,QAAQ;AAAA,YACZ,SAAS;AAAA,YACT,SAAS;AAAA,YACT,MAAM;AAAA,UACR;AAGA,gBAAM,UAAU,MAAM,KAAK,QAAS,IAAI,KAAK;AAG7C,gBAAM,eAAe,QAAQ,QAAQ;AACrC,gBAAM,iBAAiB,QAAQ,QAAQ,KAAK,QAAQ,OAAO;AAE3D,cAAI,CAAC,cAAc;AACjB,kBAAM,IAAI,MAAM,sCAAsC;AAAA,UACxD;AAEA,gBAAM,cAAe,aAAa,KAAsB,CAAC;AAGzD,cAAI,gBAAgB;AAClB,iBAAK,QAAQ,IAAI,KAAK,IAAK;AAAA,cACzB;AAAA,cACA,IAAI,aAAa,eAAe,IAAoB;AAAA,cACpD,CAAC,GAAG,GAAG,GAAG;AAAA,YACZ;AAAA,UACF;AAGA,eAAK,UAAU,WAAW,MAAM,CAAC,KAAK,WAAW;AAEjD,gBAAM,kBAAkB,YAAY,IAAI,IAAI;AAC5C,gBAAM,WAAW,cAAc,KAAK,OAAO;AAG3C,cAAI;AAEJ,cAAI,YAAY,CAAC,KAAK,aAAa;AAEjC,8BAAkB,CAAC,GAAG,KAAK,eAAe;AAC1C,iBAAK,kBAAkB,CAAC;AACxB,YAAAA,QAAO,MAAM,yCAAyC;AAAA,cACpD,iBAAiB,gBAAgB;AAAA,cACjC,YAAY,KAAK,MAAM,gBAAgB,SAAS,KAAK,mBAAmB,CAAC;AAAA,YAC3E,CAAC;AAAA,UACH,WAAW,CAAC,YAAY,CAAC,KAAK,aAAa;AAEzC,iBAAK,gBAAgB,KAAK,IAAI,aAAa,UAAU,CAAC;AACtD,gBAAI,KAAK,gBAAgB,SAAS,KAAK,OAAO,uBAAuB;AACnE,mBAAK,gBAAgB,MAAM;AAAA,YAC7B;AAAA,UACF,WAAW,CAAC,YAAY,KAAK,aAAa;AAExC,iBAAK,kBAAkB,CAAC;AAAA,UAC1B;AAEA,eAAK,cAAc;AAEnB,UAAAA,QAAO,MAAM,2BAA2B;AAAA,YACtC,aAAa,KAAK,MAAM,cAAc,GAAI,IAAI;AAAA,YAC9C;AAAA,YACA,iBAAiB,KAAK,MAAM,kBAAkB,GAAG,IAAI;AAAA,UACvD,CAAC;AAED,gBAAM,cAAc;AAAA,YAClB,yBAAyB;AAAA,YACzB,yBAAyB;AAAA,YACzB,uBAAuB;AAAA,UACzB,CAAC;AACD,gBAAM,IAAI;AACV,qBAAW,gBAAgB,2BAA2B,iBAAiB;AAAA,YACrE,OAAO;AAAA,YACP,SAAS,KAAK;AAAA,UAChB,CAAC;AACD,qBAAW,iBAAiB,yBAAyB,GAAG;AAAA,YACtD,OAAO;AAAA,YACP,SAAS,KAAK;AAAA,YACd,QAAQ;AAAA,UACV,CAAC;AAED,kBAAQ;AAAA,YACN;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,UACF,CAAC;AAAA,QACH,SAAS,KAAK;AACZ,gBAAM,aAAa,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC,CAAC;AACtE,qBAAW,iBAAiB,yBAAyB,GAAG;AAAA,YACtD,OAAO;AAAA,YACP,SAAS,KAAK;AAAA,YACd,QAAQ;AAAA,UACV,CAAC;AACD,iBAAO,GAAG;AAAA,QACZ;AAAA,MACF,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,UAAyB;AAC7B,QAAI,KAAK,SAAS;AAChB,YAAM,KAAK,QAAQ,QAAQ;AAC3B,WAAK,UAAU;AAAA,IACjB;AACA,SAAK,QAAQ;AACb,SAAK,WAAW;AAAA,EAClB;AACF;AAAA;AAAA;AAAA;AAAA;AAngBa,mBAkFJ,oBAAoB;;;AChM7B,IAAMC,UAAS,aAAa,iBAAiB;AAG7C,IAAMC,iBAAgB;AAGtB,IAAM,kBAAkB;AACxB,IAAM,uBAAuB;AAmF7B,IAAM,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAgNf,IAAM,kBAAN,MAAsB;AAAA,EA2B3B,YAAY,QAAyB;AA1BrC,SAAQ,SAAwB;AAEhC,SAAQ,YAAY;AACpB,SAAQ,YAAY;AAapB;AAAA,SAAQ,iBAAgC,QAAQ,QAAQ;AAGxD;AAAA,SAAQ,kBAAkC,CAAC;AAC3C,SAAQ,cAAc;AAGtB;AAAA,SAAQ,mBAAuG,oBAAI,IAAI;AACvH,SAAQ,YAAY;AAGlB,UAAM,aAAa,OAAO,cAAc;AAExC,QAAI,eAAe,OAAQ,eAAe,MAAO;AAC/C,YAAM,IAAI,MAAM,wDAAwD;AAAA,IAC1E;AAEA,SAAK,SAAS;AAAA,MACZ,UAAU,OAAO;AAAA,MACjB;AAAA,MACA,WAAW,OAAO,aAAa;AAAA,MAC/B,uBAAuB,OAAO,yBAAyB;AAAA,IACzD;AAGA,SAAK,YAAY,eAAe,OAAQ,MAAM;AAC9C,SAAK,cAAc,eAAe,OAAQ,KAAK;AAG/C,SAAK,QAAQ,IAAI,aAAa,IAAI,IAAI,GAAG;AACzC,SAAK,UAAU,IAAI,aAAa,KAAK,WAAW;AAAA,EAClD;AAAA,EAEA,IAAI,WAAoB;AACtB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,UAAyB;AAC3B,WAAO,KAAK,YAAY,SAAS;AAAA,EACnC;AAAA,EAEA,IAAI,aAAqB;AACvB,WAAO,KAAK,OAAO;AAAA,EACrB;AAAA,EAEA,IAAI,YAAoB;AACtB,WAAO,KAAK,OAAO;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA,EAKA,eAAuB;AACrB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,qBAA6B;AAC3B,WAAQ,KAAK,YAAY,KAAK,OAAO,aAAc;AAAA,EACrD;AAAA;AAAA;AAAA;AAAA,EAKQ,eAAuB;AAC7B,UAAM,OAAO,IAAI,KAAK,CAAC,aAAa,GAAG,EAAE,MAAM,yBAAyB,CAAC;AACzE,UAAM,UAAU,IAAI,gBAAgB,IAAI;AACxC,UAAM,SAAS,IAAI,OAAO,OAAO;AAGjC,QAAI,gBAAgB,OAAO;AAG3B,WAAO,YAAY,CAAC,UAAyC;AAC3D,WAAK,oBAAoB,MAAM,IAAI;AAAA,IACrC;AAGA,WAAO,UAAU,CAAC,UAAU;AAC1B,MAAAD,QAAO,MAAM,gBAAgB,EAAE,OAAO,MAAM,QAAQ,CAAC;AAErD,iBAAW,CAAC,EAAE,QAAQ,KAAK,KAAK,kBAAkB;AAChD,iBAAS,OAAO,IAAI,MAAM,iBAAiB,MAAM,OAAO,EAAE,CAAC;AAAA,MAC7D;AACA,WAAK,iBAAiB,MAAM;AAAA,IAC9B;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKQ,oBAAoB,QAA+B;AAEzD,UAAM,WAAW,KAAK,iBAAiB,IAAI,OAAO,IAAI;AACtD,QAAI,UAAU;AACZ,WAAK,iBAAiB,OAAO,OAAO,IAAI;AACxC,UAAI,OAAO,SAAS,SAAS;AAC3B,iBAAS,OAAO,IAAI,MAAM,OAAO,KAAK,CAAC;AAAA,MACzC,OAAO;AACL,iBAAS,QAAQ,MAAM;AAAA,MACzB;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,YAAe,SAA2B,cAAsB,WAA+B;AACrG,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,UAAI,CAAC,KAAK,QAAQ;AAChB,eAAO,IAAI,MAAM,wBAAwB,CAAC;AAC1C;AAAA,MACF;AAGA,YAAM,YAAY,WAAW,MAAM;AACjC,aAAK,iBAAiB,OAAO,YAAY;AACzC,eAAO,IAAI,MAAM,oCAAoC,SAAS,IAAI,CAAC;AAAA,MACrE,GAAG,SAAS;AAGZ,WAAK,iBAAiB,IAAI,cAAc;AAAA,QACtC,SAAS,CAAC,UAAU;AAClB,uBAAa,SAAS;AACtB,kBAAQ,KAAU;AAAA,QACpB;AAAA,QACA,QAAQ,CAAC,UAAU;AACjB,uBAAa,SAAS;AACtB,iBAAO,KAAK;AAAA,QACd;AAAA,MACF,CAAC;AAGD,WAAK,iBAAiB,IAAI,SAAS;AAAA,QACjC,SAAS,MAAM;AAAA,QAAC;AAAA;AAAA,QAChB,QAAQ,CAAC,UAAU;AACjB,uBAAa,SAAS;AACtB,eAAK,iBAAiB,OAAO,YAAY;AACzC,iBAAO,KAAK;AAAA,QACd;AAAA,MACF,CAAC;AAGD,WAAK,OAAO,YAAY,OAAO;AAAA,IACjC,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,OAAoC;AACxC,QAAI,KAAK,WAAW;AAClB,YAAM,IAAI,MAAM,0BAA0B;AAAA,IAC5C;AAEA,QAAI,KAAK,WAAW;AAClB,YAAM,IAAI,MAAM,6CAA6C;AAAA,IAC/D;AAEA,SAAK,YAAY;AACjB,UAAM,YAAY,YAAY,IAAI;AAClC,UAAM,YAAY,aAAa;AAC/B,UAAM,OAAO,WAAW,UAAU,wBAAwB;AAAA,MACxD,aAAa,KAAK,OAAO;AAAA,MACzB,qBAAqB,KAAK,OAAO;AAAA,IACnC,CAAC;AAED,QAAI;AACF,MAAAA,QAAO,KAAK,wBAAwB;AAGpC,WAAK,SAAS,KAAK,aAAa;AAEhC,MAAAA,QAAO,KAAK,8BAA8B;AAAA,QACxC,UAAU,KAAK,OAAO;AAAA,QACtB,YAAY,KAAK,OAAO;AAAA,MAC1B,CAAC;AAGD,YAAM,SAAS,MAAM,KAAK;AAAA,QAMxB;AAAA,UACE,MAAM;AAAA,UACN,UAAU,KAAK,OAAO;AAAA,UACtB,YAAY,KAAK,OAAO;AAAA,UACxB,WAAWC;AAAA,QACb;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAEA,WAAK,YAAY;AAEjB,YAAM,aAAa,YAAY,IAAI,IAAI;AAEvC,MAAAD,QAAO,KAAK,kCAAkC;AAAA,QAC5C,SAAS;AAAA,QACT,YAAY,KAAK,MAAM,UAAU;AAAA,QACjC,kBAAkB,KAAK,MAAM,OAAO,UAAU;AAAA,QAC9C,YAAY,KAAK,OAAO;AAAA,QACxB,WAAW,KAAK;AAAA,QAChB,WAAW,KAAK,OAAO;AAAA,MACzB,CAAC;AAED,YAAM,cAAc;AAAA,QAClB,iBAAiB;AAAA,QACjB,sBAAsB;AAAA,QACtB,6BAA6B,OAAO;AAAA,MACtC,CAAC;AACD,YAAM,IAAI;AACV,iBAAW,gBAAgB,yBAAyB,YAAY;AAAA,QAC9D,OAAO;AAAA,QACP,SAAS;AAAA,MACX,CAAC;AAED,aAAO;AAAA,QACL,SAAS;AAAA,QACT;AAAA,QACA,YAAY,OAAO;AAAA,QACnB,aAAa,OAAO;AAAA,QACpB,YAAY,KAAK,OAAO;AAAA,QACxB,WAAW,KAAK;AAAA,MAClB;AAAA,IACF,SAAS,OAAO;AACd,YAAM,aAAa,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC,CAAC;AAC5E,iBAAW,iBAAiB,sBAAsB,GAAG;AAAA,QACnD,OAAO;AAAA,QACP,YAAY;AAAA,MACd,CAAC;AAGD,UAAI,KAAK,QAAQ;AACf,aAAK,OAAO,UAAU;AACtB,aAAK,SAAS;AAAA,MAChB;AAEA,YAAM;AAAA,IACR,UAAE;AACA,WAAK,YAAY;AAAA,IACnB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,QAAuB;AAC3B,QAAI,CAAC,KAAK,aAAa,CAAC,KAAK,QAAQ;AACnC,YAAM,IAAI,MAAM,uCAAuC;AAAA,IACzD;AAGA,UAAM,SAAS,MAAM,KAAK;AAAA,MACxB,EAAE,MAAM,QAAQ;AAAA,MAChB;AAAA,MACA;AAAA,IACF;AAGA,SAAK,QAAQ,OAAO;AACpB,SAAK,UAAU,IAAI,aAAa,KAAK,WAAW;AAChD,SAAK,kBAAkB,CAAC;AACxB,SAAK,cAAc;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,QAAQ,YAA8C;AAC1D,QAAI,CAAC,KAAK,aAAa,CAAC,KAAK,QAAQ;AACnC,YAAM,IAAI,MAAM,uCAAuC;AAAA,IACzD;AAEA,QAAI,WAAW,WAAW,KAAK,WAAW;AACxC,YAAM,IAAI;AAAA,QACR,+BAA+B,KAAK,SAAS,iBAAiB,WAAW,MAAM;AAAA,MAEjF;AAAA,IACF;AAEA,WAAO,KAAK,eAAe,UAAU;AAAA,EACvC;AAAA;AAAA;AAAA;AAAA,EAKQ,eAAe,YAA8C;AAEnE,UAAM,iBAAiB,IAAI,aAAa,UAAU;AAElD,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,WAAK,iBAAiB,KAAK,eAAe,KAAK,YAAY;AACzD,cAAM,YAAY,aAAa;AAC/B,cAAM,OAAO,WAAW,UAAU,2BAA2B;AAAA,UAC3D,qBAAqB;AAAA,UACrB,wBAAwB,KAAK;AAAA,QAC/B,CAAC;AAED,YAAI;AACF,gBAAM,YAAY,YAAY,IAAI;AAGlC,gBAAM,SAAS,MAAM,KAAK;AAAA,YAMxB;AAAA,cACE,MAAM;AAAA,cACN,OAAO;AAAA,cACP,OAAO,KAAK;AAAA,cACZ,SAAS,KAAK;AAAA,YAChB;AAAA,YACA;AAAA,YACA;AAAA,UACF;AAGA,eAAK,QAAQ,OAAO;AAGpB,eAAK,UAAU,eAAe,MAAM,CAAC,KAAK,WAAW;AAErD,gBAAM,kBAAkB,YAAY,IAAI,IAAI;AAC5C,gBAAM,WAAW,OAAO,cAAc,KAAK,OAAO;AAGlD,cAAI;AAEJ,cAAI,YAAY,CAAC,KAAK,aAAa;AAEjC,8BAAkB,CAAC,GAAG,KAAK,eAAe;AAC1C,iBAAK,kBAAkB,CAAC;AACxB,YAAAA,QAAO,MAAM,yCAAyC;AAAA,cACpD,iBAAiB,gBAAgB;AAAA,cACjC,YAAY,KAAK,MAAM,gBAAgB,SAAS,KAAK,mBAAmB,CAAC;AAAA,YAC3E,CAAC;AAAA,UACH,WAAW,CAAC,YAAY,CAAC,KAAK,aAAa;AAEzC,iBAAK,gBAAgB,KAAK,IAAI,aAAa,cAAc,CAAC;AAC1D,gBAAI,KAAK,gBAAgB,SAAS,KAAK,OAAO,uBAAuB;AACnE,mBAAK,gBAAgB,MAAM;AAAA,YAC7B;AAAA,UACF,WAAW,CAAC,YAAY,KAAK,aAAa;AAExC,iBAAK,kBAAkB,CAAC;AAAA,UAC1B;AAEA,eAAK,cAAc;AAEnB,UAAAA,QAAO,MAAM,kCAAkC;AAAA,YAC7C,aAAa,KAAK,MAAM,OAAO,cAAc,GAAI,IAAI;AAAA,YACrD;AAAA,YACA,iBAAiB,KAAK,MAAM,kBAAkB,GAAG,IAAI;AAAA,YACrD,cAAc,KAAK,MAAM,OAAO,kBAAkB,GAAG,IAAI;AAAA,UAC3D,CAAC;AAED,gBAAM,cAAc;AAAA,YAClB,yBAAyB;AAAA,YACzB,gCAAgC,OAAO;AAAA,YACvC,yBAAyB,OAAO;AAAA,YAChC,uBAAuB;AAAA,UACzB,CAAC;AACD,gBAAM,IAAI;AACV,qBAAW,gBAAgB,2BAA2B,iBAAiB;AAAA,YACrE,OAAO;AAAA,YACP,SAAS;AAAA,UACX,CAAC;AACD,qBAAW,iBAAiB,yBAAyB,GAAG;AAAA,YACtD,OAAO;AAAA,YACP,SAAS;AAAA,YACT,QAAQ;AAAA,UACV,CAAC;AAED,kBAAQ;AAAA,YACN,aAAa,OAAO;AAAA,YACpB;AAAA,YACA;AAAA,YACA;AAAA,UACF,CAAC;AAAA,QACH,SAAS,KAAK;AACZ,gBAAM,aAAa,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC,CAAC;AACtE,qBAAW,iBAAiB,yBAAyB,GAAG;AAAA,YACtD,OAAO;AAAA,YACP,SAAS;AAAA,YACT,QAAQ;AAAA,UACV,CAAC;AACD,iBAAO,GAAG;AAAA,QACZ;AAAA,MACF,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,UAAyB;AAC7B,QAAI,KAAK,QAAQ;AACf,UAAI;AAEF,cAAM,KAAK,YAAY,EAAE,MAAM,UAAU,GAAG,YAAY,oBAAoB;AAAA,MAC9E,QAAQ;AAAA,MAER;AAGA,WAAK,OAAO,UAAU;AACtB,WAAK,SAAS;AAAA,IAChB;AAEA,SAAK,YAAY;AACjB,SAAK,QAAQ,IAAI,aAAa,IAAI,IAAI,GAAG;AACzC,SAAK,UAAU,IAAI,aAAa,KAAK,WAAW;AAChD,SAAK,kBAAkB,CAAC;AACxB,SAAK,cAAc;AACnB,SAAK,iBAAiB,MAAM;AAAA,EAC9B;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO,cAAuB;AAC5B,WAAO,OAAO,WAAW;AAAA,EAC3B;AACF;;;AChuBA,IAAME,UAAS,aAAa,iBAAiB;AAkGtC,SAAS,oBAA6B;AAE3C,MAAI,OAAO,WAAW,aAAa;AACjC,IAAAA,QAAO,MAAM,oDAAoD;AACjE,WAAO;AAAA,EACT;AAGA,MAAI,OAAO,QAAQ,eAAe,OAAO,IAAI,oBAAoB,aAAa;AAC5E,IAAAA,QAAO,MAAM,uDAAuD;AACpE,WAAO;AAAA,EACT;AAGA,MAAI,OAAO,SAAS,aAAa;AAC/B,IAAAA,QAAO,MAAM,oDAAoD;AACjE,WAAO;AAAA,EACT;AAEA,SAAO;AACT;AAgCO,SAAS,gBAAgB,QAAkD;AAChF,QAAM,kBAAkB,OAAO,mBAAmB;AAGlD,MAAI;AAEJ,MAAI,OAAO,cAAc,QAAW;AAElC,gBAAY,OAAO;AACnB,IAAAA,QAAO,MAAM,oCAAoC,EAAE,UAAU,CAAC;AAAA,EAChE,OAAO;AAEL,UAAM,kBAAkB,kBAAkB;AAC1C,UAAM,WAAW,SAAS;AAI1B,gBAAY,mBAAmB,CAAC;AAEhC,IAAAA,QAAO,MAAM,mCAAmC;AAAA,MAC9C;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH;AAGA,MAAI,WAAW;AACb,IAAAA,QAAO,KAAK,4CAA4C;AACxD,UAAM,SAAS,IAAI,gBAAgB;AAAA,MACjC,UAAU,OAAO;AAAA,MACjB,YAAY,OAAO;AAAA,MACnB,WAAW,OAAO;AAAA,MAClB,uBAAuB,OAAO;AAAA,IAChC,CAAC;AAED,QAAI,iBAAiB;AAEnB,aAAO,IAAI,sBAAsB,QAAQ,MAAM;AAAA,IACjD;AAEA,WAAO;AAAA,EACT;AAEA,EAAAA,QAAO,KAAK,2CAA2C;AACvD,SAAO,IAAI,mBAAmB,MAAM;AACtC;AAQA,IAAM,wBAAN,MAAwD;AAAA,EAKtD,YAAY,QAAyB,QAAgC;AAFrE,SAAQ,gBAAgB;AAGtB,SAAK,iBAAiB;AACtB,SAAK,SAAS;AAAA,EAChB;AAAA,EAEA,IAAI,UAAiC;AAEnC,QAAI,CAAC,KAAK,SAAU,QAAO;AAC3B,WAAO,KAAK,gBAAiB,KAAK,eAAsC,UAAU;AAAA,EACpF;AAAA,EAEA,IAAI,WAAoB;AACtB,WAAO,KAAK,eAAe;AAAA,EAC7B;AAAA,EAEA,IAAI,aAAqB;AACvB,WAAO,KAAK,eAAe;AAAA,EAC7B;AAAA,EAEA,IAAI,YAAoB;AACtB,WAAO,KAAK,eAAe;AAAA,EAC7B;AAAA,EAEA,MAAM,OAAmD;AACvD,QAAI;AACF,aAAO,MAAM,KAAK,eAAe,KAAK;AAAA,IACxC,SAAS,OAAO;AACd,MAAAA,QAAO,KAAK,mDAAmD;AAAA,QAC7D,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,MAC9D,CAAC;AAGD,UAAI;AACF,cAAM,KAAK,eAAe,QAAQ;AAAA,MACpC,QAAQ;AAAA,MAER;AAGA,WAAK,iBAAiB,IAAI,mBAAmB,KAAK,MAAM;AACxD,WAAK,gBAAgB;AAErB,MAAAA,QAAO,KAAK,2CAA2C;AACvD,aAAO,MAAM,KAAK,eAAe,KAAK;AAAA,IACxC;AAAA,EACF;AAAA,EAEA,MAAM,QAAQ,YAA8C;AAC1D,WAAO,KAAK,eAAe,QAAQ,UAAU;AAAA,EAC/C;AAAA,EAEA,QAA8B;AAC5B,WAAO,KAAK,eAAe,MAAM;AAAA,EACnC;AAAA,EAEA,MAAM,UAAyB;AAC7B,WAAO,KAAK,eAAe,QAAQ;AAAA,EACrC;AAAA,EAEA,eAAuB;AACrB,WAAO,KAAK,eAAe,aAAa;AAAA,EAC1C;AAAA,EAEA,qBAA6B;AAC3B,WAAO,KAAK,eAAe,mBAAmB;AAAA,EAChD;AACF;;;AC3RA,IAAMC,WAAS,aAAa,cAAc;AAsGnC,IAAM,0BAAN,MAAM,yBAAwB;AAAA,EAenC,YAAY,SAA6B,CAAC,GAAG;AAb7C,SAAQ,cAAiD;AACzD,SAAQ,cAAc;AACtB,SAAQ,YAAY;AACpB,SAAQ,kBAAkB;AAG1B;AAAA,SAAQ,kBAA0C,CAAC;AACnD,SAAQ,iBAAwC,CAAC;AAGjD;AAAA,SAAQ,eAAmE;AAC3E,SAAQ,eAAgD;AAGtD,SAAK,SAAS;AAAA,MACZ,UAAU,OAAO,YAAY;AAAA,MAC7B,YAAY,OAAO,cAAc;AAAA,MACjC,gBAAgB,OAAO,kBAAkB;AAAA,MACzC,iBAAiB,OAAO,mBAAmB;AAAA,IAC7C;AAEA,IAAAA,SAAO,MAAM,mCAAmC;AAAA,MAC9C,UAAU,KAAK,OAAO;AAAA,MACtB,YAAY,KAAK,OAAO;AAAA,IAC1B,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO,cAAuB;AAC5B,WAAO,6BAA6B;AAAA,EACtC;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,YAAqB;AACvB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,WAAmB;AACrB,WAAO,KAAK,OAAO;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA,EAKA,SAAS,UAAsC;AAC7C,SAAK,gBAAgB,KAAK,QAAQ;AAAA,EACpC;AAAA;AAAA;AAAA;AAAA,EAKA,QAAQ,UAAqC;AAC3C,SAAK,eAAe,KAAK,QAAQ;AAAA,EACnC;AAAA;AAAA;AAAA;AAAA,EAKA,UAAU,UAAsC;AAC9C,UAAM,QAAQ,KAAK,gBAAgB,QAAQ,QAAQ;AACnD,QAAI,UAAU,IAAI;AAChB,WAAK,gBAAgB,OAAO,OAAO,CAAC;AAAA,IACtC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,SAAS,UAAqC;AAC5C,UAAM,QAAQ,KAAK,eAAe,QAAQ,QAAQ;AAClD,QAAI,UAAU,IAAI;AAChB,WAAK,eAAe,OAAO,OAAO,CAAC;AAAA,IACrC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,QAAuB;AAC3B,QAAI,KAAK,aAAa;AACpB,MAAAA,SAAO,KAAK,mBAAmB;AAC/B;AAAA,IACF;AAEA,QAAI,CAAC,yBAAwB,YAAY,GAAG;AAC1C,YAAM,QAAQ,IAAI;AAAA,QAChB;AAAA,MAGF;AACA,WAAK,UAAU,KAAK;AACpB,YAAM;AAAA,IACR;AAEA,UAAM,YAAY,aAAa;AAC/B,UAAM,OAAO,WAAW,UAAU,sBAAsB;AAAA,MACtD,mBAAmB,KAAK,OAAO;AAAA,MAC/B,qBAAqB,KAAK,OAAO;AAAA,IACnC,CAAC;AAED,QAAI;AAEF,YAAM,yBAAyB,OAAO,qBAAqB,OAAO;AAClE,UAAI,CAAC,wBAAwB;AAC3B,cAAM,IAAI,MAAM,yCAAyC;AAAA,MAC3D;AAEA,WAAK,cAAc,IAAI,uBAAuB;AAC9C,WAAK,YAAY,aAAa,KAAK,OAAO;AAC1C,WAAK,YAAY,iBAAiB,KAAK,OAAO;AAC9C,WAAK,YAAY,OAAO,KAAK,OAAO;AACpC,WAAK,YAAY,kBAAkB,KAAK,OAAO;AAG/C,WAAK,mBAAmB;AAGxB,WAAK,YAAY,MAAM;AACvB,WAAK,cAAc;AACnB,WAAK,YAAY,YAAY,IAAI;AACjC,WAAK,kBAAkB;AAEvB,MAAAA,SAAO,KAAK,8BAA8B;AAAA,QACxC,UAAU,KAAK,OAAO;AAAA,MACxB,CAAC;AAED,YAAM,IAAI;AAAA,IACZ,SAAS,OAAO;AACd,YAAM,aAAa,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC,CAAC;AAC5E,WAAK,UAAU,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC,CAAC;AACxE,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,OAAyC;AAC7C,QAAI,CAAC,KAAK,eAAe,CAAC,KAAK,aAAa;AAC1C,MAAAA,SAAO,KAAK,yBAAyB;AACrC,aAAO;AAAA,QACL,MAAM,KAAK;AAAA,QACX,UAAU,KAAK,OAAO;AAAA,QACtB,iBAAiB;AAAA,QACjB,SAAS;AAAA,MACX;AAAA,IACF;AAEA,UAAM,YAAY,aAAa;AAC/B,UAAM,OAAO,WAAW,UAAU,mBAAmB;AAErD,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,WAAK,eAAe;AACpB,WAAK,eAAe;AAEpB,UAAI;AACF,aAAK,YAAa,KAAK;AAAA,MAEzB,SAAS,OAAO;AACd,cAAM,aAAa,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC,CAAC;AAC5E,aAAK,cAAc;AACnB,eAAO,KAAK;AAAA,MACd;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,QAAc;AACZ,QAAI,KAAK,eAAe,KAAK,aAAa;AACxC,WAAK,YAAY,MAAM;AACvB,WAAK,cAAc;AACnB,MAAAA,SAAO,KAAK,4BAA4B;AAAA,IAC1C;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,MAAM,WAAW,QAAwD;AACvE,UAAM,IAAI;AAAA,MACR;AAAA,IAGF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,UAAgB;AACd,QAAI,KAAK,aAAa;AACpB,UAAI,KAAK,aAAa;AACpB,aAAK,YAAY,MAAM;AAAA,MACzB;AACA,WAAK,cAAc;AAAA,IACrB;AACA,SAAK,cAAc;AACnB,SAAK,kBAAkB,CAAC;AACxB,SAAK,iBAAiB,CAAC;AACvB,IAAAA,SAAO,MAAM,kCAAkC;AAAA,EACjD;AAAA;AAAA;AAAA;AAAA,EAKQ,qBAA2B;AACjC,QAAI,CAAC,KAAK,YAAa;AAEvB,SAAK,YAAY,WAAW,CAAC,UAAkC;AAC7D,YAAM,YAAY,aAAa;AAC/B,YAAM,OAAO,WAAW,UAAU,uBAAuB;AAEzD,UAAI;AAEF,iBAAS,IAAI,MAAM,aAAa,IAAI,MAAM,QAAQ,QAAQ,KAAK;AAC7D,gBAAM,SAAS,MAAM,QAAQ,CAAC;AAC9B,gBAAM,cAAc,OAAO,CAAC;AAE5B,cAAI,aAAa;AACf,kBAAM,OAAO,YAAY;AACzB,kBAAM,UAAU,OAAO;AAGvB,gBAAI,SAAS;AACX,mBAAK,mBAAmB,OAAO;AAAA,YACjC;AAEA,kBAAM,eAAwC;AAAA,cAC5C,MAAM,UAAU,KAAK,gBAAgB,KAAK,IAAI;AAAA,cAC9C,UAAU,KAAK,OAAO;AAAA,cACtB,iBAAiB,YAAY,IAAI,IAAI,KAAK;AAAA,cAC1C;AAAA,cACA,YAAY,YAAY;AAAA,YAC1B;AAGA,iBAAK,WAAW,YAAY;AAE5B,YAAAA,SAAO,MAAM,iBAAiB;AAAA,cAC5B,MAAM,KAAK,UAAU,GAAG,EAAE;AAAA,cAC1B;AAAA,cACA,YAAY,YAAY;AAAA,YAC1B,CAAC;AAAA,UACH;AAAA,QACF;AAEA,cAAM,IAAI;AAAA,MACZ,SAAS,OAAO;AACd,cAAM,aAAa,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC,CAAC;AAC5E,QAAAA,SAAO,MAAM,kCAAkC,EAAE,MAAM,CAAC;AAAA,MAC1D;AAAA,IACF;AAEA,SAAK,YAAY,UAAU,CAAC,UAAuC;AACjE,YAAM,QAAQ,IAAI,MAAM,6BAA6B,MAAM,KAAK,MAAM,MAAM,OAAO,EAAE;AACrF,MAAAA,SAAO,MAAM,4BAA4B,EAAE,OAAO,MAAM,OAAO,SAAS,MAAM,QAAQ,CAAC;AACvF,WAAK,UAAU,KAAK;AAEpB,UAAI,KAAK,cAAc;AACrB,aAAK,aAAa,KAAK;AACvB,aAAK,eAAe;AACpB,aAAK,eAAe;AAAA,MACtB;AAAA,IACF;AAEA,SAAK,YAAY,QAAQ,MAAM;AAC7B,WAAK,cAAc;AACnB,MAAAA,SAAO,KAAK,4BAA4B;AAAA,QACtC,WAAW,KAAK,gBAAgB;AAAA,QAChC,YAAY,YAAY,IAAI,IAAI,KAAK;AAAA,MACvC,CAAC;AAGD,UAAI,KAAK,cAAc;AACrB,cAAM,SAAkC;AAAA,UACtC,MAAM,KAAK,gBAAgB,KAAK;AAAA,UAChC,UAAU,KAAK,OAAO;AAAA,UACtB,iBAAiB,YAAY,IAAI,IAAI,KAAK;AAAA,UAC1C,SAAS;AAAA,QACX;AACA,aAAK,aAAa,MAAM;AACxB,aAAK,eAAe;AACpB,aAAK,eAAe;AAAA,MACtB;AAAA,IACF;AAEA,SAAK,YAAY,UAAU,MAAM;AAC/B,MAAAA,SAAO,MAAM,uCAAuC;AAAA,IACtD;AAEA,SAAK,YAAY,gBAAgB,MAAM;AACrC,MAAAA,SAAO,MAAM,iBAAiB;AAAA,IAChC;AAEA,SAAK,YAAY,cAAc,MAAM;AACnC,MAAAA,SAAO,MAAM,cAAc;AAAA,IAC7B;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,WAAW,QAAuC;AACxD,eAAW,YAAY,KAAK,iBAAiB;AAC3C,UAAI;AACF,iBAAS,MAAM;AAAA,MACjB,SAAS,OAAO;AACd,QAAAA,SAAO,MAAM,4BAA4B,EAAE,MAAM,CAAC;AAAA,MACpD;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,UAAU,OAAoB;AACpC,eAAW,YAAY,KAAK,gBAAgB;AAC1C,UAAI;AACF,iBAAS,KAAK;AAAA,MAChB,SAAS,eAAe;AACtB,QAAAA,SAAO,MAAM,2BAA2B,EAAE,OAAO,cAAc,CAAC;AAAA,MAClE;AAAA,IACF;AAAA,EACF;AACF;;;ACxcO,IAAM,gBAAgB;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAQO,IAAM,sBAAsB;AAgB5B,SAAS,oBAAoB,UAA0B,CAAC,GAAiB;AAC9E,QAAM,SAAS,IAAI,aAAa,mBAAmB;AAEnD,aAAW,CAAC,MAAM,KAAK,KAAK,OAAO,QAAQ,OAAO,GAAG;AACnD,UAAM,MAAM,cAAc,QAAQ,IAAmB;AACrD,QAAI,OAAO,GAAG;AACZ,aAAO,GAAG,IAAI,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,KAAK,CAAC;AAAA,IAC9C;AAAA,EACF;AAEA,SAAO;AACT;AAKO,IAAM,iBAAiB;AAAA;AAAA,EAE5B,SAAS,oBAAoB,CAAC,CAAC;AAAA;AAAA,EAG/B,OAAO,oBAAoB,EAAE,KAAK,KAAK,WAAW,IAAI,CAAC;AAAA;AAAA,EAGvD,KAAK,oBAAoB,EAAE,SAAS,KAAK,OAAO,IAAI,CAAC;AAAA;AAAA,EAGrD,OAAO,oBAAoB,EAAE,OAAO,KAAK,SAAS,IAAI,CAAC;AAAA;AAAA,EAGvD,WAAW,oBAAoB,EAAE,WAAW,KAAK,MAAM,IAAI,CAAC;AAAA;AAAA,EAG5D,QAAQ,oBAAoB,EAAE,MAAM,KAAK,MAAM,IAAI,CAAC;AAAA;AAAA,EAGpD,WAAW,oBAAoB,EAAE,SAAS,KAAK,OAAO,IAAI,CAAC;AAAA;AAAA,EAG3D,SAAS,oBAAoB,EAAE,KAAK,KAAK,WAAW,KAAK,YAAY,IAAI,CAAC;AAAA;AAAA,EAG1E,OAAO,oBAAoB,EAAE,aAAa,KAAK,SAAS,IAAI,CAAC;AAAA;AAAA,EAG7D,SAAS,oBAAoB,EAAE,YAAY,KAAK,KAAK,IAAI,CAAC;AAAA;AAAA,EAG1D,QAAQ,oBAAoB,EAAE,MAAM,KAAK,OAAO,IAAI,CAAC;AAAA;AAAA,EAGrD,eAAe,oBAAoB,EAAE,SAAS,KAAK,OAAO,IAAI,CAAC;AACjE;AAOO,SAAS,iBAAiB,MAAuC;AACtE,SAAO,eAAe,IAAI,EAAE,MAAM;AACpC;AAgBO,SAAS,cACd,UACc;AACd,QAAM,SAAS,IAAI,aAAa,mBAAmB;AACnD,MAAI,cAAc;AAElB,aAAW,EAAE,QAAQ,OAAO,KAAK,UAAU;AACzC,mBAAe;AACf,aAAS,IAAI,GAAG,IAAI,qBAAqB,KAAK;AAC5C,aAAO,CAAC,MAAM,OAAO,CAAC,KAAK,KAAK;AAAA,IAClC;AAAA,EACF;AAGA,MAAI,cAAc,GAAG;AACnB,aAAS,IAAI,GAAG,IAAI,qBAAqB,KAAK;AAC5C,aAAO,CAAC,KAAK;AAAA,IACf;AAAA,EACF;AAEA,SAAO;AACT;AAUO,SAAS,YACd,MACA,IACA,GACc;AACd,QAAM,SAAS,IAAI,aAAa,mBAAmB;AACnD,QAAM,WAAW,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,CAAC,CAAC;AAE3C,WAAS,IAAI,GAAG,IAAI,qBAAqB,KAAK;AAC5C,WAAO,CAAC,KAAK,KAAK,CAAC,KAAK,MAAM,IAAI,aAAa,GAAG,CAAC,KAAK,KAAK;AAAA,EAC/D;AAEA,SAAO;AACT;AAKO,IAAM,oBAAN,MAAwB;AAAA,EAAxB;AACL,SAAQ,iBAAiB,IAAI,aAAa,mBAAmB;AAC7D,SAAQ,gBAAgB,IAAI,aAAa,mBAAmB;AAC5D,SAAQ,qBAAqB;AAC7B,SAAQ,qBAAqB;AAC7B,SAAQ,sBAAsB;AAAA;AAAA;AAAA;AAAA;AAAA,EAK9B,IAAI,UAAwB;AAC1B,QAAI,KAAK,sBAAsB,GAAK;AAClC,aAAO,KAAK;AAAA,IACd;AAGA,WAAO,YAAY,KAAK,gBAAgB,KAAK,eAAe,KAAK,kBAAkB;AAAA,EACrF;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,SAA+B;AACjC,UAAM,aAAa,oBAAoB,OAAO;AAC9C,SAAK,cAAc,IAAI,UAAU;AACjC,SAAK,eAAe,IAAI,UAAU;AAClC,SAAK,qBAAqB;AAAA,EAC5B;AAAA;AAAA;AAAA;AAAA,EAKA,UAAU,QAAiC;AACzC,UAAM,aAAa,iBAAiB,MAAM;AAC1C,SAAK,cAAc,IAAI,UAAU;AACjC,SAAK,eAAe,IAAI,UAAU;AAClC,SAAK,qBAAqB;AAAA,EAC5B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,aAAa,SAAyB,YAA0B;AAC9D,SAAK,eAAe,IAAI,KAAK,OAAO;AACpC,SAAK,cAAc,IAAI,oBAAoB,OAAO,CAAC;AACnD,SAAK,qBAAqB;AAC1B,SAAK,sBAAsB,YAAY,IAAI;AAC3C,SAAK,qBAAqB;AAAA,EAC5B;AAAA;AAAA;AAAA;AAAA,EAKA,mBAAmB,QAA2B,YAA0B;AACtE,SAAK,eAAe,IAAI,KAAK,OAAO;AACpC,SAAK,cAAc,IAAI,iBAAiB,MAAM,CAAC;AAC/C,SAAK,qBAAqB;AAC1B,SAAK,sBAAsB,YAAY,IAAI;AAC3C,SAAK,qBAAqB;AAAA,EAC5B;AAAA;AAAA;AAAA;AAAA,EAKA,SAAe;AACb,QAAI,KAAK,sBAAsB,EAAK;AAEpC,UAAM,UAAU,YAAY,IAAI,IAAI,KAAK;AACzC,SAAK,qBAAqB,KAAK,IAAI,GAAK,UAAU,KAAK,kBAAkB;AAAA,EAC3E;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,kBAA2B;AAC7B,WAAO,KAAK,qBAAqB;AAAA,EACnC;AAAA;AAAA;AAAA;AAAA,EAKA,QAAc;AACZ,SAAK,eAAe,KAAK,CAAC;AAC1B,SAAK,cAAc,KAAK,CAAC;AACzB,SAAK,qBAAqB;AAAA,EAC5B;AACF;;;AC1OO,IAAM,mBAAN,cAA+B,aAAmD;AAAA,EAkCvF,YAAY,QAAyB;AACnC,UAAM;AAlCR,SAAS,OAAO;AAEhB,SAAQ,SAAyB;AACjC,SAAQ,aAA4B;AACpC,SAAQ,eAAe;AAGvB;AAAA,SAAQ,MAAkC;AAC1C,SAAQ,MAAiC;AACzC,SAAQ,MAAgC;AAExC,SAAQ,WAAuC;AAG/C;AAAA,SAAQ,KAAuB;AAC/B,SAAQ,sBAAsB;AAC9B,SAAiB,uBAAuB;AAGxC;AAAA,SAAQ,cAA8B,CAAC;AAGvC;AAAA,SAAQ,UAAiC,CAAC;AAC1C,SAAQ,gBAAsC;AAI9C;AAAA,SAAQ,aAAa;AACrB,SAAQ,4BAAoD;AAG5D;AAAA,SAAQ,aAAa,oBAAI,IAAkD;AAIzE,SAAK,kBAAkB;AACvB,SAAK,oBAAoB,IAAI,kBAAkB;AAAA,EACjD;AAAA,EAEA,IAAI,QAAwB;AAC1B,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,YAA2B;AAC7B,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,cAAuB;AACzB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,QAAQ,QAAsC;AAClD,SAAK,gBAAgB;AACrB,SAAK,aAAa,OAAO;AAEzB,QAAI;AAEF,YAAM,YAAY,MAAM,KAAK,aAAa,OAAO,MAAM;AAGvD,YAAM,QAAQ,IAAI;AAAA,QAChB,KAAK,QAAQ;AAAA,QACb,KAAK,QAAQ;AAAA,MACf,CAAC;AAGD,YAAM,KAAK,iBAAiB,WAAW,MAAM;AAE7C,WAAK,eAAe;AACpB,WAAK,SAAS,MAAM;AAEpB,WAAK,KAAK,qBAAqB,EAAE,WAAW,KAAK,YAAY,SAAS,KAAK,KAAK,CAAC;AAAA,IACnF,SAAS,OAAO;AACd,WAAK,SAAS,OAAO;AACrB,WAAK,KAAK,oBAAoB;AAAA,QAC5B;AAAA,QACA,aAAa;AAAA,MACf,CAAC;AACD,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,aAA4B;AAEhC,SAAK,2BAA2B,MAAM;AAGtC,QAAI,KAAK,UAAU;AACjB,WAAK,SAAS,QAAQ;AACtB,WAAK,WAAW;AAAA,IAClB;AAGA,QAAI,KAAK,IAAI;AACX,WAAK,GAAG,MAAM,KAAM,mBAAmB;AACvC,WAAK,KAAK;AAAA,IACZ;AAGA,UAAM,QAAQ,IAAI;AAAA,MAChB,KAAK,KAAK,QAAQ;AAAA,MAClB,KAAK,KAAK,QAAQ;AAAA,MAClB,KAAK,KAAK,QAAQ;AAAA,IACpB,CAAC;AAED,SAAK,eAAe;AACpB,SAAK,SAAS,cAAc;AAE5B,SAAK,KAAK,qBAAqB,EAAE,QAAQ,oBAAoB,CAAC;AAAA,EAChE;AAAA;AAAA;AAAA;AAAA,EAKA,UAAU,OAAwC;AAChD,QAAI,CAAC,KAAK,aAAc;AAGxB,QAAI,KAAK,YAAY;AACnB,WAAK,oBAAoB,KAAK,EAAE,KAAK,CAAC,qBAAqB;AACzD,YAAI,kBAAkB;AACpB,eAAK,UAAU;AAAA,QACjB;AAAA,MACF,CAAC,EAAE,MAAM,CAAC,UAAU;AAClB,gBAAQ,MAAM,wDAAwD,KAAK;AAAA,MAC7E,CAAC;AAAA,IAEH;AAGA,UAAM,UAAU,iBAAiB,eAC7B,QACA,KAAK,eAAe,KAAK;AAG7B,SAAK,YAAY,KAAK,OAAO;AAG7B,SAAK,sBAAsB;AAAA,EAC7B;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,SAAS,MAA6B;AAC1C,QAAI,CAAC,KAAK,gBAAgB,CAAC,KAAK,IAAI;AAClC,YAAM,IAAI,MAAM,4BAA4B;AAAA,IAC9C;AAGA,SAAK,aAAa;AAAA,MAChB,MAAM;AAAA,MACN,SAAS;AAAA,MACT,WAAW,KAAK,IAAI;AAAA,IACtB,CAAC;AAED,SAAK,SAAS,UAAU;AACxB,SAAK,KAAK,qBAAqB,EAAE,WAAW,KAAK,IAAI,EAAE,CAAC;AAGxD,SAAK,GAAG,KAAK,KAAK,UAAU;AAAA,MAC1B,MAAM;AAAA,MACN,WAAW,KAAK;AAAA,MAChB,SAAS;AAAA,MACT,SAAS;AAAA,QACP,SAAS,KAAK,QAAQ,MAAM,GAAG;AAAA;AAAA,QAC/B,SAAS,MAAM,KAAK,KAAK,kBAAkB,OAAO;AAAA,MACpD;AAAA,IACF,CAAC,CAAC;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA,EAKA,YAAkB;AAChB,QAAI,CAAC,KAAK,WAAY;AAEtB,SAAK,KAAK,yBAAyB,EAAE,WAAW,KAAK,IAAI,EAAE,CAAC;AAG5D,SAAK,2BAA2B,MAAM;AACtC,SAAK,4BAA4B;AAGjC,QAAI,KAAK,IAAI,eAAe,UAAU,MAAM;AAC1C,WAAK,GAAG,KAAK,KAAK,UAAU;AAAA,QAC1B,MAAM;AAAA,QACN,WAAW,KAAK;AAAA,QAChB,WAAW,KAAK,IAAI;AAAA,MACtB,CAAC,CAAC;AAAA,IACJ;AAEA,SAAK,aAAa;AAClB,SAAK,SAAS,WAAW;AAEzB,SAAK,KAAK,wBAAwB,EAAE,WAAW,KAAK,IAAI,GAAG,QAAQ,OAAO,CAAC;AAAA,EAC7E;AAAA,EAEA,aAAoC;AAClC,WAAO,CAAC,GAAG,KAAK,OAAO;AAAA,EACzB;AAAA,EAEA,eAAqB;AACnB,SAAK,UAAU,CAAC;AAChB,SAAK,KAAK,kBAAkB,EAAE,cAAc,EAAE,CAAC;AAAA,EACjD;AAAA,EAEA,MAAM,cAAgC;AACpC,QAAI,CAAC,KAAK,MAAM,KAAK,GAAG,eAAe,UAAU,MAAM;AACrD,aAAO;AAAA,IACT;AAEA,WAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,YAAM,UAAU,WAAW,MAAM,QAAQ,KAAK,GAAG,GAAI;AAErD,YAAM,UAAU,CAAC,UAAwB;AACvC,cAAM,OAAO,KAAK,MAAM,MAAM,IAAI;AAClC,YAAI,KAAK,SAAS,QAAQ;AACxB,uBAAa,OAAO;AACpB,eAAK,IAAI,oBAAoB,WAAW,OAAO;AAC/C,kBAAQ,IAAI;AAAA,QACd;AAAA,MACF;AAEA,WAAK,IAAI,iBAAiB,WAAW,OAAO;AAC5C,WAAK,IAAI,KAAK,KAAK,UAAU,EAAE,MAAM,OAAO,CAAC,CAAC;AAAA,IAChD,CAAC;AAAA,EACH;AAAA;AAAA,EAIQ,SAAS,OAA6B;AAC5C,UAAM,gBAAgB,KAAK;AAC3B,SAAK,SAAS;AACd,SAAK,KAAK,gBAAgB,EAAE,OAAO,cAAc,CAAC;AAAA,EACpD;AAAA,EAEA,MAAc,aAAa,QAAuC;AAChE,UAAM,SAAS,KAAK,WAAW,IAAI,OAAO,QAAQ;AAClD,QAAI,UAAU,OAAO,YAAY,KAAK,IAAI,IAAI,KAAO;AACnD,aAAO,OAAO;AAAA,IAChB;AAGA,QAAI,OAAO,YAAY,WAAW;AAChC,aAAO,OAAO,YAAY;AAAA,IAC5B;AAIA,UAAM,WAAW,KAAK,gBAAgB;AACtC,QAAI,SAAS,WAAW,OAAO,KAAK,SAAS,SAAS,WAAW,GAAG;AAClE,aAAO;AAAA,IACT;AAGA,UAAM,eAAe,SAAS,QAAQ,UAAU,UAAU,EAAE,QAAQ,SAAS,SAAS;AACtF,UAAM,WAAW,MAAM,MAAM,GAAG,YAAY,eAAe;AAAA,MACzD,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAC9C,MAAM,KAAK,UAAU;AAAA,QACnB,UAAU,OAAO;AAAA,QACjB,QAAQ,OAAO,YAAY;AAAA,MAC7B,CAAC;AAAA,IACH,CAAC;AAED,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,IAAI,MAAM,gBAAgB,SAAS,UAAU,EAAE;AAAA,IACvD;AAEA,UAAM,EAAE,OAAO,UAAU,IAAI,MAAM,SAAS,KAAK;AAEjD,SAAK,WAAW,IAAI,OAAO,UAAU;AAAA,MACnC;AAAA,MACA,WAAW,KAAK,IAAI,IAAI,YAAY;AAAA,IACtC,CAAC;AAED,WAAO;AAAA,EACT;AAAA,EAEA,MAAc,UAAyB;AAErC,UAAM,QAAQ,IAAI;AAAA;AAAA,OAEf,YAAY;AACX,aAAK,MAAM,IAAI,oBAAoB;AAAA,UACjC,UAAU;AAAA,UACV,UAAU;AAAA,QACZ,CAAC;AACD,cAAM,KAAK,IAAI,KAAK;AAAA,MACtB,GAAG;AAAA;AAAA,OAEF,YAAY;AACX,aAAK,MAAM,IAAI,mBAAmB;AAAA,UAChC,UAAU;AAAA,UACV,SAAS;AAAA,UACT,YAAY;AAAA,UACZ,WAAW;AAAA,QACb,CAAC;AACD,cAAM,KAAK,IAAI,KAAK;AAAA,MACtB,GAAG;AAAA,IACL,CAAC;AAAA,EACH;AAAA,EAEA,MAAc,UAAyB;AAGrC,UAAM,SAAS,KAAK,gBAAgB,QAAQ,UAAU;AAEtD,SAAK,MAAM,IAAI,kBAAkB;AAAA,MAC/B,UAAU;AAAA,MACV,SAAS;AAAA,IACX,CAAC;AAED,UAAM,KAAK,IAAI,KAAK;AAGpB,UAAM,KAAK,aAAa;AAAA,EAC1B;AAAA,EAEA,MAAc,eAA8B;AAC1C,QAAI,CAAC,KAAK,KAAK;AACb,YAAM,IAAI,MAAM,yCAAyC;AAAA,IAC3D;AAEA,SAAK,WAAW,IAAI,oBAAoB;AAAA,MACtC,KAAK,KAAK;AAAA,MACV,YAAY;AAAA,MACZ,eAAe;AAAA,IACjB,CAAC;AAED,UAAM,KAAK,SAAS,WAAW;AAG/B,SAAK,SAAS,GAAG,eAAe,CAAC,UAAwB;AAEvD,WAAK,KAAK,aAAa;AAAA,QACrB,aAAa;AAAA,QACb,KAAK,CAAC,SAAiB;AACrB,gBAAM,MAAO,gBAAsC,QAAQ,IAAI;AAC/D,iBAAO,OAAO,IAAI,MAAM,GAAG,IAAI;AAAA,QACjC;AAAA,QACA,WAAW,KAAK,IAAI;AAAA;AAAA,QACpB,aAAa;AAAA;AAAA,MACf,CAAC;AAAA,IACH,CAAC;AAED,SAAK,SAAS,GAAG,qBAAqB,MAAM;AAC1C,WAAK,aAAa;AAClB,WAAK,SAAS,MAAM;AACpB,WAAK,KAAK,oBAAoB,EAAE,YAAY,EAAE,CAAC;AAAA,IACjD,CAAC;AAED,SAAK,SAAS,GAAG,SAAS,CAAC,UAAiB;AAC1C,cAAQ,MAAM,+BAA+B,KAAK;AAClD,WAAK,KAAK,oBAAoB;AAAA,QAC5B;AAAA,QACA,aAAa;AAAA,MACf,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA,EAEA,MAAc,iBAAiB,WAAmB,QAAsC;AACtF,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,YAAM,QAAQ,IAAI,IAAI,GAAG,KAAK,gBAAgB,SAAS,QAAQ,QAAQ,IAAI,CAAC,KAAK;AACjF,YAAM,aAAa,IAAI,aAAa,OAAO,SAAS;AACpD,YAAM,aAAa,IAAI,eAAe,OAAO,OAAO,WAAW;AAE/D,WAAK,KAAK,IAAI,UAAU,MAAM,SAAS,CAAC;AAExC,WAAK,GAAG,SAAS,MAAM;AAErB,aAAK,IAAI,KAAK,KAAK,UAAU;AAAA,UAC3B,MAAM;AAAA,UACN,OAAO;AAAA,UACP,UAAU,OAAO,OAAO;AAAA,UACxB,cAAc,OAAO;AAAA,QACvB,CAAC,CAAC;AAAA,MACJ;AAEA,WAAK,GAAG,YAAY,CAAC,UAAU;AAC7B,aAAK,uBAAuB,KAAK,MAAM,MAAM,IAAI,CAAC;AAAA,MACpD;AAEA,WAAK,GAAG,UAAU,MAAM;AACtB,eAAO,IAAI,MAAM,6BAA6B,CAAC;AAAA,MACjD;AAEA,WAAK,GAAG,UAAU,CAAC,UAAU;AAC3B,aAAK,iBAAiB,KAAK;AAAA,MAC7B;AAGA,YAAM,cAAc,WAAW,MAAM;AACnC,eAAO,IAAI,MAAM,cAAc,CAAC;AAAA,MAClC,GAAG,GAAK;AAER,YAAM,cAAc,CAAC,UAAwB;AAC3C,cAAM,OAAO,KAAK,MAAM,MAAM,IAAI;AAClC,YAAI,KAAK,SAAS,gBAAgB;AAChC,uBAAa,WAAW;AACxB,eAAK,IAAI,oBAAoB,WAAW,WAAW;AACnD,kBAAQ;AAAA,QACV,WAAW,KAAK,SAAS,eAAe;AACtC,uBAAa,WAAW;AACxB,iBAAO,IAAI,MAAM,KAAK,OAAO,CAAC;AAAA,QAChC;AAAA,MACF;AAEA,WAAK,GAAG,iBAAiB,WAAW,WAAW;AAAA,IACjD,CAAC;AAAA,EACH;AAAA,EAEQ,uBAAuB,MAAqC;AAClE,YAAQ,KAAK,MAAM;AAAA,MACjB,KAAK;AACH,aAAK,SAAS,UAAU;AACxB,aAAK,aAAa;AAClB,aAAK,KAAK,qBAAqB;AAAA,UAC7B,MAAM,KAAK;AAAA,UACX,SAAS,KAAK;AAAA,QAChB,CAAC;AAED,YAAI,KAAK,SAAS;AAChB,eAAK,kBAAkB;AAAA,YACrB,EAAE,CAAC,KAAK,OAAiB,GAAG,IAAI;AAAA,YAChC;AAAA,UACF;AAAA,QACF;AAEA,YAAI,KAAK,UAAU;AACjB,eAAK,SAAS,MAAM;AAAA,QACtB;AACA;AAAA,MAEF,KAAK;AACH,aAAK,KAAK,qBAAqB;AAAA,UAC7B,MAAM,KAAK;AAAA,UACX,QAAQ,KAAK;AAAA,QACf,CAAC;AACD;AAAA,MAEF,KAAK;AAEH,YAAI,KAAK,SAAS,KAAK,UAAU;AAC/B,gBAAM,YAAY,KAAK,oBAAoB,KAAK,KAAe;AAC/D,gBAAM,QAAQ,IAAI,WAAW,SAAS;AACtC,eAAK,SAAS,aAAa,KAAK,EAAE,MAAM,CAAC,UAAU;AACjD,oBAAQ,MAAM,qCAAqC,KAAK;AAAA,UAC1D,CAAC;AAAA,QACH;AACA;AAAA,MAEF,KAAK;AAEH,YAAI,KAAK,UAAU;AACjB,eAAK,SAAS,IAAI,EAAE,MAAM,CAAC,UAAU;AACnC,oBAAQ,MAAM,mCAAmC,KAAK;AAAA,UACxD,CAAC;AAAA,QACH;AAEA;AAAA,MAEF,KAAK;AACH,aAAK,aAAa;AAAA,UAChB,MAAM;AAAA,UACN,SAAS,KAAK;AAAA,UACd,WAAW,KAAK,IAAI;AAAA,UACpB,SAAS,KAAK;AAAA,QAChB,CAAC;AACD,aAAK,KAAK,mBAAmB;AAAA,UAC3B,UAAU,KAAK;AAAA,UACf,YAAY,KAAK,cAAwB;AAAA,QAC3C,CAAC;AACD;AAAA,MAEF,KAAK;AACH,aAAK,KAAK,kBAAkB;AAAA,UAC1B,cAAc,KAAK;AAAA,UACnB,YAAY,KAAK;AAAA,QACnB,CAAC;AACD;AAAA,MAEF,KAAK;AACH,aAAK,KAAK,oBAAoB;AAAA,UAC5B,OAAO,IAAI,MAAM,KAAK,OAAiB;AAAA,UACvC,aAAc,KAAK,eAA2B;AAAA,QAChD,CAAC;AACD;AAAA,IACJ;AAAA,EACF;AAAA,EAEQ,wBAA8B;AAIpC,QAAI,KAAK,YAAY,WAAW,EAAG;AAGnC,UAAM,cAAc,KAAK,YAAY,OAAO,CAACC,MAAK,QAAQA,OAAM,IAAI,QAAQ,CAAC;AAI7E,QAAI,cAAc,IAAM;AAExB,UAAM,QAAQ,IAAI,aAAa,WAAW;AAC1C,QAAI,SAAS;AACb,eAAW,OAAO,KAAK,aAAa;AAClC,YAAM,IAAI,KAAK,MAAM;AACrB,gBAAU,IAAI;AAAA,IAChB;AACA,SAAK,cAAc,CAAC;AAIpB,QAAI,MAAM;AACV,aAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,aAAO,MAAM,CAAC,IAAI,MAAM,CAAC;AAAA,IAC3B;AACA,UAAM,MAAM,KAAK,KAAK,MAAM,MAAM,MAAM;AAGxC,QAAI,MAAM,MAAM;AACd,cAAQ,MAAM,qCAAqC,EAAE,KAAK,SAAS,MAAM,OAAO,CAAC;AACjF;AAAA,IACF;AAGA,QAAI,KAAK,KAAK;AACZ,WAAK,SAAS,WAAW;AACzB,WAAK,KAAK,qBAAqB,EAAE,WAAW,KAAK,IAAI,EAAE,CAAC;AAExD,WAAK,IAAI,WAAW,KAAK,EAAE,KAAK,CAAC,WAAsD;AACrF,aAAK,KAAK,yBAAyB;AAAA,UACjC,MAAM,OAAO;AAAA,UACb,YAAY;AAAA,QACd,CAAC;AACD,aAAK,KAAK,mBAAmB,EAAE,WAAW,KAAK,IAAI,GAAG,YAAY,OAAO,gBAAgB,CAAC;AAG1F,cAAM,YAAY,OAAO,KAAK,KAAK;AACnC,YAAI,WAAW;AACb,eAAK,SAAS,SAAS,EAAE,MAAM,CAAC,UAAmB;AACjD,oBAAQ,MAAM,gCAAgC,KAAK;AAAA,UACrD,CAAC;AAAA,QACH;AAAA,MACF,CAAC,EAAE,MAAM,CAAC,UAAmB;AAC3B,gBAAQ,MAAM,oCAAoC,KAAK;AAAA,MACzD,CAAC;AAAA,IACH;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAc,oBAAoB,OAAoD;AAEpF,UAAM,UAAU,iBAAiB,eAC7B,QACA,KAAK,eAAe,KAAK;AAG7B,QAAI,KAAK,KAAK;AAEZ,YAAM,YAAY,KAAK,IAAI,aAAa;AAGxC,eAAS,IAAI,GAAG,IAAI,aAAa,QAAQ,QAAQ,KAAK,WAAW;AAC/D,cAAM,QAAQ,QAAQ,MAAM,GAAG,IAAI,SAAS;AAC5C,cAAM,SAAS,MAAM,KAAK,IAAI,QAAQ,KAAK;AAG3C,YAAI,OAAO,UAAU;AACnB,iBAAO;AAAA,QACT;AAAA,MACF;AAEA,aAAO;AAAA,IACT;AAGA,QAAI,MAAM;AACV,aAAS,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;AACvC,aAAO,QAAQ,CAAC,IAAI,QAAQ,CAAC;AAAA,IAC/B;AACA,UAAM,MAAM,KAAK,KAAK,MAAM,QAAQ,MAAM;AAC1C,WAAO,MAAM;AAAA,EACf;AAAA,EAEQ,eAAe,OAAiC;AACtD,UAAM,UAAU,IAAI,aAAa,MAAM,MAAM;AAC7C,aAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,cAAQ,CAAC,IAAI,MAAM,CAAC,IAAI;AAAA,IAC1B;AACA,WAAO;AAAA,EACT;AAAA,EAEQ,oBAAoB,QAA6B;AACvD,UAAM,eAAe,KAAK,MAAM;AAChC,UAAM,QAAQ,IAAI,WAAW,aAAa,MAAM;AAChD,aAAS,IAAI,GAAG,IAAI,aAAa,QAAQ,KAAK;AAC5C,YAAM,CAAC,IAAI,aAAa,WAAW,CAAC;AAAA,IACtC;AACA,WAAO,MAAM;AAAA,EACf;AAAA,EAEQ,aAAa,SAAoC;AACvD,SAAK,QAAQ,KAAK,OAAO;AACzB,SAAK,KAAK,kBAAkB,EAAE,cAAc,KAAK,QAAQ,OAAO,CAAC;AAAA,EACnE;AAAA,EAEQ,iBAAiB,OAAyB;AAChD,SAAK,eAAe;AAEpB,QAAI,MAAM,SAAS,KAAM;AAEvB,UAAI,KAAK,sBAAsB,KAAK,sBAAsB;AACxD,aAAK;AACL,mBAAW,MAAM;AACf,cAAI,KAAK,eAAe;AACtB,iBAAK,QAAQ,KAAK,aAAa,EAAE,MAAM,MAAM;AAAA,YAE7C,CAAC;AAAA,UACH;AAAA,QACF,GAAG,KAAK,IAAI,GAAG,KAAK,mBAAmB,IAAI,GAAI;AAAA,MACjD,OAAO;AACL,aAAK,SAAS,OAAO;AACrB,aAAK,KAAK,oBAAoB;AAAA,UAC5B,OAAO,IAAI,MAAM,mCAAmC;AAAA,UACpD,aAAa;AAAA,QACf,CAAC;AAAA,MACH;AAAA,IACF;AAEA,SAAK,KAAK,qBAAqB,EAAE,QAAQ,MAAM,UAAU,oBAAoB,CAAC;AAAA,EAChF;AACF;;;AC5oBA,IAAM,0BAAN,MAA6D;AAAA,EAW3D,YACE,QACA,SACA;AARF,SAAQ,WAAkC,CAAC;AAC3C,SAAQ,WAAW,oBAAI,IAAoB;AAQzC,SAAK,YAAY,OAAO;AACxB,SAAK,UAAU;AACf,SAAK,WAAW;AAChB,SAAK,YAAY,KAAK,IAAI;AAC1B,SAAK,kBAAkB,KAAK,IAAI;AAChC,SAAK,qBAAqB,IAAI,kBAAkB;AAEhD,QAAI,OAAO,SAAS;AAClB,WAAK,mBAAmB,UAAU,OAAO,OAAkE;AAAA,IAC7G;AAAA,EACF;AAAA,EAEA,IAAI,UAAqB;AACvB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,SAAwB;AAC1B,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,QAAwB;AAC1B,WAAO,KAAK,SAAS;AAAA,EACvB;AAAA,EAEA,IAAI,UAAiC;AACnC,WAAO,CAAC,GAAG,KAAK,QAAQ;AAAA,EAC1B;AAAA,EAEA,IAAI,UAA0B;AAC5B,WAAO,CAAC;AAAA,EACV;AAAA,EAEA,IAAI,iBAAyB;AAC3B,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,MAAM,QAAuB;AAC3B,UAAM,KAAK,SAAS,QAAQ,KAAK,OAAO;AACxC,SAAK,kBAAkB,KAAK,IAAI;AAAA,EAClC;AAAA,EAEA,MAAM,MAAqB;AACzB,UAAM,KAAK,SAAS,WAAW;AAAA,EACjC;AAAA,EAEA,UAAU,OAAwC;AAChD,SAAK,SAAS,UAAU,KAAK;AAC7B,SAAK,kBAAkB,KAAK,IAAI;AAAA,EAClC;AAAA,EAEA,MAAM,SAAS,MAA6B;AAC1C,UAAM,KAAK,SAAS,SAAS,IAAI;AACjC,SAAK,kBAAkB,KAAK,IAAI;AAAA,EAClC;AAAA,EAEA,YAAkB;AAChB,SAAK,SAAS,UAAU;AACxB,SAAK,kBAAkB,KAAK,IAAI;AAAA,EAClC;AAAA,EAEA,WAAW,SAA+B;AACxC,SAAK,mBAAmB,IAAI,OAAO;AAAA,EACrC;AAAA,EAEA,WAAW,KAAa,OAAqB;AAC3C,SAAK,SAAS,IAAI,KAAK,KAAK;AAAA,EAC9B;AAAA,EAEA,cAAc,KAAmB;AAC/B,SAAK,SAAS,OAAO,GAAG;AAAA,EAC1B;AAAA,EAEA,aAAqC;AACnC,WAAO,OAAO,YAAY,KAAK,QAAQ;AAAA,EACzC;AAAA,EAEA,SAA0B;AACxB,WAAO;AAAA,MACL,WAAW,KAAK;AAAA,MAChB,UAAU,KAAK,QAAQ,OAAO;AAAA,MAC9B,aAAa,KAAK,QAAQ,OAAO;AAAA,MACjC,SAAS,KAAK;AAAA,MACd,SAAS,OAAO,YAAY,KAAK,QAAQ;AAAA,MACzC,SAAS,KAAK;AAAA,MACd,WAAW,KAAK;AAAA,MAChB,gBAAgB,KAAK;AAAA,IACvB;AAAA,EACF;AAAA,EAEA,OAAO,UAAiC;AACtC,SAAK,WAAW,CAAC,GAAG,SAAS,OAAO;AACpC,SAAK,WAAW,IAAI,IAAI,OAAO,QAAQ,SAAS,OAAO,CAAC;AACxD,SAAK,kBAAkB,SAAS;AAAA,EAClC;AAAA,EAEA,cAAoB;AAClB,SAAK,WAAW,KAAK,SAAS,WAAW;AAAA,EAC3C;AACF;AAKO,IAAM,2BAAN,cAAuC,aAAiC;AAAA,EAgB7E,YAAY,QAA4B;AACtC,UAAM;AAVR;AAAA,SAAQ,WAAW,oBAAI,IAAqC;AAG5D;AAAA,SAAQ,UAAU,oBAAI,IAA0B;AAGhD;AAAA,SAAQ,sBAA6D;AACrE,SAAiB,2BAA2B;AAI1C,SAAK,SAAS;AAAA,MACZ,qBAAqB;AAAA,MACrB,YAAY;AAAA,MACZ,GAAG;AAAA,IACL;AAGA,SAAK,UAAU,IAAI,iBAAiB,OAAO,OAAO;AAAA,EACpD;AAAA;AAAA;AAAA;AAAA,EAKA,eAAe,QAA4B;AACzC,SAAK,QAAQ,IAAI,OAAO,UAAU,MAAM;AAAA,EAC1C;AAAA;AAAA;AAAA;AAAA,EAKA,iBAAiB,UAAwB;AACvC,SAAK,QAAQ,OAAO,QAAQ;AAAA,EAC9B;AAAA;AAAA;AAAA;AAAA,EAKA,UAAU,UAA4C;AACpD,WAAO,KAAK,QAAQ,IAAI,QAAQ;AAAA,EAClC;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,cACJ,UACA,UAAkC,CAAC,GACL;AAC9B,UAAM,SAAS,KAAK,QAAQ,IAAI,QAAQ;AACxC,QAAI,CAAC,QAAQ;AACX,YAAM,IAAI,MAAM,qBAAqB,QAAQ,EAAE;AAAA,IACjD;AAEA,UAAM,YAAY,QAAQ,aAAa,KAAK,kBAAkB;AAE9D,UAAM,gBAA+B;AAAA,MACnC;AAAA,MACA;AAAA,MACA,cAAc,QAAQ;AAAA,MACtB,OAAO,QAAQ;AAAA,MACf,SAAS,QAAQ;AAAA,MACjB,UAAU,QAAQ;AAAA,IACpB;AAEA,UAAM,UAAU,IAAI,wBAAwB,eAAe,KAAK,OAAO;AAEvE,SAAK,SAAS,IAAI,WAAW,OAAO;AAGpC,SAAK,qBAAqB,KAAK,SAAS,SAAS;AAGjD,UAAM,QAAQ,MAAM;AAEpB,SAAK,KAAK,mBAAmB,EAAE,WAAW,SAAS,CAAC;AAEpD,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,WAAW,WAAkC;AACjD,UAAM,UAAU,KAAK,SAAS,IAAI,SAAS;AAC3C,QAAI,SAAS;AACX,YAAM,QAAQ,IAAI;AAClB,WAAK,SAAS,OAAO,SAAS;AAC9B,WAAK,KAAK,iBAAiB,EAAE,WAAW,QAAQ,mBAAmB,CAAC;AAAA,IACtE;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,WAAW,WAAoD;AAC7D,WAAO,KAAK,SAAS,IAAI,SAAS;AAAA,EACpC;AAAA;AAAA;AAAA;AAAA,EAKA,kBAAkB,UAAyC;AACzD,WAAO,MAAM,KAAK,KAAK,SAAS,OAAO,CAAC,EACrC,OAAO,OAAK,EAAE,OAAO,OAAO,aAAa,QAAQ;AAAA,EACtD;AAAA;AAAA;AAAA;AAAA,EAKA,wBAA8B;AAC5B,QAAI,KAAK,oBAAqB;AAE9B,SAAK,sBAAsB,YAAY,YAAY;AACjD,YAAM,KAAK,mBAAmB;AAAA,IAChC,GAAG,KAAK,wBAAwB;AAAA,EAClC;AAAA;AAAA;AAAA;AAAA,EAKA,uBAA6B;AAC3B,QAAI,KAAK,qBAAqB;AAC5B,oBAAc,KAAK,mBAAmB;AACtC,WAAK,sBAAsB;AAAA,IAC7B;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,UAAyB;AAC7B,SAAK,qBAAqB;AAG1B,UAAM,cAAc,MAAM,KAAK,KAAK,SAAS,OAAO,CAAC,EAAE,IAAI,OAAK,EAAE,IAAI,CAAC;AACvE,UAAM,QAAQ,IAAI,WAAW;AAC7B,SAAK,SAAS,MAAM;AAGpB,UAAM,KAAK,QAAQ,WAAW;AAAA,EAChC;AAAA;AAAA,EAIQ,oBAA4B;AAClC,WAAO,QAAQ,KAAK,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,OAAO,GAAG,CAAC,CAAC;AAAA,EACtE;AAAA,EAEQ,qBAAqB,SAAoB,WAAyB;AAExE,UAAM,SAAoC;AAAA,MACxC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAEA,eAAW,SAAS,QAAQ;AAC1B,cAAQ,GAAG,OAAO,CAAC,SAAS;AAC1B,cAAM,YAAY;AAClB,aAAK,KAAK,OAAO,EAAE,GAAG,WAAW,UAAU,CAAkC;AAAA,MAC/E,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EAEA,MAAc,qBAAoC;AAChD,QAAI;AACF,YAAM,KAAK,QAAQ,YAAY;AAAA,IACjC,QAAQ;AAAA,IAER;AAAA,EACF;AACF;;;AC9SO,IAAM,iBAAN,MAAM,eAAc;AAAA,EAApB;AACL,SAAQ,UAAU,oBAAI,IAA0B;AAChD,SAAQ,SAAS,oBAAI,IAAyB;AAC9C,SAAQ,QAAQ,oBAAI,IAAyB;AAC7C,SAAQ,wBAAwB,oBAAI,IAAkC;AAAA;AAAA;AAAA;AAAA;AAAA,EAetE,SACE,QACA,QAAqB,eAAc,eACnC,sBACM;AACN,SAAK,QAAQ,IAAI,OAAO,UAAU,MAAM;AACxC,SAAK,OAAO,IAAI,OAAO,UAAU,KAAK;AACtC,SAAK,MAAM,IAAI,OAAO,UAAU;AAAA,MAC9B,iBAAiB;AAAA,MACjB,oBAAoB;AAAA,MACpB,YAAY;AAAA,MACZ,mBAAmB;AAAA,MACnB,iBAAiB,KAAK,IAAI;AAAA,MAC1B,gBAAgB,KAAK,IAAI;AAAA,IAC3B,CAAC;AAED,QAAI,sBAAsB;AACxB,WAAK,sBAAsB,IAAI,OAAO,UAAU,oBAAoB;AAAA,IACtE;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,WAAW,UAAwB;AACjC,SAAK,QAAQ,OAAO,QAAQ;AAC5B,SAAK,OAAO,OAAO,QAAQ;AAC3B,SAAK,MAAM,OAAO,QAAQ;AAC1B,SAAK,sBAAsB,OAAO,QAAQ;AAAA,EAC5C;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,UAA4C;AAC9C,WAAO,KAAK,QAAQ,IAAI,QAAQ;AAAA,EAClC;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,UAA2B;AAC7B,WAAO,KAAK,QAAQ,IAAI,QAAQ;AAAA,EAClC;AAAA;AAAA;AAAA;AAAA,EAKA,eAAyB;AACvB,WAAO,MAAM,KAAK,KAAK,QAAQ,KAAK,CAAC;AAAA,EACvC;AAAA;AAAA;AAAA;AAAA,EAKA,iBAAiB,UAA2B;AAC1C,UAAM,QAAQ,KAAK,OAAO,IAAI,QAAQ;AACtC,UAAM,QAAQ,KAAK,MAAM,IAAI,QAAQ;AAErC,QAAI,CAAC,SAAS,CAAC,MAAO,QAAO;AAE7B,WAAO,MAAM,kBAAkB,MAAM;AAAA,EACvC;AAAA;AAAA;AAAA;AAAA,EAKA,eAAe,UAA2B;AACxC,UAAM,QAAQ,KAAK,OAAO,IAAI,QAAQ;AACtC,UAAM,QAAQ,KAAK,MAAM,IAAI,QAAQ;AAErC,QAAI,CAAC,SAAS,CAAC,MAAO,QAAO;AAG7B,SAAK,iBAAiB,QAAQ;AAE9B,WAAO,MAAM,qBAAqB,MAAM;AAAA,EAC1C;AAAA;AAAA;AAAA;AAAA,EAKA,YAAY,UAAkB,SAA0B;AACtD,UAAM,QAAQ,KAAK,OAAO,IAAI,QAAQ;AACtC,UAAM,QAAQ,KAAK,MAAM,IAAI,QAAQ;AAErC,QAAI,CAAC,SAAS,CAAC,MAAO,QAAO;AAG7B,SAAK,gBAAgB,QAAQ;AAE7B,WAAO,MAAM,oBAAoB,WAAW,MAAM;AAAA,EACpD;AAAA;AAAA;AAAA;AAAA,EAKA,kBAAkB,UAAwB;AACxC,UAAM,QAAQ,KAAK,MAAM,IAAI,QAAQ;AACrC,QAAI,OAAO;AACT,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,kBAAkB,UAAwB;AACxC,UAAM,QAAQ,KAAK,MAAM,IAAI,QAAQ;AACrC,QAAI,SAAS,MAAM,kBAAkB,GAAG;AACtC,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,cAAc,UAAwB;AACpC,UAAM,QAAQ,KAAK,MAAM,IAAI,QAAQ;AACrC,QAAI,OAAO;AACT,WAAK,iBAAiB,QAAQ;AAC9B,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,aAAa,UAAkB,QAAsB;AACnD,UAAM,QAAQ,KAAK,MAAM,IAAI,QAAQ;AACrC,QAAI,OAAO;AACT,YAAM,cAAc;AAAA,IACtB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,mBAAmB,UAAkB,SAAuB;AAC1D,UAAM,QAAQ,KAAK,MAAM,IAAI,QAAQ;AACrC,QAAI,OAAO;AACT,WAAK,gBAAgB,QAAQ;AAC7B,YAAM,qBAAqB;AAAA,IAC7B;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,aAAa,UAAmC;AACpD,UAAM,SAAS,KAAK,QAAQ,IAAI,QAAQ;AACxC,QAAI,CAAC,QAAQ;AACX,YAAM,IAAI,MAAM,qBAAqB,QAAQ,EAAE;AAAA,IACjD;AAGA,UAAM,WAAW,KAAK,sBAAsB,IAAI,QAAQ;AACxD,QAAI,UAAU;AACZ,YAAM,QAAQ,MAAM,SAAS;AAC7B,aAAO,YAAY,YAAY;AAC/B,aAAO;AAAA,IACT;AAGA,QAAI,OAAO,YAAY,WAAW;AAChC,aAAO,OAAO,YAAY;AAAA,IAC5B;AAEA,UAAM,IAAI,MAAM,uCAAuC,QAAQ,EAAE;AAAA,EACnE;AAAA;AAAA;AAAA;AAAA,EAKA,kBAAkB,UAAkB,aAAyD;AAC3F,UAAM,SAAS,KAAK,QAAQ,IAAI,QAAQ;AACxC,QAAI,QAAQ;AACV,aAAO,cAAc,EAAE,GAAG,OAAO,aAAa,GAAG,YAAY;AAAA,IAC/D;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,SAAS,UAA2C;AAClD,WAAO,KAAK,MAAM,IAAI,QAAQ;AAAA,EAChC;AAAA;AAAA;AAAA;AAAA,EAKA,SAAS,UAA2C;AAClD,WAAO,KAAK,OAAO,IAAI,QAAQ;AAAA,EACjC;AAAA;AAAA;AAAA;AAAA,EAKA,YAAY,UAAkB,OAAmC;AAC/D,UAAM,WAAW,KAAK,OAAO,IAAI,QAAQ;AACzC,QAAI,UAAU;AACZ,WAAK,OAAO,IAAI,UAAU,EAAE,GAAG,UAAU,GAAG,MAAM,CAAC;AAAA,IACrD;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,WAAW,UAAwB;AACjC,UAAM,QAAQ,KAAK,MAAM,IAAI,QAAQ;AACrC,QAAI,OAAO;AACT,YAAM,qBAAqB;AAC3B,YAAM,aAAa;AACnB,YAAM,oBAAoB;AAC1B,YAAM,kBAAkB,KAAK,IAAI;AACjC,YAAM,iBAAiB,KAAK,IAAI;AAAA,IAClC;AAAA,EACF;AAAA;AAAA,EAIQ,iBAAiB,UAAwB;AAC/C,UAAM,QAAQ,KAAK,MAAM,IAAI,QAAQ;AACrC,QAAI,CAAC,MAAO;AAEZ,UAAM,MAAM,KAAK,IAAI;AACrB,QAAI,MAAM,MAAM,mBAAmB,KAAO;AACxC,YAAM,qBAAqB;AAC3B,YAAM,kBAAkB;AAAA,IAC1B;AAAA,EACF;AAAA,EAEQ,gBAAgB,UAAwB;AAC9C,UAAM,QAAQ,KAAK,MAAM,IAAI,QAAQ;AACrC,QAAI,CAAC,MAAO;AAEZ,UAAM,MAAM,KAAK,IAAI;AACrB,UAAM,aAAa,KAAK,KAAK,KAAK;AAClC,QAAI,MAAM,MAAM,kBAAkB,YAAY;AAC5C,YAAM,oBAAoB;AAC1B,YAAM,iBAAiB;AAAA,IACzB;AAAA,EACF;AACF;AAAA;AAAA;AAAA;AAtQa,eASK,gBAA6B;AAAA,EAC3C,aAAa;AAAA,EACb,mBAAmB;AAAA,EACnB,0BAA0B;AAAA,EAC1B,uBAAuB;AACzB;AAdK,IAAM,gBAAN;;;ACbA,IAAM,mBAAN,cAA+B,aAA8B;AAAA,EAUlE,YAAY,SAA0B,CAAC,GAAG;AACxC,UAAM;AARR,SAAQ,iBAAiB;AACzB,SAAQ,gBAAgC,CAAC;AACzC,SAAQ,YAAY;AACpB,SAAQ,eAAoC;AAC5C,SAAQ,oBAAoB;AAC5B,SAAQ,gBAAgB;AAItB,SAAK,SAAS;AAAA,MACZ,YAAY;AAAA,MACZ,YAAY;AAAA,MACZ,aAAa;AAAA,MACb,YAAY;AAAA,MACZ,GAAG;AAAA,IACL;AAEA,SAAK,cAAc,IAAI,aAAa,KAAK,OAAO,UAAU;AAAA,EAC5D;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,aAA4B;AAChC,QAAI,CAAC,KAAK,cAAc;AACtB,WAAK,eAAe,IAAI,aAAa,EAAE,YAAY,KAAK,OAAO,WAAW,CAAC;AAAA,IAC7E;AAEA,QAAI,KAAK,aAAa,UAAU,aAAa;AAC3C,YAAM,KAAK,aAAa,OAAO;AAAA,IACjC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,UAAU,OAA2B;AAEnC,SAAK,cAAc,KAAK,KAAK;AAG7B,SAAK,mBAAmB,KAAK;AAG7B,QAAI,CAAC,KAAK,aAAa,KAAK,cAAc,SAAS,GAAG;AACpD,WAAK,cAAc;AAAA,IACrB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,mBAAmB,OAA2B;AACpD,QAAI,SAAS;AAEb,WAAO,SAAS,MAAM,QAAQ;AAC5B,YAAM,YAAY,KAAK,OAAO,aAAa,KAAK;AAChD,YAAM,SAAS,KAAK,IAAI,WAAW,MAAM,SAAS,MAAM;AAExD,WAAK,YAAY,IAAI,MAAM,SAAS,QAAQ,SAAS,MAAM,GAAG,KAAK,cAAc;AACjF,WAAK,kBAAkB;AACvB,gBAAU;AAGV,UAAI,KAAK,kBAAkB,KAAK,OAAO,YAAY;AACjD,aAAK,KAAK,gBAAgB,EAAE,OAAO,IAAI,aAAa,KAAK,WAAW,EAAE,CAAC;AAGvE,cAAM,eAAe,KAAK,OAAO,aAAa,KAAK,OAAO;AAC1D,aAAK,YAAY,WAAW,GAAG,YAAY;AAC3C,aAAK,iBAAiB,KAAK,OAAO;AAAA,MACpC;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,gBAA+B;AAC3C,QAAI,CAAC,KAAK,gBAAgB,KAAK,UAAW;AAE1C,SAAK,YAAY;AACjB,SAAK,oBAAoB,KAAK,aAAa;AAC3C,SAAK,gBAAgB;AAErB,SAAK,KAAK,kBAAkB,CAAC,CAAC;AAE9B,UAAM,KAAK,qBAAqB;AAAA,EAClC;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,uBAAsC;AAClD,QAAI,CAAC,KAAK,aAAc;AAExB,WAAO,KAAK,cAAc,SAAS,GAAG;AACpC,YAAM,QAAQ,KAAK,cAAc,MAAM;AAGvC,YAAM,SAAS,KAAK,aAAa,aAAa,GAAG,MAAM,QAAQ,KAAK,OAAO,UAAU;AACrF,aAAO,cAAc,OAAO,CAAC;AAE7B,YAAM,SAAS,KAAK,aAAa,mBAAmB;AACpD,aAAO,SAAS;AAChB,aAAO,QAAQ,KAAK,aAAa,WAAW;AAG5C,YAAM,WAAW,KAAK,oBAAoB,KAAK,gBAAgB,KAAK,OAAO;AAC3E,aAAO,MAAM,QAAQ;AAErB,WAAK,iBAAiB,MAAM;AAG5B,WAAK,WAAW;AAGhB,YAAM,IAAI,QAAQ,aAAW;AAC3B,eAAO,UAAU;AAAA,MACnB,CAAC;AAAA,IACH;AAEA,SAAK,YAAY;AACjB,SAAK,KAAK,gBAAgB,CAAC,CAAC;AAAA,EAC9B;AAAA;AAAA;AAAA;AAAA,EAKQ,aAAmB;AACzB,QAAI,CAAC,KAAK,aAAc;AAExB,UAAM,eAAe,KAAK,oBAAoB,KAAK,gBAAgB,KAAK,OAAO;AAC/E,UAAM,aAAa,KAAK,aAAa;AACrC,UAAM,WAAW,aAAa,gBAAgB;AAE9C,QAAI,KAAK,IAAI,OAAO,IAAI,KAAK,OAAO,YAAY;AAC9C,WAAK,KAAK,cAAc,EAAE,QAAQ,CAAC;AAAA,IACrC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,aAAmB;AACjB,SAAK,gBAAgB,CAAC;AACtB,SAAK,iBAAiB;AACtB,SAAK,YAAY,KAAK,CAAC;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA,EAKA,OAAa;AACX,SAAK,WAAW;AAChB,SAAK,YAAY;AAAA,EACnB;AAAA;AAAA;AAAA;AAAA,EAKA,sBAA8B;AAC5B,QAAI,CAAC,KAAK,aAAc,QAAO;AAC/B,WAAO,KAAK,aAAa,cAAc,KAAK;AAAA,EAC9C;AAAA;AAAA;AAAA;AAAA,EAKA,eAAwB;AACtB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,UAAgB;AACd,SAAK,KAAK;AACV,SAAK,cAAc,MAAM;AACzB,SAAK,eAAe;AAAA,EACtB;AACF;;;ACpLO,IAAM,sBAAN,cAAkC,aAAiC;AAAA,EAWxE,YAAY,SAA6B,CAAC,GAAG;AAC3C,UAAM;AAVR,SAAQ,aAAa;AACrB,SAAQ,kBAAkB;AAC1B,SAAQ,iBAAiB;AACzB,SAAQ,eAAqD;AAC7D,SAAQ,eAAe;AAGvB;AAAA,SAAQ,mCAAmC;AAIzC,SAAK,SAAS;AAAA,MACZ,cAAc;AAAA;AAAA,MACd,qBAAqB;AAAA;AAAA,MACrB,kBAAkB;AAAA;AAAA,MAClB,SAAS;AAAA,MACT,GAAG;AAAA,IACL;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,iBAAiB,gBAAwB,cAAsB,GAAS;AACtE,QAAI,CAAC,KAAK,OAAO,QAAS;AAE1B,QAAI,iBAAiB,KAAK,OAAO,cAAc;AAC7C,WAAK,iBAAiB,eAAe,cAAc;AAAA,IACrD,OAAO;AACL,WAAK,kBAAkB;AAAA,IACzB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,aAAa,SAA0C;AACrD,QAAI,CAAC,KAAK,OAAO,QAAS;AAE1B,UAAM,MAAM,KAAK,aAAa,OAAO;AAIrC,UAAM,iBAAiB,KAAK,IAAI,MAAM,MAAM,CAAG;AAE/C,QAAI,iBAAiB,KAAK,OAAO,cAAc;AAC7C,WAAK,iBAAiB,GAAG;AAAA,IAC3B,OAAO;AACL,WAAK,kBAAkB;AAAA,IACzB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,cAAc,UAAyB;AACrC,SAAK,eAAe;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA,EAKA,WAAW,SAAwB;AACjC,SAAK,OAAO,UAAU;AACtB,QAAI,CAAC,SAAS;AACZ,WAAK,MAAM;AAAA,IACb;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,aAAa,QAA2C;AACtD,SAAK,SAAS,EAAE,GAAG,KAAK,QAAQ,GAAG,OAAO;AAAA,EAC5C;AAAA;AAAA;AAAA;AAAA,EAKA,QAAc;AACZ,SAAK,aAAa;AAClB,SAAK,kBAAkB;AACvB,SAAK,iBAAiB;AACtB,SAAK,mCAAmC;AACxC,QAAI,KAAK,cAAc;AACrB,mBAAa,KAAK,YAAY;AAC9B,WAAK,eAAe;AAAA,IACtB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,WAA8D;AAC5D,WAAO;AAAA,MACL,YAAY,KAAK;AAAA,MACjB,kBAAkB,KAAK,aAAa,KAAK,IAAI,IAAI,KAAK,kBAAkB;AAAA,IAC1E;AAAA,EACF;AAAA;AAAA,EAIQ,aAAa,SAA4C;AAC/D,QAAI,MAAM;AACV,UAAM,QAAQ,mBAAmB,aAAa,QAAQ;AAEtD,aAAS,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;AACvC,YAAM,SAAS,QAAQ,CAAC,IAAI;AAC5B,aAAO,SAAS;AAAA,IAClB;AAEA,WAAO,KAAK,KAAK,MAAM,QAAQ,MAAM;AAAA,EACvC;AAAA,EAEQ,iBAAiB,KAAmB;AAC1C,UAAM,MAAM,KAAK,IAAI;AACrB,SAAK,iBAAiB;AAGtB,QAAI,KAAK,cAAc;AACrB,mBAAa,KAAK,YAAY;AAC9B,WAAK,eAAe;AAAA,IACtB;AAGA,QAAI,CAAC,KAAK,YAAY;AACpB,WAAK,aAAa;AAClB,WAAK,kBAAkB;AACvB,WAAK,KAAK,mBAAmB,EAAE,IAAI,CAAC;AAAA,IACtC;AAGA,QAAI,KAAK,gBAAgB,CAAC,KAAK,kCAAkC;AAC/D,YAAM,iBAAiB,MAAM,KAAK;AAClC,UAAI,kBAAkB,KAAK,OAAO,qBAAqB;AACrD,aAAK,mCAAmC;AACxC,aAAK,KAAK,0BAA0B,EAAE,KAAK,YAAY,eAAe,CAAC;AAAA,MACzE;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,oBAA0B;AAChC,QAAI,CAAC,KAAK,WAAY;AAGtB,QAAI,CAAC,KAAK,cAAc;AACtB,WAAK,eAAe,WAAW,MAAM;AACnC,cAAM,aAAa,KAAK,iBAAiB,KAAK;AAC9C,aAAK,aAAa;AAClB,aAAK,eAAe;AAEpB,aAAK,mCAAmC;AACxC,aAAK,KAAK,gBAAgB,EAAE,WAAW,CAAC;AAAA,MAC1C,GAAG,KAAK,OAAO,gBAAgB;AAAA,IACjC;AAAA,EACF;AACF;;;ACRO,IAAM,2BAAiD;AAAA,EAC5D,cAAc;AAAA,EACd,sBAAsB;AAAA;AAAA,EACtB,kBAAkB;AAAA,EAClB,kBAAkB;AAAA,EAElB,QAAQ;AAAA,IACN;AAAA,MACE,MAAM;AAAA,MACN,WAAW,CAAC,cAAc;AAAA,MAC1B,aAAa,CAAC,CAAG;AAAA,MACjB,qBAAqB;AAAA,MACrB,qBAAqB;AAAA,MACrB,SAAS;AAAA,IACX;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,WAAW,CAAC,gBAAgB;AAAA,MAC5B,aAAa,CAAC,CAAG;AAAA,MACjB,qBAAqB;AAAA,MACrB,qBAAqB;AAAA,MACrB,SAAS;AAAA;AAAA,MACT,eAAe;AAAA,IACjB;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,WAAW,CAAC,oBAAoB,oBAAoB;AAAA,MACpD,aAAa,CAAC,KAAK,GAAG;AAAA,MACtB,qBAAqB;AAAA,MACrB,qBAAqB;AAAA,MACrB,SAAS;AAAA;AAAA,MACT,eAAe;AAAA,IACjB;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,WAAW,CAAC,cAAc;AAAA,MAC1B,aAAa,CAAC,CAAG;AAAA,MACjB,qBAAqB;AAAA,MACrB,qBAAqB;AAAA,MACrB,SAAS;AAAA,IACX;AAAA,EACF;AAAA,EAEA,aAAa;AAAA;AAAA,IAEX,EAAE,MAAM,QAAQ,IAAI,aAAa,SAAS,qBAAqB,UAAU,IAAI;AAAA,IAC7E,EAAE,MAAM,YAAY,IAAI,aAAa,SAAS,qBAAqB,UAAU,IAAI;AAAA;AAAA;AAAA,IAGjF,EAAE,MAAM,aAAa,IAAI,YAAY,SAAS,oBAAoB,UAAU,IAAI;AAAA;AAAA,IAGhF,EAAE,MAAM,YAAY,IAAI,YAAY,SAAS,kBAAkB,UAAU,IAAI;AAAA,IAC7E,EAAE,MAAM,QAAQ,IAAI,YAAY,SAAS,kBAAkB,UAAU,IAAI;AAAA;AAAA,IAGzE,EAAE,MAAM,YAAY,IAAI,QAAQ,SAAS,mBAAmB,UAAU,IAAI;AAAA;AAAA,IAG1E,EAAE,MAAM,aAAa,IAAI,QAAQ,SAAS,WAAW,UAAU,IAAI;AAAA,IACnE,EAAE,MAAM,YAAY,IAAI,QAAQ,SAAS,WAAW,UAAU,IAAI;AAAA;AAAA,IAGlE,EAAE,MAAM,YAAY,IAAI,aAAa,SAAS,aAAa,UAAU,IAAI;AAAA,EAC3E;AAAA,EAEA,iBAAiB;AAAA,IACf,EAAE,SAAS,SAAS,MAAM,iBAAiB,WAAW,KAAK,YAAY,EAAI;AAAA,IAC3E,EAAE,SAAS,OAAO,MAAM,eAAe,WAAW,KAAK,YAAY,IAAI;AAAA,IACvE,EAAE,SAAS,SAAS,MAAM,iBAAiB,WAAW,KAAK,YAAY,IAAI;AAAA,IAC3E,EAAE,SAAS,WAAW,MAAM,gBAAgB,WAAW,KAAK,YAAY,EAAI;AAAA,IAC5E,EAAE,SAAS,aAAa,MAAM,qBAAqB,WAAW,KAAK,YAAY,IAAI;AAAA,IACnF,EAAE,SAAS,QAAQ,MAAM,gBAAgB,WAAW,KAAK,YAAY,EAAI;AAAA,IACzE,EAAE,SAAS,WAAW,MAAM,mBAAmB,WAAW,KAAK,YAAY,EAAI;AAAA,IAC/E,EAAE,SAAS,WAAW,MAAM,mBAAmB,WAAW,GAAK,YAAY,EAAI;AAAA,EACjF;AAAA,EAEA,cAAc,CAAC,sBAAsB,uBAAuB,oBAAoB;AAClF;;;AC5NO,IAAM,iBAAN,cAA6B,aAAmC;AAAA,EA6BrE,YAAY,SAAwC,CAAC,GAAG;AACtD,UAAM;AA3BR,SAAQ,gBAAuC;AAG/C;AAAA,SAAQ,kBAA2B;AACnC,SAAQ,qBAA6B;AACrC,SAAQ,qBAA6B;AACrC,SAAQ,sBAA8B;AAGtC;AAAA,SAAQ,iBAAsC;AAC9C,SAAQ,oBAA4B;AACpC,SAAQ,qBAA6B;AACrC,SAAQ,sBAA8B;AAGtC;AAAA,SAAQ,cAAsB;AAC9B,SAAQ,gBAAwB;AAChC,SAAQ,qBAA6B;AAGrC;AAAA,SAAQ,iBAAyB;AACjC,SAAQ,iBAAyB;AAO/B,SAAK,SAAS,EAAE,GAAG,0BAA0B,GAAG,OAAO;AAGvD,UAAM,eAAe,KAAK,OAAO,OAAO;AAAA,MACtC,CAAC,MAAM,EAAE,SAAS,KAAK,OAAO;AAAA,IAChC;AACA,QAAI,CAAC,cAAc;AACjB,YAAM,IAAI,MAAM,kBAAkB,KAAK,OAAO,YAAY,aAAa;AAAA,IACzE;AACA,SAAK,eAAe;AACpB,SAAK,iBAAiB,KAAK,IAAI;AAC/B,SAAK,iBAAiB,KAAK,IAAI;AAG/B,SAAK,eAAe,KAAK,cAAc;AAAA,EACzC;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,QAA4B;AAC9B,WAAO,KAAK,aAAa;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,SAA0B;AAC5B,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,QAAQ,OAAkC;AAExC,UAAM,aAAa,KAAK,OAAO,YAAY;AAAA,MACzC,CAAC,MACC,EAAE,SAAS,KAAK,aAAa,QAC7B,EAAE,YAAY,UACb,CAAC,EAAE,aAAa,EAAE,UAAU;AAAA,IACjC;AAEA,QAAI,CAAC,YAAY;AACf,aAAO;AAAA,IACT;AAEA,SAAK,gBAAgB,YAAY,KAAK;AACtC,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,WAAW,SAAuB,YAA0B;AAC1D,UAAM,cAAc,KAAK;AAEzB,SAAK,iBAAiB;AACtB,SAAK,oBAAoB,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,UAAU,CAAC;AAG5D,UAAM,UAAU,KAAK,OAAO,gBAAgB;AAAA,MAC1C,CAAC,MAAM,EAAE,YAAY;AAAA,IACvB;AACA,QAAI,WAAW,KAAK,aAAa,qBAAqB;AACpD,WAAK,sBAAsB,QAAQ,YAAY,KAAK;AAAA,IACtD,OAAO;AACL,WAAK,sBAAsB;AAAA,IAC7B;AAEA,QAAI,gBAAgB,SAAS;AAC3B,WAAK,KAAK,kBAAkB,EAAE,SAAS,WAAW,CAAC;AAAA,IACrD;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,eAAqB;AACnB,SAAK,iBAAiB;AACtB,SAAK,oBAAoB;AACzB,SAAK,sBAAsB;AAC3B,SAAK,KAAK,kBAAkB,EAAE,SAAS,MAAM,YAAY,EAAE,CAAC;AAAA,EAC9D;AAAA;AAAA;AAAA;AAAA,EAKA,eAAe,QAAsB;AACnC,SAAK,cAAc,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,MAAM,CAAC;AAAA,EACpD;AAAA;AAAA;AAAA;AAAA,EAKA,SAAS,WAA+B,gBAAwB,KAAW;AACzE,UAAM,cAAc,KAAK,OAAO,OAAO,KAAK,CAAC,MAAM,EAAE,SAAS,SAAS;AACvE,QAAI,CAAC,aAAa;AAChB,cAAQ,KAAK,2BAA2B,SAAS,aAAa;AAC9D;AAAA,IACF;AAEA,QAAI,YAAY,SAAS,KAAK,aAAa,QAAQ,CAAC,KAAK,iBAAiB;AACxE;AAAA,IACF;AAGA,UAAM,mBAA+B;AAAA,MACnC,MAAM,KAAK,aAAa;AAAA,MACxB,IAAI;AAAA,MACJ,SAAS;AAAA;AAAA,MACT,UAAU;AAAA,IACZ;AAEA,SAAK,gBAAgB,kBAAkB,SAAS;AAAA,EAClD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,OAAO,SAAmC;AACxC,UAAM,MAAM,KAAK,IAAI;AACrB,UAAM,KAAK,WAAW,MAAM,KAAK;AACjC,SAAK,iBAAiB;AAEtB,UAAM,YAAY,KAAK;AAGvB,QAAI,KAAK,iBAAiB;AACxB,WAAK,iBAAiB,SAAS;AAAA,IACjC;AAGA,SAAK,aAAa,GAAG;AAGrB,SAAK,mBAAmB,SAAS;AAGjC,SAAK,cAAc,SAAS;AAG5B,SAAK,eAAe,KAAK,cAAc;AACvC,SAAK,KAAK,iBAAiB,KAAK,YAAY;AAE5C,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,QAAc;AACZ,UAAM,eAAe,KAAK,OAAO,OAAO;AAAA,MACtC,CAAC,MAAM,EAAE,SAAS,KAAK,OAAO;AAAA,IAChC;AACA,QAAI,cAAc;AAChB,WAAK,eAAe;AACpB,WAAK,gBAAgB;AACrB,WAAK,kBAAkB;AACvB,WAAK,qBAAqB;AAC1B,WAAK,iBAAiB,KAAK,IAAI;AAC/B,WAAK,qBAAqB;AAC1B,WAAK,gBAAgB;AACrB,WAAK,eAAe,KAAK,cAAc;AAAA,IACzC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,mBAA6B;AAC3B,UAAM,QAAQ,oBAAI,IAAY;AAG9B,eAAW,SAAS,KAAK,OAAO,QAAQ;AACtC,iBAAW,QAAQ,MAAM,WAAW;AAClC,cAAM,IAAI,IAAI;AAAA,MAChB;AAAA,IACF;AAGA,eAAW,WAAW,KAAK,OAAO,iBAAiB;AACjD,YAAM,IAAI,QAAQ,IAAI;AAAA,IACxB;AAGA,eAAW,QAAQ,KAAK,OAAO,cAAc;AAC3C,YAAM,IAAI,IAAI;AAAA,IAChB;AAEA,WAAO,MAAM,KAAK,KAAK;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA,EAMQ,gBAAgB,YAAwB,OAA+B;AAC7E,UAAM,cAAc,KAAK,OAAO,OAAO;AAAA,MACrC,CAAC,MAAM,EAAE,SAAS,WAAW;AAAA,IAC/B;AACA,QAAI,CAAC,aAAa;AAChB,cAAQ,KAAK,kCAAkC,WAAW,EAAE,aAAa;AACzE;AAAA,IACF;AAEA,UAAM,YAAY,KAAK,aAAa;AAEpC,SAAK,gBAAgB,KAAK;AAC1B,SAAK,eAAe;AACpB,SAAK,kBAAkB;AACvB,SAAK,qBAAqB;AAC1B,SAAK,qBAAqB,WAAW;AACrC,SAAK,sBAAsB,KAAK,IAAI;AACpC,SAAK,iBAAiB,KAAK,IAAI;AAG/B,QAAI,CAAC,KAAK,aAAa,qBAAqB;AAC1C,WAAK,sBAAsB;AAAA,IAC7B;AAEA,SAAK,KAAK,gBAAgB;AAAA,MACxB,MAAM;AAAA,MACN,IAAI,YAAY;AAAA,MAChB,SAAS;AAAA,IACX,CAAC;AAED,SAAK,KAAK,oBAAoB;AAAA,MAC5B,MAAM;AAAA,MACN,IAAI,YAAY;AAAA,MAChB,UAAU,WAAW;AAAA,IACvB,CAAC;AAAA,EACH;AAAA,EAEQ,iBAAiB,WAAyB;AAChD,QAAI,CAAC,KAAK,mBAAmB,KAAK,sBAAsB,GAAG;AACzD,WAAK,kBAAkB;AACvB,WAAK,qBAAqB;AAC1B;AAAA,IACF;AAGA,UAAM,UAAU,KAAK,IAAI,IAAI,KAAK;AAClC,SAAK,qBAAqB,KAAK,IAAI,GAAG,UAAU,KAAK,kBAAkB;AAEvE,QAAI,KAAK,sBAAsB,GAAG;AAChC,WAAK,kBAAkB;AACvB,WAAK,qBAAqB;AAC1B,WAAK,gBAAgB;AACrB,WAAK,KAAK,kBAAkB,EAAE,OAAO,KAAK,aAAa,KAAK,CAAC;AAAA,IAC/D;AAAA,EACF;AAAA,EAEQ,aAAa,KAAmB;AACtC,QAAI,KAAK,gBAAiB;AAC1B,QAAI,KAAK,aAAa,WAAW,EAAG;AAEpC,UAAM,UAAU,MAAM,KAAK;AAC3B,QAAI,WAAW,KAAK,aAAa,SAAS;AACxC,WAAK,QAAQ,SAAS;AAAA,IACxB;AAAA,EACF;AAAA,EAEQ,mBAAmB,WAAyB;AAClD,QAAI,CAAC,KAAK,gBAAgB;AAExB,WAAK,qBAAqB,KAAK;AAAA,QAC7B;AAAA,QACA,KAAK,qBAAqB,YAAY;AAAA,MACxC;AACA;AAAA,IACF;AAEA,UAAM,UAAU,KAAK,OAAO,gBAAgB;AAAA,MAC1C,CAAC,MAAM,EAAE,YAAY,KAAK;AAAA,IAC5B;AACA,UAAM,aAAa,SAAS,cAAc;AAG1C,UAAM,OAAO,KAAK,sBAAsB,KAAK;AAC7C,UAAM,YAAY,aAAa;AAE/B,QAAI,KAAK,IAAI,IAAI,KAAK,WAAW;AAC/B,WAAK,qBAAqB,KAAK;AAAA,IACjC,OAAO;AACL,WAAK,sBAAsB,KAAK,KAAK,IAAI,IAAI;AAAA,IAC/C;AAAA,EACF;AAAA,EAEQ,cAAc,WAAyB;AAC7C,QAAI,CAAC,KAAK,aAAa,qBAAqB;AAC1C,WAAK,gBAAgB,KAAK,IAAI,GAAG,KAAK,gBAAgB,YAAY,CAAG;AACrE;AAAA,IACF;AAGA,UAAM,gBACJ,KAAK,cAAc,KAAK,OAAO,mBAC3B,KAAK,cAAc,KAAK,OAAO,mBAC/B;AAGN,UAAM,OAAO,gBAAgB,KAAK;AAClC,UAAM,aAAa;AACnB,UAAM,YAAY,aAAa;AAE/B,QAAI,KAAK,IAAI,IAAI,KAAK,WAAW;AAC/B,WAAK,gBAAgB;AAAA,IACvB,OAAO;AACL,WAAK,iBAAiB,KAAK,KAAK,IAAI,IAAI;AAAA,IAC1C;AAGA,UAAM,YAAY,KAAK,OAAO,aAAa;AAC3C,QAAI,YAAY,GAAG;AACjB,WAAK,qBAAqB,KAAK;AAAA,QAC7B,YAAY;AAAA,QACZ,KAAK,MAAM,KAAK,gBAAgB,SAAS;AAAA,MAC3C;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,gBAAiC;AACvC,UAAM,eAA8B,CAAC;AAGrC,UAAM,IAAI,KAAK;AACf,UAAM,mBAAmB,IAAI,KAAK,IAAI,IAAI;AAG1C,QAAI,KAAK,iBAAiB,KAAK,iBAAiB;AAC9C,YAAM,UAAU,IAAI;AACpB,eAAS,IAAI,GAAG,IAAI,KAAK,cAAc,UAAU,QAAQ,KAAK;AAC5D,cAAM,OAAO,KAAK,cAAc,UAAU,CAAC;AAC3C,cAAM,aAAa,KAAK,cAAc,YAAY,CAAC,KAAK;AACxD,qBAAa,KAAK;AAAA,UAChB;AAAA,UACA,QAAQ,aAAa;AAAA,UACrB,OAAO;AAAA,UACP,MAAM;AAAA,QACR,CAAC;AAAA,MACH;AAAA,IACF;AAGA,UAAM,SAAS,KAAK,kBAAkB,mBAAmB;AACzD,aAAS,IAAI,GAAG,IAAI,KAAK,aAAa,UAAU,QAAQ,KAAK;AAC3D,YAAM,OAAO,KAAK,aAAa,UAAU,CAAC;AAC1C,YAAM,aAAa,KAAK,aAAa,YAAY,CAAC,KAAK;AACvD,mBAAa,KAAK;AAAA,QAChB;AAAA,QACA,QAAQ,aAAa;AAAA,QACrB,OAAO;AAAA,QACP,MAAM;AAAA,MACR,CAAC;AAAA,IACH;AAGA,QAAI,KAAK,kBAAkB,KAAK,qBAAqB,MAAM;AACzD,YAAM,UAAU,KAAK,OAAO,gBAAgB;AAAA,QAC1C,CAAC,MAAM,EAAE,YAAY,KAAK;AAAA,MAC5B;AACA,UAAI,SAAS;AACX,qBAAa,KAAK;AAAA,UAChB,MAAM,QAAQ;AAAA,UACd,QAAQ,KAAK;AAAA,UACb,OAAO;AAAA,UACP,MAAM;AAAA,QACR,CAAC;AAAA,MACH;AAAA,IACF;AAGA,QAAI,KAAK,gBAAgB,QAAQ,KAAK,OAAO,aAAa,SAAS,GAAG;AACpE,YAAM,cAAc,KAAK,OAAO,aAAa,KAAK,kBAAkB;AACpE,mBAAa,KAAK;AAAA,QAChB,MAAM;AAAA,QACN,QAAQ,KAAK;AAAA,QACb,OAAO,IAAM,KAAK,cAAc;AAAA;AAAA,QAChC,MAAM;AAAA,MACR,CAAC;AAAA,IACH;AAEA,WAAO;AAAA,MACL,OAAO,KAAK,aAAa;AAAA,MACzB;AAAA,MACA,eAAe,KAAK,qBAAqB,OAAO,KAAK,iBAAiB;AAAA,MACtE,kBAAkB,KAAK;AAAA,MACvB,iBAAiB,KAAK;AAAA,MACtB,oBAAoB,KAAK;AAAA,IAC3B;AAAA,EACF;AACF;;;ACrdA,IAAM,OAAO,IAAI,WAAW,GAAG;AAC/B,IAAM,QAAQ;AAAA,EACZ,CAAC,GAAG,CAAC;AAAA,EAAG,CAAC,IAAI,CAAC;AAAA,EAAG,CAAC,GAAG,EAAE;AAAA,EAAG,CAAC,IAAI,EAAE;AAAA,EACjC,CAAC,GAAG,CAAC;AAAA,EAAG,CAAC,IAAI,CAAC;AAAA,EAAG,CAAC,GAAG,CAAC;AAAA,EAAG,CAAC,GAAG,EAAE;AACjC;AAGA,IAAM,IAAI;AAAA,EACR;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAI;AAAA,EAAI;AAAA,EAAI;AAAA,EAAK;AAAA,EAAI;AAAA,EAAK;AAAA,EAAI;AAAA,EAAI;AAAA,EAAI;AAAA,EAAK;AAAA,EAAK;AAAA,EAAG;AAAA,EAClE;AAAA,EAAK;AAAA,EAAI;AAAA,EAAK;AAAA,EAAI;AAAA,EAAI;AAAA,EAAK;AAAA,EAAG;AAAA,EAAI;AAAA,EAAI;AAAA,EAAK;AAAA,EAAI;AAAA,EAAI;AAAA,EAAI;AAAA,EAAK;AAAA,EAAG;AAAA,EAC/D;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAI;AAAA,EAAG;AAAA,EAAI;AAAA,EAAK;AAAA,EAAI;AAAA,EAAI;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAI;AAAA,EAAI;AAAA,EACnE;AAAA,EAAI;AAAA,EAAK;AAAA,EAAI;AAAA,EAAI;AAAA,EAAK;AAAA,EAAK;AAAA,EAAI;AAAA,EAAI;AAAA,EAAK;AAAA,EAAI;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAI;AAAA,EACpE;AAAA,EAAI;AAAA,EAAK;AAAA,EAAI;AAAA,EAAK;AAAA,EAAK;AAAA,EAAI;AAAA,EAAI;AAAA,EAAK;AAAA,EAAI;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAI;AAAA,EAAK;AAAA,EAAK;AAAA,EACrE;AAAA,EAAI;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAI;AAAA,EAAI;AAAA,EAAI;AAAA,EAAI;AAAA,EAAK;AAAA,EAAI;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EACrE;AAAA,EAAI;AAAA,EAAI;AAAA,EAAI;AAAA,EAAK;AAAA,EAAG;AAAA,EAAK;AAAA,EAAI;AAAA,EAAI;AAAA,EAAK;AAAA,EAAI;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAI;AAAA,EAAI;AAAA,EACjE;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAI;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAG;AAAA,EACxE;AAAA,EAAI;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAG;AAAA,EAAK;AAAA,EAAI;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAI;AAAA,EAAI;AAAA,EACrE;AAAA,EAAK;AAAA,EAAK;AAAA,EAAI;AAAA,EAAK;AAAA,EAAI;AAAA,EAAI;AAAA,EAAI;AAAA,EAAI;AAAA,EAAK;AAAA,EAAK;AAAA,EAAI;AAAA,EAAI;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EACpE;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAG;AAAA,EAAI;AAAA,EAAK;AAAA,EAAK;AAAA,EAAI;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAI;AAAA,EAAK;AAAA,EACtE;AAAA,EAAK;AAAA,EAAI;AAAA,EAAI;AAAA,EAAK;AAAA,EAAI;AAAA,EAAI;AAAA,EAAK;AAAA,EAAK;AAAA,EAAI;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EACtE;AAAA,EAAK;AAAA,EAAK;AAAA,EAAI;AAAA,EAAK;AAAA,EAAK;AAAA,EAAI;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAI;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EACxE;AAAA,EAAI;AAAA,EAAI;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAI;AAAA,EAAK;AAAA,EAAK;AAAA,EAAI;AAAA,EAAK;AAAA,EAAK;AAAA,EAAI;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EACtE;AAAA,EAAK;AAAA,EAAI;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAI;AAAA,EAAI;AAAA,EAAK;AAAA,EAAG;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EACtE;AAAA,EAAK;AAAA,EAAK;AAAA,EAAI;AAAA,EAAI;AAAA,EAAI;AAAA,EAAI;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAI;AAAA,EAAI;AAAA,EAAK;AAAA,EAAI;AAAA,EAAK;AACtE;AACA,SAAS,IAAI,GAAG,IAAI,KAAK,KAAK;AAC5B,OAAK,CAAC,IAAI,EAAE,CAAC;AACb,OAAK,IAAI,GAAG,IAAI,EAAE,CAAC;AACrB;AAEA,IAAM,KAAK,OAAO,KAAK,KAAK,CAAC,IAAI;AACjC,IAAM,MAAM,IAAI,KAAK,KAAK,CAAC,KAAK;AAEhC,SAAS,KAAK,GAAa,GAAW,GAAmB;AACvD,SAAO,EAAE,CAAC,IAAI,IAAI,EAAE,CAAC,IAAI;AAC3B;AASO,SAAS,UAAU,GAAW,GAAmB;AAEtD,QAAM,KAAK,IAAI,KAAK;AACpB,QAAM,IAAI,KAAK,MAAM,IAAI,CAAC;AAC1B,QAAM,IAAI,KAAK,MAAM,IAAI,CAAC;AAE1B,QAAM,KAAK,IAAI,KAAK;AACpB,QAAM,KAAK,IAAI;AACf,QAAM,KAAK,IAAI;AACf,QAAM,KAAK,IAAI;AACf,QAAM,KAAK,IAAI;AAGf,QAAM,KAAK,KAAK,KAAK,IAAI;AACzB,QAAM,KAAK,KAAK,KAAK,IAAI;AAEzB,QAAM,KAAK,KAAK,KAAK;AACrB,QAAM,KAAK,KAAK,KAAK;AACrB,QAAM,KAAK,KAAK,IAAI,IAAI;AACxB,QAAM,KAAK,KAAK,IAAI,IAAI;AAExB,QAAM,KAAK,IAAI;AACf,QAAM,KAAK,IAAI;AACf,QAAM,MAAM,KAAK,KAAK,KAAK,EAAE,CAAC,IAAI;AAClC,QAAM,MAAM,KAAK,KAAK,KAAK,KAAK,KAAK,EAAE,CAAC,IAAI;AAC5C,QAAM,MAAM,KAAK,KAAK,IAAI,KAAK,KAAK,CAAC,CAAC,IAAI;AAG1C,MAAI,KAAK;AACT,MAAI,KAAK,MAAM,KAAK,KAAK,KAAK;AAC9B,MAAI,MAAM,GAAG;AACX,UAAM;AACN,SAAK,KAAK,KAAK,KAAK,MAAM,GAAG,GAAG,IAAI,EAAE;AAAA,EACxC;AAEA,MAAI,KAAK;AACT,MAAI,KAAK,MAAM,KAAK,KAAK,KAAK;AAC9B,MAAI,MAAM,GAAG;AACX,UAAM;AACN,SAAK,KAAK,KAAK,KAAK,MAAM,GAAG,GAAG,IAAI,EAAE;AAAA,EACxC;AAEA,MAAI,KAAK;AACT,MAAI,KAAK,MAAM,KAAK,KAAK,KAAK;AAC9B,MAAI,MAAM,GAAG;AACX,UAAM;AACN,SAAK,KAAK,KAAK,KAAK,MAAM,GAAG,GAAG,IAAI,EAAE;AAAA,EACxC;AAGA,SAAO,MAAM,KAAK,KAAK;AACzB;;;ACTA,IAAM,aAAa;AACnB,IAAM,gBAAgB;AACtB,IAAM,eAAe;AACrB,IAAM,gBAAgB;AAGtB,IAAM,uBAAuB;AAC7B,IAAM,sBAAsB;AAC5B,IAAM,sBAAsB;AAC5B,IAAM,wBAAwB;AAG9B,IAAM,sBAAsB;AAC5B,IAAM,2BAA2B;AACjC,IAAM,6BAA6B;AAGnC,IAAM,mBAAmB;AACzB,IAAM,mBAAmB;AACzB,IAAM,oBAAoB;AAC1B,IAAM,oBAAoB;AAG1B,IAAM,qBAAqB;AAC3B,IAAM,uBAAuB;AAC7B,IAAM,wBAAwB;AAC9B,IAAM,iBAAiB;AAGvB,IAAM,sBAAsB;AAC5B,IAAM,wBAAwB;AAC9B,IAAM,yBAAyB;AAC/B,IAAM,uBAAuB;AAC7B,IAAM,wBAAwB;AAG9B,IAAM,4BAA4B;AAClC,IAAM,sBAAsB;AAE5B,SAAS,MAAM,GAAW,KAAa,KAAqB;AAC1D,SAAO,IAAI,MAAM,MAAM,IAAI,MAAM,MAAM;AACzC;AAEA,SAAS,YAAY,KAAa,KAAqB;AACrD,SAAO,MAAM,KAAK,OAAO,KAAK,MAAM;AACtC;AAEA,SAAS,WAAW,GAAmB;AACrC,SAAO,IAAI,KAAK,IAAI,IAAI;AAC1B;AAEA,SAAS,UAAU,GAAW,KAAqB;AACjD,SAAO,KAAK,KAAK,IAAI,GAAG,IAAI;AAC9B;AAQO,IAAM,sBAAN,MAA0B;AAAA,EAgD/B,YAAY,QAA0B;AAlCtC;AAAA,SAAQ,aAAa;AAErB,SAAQ,aAAa;AACrB,SAAQ,gBAAgB;AACxB,SAAQ,iBAAiB;AACzB,SAAQ,oBAAoB;AAC5B,SAAQ,qBAAqB;AAG7B;AAAA,SAAQ,eAAe;AACvB,SAAQ,eAAe;AAGvB;AAAA,SAAQ,eAAe;AAGvB;AAAA,SAAQ,iBAAiB;AAEzB,SAAQ,iBAAiB;AACzB,SAAQ,oBAAoB;AAC5B,SAAQ,mBAAmB;AAC3B,SAAQ,mBAAmB;AAC3B,SAAQ,oBAAoB;AAC5B,SAAQ,oBAAoB;AAG5B;AAAA,SAAQ,kBAAkB;AAC1B,SAAQ,iBAAiB;AAGzB;AAAA,SAAQ,YAAY;AACpB,SAAQ,iBAAiB;AACzB,SAAQ,gBAAgB;AAGtB,SAAK,qBAAqB,QAAQ,sBAAsB,CAAC,KAAK,CAAC;AAC/D,SAAK,yBAAyB,QAAQ,0BAA0B,CAAC,GAAG,CAAC;AACrE,SAAK,0BAA0B,QAAQ,2BAA2B,CAAC,MAAM,GAAG;AAC5E,SAAK,oBAAoB,QAAQ,qBAAqB;AACtD,SAAK,qBAAqB,QAAQ,sBAAsB;AACxD,SAAK,4BAA4B,QAAQ,6BAA6B;AACtE,SAAK,gBAAgB,QAAQ,iBAAiB;AAC9C,SAAK,wBAAwB,QAAQ,yBAAyB;AAC9D,SAAK,kBAAkB,QAAQ,mBAAmB;AAClD,SAAK,eAAe,QAAQ,gBAAgB;AAG5C,SAAK,gBAAgB,YAAY,GAAG,KAAK,kBAAkB;AAC3D,SAAK,oBAAoB,YAAY,GAAG,KAAK,sBAAsB;AAAA,EACrE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,OAAO,OAAe,OAAyC;AAC7D,UAAM,aAAa,OAAO,cAAc;AACxC,UAAM,aAAa,OAAO,cAAc;AACxC,UAAM,cAAc,OAAO,eAAe;AAC1C,UAAM,aAAa,OAAO,cAAc;AAGxC,UAAM,YAAY,KAAK,IAAI,OAAO,GAAG;AAErC,UAAM,cAAsC,CAAC;AAK7C,SAAK,aAAa,KAAK;AAEvB,UAAM,iBAAiB;AACvB,UAAM,cAAc,KAAK,eAAe;AACxC,SAAK,sBAAsB,YAAY,OAAO,KAAK,qBAAqB,KAAK,IAAI,GAAG,YAAY,cAAc;AAC9G,SAAK,uBAAuB,YAAY,QAAQ,KAAK,sBAAsB,KAAK,IAAI,GAAG,YAAY,cAAc;AAEjH,gBAAY,cAAc,IAAI,KAAK;AACnC,gBAAY,eAAe,IAAI,KAAK;AAKpC,SAAK,iBAAiB,aAAa,KAAK,gBAAgB,KAAK,IAAI,GAAG,YAAY,KAAK,YAAY;AACjG,SAAK,iBAAiB,aAAa,KAAK,gBAAgB,KAAK,IAAI,GAAG,YAAY,KAAK,YAAY;AAKjG,SAAK,gBAAgB;AACrB,UAAM,cAAc,KAAK,kBAAkB;AAK3C,SAAK,iBAAiB,KAAK;AAK3B,UAAM,YAAY,KAAK,eAAe,KAAK,oBAAoB,YAAY;AAC3E,UAAM,YAAY,KAAK,eAAe,KAAK,oBAAoB,YAAY;AAG3E,UAAM,WAAW,UAAU,WAAW,KAAK,eAAe;AAC1D,UAAM,WAAW,UAAU,WAAW,KAAK,eAAe;AAI1D,UAAM,WAAW;AACjB,UAAM,YAAY,WAAW,WAAW,WACpC,WAAW,IAAI,YAAY,WAAW,YAAY;AACtD,UAAM,WAAW,WAAW,CAAC,WAAW,CAAC,WACrC,WAAW,IAAI,CAAC,YAAY,CAAC,WAAW,YAAY;AACxD,UAAM,SAAS,WAAW,WAAW,WACjC,WAAW,IAAI,YAAY,WAAW,YAAY;AACtD,UAAM,WAAW,WAAW,CAAC,WAAW,CAAC,WACrC,WAAW,IAAI,CAAC,YAAY,CAAC,WAAW,YAAY;AAGxD,gBAAY,eAAe,IAAI;AAC/B,gBAAY,gBAAgB,IAAI;AAChC,gBAAY,gBAAgB,IAAI;AAChC,gBAAY,iBAAiB,IAAI;AACjC,gBAAY,eAAe,IAAI;AAC/B,gBAAY,gBAAgB,IAAI;AAChC,gBAAY,iBAAiB,IAAI;AACjC,gBAAY,kBAAkB,IAAI;AAKlC,SAAK,gBAAgB,OAAO,aAAa,YAAY,WAAW;AAKhE,SAAK,mBAAmB;AACxB,SAAK,kBAAkB,QAAQ,KAAK,gBAAgB,KAAK,KAAK;AAE9D,UAAM,aAAa,KAAK,IAAI,KAAK,cAAc,IAAI;AACnD,UAAM,UAAU,KAAK;AACrB,UAAM,QAAQ,KAAK,IAAI,KAAK,kBAAkB,GAAG,IAAI,UACvC,KAAK,IAAI,KAAK,kBAAkB,GAAG,IAAI,UAAU;AAC/D,UAAM,QAAQ,KAAK,IAAI,KAAK,kBAAkB,GAAG,IAAI,UAAU,OACjD,KAAK,IAAI,KAAK,kBAAkB,GAAG,IAAI,UAAU;AAE/D,WAAO;AAAA,MACL;AAAA,MACA,WAAW;AAAA,QACT,KAAK;AAAA,QACL,OAAO,aAAa;AAAA,MACtB;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,QAAc;AAEZ,SAAK,aAAa;AAClB,SAAK,gBAAgB,YAAY,GAAG,KAAK,kBAAkB;AAC3D,SAAK,aAAa;AAClB,SAAK,gBAAgB;AACrB,SAAK,iBAAiB;AACtB,SAAK,oBAAoB;AACzB,SAAK,qBAAqB;AAG1B,SAAK,eAAe;AACpB,SAAK,eAAe;AAGpB,SAAK,eAAe;AAGpB,SAAK,iBAAiB;AACtB,SAAK,oBAAoB,YAAY,GAAG,KAAK,sBAAsB;AACnE,SAAK,iBAAiB;AACtB,SAAK,oBAAoB;AACzB,SAAK,mBAAmB;AACxB,SAAK,mBAAmB;AACxB,SAAK,oBAAoB;AACzB,SAAK,oBAAoB;AAGzB,SAAK,kBAAkB;AACvB,SAAK,iBAAiB;AAGtB,SAAK,YAAY;AACjB,SAAK,iBAAiB;AACtB,SAAK,gBAAgB;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA,EAMQ,aAAa,OAAqB;AACxC,SAAK,cAAc;AAEnB,QAAI,KAAK,cAAc,KAAK,iBAAiB,KAAK,eAAe,YAAY;AAC3E,WAAK,aAAa;AAClB,WAAK,gBAAgB;AACrB,WAAK,aAAa;AAClB,WAAK,gBAAgB,YAAY,GAAG,KAAK,kBAAkB;AAC3D,WAAK,iBAAiB,OAAO,KAAK,OAAO,IAAI;AAAA,IAC/C;AAEA,QAAI,KAAK,aAAa,YAAY;AAChC,WAAK,iBAAiB;AAEtB,UAAI,KAAK,eAAe,eAAe;AACrC,YAAI,KAAK,iBAAiB,sBAAsB;AAC9C,eAAK,aAAa;AAClB,eAAK,gBAAgB;AAAA,QACvB;AAAA,MACF,WAAW,KAAK,eAAe,cAAc;AAC3C,YAAI,KAAK,iBAAiB,qBAAqB;AAC7C,eAAK,aAAa;AAClB,eAAK,gBAAgB;AAAA,QACvB;AAAA,MACF,WAAW,KAAK,eAAe,eAAe;AAC5C,YAAI,KAAK,iBAAiB,qBAAqB;AAC7C,eAAK,aAAa;AAClB,eAAK,gBAAgB;AAAA,QACvB;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,iBAAkD;AACxD,QAAI,KAAK,eAAe,YAAY;AAClC,aAAO,EAAE,MAAM,GAAG,OAAO,EAAE;AAAA,IAC7B;AAEA,QAAI,KAAK,eAAe,eAAe;AACrC,YAAMC,KAAI,KAAK,IAAI,GAAG,KAAK,gBAAgB,oBAAoB;AAC/D,YAAMC,SAAQD,KAAIA,KAAIA;AACtB,YAAM,SAAS,KAAK,IAAI,GAAG,KAAK,IAAI,IAAI,KAAK,gBAAgB,yBAAyB,oBAAoB,CAAC;AAC3G,aAAO;AAAA,QACL,MAAMC;AAAA,QACN,OAAO,SAAS,SAAS,SAAS,KAAK;AAAA,MACzC;AAAA,IACF;AAEA,QAAI,KAAK,eAAe,cAAc;AACpC,aAAO,EAAE,MAAM,GAAG,OAAO,KAAK,eAAe;AAAA,IAC/C;AAGA,UAAM,IAAI,KAAK,IAAI,GAAG,KAAK,gBAAgB,mBAAmB;AAC9D,UAAM,QAAQ,WAAW,CAAC;AAC1B,WAAO;AAAA,MACL,MAAM,IAAI;AAAA,MACV,QAAQ,IAAI,SAAS,KAAK;AAAA,IAC5B;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAMQ,oBAA8C;AACpD,UAAM,MAAM,KAAK;AACjB,UAAM,IAAI,UAAU,KAAK,eAAe,kBAAkB,iBAAiB,IAAI;AAC/E,UAAM,IAAI,UAAU,KAAK,eAAe,kBAAkB,iBAAiB,IAAI,MAAM;AACrF,WAAO,EAAE,GAAG,EAAE;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA,EAMQ,iBAAiB,OAAqB;AAC5C,SAAK,kBAAkB;AAEvB,QAAI,KAAK,kBAAkB,KAAK,qBAAqB,KAAK,mBAAmB,YAAY;AACvF,WAAK,iBAAiB;AACtB,WAAK,oBAAoB;AACzB,WAAK,iBAAiB;AAEtB,YAAM,MAAM,YAAY,GAAG,KAAK,uBAAuB;AACvD,WAAK,oBAAoB,KAAK,OAAO,IAAI,OAAO,IAAI;AACpD,WAAK,oBAAoB,KAAK,OAAO,IAAI,OAAO,MAAM;AACtD,WAAK,oBAAoB,YAAY,GAAG,KAAK,sBAAsB;AAAA,IACrE;AAEA,QAAI,KAAK,iBAAiB,YAAY;AACpC,WAAK,qBAAqB;AAE1B,UAAI,KAAK,mBAAmB,GAAG;AAE7B,cAAM,IAAI,KAAK,IAAI,GAAG,KAAK,oBAAoB,mBAAmB;AAClE,cAAM,QAAQ,WAAW,CAAC;AAC1B,aAAK,oBAAoB,KAAK,mBAAmB;AACjD,aAAK,oBAAoB,KAAK,mBAAmB;AACjD,YAAI,KAAK,qBAAqB,qBAAqB;AACjD,eAAK,iBAAiB;AACtB,eAAK,oBAAoB;AAAA,QAC3B;AAAA,MACF,WAAW,KAAK,mBAAmB,GAAG;AAEpC,aAAK,oBAAoB,KAAK;AAC9B,aAAK,oBAAoB,KAAK;AAC9B,YAAI,KAAK,qBAAqB,0BAA0B;AACtD,eAAK,iBAAiB;AACtB,eAAK,oBAAoB;AAAA,QAC3B;AAAA,MACF,WAAW,KAAK,mBAAmB,GAAG;AAEpC,cAAM,IAAI,KAAK,IAAI,GAAG,KAAK,oBAAoB,0BAA0B;AACzE,cAAM,QAAQ,WAAW,CAAC;AAC1B,aAAK,oBAAoB,KAAK,oBAAoB,IAAI;AACtD,aAAK,oBAAoB,KAAK,oBAAoB,IAAI;AACtD,YAAI,KAAK,qBAAqB,4BAA4B;AACxD,eAAK,iBAAiB;AACtB,eAAK,oBAAoB;AACzB,eAAK,oBAAoB;AACzB,eAAK,oBAAoB;AAAA,QAC3B;AAAA,MACF;AAAA,IACF,OAAO;AACL,WAAK,oBAAoB;AACzB,WAAK,oBAAoB;AAAA,IAC3B;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAMQ,gBACN,OACA,aACA,YACA,aACM;AACN,SAAK,aAAa;AAGlB,UAAM,cAAc,cAAc,KAAK;AACvC,QAAI,cAAc,2BAA2B;AAC3C,WAAK,gBAAgB;AAAA,IACvB;AACA,SAAK,gBAAgB,KAAK,IAAI,GAAG,KAAK,gBAAgB,QAAQ,mBAAmB;AACjF,SAAK,iBAAiB;AAGtB,UAAM,YAAa,cAAc,cAAc,IAAK,KAAK,4BAA4B;AACrF,UAAM,MAAM,KAAK,qBAAqB;AAGtC,UAAM,eAAe,UAAU,KAAK,YAAY,oBAAoB,mBAAmB;AACvF,UAAM,eAAe,eAAe,MAAM,OAAO,MAAM;AACvD,UAAM,kBAAkB,KAAK,gBAAgB;AAC7C,gBAAY,aAAa,IAAI,MAAM,cAAc,iBAAiB,GAAG,CAAC;AAGtE,UAAM,iBAAiB,UAAU,KAAK,YAAY,sBAAsB,qBAAqB;AAC7F,gBAAY,iBAAiB,IAAI,OAAO,iBAAiB,MAAM,OAAO,MAAM,KAAK,GAAG,CAAC;AAGrF,UAAM,kBAAkB,UAAU,KAAK,YAAY,uBAAuB,sBAAsB;AAChG,gBAAY,kBAAkB,IAAI,OAAO,kBAAkB,MAAM,OAAO,MAAM,KAAK,GAAG,CAAC;AAGvF,UAAM,gBAAgB,UAAU,KAAK,YAAY,gBAAgB,oBAAoB;AACrF,gBAAY,cAAc,IAAI,OAAO,gBAAgB,MAAM,OAAO,MAAM,MAAM,GAAG,CAAC;AAGlF,UAAM,iBAAiB,UAAU,KAAK,YAAY,gBAAgB,qBAAqB;AACvF,gBAAY,eAAe,IAAI,OAAO,iBAAiB,MAAM,OAAO,MAAM,MAAM,GAAG,CAAC;AAAA,EACtF;AACF;;;AC5hBO,IAAM,mBAAmB;AAsEzB,SAAS,gBAAgB,KAAoC;AAClE,SACE,OAAO,QAAQ,YACf,QAAQ,QACR,OAAO,OACP,UAAU,OACV,QAAQ;AAEZ;","names":["data","module","isBrowser","module","logger","module","options","session","logger","logger","pcm16ToFloat32","window","logger","logger","logger","logger","logger","WASM_CDN_PATH","logger","logger","sum","t","eased"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/events/EventEmitter.ts","../src/audio/MicrophoneCapture.ts","../src/audio/RingBuffer.ts","../src/audio/AudioScheduler.ts","../src/audio/AudioChunkCoalescer.ts","../src/audio/LAMPipeline.ts","../src/audio/audioUtils.ts","../src/audio/SyncedAudioPipeline.ts","../src/animation/EmotionToBlendshapeMapper.ts","../src/animation/audioEnergy.ts","../src/telemetry/exporters/console.ts","../src/telemetry/exporters/otlp.ts","../src/telemetry/OmoteTelemetry.ts","../src/telemetry/types.ts","../src/cache/ModelCache.ts","../src/logging/types.ts","../src/logging/formatters.ts","../src/logging/Logger.ts","../src/utils/runtime.ts","../src/inference/onnxLoader.ts","../src/inference/blendshapeUtils.ts","../src/inference/Wav2Vec2Inference.ts","../src/audio/FullFacePipeline.ts","../src/inference/kaldiFbank.ts","../src/inference/ctcDecoder.ts","../src/inference/SenseVoiceInference.ts","../src/inference/SenseVoiceWorker.ts","../src/inference/UnifiedInferenceWorker.ts","../src/inference/createSenseVoice.ts","../src/inference/Wav2ArkitCpuInference.ts","../src/inference/Wav2ArkitCpuWorker.ts","../src/inference/createLipSync.ts","../src/inference/SileroVADInference.ts","../src/inference/SileroVADWorker.ts","../src/inference/createSileroVAD.ts","../src/inference/SafariSpeechRecognition.ts","../src/emotion/Emotion.ts","../src/ai/adapters/AgentCoreAdapter.ts","../src/ai/orchestration/ConversationOrchestrator.ts","../src/ai/tenancy/TenantManager.ts","../src/ai/utils/AudioSyncManager.ts","../src/ai/utils/InterruptionHandler.ts","../src/animation/types.ts","../src/animation/AnimationGraph.ts","../src/animation/simplex2d.ts","../src/animation/ProceduralLifeLayer.ts","../../types/src/protocol.ts"],"sourcesContent":["/**\r\n * @omote/core - WebGPU-accelerated AI character SDK\r\n *\r\n * Real-time lip sync, speech recognition, voice activity detection, and avatar animation\r\n * that runs entirely in the browser using ONNX Runtime Web.\r\n *\r\n * @packageDocumentation\r\n *\r\n * ## Main Components\r\n *\r\n * - **{@link Wav2Vec2Inference}** - LAM lip sync (52 ARKit blendshapes)\r\n * - **{@link SenseVoiceInference}** - SenseVoice ASR + emotion + language ID\r\n * - **{@link SileroVADInference}** - Voice activity detection\r\n * - **{@link EmotionController}** - Emotion state with smooth transitions\r\n *\r\n * ## Quick Start\r\n *\r\n * ```typescript\r\n * import { Wav2Vec2Inference, SenseVoiceInference } from '@omote/core';\r\n *\r\n * // Lip sync\r\n * const lam = new Wav2Vec2Inference({ modelUrl: '/models/lam-wav2vec2.onnx' });\r\n * await lam.load();\r\n * const { blendshapes } = await lam.infer(audioSamples);\r\n *\r\n * // Speech-to-text\r\n * const asr = new SenseVoiceInference({ modelUrl: '/models/sensevoice/model.int8.onnx' });\r\n * await asr.load();\r\n * const { text, emotion, language } = await asr.transcribe(audioSamples);\r\n * ```\r\n */\r\n\r\n// Events\r\nexport { EventEmitter } from './events';\r\nexport type {\r\n OmoteEvents,\r\n AnimationEvent,\r\n VisemeEvent,\r\n EmotionEvent,\r\n GazeEvent,\r\n TTSStartEvent,\r\n TTSMarkEvent,\r\n TTSEndEvent,\r\n STTPartialEvent,\r\n STTFinalEvent,\r\n SessionStateEvent,\r\n BackendEvent,\r\n} from './events';\r\n\r\n// Audio\r\nexport {\r\n MicrophoneCapture,\r\n RingBuffer,\r\n AudioScheduler,\r\n AudioChunkCoalescer,\r\n LAMPipeline,\r\n SyncedAudioPipeline,\r\n FullFacePipeline,\r\n} from './audio';\r\nexport type {\r\n MicrophoneCaptureConfig,\r\n AudioSchedulerOptions,\r\n AudioChunkCoalescerOptions,\r\n LAMPipelineOptions,\r\n LAMFrame,\r\n SyncedAudioPipelineOptions,\r\n SyncedAudioPipelineEvents,\r\n FullFacePipelineOptions,\r\n FullFaceFrame,\r\n FullFacePipelineEvents,\r\n} from './audio';\r\n\r\n// Inference - SenseVoice ASR (Speech Recognition + Emotion + Language ID + Audio Events)\r\nexport { SenseVoiceInference } from './inference';\r\nexport type {\r\n SenseVoiceConfig,\r\n SenseVoiceLanguage,\r\n SenseVoiceResult,\r\n SenseVoiceModelInfo,\r\n} from './inference';\r\n\r\n// Inference - SenseVoice Worker (Off-main-thread ASR)\r\nexport { SenseVoiceWorker } from './inference';\r\nexport type { SenseVoiceWorkerConfig } from './inference';\r\n\r\n// Inference - SenseVoice Factory (Recommended API)\r\nexport { createSenseVoice } from './inference';\r\nexport type {\r\n CreateSenseVoiceConfig,\r\n SenseVoiceBackend,\r\n} from './inference';\r\n\r\n// Inference - SenseVoice Preprocessing\r\nexport { computeKaldiFbank, applyLFR, applyCMVN, parseCMVNFromMetadata } from './inference';\r\nexport type { KaldiFbankOptions } from './inference';\r\n\r\n// Inference - SenseVoice CTC Decoder\r\nexport { ctcGreedyDecode, parseTokensFile, resolveLanguageId, resolveTextNormId } from './inference';\r\nexport type { CTCDecodeResult } from './inference';\r\n\r\n// Inference - Unified Wav2Vec2 (ASR + A2E)\r\nexport { Wav2Vec2Inference, LAM_BLENDSHAPES, ARKIT_BLENDSHAPES, CTC_VOCAB } from './inference';\r\nexport type {\r\n Wav2Vec2InferenceConfig,\r\n Wav2Vec2Result,\r\n} from './inference';\r\n\r\n// Inference - Wav2ARKit CPU (Safari/iOS lip sync)\r\nexport { Wav2ArkitCpuInference } from './inference';\r\nexport type { Wav2ArkitCpuConfig } from './inference';\r\n\r\n// Inference - Wav2ARKit CPU Worker (Off-main-thread lip sync)\r\nexport { Wav2ArkitCpuWorker } from './inference';\r\nexport type { Wav2ArkitCpuWorkerConfig } from './inference';\r\n\r\n// Inference - Lip Sync Factory (auto GPU/CPU selection)\r\nexport { createLipSync } from './inference';\r\nexport type {\r\n CreateLipSyncConfig,\r\n LipSyncBackend,\r\n LipSyncResult,\r\n LipSyncModelInfo,\r\n} from './inference';\r\n\r\n// Inference - Blendshape Utilities\r\nexport { symmetrizeBlendshapes, remapWav2ArkitToLam, WAV2ARKIT_BLENDSHAPES } from './inference';\r\n\r\n// Inference - Silero VAD (Voice Activity Detection)\r\nexport { SileroVADInference } from './inference';\r\nexport type {\r\n SileroVADConfig,\r\n VADModelInfo,\r\n VADResult,\r\n SpeechSegment,\r\n VADBackend,\r\n} from './inference';\r\n\r\n// Inference - Silero VAD Worker (Off-main-thread)\r\nexport { SileroVADWorker } from './inference';\r\nexport type {\r\n VADWorkerConfig,\r\n VADWorkerModelInfo,\r\n} from './inference';\r\n\r\n// Inference - Silero VAD Factory (Recommended API)\r\nexport { createSileroVAD, supportsVADWorker } from './inference';\r\nexport type {\r\n SileroVADFactoryConfig,\r\n SileroVADBackend,\r\n} from './inference';\r\n\r\n// Inference - Unified Inference Worker (single ORT instance for all WASM models)\r\nexport {\r\n UnifiedInferenceWorker,\r\n SenseVoiceUnifiedAdapter,\r\n Wav2ArkitCpuUnifiedAdapter,\r\n SileroVADUnifiedAdapter,\r\n} from './inference';\r\n\r\n// Inference - Safari Web Speech API (iOS native ASR)\r\nexport { SafariSpeechRecognition } from './inference';\r\nexport type {\r\n SafariSpeechConfig,\r\n SpeechRecognitionResult,\r\n SpeechResultCallback,\r\n SpeechErrorCallback,\r\n} from './inference';\r\n\r\n// Emotion\r\nexport {\r\n EMOTION_NAMES,\r\n EMOTION_VECTOR_SIZE,\r\n EmotionPresets,\r\n EmotionController,\r\n createEmotionVector,\r\n getEmotionPreset,\r\n blendEmotions,\r\n lerpEmotion,\r\n} from './emotion';\r\nexport type {\r\n EmotionName,\r\n EmotionWeights,\r\n EmotionPresetName,\r\n} from './emotion';\r\n\r\n// AI Adapters\r\nexport {\r\n AgentCoreAdapter,\r\n ConversationOrchestrator,\r\n TenantManager,\r\n AudioSyncManager,\r\n InterruptionHandler,\r\n} from './ai';\r\n\r\n// Cache\r\nexport {\r\n ModelCache,\r\n getModelCache,\r\n fetchWithCache,\r\n preloadModels,\r\n formatBytes,\r\n getCacheKey,\r\n configureCacheLimit,\r\n getCacheConfig,\r\n} from './cache/ModelCache';\r\nexport type { ValidationResult, FetchWithCacheOptions, CacheConfig, QuotaInfo } from './cache/ModelCache';\r\n\r\n// Utils - Runtime Detection & Backend Selection\r\nexport {\r\n isIOS,\r\n isAndroid,\r\n isMobile,\r\n isIOSSafari,\r\n isSafari,\r\n hasWebGPUApi,\r\n getRecommendedBackend,\r\n resolveBackend,\r\n getOptimalWasmThreads,\r\n shouldEnableWasmProxy,\r\n // Platform-specific utilities\r\n isSpeechRecognitionAvailable,\r\n shouldUseNativeASR,\r\n shouldUseServerLipSync,\r\n shouldUseCpuLipSync,\r\n} from './utils/runtime';\r\nexport type { RuntimeBackend, BackendPreference } from './utils/runtime';\r\n\r\n// ONNX Runtime Lazy Loader\r\nexport {\r\n getOnnxRuntime,\r\n getOnnxRuntimeForPreference,\r\n isWebGPUAvailable,\r\n getSessionOptions,\r\n createSessionWithFallback,\r\n getLoadedBackend,\r\n isOnnxRuntimeLoaded,\r\n preloadOnnxRuntime,\r\n} from './inference/onnxLoader';\r\nexport type { SessionOptions } from './inference/onnxLoader';\r\n\r\n// Logging\r\nexport {\r\n configureLogging,\r\n getLoggingConfig,\r\n resetLoggingConfig,\r\n setLogLevel,\r\n setLoggingEnabled,\r\n createLogger,\r\n noopLogger,\r\n LOG_LEVEL_PRIORITY,\r\n DEFAULT_LOGGING_CONFIG,\r\n} from './logging';\r\nexport type {\r\n LogLevel,\r\n LogEntry,\r\n LogSink,\r\n LogFormatter,\r\n LoggingConfig,\r\n ILogger,\r\n} from './logging';\r\n\r\n// Telemetry\r\nexport {\r\n OmoteTelemetry,\r\n configureTelemetry,\r\n getTelemetry,\r\n ConsoleExporter,\r\n OTLPExporter,\r\n MetricNames,\r\n INFERENCE_LATENCY_BUCKETS,\r\n MODEL_LOAD_TIME_BUCKETS,\r\n} from './telemetry';\r\nexport type {\r\n ActiveSpan,\r\n TelemetryConfig,\r\n TelemetryExporter,\r\n OTLPExporterConfig,\r\n SamplingConfig,\r\n SpanAttributes,\r\n ModelSpanAttributes,\r\n InferenceSpanAttributes,\r\n CacheSpanAttributes,\r\n TelemetryExporterInterface,\r\n SpanData,\r\n MetricData,\r\n} from './telemetry';\r\n\r\nexport type {\r\n AIAdapter,\r\n AIAdapterEvents,\r\n AISessionState,\r\n SessionConfig,\r\n TenantConfig,\r\n VoiceConfig,\r\n ConversationMessage,\r\n MessageRole,\r\n ConversationSession,\r\n SessionSnapshot,\r\n AgentCoreConfig,\r\n OrchestratorConfig,\r\n OrchestratorEvents,\r\n TenantQuota,\r\n TenantUsage,\r\n TokenRefreshCallback,\r\n AudioSyncConfig,\r\n AudioSyncEvents,\r\n InterruptionConfig,\r\n InterruptionEvents,\r\n} from './ai';\r\n\r\n// Animation\r\nexport {\r\n AnimationGraph,\r\n AudioEnergyAnalyzer,\r\n EmphasisDetector,\r\n EmotionToBlendshapeMapper,\r\n ProceduralLifeLayer,\r\n UPPER_FACE_BLENDSHAPES,\r\n EMOTION_ARKIT_MAP,\r\n calculateRMS,\r\n calculatePeak,\r\n DEFAULT_ANIMATION_CONFIG,\r\n} from './animation';\r\nexport type {\r\n EmotionLabel,\r\n AnimationStateName,\r\n AnimationTrigger,\r\n AnimationLayer,\r\n AnimationClip,\r\n BlendWeight,\r\n AnimationState,\r\n Transition,\r\n EmotionAnimationMap,\r\n AnimationGraphConfig,\r\n AnimationOutput,\r\n AnimationGraphEvents,\r\n EmotionFrame,\r\n Emotion2VecLabel,\r\n UpperFaceBlendshapeName,\r\n UpperFaceBlendshapes,\r\n EmotionBlendMode,\r\n EmotionBlendshapeConfig,\r\n LifeLayerConfig,\r\n LifeLayerInput,\r\n LifeLayerOutput,\r\n} from './animation';\r\n\r\n// Platform types (shared with backend via @omote/types)\r\nexport type {\r\n AvatarFormat,\r\n GSplatConfig,\r\n CharacterAvatar,\r\n CharacterVoice,\r\n CharacterPersonality,\r\n CharacterMemory,\r\n Character,\r\n CharacterSpec,\r\n} from '@omote/types';\r\nexport type {\r\n SessionStatus,\r\n SessionMessage,\r\n PlatformSession,\r\n} from '@omote/types';\r\nexport type {\r\n LAMJobStatus,\r\n LAMJob,\r\n ARKitToFLAMEMapping,\r\n} from '@omote/types';\r\nexport type {\r\n ResponseStartEvent,\r\n ResponseChunkEvent,\r\n AudioChunkEvent,\r\n ResponseEndEvent,\r\n ErrorEvent as ProtocolErrorEvent,\r\n ProtocolEvent,\r\n} from '@omote/types';\r\nexport { PROTOCOL_VERSION, isProtocolEvent } from '@omote/types';\r\nexport type {\r\n PaginatedResponse,\r\n ApiError,\r\n CreateCharacterRequest,\r\n CreateCharacterResponse,\r\n CreateSessionRequest,\r\n CreateSessionResponse,\r\n CreateLAMJobRequest,\r\n CreateLAMJobResponse,\r\n} from '@omote/types';\r\n","/**\n * Type-safe event emitter for Omote core events\n *\n * @category Events\n */\n\nexport type EventCallback<T = unknown> = (data: T) => void;\n\nexport class EventEmitter<TEvents extends { [key: string]: unknown }> {\n private listeners = new Map<keyof TEvents, Set<EventCallback<unknown>>>();\n\n on<K extends keyof TEvents>(event: K, callback: EventCallback<TEvents[K]>): () => void {\n if (!this.listeners.has(event)) {\n this.listeners.set(event, new Set());\n }\n this.listeners.get(event)!.add(callback as EventCallback<unknown>);\n\n // Return unsubscribe function\n return () => this.off(event, callback);\n }\n\n off<K extends keyof TEvents>(event: K, callback: EventCallback<TEvents[K]>): void {\n this.listeners.get(event)?.delete(callback as EventCallback<unknown>);\n }\n\n emit<K extends keyof TEvents>(event: K, data: TEvents[K]): void {\n this.listeners.get(event)?.forEach((cb) => cb(data));\n }\n\n once<K extends keyof TEvents>(event: K, callback: EventCallback<TEvents[K]>): () => void {\n const wrapper: EventCallback<TEvents[K]> = (data) => {\n this.off(event, wrapper);\n callback(data);\n };\n return this.on(event, wrapper);\n }\n\n removeAllListeners(event?: keyof TEvents): void {\n if (event) {\n this.listeners.delete(event);\n } else {\n this.listeners.clear();\n }\n }\n}\n","/**\n * Microphone capture - renderer-agnostic audio input\n *\n * Captures audio from the microphone and emits PCM chunks.\n * Works in any JavaScript environment with Web Audio API.\n *\n * @category Audio\n */\n\nimport { EventEmitter, type OmoteEvents } from '../events';\n\nexport interface MicrophoneCaptureConfig {\n /** Target sample rate (default: 16000 for speech processing) */\n sampleRate?: number;\n /** Chunk size in samples (default: 1600 = 100ms at 16kHz) */\n chunkSize?: number;\n}\n\nexport class MicrophoneCapture {\n private config: Required<MicrophoneCaptureConfig>;\n private stream: MediaStream | null = null;\n private context: AudioContext | null = null;\n private processor: ScriptProcessorNode | null = null;\n private buffer: Float32Array = new Float32Array(0);\n private _isRecording = false;\n private _loggedFirstChunk = false;\n /** Actual AudioContext sample rate (may differ from target on Firefox) */\n private _nativeSampleRate = 0;\n\n constructor(\n private events: EventEmitter<OmoteEvents>,\n config: MicrophoneCaptureConfig = {}\n ) {\n this.config = {\n sampleRate: config.sampleRate ?? 16000,\n chunkSize: config.chunkSize ?? 1600,\n };\n }\n\n get isRecording(): boolean {\n return this._isRecording;\n }\n\n get isSupported(): boolean {\n return typeof navigator !== 'undefined' && !!navigator.mediaDevices?.getUserMedia;\n }\n\n async start(): Promise<void> {\n if (!this.isSupported) {\n this.events.emit('error', {\n code: 'MICROPHONE_NOT_SUPPORTED',\n message: 'Microphone not supported in this browser',\n });\n return;\n }\n\n if (this._isRecording) return;\n\n try {\n this.stream = await navigator.mediaDevices.getUserMedia({\n audio: {\n sampleRate: { ideal: this.config.sampleRate },\n channelCount: 1,\n echoCancellation: true,\n noiseSuppression: true,\n autoGainControl: true,\n },\n });\n\n // Create AudioContext and connect mic stream.\n // Firefox throws NotSupportedError when connecting a MediaStreamSource\n // to an AudioContext with a different sample rate than the stream's native\n // rate. Fall back to native rate + JS resampling when this happens.\n this.context = new AudioContext({ sampleRate: this.config.sampleRate });\n\n // Resume AudioContext if suspended (browser autoplay policy)\n if (this.context.state === 'suspended') {\n await this.context.resume();\n }\n\n let source: MediaStreamAudioSourceNode;\n try {\n source = this.context.createMediaStreamSource(this.stream);\n this._nativeSampleRate = this.context.sampleRate;\n } catch (sourceErr) {\n // Firefox: \"Connecting AudioNodes from AudioContexts with different\n // sample-rate is currently not supported\"\n console.warn(\n '[MicrophoneCapture] Cannot connect stream at',\n this.config.sampleRate + 'Hz, falling back to native rate:',\n (sourceErr as Error).message\n );\n await this.context.close();\n this.context = new AudioContext(); // native rate (typically 48kHz)\n if (this.context.state === 'suspended') {\n await this.context.resume();\n }\n source = this.context.createMediaStreamSource(this.stream);\n this._nativeSampleRate = this.context.sampleRate;\n console.log('[MicrophoneCapture] Using native rate:', this._nativeSampleRate, 'Hz → resampling to', this.config.sampleRate, 'Hz');\n }\n\n // Use ScriptProcessor for broad compatibility\n this.processor = this.context.createScriptProcessor(4096, 1, 1);\n\n this.processor.onaudioprocess = (e) => {\n const raw = e.inputBuffer.getChannelData(0);\n\n // Resample if AudioContext is running at a different rate than target\n const input = this._nativeSampleRate !== this.config.sampleRate\n ? this.resample(raw, this._nativeSampleRate, this.config.sampleRate)\n : raw;\n\n // Calculate audio level\n let rms = 0;\n let peak = 0;\n for (let i = 0; i < input.length; i++) {\n const abs = Math.abs(input[i]);\n rms += input[i] * input[i];\n if (abs > peak) peak = abs;\n }\n rms = Math.sqrt(rms / input.length);\n\n this.events.emit('audio.level', { rms, peak });\n\n // Accumulate samples\n const newBuffer = new Float32Array(this.buffer.length + input.length);\n newBuffer.set(this.buffer);\n newBuffer.set(input, this.buffer.length);\n this.buffer = newBuffer;\n\n // Emit chunks\n let chunkCount = 0;\n while (this.buffer.length >= this.config.chunkSize) {\n const chunk = this.buffer.slice(0, this.config.chunkSize);\n this.buffer = this.buffer.slice(this.config.chunkSize);\n\n const pcm = this.floatToPCM16(chunk);\n this.events.emit('audio.chunk', {\n pcm,\n timestamp: performance.now(),\n });\n chunkCount++;\n }\n // Log first emission for debugging\n if (chunkCount > 0 && !this._loggedFirstChunk) {\n console.log('[MicrophoneCapture] Emitting audio chunks:', chunkCount);\n this._loggedFirstChunk = true;\n }\n };\n\n source.connect(this.processor);\n this.processor.connect(this.context.destination);\n\n this._isRecording = true;\n console.log('[MicrophoneCapture] Started recording, context state:', this.context.state);\n } catch (err) {\n this.events.emit('error', {\n code: 'MICROPHONE_ERROR',\n message: (err as Error).message,\n details: err,\n });\n }\n }\n\n stop(): void {\n if (this.processor) {\n this.processor.disconnect();\n this.processor = null;\n }\n\n if (this.context) {\n this.context.close();\n this.context = null;\n }\n\n if (this.stream) {\n this.stream.getTracks().forEach((t) => t.stop());\n this.stream = null;\n }\n\n this.buffer = new Float32Array(0);\n this._isRecording = false;\n }\n\n /**\n * Resample audio using linear interpolation.\n * Used when the AudioContext runs at the device's native rate (e.g. 48kHz)\n * and we need to downsample to the target rate (e.g. 16kHz).\n */\n private resample(input: Float32Array, fromRate: number, toRate: number): Float32Array {\n if (fromRate === toRate) return input;\n const ratio = fromRate / toRate;\n const outputLength = Math.floor(input.length / ratio);\n const output = new Float32Array(outputLength);\n for (let i = 0; i < outputLength; i++) {\n const srcIdx = i * ratio;\n const lo = Math.floor(srcIdx);\n const hi = Math.min(lo + 1, input.length - 1);\n const frac = srcIdx - lo;\n output[i] = input[lo] * (1 - frac) + input[hi] * frac;\n }\n return output;\n }\n\n private floatToPCM16(float32: Float32Array): Int16Array {\n const pcm = new Int16Array(float32.length);\n for (let i = 0; i < float32.length; i++) {\n const s = Math.max(-1, Math.min(1, float32[i]));\n pcm[i] = s < 0 ? s * 0x8000 : s * 0x7fff;\n }\n return pcm;\n }\n}\n","/**\n * Ring buffer for audio sample accumulation\n *\n * Efficiently accumulates audio samples and provides\n * contiguous buffers for inference without memory allocation churn.\n *\n * @category Audio\n */\n\nexport class RingBuffer {\n private buffer: Float32Array;\n private writeIndex = 0;\n private isFull = false;\n\n constructor(private readonly size: number) {\n this.buffer = new Float32Array(size);\n }\n\n /**\n * Write samples to the ring buffer\n * Converts Int16Array PCM to Float32\n */\n write(pcm: Int16Array): void {\n for (let i = 0; i < pcm.length; i++) {\n this.buffer[this.writeIndex] = pcm[i] / 32768.0;\n this.writeIndex = (this.writeIndex + 1) % this.size;\n\n if (this.writeIndex === 0) {\n this.isFull = true;\n }\n }\n }\n\n /**\n * Write float samples directly\n */\n writeFloat(samples: Float32Array): void {\n for (let i = 0; i < samples.length; i++) {\n this.buffer[this.writeIndex] = samples[i];\n this.writeIndex = (this.writeIndex + 1) % this.size;\n\n if (this.writeIndex === 0) {\n this.isFull = true;\n }\n }\n }\n\n /**\n * Get a contiguous copy of the buffer contents in chronological order\n * Returns null if buffer isn't full yet\n */\n read(): Float32Array | null {\n if (!this.isFull) return null;\n\n const output = new Float32Array(this.size);\n\n // Copy from writeIndex to end (oldest samples)\n const firstPart = this.buffer.subarray(this.writeIndex);\n output.set(firstPart, 0);\n\n // Copy from 0 to writeIndex (newest samples)\n const secondPart = this.buffer.subarray(0, this.writeIndex);\n output.set(secondPart, firstPart.length);\n\n return output;\n }\n\n /**\n * Check if buffer has enough samples\n */\n get hasData(): boolean {\n return this.isFull;\n }\n\n /**\n * Get current fill level (0-1)\n */\n get fillLevel(): number {\n if (this.isFull) return 1;\n return this.writeIndex / this.size;\n }\n\n /**\n * Reset the buffer\n */\n reset(): void {\n this.buffer.fill(0);\n this.writeIndex = 0;\n this.isFull = false;\n }\n}\n","/**\r\n * AudioScheduler - Enterprise-grade Web Audio API scheduling\r\n *\r\n * Implements the lookahead scheduling pattern from Chris Wilson's\r\n * \"A Tale of Two Clocks\" - the authoritative guide on Web Audio timing.\r\n *\r\n * Key Features:\r\n * - Uses AudioContext.currentTime (hardware clock) for sample-accurate timing\r\n * - Pre-schedules audio chunks for gapless playback\r\n * - Tracks scheduled sources for cleanup\r\n * - Provides playback state monitoring\r\n *\r\n * @see https://web.dev/articles/audio-scheduling\r\n * @category Audio\r\n */\r\n\r\nexport interface AudioSchedulerOptions {\r\n /** Sample rate in Hz (default: 16000 for speech) */\r\n sampleRate?: number\r\n /** Number of audio channels (default: 1 for mono) */\r\n channels?: number\r\n /**\r\n * Initial lookahead delay in seconds before first audio plays.\r\n * Gives LAM inference time to compute blendshapes before audio starts.\r\n * Default: 0.05 (50ms) for WebGPU, increase to 0.3-0.5 for WASM on iOS.\r\n */\r\n initialLookaheadSec?: number\r\n}\r\n\r\nexport class AudioScheduler {\r\n private context: AudioContext | null = null\r\n private nextPlayTime = 0\r\n private scheduledSources: Array<{ source: AudioBufferSourceNode; gainNode: GainNode }> = []\r\n private isPlaying = false\r\n\r\n constructor(private readonly options: AudioSchedulerOptions = {}) {}\r\n\r\n /**\r\n * Initialize AudioContext with specified sample rate\r\n *\r\n * Note: This is now a no-op. AudioContext is created lazily on first schedule()\r\n * to avoid browser autoplay policy issues (requires user gesture).\r\n */\r\n async initialize(): Promise<void> {\r\n // No-op - context will be created lazily in ensureContext()\r\n console.log('[AudioScheduler] Ready for lazy initialization')\r\n }\r\n\r\n /**\r\n * Eagerly create and warm up the AudioContext\r\n *\r\n * Call this when a playback session starts (e.g., when AI response begins).\r\n * The AudioContext needs time to initialize the audio hardware — on Windows\r\n * this can take 50-100ms. By warming up early (before audio data arrives),\r\n * the context is fully ready when schedule() is first called.\r\n *\r\n * Must be called after a user gesture (click/tap) for autoplay policy.\r\n */\r\n async warmup(): Promise<void> {\r\n await this.ensureContext()\r\n }\r\n\r\n /**\r\n * Ensure AudioContext is created and ready\r\n * Called lazily on first schedule() - requires user gesture\r\n */\r\n private async ensureContext(): Promise<AudioContext> {\r\n if (this.context && this.context.state !== 'closed') {\r\n return this.context\r\n }\r\n\r\n const sampleRate = this.options.sampleRate ?? 16000\r\n this.context = new AudioContext({ sampleRate })\r\n\r\n // Resume if suspended (browser autoplay policy)\r\n if (this.context.state === 'suspended') {\r\n await this.context.resume()\r\n }\r\n\r\n console.log(`[AudioScheduler] AudioContext initialized at ${sampleRate}Hz`)\r\n return this.context\r\n }\r\n\r\n /**\r\n * Schedule an audio chunk for playback\r\n *\r\n * Uses Web Audio's hardware-accurate clock for sample-perfect timing.\r\n * Chunks are scheduled immediately, not when they should play - this\r\n * ensures gapless playback even if main thread stalls.\r\n *\r\n * @param audioData - Float32Array of audio samples\r\n * @returns Scheduled playback time in AudioContext seconds\r\n */\r\n async schedule(audioData: Float32Array): Promise<number> {\r\n // Lazy initialization (requires user gesture)\r\n const ctx = await this.ensureContext()\r\n const channels = this.options.channels ?? 1\r\n\r\n // Initialize playback timing on first chunk\r\n // Lookahead gives AudioContext time to initialize and LAM time to pre-compute.\r\n // 50ms default is fine for WebGPU; WASM on iOS needs 300-500ms.\r\n if (!this.isPlaying) {\r\n const lookahead = this.options.initialLookaheadSec ?? 0.05\r\n this.nextPlayTime = ctx.currentTime + lookahead\r\n this.isPlaying = true\r\n }\r\n\r\n // Create audio buffer\r\n const audioBuffer = ctx.createBuffer(channels, audioData.length, ctx.sampleRate)\r\n audioBuffer.getChannelData(0).set(audioData)\r\n\r\n // Create gain node for fade control\r\n const gainNode = ctx.createGain()\r\n gainNode.gain.value = 1.0\r\n gainNode.connect(ctx.destination)\r\n\r\n // Create and schedule source\r\n const source = ctx.createBufferSource()\r\n source.buffer = audioBuffer\r\n source.connect(gainNode) // Route through gain node for fade control\r\n\r\n // Schedule at precise time for gapless playback\r\n const scheduleTime = this.nextPlayTime\r\n source.start(scheduleTime)\r\n\r\n // Track scheduled source with its gain node\r\n const entry = { source, gainNode }\r\n this.scheduledSources.push(entry)\r\n\r\n // Clean up when playback completes naturally to prevent unbounded growth\r\n source.onended = () => {\r\n const idx = this.scheduledSources.indexOf(entry)\r\n if (idx !== -1) {\r\n this.scheduledSources.splice(idx, 1)\r\n }\r\n }\r\n\r\n // Update next play time\r\n const duration = audioData.length / ctx.sampleRate\r\n this.nextPlayTime = scheduleTime + duration\r\n\r\n return scheduleTime\r\n }\r\n\r\n /**\r\n * Get current audio clock time\r\n *\r\n * This is the hardware-accurate time, NOT JavaScript time.\r\n * Use this for synchronizing visual animations to audio.\r\n *\r\n * @returns Current time in AudioContext seconds\r\n */\r\n getCurrentTime(): number {\r\n if (!this.context) return 0\r\n return this.context.currentTime\r\n }\r\n\r\n /**\r\n * Get scheduled playback end time\r\n */\r\n getPlaybackEndTime(): number {\r\n return this.nextPlayTime\r\n }\r\n\r\n /**\r\n * Check if all scheduled audio has finished playing\r\n */\r\n isComplete(): boolean {\r\n if (!this.context || !this.isPlaying) return false\r\n return this.context.currentTime >= this.nextPlayTime\r\n }\r\n\r\n /**\r\n * Cancel all scheduled audio with smooth fade-out\r\n *\r\n * Applies a linear fade-out to all playing sources and stops them gracefully.\r\n * Prevents audio clicks/pops by ramping gain to zero before stopping.\r\n *\r\n * @param fadeOutMs - Fade-out duration in milliseconds (default: 50ms)\r\n * @returns Promise that resolves when fade-out completes\r\n */\r\n async cancelAll(fadeOutMs: number = 50): Promise<void> {\r\n if (!this.context || this.scheduledSources.length === 0) {\r\n return\r\n }\r\n\r\n const ctx = this.context\r\n const currentTime = ctx.currentTime\r\n const fadeOutSec = fadeOutMs / 1000\r\n\r\n // Apply fade-out to all scheduled sources\r\n for (const { source, gainNode } of this.scheduledSources) {\r\n try {\r\n // Ramp gain from current value to zero\r\n gainNode.gain.setValueAtTime(gainNode.gain.value, currentTime)\r\n gainNode.gain.linearRampToValueAtTime(0.0, currentTime + fadeOutSec)\r\n\r\n // Stop source after fade completes\r\n source.stop(currentTime + fadeOutSec)\r\n } catch (err) {\r\n // Source may have already stopped naturally - ignore error\r\n }\r\n }\r\n\r\n // Clear tracking arrays\r\n this.scheduledSources = []\r\n this.isPlaying = false\r\n this.nextPlayTime = 0\r\n\r\n // Wait for fade-out to complete\r\n await new Promise(resolve => setTimeout(resolve, fadeOutMs))\r\n }\r\n\r\n /**\r\n * Reset scheduler state for new playback session\r\n * Stops any orphaned sources that weren't cleaned up by cancelAll()\r\n */\r\n reset(): void {\r\n // Stop any still-playing sources before clearing\r\n if (this.context) {\r\n const now = this.context.currentTime\r\n for (const { source, gainNode } of this.scheduledSources) {\r\n try {\r\n gainNode.gain.setValueAtTime(0, now)\r\n source.stop(now)\r\n } catch {\r\n // Already stopped\r\n }\r\n }\r\n }\r\n this.nextPlayTime = 0\r\n this.isPlaying = false\r\n this.scheduledSources = []\r\n }\r\n\r\n /**\r\n * Cleanup resources\r\n */\r\n dispose(): void {\r\n if (this.context) {\r\n this.context.close()\r\n this.context = null\r\n }\r\n this.scheduledSources = []\r\n this.isPlaying = false\r\n }\r\n}\r\n","/**\n * AudioChunkCoalescer - Combine small network chunks into optimal buffers\n *\n * Network streaming often delivers audio in small chunks (e.g., 32ms from TTS APIs).\n * Creating an AudioBufferSourceNode for each tiny chunk is inefficient and can cause\n * overhead from object creation/GC.\n *\n * This class implements a double-buffering pattern: accumulate small chunks in a\n * temporary buffer, then flush to playback queue when threshold is reached.\n *\n * Benefits:\n * - Reduces AudioBufferSourceNode overhead (fewer nodes = less GC pressure)\n * - Configurable buffer size for optimal playback chunk duration\n * - Maintains sample-accurate timing despite buffering\n *\n * Based on patterns from HLS.js and production streaming implementations.\n *\n * @category Audio\n */\n\nexport interface AudioChunkCoalescerOptions {\n /**\n * Target duration in milliseconds for combined chunks\n * Default: 200ms (balances latency vs overhead)\n *\n * Smaller values = lower latency, more overhead\n * Larger values = higher latency, less overhead\n */\n targetDurationMs?: number\n\n /**\n * Sample rate in Hz\n * Default: 16000 (speech quality)\n */\n sampleRate?: number\n}\n\nexport class AudioChunkCoalescer {\n private tempBuffer: Uint8Array[] = []\n private readonly targetBytes: number\n\n constructor(private readonly options: AudioChunkCoalescerOptions = {}) {\n const targetMs = options.targetDurationMs ?? 200\n const sampleRate = options.sampleRate ?? 16000\n\n // Calculate target bytes: (duration_s) * (samples/s) * (2 bytes per Int16 sample)\n this.targetBytes = (targetMs / 1000) * sampleRate * 2\n }\n\n /**\n * Add a chunk to the temporary buffer\n *\n * @param chunk - Uint8Array containing Int16 PCM audio\n * @returns Combined buffer if threshold reached, null otherwise\n */\n add(chunk: Uint8Array): ArrayBuffer | null {\n // Add to temporary buffer\n this.tempBuffer.push(chunk)\n\n // Calculate total bytes buffered\n const totalBytes = this.tempBuffer.reduce((sum, c) => sum + c.length, 0)\n\n // If we've reached the threshold, combine and return\n if (totalBytes >= this.targetBytes) {\n return this.flush()\n }\n\n return null\n }\n\n /**\n * Flush remaining buffered data\n *\n * Call this when the stream ends to ensure all audio is processed,\n * even if it doesn't reach the target threshold.\n *\n * @returns Combined buffer, or null if buffer is empty\n */\n flush(): ArrayBuffer | null {\n if (this.tempBuffer.length === 0) {\n return null\n }\n\n // Calculate total size\n const totalBytes = this.tempBuffer.reduce((sum, c) => sum + c.length, 0)\n\n // Combine all chunks into single buffer\n const combined = new Uint8Array(totalBytes)\n let offset = 0\n for (const chunk of this.tempBuffer) {\n combined.set(chunk, offset)\n offset += chunk.length\n }\n\n // Clear temp buffer\n this.tempBuffer = []\n\n return combined.buffer\n }\n\n /**\n * Get current buffer fill level (0-1)\n */\n get fillLevel(): number {\n const totalBytes = this.tempBuffer.reduce((sum, c) => sum + c.length, 0)\n return Math.min(1, totalBytes / this.targetBytes)\n }\n\n /**\n * Get current buffered duration in milliseconds\n */\n getBufferedDurationMs(): number {\n const sampleRate = this.options.sampleRate ?? 16000\n const totalBytes = this.tempBuffer.reduce((sum, c) => sum + c.length, 0)\n const samples = totalBytes / 2 // Int16 = 2 bytes per sample\n return (samples / sampleRate) * 1000\n }\n\n /**\n * Get number of chunks currently buffered\n */\n get chunkCount(): number {\n return this.tempBuffer.length\n }\n\n /**\n * Reset the coalescer\n */\n reset(): void {\n this.tempBuffer = []\n }\n}\n","/**\r\n * LAMPipeline - Coordinate LAM (Wav2Vec2) inference with frame synchronization\r\n *\r\n * Manages the buffering and processing pipeline for LAM lip sync:\r\n * 1. Accumulates audio samples in a ring buffer\r\n * 2. Triggers LAM inference when buffer reaches required size (16000 samples @ 16kHz = 1.0s)\r\n * 3. Queues resulting blendshape frames with precise timestamps\r\n * 4. Provides frames synchronized to AudioContext clock\r\n *\r\n * Key Design Decisions:\r\n * - Ring buffer pattern for efficient sample accumulation (no allocation churn)\r\n * - Frame queue with timestamps for deterministic playback\r\n * - Timestamp-based frame retrieval (not callback) for renderer flexibility\r\n *\r\n * Based on patterns from Chrome Audio Worklet design and Web Audio clock management.\r\n *\r\n * @see https://developer.chrome.com/blog/audio-worklet-design-pattern\r\n * @category Audio\r\n */\r\n\r\nimport { RingBuffer } from './RingBuffer'\r\nimport type { LipSyncBackend } from '../inference/LipSyncBackend'\r\n\r\nexport interface LAMFrame {\r\n /** 52 ARKit blendshape weights */\r\n frame: Float32Array\r\n /** AudioContext time when this frame should be displayed */\r\n timestamp: number\r\n}\r\n\r\nexport interface LAMPipelineOptions {\r\n /**\r\n * Sample rate in Hz (must match audio playback)\r\n * Default: 16000\r\n */\r\n sampleRate?: number\r\n\r\n /**\r\n * LAM inference callback\r\n * Called each time LAM processes a buffer\r\n */\r\n onInference?: (frameCount: number) => void\r\n\r\n /**\r\n * Error callback for inference failures\r\n */\r\n onError?: (error: Error) => void\r\n}\r\n\r\nexport class LAMPipeline {\r\n private readonly REQUIRED_SAMPLES = 16000 // 1.0s at 16kHz (LAM requirement)\r\n private readonly FRAME_RATE = 30 // LAM outputs 30fps\r\n\r\n private buffer: Float32Array = new Float32Array(0)\r\n private bufferStartTime = 0\r\n private frameQueue: LAMFrame[] = []\r\n\r\n /**\r\n * Last successfully retrieved frame\r\n * Used as fallback when no new frame is available to prevent avatar freezing\r\n */\r\n private lastFrame: Float32Array | null = null\r\n\r\n constructor(private readonly options: LAMPipelineOptions = {}) {}\r\n\r\n /**\r\n * Push audio samples into the pipeline\r\n *\r\n * Accumulates samples and triggers LAM inference when buffer is full.\r\n * Multiple calls may be needed to accumulate enough samples.\r\n *\r\n * @param samples - Float32Array of audio samples\r\n * @param timestamp - AudioContext time when these samples start playing\r\n * @param lam - LAM inference engine\r\n */\r\n async push(samples: Float32Array, timestamp: number, lam: LipSyncBackend): Promise<void> {\r\n // Track buffer start time when empty\r\n if (this.buffer.length === 0) {\r\n this.bufferStartTime = timestamp\r\n }\r\n\r\n // Accumulate samples\r\n const newBuffer = new Float32Array(this.buffer.length + samples.length)\r\n newBuffer.set(this.buffer, 0)\r\n newBuffer.set(samples, this.buffer.length)\r\n this.buffer = newBuffer\r\n\r\n // Process ALL complete chunks (not just one)\r\n // Critical for AgentCore which delivers entire sentences at once (30-50K+ samples)\r\n // Without the while loop, samples pile up and LAM falls behind audio playback\r\n while (this.buffer.length >= this.REQUIRED_SAMPLES) {\r\n await this.processBuffer(lam)\r\n\r\n // Yield to main thread between inference batches so RAF can run.\r\n // On iOS WASM, each inference blocks 100-500ms — without yielding,\r\n // the renderer freezes for the entire while-loop duration.\r\n if (this.buffer.length >= this.REQUIRED_SAMPLES) {\r\n await new Promise<void>(r => setTimeout(r, 0))\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Process accumulated buffer through LAM inference\r\n */\r\n private async processBuffer(lam: LipSyncBackend): Promise<void> {\r\n try {\r\n // Extract exactly REQUIRED_SAMPLES for inference\r\n const toProcess = this.buffer.slice(0, this.REQUIRED_SAMPLES)\r\n const processedStartTime = this.bufferStartTime\r\n\r\n // Keep remaining samples for next inference\r\n this.buffer = this.buffer.slice(this.REQUIRED_SAMPLES)\r\n\r\n // Update start time for remaining buffer\r\n const processedDuration = this.REQUIRED_SAMPLES / (this.options.sampleRate ?? 16000)\r\n this.bufferStartTime = processedStartTime + processedDuration\r\n\r\n // Run LAM inference\r\n const result = await lam.infer(toProcess)\r\n\r\n // Queue frames with timestamps\r\n const frameDuration = 1 / this.FRAME_RATE\r\n for (let i = 0; i < result.blendshapes.length; i++) {\r\n const frame = result.blendshapes[i]\r\n const timestamp = processedStartTime + (i * frameDuration)\r\n this.frameQueue.push({ frame, timestamp })\r\n }\r\n\r\n // Notify callback\r\n this.options.onInference?.(result.blendshapes.length)\r\n } catch (error) {\r\n this.options.onError?.(error as Error)\r\n\r\n // Clear buffer on error to prevent repeated failures\r\n this.buffer = new Float32Array(0)\r\n this.bufferStartTime = 0\r\n }\r\n }\r\n\r\n /**\r\n * Get the frame that should be displayed at the current time\r\n *\r\n * Automatically removes frames that have already been displayed.\r\n * This prevents memory leaks from accumulating old frames.\r\n *\r\n * Discard Window (prevents premature frame discarding):\r\n * - WebGPU: 0.5s (LAM inference 20-100ms + RAF jitter + React stalls)\r\n * - WASM: 1.0s (LAM inference 50-500ms + higher variability)\r\n *\r\n * Last-Frame-Hold: Returns last valid frame instead of null to prevent\r\n * avatar freezing when between frames (RAF at 60fps vs LAM at 30fps).\r\n *\r\n * @param currentTime - Current AudioContext time\r\n * @param lam - LAM inference engine (optional, for backend detection)\r\n * @returns Current frame, or last frame as fallback, or null if no frames yet\r\n */\r\n getFrameForTime(currentTime: number, lam?: { backend: 'webgpu' | 'wasm' | null }): Float32Array | null {\r\n // Dynamic discard window based on backend performance characteristics\r\n const discardWindow = lam?.backend === 'wasm' ? 1.0 : 0.5\r\n\r\n // Remove frames that are too old (already displayed)\r\n let discardedCount = 0\r\n while (this.frameQueue.length > 0 && this.frameQueue[0].timestamp < currentTime - discardWindow) {\r\n const discarded = this.frameQueue.shift()!\r\n discardedCount++\r\n\r\n // Log frame discards for debugging sync issues\r\n if (discardedCount === 1) {\r\n const ageMs = ((currentTime - discarded.timestamp) * 1000).toFixed(0)\r\n console.warn('[LAM] Frame(s) discarded as too old', {\r\n ageMs,\r\n discardWindowMs: discardWindow * 1000,\r\n queueLength: this.frameQueue.length,\r\n backend: lam?.backend ?? 'unknown'\r\n })\r\n }\r\n }\r\n\r\n // Return the frame that should be playing now\r\n if (this.frameQueue.length > 0 && this.frameQueue[0].timestamp <= currentTime) {\r\n const { frame } = this.frameQueue.shift()!\r\n this.lastFrame = frame // Cache for fallback\r\n return frame\r\n }\r\n\r\n // Last-frame-hold: Return cached frame instead of null to prevent freezing\r\n // This handles RAF running at 60fps while LAM produces 30fps\r\n return this.lastFrame\r\n }\r\n\r\n /**\r\n * Get all frames in the queue (for debugging/monitoring)\r\n */\r\n getQueuedFrames(): LAMFrame[] {\r\n return [...this.frameQueue]\r\n }\r\n\r\n /**\r\n * Get current buffer fill level (0-1)\r\n */\r\n get fillLevel(): number {\r\n return Math.min(1, this.buffer.length / this.REQUIRED_SAMPLES)\r\n }\r\n\r\n /**\r\n * Get number of frames queued\r\n */\r\n get queuedFrameCount(): number {\r\n return this.frameQueue.length\r\n }\r\n\r\n /**\r\n * Get buffered audio duration in seconds\r\n */\r\n get bufferedDuration(): number {\r\n return this.buffer.length / (this.options.sampleRate ?? 16000)\r\n }\r\n\r\n /**\r\n * Flush remaining buffered audio\r\n *\r\n * Processes any remaining audio in the buffer, even if less than REQUIRED_SAMPLES.\r\n * This ensures the final audio chunk generates blendshape frames.\r\n *\r\n * Should be called when audio stream ends to prevent losing the last 0-1 seconds.\r\n *\r\n * @param lam - LAM inference engine\r\n */\r\n async flush(lam: LipSyncBackend): Promise<void> {\r\n if (this.buffer.length === 0) {\r\n return // Nothing to flush\r\n }\r\n\r\n // Pad buffer to REQUIRED_SAMPLES (LAM expects exactly 16000 samples)\r\n const padded = new Float32Array(this.REQUIRED_SAMPLES)\r\n padded.set(this.buffer, 0)\r\n // Remaining samples are already zero (Float32Array default)\r\n\r\n // Process the padded buffer\r\n const processedStartTime = this.bufferStartTime\r\n\r\n try {\r\n // Run LAM inference\r\n const result = await lam.infer(padded)\r\n\r\n // Queue frames with timestamps\r\n // Only queue frames that correspond to actual audio (not padding)\r\n const actualDuration = this.buffer.length / (this.options.sampleRate ?? 16000)\r\n const frameDuration = 1 / this.FRAME_RATE\r\n const actualFrameCount = Math.ceil(actualDuration * this.FRAME_RATE)\r\n\r\n for (let i = 0; i < Math.min(actualFrameCount, result.blendshapes.length); i++) {\r\n const frame = result.blendshapes[i]\r\n const timestamp = processedStartTime + (i * frameDuration)\r\n this.frameQueue.push({ frame, timestamp })\r\n }\r\n\r\n // Clear buffer after flushing\r\n this.buffer = new Float32Array(0)\r\n this.bufferStartTime = 0\r\n\r\n // Notify callback\r\n this.options.onInference?.(Math.min(actualFrameCount, result.blendshapes.length))\r\n } catch (error) {\r\n this.options.onError?.(error as Error)\r\n\r\n // Clear buffer on error\r\n this.buffer = new Float32Array(0)\r\n this.bufferStartTime = 0\r\n }\r\n }\r\n\r\n /**\r\n * Adjust all queued frame timestamps by an offset\r\n *\r\n * Used for synchronization when audio scheduling time differs from\r\n * the estimated time used during LAM processing.\r\n *\r\n * @param offset - Time offset in seconds to add to all timestamps\r\n */\r\n adjustTimestamps(offset: number): void {\r\n for (const frame of this.frameQueue) {\r\n frame.timestamp += offset\r\n }\r\n }\r\n\r\n /**\r\n * Reset the pipeline\r\n */\r\n reset(): void {\r\n this.buffer = new Float32Array(0)\r\n this.bufferStartTime = 0\r\n this.frameQueue = []\r\n this.lastFrame = null // Clear last-frame-hold cache\r\n }\r\n}\r\n","/**\r\n * Shared audio utility functions\r\n *\r\n * @module audio\r\n */\r\n\r\n/**\r\n * Safely convert an ArrayBuffer of PCM16 bytes to Float32 samples.\r\n * Handles odd-length buffers by truncating to the nearest even byte boundary.\r\n */\r\nexport function pcm16ToFloat32(buffer: ArrayBuffer): Float32Array {\r\n // Int16Array requires even byte length — truncate if odd\r\n const byteLen = buffer.byteLength & ~1;\r\n const int16 = byteLen === buffer.byteLength\r\n ? new Int16Array(buffer)\r\n : new Int16Array(buffer, 0, byteLen / 2);\r\n const float32 = new Float32Array(int16.length);\r\n for (let i = 0; i < int16.length; i++) {\r\n float32[i] = int16[i] / 32768;\r\n }\r\n return float32;\r\n}\r\n\r\n/**\r\n * Convert Int16Array samples to Float32Array.\r\n * Each sample is divided by 32768 to normalize to [-1, 1] range.\r\n */\r\nexport function int16ToFloat32(int16: Int16Array): Float32Array {\r\n const float32 = new Float32Array(int16.length);\r\n for (let i = 0; i < int16.length; i++) {\r\n float32[i] = int16[i] / 32768;\r\n }\r\n return float32;\r\n}\r\n","/**\r\n * SyncedAudioPipeline - Audio playback + LAM lip sync coordinator\r\n *\r\n * Orchestrates the complete pipeline for synchronized audio playback and lip sync:\r\n * 1. Network chunks → Coalescer → Optimized buffers\r\n * 2. Audio buffers → Scheduler → Gapless playback (immediate, never blocks)\r\n * 3. Audio buffers → LAM Pipeline → Blendshape frames (background, fire-and-forget)\r\n * 4. Frames synchronized to AudioContext clock → Renderer\r\n *\r\n * Key Architecture Pattern: Audio-First, LAM-Background\r\n * - Audio chunks are scheduled for playback immediately (never waits for LAM)\r\n * - LAM inference runs in background without blocking the audio path\r\n * - Lip sync starts ~1 second after audio (LAM needs 16000 samples to infer)\r\n * - Once LAM catches up, frames stay synchronized to AudioContext clock\r\n *\r\n * This decoupled design prevents LAM inference (50-300ms) from blocking audio\r\n * scheduling, which caused audible stuttering when audio arrived as a continuous\r\n * stream (e.g., single-call TTS from ElevenLabs via AgentCore).\r\n *\r\n * @see https://web.dev/articles/audio-scheduling (Web Audio clock patterns)\r\n * @category Audio\r\n */\r\n\r\nimport { AudioScheduler } from './AudioScheduler'\r\nimport { AudioChunkCoalescer } from './AudioChunkCoalescer'\r\nimport { LAMPipeline } from './LAMPipeline'\r\nimport { EventEmitter } from '../events/EventEmitter'\r\nimport type { LipSyncBackend } from '../inference/LipSyncBackend'\r\nimport { pcm16ToFloat32 } from './audioUtils'\r\n\r\nexport interface SyncedAudioPipelineOptions {\r\n /** Sample rate in Hz (default: 16000) */\r\n sampleRate?: number\r\n /** Target chunk duration in ms for coalescing (default: 200) */\r\n chunkTargetMs?: number\r\n /** LAM inference engine */\r\n lam: LipSyncBackend\r\n /**\r\n * Audio playback delay in ms before first audio plays.\r\n * Gives LAM inference time to pre-compute blendshapes.\r\n * Default: auto-detected from lam.backend (50ms WebGPU, 350ms WASM).\r\n */\r\n audioDelayMs?: number\r\n}\r\n\r\nexport interface SyncedAudioPipelineEvents {\r\n /** New frame ready for display */\r\n frame_ready: Float32Array\r\n /** Playback has completed */\r\n playback_complete: void\r\n /** First audio chunk scheduled, playback starting */\r\n playback_start: number\r\n /** Error occurred */\r\n error: Error\r\n /** Index signature for EventEmitter compatibility */\r\n [key: string]: unknown\r\n}\r\n\r\nexport class SyncedAudioPipeline extends EventEmitter<SyncedAudioPipelineEvents> {\r\n private scheduler: AudioScheduler\r\n private coalescer: AudioChunkCoalescer\r\n private lamPipeline: LAMPipeline\r\n\r\n private playbackStarted = false\r\n private monitorInterval: number | null = null\r\n private frameAnimationId: number | null = null\r\n\r\n constructor(private readonly options: SyncedAudioPipelineOptions) {\r\n super()\r\n\r\n const sampleRate = options.sampleRate ?? 16000\r\n\r\n // Auto-detect audio delay from model + backend:\r\n // - wav2arkit_cpu on iOS: 750ms (single-threaded WASM, 100-500ms inference per chunk)\r\n // - Wav2Vec2 WASM fallback: 350ms (multi-threaded, faster inference)\r\n // - Wav2Vec2 WebGPU: 50ms (GPU-accelerated, near-instant)\r\n const autoDelay = options.lam.modelId === 'wav2arkit_cpu' ? 750\r\n : options.lam.backend === 'wasm' ? 350\r\n : 50\r\n const audioDelayMs = options.audioDelayMs ?? autoDelay\r\n\r\n this.scheduler = new AudioScheduler({\r\n sampleRate,\r\n initialLookaheadSec: audioDelayMs / 1000,\r\n })\r\n this.coalescer = new AudioChunkCoalescer({\r\n sampleRate,\r\n targetDurationMs: options.chunkTargetMs ?? 200,\r\n })\r\n this.lamPipeline = new LAMPipeline({\r\n sampleRate,\r\n onError: (error) => {\r\n this.emit('error', error)\r\n },\r\n })\r\n }\r\n\r\n /**\r\n * Initialize the pipeline\r\n */\r\n async initialize(): Promise<void> {\r\n await this.scheduler.initialize()\r\n }\r\n\r\n /**\r\n * Start a new playback session\r\n *\r\n * Resets all state and prepares for incoming audio chunks.\r\n * Audio will be scheduled immediately as chunks arrive (no buffering).\r\n */\r\n start(): void {\r\n // Stop any active session first (prevents duplicate frame loops/monitors)\r\n this.stopMonitoring()\r\n\r\n this.scheduler.reset()\r\n this.coalescer.reset()\r\n this.lamPipeline.reset()\r\n this.playbackStarted = false\r\n\r\n // Eagerly warm up AudioContext so audio hardware is ready when\r\n // first audio chunk arrives. Without this, AudioContext creation\r\n // happens at schedule time and the first 50-100ms of audio stutters\r\n // while Windows WASAPI initializes.\r\n this.scheduler.warmup()\r\n\r\n // Start frame animation loop\r\n this.startFrameLoop()\r\n\r\n // Start playback monitoring\r\n this.startMonitoring()\r\n }\r\n\r\n /**\r\n * Receive audio chunk from network\r\n *\r\n * Audio-first design: schedules audio immediately, LAM runs in background.\r\n * This prevents LAM inference (50-300ms) from blocking audio scheduling,\r\n * which caused audible stuttering with continuous audio streams.\r\n *\r\n * @param chunk - Uint8Array containing Int16 PCM audio\r\n */\r\n async onAudioChunk(chunk: Uint8Array): Promise<void> {\r\n // Coalesce small chunks into optimal buffers\r\n const combined = this.coalescer.add(chunk)\r\n if (!combined) {\r\n return // Not enough data yet\r\n }\r\n\r\n // Convert PCM16 bytes to Float32 samples (handles odd-length buffers safely)\r\n const float32 = pcm16ToFloat32(combined)\r\n\r\n // Schedule audio immediately — never wait for LAM\r\n const scheduleTime = await this.scheduler.schedule(float32)\r\n\r\n // Emit playback_start on first scheduled chunk\r\n if (!this.playbackStarted) {\r\n this.playbackStarted = true\r\n this.emit('playback_start', scheduleTime)\r\n }\r\n\r\n // LAM runs in background — never blocks audio scheduling.\r\n // lam.infer() takes 50-300ms when it triggers (every 16000 samples).\r\n // If we awaited here, the NDJSON processing loop in useVoice.tsx would\r\n // stall, preventing new audio chunks from being scheduled. The already-\r\n // scheduled audio plays out and runs dry → gap → audible stutter.\r\n this.lamPipeline.push(float32, scheduleTime, this.options.lam).catch(err => {\r\n this.emit('error', err)\r\n })\r\n }\r\n\r\n /**\r\n * End of audio stream\r\n *\r\n * Flushes any remaining buffered data.\r\n */\r\n async end(): Promise<void> {\r\n // Flush remaining coalesced data\r\n const remaining = this.coalescer.flush()\r\n if (remaining) {\r\n const chunk = new Uint8Array(remaining)\r\n await this.onAudioChunk(chunk)\r\n }\r\n\r\n // Flush remaining LAM buffer to process final audio chunk\r\n // This ensures blendshapes are generated for the last 0-1 seconds of audio\r\n await this.lamPipeline.flush(this.options.lam)\r\n }\r\n\r\n /**\r\n * Stop playback immediately with smooth fade-out\r\n *\r\n * Gracefully cancels all audio playback and LAM processing:\r\n * - Fades out audio over specified duration (default: 50ms)\r\n * - Cancels pending LAM inferences\r\n * - Clears all buffers and queues\r\n * - Emits 'playback_complete' event\r\n *\r\n * Use this for interruptions (e.g., user barge-in during AI speech).\r\n *\r\n * @param fadeOutMs - Fade-out duration in milliseconds (default: 50ms)\r\n * @returns Promise that resolves when fade-out completes\r\n */\r\n async stop(fadeOutMs: number = 50): Promise<void> {\r\n // Stop monitoring and frame loop\r\n this.stopMonitoring()\r\n\r\n // Cancel audio playback with fade-out\r\n await this.scheduler.cancelAll(fadeOutMs)\r\n\r\n // Clear all buffers\r\n this.coalescer.reset()\r\n this.lamPipeline.reset()\r\n this.playbackStarted = false\r\n\r\n // Emit completion event\r\n this.emit('playback_complete', undefined as any)\r\n }\r\n\r\n /**\r\n * Start frame animation loop\r\n *\r\n * Uses requestAnimationFrame to check for new LAM frames.\r\n * Synchronized to AudioContext clock (not visual refresh rate).\r\n *\r\n * Frame Emission Strategy:\r\n * - LAMPipeline uses last-frame-hold to prevent null returns\r\n * - Always emit frames (even repeated frames) to maintain smooth animation\r\n * - Renderer is responsible for detecting duplicate frames if needed\r\n */\r\n private startFrameLoop(): void {\r\n const updateFrame = () => {\r\n const currentTime = this.scheduler.getCurrentTime()\r\n const frame = this.lamPipeline.getFrameForTime(currentTime, this.options.lam)\r\n\r\n if (frame) {\r\n this.emit('frame_ready', frame)\r\n }\r\n\r\n this.frameAnimationId = requestAnimationFrame(updateFrame)\r\n }\r\n\r\n this.frameAnimationId = requestAnimationFrame(updateFrame)\r\n }\r\n\r\n /**\r\n * Start monitoring for playback completion\r\n */\r\n private startMonitoring(): void {\r\n if (this.monitorInterval) {\r\n clearInterval(this.monitorInterval)\r\n }\r\n\r\n this.monitorInterval = window.setInterval(() => {\r\n if (this.scheduler.isComplete() && this.lamPipeline.queuedFrameCount === 0) {\r\n this.emit('playback_complete', undefined as any)\r\n this.stopMonitoring()\r\n }\r\n }, 100)\r\n }\r\n\r\n /**\r\n * Stop monitoring\r\n */\r\n private stopMonitoring(): void {\r\n if (this.monitorInterval) {\r\n clearInterval(this.monitorInterval)\r\n this.monitorInterval = null\r\n }\r\n\r\n if (this.frameAnimationId) {\r\n cancelAnimationFrame(this.frameAnimationId)\r\n this.frameAnimationId = null\r\n }\r\n }\r\n\r\n /**\r\n * Get current pipeline state (for debugging/monitoring)\r\n */\r\n getState() {\r\n return {\r\n playbackStarted: this.playbackStarted,\r\n coalescerFill: this.coalescer.fillLevel,\r\n lamFill: this.lamPipeline.fillLevel,\r\n queuedFrames: this.lamPipeline.queuedFrameCount,\r\n currentTime: this.scheduler.getCurrentTime(),\r\n playbackEndTime: this.scheduler.getPlaybackEndTime(),\r\n }\r\n }\r\n\r\n /**\r\n * Cleanup resources\r\n */\r\n dispose(): void {\r\n this.stopMonitoring()\r\n this.scheduler.dispose()\r\n this.coalescer.reset()\r\n this.lamPipeline.reset()\r\n }\r\n}\r\n","/**\n * Emotion to ARKit Blendshape Mapper\n *\n * Converts Emotion2VecInference output to upper face ARKit blendshapes for\n * expressive avatar animation. Maps 4 emotion categories (neutral, happy, angry, sad)\n * to 11 upper face blendshapes (brows, eyes, cheeks).\n *\n * Supports two blend modes:\n * - 'dominant': Uses only the strongest emotion (simpler, more stable)\n * - 'weighted': Blends all emotions by probability (more nuanced, e.g., bittersweet)\n *\n * Also supports energy modulation to scale emotion intensity by audio energy,\n * making expressions stronger during emphasized speech.\n *\n * @example Basic usage\n * ```typescript\n * import { EmotionToBlendshapeMapper } from '@omote/core';\n * import { Emotion2VecInference } from '@omote/core';\n *\n * const emotion = new Emotion2VecInference({ modelUrl: '/models/emotion.onnx' });\n * const mapper = new EmotionToBlendshapeMapper();\n *\n * // Process emotion frame\n * const result = await emotion.infer(audioSamples);\n * const blendshapes = mapper.mapFrame(result.dominant);\n *\n * // Apply to avatar\n * for (const [name, value] of Object.entries(blendshapes)) {\n * avatar.setBlendshape(name, value);\n * }\n * ```\n *\n * @example Weighted blending for nuanced expressions\n * ```typescript\n * const mapper = new EmotionToBlendshapeMapper({\n * blendMode: 'weighted',\n * minBlendProbability: 0.1,\n * });\n *\n * // Frame with mixed emotions: { happy: 0.6, sad: 0.3, neutral: 0.1 }\n * // Result: bittersweet expression (smiling but worried brow)\n * const blendshapes = mapper.mapFrame(emotionFrame);\n * ```\n *\n * @example Energy-modulated emotion\n * ```typescript\n * import { AudioEnergyAnalyzer } from '@omote/core';\n *\n * const energyAnalyzer = new AudioEnergyAnalyzer();\n * const mapper = new EmotionToBlendshapeMapper({ energyModulation: true });\n *\n * // In animation loop\n * function animate(audioChunk: Float32Array, emotionFrame: EmotionFrame) {\n * const { energy } = energyAnalyzer.analyze(audioChunk);\n * mapper.mapFrame(emotionFrame, energy); // Louder = stronger emotion\n * mapper.update(16);\n * applyToAvatar(mapper.getCurrentBlendshapes());\n * }\n * ```\n *\n * @module animation\n */\n\n// ─── Emotion types (formerly in Emotion2VecInference, inlined here) ──────────\n\nexport const EMOTION2VEC_LABELS = ['neutral', 'happy', 'angry', 'sad'] as const;\nexport type Emotion2VecLabel = (typeof EMOTION2VEC_LABELS)[number];\n\nexport interface EmotionFrame {\n /** Primary emotion label */\n emotion: Emotion2VecLabel;\n /** Confidence for primary emotion (0-1) */\n confidence: number;\n /** All emotion probabilities */\n probabilities: Record<Emotion2VecLabel, number>;\n}\n\n/**\n * Upper face ARKit blendshape names (11 total)\n *\n * These blendshapes control the upper face (brows, eyes, cheeks) and are\n * driven by emotion detection, complementing the mouth blendshapes from\n * LAM lip sync.\n */\nexport const UPPER_FACE_BLENDSHAPES = [\n // Brows (5)\n 'browDownLeft',\n 'browDownRight',\n 'browInnerUp',\n 'browOuterUpLeft',\n 'browOuterUpRight',\n // Eyes (4)\n 'eyeSquintLeft',\n 'eyeSquintRight',\n 'eyeWideLeft',\n 'eyeWideRight',\n // Cheeks (2)\n 'cheekSquintLeft',\n 'cheekSquintRight',\n] as const;\n\nexport type UpperFaceBlendshapeName = (typeof UPPER_FACE_BLENDSHAPES)[number];\n\n/**\n * Upper face blendshape values (0-1 for each)\n */\nexport type UpperFaceBlendshapes = Record<UpperFaceBlendshapeName, number>;\n\n/**\n * Blend mode for combining emotions\n * - 'dominant': Use only the strongest emotion (default, more stable)\n * - 'weighted': Blend all emotions by probability (more nuanced)\n */\nexport type EmotionBlendMode = 'dominant' | 'weighted';\n\n/**\n * Emotion to ARKit blendshape mapping\n *\n * Based on Paul Ekman's FACS (Facial Action Coding System) research:\n *\n * - Happy (AU6+AU12): Cheek raise + lip corner pull (Duchenne smile)\n * Upper face: cheekSquint (AU6) + slight eyeSquint from genuine smile\n *\n * - Angry (AU4+AU5+AU7+AU23): Brow lower + eye wide + lid tighten + lip press\n * Upper face: browDown (AU4) + eyeWide (AU5) + eyeSquint (AU7) creates the \"glare\"\n *\n * - Sad (AU1+AU4+AU15): Inner brow raise + brow furrow + lip corner depress\n * Upper face: browInnerUp (AU1) + browDown (AU4) creates the worried/sad brow\n *\n * - Neutral: All zeros (no expression overlay)\n *\n * @see https://imotions.com/blog/learning/research-fundamentals/facial-action-coding-system/\n * @see https://melindaozel.com/arkit-to-facs-cheat-sheet/\n */\nexport const EMOTION_ARKIT_MAP: Record<Emotion2VecLabel, Partial<UpperFaceBlendshapes>> = {\n happy: {\n // AU6 - Cheek raiser (primary Duchenne smile marker)\n cheekSquintLeft: 0.5,\n cheekSquintRight: 0.5,\n // Slight eye squint from genuine smile (orbicularis oculi activation)\n eyeSquintLeft: 0.2,\n eyeSquintRight: 0.2,\n },\n angry: {\n // AU4 - Brow lowerer (intense, primary anger marker)\n browDownLeft: 0.7,\n browDownRight: 0.7,\n // AU5 - Upper lid raiser (wide eyes, part of the \"glare\")\n eyeWideLeft: 0.4,\n eyeWideRight: 0.4,\n // AU7 - Lid tightener (tense stare, combines with AU5 for angry glare)\n eyeSquintLeft: 0.3,\n eyeSquintRight: 0.3,\n },\n sad: {\n // AU1 - Inner brow raiser (primary sadness marker)\n browInnerUp: 0.6,\n // AU4 - Brow lowerer (brows drawn together)\n browDownLeft: 0.3,\n browDownRight: 0.3,\n },\n neutral: {}, // All zeros - no expression overlay\n};\n\n/**\n * Configuration for EmotionToBlendshapeMapper\n */\nexport interface EmotionBlendshapeConfig {\n /**\n * Smoothing factor for exponential moving average (0-1)\n * Lower = slower, smoother transitions\n * Higher = faster, more responsive\n * @default 0.15\n */\n smoothingFactor?: number;\n\n /**\n * Minimum confidence threshold for emotion to take effect\n * Emotions below this confidence are treated as neutral\n * @default 0.3\n */\n confidenceThreshold?: number;\n\n /**\n * Global intensity multiplier for all blendshapes (0-2)\n * @default 1.0\n */\n intensity?: number;\n\n /**\n * Blend mode for combining emotions\n * - 'dominant': Use only the strongest emotion (default)\n * - 'weighted': Blend all emotions by probability\n * @default 'dominant'\n */\n blendMode?: EmotionBlendMode;\n\n /**\n * Minimum probability for an emotion to contribute in weighted blend mode\n * Emotions with probability below this are ignored\n * @default 0.1\n */\n minBlendProbability?: number;\n\n /**\n * Enable energy modulation - scale emotion intensity by audio energy\n * When enabled, louder speech produces stronger expressions\n * @default false\n */\n energyModulation?: boolean;\n\n /**\n * Minimum energy scale when energy modulation is enabled (0-1)\n * At zero audio energy, emotion intensity is scaled by this factor\n * @default 0.3\n */\n minEnergyScale?: number;\n\n /**\n * Maximum energy scale when energy modulation is enabled (0-2)\n * At maximum audio energy, emotion intensity is scaled by this factor\n * @default 1.0\n */\n maxEnergyScale?: number;\n}\n\nconst DEFAULT_CONFIG: Required<EmotionBlendshapeConfig> = {\n smoothingFactor: 0.15,\n confidenceThreshold: 0.3,\n intensity: 1.0,\n blendMode: 'dominant',\n minBlendProbability: 0.1,\n energyModulation: false,\n minEnergyScale: 0.3,\n maxEnergyScale: 1.0,\n};\n\n/**\n * Creates a zeroed UpperFaceBlendshapes object\n */\nfunction createZeroBlendshapes(): UpperFaceBlendshapes {\n const result = {} as UpperFaceBlendshapes;\n for (const name of UPPER_FACE_BLENDSHAPES) {\n result[name] = 0;\n }\n return result;\n}\n\n/**\n * Clamp value between 0 and 1\n */\nfunction clamp01(value: number): number {\n return Math.max(0, Math.min(1, value));\n}\n\n/**\n * EmotionToBlendshapeMapper\n *\n * Converts emotion detection output to upper face ARKit blendshapes.\n * Provides smooth transitions between emotion states using exponential\n * moving average interpolation.\n *\n * Supports two blend modes:\n * - 'dominant': Uses only the strongest emotion\n * - 'weighted': Blends all emotions by probability for nuanced expressions\n *\n * Also supports energy modulation to scale emotion intensity by audio energy.\n */\nexport class EmotionToBlendshapeMapper {\n private config: Required<EmotionBlendshapeConfig>;\n private targetBlendshapes: UpperFaceBlendshapes;\n private currentBlendshapes: UpperFaceBlendshapes;\n private currentEnergy: number = 1.0;\n\n /**\n * Create a new EmotionToBlendshapeMapper\n *\n * @param config - Optional configuration\n */\n constructor(config?: EmotionBlendshapeConfig) {\n this.config = {\n ...DEFAULT_CONFIG,\n ...config,\n };\n this.targetBlendshapes = createZeroBlendshapes();\n this.currentBlendshapes = createZeroBlendshapes();\n }\n\n /**\n * Map an emotion frame to target blendshapes\n *\n * This sets the target values that the mapper will smoothly interpolate\n * towards. Call update() each frame to apply smoothing.\n *\n * @param frame - Emotion frame from Emotion2VecInference\n * @param audioEnergy - Optional audio energy (0-1) for energy modulation\n * @returns Target upper face blendshapes (before smoothing)\n */\n mapFrame(frame: EmotionFrame, audioEnergy?: number): UpperFaceBlendshapes {\n // Reset target to zeros\n this.targetBlendshapes = createZeroBlendshapes();\n\n // Store energy for modulation\n if (audioEnergy !== undefined) {\n this.currentEnergy = clamp01(audioEnergy);\n }\n\n // Check for valid frame\n if (!frame) {\n return { ...this.targetBlendshapes };\n }\n\n // Route to appropriate blend method\n if (this.config.blendMode === 'weighted') {\n this.mapFrameWeighted(frame);\n } else {\n this.mapFrameDominant(frame);\n }\n\n // Apply energy modulation if enabled\n if (this.config.energyModulation) {\n this.applyEnergyModulation();\n }\n\n return { ...this.targetBlendshapes };\n }\n\n /**\n * Map using dominant emotion only (original behavior)\n */\n private mapFrameDominant(frame: EmotionFrame): void {\n // Check confidence threshold\n if (frame.confidence < this.config.confidenceThreshold) {\n return;\n }\n\n // Get emotion mapping\n const emotion = frame.emotion as Emotion2VecLabel;\n const mapping = EMOTION_ARKIT_MAP[emotion];\n\n if (!mapping) {\n return;\n }\n\n // Apply mapping with intensity and confidence scaling\n const scale = this.config.intensity * frame.confidence;\n\n for (const [name, value] of Object.entries(mapping)) {\n const blendshapeName = name as UpperFaceBlendshapeName;\n if (value !== undefined) {\n this.targetBlendshapes[blendshapeName] = clamp01(value * scale);\n }\n }\n }\n\n /**\n * Map using weighted blend of all emotions by probability\n * Creates more nuanced expressions (e.g., bittersweet = happy + sad)\n */\n private mapFrameWeighted(frame: EmotionFrame): void {\n if (!frame.probabilities) {\n // Fall back to dominant if no probabilities\n this.mapFrameDominant(frame);\n return;\n }\n\n // Blend all emotions by their probability\n for (const [emotion, probability] of Object.entries(frame.probabilities)) {\n // Skip emotions below minimum probability\n if (probability < this.config.minBlendProbability) {\n continue;\n }\n\n const mapping = EMOTION_ARKIT_MAP[emotion as Emotion2VecLabel];\n if (!mapping) {\n continue;\n }\n\n // Add this emotion's contribution weighted by probability\n const scale = this.config.intensity * probability;\n\n for (const [name, value] of Object.entries(mapping)) {\n const blendshapeName = name as UpperFaceBlendshapeName;\n if (value !== undefined) {\n // Additive blending - sum contributions\n this.targetBlendshapes[blendshapeName] += value * scale;\n }\n }\n }\n\n // Clamp all values to 0-1 after blending\n for (const name of UPPER_FACE_BLENDSHAPES) {\n this.targetBlendshapes[name] = clamp01(this.targetBlendshapes[name]);\n }\n }\n\n /**\n * Apply energy modulation to scale emotion intensity by audio energy\n * Louder speech = stronger expressions\n */\n private applyEnergyModulation(): void {\n const { minEnergyScale, maxEnergyScale } = this.config;\n\n // Linear interpolation: energy 0 -> minScale, energy 1 -> maxScale\n const energyScale = minEnergyScale + this.currentEnergy * (maxEnergyScale - minEnergyScale);\n\n for (const name of UPPER_FACE_BLENDSHAPES) {\n this.targetBlendshapes[name] = clamp01(this.targetBlendshapes[name] * energyScale);\n }\n }\n\n /**\n * Apply smoothing to interpolate current values towards target\n *\n * Uses exponential moving average:\n * current = current + smoothingFactor * (target - current)\n *\n * @param _deltaMs - Delta time in milliseconds (reserved for future time-based smoothing)\n */\n update(_deltaMs: number): void {\n const factor = this.config.smoothingFactor;\n\n for (const name of UPPER_FACE_BLENDSHAPES) {\n const target = this.targetBlendshapes[name];\n const current = this.currentBlendshapes[name];\n this.currentBlendshapes[name] = clamp01(current + factor * (target - current));\n }\n }\n\n /**\n * Get current smoothed blendshape values\n *\n * @returns Current upper face blendshapes (after smoothing)\n */\n getCurrentBlendshapes(): UpperFaceBlendshapes {\n return { ...this.currentBlendshapes };\n }\n\n /**\n * Reset mapper to neutral state\n *\n * Sets both target and current blendshapes to zero.\n */\n reset(): void {\n this.targetBlendshapes = createZeroBlendshapes();\n this.currentBlendshapes = createZeroBlendshapes();\n this.currentEnergy = 1.0;\n }\n\n /**\n * Get current configuration\n */\n getConfig(): Required<EmotionBlendshapeConfig> {\n return { ...this.config };\n }\n\n /**\n * Update configuration\n *\n * @param config - Partial configuration to update\n */\n setConfig(config: Partial<EmotionBlendshapeConfig>): void {\n this.config = {\n ...this.config,\n ...config,\n };\n }\n}\n","/**\n * Audio Energy Analysis\n *\n * Utilities for extracting energy/loudness from audio for gesture animation.\n *\n * @module animation\n */\n\n/**\n * Calculate RMS (Root Mean Square) energy from audio samples\n * @param samples Audio samples (Float32Array, normalized -1 to 1)\n * @returns RMS energy value (0 to 1)\n */\nexport function calculateRMS(samples: Float32Array): number {\n if (samples.length === 0) return 0;\n\n let sumSquares = 0;\n for (let i = 0; i < samples.length; i++) {\n sumSquares += samples[i] * samples[i];\n }\n\n return Math.sqrt(sumSquares / samples.length);\n}\n\n/**\n * Calculate peak amplitude from audio samples\n * @param samples Audio samples (Float32Array, normalized -1 to 1)\n * @returns Peak amplitude (0 to 1)\n */\nexport function calculatePeak(samples: Float32Array): number {\n let peak = 0;\n for (let i = 0; i < samples.length; i++) {\n const abs = Math.abs(samples[i]);\n if (abs > peak) peak = abs;\n }\n return peak;\n}\n\n/**\n * Smoothed energy analyzer for gesture animation\n */\nexport class AudioEnergyAnalyzer {\n private smoothedRMS: number = 0;\n private smoothedPeak: number = 0;\n private readonly smoothingFactor: number;\n private readonly noiseFloor: number;\n\n /**\n * @param smoothingFactor How much to smooth (0 = no smoothing, 1 = infinite smoothing). Default 0.85\n * @param noiseFloor Minimum energy threshold to consider as signal. Default 0.01\n */\n constructor(smoothingFactor: number = 0.85, noiseFloor: number = 0.01) {\n this.smoothingFactor = Math.max(0, Math.min(0.99, smoothingFactor));\n this.noiseFloor = noiseFloor;\n }\n\n /**\n * Process audio samples and return smoothed energy values\n * @param samples Audio samples (Float32Array)\n * @returns Object with rms and peak values\n */\n process(samples: Float32Array): { rms: number; peak: number; energy: number } {\n const instantRMS = calculateRMS(samples);\n const instantPeak = calculatePeak(samples);\n\n // Apply noise gate\n const gatedRMS = instantRMS > this.noiseFloor ? instantRMS : 0;\n const gatedPeak = instantPeak > this.noiseFloor ? instantPeak : 0;\n\n // Smooth the values (exponential moving average)\n // Attack fast (when getting louder), release slower\n if (gatedRMS > this.smoothedRMS) {\n // Fast attack\n this.smoothedRMS =\n this.smoothedRMS * 0.5 + gatedRMS * 0.5;\n } else {\n // Slow release\n this.smoothedRMS =\n this.smoothedRMS * this.smoothingFactor +\n gatedRMS * (1 - this.smoothingFactor);\n }\n\n if (gatedPeak > this.smoothedPeak) {\n this.smoothedPeak = this.smoothedPeak * 0.3 + gatedPeak * 0.7;\n } else {\n this.smoothedPeak =\n this.smoothedPeak * this.smoothingFactor +\n gatedPeak * (1 - this.smoothingFactor);\n }\n\n // Combined energy (weighted average of RMS and peak)\n // RMS is more stable, peak catches transients\n const energy = this.smoothedRMS * 0.7 + this.smoothedPeak * 0.3;\n\n return {\n rms: this.smoothedRMS,\n peak: this.smoothedPeak,\n energy: Math.min(1, energy * 2), // Scale up and clamp\n };\n }\n\n /**\n * Reset analyzer state\n */\n reset(): void {\n this.smoothedRMS = 0;\n this.smoothedPeak = 0;\n }\n\n /**\n * Get current smoothed RMS value\n */\n get rms(): number {\n return this.smoothedRMS;\n }\n\n /**\n * Get current smoothed peak value\n */\n get peak(): number {\n return this.smoothedPeak;\n }\n}\n\n/**\n * Extract emphasis points from audio (for gesture timing)\n *\n * Detects sudden increases in energy that correspond to speech emphasis.\n */\nexport class EmphasisDetector {\n private energyHistory: number[] = [];\n private readonly historySize: number;\n private readonly emphasisThreshold: number;\n\n /**\n * @param historySize Number of frames to track. Default 10\n * @param emphasisThreshold Minimum energy increase to count as emphasis. Default 0.15\n */\n constructor(historySize: number = 10, emphasisThreshold: number = 0.15) {\n this.historySize = historySize;\n this.emphasisThreshold = emphasisThreshold;\n }\n\n /**\n * Process energy value and detect emphasis\n * @param energy Current energy value (0-1)\n * @returns Object with isEmphasis flag and emphasisStrength\n */\n process(energy: number): { isEmphasis: boolean; emphasisStrength: number } {\n this.energyHistory.push(energy);\n if (this.energyHistory.length > this.historySize) {\n this.energyHistory.shift();\n }\n\n if (this.energyHistory.length < 3) {\n return { isEmphasis: false, emphasisStrength: 0 };\n }\n\n // Calculate average of previous frames (excluding current)\n const prevFrames = this.energyHistory.slice(0, -1);\n const avgPrev = prevFrames.reduce((a, b) => a + b, 0) / prevFrames.length;\n\n // Compare current to average\n const increase = energy - avgPrev;\n const isEmphasis = increase > this.emphasisThreshold;\n\n return {\n isEmphasis,\n emphasisStrength: isEmphasis ? Math.min(1, increase / 0.3) : 0,\n };\n }\n\n /**\n * Reset detector state\n */\n reset(): void {\n this.energyHistory = [];\n }\n}\n","/**\n * Console Exporter\n *\n * Exports telemetry data to the browser console for development/debugging.\n *\n * @category Telemetry\n */\n\nimport type { SpanAttributes } from '../types';\n\n/**\n * Span data structure for export\n */\nexport interface SpanData {\n name: string;\n traceId: string;\n spanId: string;\n parentSpanId?: string;\n startTime: number;\n endTime: number;\n durationMs: number;\n status: 'ok' | 'error';\n attributes: SpanAttributes;\n error?: Error;\n}\n\n/**\n * Metric data structure for export\n */\nexport interface MetricData {\n name: string;\n type: 'counter' | 'histogram';\n value: number;\n attributes: Record<string, string | number | boolean>;\n timestamp: number;\n}\n\n/**\n * Exporter interface that all exporters must implement\n */\nexport interface TelemetryExporterInterface {\n /** Export a completed span */\n exportSpan(span: SpanData): void;\n /** Export a metric */\n exportMetric(metric: MetricData): void;\n /** Flush any buffered data */\n flush(): Promise<void>;\n /** Shutdown the exporter */\n shutdown(): Promise<void>;\n}\n\n/**\n * Console exporter for development/debugging\n *\n * Outputs spans and metrics to the browser console with formatting.\n */\nexport class ConsoleExporter implements TelemetryExporterInterface {\n private enabled: boolean;\n private prefix: string;\n\n constructor(options: { enabled?: boolean; prefix?: string } = {}) {\n this.enabled = options.enabled ?? true;\n this.prefix = options.prefix ?? '[Omote Telemetry]';\n }\n\n exportSpan(span: SpanData): void {\n if (!this.enabled) return;\n\n const statusIcon = span.status === 'ok' ? '✓' : '✗';\n const statusColor = span.status === 'ok' ? 'color: green' : 'color: red';\n\n console.groupCollapsed(\n `%c${this.prefix} %c${statusIcon} ${span.name} %c(${span.durationMs.toFixed(2)}ms)`,\n 'color: gray',\n statusColor,\n 'color: gray'\n );\n\n console.log('Trace ID:', span.traceId);\n console.log('Span ID:', span.spanId);\n if (span.parentSpanId) {\n console.log('Parent Span ID:', span.parentSpanId);\n }\n console.log('Duration:', `${span.durationMs.toFixed(2)}ms`);\n console.log('Status:', span.status);\n\n if (Object.keys(span.attributes).length > 0) {\n console.log('Attributes:', span.attributes);\n }\n\n if (span.error) {\n console.error('Error:', span.error);\n }\n\n console.groupEnd();\n }\n\n exportMetric(metric: MetricData): void {\n if (!this.enabled) return;\n\n const typeIcon = metric.type === 'counter' ? '↑' : '📊';\n\n console.log(\n `%c${this.prefix} %c${typeIcon} ${metric.name}: %c${metric.value}`,\n 'color: gray',\n 'color: blue',\n 'color: black; font-weight: bold',\n metric.attributes\n );\n }\n\n async flush(): Promise<void> {\n // Console exporter doesn't buffer, nothing to flush\n }\n\n async shutdown(): Promise<void> {\n this.enabled = false;\n }\n}\n","/**\n * OTLP Exporter\n *\n * Exports telemetry data to OTLP-compatible backends (Jaeger, Tempo, etc.)\n * using the OTLP/HTTP JSON protocol.\n *\n * @category Telemetry\n */\n\nimport type { OTLPExporterConfig } from '../types';\nimport type { SpanData, MetricData, TelemetryExporterInterface } from './console';\n\n/**\n * OTLP span status codes\n */\nconst StatusCode = {\n UNSET: 0,\n OK: 1,\n ERROR: 2,\n} as const;\n\n/**\n * Convert internal span to OTLP format\n */\nfunction spanToOTLP(span: SpanData, serviceName: string, serviceVersion: string) {\n const attributes = Object.entries(span.attributes)\n .filter(([, v]) => v !== undefined)\n .map(([key, value]) => ({\n key,\n value: typeof value === 'string'\n ? { stringValue: value }\n : typeof value === 'number'\n ? Number.isInteger(value)\n ? { intValue: value }\n : { doubleValue: value }\n : { boolValue: value },\n }));\n\n return {\n resourceSpans: [{\n resource: {\n attributes: [\n { key: 'service.name', value: { stringValue: serviceName } },\n { key: 'service.version', value: { stringValue: serviceVersion } },\n { key: 'telemetry.sdk.name', value: { stringValue: 'omote-sdk' } },\n { key: 'telemetry.sdk.language', value: { stringValue: 'javascript' } },\n ],\n },\n scopeSpans: [{\n scope: {\n name: 'omote-sdk',\n version: serviceVersion,\n },\n spans: [{\n traceId: span.traceId,\n spanId: span.spanId,\n parentSpanId: span.parentSpanId || '',\n name: span.name,\n kind: 1, // INTERNAL\n startTimeUnixNano: String(span.startTime * 1_000_000),\n endTimeUnixNano: String(span.endTime * 1_000_000),\n attributes,\n status: {\n code: span.status === 'ok' ? StatusCode.OK : StatusCode.ERROR,\n message: span.error?.message || '',\n },\n }],\n }],\n }],\n };\n}\n\n/**\n * Convert internal metric to OTLP format\n */\nfunction metricToOTLP(metric: MetricData, serviceName: string, serviceVersion: string) {\n const attributes = Object.entries(metric.attributes)\n .filter(([, v]) => v !== undefined)\n .map(([key, value]) => ({\n key,\n value: typeof value === 'string'\n ? { stringValue: value }\n : typeof value === 'number'\n ? Number.isInteger(value)\n ? { intValue: value }\n : { doubleValue: value }\n : { boolValue: value },\n }));\n\n const dataPoint = {\n attributes,\n timeUnixNano: String(metric.timestamp * 1_000_000),\n ...(metric.type === 'counter'\n ? { asInt: metric.value }\n : { asDouble: metric.value }),\n };\n\n return {\n resourceMetrics: [{\n resource: {\n attributes: [\n { key: 'service.name', value: { stringValue: serviceName } },\n { key: 'service.version', value: { stringValue: serviceVersion } },\n ],\n },\n scopeMetrics: [{\n scope: {\n name: 'omote-sdk',\n version: serviceVersion,\n },\n metrics: [{\n name: metric.name,\n ...(metric.type === 'counter'\n ? {\n sum: {\n dataPoints: [dataPoint],\n aggregationTemporality: 2, // CUMULATIVE\n isMonotonic: true,\n },\n }\n : {\n gauge: {\n dataPoints: [dataPoint],\n },\n }),\n }],\n }],\n }],\n };\n}\n\n/**\n * OTLP exporter for production telemetry\n *\n * Sends spans and metrics to OTLP-compatible backends like:\n * - Jaeger\n * - Grafana Tempo\n * - Honeycomb\n * - Datadog\n * - AWS X-Ray (with collector)\n */\nexport class OTLPExporter implements TelemetryExporterInterface {\n private config: Required<OTLPExporterConfig>;\n private serviceName: string;\n private serviceVersion: string;\n private spanBuffer: SpanData[] = [];\n private metricBuffer: MetricData[] = [];\n private flushIntervalId: ReturnType<typeof setInterval> | null = null;\n private readonly BUFFER_SIZE = 100;\n private readonly FLUSH_INTERVAL_MS = 5000;\n private isShutdown = false;\n\n constructor(\n config: OTLPExporterConfig,\n serviceName: string = 'omote-sdk',\n serviceVersion: string = '0.1.0'\n ) {\n this.config = {\n timeoutMs: 10000,\n headers: {},\n ...config,\n };\n this.serviceName = serviceName;\n this.serviceVersion = serviceVersion;\n\n // Start periodic flush\n this.flushIntervalId = setInterval(() => {\n this.flush().catch(console.error);\n }, this.FLUSH_INTERVAL_MS);\n }\n\n exportSpan(span: SpanData): void {\n if (this.isShutdown) return;\n\n this.spanBuffer.push(span);\n\n if (this.spanBuffer.length >= this.BUFFER_SIZE) {\n this.flush().catch(console.error);\n }\n }\n\n exportMetric(metric: MetricData): void {\n if (this.isShutdown) return;\n\n this.metricBuffer.push(metric);\n\n if (this.metricBuffer.length >= this.BUFFER_SIZE) {\n this.flush().catch(console.error);\n }\n }\n\n async flush(): Promise<void> {\n if (this.isShutdown) return;\n\n const spans = this.spanBuffer.splice(0);\n const metrics = this.metricBuffer.splice(0);\n\n const promises: Promise<void>[] = [];\n\n // Export spans\n if (spans.length > 0) {\n promises.push(this.exportSpans(spans));\n }\n\n // Export metrics\n if (metrics.length > 0) {\n promises.push(this.exportMetrics(metrics));\n }\n\n await Promise.all(promises);\n }\n\n async shutdown(): Promise<void> {\n if (this.flushIntervalId) {\n clearInterval(this.flushIntervalId);\n this.flushIntervalId = null;\n }\n\n // Final flush before marking shutdown\n await this.flush();\n\n this.isShutdown = true;\n }\n\n private async exportSpans(spans: SpanData[]): Promise<void> {\n // Combine all spans into a single request\n const resourceSpans = spans.map(span =>\n spanToOTLP(span, this.serviceName, this.serviceVersion).resourceSpans[0]\n );\n\n const body = { resourceSpans };\n const endpoint = this.config.endpoint.replace(/\\/$/, '') + '/v1/traces';\n\n await this.sendRequest(endpoint, body);\n }\n\n private async exportMetrics(metrics: MetricData[]): Promise<void> {\n // Combine all metrics into a single request\n const resourceMetrics = metrics.map(metric =>\n metricToOTLP(metric, this.serviceName, this.serviceVersion).resourceMetrics[0]\n );\n\n const body = { resourceMetrics };\n const endpoint = this.config.endpoint.replace(/\\/$/, '') + '/v1/metrics';\n\n await this.sendRequest(endpoint, body);\n }\n\n private async sendRequest(endpoint: string, body: unknown): Promise<void> {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.config.timeoutMs);\n\n try {\n const response = await fetch(endpoint, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...this.config.headers,\n },\n body: JSON.stringify(body),\n signal: controller.signal,\n });\n\n if (!response.ok) {\n console.warn(`[OTLP] Export failed: ${response.status} ${response.statusText}`);\n }\n } catch (error) {\n if ((error as Error).name === 'AbortError') {\n console.warn('[OTLP] Export timed out');\n } else {\n console.warn('[OTLP] Export error:', error);\n }\n } finally {\n clearTimeout(timeoutId);\n }\n }\n}\n","/**\n * Muse Telemetry\n *\n * Main orchestrator for SDK telemetry. Manages spans, metrics, and exporters.\n *\n * @category Telemetry\n */\n\nimport type { TelemetryConfig, SpanAttributes, SamplingConfig } from './types';\nimport type { SpanData, MetricData, TelemetryExporterInterface } from './exporters/console';\nimport { ConsoleExporter } from './exporters/console';\nimport { OTLPExporter } from './exporters/otlp';\n\n/**\n * Generate a random hex ID\n */\nfunction generateId(length: number = 16): string {\n const bytes = new Uint8Array(length);\n crypto.getRandomValues(bytes);\n return Array.from(bytes)\n .map(b => b.toString(16).padStart(2, '0'))\n .join('');\n}\n\n/**\n * Span context for tracing\n */\ninterface SpanContext {\n traceId: string;\n spanId: string;\n parentSpanId?: string;\n}\n\n/**\n * Active span handle returned by startSpan\n */\nexport interface ActiveSpan {\n /** End the span with success status */\n end(): void;\n /** End the span with error status */\n endWithError(error: Error): void;\n /** Add attributes to the span */\n setAttributes(attrs: Partial<SpanAttributes>): void;\n /** Get the span context */\n getContext(): SpanContext;\n}\n\n/**\n * Global telemetry instance\n */\nlet globalTelemetry: OmoteTelemetry | null = null;\n\n/**\n * Configure global telemetry\n *\n * @example\n * ```typescript\n * // Development\n * configureTelemetry({\n * enabled: true,\n * serviceName: 'omote-dev',\n * exporter: 'console',\n * });\n *\n * // Production\n * configureTelemetry({\n * enabled: true,\n * serviceName: 'omote-prod',\n * exporter: 'otlp',\n * exporterConfig: {\n * endpoint: 'https://tempo.example.com',\n * },\n * sampling: { ratio: 0.1 },\n * });\n * ```\n */\nexport function configureTelemetry(config: TelemetryConfig): OmoteTelemetry {\n if (globalTelemetry) {\n globalTelemetry.shutdown();\n }\n globalTelemetry = new OmoteTelemetry(config);\n return globalTelemetry;\n}\n\n/**\n * Get the global telemetry instance\n */\nexport function getTelemetry(): OmoteTelemetry | null {\n return globalTelemetry;\n}\n\n/**\n * Main telemetry class\n *\n * Manages spans, metrics, and exports to configured backends.\n */\nexport class OmoteTelemetry {\n private config: Required<Omit<TelemetryConfig, 'exporterConfig'>> & { exporterConfig?: TelemetryConfig['exporterConfig'] };\n private exporter: TelemetryExporterInterface | null = null;\n private activeTraceId: string | null = null;\n private metricsIntervalId: ReturnType<typeof setInterval> | null = null;\n\n // Metric accumulators\n private counters: Map<string, { value: number; attributes: Record<string, string | number | boolean> }> = new Map();\n private histograms: Map<string, { values: number[]; attributes: Record<string, string | number | boolean> }> = new Map();\n\n constructor(config: TelemetryConfig) {\n this.config = {\n enabled: config.enabled ?? false,\n serviceName: config.serviceName ?? 'omote-sdk',\n serviceVersion: config.serviceVersion ?? '0.1.0',\n exporter: config.exporter ?? 'none',\n exporterConfig: config.exporterConfig,\n sampling: config.sampling ?? { ratio: 1.0, alwaysSampleErrors: true },\n metricsEnabled: config.metricsEnabled ?? true,\n metricsIntervalMs: config.metricsIntervalMs ?? 60000,\n };\n\n if (this.config.enabled) {\n this.initExporter();\n this.startMetricsCollection();\n }\n }\n\n /**\n * Initialize the configured exporter\n */\n private initExporter(): void {\n switch (this.config.exporter) {\n case 'console':\n this.exporter = new ConsoleExporter({ enabled: true });\n break;\n case 'otlp':\n if (!this.config.exporterConfig) {\n console.warn('[Telemetry] OTLP exporter requires exporterConfig with endpoint');\n return;\n }\n this.exporter = new OTLPExporter(\n this.config.exporterConfig,\n this.config.serviceName,\n this.config.serviceVersion\n );\n break;\n case 'none':\n default:\n this.exporter = null;\n }\n }\n\n /**\n * Start periodic metrics collection\n */\n private startMetricsCollection(): void {\n if (!this.config.metricsEnabled || !this.exporter) return;\n\n this.metricsIntervalId = setInterval(() => {\n this.flushMetrics();\n }, this.config.metricsIntervalMs);\n }\n\n /**\n * Check if this operation should be sampled\n */\n private shouldSample(isError: boolean = false): boolean {\n if (!this.config.enabled) return false;\n\n const sampling = this.config.sampling as SamplingConfig;\n if (isError && sampling.alwaysSampleErrors) return true;\n\n const ratio = sampling.ratio ?? 1.0;\n return Math.random() < ratio;\n }\n\n /**\n * Start a new span\n *\n * @example\n * ```typescript\n * const span = telemetry.startSpan('Wav2Vec2.infer', {\n * 'inference.input_samples': samples.length,\n * 'model.backend': 'webgpu',\n * });\n *\n * try {\n * const result = await doInference();\n * span.setAttributes({ 'inference.output_frames': result.frames });\n * span.end();\n * } catch (error) {\n * span.endWithError(error);\n * }\n * ```\n */\n startSpan(name: string, attributes: Partial<SpanAttributes> = {}, parentContext?: SpanContext): ActiveSpan {\n const traceId = parentContext?.traceId ?? this.activeTraceId ?? generateId(16);\n const spanId = generateId(8);\n const parentSpanId = parentContext?.spanId;\n const startTime = performance.now();\n\n // Set active trace if this is a root span\n if (!parentContext && !this.activeTraceId) {\n this.activeTraceId = traceId;\n }\n\n let spanAttributes = { ...attributes };\n let ended = false;\n let sampled = this.shouldSample();\n\n const context: SpanContext = { traceId, spanId, parentSpanId };\n\n const endSpan = (status: 'ok' | 'error', error?: Error): void => {\n if (ended) return;\n ended = true;\n\n const endTime = performance.now();\n const durationMs = endTime - startTime;\n\n // Re-check sampling for errors\n if (status === 'error' && !sampled) {\n sampled = this.shouldSample(true);\n }\n\n if (!sampled || !this.exporter) return;\n\n const spanData: SpanData = {\n name,\n traceId,\n spanId,\n parentSpanId,\n startTime,\n endTime,\n durationMs,\n status,\n attributes: spanAttributes as SpanAttributes,\n error,\n };\n\n this.exporter.exportSpan(spanData);\n\n // Clear active trace if this was the root span\n if (!parentSpanId && this.activeTraceId === traceId) {\n this.activeTraceId = null;\n }\n };\n\n return {\n end: () => endSpan('ok'),\n endWithError: (error: Error) => endSpan('error', error),\n setAttributes: (attrs: Partial<SpanAttributes>) => {\n spanAttributes = { ...spanAttributes, ...attrs };\n },\n getContext: () => context,\n };\n }\n\n /**\n * Wrap an async function with a span\n *\n * @example\n * ```typescript\n * const result = await telemetry.withSpan('Model.load', async (span) => {\n * const model = await loadModel();\n * span.setAttributes({ 'model.size_bytes': model.size });\n * return model;\n * });\n * ```\n */\n async withSpan<T>(\n name: string,\n fn: (span: ActiveSpan) => Promise<T>,\n attributes: Partial<SpanAttributes> = {},\n parentContext?: SpanContext\n ): Promise<T> {\n const span = this.startSpan(name, attributes, parentContext);\n\n try {\n const result = await fn(span);\n span.end();\n return result;\n } catch (error) {\n span.endWithError(error as Error);\n throw error;\n }\n }\n\n /**\n * Increment a counter metric\n *\n * @example\n * ```typescript\n * telemetry.incrementCounter('omote.inference.total', 1, {\n * model: 'wav2vec2',\n * backend: 'webgpu',\n * status: 'success',\n * });\n * ```\n */\n incrementCounter(\n name: string,\n value: number = 1,\n attributes: Record<string, string | number | boolean> = {}\n ): void {\n if (!this.config.enabled || !this.config.metricsEnabled) return;\n\n const key = this.getMetricKey(name, attributes);\n const existing = this.counters.get(key);\n\n if (existing) {\n existing.value += value;\n } else {\n this.counters.set(key, { value, attributes });\n }\n }\n\n /**\n * Record a histogram value\n *\n * @example\n * ```typescript\n * telemetry.recordHistogram('omote.inference.latency', durationMs, {\n * model: 'wav2vec2',\n * backend: 'webgpu',\n * });\n * ```\n */\n recordHistogram(\n name: string,\n value: number,\n attributes: Record<string, string | number | boolean> = {}\n ): void {\n if (!this.config.enabled || !this.config.metricsEnabled) return;\n\n const key = this.getMetricKey(name, attributes);\n const existing = this.histograms.get(key);\n\n if (existing) {\n existing.values.push(value);\n } else {\n this.histograms.set(key, { values: [value], attributes });\n }\n }\n\n /**\n * Generate unique key for metric with attributes\n */\n private getMetricKey(name: string, attributes: Record<string, string | number | boolean>): string {\n const sortedAttrs = Object.entries(attributes)\n .sort(([a], [b]) => a.localeCompare(b))\n .map(([k, v]) => `${k}=${v}`)\n .join(',');\n return `${name}|${sortedAttrs}`;\n }\n\n /**\n * Flush accumulated metrics to exporter\n */\n private flushMetrics(): void {\n if (!this.exporter) return;\n\n const timestamp = performance.now();\n\n // Export counters\n for (const [key, data] of this.counters) {\n const name = key.split('|')[0];\n const metric: MetricData = {\n name,\n type: 'counter',\n value: data.value,\n attributes: data.attributes,\n timestamp,\n };\n this.exporter.exportMetric(metric);\n }\n\n // Export histogram aggregates\n for (const [key, data] of this.histograms) {\n const name = key.split('|')[0];\n if (data.values.length === 0) continue;\n\n // Calculate average for histogram\n const sum = data.values.reduce((a, b) => a + b, 0);\n const avg = sum / data.values.length;\n\n const metric: MetricData = {\n name,\n type: 'histogram',\n value: avg,\n attributes: {\n ...data.attributes,\n count: data.values.length,\n sum,\n min: Math.min(...data.values),\n max: Math.max(...data.values),\n },\n timestamp,\n };\n this.exporter.exportMetric(metric);\n\n // Clear values after export\n data.values = [];\n }\n }\n\n /**\n * Force flush all pending data\n */\n async flush(): Promise<void> {\n this.flushMetrics();\n await this.exporter?.flush();\n }\n\n /**\n * Shutdown telemetry\n */\n async shutdown(): Promise<void> {\n if (this.metricsIntervalId) {\n clearInterval(this.metricsIntervalId);\n this.metricsIntervalId = null;\n }\n\n await this.flush();\n await this.exporter?.shutdown();\n this.exporter = null;\n }\n\n /**\n * Check if telemetry is enabled\n */\n isEnabled(): boolean {\n return this.config.enabled;\n }\n\n /**\n * Get current configuration\n */\n getConfig(): TelemetryConfig {\n return { ...this.config };\n }\n}\n","/**\n * Telemetry Types\n *\n * Configuration and type definitions for OpenTelemetry instrumentation.\n *\n * @category Telemetry\n */\n\n/**\n * Supported telemetry exporters\n */\nexport type TelemetryExporter = 'console' | 'otlp' | 'none';\n\n/**\n * Sampling configuration\n */\nexport interface SamplingConfig {\n /** Sampling ratio (0.0 - 1.0). Default: 1.0 (sample everything) */\n ratio?: number;\n /** Always sample errors regardless of ratio */\n alwaysSampleErrors?: boolean;\n}\n\n/**\n * OTLP exporter configuration\n */\nexport interface OTLPExporterConfig {\n /** OTLP endpoint URL (e.g., 'https://tempo.example.com/v1/traces') */\n endpoint: string;\n /** Optional headers for authentication */\n headers?: Record<string, string>;\n /** Request timeout in ms. Default: 10000 */\n timeoutMs?: number;\n}\n\n/**\n * Main telemetry configuration\n */\nexport interface TelemetryConfig {\n /** Enable/disable telemetry. Default: false */\n enabled?: boolean;\n /** Service name for spans. Default: 'omote-sdk' */\n serviceName?: string;\n /** Service version. Default: SDK version */\n serviceVersion?: string;\n /** Exporter type. Default: 'none' */\n exporter?: TelemetryExporter;\n /** OTLP exporter config (required if exporter is 'otlp') */\n exporterConfig?: OTLPExporterConfig;\n /** Sampling configuration */\n sampling?: SamplingConfig;\n /** Enable metrics collection. Default: true when telemetry enabled */\n metricsEnabled?: boolean;\n /** Metrics export interval in ms. Default: 60000 */\n metricsIntervalMs?: number;\n}\n\n/**\n * Span attributes for model operations\n */\nexport interface ModelSpanAttributes {\n /** Model URL or identifier */\n 'model.url'?: string;\n /** Model name (e.g., 'whisper', 'lam', 'silero-vad') */\n 'model.name'?: string;\n /** Inference backend used */\n 'model.backend'?: 'webgpu' | 'wasm';\n /** Whether model was loaded from cache */\n 'model.cached'?: boolean;\n /** Model size in bytes */\n 'model.size_bytes'?: number;\n}\n\n/**\n * Span attributes for inference operations\n */\nexport interface InferenceSpanAttributes extends ModelSpanAttributes {\n /** Number of input audio samples */\n 'inference.input_samples'?: number;\n /** Input duration in ms */\n 'inference.input_duration_ms'?: number;\n /** Number of output frames (for LAM) */\n 'inference.output_frames'?: number;\n /** Inference duration in ms */\n 'inference.duration_ms'?: number;\n /** Whether inference succeeded */\n 'inference.success'?: boolean;\n /** Error type if failed */\n 'inference.error_type'?: string;\n}\n\n/**\n * Span attributes for cache operations\n */\nexport interface CacheSpanAttributes {\n /** Cache key (URL) */\n 'cache.key'?: string;\n /** Whether it was a cache hit */\n 'cache.hit'?: boolean;\n /** Size of cached item in bytes */\n 'cache.size_bytes'?: number;\n /** Cache operation type */\n 'cache.operation'?: 'get' | 'set' | 'delete';\n}\n\n/**\n * Combined span attributes type\n */\nexport type SpanAttributes =\n | ModelSpanAttributes\n | InferenceSpanAttributes\n | CacheSpanAttributes\n | Record<string, string | number | boolean | undefined>;\n\n/**\n * Metric names used by the SDK\n */\nexport const MetricNames = {\n /** Histogram: Inference latency in ms */\n INFERENCE_LATENCY: 'omote.inference.latency',\n /** Histogram: Model load time in ms */\n MODEL_LOAD_TIME: 'omote.model.load_time',\n /** Counter: Total inference operations */\n INFERENCE_TOTAL: 'omote.inference.total',\n /** Counter: Total errors */\n ERRORS_TOTAL: 'omote.errors.total',\n /** Counter: Cache hits */\n CACHE_HITS: 'omote.cache.hits',\n /** Counter: Cache misses */\n CACHE_MISSES: 'omote.cache.misses',\n} as const;\n\n/**\n * Histogram buckets for inference latency (ms)\n */\nexport const INFERENCE_LATENCY_BUCKETS = [1, 5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000];\n\n/**\n * Histogram buckets for model load time (ms)\n */\nexport const MODEL_LOAD_TIME_BUCKETS = [100, 500, 1000, 2500, 5000, 10000, 30000, 60000];\n","/**\n * Model Cache\n *\n * Caches ONNX models in IndexedDB for faster subsequent loads.\n * IndexedDB can handle large files (100s of MBs) unlike localStorage.\n *\n * @category Cache\n */\n\nimport { getTelemetry } from '../telemetry';\n\nconst DB_NAME = 'omote-model-cache';\nconst DB_VERSION = 2;\nconst STORE_NAME = 'models';\n\n/** Default cache size limit: 1GB */\nconst DEFAULT_MAX_SIZE_BYTES = 1024 * 1024 * 1024;\n\n/**\n * Configuration for cache size limits and eviction behavior\n */\nexport interface CacheConfig {\n /** Maximum total cache size in bytes (default: 1GB) */\n maxSizeBytes?: number;\n /** Maximum age in milliseconds before eviction (default: none) */\n maxAgeMs?: number;\n /** Callback when storage quota exceeds warning threshold */\n onQuotaWarning?: (info: QuotaInfo) => void;\n}\n\n/**\n * Storage quota information\n */\nexport interface QuotaInfo {\n /** Total bytes used across all origins */\n usedBytes: number;\n /** Total available quota in bytes */\n quotaBytes: number;\n /** Percentage of quota used (0-100) */\n percentUsed: number;\n /** Bytes used by omote cache specifically */\n cacheBytes: number;\n}\n\n/** Global cache configuration */\nlet globalCacheConfig: CacheConfig = {\n maxSizeBytes: DEFAULT_MAX_SIZE_BYTES,\n};\n\n/**\n * Configure cache size limits and eviction behavior\n *\n * @param config - Cache configuration options\n *\n * @example\n * ```typescript\n * import { configureCacheLimit } from '@omote/core';\n *\n * // Set 500MB limit with 24-hour max age\n * configureCacheLimit({\n * maxSizeBytes: 500 * 1024 * 1024,\n * maxAgeMs: 24 * 60 * 60 * 1000,\n * onQuotaWarning: (info) => {\n * console.warn(`Storage ${info.percentUsed.toFixed(1)}% used`);\n * }\n * });\n * ```\n */\nexport function configureCacheLimit(config: CacheConfig): void {\n globalCacheConfig = {\n ...globalCacheConfig,\n ...config,\n };\n\n // Trigger immediate cleanup if over limit\n const cache = getModelCache();\n cache.enforceLimit().catch((err) => {\n console.warn('[ModelCache] Failed to enforce limit after config change:', err);\n });\n}\n\n/**\n * Get current cache configuration\n */\nexport function getCacheConfig(): CacheConfig {\n return { ...globalCacheConfig };\n}\n\ninterface CachedModel {\n url: string;\n data: ArrayBuffer;\n size: number;\n cachedAt: number;\n /** Last time this model was accessed (for LRU eviction) */\n lastAccessedAt: number;\n etag?: string;\n version?: string;\n}\n\n/**\n * Result from getWithValidation() method\n */\nexport interface ValidationResult {\n /** The cached data, or null if not found */\n data: ArrayBuffer | null;\n /** True if the cached data is stale (etag mismatch) */\n stale: boolean;\n}\n\n/**\n * Generate a version-aware cache key\n *\n * @param url - The model URL\n * @param version - Optional version string\n * @returns The cache key (url#vX.X.X if version provided, url otherwise)\n *\n * @example\n * ```typescript\n * getCacheKey('http://example.com/model.onnx', '1.0.0')\n * // Returns: 'http://example.com/model.onnx#v1.0.0'\n *\n * getCacheKey('http://example.com/model.onnx')\n * // Returns: 'http://example.com/model.onnx'\n * ```\n */\nexport function getCacheKey(url: string, version?: string): string {\n if (version) {\n return `${url}#v${version}`;\n }\n return url;\n}\n\ninterface CacheStats {\n totalSize: number;\n modelCount: number;\n models: { url: string; size: number; cachedAt: Date }[];\n}\n\n/**\n * ModelCache - IndexedDB-based cache for ONNX models\n */\nexport class ModelCache {\n private db: IDBDatabase | null = null;\n private dbPromise: Promise<IDBDatabase> | null = null;\n\n /**\n * Initialize the cache database\n */\n private async getDB(): Promise<IDBDatabase> {\n if (this.db) return this.db;\n if (this.dbPromise) return this.dbPromise;\n\n // Request persistent storage for more generous quota on iOS/mobile browsers\n // This increases available storage from ~50MB to potentially GBs\n if (navigator.storage && navigator.storage.persist) {\n try {\n const isPersisted = await navigator.storage.persist();\n if (isPersisted) {\n console.log('[ModelCache] Persistent storage granted - increased quota available');\n } else {\n console.log('[ModelCache] Persistent storage denied - using default quota');\n }\n\n // Log current quota usage (helpful for debugging iOS limits)\n if (navigator.storage.estimate) {\n const estimate = await navigator.storage.estimate();\n const usedMB = ((estimate.usage || 0) / 1024 / 1024).toFixed(2);\n const quotaMB = ((estimate.quota || 0) / 1024 / 1024).toFixed(2);\n console.log(`[ModelCache] Storage: ${usedMB}MB / ${quotaMB}MB quota`);\n }\n } catch (err) {\n console.warn('[ModelCache] Failed to request persistent storage:', err);\n }\n }\n\n this.dbPromise = new Promise((resolve, reject) => {\n const request = indexedDB.open(DB_NAME, DB_VERSION);\n\n request.onerror = () => {\n console.error('[ModelCache] Failed to open IndexedDB:', request.error);\n reject(request.error);\n };\n\n request.onsuccess = () => {\n this.db = request.result;\n resolve(this.db);\n };\n\n request.onupgradeneeded = (event) => {\n const db = (event.target as IDBOpenDBRequest).result;\n const oldVersion = (event as IDBVersionChangeEvent).oldVersion;\n const tx = (event.target as IDBOpenDBRequest).transaction;\n\n if (oldVersion < 1) {\n // Initial schema: create store with url as key\n const store = db.createObjectStore(STORE_NAME, { keyPath: 'url' });\n store.createIndex('lastAccessedAt', 'lastAccessedAt', { unique: false });\n } else if (oldVersion < 2 && tx) {\n // Migrate from v1 to v2: add lastAccessedAt index and backfill existing entries\n const store = tx.objectStore(STORE_NAME);\n\n // Create index if it doesn't exist\n if (!store.indexNames.contains('lastAccessedAt')) {\n store.createIndex('lastAccessedAt', 'lastAccessedAt', { unique: false });\n }\n\n // Migrate existing entries: set lastAccessedAt = cachedAt\n const cursorRequest = store.openCursor();\n cursorRequest.onsuccess = (cursorEvent) => {\n const cursor = (cursorEvent.target as IDBRequest<IDBCursorWithValue>).result;\n if (cursor) {\n const value = cursor.value;\n if (value.lastAccessedAt === undefined) {\n value.lastAccessedAt = value.cachedAt || Date.now();\n cursor.update(value);\n }\n cursor.continue();\n }\n };\n }\n };\n });\n\n return this.dbPromise;\n }\n\n /**\n * Check if a model is cached\n */\n async has(url: string): Promise<boolean> {\n try {\n const db = await this.getDB();\n return new Promise((resolve) => {\n const tx = db.transaction(STORE_NAME, 'readonly');\n const store = tx.objectStore(STORE_NAME);\n const request = store.count(url);\n request.onsuccess = () => resolve(request.result > 0);\n request.onerror = () => resolve(false);\n });\n } catch {\n return false;\n }\n }\n\n /**\n * Get a cached model\n *\n * Updates lastAccessedAt timestamp for LRU tracking on cache hit.\n */\n async get(url: string): Promise<ArrayBuffer | null> {\n const telemetry = getTelemetry();\n const span = telemetry?.startSpan('ModelCache.get', { 'cache.url': url });\n try {\n const db = await this.getDB();\n return new Promise((resolve) => {\n // Use readwrite to update lastAccessedAt on hit\n const tx = db.transaction(STORE_NAME, 'readwrite');\n const store = tx.objectStore(STORE_NAME);\n const request = store.get(url);\n request.onsuccess = () => {\n const cached = request.result as CachedModel | undefined;\n const hit = cached?.data != null;\n span?.setAttributes({ 'cache.hit': hit });\n if (cached) {\n span?.setAttributes({ 'cache.size_bytes': cached.size });\n // Update lastAccessedAt for LRU tracking\n cached.lastAccessedAt = Date.now();\n store.put(cached);\n }\n span?.end();\n if (hit) {\n telemetry?.incrementCounter('omote.cache.hits', 1, {});\n } else {\n telemetry?.incrementCounter('omote.cache.misses', 1, {});\n }\n resolve(cached?.data ?? null);\n };\n request.onerror = () => {\n span?.setAttributes({ 'cache.hit': false });\n span?.end();\n telemetry?.incrementCounter('omote.cache.misses', 1, {});\n resolve(null);\n };\n });\n } catch {\n span?.endWithError(new Error('Cache get failed'));\n return null;\n }\n }\n\n /**\n * Get a cached model with ETag validation\n *\n * Validates the cached data against the server's current ETag.\n * If the cached ETag differs from the server's, the data is marked as stale.\n *\n * @param url - The cache key\n * @param originalUrl - The original URL for HEAD request (if different from cache key)\n * @returns ValidationResult with data and stale flag\n *\n * @example\n * ```typescript\n * const result = await cache.getWithValidation('http://example.com/model.onnx');\n * if (result.data && !result.stale) {\n * // Use cached data\n * } else if (result.stale) {\n * // Refetch and update cache\n * }\n * ```\n */\n async getWithValidation(url: string, originalUrl?: string): Promise<ValidationResult> {\n const telemetry = getTelemetry();\n const span = telemetry?.startSpan('ModelCache.getWithValidation', { 'cache.url': url });\n\n try {\n const db = await this.getDB();\n const cached = await new Promise<CachedModel | undefined>((resolve) => {\n const tx = db.transaction(STORE_NAME, 'readonly');\n const store = tx.objectStore(STORE_NAME);\n const request = store.get(url);\n request.onsuccess = () => resolve(request.result as CachedModel | undefined);\n request.onerror = () => resolve(undefined);\n });\n\n // Cache miss\n if (!cached?.data) {\n span?.setAttributes({ 'cache.hit': false });\n span?.end();\n telemetry?.incrementCounter('omote.cache.misses', 1, {});\n return { data: null, stale: false };\n }\n\n span?.setAttributes({ 'cache.hit': true, 'cache.size_bytes': cached.size });\n\n // No etag stored - can't validate, return as fresh\n if (!cached.etag) {\n span?.setAttributes({ 'cache.validated': false, 'cache.stale': false });\n span?.end();\n telemetry?.incrementCounter('omote.cache.hits', 1, {});\n return { data: cached.data, stale: false };\n }\n\n // Validate via HEAD request\n const fetchUrl = originalUrl || url;\n try {\n const response = await fetch(fetchUrl, { method: 'HEAD' });\n if (!response.ok) {\n // Server error - assume cache is still valid\n span?.setAttributes({ 'cache.validated': false, 'cache.stale': false });\n span?.end();\n telemetry?.incrementCounter('omote.cache.hits', 1, {});\n return { data: cached.data, stale: false };\n }\n\n const serverEtag = response.headers.get('etag');\n const isStale = serverEtag !== null && serverEtag !== cached.etag;\n\n span?.setAttributes({\n 'cache.validated': true,\n 'cache.stale': isStale,\n 'cache.server_etag': serverEtag || 'none',\n 'cache.cached_etag': cached.etag,\n });\n span?.end();\n\n if (isStale) {\n telemetry?.incrementCounter('omote.cache.stale', 1, {});\n console.log(`[ModelCache] Stale cache detected for ${url}`);\n } else {\n telemetry?.incrementCounter('omote.cache.hits', 1, {});\n }\n\n return { data: cached.data, stale: isStale };\n } catch (fetchError) {\n // HEAD request failed (network error, CORS, etc.)\n // Return cached data as non-stale - better than failing completely\n console.warn('[ModelCache] HEAD validation failed, using cached data:', fetchError);\n span?.setAttributes({ 'cache.validated': false, 'cache.stale': false });\n span?.end();\n telemetry?.incrementCounter('omote.cache.hits', 1, {});\n return { data: cached.data, stale: false };\n }\n } catch {\n span?.endWithError(new Error('Cache getWithValidation failed'));\n return { data: null, stale: false };\n }\n }\n\n /**\n * Store a model in cache\n *\n * After storing, triggers LRU eviction if cache exceeds size limit.\n *\n * @param url - The cache key (use getCacheKey() for versioned keys)\n * @param data - The model data\n * @param etag - Optional ETag for staleness validation\n * @param version - Optional version string for metadata\n */\n async set(url: string, data: ArrayBuffer, etag?: string, version?: string): Promise<void> {\n const telemetry = getTelemetry();\n const span = telemetry?.startSpan('ModelCache.set', {\n 'cache.url': url,\n 'cache.size_bytes': data.byteLength,\n ...(version && { 'cache.version': version }),\n });\n try {\n // Check quota before caching (best effort, don't block write)\n this.checkQuota().catch((err) => {\n console.warn('[ModelCache] Quota check failed:', err);\n });\n\n const db = await this.getDB();\n await new Promise<void>((resolve, reject) => {\n const tx = db.transaction(STORE_NAME, 'readwrite');\n const store = tx.objectStore(STORE_NAME);\n const now = Date.now();\n const cached: CachedModel = {\n url,\n data,\n size: data.byteLength,\n cachedAt: now,\n lastAccessedAt: now,\n etag,\n version,\n };\n const request = store.put(cached);\n request.onsuccess = () => {\n span?.end();\n resolve();\n };\n request.onerror = () => {\n span?.endWithError(request.error || new Error('Cache set failed'));\n reject(request.error);\n };\n });\n\n // Trigger LRU cleanup after write (don't block)\n this.enforceLimit().catch((err) => {\n console.warn('[ModelCache] Failed to enforce limit after set:', err);\n });\n } catch (err) {\n console.warn('[ModelCache] Failed to cache model:', err);\n span?.endWithError(err instanceof Error ? err : new Error(String(err)));\n }\n }\n\n /**\n * Check storage quota and trigger warnings/cleanup as needed\n *\n * - Logs warning if quota > 90% used\n * - Triggers LRU cleanup if quota > 95% used\n * - Calls onQuotaWarning callback if configured\n */\n private async checkQuota(): Promise<void> {\n const quota = await this.getQuotaInfo();\n if (!quota) {\n return; // API unavailable\n }\n\n const config = globalCacheConfig;\n const telemetry = getTelemetry();\n\n if (quota.percentUsed > 90) {\n console.warn(`[ModelCache] Storage quota ${quota.percentUsed.toFixed(1)}% used (${formatBytes(quota.usedBytes)} / ${formatBytes(quota.quotaBytes)})`);\n\n // Emit telemetry counter\n telemetry?.incrementCounter('omote.cache.quota_warning', 1, {\n percent_used: String(Math.round(quota.percentUsed)),\n });\n\n // Call user callback if configured\n if (config.onQuotaWarning) {\n try {\n config.onQuotaWarning(quota);\n } catch (err) {\n console.warn('[ModelCache] onQuotaWarning callback error:', err);\n }\n }\n }\n\n if (quota.percentUsed > 95) {\n console.warn('[ModelCache] Storage quota critical (>95%), triggering LRU cleanup');\n // Free at least 10% of cache to make room\n const bytesToFree = Math.max(quota.cacheBytes * 0.1, 10 * 1024 * 1024);\n await this.evictOldest(bytesToFree);\n }\n }\n\n /**\n * Delete a cached model\n */\n async delete(url: string): Promise<void> {\n try {\n const db = await this.getDB();\n return new Promise((resolve) => {\n const tx = db.transaction(STORE_NAME, 'readwrite');\n const store = tx.objectStore(STORE_NAME);\n store.delete(url);\n tx.oncomplete = () => resolve();\n });\n } catch {\n // Ignore errors\n }\n }\n\n /**\n * Clear all cached models\n */\n async clear(): Promise<void> {\n try {\n const db = await this.getDB();\n return new Promise((resolve) => {\n const tx = db.transaction(STORE_NAME, 'readwrite');\n const store = tx.objectStore(STORE_NAME);\n store.clear();\n tx.oncomplete = () => resolve();\n });\n } catch {\n // Ignore errors\n }\n }\n\n /**\n * Get cache statistics\n */\n async getStats(): Promise<CacheStats> {\n try {\n const db = await this.getDB();\n return new Promise((resolve) => {\n const tx = db.transaction(STORE_NAME, 'readonly');\n const store = tx.objectStore(STORE_NAME);\n const request = store.getAll();\n request.onsuccess = () => {\n const models = (request.result as CachedModel[]) || [];\n resolve({\n totalSize: models.reduce((sum, m) => sum + m.size, 0),\n modelCount: models.length,\n models: models.map((m) => ({\n url: m.url,\n size: m.size,\n cachedAt: new Date(m.cachedAt),\n })),\n });\n };\n request.onerror = () => resolve({ totalSize: 0, modelCount: 0, models: [] });\n });\n } catch {\n return { totalSize: 0, modelCount: 0, models: [] };\n }\n }\n\n /**\n * Enforce cache size limit by evicting oldest entries (LRU)\n *\n * Called automatically after each set() operation.\n * Can also be called manually to trigger cleanup.\n */\n async enforceLimit(): Promise<void> {\n const config = globalCacheConfig;\n const maxSize = config.maxSizeBytes ?? DEFAULT_MAX_SIZE_BYTES;\n\n const stats = await this.getStats();\n if (stats.totalSize <= maxSize) {\n return; // Under limit, nothing to do\n }\n\n const bytesToFree = stats.totalSize - maxSize;\n const evictedUrls = await this.evictOldest(bytesToFree);\n\n if (evictedUrls.length > 0) {\n console.log(`[ModelCache] LRU eviction: removed ${evictedUrls.length} models to free ${formatBytes(bytesToFree)}`);\n }\n }\n\n /**\n * Evict oldest entries (by lastAccessedAt) to free space\n *\n * @param bytesToFree - Minimum bytes to free\n * @returns List of evicted URLs\n *\n * @example\n * ```typescript\n * const cache = getModelCache();\n * const evicted = await cache.evictOldest(100 * 1024 * 1024); // Free 100MB\n * console.log('Evicted:', evicted);\n * ```\n */\n async evictOldest(bytesToFree: number): Promise<string[]> {\n const telemetry = getTelemetry();\n const span = telemetry?.startSpan('ModelCache.evictOldest', {\n 'eviction.bytes_requested': bytesToFree,\n });\n\n try {\n const db = await this.getDB();\n\n // Get all models sorted by lastAccessedAt (oldest first)\n const models = await new Promise<CachedModel[]>((resolve) => {\n const tx = db.transaction(STORE_NAME, 'readonly');\n const store = tx.objectStore(STORE_NAME);\n const request = store.getAll();\n request.onsuccess = () => {\n const all = (request.result as CachedModel[]) || [];\n // Sort by lastAccessedAt ascending (oldest first)\n all.sort((a, b) => (a.lastAccessedAt || a.cachedAt || 0) - (b.lastAccessedAt || b.cachedAt || 0));\n resolve(all);\n };\n request.onerror = () => resolve([]);\n });\n\n const evictedUrls: string[] = [];\n let freedBytes = 0;\n\n // Evict models until we've freed enough space\n for (const model of models) {\n if (freedBytes >= bytesToFree) {\n break;\n }\n\n await this.delete(model.url);\n evictedUrls.push(model.url);\n freedBytes += model.size;\n\n console.log(`[ModelCache] Evicted: ${model.url} (${formatBytes(model.size)})`);\n }\n\n span?.setAttributes({\n 'eviction.bytes_freed': freedBytes,\n 'eviction.models_evicted': evictedUrls.length,\n });\n span?.end();\n\n // Emit telemetry counter\n if (freedBytes > 0) {\n telemetry?.incrementCounter('omote.cache.eviction', evictedUrls.length, {\n bytes_freed: String(freedBytes),\n });\n }\n\n return evictedUrls;\n } catch (err) {\n span?.endWithError(err instanceof Error ? err : new Error(String(err)));\n console.warn('[ModelCache] Eviction failed:', err);\n return [];\n }\n }\n\n /**\n * Get storage quota information\n *\n * Uses navigator.storage.estimate() to get quota details.\n * Returns null if the API is unavailable.\n *\n * @returns Quota info or null if unavailable\n *\n * @example\n * ```typescript\n * const cache = getModelCache();\n * const quota = await cache.getQuotaInfo();\n * if (quota) {\n * console.log(`Using ${quota.percentUsed.toFixed(1)}% of quota`);\n * }\n * ```\n */\n async getQuotaInfo(): Promise<QuotaInfo | null> {\n if (!navigator?.storage?.estimate) {\n return null;\n }\n\n try {\n const estimate = await navigator.storage.estimate();\n const usedBytes = estimate.usage || 0;\n const quotaBytes = estimate.quota || 0;\n const percentUsed = quotaBytes > 0 ? (usedBytes / quotaBytes) * 100 : 0;\n\n const stats = await this.getStats();\n\n return {\n usedBytes,\n quotaBytes,\n percentUsed,\n cacheBytes: stats.totalSize,\n };\n } catch {\n return null;\n }\n }\n}\n\n// Singleton instance\nlet cacheInstance: ModelCache | null = null;\n\n/**\n * Get the global ModelCache instance\n */\nexport function getModelCache(): ModelCache {\n if (!cacheInstance) {\n cacheInstance = new ModelCache();\n }\n return cacheInstance;\n}\n\n// Max size for IndexedDB caching\n// When storing ArrayBuffer in IndexedDB, browser does structured clone which\n// temporarily doubles memory usage. To avoid STATUS_BREAKPOINT crashes:\n// - Files < 500MB: Cache as ArrayBuffer (safe, fast retrieval)\n// - Files >= 500MB: Skip IndexedDB, rely on HTTP cache\n// See: https://bugs.chromium.org/p/chromium/issues/detail?id=170845\nconst MAX_CACHE_SIZE_BYTES = 500 * 1024 * 1024;\n\n/**\n * Options for fetchWithCache\n */\nexport interface FetchWithCacheOptions {\n /** Optional version string for versioned caching */\n version?: string;\n /** If true, validates cached data against server ETag and refetches if stale */\n validateStale?: boolean;\n /** Progress callback during download */\n onProgress?: (loaded: number, total: number) => void;\n}\n\n/**\n * Fetch a model with caching\n * Uses IndexedDB cache with network fallback\n * Files larger than 500MB are not cached to IndexedDB to avoid memory pressure\n * (structured clone during IndexedDB write temporarily doubles memory usage)\n *\n * @param url - The URL to fetch\n * @param onProgress - Optional progress callback (legacy signature)\n * @returns The fetched ArrayBuffer\n *\n * @example\n * ```typescript\n * // Simple usage (backwards compatible)\n * const data = await fetchWithCache('http://example.com/model.onnx');\n *\n * // With progress callback (backwards compatible)\n * const data = await fetchWithCache('http://example.com/model.onnx', (loaded, total) => {\n * console.log(`${loaded}/${total} bytes`);\n * });\n *\n * // With options (new API)\n * const data = await fetchWithCache('http://example.com/model.onnx', {\n * version: '1.0.0',\n * validateStale: true,\n * onProgress: (loaded, total) => console.log(`${loaded}/${total}`)\n * });\n * ```\n */\nexport async function fetchWithCache(\n url: string,\n optionsOrProgress?: FetchWithCacheOptions | ((loaded: number, total: number) => void)\n): Promise<ArrayBuffer> {\n // Normalize arguments - support both old and new signatures\n let options: FetchWithCacheOptions = {};\n if (typeof optionsOrProgress === 'function') {\n // Legacy signature: fetchWithCache(url, onProgress)\n options = { onProgress: optionsOrProgress };\n } else if (optionsOrProgress) {\n // New signature: fetchWithCache(url, options)\n options = optionsOrProgress;\n }\n\n const { version, validateStale = false, onProgress } = options;\n\n const cache = getModelCache();\n const cacheKey = version ? getCacheKey(url, version) : url;\n const telemetry = getTelemetry();\n const span = telemetry?.startSpan('fetchWithCache', {\n 'fetch.url': url,\n ...(version && { 'fetch.version': version }),\n 'fetch.validate_stale': validateStale,\n });\n\n // Check cache with optional staleness validation\n if (validateStale) {\n const validation = await cache.getWithValidation(cacheKey, url);\n\n if (validation.data && !validation.stale) {\n console.log(`[ModelCache] Cache hit (validated): ${url} (${(validation.data.byteLength / 1024 / 1024).toFixed(1)}MB)`);\n onProgress?.(validation.data.byteLength, validation.data.byteLength);\n span?.setAttributes({\n 'fetch.cache_hit': true,\n 'fetch.cache_validated': true,\n 'fetch.cache_stale': false,\n 'fetch.size_bytes': validation.data.byteLength,\n });\n span?.end();\n return validation.data;\n }\n\n if (validation.stale) {\n console.log(`[ModelCache] Cache stale, refetching: ${url}`);\n span?.setAttributes({\n 'fetch.cache_hit': true,\n 'fetch.cache_validated': true,\n 'fetch.cache_stale': true,\n });\n // Continue to fetch fresh data\n }\n // If data is null, continue to fetch\n } else {\n // Simple cache check without validation (backwards compatible behavior)\n const cached = await cache.get(cacheKey);\n if (cached) {\n console.log(`[ModelCache] Cache hit: ${url} (${(cached.byteLength / 1024 / 1024).toFixed(1)}MB)`);\n onProgress?.(cached.byteLength, cached.byteLength);\n span?.setAttributes({\n 'fetch.cache_hit': true,\n 'fetch.size_bytes': cached.byteLength,\n });\n span?.end();\n return cached;\n }\n }\n\n span?.setAttributes({ 'fetch.cache_hit': false });\n console.log(`[ModelCache] Cache miss, fetching: ${url}`);\n\n try {\n // Fetch with progress\n const response = await fetch(url);\n if (!response.ok) {\n throw new Error(`Failed to fetch ${url}: ${response.status}`);\n }\n\n const contentLength = response.headers.get('content-length');\n const total = contentLength ? parseInt(contentLength, 10) : 0;\n const etag = response.headers.get('etag') ?? undefined;\n\n // Check if file is too large for IndexedDB (avoid memory pressure during structured clone)\n const tooLargeForCache = total > MAX_CACHE_SIZE_BYTES;\n if (tooLargeForCache) {\n console.log(`[ModelCache] File too large for IndexedDB (${(total / 1024 / 1024).toFixed(0)}MB > 500MB), using HTTP cache only`);\n }\n\n if (!response.body) {\n const data = await response.arrayBuffer();\n if (!tooLargeForCache) {\n await cache.set(cacheKey, data, etag, version);\n }\n span?.setAttributes({\n 'fetch.size_bytes': data.byteLength,\n 'fetch.cached_to_indexeddb': !tooLargeForCache,\n });\n span?.end();\n return data;\n }\n\n // Stream with progress\n const reader = response.body.getReader();\n const chunks: Uint8Array[] = [];\n let loaded = 0;\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n chunks.push(value);\n loaded += value.length;\n onProgress?.(loaded, total || loaded);\n }\n\n // Combine chunks\n const data = new Uint8Array(loaded);\n let offset = 0;\n for (const chunk of chunks) {\n data.set(chunk, offset);\n offset += chunk.length;\n }\n\n const buffer = data.buffer;\n\n // Cache for next time (if not too large)\n if (!tooLargeForCache) {\n await cache.set(cacheKey, buffer, etag, version);\n console.log(`[ModelCache] Cached: ${url} (${(buffer.byteLength / 1024 / 1024).toFixed(1)}MB)`);\n }\n\n span?.setAttributes({\n 'fetch.size_bytes': buffer.byteLength,\n 'fetch.cached_to_indexeddb': !tooLargeForCache,\n });\n span?.end();\n\n return buffer;\n } catch (error) {\n span?.endWithError(error instanceof Error ? error : new Error(String(error)));\n throw error;\n }\n}\n\n/**\n * Preload models into cache without creating sessions\n */\nexport async function preloadModels(\n urls: string[],\n onProgress?: (current: number, total: number, url: string) => void\n): Promise<void> {\n const cache = getModelCache();\n\n for (let i = 0; i < urls.length; i++) {\n const url = urls[i];\n onProgress?.(i, urls.length, url);\n\n if (await cache.has(url)) {\n console.log(`[ModelCache] Already cached: ${url}`);\n continue;\n }\n\n await fetchWithCache(url);\n }\n\n onProgress?.(urls.length, urls.length, 'done');\n}\n\n/**\n * Format bytes as human readable string\n */\nexport function formatBytes(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n if (bytes < 1024 * 1024 * 1024) return `${(bytes / 1024 / 1024).toFixed(1)} MB`;\n return `${(bytes / 1024 / 1024 / 1024).toFixed(1)} GB`;\n}\n","/**\n * Logging types for Omote SDK\n *\n * 6-level logging system with structured output:\n * - error: Critical failures that prevent operation\n * - warn: Recoverable issues or degraded performance\n * - info: Key lifecycle events (model loaded, inference complete)\n * - debug: Detailed operational info for development\n * - trace: Fine-grained tracing for performance analysis\n * - verbose: Extremely detailed output (tensor shapes, intermediate values)\n */\n\nexport type LogLevel = 'error' | 'warn' | 'info' | 'debug' | 'trace' | 'verbose';\n\n/**\n * Numeric priority for log levels (lower = more severe)\n */\nexport const LOG_LEVEL_PRIORITY: Record<LogLevel, number> = {\n error: 0,\n warn: 1,\n info: 2,\n debug: 3,\n trace: 4,\n verbose: 5,\n};\n\n/**\n * Structured log entry\n */\nexport interface LogEntry {\n /** Unix timestamp in milliseconds */\n timestamp: number;\n /** Log level */\n level: LogLevel;\n /** Module name (e.g., 'LocalInference', 'ModelCache') */\n module: string;\n /** Human-readable message */\n message: string;\n /** Optional structured data */\n data?: Record<string, unknown>;\n /** Optional error object */\n error?: Error;\n}\n\n/**\n * Log output sink interface\n */\nexport interface LogSink {\n (entry: LogEntry): void;\n}\n\n/**\n * Log formatter interface\n */\nexport interface LogFormatter {\n (entry: LogEntry): string;\n}\n\n/**\n * Global logging configuration\n */\nexport interface LoggingConfig {\n /** Minimum log level to output (default: 'info') */\n level: LogLevel;\n /** Enable/disable logging globally (default: true) */\n enabled: boolean;\n /** Output format: 'json' for structured, 'pretty' for human-readable */\n format: 'json' | 'pretty';\n /** Custom output sink (default: console) */\n sink?: LogSink;\n /** Include timestamps in output (default: true) */\n timestamps?: boolean;\n /** Include module name in output (default: true) */\n includeModule?: boolean;\n}\n\n/**\n * Logger interface for module-specific logging\n */\nexport interface ILogger {\n error(message: string, data?: Record<string, unknown>): void;\n warn(message: string, data?: Record<string, unknown>): void;\n info(message: string, data?: Record<string, unknown>): void;\n debug(message: string, data?: Record<string, unknown>): void;\n trace(message: string, data?: Record<string, unknown>): void;\n verbose(message: string, data?: Record<string, unknown>): void;\n\n /** Create a child logger with a sub-module name */\n child(subModule: string): ILogger;\n\n /** Get the module name for this logger */\n readonly module: string;\n}\n\n/**\n * Default configuration\n */\nexport const DEFAULT_LOGGING_CONFIG: LoggingConfig = {\n level: 'info',\n enabled: true,\n format: 'pretty',\n timestamps: true,\n includeModule: true,\n};\n","/**\n * Log formatters for different output formats\n */\n\nimport type { LogEntry, LogFormatter, LogLevel } from './types';\n\n/**\n * ANSI color codes for terminal output\n */\nconst COLORS = {\n reset: '\\x1b[0m',\n red: '\\x1b[31m',\n yellow: '\\x1b[33m',\n blue: '\\x1b[34m',\n cyan: '\\x1b[36m',\n gray: '\\x1b[90m',\n white: '\\x1b[37m',\n magenta: '\\x1b[35m',\n};\n\n/**\n * Level-specific colors\n */\nconst LEVEL_COLORS: Record<LogLevel, string> = {\n error: COLORS.red,\n warn: COLORS.yellow,\n info: COLORS.blue,\n debug: COLORS.cyan,\n trace: COLORS.magenta,\n verbose: COLORS.gray,\n};\n\n/**\n * Level display names (padded for alignment)\n */\nconst LEVEL_NAMES: Record<LogLevel, string> = {\n error: 'ERROR ',\n warn: 'WARN ',\n info: 'INFO ',\n debug: 'DEBUG ',\n trace: 'TRACE ',\n verbose: 'VERBOSE',\n};\n\n/**\n * Check if we're in a browser environment\n */\nconst isBrowser = typeof window !== 'undefined';\n\n/**\n * Format timestamp as ISO string or relative time\n */\nfunction formatTimestamp(timestamp: number): string {\n const date = new Date(timestamp);\n return date.toISOString().substring(11, 23); // HH:mm:ss.SSS\n}\n\n/**\n * Safely serialize data to JSON, handling circular references\n */\nfunction safeStringify(data: unknown): string {\n const seen = new WeakSet();\n return JSON.stringify(data, (key, value) => {\n if (typeof value === 'object' && value !== null) {\n if (seen.has(value)) {\n return '[Circular]';\n }\n seen.add(value);\n }\n // Handle special types\n if (value instanceof Error) {\n return {\n name: value.name,\n message: value.message,\n stack: value.stack,\n };\n }\n if (value instanceof Float32Array || value instanceof Int16Array) {\n return `${value.constructor.name}(${value.length})`;\n }\n if (ArrayBuffer.isView(value)) {\n return `${value.constructor.name}(${value.byteLength})`;\n }\n return value;\n });\n}\n\n/**\n * JSON formatter - structured output for machine parsing\n */\nexport const jsonFormatter: LogFormatter = (entry: LogEntry): string => {\n const output: Record<string, unknown> = {\n timestamp: entry.timestamp,\n level: entry.level,\n module: entry.module,\n message: entry.message,\n };\n\n if (entry.data && Object.keys(entry.data).length > 0) {\n output.data = entry.data;\n }\n\n if (entry.error) {\n output.error = {\n name: entry.error.name,\n message: entry.error.message,\n stack: entry.error.stack,\n };\n }\n\n return safeStringify(output);\n};\n\n/**\n * Pretty formatter - human-readable output with colors\n */\nexport const prettyFormatter: LogFormatter = (entry: LogEntry): string => {\n const time = formatTimestamp(entry.timestamp);\n const level = LEVEL_NAMES[entry.level];\n const module = entry.module;\n const message = entry.message;\n\n // Build the base message\n let output: string;\n\n if (isBrowser) {\n // Browser: no ANSI colors, use plain text\n output = `${time} ${level} [${module}] ${message}`;\n } else {\n // Terminal: use ANSI colors\n const color = LEVEL_COLORS[entry.level];\n output = `${COLORS.gray}${time}${COLORS.reset} ${color}${level}${COLORS.reset} ${COLORS.cyan}[${module}]${COLORS.reset} ${message}`;\n }\n\n // Append data if present\n if (entry.data && Object.keys(entry.data).length > 0) {\n const dataStr = safeStringify(entry.data);\n // For pretty format, indent multi-line data\n if (dataStr.length > 80) {\n output += '\\n ' + JSON.stringify(entry.data, null, 2).replace(/\\n/g, '\\n ');\n } else {\n output += ' ' + dataStr;\n }\n }\n\n // Append error if present\n if (entry.error) {\n output += `\\n ${entry.error.name}: ${entry.error.message}`;\n if (entry.error.stack) {\n const stackLines = entry.error.stack.split('\\n').slice(1, 4);\n output += '\\n ' + stackLines.join('\\n ');\n }\n }\n\n return output;\n};\n\n/**\n * Get formatter by name\n */\nexport function getFormatter(format: 'json' | 'pretty'): LogFormatter {\n return format === 'json' ? jsonFormatter : prettyFormatter;\n}\n\n/**\n * Create browser console arguments for styled output\n * Returns [formatString, ...styleArgs] for console.log\n */\nexport function createBrowserConsoleArgs(entry: LogEntry): [string, ...string[]] {\n const time = formatTimestamp(entry.timestamp);\n const level = entry.level.toUpperCase().padEnd(7);\n const module = entry.module;\n const message = entry.message;\n\n // CSS styles for browser console\n const styles = {\n time: 'color: gray;',\n error: 'color: red; font-weight: bold;',\n warn: 'color: orange; font-weight: bold;',\n info: 'color: blue;',\n debug: 'color: cyan;',\n trace: 'color: magenta;',\n verbose: 'color: gray;',\n module: 'color: teal; font-weight: bold;',\n message: 'color: inherit;',\n };\n\n let formatStr = '%c%s %c%s %c[%s]%c %s';\n const args: string[] = [\n styles.time,\n time,\n styles[entry.level],\n level,\n styles.module,\n module,\n styles.message,\n message,\n ];\n\n // Append data\n if (entry.data && Object.keys(entry.data).length > 0) {\n formatStr += ' %o';\n args.push(entry.data as unknown as string);\n }\n\n return [formatStr, ...args];\n}\n","/**\n * Omote SDK Logger\n *\n * Unified logging system with:\n * - 6 log levels (error, warn, info, debug, trace, verbose)\n * - Structured JSON output for machine parsing\n * - Pretty output for human readability\n * - Module-based child loggers\n * - Runtime configuration\n * - Browser and Node.js compatible\n */\n\nimport type {\n LogLevel,\n LogEntry,\n LoggingConfig,\n LogSink,\n ILogger,\n} from './types';\nimport { LOG_LEVEL_PRIORITY, DEFAULT_LOGGING_CONFIG } from './types';\nimport { getFormatter, createBrowserConsoleArgs } from './formatters';\n\n/**\n * Check if running in browser\n */\nconst isBrowser = typeof window !== 'undefined';\n\n/**\n * Global logging configuration\n */\nlet globalConfig: LoggingConfig = { ...DEFAULT_LOGGING_CONFIG };\n\n/**\n * Configure global logging settings\n */\nexport function configureLogging(config: Partial<LoggingConfig>): void {\n globalConfig = { ...globalConfig, ...config };\n}\n\n/**\n * Get current logging configuration\n */\nexport function getLoggingConfig(): LoggingConfig {\n return { ...globalConfig };\n}\n\n/**\n * Reset logging configuration to defaults\n */\nexport function resetLoggingConfig(): void {\n globalConfig = { ...DEFAULT_LOGGING_CONFIG };\n}\n\n/**\n * Set log level at runtime\n */\nexport function setLogLevel(level: LogLevel): void {\n globalConfig.level = level;\n}\n\n/**\n * Enable or disable logging\n */\nexport function setLoggingEnabled(enabled: boolean): void {\n globalConfig.enabled = enabled;\n}\n\n/**\n * Default console sink with browser-optimized output\n */\nconst consoleSink: LogSink = (entry: LogEntry): void => {\n const consoleMethod = entry.level === 'error' ? 'error'\n : entry.level === 'warn' ? 'warn'\n : 'log';\n\n if (globalConfig.format === 'pretty' && isBrowser) {\n // Use styled console output in browser\n const args = createBrowserConsoleArgs(entry);\n (console as any)[consoleMethod](...args);\n } else {\n // Use formatter for terminal or JSON output\n const formatter = getFormatter(globalConfig.format);\n const formatted = formatter(entry);\n (console as any)[consoleMethod](formatted);\n }\n};\n\n/**\n * Get the active sink (custom or default console)\n */\nfunction getActiveSink(): LogSink {\n return globalConfig.sink || consoleSink;\n}\n\n/**\n * Check if a log level should be output given current config\n */\nfunction shouldLog(level: LogLevel): boolean {\n if (!globalConfig.enabled) return false;\n return LOG_LEVEL_PRIORITY[level] <= LOG_LEVEL_PRIORITY[globalConfig.level];\n}\n\n/**\n * Logger implementation\n */\nclass Logger implements ILogger {\n readonly module: string;\n\n constructor(module: string) {\n this.module = module;\n }\n\n private log(level: LogLevel, message: string, data?: Record<string, unknown>): void {\n if (!shouldLog(level)) return;\n\n const entry: LogEntry = {\n timestamp: Date.now(),\n level,\n module: this.module,\n message,\n data,\n };\n\n // Extract error from data if present\n if (data?.error instanceof Error) {\n entry.error = data.error;\n // Remove from data to avoid duplication\n const { error, ...rest } = data;\n entry.data = Object.keys(rest).length > 0 ? rest : undefined;\n }\n\n getActiveSink()(entry);\n }\n\n error(message: string, data?: Record<string, unknown>): void {\n this.log('error', message, data);\n }\n\n warn(message: string, data?: Record<string, unknown>): void {\n this.log('warn', message, data);\n }\n\n info(message: string, data?: Record<string, unknown>): void {\n this.log('info', message, data);\n }\n\n debug(message: string, data?: Record<string, unknown>): void {\n this.log('debug', message, data);\n }\n\n trace(message: string, data?: Record<string, unknown>): void {\n this.log('trace', message, data);\n }\n\n verbose(message: string, data?: Record<string, unknown>): void {\n this.log('verbose', message, data);\n }\n\n child(subModule: string): ILogger {\n return new Logger(`${this.module}.${subModule}`);\n }\n}\n\n/**\n * Logger cache for reusing instances\n */\nconst loggerCache = new Map<string, Logger>();\n\n/**\n * Create a logger for a specific module\n *\n * @param module - Module name (e.g., 'LocalInference', 'ModelCache')\n * @returns Logger instance\n *\n * @example\n * ```typescript\n * const logger = createLogger('LocalInference');\n * logger.info('Model loaded', { backend: 'webgpu', loadTimeMs: 1234 });\n * ```\n */\nexport function createLogger(module: string): ILogger {\n let logger = loggerCache.get(module);\n if (!logger) {\n logger = new Logger(module);\n loggerCache.set(module, logger);\n }\n return logger;\n}\n\n/**\n * Clear logger cache (useful for testing)\n */\nexport function clearLoggerCache(): void {\n loggerCache.clear();\n}\n\n/**\n * No-op logger for when logging is completely disabled\n */\nexport const noopLogger: ILogger = {\n module: 'noop',\n error: () => {},\n warn: () => {},\n info: () => {},\n debug: () => {},\n trace: () => {},\n verbose: () => {},\n child: () => noopLogger,\n};\n\n/**\n * Get a no-op logger (for production builds that tree-shake logging)\n */\nexport function getNoopLogger(): ILogger {\n return noopLogger;\n}\n","/**\r\n * Runtime detection utilities for platform-specific inference configuration\r\n *\r\n * These utilities help determine the optimal backend (WebGPU vs WASM) based on\r\n * the current platform's capabilities and known limitations.\r\n *\r\n * Key considerations:\r\n * - iOS Safari: WebGPU crashes due to JSEP bugs (GitHub #22776, #26827)\r\n * - Android Chrome: WebGPU works well (Chrome 121+)\r\n * - Desktop: WebGPU preferred for performance\r\n *\r\n * @module utils/runtime\r\n */\r\n\r\n/**\r\n * Supported inference backends\r\n */\r\nexport type RuntimeBackend = 'webgpu' | 'wasm';\r\n\r\n/**\r\n * User-configurable backend preference\r\n */\r\nexport type BackendPreference =\r\n | 'auto' // iOS→WASM, else→WebGPU with fallback\r\n | 'webgpu' // Prefer WebGPU, fallback to WASM on error\r\n | 'wasm' // Prefer WASM, no WebGPU attempt\r\n | 'webgpu-only' // Force WebGPU, throw on failure (for debugging)\r\n | 'wasm-only'; // Force WASM, never load WebGPU bundle (smallest bundle)\r\n\r\n/**\r\n * Detect iOS Safari browser\r\n *\r\n * iOS Safari has severe WebGPU issues:\r\n * - JSEP compilation bugs cause OOM during session creation\r\n * - Threading bugs require numThreads=1\r\n * - Proxy mode triggers memory leaks\r\n *\r\n * @returns true if running in iOS Safari\r\n */\r\nexport function isIOSSafari(): boolean {\r\n if (typeof navigator === 'undefined') return false;\r\n const ua = navigator.userAgent.toLowerCase();\r\n // Must be on iOS device AND using Safari (not Chrome/CriOS/Firefox/Edge)\r\n return /iphone|ipad|ipod/.test(ua) && /safari/.test(ua) && !/chrome|crios|fxios|chromium|edg/.test(ua);\r\n}\r\n\r\n/**\r\n * Detect any iOS device (regardless of browser)\r\n *\r\n * On iOS, all browsers use WebKit, so Chrome/Firefox on iOS\r\n * have the same limitations as Safari.\r\n *\r\n * @returns true if running on any iOS device\r\n */\r\nexport function isIOS(): boolean {\r\n if (typeof navigator === 'undefined') return false;\r\n const ua = navigator.userAgent.toLowerCase();\r\n // iPadOS 13+ reports macOS user agent — detect via maxTouchPoints\r\n return /iphone|ipad|ipod/.test(ua) ||\r\n (/macintosh/.test(ua) && navigator.maxTouchPoints > 1);\r\n}\r\n\r\n/**\r\n * Detect Android device\r\n *\r\n * Android Chrome 121+ has good WebGPU support with Qualcomm/ARM GPUs.\r\n *\r\n * @returns true if running on Android\r\n */\r\nexport function isAndroid(): boolean {\r\n if (typeof navigator === 'undefined') return false;\r\n return /android/i.test(navigator.userAgent);\r\n}\r\n\r\n/**\r\n * Detect any mobile device (iOS or Android)\r\n *\r\n * Mobile devices have different performance characteristics:\r\n * - Lower memory limits\r\n * - Thermal throttling\r\n * - Different GPU architectures\r\n *\r\n * @returns true if running on mobile\r\n */\r\nexport function isMobile(): boolean {\r\n return isIOS() || isAndroid();\r\n}\r\n\r\n/**\r\n * Check if WebGPU API is available in the browser\r\n *\r\n * Note: This only checks if the API exists, not if it works reliably.\r\n * iOS has navigator.gpu but ONNX Runtime's WebGPU backend crashes.\r\n *\r\n * @returns true if navigator.gpu exists\r\n */\r\nexport function hasWebGPUApi(): boolean {\r\n if (typeof navigator === 'undefined') return false;\r\n return 'gpu' in navigator && navigator.gpu !== undefined;\r\n}\r\n\r\n/**\r\n * Get the recommended backend for the current platform\r\n *\r\n * Decision tree:\r\n * 1. iOS (any browser): Force WASM (WebGPU crashes)\r\n * 2. Android: WebGPU preferred (works in Chrome 121+)\r\n * 3. Desktop: WebGPU preferred (best performance)\r\n *\r\n * @returns 'wasm' for iOS, 'webgpu' for everything else\r\n */\r\nexport function getRecommendedBackend(): RuntimeBackend {\r\n // Safari (all platforms): Always WASM - WebGPU crashes due to JSEP bugs\r\n // iOS: All browsers use WebKit, so all have the same issue\r\n // macOS Safari: Same multithreaded JSEP build bug\r\n if (isSafari() || isIOS()) {\r\n return 'wasm';\r\n }\r\n\r\n // Android/Desktop (non-Safari): WebGPU preferred\r\n return 'webgpu';\r\n}\r\n\r\n/**\r\n * Resolve user preference to actual backend\r\n *\r\n * @param preference User's backend preference\r\n * @param webgpuAvailable Whether WebGPU is available and working\r\n * @returns The backend to use\r\n */\r\nexport function resolveBackend(\r\n preference: BackendPreference,\r\n webgpuAvailable: boolean\r\n): RuntimeBackend {\r\n switch (preference) {\r\n case 'wasm-only':\r\n return 'wasm';\r\n\r\n case 'webgpu-only':\r\n if (!webgpuAvailable) {\r\n throw new Error(\r\n 'WebGPU requested but not available. Use \"webgpu\" or \"auto\" for fallback.'\r\n );\r\n }\r\n return 'webgpu';\r\n\r\n case 'wasm':\r\n return 'wasm';\r\n\r\n case 'webgpu':\r\n return webgpuAvailable ? 'webgpu' : 'wasm';\r\n\r\n case 'auto':\r\n default:\r\n // Auto: Use platform recommendation, with WebGPU availability check\r\n const recommended = getRecommendedBackend();\r\n if (recommended === 'webgpu' && !webgpuAvailable) {\r\n return 'wasm';\r\n }\r\n return recommended;\r\n }\r\n}\r\n\r\n/**\r\n * Get optimal WASM thread count for current platform\r\n *\r\n * @returns Recommended number of WASM threads\r\n */\r\nexport function getOptimalWasmThreads(): number {\r\n if (isIOS()) {\r\n // iOS: Must be 1 to avoid shared memory bugs (GitHub #22086)\r\n return 1;\r\n }\r\n\r\n if (isAndroid()) {\r\n // Android: Conservative threading (2 threads)\r\n return 2;\r\n }\r\n\r\n // Desktop: Full threading (4 threads)\r\n return 4;\r\n}\r\n\r\n/**\r\n * Check if WASM proxy mode should be enabled\r\n *\r\n * Proxy mode offloads inference to a Web Worker, but has issues:\r\n * - iOS: Triggers Safari 26 JSEP memory leak\r\n * - Mobile: Generally unstable\r\n *\r\n * @returns true if proxy mode is safe to enable\r\n */\r\nexport function shouldEnableWasmProxy(): boolean {\r\n // Always disable proxy. When proxy=true, WASM initializes in a Web Worker.\r\n // The WebGPU EP needs WASM on the main thread for model parsing. If a\r\n // consumer Vite-aliases all ORT imports to the same module, proxy=true\r\n // on one session causes \"WebAssembly is not initialized yet\" for WebGPU.\r\n return false;\r\n}\r\n\r\n/**\r\n * Detect Safari browser on any platform (macOS + iOS)\r\n *\r\n * Safari WebKit has bugs with ONNX Runtime's WebGPU multithreaded JSEP build\r\n * that crash session creation. Both iOS and macOS Safari are affected.\r\n *\r\n * @returns true if running in Safari on any platform\r\n */\r\nexport function isSafari(): boolean {\r\n if (typeof navigator === 'undefined') return false;\r\n const ua = navigator.userAgent.toLowerCase();\r\n // Safari: has \"safari\" but not Chrome, Chromium, CriOS, FxiOS, or Edge\r\n return /safari/.test(ua) && !/chrome|crios|fxios|chromium|edg/.test(ua);\r\n}\r\n\r\n/**\r\n * Recommend using CPU-optimized lip sync model (wav2arkit_cpu)\r\n *\r\n * All iOS browsers use WebKit and have tight memory limits — the 384MB\r\n * LAM model causes silent crashes. wav2arkit_cpu uses URL pass-through\r\n * (ORT fetches the 402MB weights directly into WASM, no JS heap copy).\r\n *\r\n * macOS Safari also needs this due to ONNX Runtime JSEP/ASYNCIFY bugs\r\n * that crash WebKit's JIT compiler.\r\n *\r\n * @returns true if iOS (any browser) or Safari (any platform)\r\n */\r\nexport function shouldUseCpuLipSync(): boolean {\r\n return isSafari() || isIOS();\r\n}\r\n\r\n/**\r\n * Check if Web Speech API is available in the browser\r\n *\r\n * The Web Speech API provides native speech recognition in Safari and Chrome.\r\n * On iOS Safari, this is significantly faster than Whisper WASM.\r\n *\r\n * @returns true if SpeechRecognition API is available\r\n */\r\nexport function isSpeechRecognitionAvailable(): boolean {\r\n if (typeof window === 'undefined') return false;\r\n return 'SpeechRecognition' in window || 'webkitSpeechRecognition' in window;\r\n}\r\n\r\n/**\r\n * Recommend using native Safari Speech API over Whisper on iOS\r\n *\r\n * On iOS, Whisper ASR via WASM takes ~1.3s per inference (30% over target).\r\n * Safari's native Web Speech API is:\r\n * - Much faster (native implementation)\r\n * - Battery-efficient (no WASM overhead)\r\n * - No model download needed (saves 30-150MB)\r\n *\r\n * @returns true if on iOS or Safari with Speech API available\r\n */\r\nexport function shouldUseNativeASR(): boolean {\r\n return (isIOS() || isSafari()) && isSpeechRecognitionAvailable();\r\n}\r\n\r\n/**\r\n * Recommend using server-side LAM over client-side on iOS\r\n *\r\n * On iOS, LAM lip sync via WASM takes ~332ms per second of audio (3.3x over target).\r\n * Server-side inference with GPU can achieve ~50ms, providing:\r\n * - Real-time lip sync (under 100ms target)\r\n * - Reduced iOS device thermal/battery impact\r\n * - Better user experience\r\n *\r\n * @returns true if on iOS (should use server-side lip sync)\r\n */\r\nexport function shouldUseServerLipSync(): boolean {\r\n return isIOS();\r\n}\r\n","/**\r\n * Lazy ONNX Runtime loader with conditional WebGPU/WASM bundle loading\r\n *\r\n * This module provides a way to dynamically load the appropriate ONNX Runtime bundle\r\n * based on the platform's capabilities. This is critical for iOS support because:\r\n *\r\n * 1. iOS Safari has WebGPU API but ONNX Runtime's WebGPU backend crashes\r\n * 2. Loading the WebGPU bundle wastes bandwidth and can cause issues\r\n * 3. WASM-only bundle is smaller and more reliable on iOS\r\n *\r\n * Usage:\r\n * ```typescript\r\n * const ort = await getOnnxRuntime('wasm'); // Load WASM-only bundle\r\n * const ort = await getOnnxRuntime('webgpu'); // Load WebGPU bundle (includes WASM)\r\n * ```\r\n *\r\n * @module inference/onnxLoader\r\n */\r\n\r\n// Type-only import for TypeScript (no runtime code loaded at import time)\r\n// At runtime, we dynamically import either 'onnxruntime-web' or 'onnxruntime-web/webgpu'\r\nimport type { InferenceSession, Tensor, Env } from 'onnxruntime-common';\r\n\r\n// Type alias for the ORT module (loaded dynamically)\r\nexport type OrtModule = {\r\n InferenceSession: typeof InferenceSession;\r\n Tensor: typeof Tensor;\r\n env: Env;\r\n};\r\n\r\n// Re-export session options type\r\nexport type SessionOptions = InferenceSession.SessionOptions;\r\nimport {\r\n RuntimeBackend,\r\n BackendPreference,\r\n isIOS,\r\n isIOSSafari,\r\n isSafari,\r\n isMobile,\r\n getOptimalWasmThreads,\r\n shouldEnableWasmProxy,\r\n resolveBackend,\r\n hasWebGPUApi,\r\n} from '../utils/runtime';\r\n\r\n// Re-export RuntimeBackend for consumers\r\nexport type { RuntimeBackend } from '../utils/runtime';\r\nimport { createLogger } from '../logging';\r\n\r\nconst logger = createLogger('OnnxLoader');\r\n\r\n// Cached ONNX Runtime instance\r\nlet ortInstance: OrtModule | null = null;\r\nlet loadedBackend: RuntimeBackend | null = null;\r\n\r\n// CDN path for WASM files\r\nconst WASM_CDN_PATH = 'https://cdn.jsdelivr.net/npm/onnxruntime-web@1.23.2/dist/';\r\n\r\n/**\r\n * Check if WebGPU is available and likely to work\r\n *\r\n * This is more thorough than just checking navigator.gpu exists.\r\n * It actually requests an adapter to verify the GPU is accessible.\r\n *\r\n * @returns true if WebGPU is available and working\r\n */\r\nexport async function isWebGPUAvailable(): Promise<boolean> {\r\n // iOS 18+ exposes navigator.gpu, but the ORT WebGPU bundle uses the\r\n // asyncify WASM binary (ort-wasm-simd-threaded.asyncify.wasm) which\r\n // crashes WebKit's JIT on all iOS browsers. Return false early to\r\n // prevent probing the GPU adapter (which itself consumes memory).\r\n if (isIOS()) {\r\n logger.debug('WebGPU check: disabled on iOS (asyncify bundle crashes WebKit)');\r\n return false;\r\n }\r\n\r\n if (!hasWebGPUApi()) {\r\n logger.debug('WebGPU check: navigator.gpu not available', {\r\n isSecureContext: typeof window !== 'undefined' ? (window as any).isSecureContext : 'N/A',\r\n });\r\n return false;\r\n }\r\n\r\n try {\r\n const adapter = await navigator.gpu.requestAdapter();\r\n if (!adapter) {\r\n logger.debug('WebGPU check: No adapter available');\r\n return false;\r\n }\r\n\r\n // Check for minimum required features\r\n const device = await adapter.requestDevice();\r\n if (!device) {\r\n logger.debug('WebGPU check: Could not create device');\r\n return false;\r\n }\r\n\r\n // Clean up\r\n device.destroy();\r\n\r\n logger.debug('WebGPU check: Available and working');\r\n return true;\r\n } catch (err) {\r\n logger.debug('WebGPU check: Error during availability check', { error: err });\r\n return false;\r\n }\r\n}\r\n\r\n/**\r\n * iOS WASM memory patch\r\n *\r\n * ONNX Runtime's ort-wasm-simd-threaded.wasm creates\r\n * WebAssembly.Memory({initial:256, maximum:65536, shared:true}).\r\n * iOS Safari rejects this with \"RangeError: Out of memory\" because\r\n * shared WebAssembly.Memory with a 4GB maximum exceeds iOS limits.\r\n *\r\n * This patch intercepts WebAssembly.Memory construction on iOS to:\r\n * 1. Keep `shared` flag (required by ORT v1.23.2 threaded WASM)\r\n * 2. Cap `maximum` to 32768 pages (2GB)\r\n *\r\n * The 2GB cap balances memory headroom for the 404MB wav2arkit_cpu model\r\n * (needs ~500MB WASM for weights + activations) while staying within\r\n * iOS WebKit's process limits on modern iPhones (6-8GB RAM).\r\n *\r\n * Applied once on first ORT load, stays active for the page lifetime.\r\n */\r\nlet iosWasmPatched = false;\r\n\r\nfunction applyIOSWasmMemoryPatch(): void {\r\n // Only patch on iOS Safari. iOS Chrome/Firefox (all use WebKit) may handle\r\n // the default 4GB maximum without the constructor wrapper interfering.\r\n // Safari specifically throws \"RangeError: Out of memory\" on 4GB shared memory.\r\n if (iosWasmPatched || !isIOSSafari()) return;\r\n iosWasmPatched = true;\r\n\r\n const OrigMemory = WebAssembly.Memory;\r\n const MAX_IOS_PAGES = 32768; // 32768 × 64KB = 2GB\r\n\r\n logger.info('Applying iOS WASM memory patch (max→2GB, shared preserved)');\r\n\r\n // Replace WebAssembly.Memory constructor to cap maximum pages.\r\n // ORT v1.23.2 threaded WASM binaries ALL declare shared:true in their\r\n // memory import. Setting shared:false causes a LinkError on instantiation.\r\n // Instead, keep shared:true and only cap maximum from 65536 (4GB) to\r\n // 32768 (2GB) to allow sufficient headroom for model weights + inference\r\n // activations. Log the actual descriptor for debugging.\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n (WebAssembly as any).Memory = function IOSPatchedMemory(\r\n descriptor: WebAssembly.MemoryDescriptor\r\n ): WebAssembly.Memory {\r\n const patched = { ...descriptor };\r\n\r\n if (patched.maximum !== undefined && patched.maximum > MAX_IOS_PAGES) {\r\n logger.info('iOS memory patch: capping maximum', {\r\n original: patched.maximum,\r\n capped: MAX_IOS_PAGES,\r\n shared: patched.shared,\r\n initial: patched.initial,\r\n });\r\n patched.maximum = MAX_IOS_PAGES;\r\n }\r\n\r\n return new OrigMemory(patched);\r\n };\r\n\r\n // Preserve prototype chain so instanceof checks still work\r\n (WebAssembly as any).Memory.prototype = OrigMemory.prototype;\r\n}\r\n\r\n/**\r\n * Configure WASM environment settings based on platform\r\n *\r\n * This must be called before creating any inference sessions.\r\n */\r\nfunction configureWasm(ort: OrtModule): void {\r\n // Set CDN path for WASM files\r\n ort.env.wasm.wasmPaths = WASM_CDN_PATH;\r\n\r\n // Platform-specific threading configuration\r\n const numThreads = getOptimalWasmThreads();\r\n const enableProxy = shouldEnableWasmProxy();\r\n\r\n ort.env.wasm.numThreads = numThreads;\r\n ort.env.wasm.simd = true; // SIMD always helps\r\n ort.env.wasm.proxy = enableProxy;\r\n\r\n logger.info('WASM configured', {\r\n numThreads,\r\n simd: true,\r\n proxy: enableProxy,\r\n platform: isIOS() ? 'iOS' : isMobile() ? 'Android' : 'Desktop',\r\n });\r\n}\r\n\r\n/**\r\n * Load ONNX Runtime with the specified backend\r\n *\r\n * This lazily loads the appropriate bundle:\r\n * - 'wasm': Loads onnxruntime-web (WASM-only, smaller)\r\n * - 'webgpu': Loads onnxruntime-web/webgpu (includes WebGPU + WASM fallback)\r\n *\r\n * Once loaded, the same instance is reused for all subsequent calls.\r\n * If you need to switch backends, you must reload the page.\r\n *\r\n * @param backend The backend to load ('webgpu' or 'wasm')\r\n * @returns The ONNX Runtime module\r\n */\r\nexport async function getOnnxRuntime(\r\n backend: RuntimeBackend\r\n): Promise<OrtModule> {\r\n // Return cached instance if same backend\r\n if (ortInstance && loadedBackend === backend) {\r\n return ortInstance;\r\n }\r\n\r\n // Warn if trying to switch backends (not supported without page reload)\r\n if (ortInstance && loadedBackend !== backend) {\r\n logger.warn(\r\n `ONNX Runtime already loaded with ${loadedBackend} backend. ` +\r\n `Cannot switch to ${backend}. Returning existing instance.`\r\n );\r\n return ortInstance;\r\n }\r\n\r\n logger.info(`Loading ONNX Runtime with ${backend} backend...`);\r\n\r\n // iOS: Patch WebAssembly.Memory before ORT loads its WASM binary.\r\n // Must be applied before any InferenceSession.create() call.\r\n applyIOSWasmMemoryPatch();\r\n\r\n try {\r\n if (backend === 'wasm' && (isIOS() || isSafari())) {\r\n // Safari (all platforms) + iOS (all browsers): Import via\r\n // 'onnxruntime-web/wasm' sub-path. This uses the plain WASM binaries\r\n // (ort-wasm-simd / ort-wasm-simd-threaded) WITHOUT JSEP/ASYNCIFY.\r\n //\r\n // The JSEP build (ort-wasm-simd-threaded.jsep) crashes WebKit's JIT\r\n // on iOS and macOS Safari due to ASYNCIFY code generation issues.\r\n // See: https://bugs.webkit.org/show_bug.cgi?id=262475\r\n //\r\n // With COEP/COOP headers removed for iOS (no SharedArrayBuffer),\r\n // ORT picks ort-wasm-simd.wasm (single-threaded, Case A = works).\r\n // On macOS Safari (COEP/COOP present), ORT picks\r\n // ort-wasm-simd-threaded.wasm (threaded but non-JSEP, Case B = works).\r\n const module = await import('onnxruntime-web/wasm');\r\n ortInstance = module.default || module;\r\n } else if (backend === 'wasm') {\r\n // Desktop WASM: load bare 'onnxruntime-web'\r\n const module = await import('onnxruntime-web');\r\n ortInstance = module.default || module;\r\n } else {\r\n // Load WebGPU bundle (includes WASM fallback)\r\n const module = await import('onnxruntime-web/webgpu');\r\n ortInstance = module.default || module;\r\n }\r\n\r\n loadedBackend = backend;\r\n\r\n // Configure WASM settings (applies to both bundles)\r\n configureWasm(ortInstance);\r\n\r\n logger.info(`ONNX Runtime loaded successfully`, { backend });\r\n\r\n return ortInstance;\r\n } catch (err) {\r\n logger.error(`Failed to load ONNX Runtime with ${backend} backend`, {\r\n error: err,\r\n });\r\n throw new Error(\r\n `Failed to load ONNX Runtime: ${err instanceof Error ? err.message : String(err)}`\r\n );\r\n }\r\n}\r\n\r\n/**\r\n * Get the appropriate ONNX Runtime based on user preference\r\n *\r\n * This resolves the user's preference against platform capabilities\r\n * and loads the appropriate bundle.\r\n *\r\n * @param preference User's backend preference\r\n * @returns The ONNX Runtime module and the resolved backend\r\n */\r\nexport async function getOnnxRuntimeForPreference(\r\n preference: BackendPreference = 'auto'\r\n): Promise<{ ort: OrtModule; backend: RuntimeBackend }> {\r\n // Check WebGPU availability (skip for iOS)\r\n const webgpuAvailable = await isWebGPUAvailable();\r\n\r\n // Resolve preference to actual backend\r\n const backend = resolveBackend(preference, webgpuAvailable);\r\n\r\n logger.info('Resolved backend preference', {\r\n preference,\r\n webgpuAvailable,\r\n resolvedBackend: backend,\r\n });\r\n\r\n // Load the appropriate bundle\r\n const ort = await getOnnxRuntime(backend);\r\n\r\n return { ort, backend };\r\n}\r\n\r\n/**\r\n * Get session options for creating an inference session\r\n *\r\n * This returns optimized session options based on the backend and platform.\r\n *\r\n * @param backend The backend being used\r\n * @returns Session options for InferenceSession.create()\r\n */\r\nexport function getSessionOptions(\r\n backend: RuntimeBackend\r\n): SessionOptions {\r\n if (backend === 'webgpu') {\r\n return {\r\n executionProviders: [\r\n {\r\n name: 'webgpu',\r\n preferredLayout: 'NHWC', // Reduces memory overhead for layout conversions\r\n } as const,\r\n ],\r\n graphOptimizationLevel: 'all',\r\n };\r\n }\r\n\r\n // WASM backend\r\n // iOS: Use 'basic' graph optimization to reduce peak WASM memory during\r\n // session creation. 'all' causes ORT to create temporary copies of graph\r\n // nodes for complex transformers (attention fusion, constant folding),\r\n // pushing peak memory to ~750-950MB which exceeds iOS WebKit's per-tab\r\n // process limit (~1-1.5GB). Also disable memory arenas to avoid upfront\r\n // allocation. See: https://github.com/microsoft/onnxruntime/issues/13408\r\n if (isIOS()) {\r\n return {\r\n executionProviders: ['wasm'],\r\n graphOptimizationLevel: 'basic',\r\n enableCpuMemArena: false,\r\n enableMemPattern: false,\r\n };\r\n }\r\n\r\n return {\r\n executionProviders: ['wasm'],\r\n graphOptimizationLevel: 'all',\r\n };\r\n}\r\n\r\n/**\r\n * Create an inference session with automatic fallback\r\n *\r\n * If WebGPU session creation fails, automatically falls back to WASM.\r\n *\r\n * @param modelBuffer The model data as ArrayBuffer\r\n * @param preferredBackend The preferred backend\r\n * @returns The created session and the backend used\r\n */\r\nexport async function createSessionWithFallback(\r\n modelBuffer: ArrayBuffer,\r\n preferredBackend: RuntimeBackend\r\n): Promise<{\r\n session: InferenceSession;\r\n backend: RuntimeBackend;\r\n}> {\r\n const ort = await getOnnxRuntime(preferredBackend);\r\n\r\n // Convert ArrayBuffer to Uint8Array for onnxruntime-common types\r\n const modelData = new Uint8Array(modelBuffer);\r\n\r\n if (preferredBackend === 'webgpu') {\r\n try {\r\n const options = getSessionOptions('webgpu');\r\n const session = await ort.InferenceSession.create(modelData, options);\r\n\r\n logger.info('Session created with WebGPU backend');\r\n return { session, backend: 'webgpu' };\r\n } catch (err) {\r\n logger.warn('WebGPU session creation failed, falling back to WASM', {\r\n error: err instanceof Error ? err.message : String(err),\r\n });\r\n // Fall through to WASM\r\n }\r\n }\r\n\r\n // WASM (primary or fallback)\r\n const options = getSessionOptions('wasm');\r\n const session = await ort.InferenceSession.create(modelData, options);\r\n\r\n logger.info('Session created with WASM backend');\r\n return { session, backend: 'wasm' };\r\n}\r\n\r\n/**\r\n * Get the currently loaded backend (if any)\r\n */\r\nexport function getLoadedBackend(): RuntimeBackend | null {\r\n return loadedBackend;\r\n}\r\n\r\n/**\r\n * Check if ONNX Runtime has been loaded\r\n */\r\nexport function isOnnxRuntimeLoaded(): boolean {\r\n return ortInstance !== null;\r\n}\r\n\r\n/**\r\n * Preload ONNX Runtime and compile the WASM binary early\r\n *\r\n * Call this before loading heavy resources (Three.js, VRM models) to ensure\r\n * WASM memory is allocated in a clean JS heap, reducing iOS memory pressure.\r\n * Uses the singleton pattern — subsequent model loading reuses this instance.\r\n *\r\n * @param preference Backend preference (default: 'auto')\r\n * @returns The resolved backend that was loaded\r\n */\r\nexport async function preloadOnnxRuntime(\r\n preference: BackendPreference = 'auto'\r\n): Promise<RuntimeBackend> {\r\n if (ortInstance) {\r\n logger.info('ONNX Runtime already preloaded', { backend: loadedBackend });\r\n return loadedBackend!;\r\n }\r\n\r\n logger.info('Preloading ONNX Runtime...', { preference });\r\n const { backend } = await getOnnxRuntimeForPreference(preference);\r\n logger.info('ONNX Runtime preloaded', { backend });\r\n return backend;\r\n}\r\n","/**\n * Shared blendshape constants and utilities for lip sync inference\n *\n * Contains LAM_BLENDSHAPES (canonical ordering), symmetrization, and\n * index remapping used by both Wav2Vec2Inference and Wav2ArkitCpuInference.\n *\n * This module is the single source of truth for blendshape ordering to\n * avoid circular dependencies between inference classes.\n *\n * @category Inference\n */\n\n/**\n * LAM model blendshape names in order (52 total)\n * NOTE: This is alphabetical ordering used by LAM, different from standard ARKit order\n */\nexport const LAM_BLENDSHAPES = [\n 'browDownLeft', 'browDownRight', 'browInnerUp', 'browOuterUpLeft', 'browOuterUpRight',\n 'cheekPuff', 'cheekSquintLeft', 'cheekSquintRight',\n 'eyeBlinkLeft', 'eyeBlinkRight', 'eyeLookDownLeft', 'eyeLookDownRight',\n 'eyeLookInLeft', 'eyeLookInRight', 'eyeLookOutLeft', 'eyeLookOutRight',\n 'eyeLookUpLeft', 'eyeLookUpRight', 'eyeSquintLeft', 'eyeSquintRight',\n 'eyeWideLeft', 'eyeWideRight',\n 'jawForward', 'jawLeft', 'jawOpen', 'jawRight',\n 'mouthClose', 'mouthDimpleLeft', 'mouthDimpleRight', 'mouthFrownLeft', 'mouthFrownRight',\n 'mouthFunnel', 'mouthLeft', 'mouthLowerDownLeft', 'mouthLowerDownRight',\n 'mouthPressLeft', 'mouthPressRight', 'mouthPucker', 'mouthRight',\n 'mouthRollLower', 'mouthRollUpper', 'mouthShrugLower', 'mouthShrugUpper',\n 'mouthSmileLeft', 'mouthSmileRight', 'mouthStretchLeft', 'mouthStretchRight',\n 'mouthUpperUpLeft', 'mouthUpperUpRight',\n 'noseSneerLeft', 'noseSneerRight', 'tongueOut'\n] as const;\n\n/** Alias for backwards compatibility */\nexport const ARKIT_BLENDSHAPES = LAM_BLENDSHAPES;\n\n/**\n * ARKit Left/Right symmetric pairs for blendshape symmetrization\n * From LAM official postprocessing (models/utils.py)\n */\nconst ARKIT_SYMMETRIC_PAIRS: [string, string][] = [\n ['jawLeft', 'jawRight'],\n ['mouthLeft', 'mouthRight'],\n ['mouthSmileLeft', 'mouthSmileRight'],\n ['mouthFrownLeft', 'mouthFrownRight'],\n ['mouthDimpleLeft', 'mouthDimpleRight'],\n ['mouthStretchLeft', 'mouthStretchRight'],\n ['mouthPressLeft', 'mouthPressRight'],\n ['mouthUpperUpLeft', 'mouthUpperUpRight'],\n ['mouthLowerDownLeft', 'mouthLowerDownRight'],\n ['noseSneerLeft', 'noseSneerRight'],\n ['cheekSquintLeft', 'cheekSquintRight'],\n ['browDownLeft', 'browDownRight'],\n ['browOuterUpLeft', 'browOuterUpRight'],\n ['eyeBlinkLeft', 'eyeBlinkRight'],\n ['eyeLookUpLeft', 'eyeLookUpRight'],\n ['eyeLookDownLeft', 'eyeLookDownRight'],\n ['eyeLookInLeft', 'eyeLookInRight'],\n ['eyeLookOutLeft', 'eyeLookOutRight'],\n ['eyeSquintLeft', 'eyeSquintRight'],\n ['eyeWideLeft', 'eyeWideRight'],\n];\n\n// Precompute index pairs for fast symmetrization\nconst SYMMETRIC_INDEX_PAIRS: [number, number][] = ARKIT_SYMMETRIC_PAIRS.map(([l, r]) => [\n LAM_BLENDSHAPES.indexOf(l as typeof LAM_BLENDSHAPES[number]),\n LAM_BLENDSHAPES.indexOf(r as typeof LAM_BLENDSHAPES[number]),\n]).filter(([l, r]) => l !== -1 && r !== -1) as [number, number][];\n\n/**\n * Symmetrize blendshapes by averaging left/right pairs\n * From LAM official postprocessing (models/utils.py)\n * This fixes asymmetric output from the raw model\n */\nexport function symmetrizeBlendshapes(frame: Float32Array): Float32Array {\n const result = new Float32Array(frame);\n for (const [lIdx, rIdx] of SYMMETRIC_INDEX_PAIRS) {\n const avg = (frame[lIdx] + frame[rIdx]) / 2;\n result[lIdx] = avg;\n result[rIdx] = avg;\n }\n return result;\n}\n\n/**\n * wav2arkit_cpu model blendshape ordering\n *\n * Indices 0-24 match LAM_BLENDSHAPES, but 25+ diverge:\n * - LAM puts jawRight, mouthClose, mouthDimpleLeft, mouthDimpleRight at 25-28\n * - wav2arkit_cpu puts mouthFrownLeft at 25 and moves those four to 48-51\n */\nexport const WAV2ARKIT_BLENDSHAPES = [\n 'browDownLeft', 'browDownRight', 'browInnerUp', 'browOuterUpLeft', 'browOuterUpRight',\n 'cheekPuff', 'cheekSquintLeft', 'cheekSquintRight',\n 'eyeBlinkLeft', 'eyeBlinkRight', 'eyeLookDownLeft', 'eyeLookDownRight',\n 'eyeLookInLeft', 'eyeLookInRight', 'eyeLookOutLeft', 'eyeLookOutRight',\n 'eyeLookUpLeft', 'eyeLookUpRight', 'eyeSquintLeft', 'eyeSquintRight',\n 'eyeWideLeft', 'eyeWideRight',\n 'jawForward', 'jawLeft', 'jawOpen',\n 'mouthFrownLeft', 'mouthFrownRight', 'mouthFunnel', 'mouthLeft',\n 'mouthLowerDownLeft', 'mouthLowerDownRight',\n 'mouthPressLeft', 'mouthPressRight', 'mouthPucker', 'mouthRight',\n 'mouthRollLower', 'mouthRollUpper', 'mouthShrugLower', 'mouthShrugUpper',\n 'mouthSmileLeft', 'mouthSmileRight', 'mouthStretchLeft', 'mouthStretchRight',\n 'mouthUpperUpLeft', 'mouthUpperUpRight',\n 'noseSneerLeft', 'noseSneerRight', 'tongueOut',\n 'mouthClose', 'mouthDimpleLeft', 'mouthDimpleRight', 'jawRight',\n] as const;\n\n/**\n * Precomputed remap table: wav2arkit_cpu output index → LAM_BLENDSHAPES index\n *\n * For each wav2arkit output index i, REMAP_TO_LAM[i] gives the LAM_BLENDSHAPES\n * index where that value should be placed.\n */\nexport const REMAP_WAV2ARKIT_TO_LAM: number[] = WAV2ARKIT_BLENDSHAPES.map(\n (name) => LAM_BLENDSHAPES.indexOf(name as typeof LAM_BLENDSHAPES[number])\n);\n\n/**\n * Remap a blendshape frame from wav2arkit_cpu ordering to LAM_BLENDSHAPES ordering\n *\n * @param frame - Float32Array of 52 blendshape values in wav2arkit_cpu order\n * @returns Float32Array of 52 blendshape values in LAM_BLENDSHAPES order\n */\nexport function remapWav2ArkitToLam(frame: Float32Array): Float32Array {\n const result = new Float32Array(52);\n for (let i = 0; i < 52; i++) {\n result[REMAP_WAV2ARKIT_TO_LAM[i]] = frame[i];\n }\n return result;\n}\n","/**\r\n * Unified Wav2Vec2 inference engine for Audio-to-Expression + ASR\r\n *\r\n * Runs entirely in the browser using WebGPU or WASM.\r\n * Takes raw 16kHz audio and outputs:\r\n * - 52 ARKit blendshapes (lip sync)\r\n * - 32-token CTC logits (speech recognition)\r\n *\r\n * @category Inference\r\n *\r\n * @example Basic usage\r\n * ```typescript\r\n * import { Wav2Vec2Inference } from '@omote/core';\r\n *\r\n * const wav2vec = new Wav2Vec2Inference({ modelUrl: '/models/unified_wav2vec2_asr_a2e.onnx' });\r\n * await wav2vec.load();\r\n *\r\n * // Process 1 second of audio (16kHz = 16000 samples)\r\n * const result = await wav2vec.infer(audioSamples);\r\n *\r\n * console.log('Blendshapes:', result.blendshapes); // [30, 52] for 30fps\r\n * console.log('ASR text:', result.text); // Decoded transcription\r\n * ```\r\n */\r\n\r\n// Type-only import for TypeScript (no runtime code loaded at import time)\r\n// At runtime, we dynamically import either 'onnxruntime-web' or 'onnxruntime-web/webgpu'\r\nimport type { InferenceSession, Tensor } from 'onnxruntime-common';\r\n\r\nimport { fetchWithCache, getModelCache, formatBytes } from '../cache/ModelCache';\r\nimport { createLogger } from '../logging';\r\nimport { getTelemetry } from '../telemetry';\r\nimport {\r\n getOnnxRuntimeForPreference,\r\n getSessionOptions,\r\n isWebGPUAvailable,\r\n type RuntimeBackend,\r\n type OrtModule,\r\n} from './onnxLoader';\r\nimport { BackendPreference, isIOS } from '../utils/runtime';\r\nimport { symmetrizeBlendshapes, LAM_BLENDSHAPES, ARKIT_BLENDSHAPES } from './blendshapeUtils';\r\nimport type { LipSyncBackend } from './LipSyncBackend';\r\n\r\nconst logger = createLogger('Wav2Vec2');\r\n\r\n// Re-export for backward compatibility\r\nexport type InferenceBackend = BackendPreference;\r\n\r\nexport interface Wav2Vec2InferenceConfig {\r\n /** Path or URL to the ONNX model */\r\n modelUrl: string;\r\n /**\r\n * Path or URL to external model data file (.onnx.data weights).\r\n * Default: `${modelUrl}.data` (e.g., /models/model.onnx.data)\r\n *\r\n * Set to `false` to skip external data loading (single-file models only).\r\n */\r\n externalDataUrl?: string | false;\r\n /** Preferred backend (auto will try WebGPU first, fallback to WASM) */\r\n backend?: InferenceBackend;\r\n /** Number of identity classes (default: 12 for streaming model) */\r\n numIdentityClasses?: number;\r\n}\r\n\r\nexport interface ModelInfo {\r\n backend: 'webgpu' | 'wasm';\r\n loadTimeMs: number;\r\n inputNames: string[];\r\n outputNames: string[];\r\n}\r\n\r\n// Re-export blendshape constants from shared utils (canonical source)\r\nexport { LAM_BLENDSHAPES, ARKIT_BLENDSHAPES } from './blendshapeUtils';\r\n\r\n/** CTC vocabulary (32 tokens from wav2vec2-base-960h) */\r\nexport const CTC_VOCAB = [\r\n '<pad>', '<s>', '</s>', '<unk>', '|', 'E', 'T', 'A', 'O', 'N',\r\n 'I', 'H', 'S', 'R', 'D', 'L', 'U', 'M', 'W', 'C',\r\n 'F', 'G', 'Y', 'P', 'B', 'V', 'K', \"'\", 'X', 'J', 'Q', 'Z'\r\n];\r\n\r\nexport interface Wav2Vec2Result {\r\n /** Blendshape weights [frames, 52] - 30fps */\r\n blendshapes: Float32Array[];\r\n /** Raw CTC logits [frames, 32] - 50fps */\r\n asrLogits: Float32Array[];\r\n /** Decoded text from CTC */\r\n text: string;\r\n /** Number of blendshape frames (30fps) — alias for numA2EFrames */\r\n numFrames: number;\r\n /** Number of A2E frames (30fps) */\r\n numA2EFrames: number;\r\n /** Number of ASR frames (50fps) */\r\n numASRFrames: number;\r\n /** Inference time in ms */\r\n inferenceTimeMs: number;\r\n}\r\n\r\nexport class Wav2Vec2Inference implements LipSyncBackend {\r\n readonly modelId = 'wav2vec2' as const;\r\n\r\n private session: InferenceSession | null = null;\r\n private ort: OrtModule | null = null; // Lazy-loaded ONNX Runtime module\r\n private config: Wav2Vec2InferenceConfig;\r\n private _backend: RuntimeBackend = 'wasm';\r\n private isLoading = false;\r\n private numIdentityClasses: number;\r\n\r\n // Inference queue for handling concurrent calls\r\n private inferenceQueue: Promise<void> = Promise.resolve();\r\n\r\n // Session health: set to true if session.run() times out.\r\n // A timed-out session may have a zombie GPU/WASM dispatch still running,\r\n // so all future infer() calls reject immediately to prevent concurrent access.\r\n private poisoned = false;\r\n private static readonly INFERENCE_TIMEOUT_MS = 5_000;\r\n\r\n constructor(config: Wav2Vec2InferenceConfig) {\r\n this.config = config;\r\n this.numIdentityClasses = config.numIdentityClasses ?? 12;\r\n }\r\n\r\n /**\r\n * Check if WebGPU is available and working\r\n * (iOS returns false even if navigator.gpu exists due to ONNX Runtime bugs)\r\n */\r\n static isWebGPUAvailable = isWebGPUAvailable;\r\n\r\n get backend(): 'webgpu' | 'wasm' | null {\r\n return this.session ? this._backend : null;\r\n }\r\n\r\n get isLoaded(): boolean {\r\n return this.session !== null;\r\n }\r\n\r\n /** True if inference timed out and the session is permanently unusable */\r\n get isSessionPoisoned(): boolean {\r\n return this.poisoned;\r\n }\r\n\r\n /**\r\n * Load the ONNX model\r\n */\r\n async load(): Promise<ModelInfo> {\r\n if (this.isLoading) {\r\n throw new Error('Model is already loading');\r\n }\r\n\r\n if (this.session) {\r\n throw new Error('Model already loaded. Call dispose() first.');\r\n }\r\n\r\n this.isLoading = true;\r\n const startTime = performance.now();\r\n const telemetry = getTelemetry();\r\n const span = telemetry?.startSpan('Wav2Vec2.load', {\r\n 'model.url': this.config.modelUrl,\r\n 'model.backend_requested': this.config.backend || 'auto',\r\n });\r\n\r\n try {\r\n // Lazy load ONNX Runtime with appropriate backend\r\n // iOS: Loads WASM-only bundle (smaller, no WebGPU code)\r\n // Android/Desktop: Loads WebGPU bundle (with WASM fallback)\r\n logger.info('Loading ONNX Runtime...', { preference: this.config.backend || 'auto' });\r\n\r\n const { ort, backend } = await getOnnxRuntimeForPreference(this.config.backend || 'auto');\r\n this.ort = ort;\r\n this._backend = backend;\r\n\r\n logger.info('ONNX Runtime loaded', { backend: this._backend });\r\n\r\n const modelUrl = this.config.modelUrl;\r\n const dataUrl = this.config.externalDataUrl !== false\r\n ? (typeof this.config.externalDataUrl === 'string'\r\n ? this.config.externalDataUrl\r\n : `${modelUrl}.data`)\r\n : null;\r\n const sessionOptions = getSessionOptions(this._backend);\r\n let isCached = false;\r\n\r\n // iOS: Pass URLs directly to ORT to avoid loading 384MB into JS heap.\r\n // ORT fetches externally into WASM memory, cutting peak JS memory from\r\n // ~768MB (ArrayBuffer + Uint8Array copy) to ~2MB (just the graph URL).\r\n // Desktop uses cached ArrayBuffers for faster reloads via IndexedDB.\r\n if (isIOS()) {\r\n logger.info('iOS: passing model URLs directly to ORT (low-memory path)', {\r\n modelUrl,\r\n dataUrl,\r\n });\r\n\r\n if (dataUrl) {\r\n const dataFilename = dataUrl.split('/').pop()!;\r\n logger.info('iOS: setting externalData', { dataFilename, dataUrl });\r\n (sessionOptions as Record<string, unknown>).externalData = [{\r\n path: dataFilename,\r\n data: dataUrl, // URL string — ORT fetches directly into WASM\r\n }];\r\n }\r\n\r\n logger.info('iOS: calling InferenceSession.create() with URL string', {\r\n modelUrl,\r\n sessionOptions: JSON.stringify(sessionOptions, (_, v) =>\r\n typeof v === 'string' && v.length > 100 ? v.slice(0, 100) + '...' : v\r\n ),\r\n });\r\n\r\n try {\r\n this.session = await this.ort!.InferenceSession.create(modelUrl, sessionOptions);\r\n } catch (sessionErr) {\r\n logger.error('iOS: InferenceSession.create() failed', {\r\n error: sessionErr instanceof Error ? sessionErr.message : String(sessionErr),\r\n errorType: sessionErr?.constructor?.name,\r\n stack: sessionErr instanceof Error ? sessionErr.stack : undefined,\r\n });\r\n throw sessionErr;\r\n }\r\n\r\n logger.info('iOS: session created successfully', {\r\n inputNames: this.session.inputNames,\r\n outputNames: this.session.outputNames,\r\n });\r\n } else {\r\n // Desktop: fetch + cache in IndexedDB for fast reloads\r\n const cache = getModelCache();\r\n isCached = await cache.has(modelUrl);\r\n\r\n let modelBuffer: ArrayBuffer;\r\n if (isCached) {\r\n logger.debug('Loading model from cache', { modelUrl });\r\n modelBuffer = (await cache.get(modelUrl))!;\r\n\r\n if (!modelBuffer) {\r\n logger.warn('Cache corruption detected, clearing and retrying', { modelUrl });\r\n await cache.delete(modelUrl);\r\n modelBuffer = await fetchWithCache(modelUrl);\r\n }\r\n } else {\r\n logger.debug('Fetching and caching model', { modelUrl });\r\n modelBuffer = await fetchWithCache(modelUrl);\r\n }\r\n\r\n if (!modelBuffer) {\r\n throw new Error(`Failed to load model: ${modelUrl}`);\r\n }\r\n\r\n // Load external data file (.onnx.data weights) if present\r\n let externalDataBuffer: ArrayBuffer | null = null;\r\n if (dataUrl) {\r\n try {\r\n const isDataCached = await cache.has(dataUrl);\r\n if (isDataCached) {\r\n logger.debug('Loading external data from cache', { dataUrl });\r\n externalDataBuffer = (await cache.get(dataUrl))!;\r\n if (!externalDataBuffer) {\r\n logger.warn('Cache corruption for external data, retrying', { dataUrl });\r\n await cache.delete(dataUrl);\r\n externalDataBuffer = await fetchWithCache(dataUrl);\r\n }\r\n } else {\r\n logger.info('Fetching external model data', {\r\n dataUrl,\r\n note: 'This may be a large download (383MB+)',\r\n });\r\n externalDataBuffer = await fetchWithCache(dataUrl);\r\n }\r\n logger.info('External data loaded', {\r\n size: formatBytes(externalDataBuffer.byteLength),\r\n });\r\n } catch (err) {\r\n logger.debug('No external data file found (single-file model)', {\r\n dataUrl,\r\n error: (err as Error).message,\r\n });\r\n }\r\n }\r\n\r\n logger.debug('Creating ONNX session', {\r\n graphSize: formatBytes(modelBuffer.byteLength),\r\n externalDataSize: externalDataBuffer ? formatBytes(externalDataBuffer.byteLength) : 'none',\r\n backend: this._backend,\r\n });\r\n\r\n // Pass external data to session if loaded\r\n if (externalDataBuffer) {\r\n const dataFilename = dataUrl!.split('/').pop()!;\r\n (sessionOptions as Record<string, unknown>).externalData = [{\r\n path: dataFilename,\r\n data: new Uint8Array(externalDataBuffer),\r\n }];\r\n }\r\n\r\n const modelData = new Uint8Array(modelBuffer);\r\n this.session = await this.ort!.InferenceSession.create(modelData, sessionOptions);\r\n }\r\n\r\n logger.info('ONNX session created successfully', {\r\n executionProvider: this._backend,\r\n backend: this._backend,\r\n });\r\n\r\n const loadTimeMs = performance.now() - startTime;\r\n\r\n logger.info('Model loaded successfully', {\r\n backend: this._backend,\r\n loadTimeMs: Math.round(loadTimeMs),\r\n inputs: this.session.inputNames,\r\n outputs: this.session.outputNames,\r\n });\r\n\r\n span?.setAttributes({\r\n 'model.backend': this._backend,\r\n 'model.load_time_ms': loadTimeMs,\r\n 'model.cached': !isIOS() && isCached,\r\n });\r\n span?.end();\r\n telemetry?.recordHistogram('omote.model.load_time', loadTimeMs, {\r\n model: 'wav2vec2',\r\n backend: this._backend,\r\n });\r\n\r\n // Warmup inference to initialize GPU kernels and contexts\r\n // This prevents hitching on the first real inference during playback.\r\n // Uses session.run() directly (not this.infer()) to avoid the 5s\r\n // inference timeout that poisons the session. First WebGPU inference\r\n // triggers shader compilation which can take 5-15s on some GPUs.\r\n logger.debug('Running warmup inference to initialize GPU context');\r\n const warmupStart = performance.now();\r\n const warmupAudio = new Float32Array(16000); // 1 second of silence\r\n const warmupIdentity = new Float32Array(this.numIdentityClasses);\r\n warmupIdentity[0] = 1.0;\r\n const warmupFeeds = {\r\n 'audio': new this.ort!.Tensor('float32', warmupAudio, [1, 16000]),\r\n 'identity': new this.ort!.Tensor('float32', warmupIdentity, [1, this.numIdentityClasses]),\r\n };\r\n const WARMUP_TIMEOUT_MS = 15_000;\r\n const warmupResult = await Promise.race([\r\n this.session!.run(warmupFeeds).then(() => 'ok' as const),\r\n new Promise<'timeout'>(r => setTimeout(() => r('timeout'), WARMUP_TIMEOUT_MS)),\r\n ]);\r\n const warmupTimeMs = performance.now() - warmupStart;\r\n if (warmupResult === 'timeout') {\r\n logger.warn('Warmup inference timed out — GPU may be unresponsive. Continuing without warmup.', {\r\n timeoutMs: WARMUP_TIMEOUT_MS,\r\n backend: this._backend,\r\n });\r\n } else {\r\n logger.info('Warmup inference complete', {\r\n warmupTimeMs: Math.round(warmupTimeMs),\r\n backend: this._backend,\r\n });\r\n }\r\n telemetry?.recordHistogram('omote.model.warmup_time', warmupTimeMs, {\r\n model: 'wav2vec2',\r\n backend: this._backend,\r\n });\r\n\r\n return {\r\n backend: this._backend,\r\n loadTimeMs,\r\n inputNames: [...this.session.inputNames],\r\n outputNames: [...this.session.outputNames],\r\n };\r\n } catch (error) {\r\n span?.endWithError(error instanceof Error ? error : new Error(String(error)));\r\n telemetry?.incrementCounter('omote.errors.total', 1, {\r\n model: 'wav2vec2',\r\n error_type: 'load_failed',\r\n });\r\n throw error;\r\n } finally {\r\n this.isLoading = false;\r\n }\r\n }\r\n\r\n /**\r\n * Run inference on raw audio\r\n * @param audioSamples - Float32Array of raw audio at 16kHz (16000 samples = 1 second)\r\n * @param identityIndex - Optional identity index (0-11, default 0 = neutral)\r\n *\r\n * Note: Model expects 1-second chunks (16000 samples) for optimal performance.\r\n * Audio will be zero-padded or truncated to 16000 samples.\r\n */\r\n async infer(\r\n audioSamples: Float32Array,\r\n identityIndex: number = 0\r\n ): Promise<Wav2Vec2Result> {\r\n if (!this.session) {\r\n throw new Error('Model not loaded. Call load() first.');\r\n }\r\n\r\n if (this.poisoned) {\r\n throw new Error('Wav2Vec2 session timed out — inference unavailable until page reload');\r\n }\r\n\r\n // CRITICAL: Force copy IMMEDIATELY to prevent ArrayBuffer detachment\r\n // During interruptions, audioSamples buffer may get detached by ONNX Runtime\r\n // before we process it. Copy synchronously to preserve data.\r\n const audioSamplesCopy = new Float32Array(audioSamples);\r\n\r\n // Ensure audio is exactly 16000 samples (1 second)\r\n let audio: Float32Array;\r\n if (audioSamplesCopy.length === 16000) {\r\n audio = audioSamplesCopy;\r\n } else if (audioSamplesCopy.length < 16000) {\r\n // Zero-pad\r\n audio = new Float32Array(16000);\r\n audio.set(audioSamplesCopy, 0);\r\n } else {\r\n // Truncate\r\n audio = audioSamplesCopy.slice(0, 16000);\r\n }\r\n\r\n // Create identity one-hot vector\r\n const identity = new Float32Array(this.numIdentityClasses);\r\n identity[Math.min(identityIndex, this.numIdentityClasses - 1)] = 1.0;\r\n\r\n // CRITICAL: Force copy to prevent ArrayBuffer detachment by ONNX Runtime Web workers\r\n // Without copy, WASM backend transfers buffers to workers, causing \"memory access out of bounds\" errors\r\n const audioCopy = new Float32Array(audio);\r\n const identityCopy = new Float32Array(identity);\r\n\r\n const feeds = {\r\n 'audio': new this.ort!.Tensor('float32', audioCopy, [1, 16000]),\r\n 'identity': new this.ort!.Tensor('float32', identityCopy, [1, this.numIdentityClasses]),\r\n };\r\n\r\n // Queue the inference\r\n return this.queueInference(feeds);\r\n }\r\n\r\n /**\r\n * Decode CTC logits to text using greedy decoding\r\n */\r\n private decodeCTC(logits: Float32Array[]): string {\r\n const tokens: number[] = [];\r\n let prevToken = -1;\r\n\r\n for (const frame of logits) {\r\n // Find argmax\r\n let maxIdx = 0;\r\n let maxVal = frame[0];\r\n for (let i = 1; i < frame.length; i++) {\r\n if (frame[i] > maxVal) {\r\n maxVal = frame[i];\r\n maxIdx = i;\r\n }\r\n }\r\n\r\n // CTC collapse: skip duplicates and blanks (token 0)\r\n if (maxIdx !== prevToken && maxIdx !== 0) {\r\n tokens.push(maxIdx);\r\n }\r\n prevToken = maxIdx;\r\n }\r\n\r\n // Convert to text (token 4 = '|' = word separator = space)\r\n return tokens.map(t => CTC_VOCAB[t] === '|' ? ' ' : CTC_VOCAB[t]).join('');\r\n }\r\n\r\n /**\r\n * Queue inference to serialize ONNX session calls\r\n */\r\n private queueInference(\r\n feeds: Record<string, Tensor>\r\n ): Promise<Wav2Vec2Result> {\r\n return new Promise((resolve, reject) => {\r\n this.inferenceQueue = this.inferenceQueue.then(async () => {\r\n const telemetry = getTelemetry();\r\n const span = telemetry?.startSpan('Wav2Vec2.infer', {\r\n 'inference.backend': this._backend,\r\n 'inference.input_samples': 16000,\r\n });\r\n try {\r\n const startTime = performance.now();\r\n let timeoutId: ReturnType<typeof setTimeout>;\r\n const results = await Promise.race([\r\n this.session!.run(feeds).then(r => { clearTimeout(timeoutId); return r; }),\r\n new Promise<never>((_, rej) => {\r\n timeoutId = setTimeout(\r\n () => rej(new Error(`Wav2Vec2 inference timed out after ${Wav2Vec2Inference.INFERENCE_TIMEOUT_MS}ms`)),\r\n Wav2Vec2Inference.INFERENCE_TIMEOUT_MS,\r\n );\r\n }),\r\n ]);\r\n const inferenceTimeMs = performance.now() - startTime;\r\n\r\n const asrOutput = results['asr_logits'];\r\n const blendshapeOutput = results['blendshapes'];\r\n\r\n if (!asrOutput || !blendshapeOutput) {\r\n throw new Error('Missing outputs from model');\r\n }\r\n\r\n const asrData = asrOutput.data as Float32Array;\r\n const blendshapeData = blendshapeOutput.data as Float32Array;\r\n\r\n // Parse shapes: ASR is [1, time_50fps, 32], A2E is [1, time_30fps, 52]\r\n const numASRFrames = asrOutput.dims[1] as number;\r\n const numA2EFrames = blendshapeOutput.dims[1] as number;\r\n const asrVocabSize = asrOutput.dims[2] as number;\r\n const numBlendshapes = blendshapeOutput.dims[2] as number;\r\n\r\n // Split into per-frame arrays\r\n const asrLogits: Float32Array[] = [];\r\n const blendshapes: Float32Array[] = [];\r\n\r\n for (let f = 0; f < numASRFrames; f++) {\r\n asrLogits.push(asrData.slice(f * asrVocabSize, (f + 1) * asrVocabSize));\r\n }\r\n\r\n for (let f = 0; f < numA2EFrames; f++) {\r\n const rawFrame = blendshapeData.slice(f * numBlendshapes, (f + 1) * numBlendshapes);\r\n // Apply symmetrization postprocessing (from LAM official pipeline)\r\n blendshapes.push(symmetrizeBlendshapes(rawFrame));\r\n }\r\n\r\n // Decode CTC\r\n const text = this.decodeCTC(asrLogits);\r\n\r\n logger.trace('Inference completed', {\r\n inferenceTimeMs: Math.round(inferenceTimeMs * 100) / 100,\r\n numA2EFrames,\r\n numASRFrames,\r\n textLength: text.length,\r\n });\r\n\r\n span?.setAttributes({\r\n 'inference.duration_ms': inferenceTimeMs,\r\n 'inference.a2e_frames': numA2EFrames,\r\n 'inference.asr_frames': numASRFrames,\r\n });\r\n span?.end();\r\n telemetry?.recordHistogram('omote.inference.latency', inferenceTimeMs, {\r\n model: 'wav2vec2',\r\n backend: this._backend,\r\n });\r\n telemetry?.incrementCounter('omote.inference.total', 1, {\r\n model: 'wav2vec2',\r\n backend: this._backend,\r\n status: 'success',\r\n });\r\n\r\n resolve({\r\n blendshapes,\r\n asrLogits,\r\n text,\r\n numFrames: numA2EFrames,\r\n numA2EFrames,\r\n numASRFrames,\r\n inferenceTimeMs,\r\n });\r\n } catch (err) {\r\n const errMsg = err instanceof Error ? err.message : String(err);\r\n if (errMsg.includes('timed out')) {\r\n this.poisoned = true;\r\n logger.error('CRITICAL: Inference session timed out — LAM is dead. Page reload required.', {\r\n backend: this._backend,\r\n timeoutMs: Wav2Vec2Inference.INFERENCE_TIMEOUT_MS,\r\n });\r\n } else {\r\n logger.error('Inference failed', { error: errMsg, backend: this._backend });\r\n }\r\n span?.endWithError(err instanceof Error ? err : new Error(String(err)));\r\n telemetry?.incrementCounter('omote.inference.total', 1, {\r\n model: 'wav2vec2',\r\n backend: this._backend,\r\n status: 'error',\r\n });\r\n reject(err);\r\n }\r\n });\r\n });\r\n }\r\n\r\n /**\r\n * Get blendshape value by name for a specific frame\r\n */\r\n getBlendshape(blendshapes: Float32Array, name: typeof LAM_BLENDSHAPES[number]): number {\r\n const index = LAM_BLENDSHAPES.indexOf(name);\r\n if (index === -1) {\r\n throw new Error(`Unknown blendshape: ${name}`);\r\n }\r\n return blendshapes[index];\r\n }\r\n\r\n /**\r\n * Dispose of the model and free resources\r\n */\r\n async dispose(): Promise<void> {\r\n if (this.session) {\r\n await this.session.release();\r\n this.session = null;\r\n }\r\n }\r\n}\r\n","/**\r\n * FullFacePipeline - Combined LAM lip sync + Emotion upper face pipeline\r\n *\r\n * Orchestrates full-face animation by combining:\r\n * 1. LAM lip sync (52 ARKit blendshapes) via audio-first scheduling\r\n * 2. Emotion labels (from backend LLM or `setEmotionLabel()`) for upper face\r\n * 3. AudioEnergyAnalyzer for prosody-driven fallback when no emotion label is set\r\n *\r\n * Architecture: Audio-First, LAM-Background (same as SyncedAudioPipeline)\r\n * - Audio chunks are scheduled for playback immediately (never waits for LAM)\r\n * - LAM inference runs in background without blocking the audio path\r\n * - Lip sync starts ~1 second after audio (LAM needs 16000 samples to infer)\r\n *\r\n * Merge Strategy:\r\n * - Lower face (41 blendshapes): 100% from LAM (mouth, jaw, tongue, etc.)\r\n * - Upper face (11 blendshapes): Emotion overlay with LAM as subtle fallback\r\n * Formula: emotion * emotionBlendFactor + lam * lamBlendFactor\r\n *\r\n * Emotion Sources (in priority order):\r\n * 1. `setEmotionLabel()` — explicit label from backend LLM (recommended)\r\n * 2. Prosody fallback — subtle brow movement from audio energy (automatic)\r\n *\r\n * @category Audio\r\n *\r\n * @example Basic usage\r\n * ```typescript\r\n * import { FullFacePipeline } from '@omote/core';\r\n *\r\n * const pipeline = new FullFacePipeline({\r\n * lam,\r\n * emotionBlendFactor: 0.8,\r\n * lamBlendFactor: 0.2,\r\n * });\r\n * await pipeline.initialize();\r\n *\r\n * pipeline.on('full_frame_ready', (frame) => {\r\n * applyToAvatar(frame.blendshapes);\r\n * });\r\n *\r\n * pipeline.start();\r\n * pipeline.setEmotionLabel('happy'); // From backend LLM\r\n * await pipeline.onAudioChunk(audioData);\r\n * ```\r\n */\r\n\r\nimport { AudioScheduler } from './AudioScheduler';\r\nimport { AudioChunkCoalescer } from './AudioChunkCoalescer';\r\nimport { LAMPipeline } from './LAMPipeline';\r\nimport { EventEmitter } from '../events/EventEmitter';\r\nimport { EmotionToBlendshapeMapper, UPPER_FACE_BLENDSHAPES } from '../animation/EmotionToBlendshapeMapper';\r\nimport { AudioEnergyAnalyzer } from '../animation/audioEnergy';\r\nimport type { UpperFaceBlendshapes } from '../animation/EmotionToBlendshapeMapper';\r\nimport type { LipSyncBackend } from '../inference/LipSyncBackend';\r\nimport { LAM_BLENDSHAPES } from '../inference/Wav2Vec2Inference';\r\nimport type { EmotionFrame, Emotion2VecLabel } from '../animation/EmotionToBlendshapeMapper';\r\nimport { createLogger } from '../logging';\r\nimport { pcm16ToFloat32 } from './audioUtils';\r\n\r\nconst logger = createLogger('FullFacePipeline');\r\n\r\n/**\r\n * Index map for O(1) blendshape name lookup\r\n */\r\nconst BLENDSHAPE_INDEX_MAP = new Map<string, number>();\r\nLAM_BLENDSHAPES.forEach((name, index) => {\r\n BLENDSHAPE_INDEX_MAP.set(name, index);\r\n});\r\n\r\n/**\r\n * Set of upper face blendshape names for fast lookup\r\n */\r\nconst UPPER_FACE_SET = new Set<string>(UPPER_FACE_BLENDSHAPES);\r\n\r\n/**\r\n * Map of natural language + SenseVoice emotion labels to Emotion2VecLabel\r\n */\r\nconst EMOTION_LABEL_MAP: Record<string, Emotion2VecLabel> = {\r\n // Direct labels\r\n happy: 'happy',\r\n sad: 'sad',\r\n angry: 'angry',\r\n neutral: 'neutral',\r\n // Natural language synonyms\r\n excited: 'happy',\r\n joyful: 'happy',\r\n cheerful: 'happy',\r\n delighted: 'happy',\r\n amused: 'happy',\r\n melancholic: 'sad',\r\n sorrowful: 'sad',\r\n disappointed: 'sad',\r\n frustrated: 'angry',\r\n irritated: 'angry',\r\n furious: 'angry',\r\n annoyed: 'angry',\r\n // SenseVoice labels\r\n fearful: 'sad',\r\n disgusted: 'angry',\r\n surprised: 'happy',\r\n};\r\n\r\n/**\r\n * Configuration for FullFacePipeline\r\n */\r\nexport interface FullFacePipelineOptions {\r\n /** Sample rate in Hz (default: 16000) */\r\n sampleRate?: number;\r\n /** Target chunk duration in ms for coalescing (default: 200) */\r\n chunkTargetMs?: number;\r\n /**\r\n * Audio playback delay in ms before first audio plays.\r\n * Gives LAM inference time to pre-compute blendshapes.\r\n * Default: auto-detected from lam.backend (50ms WebGPU, 350ms WASM).\r\n */\r\n audioDelayMs?: number;\r\n /** LAM inference engine */\r\n lam: LipSyncBackend;\r\n /**\r\n * Emotion blend factor for upper face blendshapes (0-1)\r\n * Higher values give more weight to emotion detection\r\n * @default 0.8\r\n */\r\n emotionBlendFactor?: number;\r\n /**\r\n * LAM blend factor for upper face blendshapes (0-1)\r\n * Provides subtle fallback from LAM when emotion is weak\r\n * @default 0.2\r\n */\r\n lamBlendFactor?: number;\r\n}\r\n\r\n/**\r\n * Full face frame with merged blendshapes and emotion data\r\n */\r\nexport interface FullFaceFrame {\r\n /** Merged 52 ARKit blendshapes (lower face from LAM + upper face from emotion) */\r\n blendshapes: Float32Array;\r\n /** Original LAM blendshapes (52) */\r\n lamBlendshapes: Float32Array;\r\n /** Emotion-driven upper face blendshapes (11) */\r\n emotionBlendshapes: UpperFaceBlendshapes;\r\n /** Raw emotion frame data */\r\n emotion: EmotionFrame | null;\r\n /** AudioContext timestamp for this frame */\r\n timestamp: number;\r\n}\r\n\r\n/**\r\n * Events emitted by FullFacePipeline\r\n */\r\nexport interface FullFacePipelineEvents {\r\n /** New merged frame ready for display */\r\n full_frame_ready: FullFaceFrame;\r\n /** Raw LAM frame ready (for debugging/monitoring) */\r\n lam_frame_ready: Float32Array;\r\n /** Emotion frame ready (for debugging/monitoring) */\r\n emotion_frame_ready: EmotionFrame;\r\n /** Playback has completed */\r\n playback_complete: void;\r\n /** First frame ready, playback starting */\r\n playback_start: number;\r\n /** Error occurred */\r\n error: Error;\r\n /** Index signature for EventEmitter compatibility */\r\n [key: string]: unknown;\r\n}\r\n\r\n/**\r\n * FullFacePipeline - Unified LAM + Emotion animation pipeline\r\n *\r\n * Audio-first design matching SyncedAudioPipeline:\r\n * - Audio is scheduled immediately (never waits for LAM)\r\n * - LAM runs in background (fire-and-forget)\r\n * - Emotion from setEmotionLabel() or prosody fallback\r\n */\r\nexport class FullFacePipeline extends EventEmitter<FullFacePipelineEvents> {\r\n private scheduler: AudioScheduler;\r\n private coalescer: AudioChunkCoalescer;\r\n private lamPipeline: LAMPipeline;\r\n private emotionMapper: EmotionToBlendshapeMapper;\r\n private energyAnalyzer: AudioEnergyAnalyzer;\r\n\r\n private playbackStarted = false;\r\n private monitorInterval: ReturnType<typeof setInterval> | null = null;\r\n private frameAnimationId: number | null = null;\r\n\r\n // Emotion state\r\n private lastEmotionFrame: EmotionFrame | null = null;\r\n private currentAudioEnergy = 0;\r\n\r\n // Stale frame detection\r\n private lastNewFrameTime = 0;\r\n private lastKnownLamFrame: Float32Array | null = null;\r\n private staleWarningEmitted = false;\r\n private static readonly STALE_FRAME_THRESHOLD_MS = 3_000;\r\n\r\n // Blend factors\r\n private emotionBlendFactor: number;\r\n private lamBlendFactor: number;\r\n\r\n constructor(private readonly options: FullFacePipelineOptions) {\r\n super();\r\n\r\n const sampleRate = options.sampleRate ?? 16000;\r\n this.emotionBlendFactor = options.emotionBlendFactor ?? 0.8;\r\n this.lamBlendFactor = options.lamBlendFactor ?? 0.2;\r\n\r\n // Auto-detect audio delay from model + backend (same logic as SyncedAudioPipeline)\r\n const autoDelay = options.lam.modelId === 'wav2arkit_cpu' ? 750\r\n : options.lam.backend === 'wasm' ? 350\r\n : 50;\r\n const audioDelayMs = options.audioDelayMs ?? autoDelay;\r\n\r\n this.scheduler = new AudioScheduler({\r\n sampleRate,\r\n initialLookaheadSec: audioDelayMs / 1000,\r\n });\r\n this.coalescer = new AudioChunkCoalescer({\r\n sampleRate,\r\n targetDurationMs: options.chunkTargetMs ?? 200,\r\n });\r\n this.lamPipeline = new LAMPipeline({\r\n sampleRate,\r\n onError: (error) => {\r\n logger.error('LAM inference error', { message: error.message, stack: error.stack });\r\n this.emit('error', error);\r\n },\r\n });\r\n this.emotionMapper = new EmotionToBlendshapeMapper({\r\n smoothingFactor: 0.15,\r\n confidenceThreshold: 0.3,\r\n intensity: 1.0,\r\n energyModulation: true,\r\n });\r\n this.energyAnalyzer = new AudioEnergyAnalyzer();\r\n }\r\n\r\n /**\r\n * Initialize the pipeline\r\n */\r\n async initialize(): Promise<void> {\r\n await this.scheduler.initialize();\r\n }\r\n\r\n /**\r\n * Set emotion label from backend (e.g., LLM response emotion).\r\n *\r\n * Converts a natural language emotion label into an EmotionFrame\r\n * that drives upper face blendshapes for the duration of the utterance.\r\n *\r\n * Supported labels: happy, excited, joyful, sad, melancholic, angry,\r\n * frustrated, neutral, etc.\r\n *\r\n * @param label - Emotion label string (case-insensitive)\r\n */\r\n setEmotionLabel(label: string): void {\r\n const normalized = label.toLowerCase();\r\n const mapped = EMOTION_LABEL_MAP[normalized] ?? 'neutral';\r\n\r\n // Build synthetic probability distribution\r\n const probabilities: Record<Emotion2VecLabel, number> = {\r\n neutral: 0.1,\r\n happy: 0.1,\r\n angry: 0.1,\r\n sad: 0.1,\r\n };\r\n probabilities[mapped] = 0.7;\r\n\r\n const frame: EmotionFrame = {\r\n emotion: mapped,\r\n confidence: 0.7,\r\n probabilities,\r\n };\r\n\r\n this.lastEmotionFrame = frame;\r\n logger.info('Emotion label set', { label, mapped });\r\n }\r\n\r\n /**\r\n * Clear any set emotion label.\r\n * Falls back to prosody-only upper face animation.\r\n */\r\n clearEmotionLabel(): void {\r\n this.lastEmotionFrame = null;\r\n }\r\n\r\n /**\r\n * Start a new playback session\r\n *\r\n * Resets all state and prepares for incoming audio chunks.\r\n * Audio will be scheduled immediately as chunks arrive (no buffering).\r\n */\r\n start(): void {\r\n // Stop any active session first (prevents duplicate frame loops/monitors)\r\n this.stopMonitoring();\r\n\r\n this.scheduler.reset();\r\n this.coalescer.reset();\r\n this.lamPipeline.reset();\r\n this.playbackStarted = false;\r\n\r\n // Reset emotion state\r\n this.lastEmotionFrame = null;\r\n this.currentAudioEnergy = 0;\r\n this.emotionMapper.reset();\r\n this.energyAnalyzer.reset();\r\n\r\n // Reset stale frame tracking\r\n this.lastNewFrameTime = 0;\r\n this.lastKnownLamFrame = null;\r\n this.staleWarningEmitted = false;\r\n\r\n // Eagerly warm up AudioContext so audio hardware is ready\r\n this.scheduler.warmup();\r\n\r\n this.startFrameLoop();\r\n this.startMonitoring();\r\n }\r\n\r\n /**\r\n * Receive audio chunk from network\r\n *\r\n * Audio-first design: schedules audio immediately, LAM runs in background.\r\n * This prevents LAM inference (50-300ms) from blocking audio scheduling.\r\n *\r\n * @param chunk - Uint8Array containing Int16 PCM audio\r\n */\r\n async onAudioChunk(chunk: Uint8Array): Promise<void> {\r\n // Coalesce small chunks into optimal buffers\r\n const combined = this.coalescer.add(chunk);\r\n if (!combined) {\r\n return; // Not enough data yet\r\n }\r\n\r\n // Convert PCM16 bytes to Float32 samples\r\n const float32 = pcm16ToFloat32(combined);\r\n\r\n // Schedule audio immediately — never wait for LAM\r\n const scheduleTime = await this.scheduler.schedule(float32);\r\n\r\n // Emit playback_start on first scheduled chunk\r\n if (!this.playbackStarted) {\r\n this.playbackStarted = true;\r\n this.emit('playback_start', scheduleTime);\r\n }\r\n\r\n // Compute audio energy for prosody fallback\r\n const { energy } = this.energyAnalyzer.process(float32);\r\n this.currentAudioEnergy = energy;\r\n\r\n // LAM runs in background — never blocks audio scheduling\r\n this.lamPipeline.push(float32, scheduleTime, this.options.lam).catch(err => {\r\n this.emit('error', err);\r\n });\r\n }\r\n\r\n /**\r\n * Get emotion frame for current animation.\r\n *\r\n * Priority:\r\n * 1. Explicit emotion label from setEmotionLabel()\r\n * 2. Prosody fallback: subtle brow movement from audio energy\r\n */\r\n private getEmotionFrame(): { frame: EmotionFrame | null; energy: number } {\r\n if (this.lastEmotionFrame) {\r\n return { frame: this.lastEmotionFrame, energy: this.currentAudioEnergy };\r\n }\r\n\r\n // No explicit emotion label — return null so mergeBlendshapes() uses\r\n // the direct prosody path (brow movement from audio energy) instead of\r\n // routing through EmotionToBlendshapeMapper which maps neutral → zero.\r\n return { frame: null, energy: this.currentAudioEnergy };\r\n }\r\n\r\n /**\r\n * Merge LAM blendshapes with emotion upper face blendshapes\r\n */\r\n mergeBlendshapes(\r\n lamFrame: Float32Array,\r\n emotionFrame: EmotionFrame | null,\r\n audioEnergy?: number,\r\n ): { merged: Float32Array; emotionBlendshapes: UpperFaceBlendshapes } {\r\n const merged = new Float32Array(52);\r\n let emotionBlendshapes: UpperFaceBlendshapes;\r\n\r\n if (emotionFrame) {\r\n // Get emotion-driven blendshapes with energy modulation\r\n this.emotionMapper.mapFrame(emotionFrame, audioEnergy);\r\n this.emotionMapper.update(33); // ~30fps\r\n emotionBlendshapes = this.emotionMapper.getCurrentBlendshapes();\r\n } else {\r\n // No emotion — zero upper face from this pipeline.\r\n // Brow animation is handled by ProceduralLifeLayer (simplex noise + speech modulation).\r\n emotionBlendshapes = {} as UpperFaceBlendshapes;\r\n for (const name of UPPER_FACE_BLENDSHAPES) {\r\n emotionBlendshapes[name] = 0;\r\n }\r\n }\r\n\r\n // Merge: lower face 100% LAM, upper face emotion + LAM fallback\r\n for (let i = 0; i < 52; i++) {\r\n const name = LAM_BLENDSHAPES[i];\r\n\r\n if (UPPER_FACE_SET.has(name)) {\r\n // Upper face: emotion * factor + LAM * factor\r\n const emotionValue = emotionBlendshapes[name as keyof UpperFaceBlendshapes] ?? 0;\r\n const lamValue = lamFrame[i];\r\n merged[i] = emotionValue * this.emotionBlendFactor + lamValue * this.lamBlendFactor;\r\n } else {\r\n // Lower face: 100% LAM\r\n merged[i] = lamFrame[i];\r\n }\r\n }\r\n\r\n return { merged, emotionBlendshapes };\r\n }\r\n\r\n /**\r\n * Start frame animation loop\r\n */\r\n private startFrameLoop(): void {\r\n const updateFrame = () => {\r\n const currentTime = this.scheduler.getCurrentTime();\r\n const lamFrame = this.lamPipeline.getFrameForTime(currentTime, this.options.lam);\r\n\r\n if (lamFrame) {\r\n // Track stale frame detection\r\n if (lamFrame !== this.lastKnownLamFrame) {\r\n this.lastNewFrameTime = performance.now();\r\n this.lastKnownLamFrame = lamFrame;\r\n this.staleWarningEmitted = false;\r\n }\r\n\r\n // Get emotion frame (explicit label or prosody fallback)\r\n const { frame: emotionFrame, energy } = this.getEmotionFrame();\r\n\r\n // Merge LAM + emotion\r\n const { merged, emotionBlendshapes } = this.mergeBlendshapes(lamFrame, emotionFrame, energy);\r\n\r\n // Emit merged frame\r\n const fullFrame: FullFaceFrame = {\r\n blendshapes: merged,\r\n lamBlendshapes: lamFrame,\r\n emotionBlendshapes,\r\n emotion: emotionFrame,\r\n timestamp: currentTime,\r\n };\r\n\r\n this.emit('full_frame_ready', fullFrame);\r\n this.emit('lam_frame_ready', lamFrame);\r\n\r\n if (emotionFrame) {\r\n this.emit('emotion_frame_ready', emotionFrame);\r\n }\r\n } else if (this.playbackStarted && !this.lastKnownLamFrame) {\r\n // Pre-speech window: LAM hasn't produced any frames yet (~first 1s).\r\n // Emit subtle prosody-only animation to prevent dead face on startup.\r\n const { frame: emotionFrame, energy } = this.getEmotionFrame();\r\n if (emotionFrame && energy > 0.05) {\r\n const startupFrame = new Float32Array(52);\r\n const { merged, emotionBlendshapes } = this.mergeBlendshapes(startupFrame, emotionFrame, energy);\r\n this.emit('full_frame_ready', {\r\n blendshapes: merged,\r\n lamBlendshapes: startupFrame,\r\n emotionBlendshapes,\r\n emotion: emotionFrame,\r\n timestamp: currentTime,\r\n });\r\n }\r\n }\r\n\r\n // Stale frame detection: warn if LAM hasn't produced new frames during playback\r\n if (\r\n this.playbackStarted &&\r\n this.lastNewFrameTime > 0 &&\r\n !this.staleWarningEmitted &&\r\n performance.now() - this.lastNewFrameTime > FullFacePipeline.STALE_FRAME_THRESHOLD_MS\r\n ) {\r\n this.staleWarningEmitted = true;\r\n logger.warn('LAM appears stalled — no new frames for 3+ seconds during playback', {\r\n staleDurationMs: Math.round(performance.now() - this.lastNewFrameTime),\r\n queuedFrames: this.lamPipeline.queuedFrameCount,\r\n });\r\n }\r\n\r\n this.frameAnimationId = requestAnimationFrame(updateFrame);\r\n };\r\n\r\n this.frameAnimationId = requestAnimationFrame(updateFrame);\r\n }\r\n\r\n /**\r\n * End of audio stream\r\n */\r\n async end(): Promise<void> {\r\n // Flush remaining coalesced data\r\n const remaining = this.coalescer.flush();\r\n if (remaining) {\r\n const chunk = new Uint8Array(remaining);\r\n await this.onAudioChunk(chunk);\r\n }\r\n\r\n // Flush remaining LAM buffer\r\n await this.lamPipeline.flush(this.options.lam);\r\n }\r\n\r\n /**\r\n * Stop playback immediately with smooth fade-out\r\n */\r\n async stop(fadeOutMs: number = 50): Promise<void> {\r\n this.stopMonitoring();\r\n await this.scheduler.cancelAll(fadeOutMs);\r\n\r\n this.coalescer.reset();\r\n this.lamPipeline.reset();\r\n this.playbackStarted = false;\r\n\r\n // Clear emotion state\r\n this.lastEmotionFrame = null;\r\n this.currentAudioEnergy = 0;\r\n this.emotionMapper.reset();\r\n this.energyAnalyzer.reset();\r\n\r\n // Reset stale frame tracking\r\n this.lastNewFrameTime = 0;\r\n this.lastKnownLamFrame = null;\r\n this.staleWarningEmitted = false;\r\n\r\n this.emit('playback_complete', undefined as any);\r\n }\r\n\r\n /**\r\n * Start monitoring for playback completion\r\n */\r\n private startMonitoring(): void {\r\n if (this.monitorInterval) {\r\n clearInterval(this.monitorInterval);\r\n }\r\n\r\n this.monitorInterval = setInterval(() => {\r\n if (this.scheduler.isComplete() && this.lamPipeline.queuedFrameCount === 0) {\r\n this.emit('playback_complete', undefined as any);\r\n this.stopMonitoring();\r\n }\r\n }, 100);\r\n }\r\n\r\n /**\r\n * Stop monitoring\r\n */\r\n private stopMonitoring(): void {\r\n if (this.monitorInterval) {\r\n clearInterval(this.monitorInterval);\r\n this.monitorInterval = null;\r\n }\r\n\r\n if (this.frameAnimationId) {\r\n cancelAnimationFrame(this.frameAnimationId);\r\n this.frameAnimationId = null;\r\n }\r\n }\r\n\r\n /**\r\n * Get current pipeline state (for debugging/monitoring)\r\n */\r\n getState() {\r\n return {\r\n playbackStarted: this.playbackStarted,\r\n coalescerFill: this.coalescer.fillLevel,\r\n lamFill: this.lamPipeline.fillLevel,\r\n queuedLAMFrames: this.lamPipeline.queuedFrameCount,\r\n emotionLabel: this.lastEmotionFrame?.emotion ?? null,\r\n currentAudioEnergy: this.currentAudioEnergy,\r\n currentTime: this.scheduler.getCurrentTime(),\r\n playbackEndTime: this.scheduler.getPlaybackEndTime(),\r\n };\r\n }\r\n\r\n /**\r\n * Check if an explicit emotion label is currently set\r\n */\r\n get hasEmotionLabel(): boolean {\r\n return this.lastEmotionFrame !== null;\r\n }\r\n\r\n /**\r\n * Cleanup resources\r\n */\r\n dispose(): void {\r\n this.stopMonitoring();\r\n this.scheduler.dispose();\r\n this.coalescer.reset();\r\n this.lamPipeline.reset();\r\n this.lastEmotionFrame = null;\r\n this.currentAudioEnergy = 0;\r\n }\r\n}\r\n","/**\n * Kaldi-compatible filterbank (fbank) feature extraction\n *\n * Pure TypeScript implementation matching kaldi-native-fbank parameters\n * used by SenseVoice. No external dependencies.\n *\n * Pipeline: audio → framing → windowing → FFT → power spectrum → mel filterbank → log\n *\n * @module inference/kaldiFbank\n */\n\n// ─── FFT ────────────────────────────────────────────────────────────────────\n\n/**\n * In-place Radix-2 Cooley-Tukey FFT\n * @param re Real parts (modified in-place)\n * @param im Imaginary parts (modified in-place)\n */\nfunction fft(re: Float64Array, im: Float64Array): void {\n const n = re.length;\n\n // Bit-reversal permutation\n for (let i = 1, j = 0; i < n; i++) {\n let bit = n >> 1;\n while (j & bit) {\n j ^= bit;\n bit >>= 1;\n }\n j ^= bit;\n if (i < j) {\n let tmp = re[i]; re[i] = re[j]; re[j] = tmp;\n tmp = im[i]; im[i] = im[j]; im[j] = tmp;\n }\n }\n\n // Butterfly passes\n for (let len = 2; len <= n; len *= 2) {\n const halfLen = len / 2;\n const angle = -2 * Math.PI / len;\n const wRe = Math.cos(angle);\n const wIm = Math.sin(angle);\n\n for (let i = 0; i < n; i += len) {\n let curRe = 1;\n let curIm = 0;\n for (let j = 0; j < halfLen; j++) {\n const a = i + j;\n const b = a + halfLen;\n const tRe = curRe * re[b] - curIm * im[b];\n const tIm = curRe * im[b] + curIm * re[b];\n re[b] = re[a] - tRe;\n im[b] = im[a] - tIm;\n re[a] += tRe;\n im[a] += tIm;\n const nextRe = curRe * wRe - curIm * wIm;\n curIm = curRe * wIm + curIm * wRe;\n curRe = nextRe;\n }\n }\n }\n}\n\n// ─── Mel Scale ──────────────────────────────────────────────────────────────\n\n/** HTK mel scale (same as Kaldi default) */\nfunction htkMel(freq: number): number {\n return 1127.0 * Math.log(1.0 + freq / 700.0);\n}\n\nfunction htkMelInverse(mel: number): number {\n return 700.0 * (Math.exp(mel / 1127.0) - 1.0);\n}\n\n// ─── Mel Filterbank ─────────────────────────────────────────────────────────\n\n/**\n * Build triangular mel filterbank matrix\n * @returns Array of numBins filters, each is a sparse {startBin, weights} pair\n */\ninterface MelFilter {\n startBin: number;\n weights: Float32Array;\n}\n\nfunction buildMelFilterbank(\n numBins: number,\n fftSize: number,\n sampleRate: number,\n lowFreq: number,\n highFreq: number,\n): MelFilter[] {\n const numFftBins = fftSize / 2 + 1;\n const lowMel = htkMel(lowFreq);\n const highMel = htkMel(highFreq);\n\n // numBins + 2 equally spaced points in mel space\n const melPoints = new Float64Array(numBins + 2);\n for (let i = 0; i < numBins + 2; i++) {\n melPoints[i] = lowMel + (highMel - lowMel) * i / (numBins + 1);\n }\n\n // Convert mel points to FFT bin indices (float, not rounded)\n const binFreqs = new Float64Array(numBins + 2);\n for (let i = 0; i < numBins + 2; i++) {\n binFreqs[i] = htkMelInverse(melPoints[i]) * fftSize / sampleRate;\n }\n\n const filters: MelFilter[] = [];\n\n for (let m = 0; m < numBins; m++) {\n const left = binFreqs[m];\n const center = binFreqs[m + 1];\n const right = binFreqs[m + 2];\n\n const startBin = Math.max(0, Math.ceil(left));\n const endBin = Math.min(numFftBins - 1, Math.floor(right));\n\n const weights = new Float32Array(endBin - startBin + 1);\n for (let k = startBin; k <= endBin; k++) {\n if (k <= center) {\n weights[k - startBin] = (center - left) > 0 ? (k - left) / (center - left) : 0;\n } else {\n weights[k - startBin] = (right - center) > 0 ? (right - k) / (right - center) : 0;\n }\n }\n\n filters.push({ startBin, weights });\n }\n\n return filters;\n}\n\n// ─── Hamming Window ─────────────────────────────────────────────────────────\n\nfunction createHammingWindow(length: number): Float32Array {\n const window = new Float32Array(length);\n for (let i = 0; i < length; i++) {\n window[i] = 0.54 - 0.46 * Math.cos(2 * Math.PI * i / (length - 1));\n }\n return window;\n}\n\n// ─── Options ────────────────────────────────────────────────────────────────\n\nexport interface KaldiFbankOptions {\n /** Frame length in ms (default: 25) */\n frameLengthMs?: number;\n /** Frame shift in ms (default: 10) */\n frameShiftMs?: number;\n /** Low frequency cutoff in Hz (default: 20) */\n lowFreq?: number;\n /** High frequency cutoff in Hz (default: sampleRate / 2) */\n highFreq?: number;\n /** Dither amount (default: 0 for deterministic output) */\n dither?: number;\n /** Preemphasis coefficient (default: 0.97) */\n preemphasis?: number;\n}\n\n// ─── Main Fbank ─────────────────────────────────────────────────────────────\n\n/**\n * Compute Kaldi-compatible log mel filterbank features\n *\n * @param audio Raw audio samples (float32, [-1, 1] range)\n * @param sampleRate Sample rate in Hz (must be 16000 for SenseVoice)\n * @param numMelBins Number of mel bins (80 for SenseVoice)\n * @param opts Optional parameters\n * @returns Flattened Float32Array of shape [numFrames, numMelBins]\n */\nexport function computeKaldiFbank(\n audio: Float32Array,\n sampleRate: number,\n numMelBins: number,\n opts?: KaldiFbankOptions,\n): Float32Array {\n const frameLengthMs = opts?.frameLengthMs ?? 25;\n const frameShiftMs = opts?.frameShiftMs ?? 10;\n const lowFreq = opts?.lowFreq ?? 20;\n const highFreq = opts?.highFreq ?? (sampleRate / 2);\n const dither = opts?.dither ?? 0;\n const preemphasis = opts?.preemphasis ?? 0.97;\n\n const frameLengthSamples = Math.round(sampleRate * frameLengthMs / 1000);\n const frameShiftSamples = Math.round(sampleRate * frameShiftMs / 1000);\n\n // Kaldi signal scaling: float [-1,1] → int16 range\n const scaled = new Float32Array(audio.length);\n for (let i = 0; i < audio.length; i++) {\n scaled[i] = audio[i] * 32768;\n }\n\n // Optional dithering\n if (dither > 0) {\n for (let i = 0; i < scaled.length; i++) {\n // Box-Muller for Gaussian noise\n const u1 = Math.random();\n const u2 = Math.random();\n scaled[i] += dither * Math.sqrt(-2 * Math.log(u1 + 1e-10)) * Math.cos(2 * Math.PI * u2);\n }\n }\n\n // Number of frames (snip_edges=true: only complete frames)\n const numFrames = Math.max(0, Math.floor((scaled.length - frameLengthSamples) / frameShiftSamples) + 1);\n if (numFrames === 0) {\n return new Float32Array(0);\n }\n\n // FFT size: next power of 2\n let fftSize = 1;\n while (fftSize < frameLengthSamples) fftSize *= 2;\n\n const numFftBins = fftSize / 2 + 1;\n\n // Pre-compute window and filterbank\n const window = createHammingWindow(frameLengthSamples);\n const filters = buildMelFilterbank(numMelBins, fftSize, sampleRate, lowFreq, highFreq);\n\n // Allocate output\n const output = new Float32Array(numFrames * numMelBins);\n\n // FFT buffers (reused per frame)\n const fftRe = new Float64Array(fftSize);\n const fftIm = new Float64Array(fftSize);\n\n for (let f = 0; f < numFrames; f++) {\n const offset = f * frameShiftSamples;\n\n // Clear FFT buffers\n fftRe.fill(0);\n fftIm.fill(0);\n\n // Extract frame with preemphasis and windowing\n for (let i = 0; i < frameLengthSamples; i++) {\n let sample = scaled[offset + i];\n // Preemphasis: y[n] = x[n] - coeff * x[n-1]\n if (preemphasis > 0 && i > 0) {\n sample -= preemphasis * scaled[offset + i - 1];\n } else if (preemphasis > 0 && i === 0 && offset > 0) {\n sample -= preemphasis * scaled[offset - 1];\n }\n // Remove DC offset per frame\n fftRe[i] = sample * window[i];\n }\n\n // FFT\n fft(fftRe, fftIm);\n\n // Power spectrum: |X(k)|^2\n // Apply mel filterbank and take log\n const outOffset = f * numMelBins;\n for (let m = 0; m < numMelBins; m++) {\n const filter = filters[m];\n let energy = 0;\n for (let k = 0; k < filter.weights.length; k++) {\n const bin = filter.startBin + k;\n if (bin < numFftBins) {\n const powerSpec = fftRe[bin] * fftRe[bin] + fftIm[bin] * fftIm[bin];\n energy += filter.weights[k] * powerSpec;\n }\n }\n output[outOffset + m] = Math.log(Math.max(energy, 1e-10));\n }\n }\n\n return output;\n}\n\n// ─── LFR (Low Frame Rate) Stacking ─────────────────────────────────────────\n\n/**\n * Apply Low Frame Rate stacking for SenseVoice\n *\n * Concatenates lfrM consecutive frames with stride lfrN.\n * Left-pads with copies of first frame, right-pads last group.\n *\n * @param features Flattened [numFrames, featureDim]\n * @param featureDim Feature dimension per frame (e.g., 80)\n * @param lfrM Number of frames to stack (default: 7)\n * @param lfrN Stride (default: 6)\n * @returns Flattened [numOutputFrames, featureDim * lfrM]\n */\nexport function applyLFR(\n features: Float32Array,\n featureDim: number,\n lfrM: number = 7,\n lfrN: number = 6,\n): Float32Array {\n const numFrames = features.length / featureDim;\n if (numFrames === 0) return new Float32Array(0);\n\n const leftPad = Math.floor((lfrM - 1) / 2); // 3 for lfrM=7\n const paddedLen = numFrames + leftPad;\n const numOutputFrames = Math.ceil(paddedLen / lfrN);\n const outputDim = featureDim * lfrM;\n\n const output = new Float32Array(numOutputFrames * outputDim);\n\n for (let i = 0; i < numOutputFrames; i++) {\n const startFrame = i * lfrN - leftPad;\n\n for (let j = 0; j < lfrM; j++) {\n let srcFrame = startFrame + j;\n // Clamp to valid range\n if (srcFrame < 0) srcFrame = 0;\n if (srcFrame >= numFrames) srcFrame = numFrames - 1;\n\n const srcOffset = srcFrame * featureDim;\n const dstOffset = i * outputDim + j * featureDim;\n for (let k = 0; k < featureDim; k++) {\n output[dstOffset + k] = features[srcOffset + k];\n }\n }\n }\n\n return output;\n}\n\n// ─── CMVN (Cepstral Mean-Variance Normalization) ───────────────────────────\n\n/**\n * Apply CMVN normalization in-place\n *\n * Formula: normalized[i] = (features[i] + negMean[i % dim]) * invStddev[i % dim]\n *\n * @param features Flattened feature array (modified in-place)\n * @param dim Feature dimension (560 for SenseVoice after LFR)\n * @param negMean Negative mean vector (dim-dimensional)\n * @param invStddev Inverse standard deviation vector (dim-dimensional)\n * @returns The same features array (for chaining)\n */\nexport function applyCMVN(\n features: Float32Array,\n dim: number,\n negMean: Float32Array,\n invStddev: Float32Array,\n): Float32Array {\n for (let i = 0; i < features.length; i++) {\n const d = i % dim;\n features[i] = (features[i] + negMean[d]) * invStddev[d];\n }\n return features;\n}\n\n// ─── CMVN Parsing ───────────────────────────────────────────────────────────\n\n/**\n * Parse CMVN vectors from comma-separated strings (stored in ONNX metadata)\n *\n * The sherpa-onnx SenseVoice export stores neg_mean and inv_stddev\n * as comma-separated float strings in the model's metadata.\n */\nexport function parseCMVNFromMetadata(\n negMeanStr: string,\n invStddevStr: string,\n): { negMean: Float32Array; invStddev: Float32Array } {\n const negMean = new Float32Array(\n negMeanStr.split(',').map(s => parseFloat(s.trim()))\n );\n const invStddev = new Float32Array(\n invStddevStr.split(',').map(s => parseFloat(s.trim()))\n );\n return { negMean, invStddev };\n}\n","/**\n * CTC greedy decoder for SenseVoice\n *\n * Decodes CTC logits into text with structured token parsing\n * for language, emotion, and audio event detection.\n *\n * @module inference/ctcDecoder\n */\n\nexport interface CTCDecodeResult {\n /** Decoded text (speech content only) */\n text: string;\n /** Detected language (e.g., 'zh', 'en', 'ja', 'ko', 'yue') */\n language?: string;\n /** Detected emotion (e.g., 'HAPPY', 'SAD', 'ANGRY', 'NEUTRAL') */\n emotion?: string;\n /** Detected audio event (e.g., 'Speech', 'BGM', 'Laughter') */\n event?: string;\n}\n\n/** SenseVoice language ID → string mapping */\nconst LANGUAGE_IDS: Record<number, string> = {\n 0: 'auto',\n 3: 'zh',\n 4: 'en',\n 7: 'yue',\n 11: 'ja',\n 12: 'ko',\n 13: 'nospeech',\n};\n\n/** SenseVoice text normalization ID → string mapping */\nconst TEXT_NORM_IDS: Record<number, string> = {\n 14: 'with_itn',\n 15: 'without_itn',\n};\n\n/** Resolve language string to SenseVoice language ID */\nexport function resolveLanguageId(language: string): number {\n const map: Record<string, number> = {\n auto: 0,\n zh: 3,\n en: 4,\n yue: 7,\n ja: 11,\n ko: 12,\n };\n return map[language] ?? 0;\n}\n\n/** Resolve text norm string to SenseVoice text norm ID */\nexport function resolveTextNormId(textNorm: string): number {\n return textNorm === 'without_itn' ? 15 : 14;\n}\n\n/**\n * Parse tokens.txt into a token ID → string map\n *\n * Format: each line is \"token_string token_id\"\n * e.g., \"<unk> 0\", \"▁the 3\", \"s 4\"\n */\nexport function parseTokensFile(content: string): Map<number, string> {\n const map = new Map<number, string>();\n const lines = content.split('\\n');\n for (const line of lines) {\n const trimmed = line.trim();\n if (!trimmed) continue;\n // Find the last space — token string may contain spaces\n const lastSpace = trimmed.lastIndexOf(' ');\n if (lastSpace === -1) continue;\n const token = trimmed.substring(0, lastSpace);\n const id = parseInt(trimmed.substring(lastSpace + 1), 10);\n if (!isNaN(id)) {\n map.set(id, token);\n }\n }\n return map;\n}\n\n/**\n * SenseVoice structured token pattern matching\n * Structured tokens appear in the first few decoded tokens as <|tag|>\n */\nfunction parseStructuredToken(token: string): { type: string; value: string } | null {\n const match = token.match(/^<\\|(.+)\\|>$/);\n if (!match) return null;\n\n const value = match[1];\n\n // Language tokens\n if (value === 'zh' || value === 'en' || value === 'ja' || value === 'ko' || value === 'yue' || value === 'nospeech') {\n return { type: 'language', value };\n }\n\n // Emotion tokens\n const emotions = ['HAPPY', 'SAD', 'ANGRY', 'NEUTRAL', 'FEARFUL', 'DISGUSTED', 'SURPRISED', 'EMO_UNKNOWN'];\n if (emotions.includes(value)) {\n return { type: 'emotion', value };\n }\n\n // Audio event tokens\n const events = ['Speech', 'BGM', 'Applause', 'Laughter', 'Crying', 'Coughing', 'Sneezing', 'EVENT_UNKNOWN'];\n if (events.includes(value)) {\n return { type: 'event', value };\n }\n\n // ITN tokens\n if (value === 'withitn' || value === 'woitn' || value === 'with_itn' || value === 'without_itn') {\n return { type: 'textnorm', value };\n }\n\n return null;\n}\n\n/**\n * CTC greedy decode\n *\n * @param logits Raw logits from model output, flattened [seqLen, vocabSize]\n * @param seqLen Sequence length (time steps)\n * @param vocabSize Vocabulary size\n * @param tokenMap Token ID → string map from tokens.txt\n * @returns Decoded text and structured metadata\n */\nexport function ctcGreedyDecode(\n logits: Float32Array,\n seqLen: number,\n vocabSize: number,\n tokenMap: Map<number, string>,\n): CTCDecodeResult {\n // Step 1: Argmax per time step\n const tokenIds: number[] = [];\n for (let t = 0; t < seqLen; t++) {\n const offset = t * vocabSize;\n let maxIdx = 0;\n let maxVal = logits[offset];\n for (let v = 1; v < vocabSize; v++) {\n if (logits[offset + v] > maxVal) {\n maxVal = logits[offset + v];\n maxIdx = v;\n }\n }\n tokenIds.push(maxIdx);\n }\n\n // Step 2: Collapse consecutive duplicates\n const collapsed: number[] = [];\n let prev = -1;\n for (const id of tokenIds) {\n if (id !== prev) {\n collapsed.push(id);\n prev = id;\n }\n }\n\n // Step 3: Remove blank tokens (ID 0) and special tokens (<s>=1, </s>=2)\n const filtered = collapsed.filter(id => id !== 0 && id !== 1 && id !== 2);\n\n // Step 4: Convert to token strings and parse structured tokens\n let language: string | undefined;\n let emotion: string | undefined;\n let event: string | undefined;\n const textTokens: string[] = [];\n\n for (const id of filtered) {\n const token = tokenMap.get(id);\n if (!token) continue;\n\n const structured = parseStructuredToken(token);\n if (structured) {\n if (structured.type === 'language') language = structured.value;\n else if (structured.type === 'emotion') emotion = structured.value;\n else if (structured.type === 'event') event = structured.value;\n // Skip textnorm tokens — not useful in output\n } else {\n textTokens.push(token);\n }\n }\n\n // Step 5: Join tokens, handle SentencePiece boundary marker\n let text = textTokens.join('');\n // Replace SentencePiece word boundary (▁ = U+2581) with space\n text = text.replace(/\\u2581/g, ' ').trim();\n\n return { text, language, emotion, event };\n}\n","/**\r\n * SenseVoice automatic speech recognition using ONNX Runtime Web\r\n *\r\n * Non-autoregressive CTC-based ASR that is 5x faster than Whisper-Small.\r\n * Runs entirely in browser via WebGPU or WASM. No transformers.js dependency.\r\n *\r\n * Uses the sherpa-onnx SenseVoice export (model.int8.onnx, 239MB int8 quantized).\r\n * Also provides emotion detection, language identification, and audio event detection\r\n * from the same forward pass.\r\n *\r\n * @category Inference\r\n *\r\n * @example Basic usage\r\n * ```typescript\r\n * import { SenseVoiceInference } from '@omote/core';\r\n *\r\n * const asr = new SenseVoiceInference({\r\n * modelUrl: '/models/sensevoice/model.int8.onnx',\r\n * tokensUrl: '/models/sensevoice/tokens.txt',\r\n * });\r\n * await asr.load();\r\n *\r\n * const { text, emotion, language } = await asr.transcribe(audioSamples);\r\n * console.log(text); // \"Hello world\"\r\n * console.log(emotion); // \"NEUTRAL\"\r\n * console.log(language); // \"en\"\r\n * ```\r\n *\r\n * @module inference/SenseVoiceInference\r\n */\r\n\r\nimport type { InferenceSession } from 'onnxruntime-common';\r\nimport { fetchWithCache, getModelCache, formatBytes } from '../cache/ModelCache';\r\nimport { createLogger } from '../logging';\r\nimport { getTelemetry } from '../telemetry';\r\nimport {\r\n getOnnxRuntimeForPreference,\r\n getSessionOptions,\r\n type RuntimeBackend,\r\n type OrtModule,\r\n} from './onnxLoader';\r\nimport { BackendPreference, isIOS } from '../utils/runtime';\r\nimport { computeKaldiFbank, applyLFR, applyCMVN, parseCMVNFromMetadata } from './kaldiFbank';\r\nimport { ctcGreedyDecode, parseTokensFile, resolveLanguageId, resolveTextNormId } from './ctcDecoder';\r\nimport type { CTCDecodeResult } from './ctcDecoder';\r\n\r\nconst logger = createLogger('SenseVoice');\r\n\r\n// ─── Config ─────────────────────────────────────────────────────────────────\r\n\r\nexport type SenseVoiceLanguage = 'auto' | 'zh' | 'en' | 'ja' | 'ko' | 'yue';\r\n\r\nexport interface SenseVoiceConfig {\r\n /** Path or URL to model.int8.onnx (239MB) */\r\n modelUrl: string;\r\n /** Path or URL to tokens.txt vocabulary file (default: sibling of modelUrl) */\r\n tokensUrl?: string;\r\n /** Language hint (default: 'auto' for auto-detection) */\r\n language?: SenseVoiceLanguage;\r\n /** Text normalization: 'with_itn' applies inverse text normalization (default: 'with_itn') */\r\n textNorm?: 'with_itn' | 'without_itn';\r\n /** Preferred backend (default: 'auto') */\r\n backend?: BackendPreference;\r\n}\r\n\r\n// ─── Result Types ───────────────────────────────────────────────────────────\r\n\r\nexport interface SenseVoiceResult {\r\n /** Transcribed text */\r\n text: string;\r\n /** Detected language (e.g., 'zh', 'en', 'ja', 'ko', 'yue') */\r\n language?: string;\r\n /** Detected emotion (e.g., 'HAPPY', 'SAD', 'ANGRY', 'NEUTRAL') */\r\n emotion?: string;\r\n /** Detected audio event (e.g., 'Speech', 'BGM', 'Laughter') */\r\n event?: string;\r\n /** Inference time in milliseconds (preprocessing + model + decode) */\r\n inferenceTimeMs: number;\r\n /** Preprocessing time in milliseconds (fbank + LFR + CMVN) */\r\n preprocessTimeMs: number;\r\n}\r\n\r\nexport interface SenseVoiceModelInfo {\r\n backend: RuntimeBackend;\r\n loadTimeMs: number;\r\n inputNames: string[];\r\n outputNames: string[];\r\n vocabSize: number;\r\n}\r\n\r\n// ─── Inference Class ────────────────────────────────────────────────────────\r\n\r\nexport class SenseVoiceInference {\r\n private session: InferenceSession | null = null;\r\n private ort: OrtModule | null = null;\r\n private config: Required<SenseVoiceConfig>;\r\n private _backend: RuntimeBackend = 'wasm';\r\n private isLoading = false;\r\n private inferenceQueue: Promise<void> = Promise.resolve();\r\n\r\n // Session health: set to true if session.run() times out.\r\n // A timed-out session may have a zombie WASM dispatch still running,\r\n // so all future transcribe() calls reject immediately to prevent concurrent access.\r\n private poisoned = false;\r\n private static readonly INFERENCE_TIMEOUT_MS = 10_000; // 10s for SenseVoice (heavier preprocessing)\r\n\r\n // Preprocessing state (loaded once)\r\n private tokenMap: Map<number, string> | null = null;\r\n private negMean: Float32Array | null = null;\r\n private invStddev: Float32Array | null = null;\r\n private languageId: number = 0;\r\n private textNormId: number = 14;\r\n\r\n constructor(config: SenseVoiceConfig) {\r\n // Default tokensUrl to sibling of modelUrl\r\n const modelDir = config.modelUrl.substring(0, config.modelUrl.lastIndexOf('/'));\r\n const tokensUrl = config.tokensUrl ?? `${modelDir}/tokens.txt`;\r\n\r\n this.config = {\r\n modelUrl: config.modelUrl,\r\n tokensUrl,\r\n language: config.language ?? 'auto',\r\n textNorm: config.textNorm ?? 'with_itn',\r\n backend: config.backend ?? 'auto',\r\n };\r\n\r\n this.languageId = resolveLanguageId(this.config.language);\r\n this.textNormId = resolveTextNormId(this.config.textNorm);\r\n }\r\n\r\n get backend(): RuntimeBackend | null {\r\n return this.session ? this._backend : null;\r\n }\r\n\r\n get isLoaded(): boolean {\r\n return this.session !== null;\r\n }\r\n\r\n // ─── Load ───────────────────────────────────────────────────────────────\r\n\r\n async load(onProgress?: (loaded: number, total: number) => void): Promise<SenseVoiceModelInfo> {\r\n if (this.isLoading) {\r\n throw new Error('Model is already loading');\r\n }\r\n if (this.session) {\r\n throw new Error('Model already loaded. Call dispose() first.');\r\n }\r\n\r\n this.isLoading = true;\r\n const startTime = performance.now();\r\n const telemetry = getTelemetry();\r\n const span = telemetry?.startSpan('SenseVoice.load', {\r\n 'model.url': this.config.modelUrl,\r\n 'model.backend_requested': this.config.backend,\r\n });\r\n\r\n try {\r\n // 1. Load ORT runtime\r\n logger.info('Loading ONNX Runtime...', { preference: this.config.backend });\r\n const { ort, backend } = await getOnnxRuntimeForPreference(this.config.backend);\r\n this.ort = ort;\r\n this._backend = backend;\r\n logger.info('ONNX Runtime loaded', { backend: this._backend });\r\n\r\n // 2. Fetch tokens.txt (small, safe on all platforms)\r\n logger.debug('Fetching tokens vocabulary', { tokensUrl: this.config.tokensUrl });\r\n const tokensResponse = await fetch(this.config.tokensUrl);\r\n if (!tokensResponse.ok) {\r\n throw new Error(`Failed to fetch tokens.txt: ${tokensResponse.status} ${tokensResponse.statusText}`);\r\n }\r\n const tokensText = await tokensResponse.text();\r\n this.tokenMap = parseTokensFile(tokensText);\r\n logger.debug('Tokens loaded', { vocabSize: this.tokenMap.size });\r\n\r\n // 3. Create inference session\r\n const sessionOptions = getSessionOptions(this._backend);\r\n\r\n // SenseVoice uses variable-length inputs (progressive transcription sends\r\n // growing audio buffers where numLfrFrames changes each call: 13 → 26 → 40…).\r\n // WebGPU 'all' optimization pre-compiles GPU kernels assuming fixed shapes\r\n // from the first inference, causing index-out-of-bounds on subsequent calls\r\n // with different numLfrFrames. Use 'basic' to preserve dynamic shape support.\r\n // Wav2Vec2 is unaffected (always fixed 16000-sample input → constant shape).\r\n if (this._backend === 'webgpu') {\r\n sessionOptions.graphOptimizationLevel = 'basic';\r\n }\r\n\r\n let isCached = false;\r\n\r\n // iOS: Pass model URL directly to ORT to avoid loading 239MB into JS heap.\r\n // ORT fetches into WASM memory, keeping JS heap at ~2MB.\r\n // Desktop: fetch + cache in IndexedDB for fast reloads.\r\n if (isIOS()) {\r\n logger.info('iOS: passing model URL directly to ORT (low-memory path)', {\r\n modelUrl: this.config.modelUrl,\r\n });\r\n this.session = await this.ort.InferenceSession.create(\r\n this.config.modelUrl, sessionOptions\r\n );\r\n } else {\r\n const cache = getModelCache();\r\n isCached = await cache.has(this.config.modelUrl);\r\n\r\n let modelBuffer: ArrayBuffer;\r\n if (isCached) {\r\n logger.debug('Loading model from cache', { modelUrl: this.config.modelUrl });\r\n modelBuffer = (await cache.get(this.config.modelUrl))!;\r\n onProgress?.(modelBuffer.byteLength, modelBuffer.byteLength);\r\n } else {\r\n logger.debug('Fetching and caching model', { modelUrl: this.config.modelUrl });\r\n modelBuffer = await fetchWithCache(this.config.modelUrl, onProgress);\r\n }\r\n\r\n logger.debug('Creating ONNX session', {\r\n size: formatBytes(modelBuffer.byteLength),\r\n backend: this._backend,\r\n });\r\n\r\n const modelData = new Uint8Array(modelBuffer);\r\n this.session = await this.ort.InferenceSession.create(modelData, sessionOptions);\r\n }\r\n\r\n // 5. Try to read CMVN from model metadata\r\n try {\r\n const metadata = (this.session as any).handler?.metadata;\r\n if (metadata?.neg_mean && metadata?.inv_stddev) {\r\n const cmvn = parseCMVNFromMetadata(metadata.neg_mean, metadata.inv_stddev);\r\n this.negMean = cmvn.negMean;\r\n this.invStddev = cmvn.invStddev;\r\n logger.debug('CMVN loaded from model metadata', { dim: this.negMean.length });\r\n } else {\r\n logger.warn('CMVN not found in model metadata — features will not be normalized');\r\n }\r\n } catch (cmvnErr) {\r\n logger.warn('Failed to read CMVN from model metadata', { error: cmvnErr });\r\n }\r\n\r\n const loadTimeMs = performance.now() - startTime;\r\n\r\n logger.info('SenseVoice model loaded', {\r\n backend: this._backend,\r\n loadTimeMs: Math.round(loadTimeMs),\r\n vocabSize: this.tokenMap.size,\r\n inputs: this.session.inputNames,\r\n outputs: this.session.outputNames,\r\n hasCMVN: this.negMean !== null,\r\n });\r\n\r\n span?.setAttributes({\r\n 'model.backend': this._backend,\r\n 'model.load_time_ms': loadTimeMs,\r\n 'model.cached': !isIOS() && isCached,\r\n 'model.vocab_size': this.tokenMap.size,\r\n });\r\n span?.end();\r\n\r\n telemetry?.recordHistogram('omote.model.load_time', loadTimeMs, {\r\n model: 'sensevoice',\r\n backend: this._backend,\r\n });\r\n\r\n return {\r\n backend: this._backend,\r\n loadTimeMs,\r\n inputNames: [...this.session.inputNames],\r\n outputNames: [...this.session.outputNames],\r\n vocabSize: this.tokenMap.size,\r\n };\r\n } catch (error) {\r\n span?.endWithError(error instanceof Error ? error : new Error(String(error)));\r\n telemetry?.incrementCounter('omote.errors.total', 1, {\r\n model: 'sensevoice',\r\n error_type: 'load_failed',\r\n });\r\n throw error;\r\n } finally {\r\n this.isLoading = false;\r\n }\r\n }\r\n\r\n // ─── Transcribe ─────────────────────────────────────────────────────────\r\n\r\n /**\r\n * Transcribe audio samples to text\r\n *\r\n * @param audioSamples Float32Array of audio samples at 16kHz, [-1, 1] range\r\n * @returns Transcription result with text, emotion, language, and event\r\n */\r\n async transcribe(audioSamples: Float32Array): Promise<SenseVoiceResult> {\r\n if (!this.session || !this.ort || !this.tokenMap) {\r\n throw new Error('Model not loaded. Call load() first.');\r\n }\r\n\r\n if (this.poisoned) {\r\n throw new Error('SenseVoice session timed out — inference unavailable until page reload');\r\n }\r\n\r\n // Copy to prevent ArrayBuffer detachment\r\n const audio = new Float32Array(audioSamples);\r\n\r\n return this.queueInference(audio);\r\n }\r\n\r\n private queueInference(audio: Float32Array): Promise<SenseVoiceResult> {\r\n return new Promise((resolve, reject) => {\r\n this.inferenceQueue = this.inferenceQueue.then(async () => {\r\n const telemetry = getTelemetry();\r\n const span = telemetry?.startSpan('SenseVoice.transcribe', {\r\n 'inference.backend': this._backend,\r\n 'inference.input_samples': audio.length,\r\n });\r\n\r\n try {\r\n const startTime = performance.now();\r\n\r\n // ── Preprocessing ──────────────────────────────────\r\n const preprocessStart = performance.now();\r\n\r\n // 1. Compute Kaldi fbank features [T, 80]\r\n const fbank = computeKaldiFbank(audio, 16000, 80);\r\n const numFrames = fbank.length / 80;\r\n\r\n if (numFrames === 0) {\r\n resolve({\r\n text: '',\r\n inferenceTimeMs: performance.now() - startTime,\r\n preprocessTimeMs: performance.now() - preprocessStart,\r\n });\r\n return;\r\n }\r\n\r\n // 2. Apply LFR stacking [T_reduced, 560]\r\n const lfrFeatures = applyLFR(fbank, 80, 7, 6);\r\n const numLfrFrames = lfrFeatures.length / 560;\r\n\r\n // 3. Apply CMVN normalization (in-place)\r\n if (this.negMean && this.invStddev) {\r\n applyCMVN(lfrFeatures, 560, this.negMean, this.invStddev);\r\n }\r\n\r\n const preprocessTimeMs = performance.now() - preprocessStart;\r\n\r\n // ── Build feeds ────────────────────────────────────\r\n const ort = this.ort!;\r\n const feeds: Record<string, InstanceType<typeof ort.Tensor>> = {\r\n x: new ort.Tensor('float32', lfrFeatures, [1, numLfrFrames, 560]),\r\n x_length: new ort.Tensor('int32', new Int32Array([numLfrFrames]), [1]),\r\n language: new ort.Tensor('int32', new Int32Array([this.languageId]), [1]),\r\n text_norm: new ort.Tensor('int32', new Int32Array([this.textNormId]), [1]),\r\n };\r\n\r\n // ── Inference ──────────────────────────────────────\r\n let timeoutId: ReturnType<typeof setTimeout>;\r\n const results = await Promise.race([\r\n this.session!.run(feeds).then(r => { clearTimeout(timeoutId); return r; }),\r\n new Promise<never>((_, rej) => {\r\n timeoutId = setTimeout(\r\n () => rej(new Error(`SenseVoice inference timed out after ${SenseVoiceInference.INFERENCE_TIMEOUT_MS}ms`)),\r\n SenseVoiceInference.INFERENCE_TIMEOUT_MS,\r\n );\r\n }),\r\n ]);\r\n\r\n const logitsOutput = results['logits'];\r\n if (!logitsOutput) {\r\n throw new Error('Model output missing \"logits\" tensor');\r\n }\r\n\r\n const logitsData = logitsOutput.data as Float32Array;\r\n const logitsDims = logitsOutput.dims;\r\n const seqLen = logitsDims[1] as number;\r\n const vocabSize = logitsDims[2] as number;\r\n\r\n // ── CTC Decode ─────────────────────────────────────\r\n const decoded = ctcGreedyDecode(logitsData, seqLen, vocabSize, this.tokenMap!);\r\n\r\n const inferenceTimeMs = performance.now() - startTime;\r\n\r\n logger.trace('Transcription complete', {\r\n text: decoded.text.substring(0, 50),\r\n language: decoded.language,\r\n emotion: decoded.emotion,\r\n event: decoded.event,\r\n preprocessTimeMs: Math.round(preprocessTimeMs * 100) / 100,\r\n inferenceTimeMs: Math.round(inferenceTimeMs * 100) / 100,\r\n numFrames,\r\n numLfrFrames,\r\n });\r\n\r\n span?.setAttributes({\r\n 'inference.duration_ms': inferenceTimeMs,\r\n 'inference.preprocess_ms': preprocessTimeMs,\r\n 'inference.num_frames': numFrames,\r\n 'inference.text_length': decoded.text.length,\r\n });\r\n span?.end();\r\n\r\n telemetry?.recordHistogram('omote.inference.latency', inferenceTimeMs, {\r\n model: 'sensevoice',\r\n backend: this._backend,\r\n });\r\n telemetry?.incrementCounter('omote.inference.total', 1, {\r\n model: 'sensevoice',\r\n backend: this._backend,\r\n status: 'success',\r\n });\r\n\r\n resolve({\r\n text: decoded.text,\r\n language: decoded.language,\r\n emotion: decoded.emotion,\r\n event: decoded.event,\r\n inferenceTimeMs,\r\n preprocessTimeMs,\r\n });\r\n } catch (err) {\r\n // Handle timeout — poison session to prevent zombie dispatches\r\n const errMsg = err instanceof Error ? err.message : String(err);\r\n if (errMsg.includes('timed out')) {\r\n this.poisoned = true;\r\n logger.error('CRITICAL: Inference session timed out — SenseVoice is dead. Page reload required.', {\r\n backend: this._backend,\r\n timeoutMs: SenseVoiceInference.INFERENCE_TIMEOUT_MS,\r\n });\r\n } else if (typeof err === 'number') {\r\n // ORT WASM throws raw C++ exception pointers as bare numbers (not Error objects)\r\n // when C++ throws (e.g., std::bad_alloc OOM). Almost always OOM.\r\n const oomError = new Error(\r\n `SenseVoice inference failed with raw C++ exception pointer (0x${err.toString(16)}). ` +\r\n `This is likely an OOM crash in WASM. Try reloading the page.`\r\n );\r\n logger.error('ORT WASM OOM — raw C++ exception pointer', {\r\n pointer: `0x${err.toString(16)}`,\r\n backend: this._backend,\r\n });\r\n span?.endWithError(oomError);\r\n telemetry?.incrementCounter('omote.inference.total', 1, {\r\n model: 'sensevoice',\r\n backend: this._backend,\r\n status: 'error',\r\n });\r\n reject(oomError);\r\n return;\r\n } else {\r\n logger.error('Inference failed', { error: errMsg, backend: this._backend });\r\n }\r\n span?.endWithError(err instanceof Error ? err : new Error(String(err)));\r\n telemetry?.incrementCounter('omote.inference.total', 1, {\r\n model: 'sensevoice',\r\n backend: this._backend,\r\n status: 'error',\r\n });\r\n reject(err);\r\n }\r\n });\r\n });\r\n }\r\n\r\n // ─── Dispose ──────────────────────────────────────────────────────────\r\n\r\n async dispose(): Promise<void> {\r\n if (this.session) {\r\n await this.session.release();\r\n this.session = null;\r\n }\r\n this.ort = null;\r\n this.tokenMap = null;\r\n this.negMean = null;\r\n this.invStddev = null;\r\n }\r\n}\r\n","/**\n * SenseVoice ASR Web Worker implementation\n *\n * Runs SenseVoice speech recognition in a dedicated Web Worker to prevent\n * main thread blocking. Uses inline worker script (Blob URL pattern) to\n * avoid separate file deployment.\n *\n * Key design decisions:\n * - WASM backend only (WebGPU doesn't work in Workers)\n * - All preprocessing (fbank, LFR, CMVN) and CTC decoding inlined in worker\n * - Audio copied (not transferred) to retain main thread access\n * - ONNX Runtime loaded from CDN in worker (no bundler complications)\n * - iOS: model URL passed as string to ORT (avoids 239MB JS heap allocation)\n *\n * @category Inference\n *\n * @example Basic usage\n * ```typescript\n * import { SenseVoiceWorker } from '@omote/core';\n *\n * const asr = new SenseVoiceWorker({\n * modelUrl: '/models/sensevoice/model.int8.onnx',\n * tokensUrl: '/models/sensevoice/tokens.txt',\n * });\n * await asr.load();\n *\n * const { text, emotion, language } = await asr.transcribe(audioSamples);\n * console.log(text); // \"Hello world\"\n * console.log(emotion); // \"NEUTRAL\"\n * console.log(language); // \"en\"\n * ```\n */\n\nimport { createLogger } from '../logging';\nimport { getTelemetry } from '../telemetry';\nimport { isIOS } from '../utils/runtime';\nimport { resolveLanguageId, resolveTextNormId } from './ctcDecoder';\nimport type { SenseVoiceLanguage, SenseVoiceResult, SenseVoiceModelInfo } from './SenseVoiceInference';\n\nconst logger = createLogger('SenseVoiceWorker');\n\n// ONNX Runtime CDN path (matches onnxLoader.ts)\nconst WASM_CDN_PATH = 'https://cdn.jsdelivr.net/npm/onnxruntime-web@1.23.2/dist/';\n\n// Worker script timeouts\nconst LOAD_TIMEOUT_MS = 30_000; // 30 seconds for 239MB download + ORT init\nconst INFERENCE_TIMEOUT_MS = 10_000; // 10 seconds (same as SenseVoiceInference)\n\n/**\n * Messages sent from main thread to worker\n */\nexport type SenseVoiceWorkerMessage =\n | { type: 'load'; modelUrl: string; tokensUrl: string; wasmPaths: string;\n isIOS: boolean; language: number; textNorm: number }\n | { type: 'transcribe'; audio: Float32Array }\n | { type: 'dispose' };\n\n/**\n * Messages sent from worker to main thread\n */\nexport type SenseVoiceWorkerResult =\n | { type: 'loaded'; vocabSize: number; inputNames: string[];\n outputNames: string[]; loadTimeMs: number }\n | { type: 'result'; text: string; language?: string; emotion?: string;\n event?: string; inferenceTimeMs: number; preprocessTimeMs: number }\n | { type: 'error'; error: string }\n | { type: 'disposed' };\n\n/**\n * Configuration for SenseVoice Worker\n */\nexport interface SenseVoiceWorkerConfig {\n /** Path or URL to model.int8.onnx (239MB) */\n modelUrl: string;\n /** Path or URL to tokens.txt vocabulary file (default: sibling of modelUrl) */\n tokensUrl?: string;\n /** Language hint (default: 'auto' for auto-detection) */\n language?: SenseVoiceLanguage;\n /** Text normalization: 'with_itn' applies inverse text normalization (default: 'with_itn') */\n textNorm?: 'with_itn' | 'without_itn';\n}\n\n/**\n * Inline worker script for SenseVoice ASR inference\n *\n * This script is embedded as a string and loaded via Blob URL.\n * It loads ONNX Runtime from CDN, performs all preprocessing (fbank, LFR, CMVN),\n * runs model inference, and decodes CTC output — all off the main thread.\n *\n * All functions from kaldiFbank.ts and ctcDecoder.ts are inlined as plain JavaScript.\n */\n\n/** Resolve a potentially relative URL to absolute (blob workers have no base URL) */\nfunction resolveUrl(url: string): string {\n if (/^https?:\\/\\//i.test(url) || /^blob:/i.test(url)) return url;\n try {\n return new URL(url, globalThis.location?.origin ?? 'https://localhost').href;\n } catch {\n return url;\n }\n}\n\nconst WORKER_SCRIPT = `\n// SenseVoice ASR Worker Script\n// Loaded via Blob URL - no separate file needed\n\nvar ort = null;\nvar session = null;\nvar tokenMap = null;\nvar negMean = null;\nvar invStddev = null;\nvar languageId = 0;\nvar textNormId = 14;\nvar vocabSize = 0;\n\n// ═══════════════════════════════════════════════════════════════════════════\n// kaldiFbank.ts — inlined as plain JavaScript\n// ═══════════════════════════════════════════════════════════════════════════\n\n/**\n * In-place Radix-2 Cooley-Tukey FFT\n */\nfunction fft(re, im) {\n var n = re.length;\n\n // Bit-reversal permutation\n for (var i = 1, j = 0; i < n; i++) {\n var bit = n >> 1;\n while (j & bit) {\n j ^= bit;\n bit >>= 1;\n }\n j ^= bit;\n if (i < j) {\n var tmp = re[i]; re[i] = re[j]; re[j] = tmp;\n tmp = im[i]; im[i] = im[j]; im[j] = tmp;\n }\n }\n\n // Butterfly passes\n for (var len = 2; len <= n; len *= 2) {\n var halfLen = len / 2;\n var angle = -2 * Math.PI / len;\n var wRe = Math.cos(angle);\n var wIm = Math.sin(angle);\n\n for (var i = 0; i < n; i += len) {\n var curRe = 1;\n var curIm = 0;\n for (var j = 0; j < halfLen; j++) {\n var a = i + j;\n var b = a + halfLen;\n var tRe = curRe * re[b] - curIm * im[b];\n var tIm = curRe * im[b] + curIm * re[b];\n re[b] = re[a] - tRe;\n im[b] = im[a] - tIm;\n re[a] += tRe;\n im[a] += tIm;\n var nextRe = curRe * wRe - curIm * wIm;\n curIm = curRe * wIm + curIm * wRe;\n curRe = nextRe;\n }\n }\n }\n}\n\n/** HTK mel scale */\nfunction htkMel(freq) {\n return 1127.0 * Math.log(1.0 + freq / 700.0);\n}\n\nfunction htkMelInverse(mel) {\n return 700.0 * (Math.exp(mel / 1127.0) - 1.0);\n}\n\n/**\n * Build triangular mel filterbank matrix\n */\nfunction buildMelFilterbank(numBins, fftSize, sampleRate, lowFreq, highFreq) {\n var numFftBins = fftSize / 2 + 1;\n var lowMel = htkMel(lowFreq);\n var highMel = htkMel(highFreq);\n\n // numBins + 2 equally spaced points in mel space\n var melPoints = new Float64Array(numBins + 2);\n for (var i = 0; i < numBins + 2; i++) {\n melPoints[i] = lowMel + (highMel - lowMel) * i / (numBins + 1);\n }\n\n // Convert mel points to FFT bin indices (float, not rounded)\n var binFreqs = new Float64Array(numBins + 2);\n for (var i = 0; i < numBins + 2; i++) {\n binFreqs[i] = htkMelInverse(melPoints[i]) * fftSize / sampleRate;\n }\n\n var filters = [];\n\n for (var m = 0; m < numBins; m++) {\n var left = binFreqs[m];\n var center = binFreqs[m + 1];\n var right = binFreqs[m + 2];\n\n var startBin = Math.max(0, Math.ceil(left));\n var endBin = Math.min(numFftBins - 1, Math.floor(right));\n\n var weights = new Float32Array(endBin - startBin + 1);\n for (var k = startBin; k <= endBin; k++) {\n if (k <= center) {\n weights[k - startBin] = (center - left) > 0 ? (k - left) / (center - left) : 0;\n } else {\n weights[k - startBin] = (right - center) > 0 ? (right - k) / (right - center) : 0;\n }\n }\n\n filters.push({ startBin: startBin, weights: weights });\n }\n\n return filters;\n}\n\n/** Create Hamming window */\nfunction createHammingWindow(length) {\n var w = new Float32Array(length);\n for (var i = 0; i < length; i++) {\n w[i] = 0.54 - 0.46 * Math.cos(2 * Math.PI * i / (length - 1));\n }\n return w;\n}\n\n/**\n * Compute Kaldi-compatible log mel filterbank features\n */\nfunction computeKaldiFbank(audio, sampleRate, numMelBins, opts) {\n var frameLengthMs = (opts && opts.frameLengthMs !== undefined) ? opts.frameLengthMs : 25;\n var frameShiftMs = (opts && opts.frameShiftMs !== undefined) ? opts.frameShiftMs : 10;\n var lowFreq = (opts && opts.lowFreq !== undefined) ? opts.lowFreq : 20;\n var highFreq = (opts && opts.highFreq !== undefined) ? opts.highFreq : (sampleRate / 2);\n var dither = (opts && opts.dither !== undefined) ? opts.dither : 0;\n var preemphasis = (opts && opts.preemphasis !== undefined) ? opts.preemphasis : 0.97;\n\n var frameLengthSamples = Math.round(sampleRate * frameLengthMs / 1000);\n var frameShiftSamples = Math.round(sampleRate * frameShiftMs / 1000);\n\n // Kaldi signal scaling: float [-1,1] -> int16 range\n var scaled = new Float32Array(audio.length);\n for (var i = 0; i < audio.length; i++) {\n scaled[i] = audio[i] * 32768;\n }\n\n // Optional dithering\n if (dither > 0) {\n for (var i = 0; i < scaled.length; i++) {\n var u1 = Math.random();\n var u2 = Math.random();\n scaled[i] += dither * Math.sqrt(-2 * Math.log(u1 + 1e-10)) * Math.cos(2 * Math.PI * u2);\n }\n }\n\n // Number of frames (snip_edges=true: only complete frames)\n var numFrames = Math.max(0, Math.floor((scaled.length - frameLengthSamples) / frameShiftSamples) + 1);\n if (numFrames === 0) {\n return new Float32Array(0);\n }\n\n // FFT size: next power of 2\n var fftSize = 1;\n while (fftSize < frameLengthSamples) fftSize *= 2;\n\n var numFftBins = fftSize / 2 + 1;\n\n // Pre-compute window and filterbank\n var window = createHammingWindow(frameLengthSamples);\n var filters = buildMelFilterbank(numMelBins, fftSize, sampleRate, lowFreq, highFreq);\n\n // Allocate output\n var output = new Float32Array(numFrames * numMelBins);\n\n // FFT buffers (reused per frame)\n var fftRe = new Float64Array(fftSize);\n var fftIm = new Float64Array(fftSize);\n\n for (var f = 0; f < numFrames; f++) {\n var offset = f * frameShiftSamples;\n\n // Clear FFT buffers\n fftRe.fill(0);\n fftIm.fill(0);\n\n // Extract frame with preemphasis and windowing\n for (var i = 0; i < frameLengthSamples; i++) {\n var sample = scaled[offset + i];\n // Preemphasis: y[n] = x[n] - coeff * x[n-1]\n if (preemphasis > 0 && i > 0) {\n sample -= preemphasis * scaled[offset + i - 1];\n } else if (preemphasis > 0 && i === 0 && offset > 0) {\n sample -= preemphasis * scaled[offset - 1];\n }\n // Apply window\n fftRe[i] = sample * window[i];\n }\n\n // FFT\n fft(fftRe, fftIm);\n\n // Power spectrum -> mel filterbank -> log\n var outOffset = f * numMelBins;\n for (var m = 0; m < numMelBins; m++) {\n var filter = filters[m];\n var energy = 0;\n for (var k = 0; k < filter.weights.length; k++) {\n var bin = filter.startBin + k;\n if (bin < numFftBins) {\n var powerSpec = fftRe[bin] * fftRe[bin] + fftIm[bin] * fftIm[bin];\n energy += filter.weights[k] * powerSpec;\n }\n }\n output[outOffset + m] = Math.log(Math.max(energy, 1e-10));\n }\n }\n\n return output;\n}\n\n/**\n * Apply Low Frame Rate stacking for SenseVoice\n */\nfunction applyLFR(features, featureDim, lfrM, lfrN) {\n var numFrames = features.length / featureDim;\n if (numFrames === 0) return new Float32Array(0);\n\n var leftPad = Math.floor((lfrM - 1) / 2); // 3 for lfrM=7\n var paddedLen = numFrames + leftPad;\n var numOutputFrames = Math.ceil(paddedLen / lfrN);\n var outputDim = featureDim * lfrM;\n\n var output = new Float32Array(numOutputFrames * outputDim);\n\n for (var i = 0; i < numOutputFrames; i++) {\n var startFrame = i * lfrN - leftPad;\n\n for (var j = 0; j < lfrM; j++) {\n var srcFrame = startFrame + j;\n // Clamp to valid range\n if (srcFrame < 0) srcFrame = 0;\n if (srcFrame >= numFrames) srcFrame = numFrames - 1;\n\n var srcOffset = srcFrame * featureDim;\n var dstOffset = i * outputDim + j * featureDim;\n for (var k = 0; k < featureDim; k++) {\n output[dstOffset + k] = features[srcOffset + k];\n }\n }\n }\n\n return output;\n}\n\n/**\n * Apply CMVN normalization in-place\n */\nfunction applyCMVN(features, dim, negMeanVec, invStddevVec) {\n for (var i = 0; i < features.length; i++) {\n var d = i % dim;\n features[i] = (features[i] + negMeanVec[d]) * invStddevVec[d];\n }\n return features;\n}\n\n/**\n * Parse CMVN vectors from comma-separated strings (stored in ONNX metadata)\n */\nfunction parseCMVNFromMetadata(negMeanStr, invStddevStr) {\n var negMeanArr = new Float32Array(\n negMeanStr.split(',').map(function(s) { return parseFloat(s.trim()); })\n );\n var invStddevArr = new Float32Array(\n invStddevStr.split(',').map(function(s) { return parseFloat(s.trim()); })\n );\n return { negMean: negMeanArr, invStddev: invStddevArr };\n}\n\n// ═══════════════════════════════════════════════════════════════════════════\n// ctcDecoder.ts — inlined as plain JavaScript\n// ═══════════════════════════════════════════════════════════════════════════\n\n/** SenseVoice language ID -> string mapping */\nvar LANGUAGE_IDS = {\n 0: 'auto',\n 3: 'zh',\n 4: 'en',\n 7: 'yue',\n 11: 'ja',\n 12: 'ko',\n 13: 'nospeech'\n};\n\n/** SenseVoice text normalization ID -> string mapping */\nvar TEXT_NORM_IDS = {\n 14: 'with_itn',\n 15: 'without_itn'\n};\n\n/** Resolve language string to SenseVoice language ID */\nfunction resolveLanguageId(language) {\n var map = {\n auto: 0,\n zh: 3,\n en: 4,\n yue: 7,\n ja: 11,\n ko: 12\n };\n return map[language] !== undefined ? map[language] : 0;\n}\n\n/** Resolve text norm string to SenseVoice text norm ID */\nfunction resolveTextNormId(textNorm) {\n return textNorm === 'without_itn' ? 15 : 14;\n}\n\n/**\n * Parse tokens.txt into a token ID -> string map\n */\nfunction parseTokensFile(content) {\n var map = new Map();\n var lines = content.split('\\\\n');\n for (var idx = 0; idx < lines.length; idx++) {\n var trimmed = lines[idx].trim();\n if (!trimmed) continue;\n // Find the last space - token string may contain spaces\n var lastSpace = trimmed.lastIndexOf(' ');\n if (lastSpace === -1) continue;\n var token = trimmed.substring(0, lastSpace);\n var id = parseInt(trimmed.substring(lastSpace + 1), 10);\n if (!isNaN(id)) {\n map.set(id, token);\n }\n }\n return map;\n}\n\n/**\n * SenseVoice structured token pattern matching\n */\nfunction parseStructuredToken(token) {\n var match = token.match(/^<\\\\|(.+)\\\\|>$/);\n if (!match) return null;\n\n var value = match[1];\n\n // Language tokens\n if (value === 'zh' || value === 'en' || value === 'ja' || value === 'ko' || value === 'yue' || value === 'nospeech') {\n return { type: 'language', value: value };\n }\n\n // Emotion tokens\n var emotions = ['HAPPY', 'SAD', 'ANGRY', 'NEUTRAL', 'FEARFUL', 'DISGUSTED', 'SURPRISED', 'EMO_UNKNOWN'];\n if (emotions.indexOf(value) !== -1) {\n return { type: 'emotion', value: value };\n }\n\n // Audio event tokens\n var events = ['Speech', 'BGM', 'Applause', 'Laughter', 'Crying', 'Coughing', 'Sneezing', 'EVENT_UNKNOWN'];\n if (events.indexOf(value) !== -1) {\n return { type: 'event', value: value };\n }\n\n // ITN tokens\n if (value === 'withitn' || value === 'woitn' || value === 'with_itn' || value === 'without_itn') {\n return { type: 'textnorm', value: value };\n }\n\n return null;\n}\n\n/**\n * CTC greedy decode\n */\nfunction ctcGreedyDecode(logits, seqLen, vocabSz, tokenMapLocal) {\n // Step 1: Argmax per time step\n var tokenIds = [];\n for (var t = 0; t < seqLen; t++) {\n var offset = t * vocabSz;\n var maxIdx = 0;\n var maxVal = logits[offset];\n for (var v = 1; v < vocabSz; v++) {\n if (logits[offset + v] > maxVal) {\n maxVal = logits[offset + v];\n maxIdx = v;\n }\n }\n tokenIds.push(maxIdx);\n }\n\n // Step 2: Collapse consecutive duplicates\n var collapsed = [];\n var prev = -1;\n for (var idx = 0; idx < tokenIds.length; idx++) {\n var id = tokenIds[idx];\n if (id !== prev) {\n collapsed.push(id);\n prev = id;\n }\n }\n\n // Step 3: Remove blank tokens (ID 0) and special tokens (<s>=1, </s>=2)\n var filtered = collapsed.filter(function(id) { return id !== 0 && id !== 1 && id !== 2; });\n\n // Step 4: Convert to token strings and parse structured tokens\n var language = undefined;\n var emotion = undefined;\n var event = undefined;\n var textTokens = [];\n\n for (var idx = 0; idx < filtered.length; idx++) {\n var id = filtered[idx];\n var token = tokenMapLocal.get(id);\n if (!token) continue;\n\n var structured = parseStructuredToken(token);\n if (structured) {\n if (structured.type === 'language') language = structured.value;\n else if (structured.type === 'emotion') emotion = structured.value;\n else if (structured.type === 'event') event = structured.value;\n // Skip textnorm tokens\n } else {\n textTokens.push(token);\n }\n }\n\n // Step 5: Join tokens, handle SentencePiece boundary marker\n var text = textTokens.join('');\n // Replace SentencePiece word boundary (U+2581) with space\n text = text.replace(/\\\\u2581/g, ' ').trim();\n\n return { text: text, language: language, emotion: emotion, event: event };\n}\n\n// ═══════════════════════════════════════════════════════════════════════════\n// Worker globals and message handler\n// ═══════════════════════════════════════════════════════════════════════════\n\n/**\n * Load ONNX Runtime from CDN\n */\nasync function loadOrt(wasmPaths) {\n if (ort) return;\n\n // Import ONNX Runtime from CDN\n var ortUrl = wasmPaths + 'ort.wasm.min.js';\n\n // Load the script by fetching and executing it\n var response = await fetch(ortUrl);\n var scriptText = await response.text();\n\n // Create a blob URL for the script\n var blob = new Blob([scriptText], { type: 'application/javascript' });\n var blobUrl = URL.createObjectURL(blob);\n\n // Import the module\n importScripts(blobUrl);\n URL.revokeObjectURL(blobUrl);\n\n // ort is now available as global\n ort = self.ort;\n\n // Configure WASM settings\n ort.env.wasm.wasmPaths = wasmPaths;\n ort.env.wasm.numThreads = 1; // Single thread in worker\n ort.env.wasm.simd = true;\n ort.env.wasm.proxy = false; // No proxy in worker\n}\n\n/**\n * Load the SenseVoice model and tokens\n */\nasync function loadModel(modelUrl, tokensUrl, isIOSDevice, lang, textNorm) {\n // 1. Fetch and parse tokens.txt\n var tokensResponse = await fetch(tokensUrl);\n if (!tokensResponse.ok) {\n throw new Error('Failed to fetch tokens.txt: ' + tokensResponse.status + ' ' + tokensResponse.statusText);\n }\n var tokensText = await tokensResponse.text();\n tokenMap = parseTokensFile(tokensText);\n\n // 2. Store language/textNorm IDs\n languageId = lang;\n textNormId = textNorm;\n\n // 3. Create inference session\n var sessionOptions = {\n executionProviders: ['wasm'],\n graphOptimizationLevel: 'all',\n };\n\n if (isIOSDevice) {\n // iOS: pass URL string directly to ORT to avoid 239MB JS heap allocation\n // ORT fetches into WASM memory, keeping JS heap at ~2MB\n session = await ort.InferenceSession.create(modelUrl, sessionOptions);\n } else {\n // Desktop: fetch ArrayBuffer for potential caching\n var modelResponse = await fetch(modelUrl);\n if (!modelResponse.ok) {\n throw new Error('Failed to fetch model: ' + modelResponse.status + ' ' + modelResponse.statusText);\n }\n var modelBuffer = await modelResponse.arrayBuffer();\n var modelData = new Uint8Array(modelBuffer);\n session = await ort.InferenceSession.create(modelData, sessionOptions);\n }\n\n // 4. Try to read CMVN from model metadata\n try {\n var metadata = session.handler && session.handler.metadata;\n if (metadata && metadata.neg_mean && metadata.inv_stddev) {\n var cmvn = parseCMVNFromMetadata(metadata.neg_mean, metadata.inv_stddev);\n negMean = cmvn.negMean;\n invStddev = cmvn.invStddev;\n }\n } catch (cmvnErr) {\n // CMVN not available — features will not be normalized\n }\n\n // 5. Determine vocab size from tokenMap\n vocabSize = 0;\n tokenMap.forEach(function(val, key) {\n if (key >= vocabSize) vocabSize = key + 1;\n });\n\n return {\n vocabSize: vocabSize,\n inputNames: session.inputNames.slice(),\n outputNames: session.outputNames.slice(),\n };\n}\n\n/**\n * Run transcription on audio samples\n */\nasync function runTranscription(audio) {\n var preprocessStart = performance.now();\n\n // 1. Compute Kaldi fbank features [T, 80]\n var fbank = computeKaldiFbank(audio, 16000, 80);\n var numFrames = fbank.length / 80;\n\n if (numFrames === 0) {\n return {\n text: '',\n language: undefined,\n emotion: undefined,\n event: undefined,\n inferenceTimeMs: performance.now() - preprocessStart,\n preprocessTimeMs: performance.now() - preprocessStart,\n };\n }\n\n // 2. Apply LFR stacking [T_reduced, 560]\n var lfrFeatures = applyLFR(fbank, 80, 7, 6);\n var numLfrFrames = lfrFeatures.length / 560;\n\n // 3. Apply CMVN normalization (in-place)\n if (negMean && invStddev) {\n applyCMVN(lfrFeatures, 560, negMean, invStddev);\n }\n\n var preprocessTimeMs = performance.now() - preprocessStart;\n\n // 4. Build ORT tensors\n var feeds = {\n x: new ort.Tensor('float32', lfrFeatures, [1, numLfrFrames, 560]),\n x_length: new ort.Tensor('int32', new Int32Array([numLfrFrames]), [1]),\n language: new ort.Tensor('int32', new Int32Array([languageId]), [1]),\n text_norm: new ort.Tensor('int32', new Int32Array([textNormId]), [1]),\n };\n\n // 5. Run inference\n var results = await session.run(feeds);\n\n var logitsOutput = results['logits'];\n if (!logitsOutput) {\n throw new Error('Model output missing \"logits\" tensor');\n }\n\n var logitsData = logitsOutput.data;\n var logitsDims = logitsOutput.dims;\n var seqLen = logitsDims[1];\n var modelVocabSize = logitsDims[2];\n\n // 6. CTC decode\n var decoded = ctcGreedyDecode(logitsData, seqLen, modelVocabSize, tokenMap);\n\n var totalTimeMs = performance.now() - preprocessStart;\n\n return {\n text: decoded.text,\n language: decoded.language,\n emotion: decoded.emotion,\n event: decoded.event,\n inferenceTimeMs: totalTimeMs,\n preprocessTimeMs: preprocessTimeMs,\n };\n}\n\n// Message handler\nself.onmessage = async function(e) {\n var msg = e.data;\n\n try {\n switch (msg.type) {\n case 'load': {\n var startTime = performance.now();\n await loadOrt(msg.wasmPaths);\n var info = await loadModel(msg.modelUrl, msg.tokensUrl, msg.isIOS, msg.language, msg.textNorm);\n var loadTimeMs = performance.now() - startTime;\n\n self.postMessage({\n type: 'loaded',\n vocabSize: info.vocabSize,\n inputNames: info.inputNames,\n outputNames: info.outputNames,\n loadTimeMs: loadTimeMs,\n });\n break;\n }\n\n case 'transcribe': {\n var result = await runTranscription(msg.audio);\n\n self.postMessage({\n type: 'result',\n text: result.text,\n language: result.language,\n emotion: result.emotion,\n event: result.event,\n inferenceTimeMs: result.inferenceTimeMs,\n preprocessTimeMs: result.preprocessTimeMs,\n });\n break;\n }\n\n case 'dispose': {\n if (session) {\n await session.release();\n session = null;\n }\n ort = null;\n tokenMap = null;\n negMean = null;\n invStddev = null;\n self.postMessage({ type: 'disposed' });\n break;\n }\n\n default:\n self.postMessage({\n type: 'error',\n error: 'Unknown message type: ' + msg.type,\n });\n }\n } catch (err) {\n var errorMsg = err.message || String(err);\n // Handle raw C++ exception pointers from ORT WASM\n if (typeof err === 'number') {\n errorMsg = 'Raw C++ exception pointer (0x' + err.toString(16) + '). Likely OOM in WASM.';\n }\n self.postMessage({\n type: 'error',\n error: errorMsg,\n });\n }\n};\n\n// Error handler\nself.onerror = function(err) {\n self.postMessage({\n type: 'error',\n error: 'Worker error: ' + (err.message || String(err)),\n });\n};\n`;\n\n/**\n * SenseVoice ASR Worker - Speech Recognition in a Web Worker\n *\n * Runs SenseVoice inference off the main thread to prevent UI blocking.\n * All preprocessing (fbank, LFR, CMVN) and CTC decoding run in the worker.\n *\n * @see SenseVoiceInference for main-thread version\n */\nexport class SenseVoiceWorker {\n private worker: Worker | null = null;\n private config: Required<SenseVoiceWorkerConfig>;\n private isLoading = false;\n private _isLoaded = false;\n\n // Inference queue for serialization\n private inferenceQueue: Promise<void> = Promise.resolve();\n\n // Session health: set to true if worker operation times out\n private poisoned = false;\n\n // Pending message handlers\n private pendingResolvers: Map<string, { resolve: (value: unknown) => void; reject: (error: Error) => void }> = new Map();\n\n // Resolved language/textNorm IDs\n private languageId: number;\n private textNormId: number;\n\n constructor(config: SenseVoiceWorkerConfig) {\n // Default tokensUrl to sibling of modelUrl\n const modelDir = config.modelUrl.substring(0, config.modelUrl.lastIndexOf('/'));\n const tokensUrl = config.tokensUrl ?? `${modelDir}/tokens.txt`;\n\n this.config = {\n modelUrl: config.modelUrl,\n tokensUrl,\n language: config.language ?? 'auto',\n textNorm: config.textNorm ?? 'with_itn',\n };\n\n this.languageId = resolveLanguageId(this.config.language);\n this.textNormId = resolveTextNormId(this.config.textNorm);\n }\n\n get isLoaded(): boolean {\n return this._isLoaded;\n }\n\n /**\n * Backend type (always 'wasm' for Worker, WebGPU not supported in Workers)\n */\n get backend(): 'wasm' | null {\n return this._isLoaded ? 'wasm' : null;\n }\n\n /**\n * Create the worker from inline script\n */\n private createWorker(): Worker {\n const blob = new Blob([WORKER_SCRIPT], { type: 'application/javascript' });\n const blobUrl = URL.createObjectURL(blob);\n const worker = new Worker(blobUrl);\n\n // Revoke blob URL after worker is created (worker has its own copy)\n URL.revokeObjectURL(blobUrl);\n\n // Set up message handler\n worker.onmessage = (event: MessageEvent<SenseVoiceWorkerResult>) => {\n this.handleWorkerMessage(event.data);\n };\n\n // Set up error handler\n worker.onerror = (error) => {\n logger.error('Worker error', { error: error.message });\n // Reject any pending operations\n for (const [, resolver] of this.pendingResolvers) {\n resolver.reject(new Error(`Worker error: ${error.message}`));\n }\n this.pendingResolvers.clear();\n };\n\n return worker;\n }\n\n /**\n * Handle messages from worker\n */\n private handleWorkerMessage(result: SenseVoiceWorkerResult): void {\n // Route to pending resolver based on result type\n const resolver = this.pendingResolvers.get(result.type);\n if (resolver) {\n this.pendingResolvers.delete(result.type);\n if (result.type === 'error') {\n resolver.reject(new Error(result.error));\n } else {\n resolver.resolve(result);\n }\n }\n }\n\n /**\n * Send message to worker and wait for response\n */\n private sendMessage<T>(message: SenseVoiceWorkerMessage, expectedType: string, timeoutMs: number): Promise<T> {\n return new Promise((resolve, reject) => {\n if (!this.worker) {\n reject(new Error('Worker not initialized'));\n return;\n }\n\n // Set up timeout\n const timeoutId = setTimeout(() => {\n this.pendingResolvers.delete(expectedType);\n this.poisoned = true;\n reject(new Error(`Worker operation timed out after ${timeoutMs}ms`));\n }, timeoutMs);\n\n // Register resolver\n this.pendingResolvers.set(expectedType, {\n resolve: (value) => {\n clearTimeout(timeoutId);\n resolve(value as T);\n },\n reject: (error) => {\n clearTimeout(timeoutId);\n reject(error);\n },\n });\n\n // Also listen for errors\n this.pendingResolvers.set('error', {\n resolve: () => {}, // Never called for errors\n reject: (error) => {\n clearTimeout(timeoutId);\n this.pendingResolvers.delete(expectedType);\n reject(error);\n },\n });\n\n // Send message\n this.worker.postMessage(message);\n });\n }\n\n /**\n * Load the ONNX model in the worker\n *\n * @param onProgress - Optional progress callback. Fires once at 100% when load completes\n * (the worker downloads and loads the model internally, so granular progress is not available).\n */\n async load(onProgress?: (loaded: number, total: number) => void): Promise<SenseVoiceModelInfo> {\n if (this.isLoading) {\n throw new Error('Model is already loading');\n }\n\n if (this._isLoaded) {\n throw new Error('Model already loaded. Call dispose() first.');\n }\n\n this.isLoading = true;\n const startTime = performance.now();\n const telemetry = getTelemetry();\n const span = telemetry?.startSpan('SenseVoiceWorker.load', {\n 'model.url': this.config.modelUrl,\n 'model.language': this.config.language,\n });\n\n try {\n logger.info('Creating SenseVoice worker...');\n\n // Create worker\n this.worker = this.createWorker();\n\n logger.info('Loading model in worker...', {\n modelUrl: this.config.modelUrl,\n tokensUrl: this.config.tokensUrl,\n language: this.config.language,\n textNorm: this.config.textNorm,\n });\n\n // Send load message to worker\n const result = await this.sendMessage<{\n type: 'loaded';\n vocabSize: number;\n inputNames: string[];\n outputNames: string[];\n loadTimeMs: number;\n }>(\n {\n type: 'load',\n modelUrl: resolveUrl(this.config.modelUrl),\n tokensUrl: resolveUrl(this.config.tokensUrl),\n wasmPaths: WASM_CDN_PATH,\n isIOS: isIOS(),\n language: this.languageId,\n textNorm: this.textNormId,\n },\n 'loaded',\n LOAD_TIMEOUT_MS\n );\n\n this._isLoaded = true;\n\n const loadTimeMs = performance.now() - startTime;\n\n // Fire progress callback at 100%\n onProgress?.(1, 1);\n\n logger.info('SenseVoice worker loaded successfully', {\n backend: 'wasm',\n loadTimeMs: Math.round(loadTimeMs),\n workerLoadTimeMs: Math.round(result.loadTimeMs),\n vocabSize: result.vocabSize,\n language: this.config.language,\n textNorm: this.config.textNorm,\n });\n\n span?.setAttributes({\n 'model.backend': 'wasm',\n 'model.load_time_ms': loadTimeMs,\n 'model.worker_load_time_ms': result.loadTimeMs,\n 'model.vocab_size': result.vocabSize,\n });\n span?.end();\n telemetry?.recordHistogram('omote.model.load_time', loadTimeMs, {\n model: 'sensevoice-worker',\n backend: 'wasm',\n });\n\n return {\n backend: 'wasm',\n loadTimeMs,\n inputNames: result.inputNames,\n outputNames: result.outputNames,\n vocabSize: result.vocabSize,\n };\n } catch (error) {\n span?.endWithError(error instanceof Error ? error : new Error(String(error)));\n telemetry?.incrementCounter('omote.errors.total', 1, {\n model: 'sensevoice-worker',\n error_type: 'load_failed',\n });\n\n // Clean up on failure\n if (this.worker) {\n this.worker.terminate();\n this.worker = null;\n }\n\n throw error;\n } finally {\n this.isLoading = false;\n }\n }\n\n /**\n * Transcribe audio samples to text\n *\n * @param audioSamples Float32Array of audio samples at 16kHz, [-1, 1] range\n * @returns Transcription result with text, emotion, language, and event\n */\n async transcribe(audioSamples: Float32Array): Promise<SenseVoiceResult> {\n if (!this._isLoaded || !this.worker) {\n throw new Error('Worker not loaded. Call load() first.');\n }\n\n if (this.poisoned) {\n throw new Error('SenseVoice worker timed out — inference unavailable until page reload');\n }\n\n // Copy to prevent ArrayBuffer detachment\n const audio = new Float32Array(audioSamples);\n\n return this.queueInference(audio);\n }\n\n /**\n * Queue inference to serialize worker calls\n */\n private queueInference(audio: Float32Array): Promise<SenseVoiceResult> {\n return new Promise((resolve, reject) => {\n this.inferenceQueue = this.inferenceQueue.then(async () => {\n const telemetry = getTelemetry();\n const span = telemetry?.startSpan('SenseVoiceWorker.transcribe', {\n 'inference.backend': 'wasm',\n 'inference.input_samples': audio.length,\n });\n\n try {\n const startTime = performance.now();\n\n // Send transcribe message to worker\n const result = await this.sendMessage<{\n type: 'result';\n text: string;\n language?: string;\n emotion?: string;\n event?: string;\n inferenceTimeMs: number;\n preprocessTimeMs: number;\n }>(\n {\n type: 'transcribe',\n audio,\n },\n 'result',\n INFERENCE_TIMEOUT_MS\n );\n\n const totalTimeMs = performance.now() - startTime;\n\n logger.trace('Worker transcription complete', {\n text: result.text.substring(0, 50),\n language: result.language,\n emotion: result.emotion,\n event: result.event,\n preprocessTimeMs: Math.round(result.preprocessTimeMs * 100) / 100,\n inferenceTimeMs: Math.round(result.inferenceTimeMs * 100) / 100,\n roundTripMs: Math.round(totalTimeMs * 100) / 100,\n });\n\n span?.setAttributes({\n 'inference.duration_ms': totalTimeMs,\n 'inference.worker_duration_ms': result.inferenceTimeMs,\n 'inference.preprocess_ms': result.preprocessTimeMs,\n 'inference.text_length': result.text.length,\n });\n span?.end();\n\n telemetry?.recordHistogram('omote.inference.latency', totalTimeMs, {\n model: 'sensevoice-worker',\n backend: 'wasm',\n });\n telemetry?.incrementCounter('omote.inference.total', 1, {\n model: 'sensevoice-worker',\n backend: 'wasm',\n status: 'success',\n });\n\n resolve({\n text: result.text,\n language: result.language,\n emotion: result.emotion,\n event: result.event,\n inferenceTimeMs: result.inferenceTimeMs,\n preprocessTimeMs: result.preprocessTimeMs,\n });\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n if (errMsg.includes('timed out')) {\n logger.error('CRITICAL: Worker inference timed out — SenseVoice worker is dead. Page reload required.', {\n timeoutMs: INFERENCE_TIMEOUT_MS,\n });\n } else {\n logger.error('Worker inference failed', { error: errMsg });\n }\n\n span?.endWithError(err instanceof Error ? err : new Error(String(err)));\n telemetry?.incrementCounter('omote.inference.total', 1, {\n model: 'sensevoice-worker',\n backend: 'wasm',\n status: 'error',\n });\n reject(err);\n }\n });\n });\n }\n\n /**\n * Dispose of the worker and free resources\n */\n async dispose(): Promise<void> {\n if (this.worker) {\n try {\n // Ask worker to clean up\n await this.sendMessage({ type: 'dispose' }, 'disposed', INFERENCE_TIMEOUT_MS);\n } catch {\n // Ignore errors during dispose\n }\n\n // Terminate worker\n this.worker.terminate();\n this.worker = null;\n }\n\n this._isLoaded = false;\n this.poisoned = false;\n this.pendingResolvers.clear();\n }\n\n /**\n * Check if Web Workers are supported\n */\n static isSupported(): boolean {\n return typeof Worker !== 'undefined';\n }\n}\n","/**\n * Unified Inference Worker — single Web Worker hosting all WASM models\n *\n * Solves the multi-worker ORT problem: three per-model workers each load their\n * own ORT WASM instance (~40MB each). On iOS this exceeds the ~1-1.5GB tab\n * limit, forcing main-thread fallback which blocks the render loop.\n *\n * This worker hosts SenseVoice + Wav2ArkitCpu + Silero VAD in a single\n * ORT WASM instance. Same total model memory (~643MB), but inference runs\n * off-main-thread. Works on iOS because there's only one ORT instance.\n *\n * Consumer usage:\n * ```typescript\n * const worker = new UnifiedInferenceWorker();\n * await worker.init();\n *\n * const asr = createSenseVoice({ modelUrl: '...', unifiedWorker: worker });\n * const lam = createLipSync({ gpuModelUrl: '...', cpuModelUrl: '...', unifiedWorker: worker });\n * const vad = createSileroVAD({ modelUrl: '...', unifiedWorker: worker });\n * ```\n *\n * @category Inference\n */\n\nimport { createLogger } from '../logging';\nimport { getTelemetry } from '../telemetry';\nimport { isIOS } from '../utils/runtime';\nimport { resolveLanguageId, resolveTextNormId } from './ctcDecoder';\nimport type { SenseVoiceLanguage, SenseVoiceResult, SenseVoiceModelInfo } from './SenseVoiceInference';\nimport type { LipSyncBackend, LipSyncModelInfo, LipSyncResult } from './LipSyncBackend';\nimport type { SenseVoiceBackend } from './createSenseVoice';\nimport type { SileroVADBackend } from './createSileroVAD';\nimport type { SenseVoiceWorkerConfig } from './SenseVoiceWorker';\nimport type { Wav2ArkitCpuWorkerConfig } from './Wav2ArkitCpuWorker';\nimport type { SileroVADConfig, VADModelInfo, VADResult } from './SileroVADInference';\nimport type { VADWorkerModelInfo } from './SileroVADWorker';\nimport type { RuntimeBackend } from '../utils/runtime';\n\nconst logger = createLogger('UnifiedInferenceWorker');\n\n// ONNX Runtime CDN path\nconst WASM_CDN_PATH = 'https://cdn.jsdelivr.net/npm/onnxruntime-web@1.23.2/dist/';\n\n// Timeouts per operation\nconst INIT_TIMEOUT_MS = 15_000;\nconst SV_LOAD_TIMEOUT_MS = 30_000;\nconst SV_INFER_TIMEOUT_MS = 10_000;\nconst CPU_LOAD_TIMEOUT_MS = 60_000;\nconst CPU_INFER_TIMEOUT_MS = 5_000;\nconst VAD_LOAD_TIMEOUT_MS = 10_000;\nconst VAD_INFER_TIMEOUT_MS = 1_000;\nconst DISPOSE_TIMEOUT_MS = 5_000;\n\n/** Resolve a potentially relative URL to absolute (blob workers have no base URL) */\nfunction resolveUrl(url: string): string {\n if (/^https?:\\/\\//i.test(url) || /^blob:/i.test(url)) return url;\n try {\n return new URL(url, globalThis.location?.origin ?? 'https://localhost').href;\n } catch {\n return url;\n }\n}\n\nlet requestCounter = 0;\nfunction nextRequestId(): string {\n return `req_${++requestCounter}_${Date.now()}`;\n}\n\n// ═══════════════════════════════════════════════════════════════════════════\n// Inline Worker Script\n// ═══════════════════════════════════════════════════════════════════════════\n\nconst WORKER_SCRIPT = `\n// Unified Inference Worker Script\n// Hosts SenseVoice + Wav2ArkitCpu + Silero VAD in a single ORT instance\n\nvar ort = null;\n\n// SenseVoice state\nvar svSession = null;\nvar svTokenMap = null;\nvar svNegMean = null;\nvar svInvStddev = null;\nvar svLanguageId = 0;\nvar svTextNormId = 14;\nvar svVocabSize = 0;\n\n// Wav2ArkitCpu state\nvar cpuSession = null;\n\n// Silero VAD state\nvar vadSession = null;\nvar vadSampleRate = 16000;\nvar vadChunkSize = 512;\nvar vadContextSize = 64;\n\n// ═══════════════════════════════════════════════════════════════════════════\n// kaldiFbank.ts — inlined as plain JavaScript\n// ═══════════════════════════════════════════════════════════════════════════\n\nfunction fft(re, im) {\n var n = re.length;\n for (var i = 1, j = 0; i < n; i++) {\n var bit = n >> 1;\n while (j & bit) { j ^= bit; bit >>= 1; }\n j ^= bit;\n if (i < j) {\n var tmp = re[i]; re[i] = re[j]; re[j] = tmp;\n tmp = im[i]; im[i] = im[j]; im[j] = tmp;\n }\n }\n for (var len = 2; len <= n; len *= 2) {\n var halfLen = len / 2;\n var angle = -2 * Math.PI / len;\n var wRe = Math.cos(angle);\n var wIm = Math.sin(angle);\n for (var i = 0; i < n; i += len) {\n var curRe = 1, curIm = 0;\n for (var j = 0; j < halfLen; j++) {\n var a = i + j, b = a + halfLen;\n var tRe = curRe * re[b] - curIm * im[b];\n var tIm = curRe * im[b] + curIm * re[b];\n re[b] = re[a] - tRe; im[b] = im[a] - tIm;\n re[a] += tRe; im[a] += tIm;\n var nextRe = curRe * wRe - curIm * wIm;\n curIm = curRe * wIm + curIm * wRe;\n curRe = nextRe;\n }\n }\n }\n}\n\nfunction htkMel(freq) { return 1127.0 * Math.log(1.0 + freq / 700.0); }\nfunction htkMelInverse(mel) { return 700.0 * (Math.exp(mel / 1127.0) - 1.0); }\n\nfunction buildMelFilterbank(numBins, fftSize, sampleRate, lowFreq, highFreq) {\n var numFftBins = fftSize / 2 + 1;\n var lowMel = htkMel(lowFreq);\n var highMel = htkMel(highFreq);\n var melPoints = new Float64Array(numBins + 2);\n for (var i = 0; i < numBins + 2; i++) {\n melPoints[i] = lowMel + (highMel - lowMel) * i / (numBins + 1);\n }\n var binFreqs = new Float64Array(numBins + 2);\n for (var i = 0; i < numBins + 2; i++) {\n binFreqs[i] = htkMelInverse(melPoints[i]) * fftSize / sampleRate;\n }\n var filters = [];\n for (var m = 0; m < numBins; m++) {\n var left = binFreqs[m], center = binFreqs[m + 1], right = binFreqs[m + 2];\n var startBin = Math.max(0, Math.ceil(left));\n var endBin = Math.min(numFftBins - 1, Math.floor(right));\n var weights = new Float32Array(endBin - startBin + 1);\n for (var k = startBin; k <= endBin; k++) {\n if (k <= center) {\n weights[k - startBin] = (center - left) > 0 ? (k - left) / (center - left) : 0;\n } else {\n weights[k - startBin] = (right - center) > 0 ? (right - k) / (right - center) : 0;\n }\n }\n filters.push({ startBin: startBin, weights: weights });\n }\n return filters;\n}\n\nfunction createHammingWindow(length) {\n var w = new Float32Array(length);\n for (var i = 0; i < length; i++) {\n w[i] = 0.54 - 0.46 * Math.cos(2 * Math.PI * i / (length - 1));\n }\n return w;\n}\n\nfunction computeKaldiFbank(audio, sampleRate, numMelBins, opts) {\n var frameLengthMs = (opts && opts.frameLengthMs !== undefined) ? opts.frameLengthMs : 25;\n var frameShiftMs = (opts && opts.frameShiftMs !== undefined) ? opts.frameShiftMs : 10;\n var lowFreq = (opts && opts.lowFreq !== undefined) ? opts.lowFreq : 20;\n var highFreq = (opts && opts.highFreq !== undefined) ? opts.highFreq : (sampleRate / 2);\n var dither = (opts && opts.dither !== undefined) ? opts.dither : 0;\n var preemphasis = (opts && opts.preemphasis !== undefined) ? opts.preemphasis : 0.97;\n\n var frameLengthSamples = Math.round(sampleRate * frameLengthMs / 1000);\n var frameShiftSamples = Math.round(sampleRate * frameShiftMs / 1000);\n\n var scaled = new Float32Array(audio.length);\n for (var i = 0; i < audio.length; i++) { scaled[i] = audio[i] * 32768; }\n\n if (dither > 0) {\n for (var i = 0; i < scaled.length; i++) {\n var u1 = Math.random(), u2 = Math.random();\n scaled[i] += dither * Math.sqrt(-2 * Math.log(u1 + 1e-10)) * Math.cos(2 * Math.PI * u2);\n }\n }\n\n var numFrames = Math.max(0, Math.floor((scaled.length - frameLengthSamples) / frameShiftSamples) + 1);\n if (numFrames === 0) return new Float32Array(0);\n\n var fftSize = 1;\n while (fftSize < frameLengthSamples) fftSize *= 2;\n var numFftBins = fftSize / 2 + 1;\n\n var window = createHammingWindow(frameLengthSamples);\n var filters = buildMelFilterbank(numMelBins, fftSize, sampleRate, lowFreq, highFreq);\n var output = new Float32Array(numFrames * numMelBins);\n var fftRe = new Float64Array(fftSize);\n var fftIm = new Float64Array(fftSize);\n\n for (var f = 0; f < numFrames; f++) {\n var offset = f * frameShiftSamples;\n fftRe.fill(0); fftIm.fill(0);\n for (var i = 0; i < frameLengthSamples; i++) {\n var sample = scaled[offset + i];\n if (preemphasis > 0 && i > 0) {\n sample -= preemphasis * scaled[offset + i - 1];\n } else if (preemphasis > 0 && i === 0 && offset > 0) {\n sample -= preemphasis * scaled[offset - 1];\n }\n fftRe[i] = sample * window[i];\n }\n fft(fftRe, fftIm);\n var outOffset = f * numMelBins;\n for (var m = 0; m < numMelBins; m++) {\n var filter = filters[m];\n var energy = 0;\n for (var k = 0; k < filter.weights.length; k++) {\n var bin = filter.startBin + k;\n if (bin < numFftBins) {\n var powerSpec = fftRe[bin] * fftRe[bin] + fftIm[bin] * fftIm[bin];\n energy += filter.weights[k] * powerSpec;\n }\n }\n output[outOffset + m] = Math.log(Math.max(energy, 1e-10));\n }\n }\n return output;\n}\n\nfunction applyLFR(features, featureDim, lfrM, lfrN) {\n var numFrames = features.length / featureDim;\n if (numFrames === 0) return new Float32Array(0);\n var leftPad = Math.floor((lfrM - 1) / 2);\n var paddedLen = numFrames + leftPad;\n var numOutputFrames = Math.ceil(paddedLen / lfrN);\n var outputDim = featureDim * lfrM;\n var output = new Float32Array(numOutputFrames * outputDim);\n for (var i = 0; i < numOutputFrames; i++) {\n var startFrame = i * lfrN - leftPad;\n for (var j = 0; j < lfrM; j++) {\n var srcFrame = startFrame + j;\n if (srcFrame < 0) srcFrame = 0;\n if (srcFrame >= numFrames) srcFrame = numFrames - 1;\n var srcOffset = srcFrame * featureDim;\n var dstOffset = i * outputDim + j * featureDim;\n for (var k = 0; k < featureDim; k++) {\n output[dstOffset + k] = features[srcOffset + k];\n }\n }\n }\n return output;\n}\n\nfunction applyCMVN(features, dim, negMeanVec, invStddevVec) {\n for (var i = 0; i < features.length; i++) {\n var d = i % dim;\n features[i] = (features[i] + negMeanVec[d]) * invStddevVec[d];\n }\n return features;\n}\n\nfunction parseCMVNFromMetadata(negMeanStr, invStddevStr) {\n var negMeanArr = new Float32Array(\n negMeanStr.split(',').map(function(s) { return parseFloat(s.trim()); })\n );\n var invStddevArr = new Float32Array(\n invStddevStr.split(',').map(function(s) { return parseFloat(s.trim()); })\n );\n return { negMean: negMeanArr, invStddev: invStddevArr };\n}\n\n// ═══════════════════════════════════════════════════════════════════════════\n// ctcDecoder.ts — inlined as plain JavaScript\n// ═══════════════════════════════════════════════════════════════════════════\n\nvar LANGUAGE_IDS = { 0: 'auto', 3: 'zh', 4: 'en', 7: 'yue', 11: 'ja', 12: 'ko', 13: 'nospeech' };\nvar TEXT_NORM_IDS = { 14: 'with_itn', 15: 'without_itn' };\n\nfunction resolveLanguageIdW(language) {\n var map = { auto: 0, zh: 3, en: 4, yue: 7, ja: 11, ko: 12 };\n return map[language] !== undefined ? map[language] : 0;\n}\n\nfunction resolveTextNormIdW(textNorm) {\n return textNorm === 'without_itn' ? 15 : 14;\n}\n\nfunction parseTokensFile(content) {\n var map = new Map();\n var lines = content.split('\\\\n');\n for (var idx = 0; idx < lines.length; idx++) {\n var trimmed = lines[idx].trim();\n if (!trimmed) continue;\n var lastSpace = trimmed.lastIndexOf(' ');\n if (lastSpace === -1) continue;\n var token = trimmed.substring(0, lastSpace);\n var id = parseInt(trimmed.substring(lastSpace + 1), 10);\n if (!isNaN(id)) map.set(id, token);\n }\n return map;\n}\n\nfunction parseStructuredToken(token) {\n var match = token.match(/^<\\\\|(.+)\\\\|>$/);\n if (!match) return null;\n var value = match[1];\n if (value === 'zh' || value === 'en' || value === 'ja' || value === 'ko' || value === 'yue' || value === 'nospeech') {\n return { type: 'language', value: value };\n }\n var emotions = ['HAPPY', 'SAD', 'ANGRY', 'NEUTRAL', 'FEARFUL', 'DISGUSTED', 'SURPRISED', 'EMO_UNKNOWN'];\n if (emotions.indexOf(value) !== -1) return { type: 'emotion', value: value };\n var events = ['Speech', 'BGM', 'Applause', 'Laughter', 'Crying', 'Coughing', 'Sneezing', 'EVENT_UNKNOWN'];\n if (events.indexOf(value) !== -1) return { type: 'event', value: value };\n if (value === 'withitn' || value === 'woitn' || value === 'with_itn' || value === 'without_itn') {\n return { type: 'textnorm', value: value };\n }\n return null;\n}\n\nfunction ctcGreedyDecode(logits, seqLen, vocabSz, tokenMapLocal) {\n var tokenIds = [];\n for (var t = 0; t < seqLen; t++) {\n var offset = t * vocabSz;\n var maxIdx = 0, maxVal = logits[offset];\n for (var v = 1; v < vocabSz; v++) {\n if (logits[offset + v] > maxVal) { maxVal = logits[offset + v]; maxIdx = v; }\n }\n tokenIds.push(maxIdx);\n }\n var collapsed = [], prev = -1;\n for (var idx = 0; idx < tokenIds.length; idx++) {\n var id = tokenIds[idx];\n if (id !== prev) { collapsed.push(id); prev = id; }\n }\n var filtered = collapsed.filter(function(id) { return id !== 0 && id !== 1 && id !== 2; });\n var language = undefined, emotion = undefined, event = undefined;\n var textTokens = [];\n for (var idx = 0; idx < filtered.length; idx++) {\n var id = filtered[idx];\n var token = tokenMapLocal.get(id);\n if (!token) continue;\n var structured = parseStructuredToken(token);\n if (structured) {\n if (structured.type === 'language') language = structured.value;\n else if (structured.type === 'emotion') emotion = structured.value;\n else if (structured.type === 'event') event = structured.value;\n } else {\n textTokens.push(token);\n }\n }\n var text = textTokens.join('');\n text = text.replace(/\\\\u2581/g, ' ').trim();\n return { text: text, language: language, emotion: emotion, event: event };\n}\n\n// ═══════════════════════════════════════════════════════════════════════════\n// blendshapeUtils.ts — inlined\n// ═══════════════════════════════════════════════════════════════════════════\n\nvar SYMMETRIC_INDEX_PAIRS = [\n [23, 25], [32, 38], [43, 44], [29, 30], [27, 28], [45, 46],\n [35, 36], [47, 48], [33, 34], [49, 50], [6, 7], [0, 1],\n [3, 4], [8, 9], [16, 17], [10, 11], [12, 13], [14, 15],\n [18, 19], [20, 21],\n];\n\nfunction symmetrizeBlendshapes(frame) {\n var result = new Float32Array(frame);\n for (var p = 0; p < SYMMETRIC_INDEX_PAIRS.length; p++) {\n var lIdx = SYMMETRIC_INDEX_PAIRS[p][0], rIdx = SYMMETRIC_INDEX_PAIRS[p][1];\n var avg = (frame[lIdx] + frame[rIdx]) / 2;\n result[lIdx] = avg;\n result[rIdx] = avg;\n }\n return result;\n}\n\n// ═══════════════════════════════════════════════════════════════════════════\n// Shared ORT loader\n// ═══════════════════════════════════════════════════════════════════════════\n\nasync function loadOrt(wasmPaths, isIOSDevice) {\n if (ort) return;\n var ortUrl = wasmPaths + 'ort.wasm.min.js';\n var response = await fetch(ortUrl);\n var scriptText = await response.text();\n var blob = new Blob([scriptText], { type: 'application/javascript' });\n var blobUrl = URL.createObjectURL(blob);\n importScripts(blobUrl);\n URL.revokeObjectURL(blobUrl);\n ort = self.ort;\n ort.env.wasm.wasmPaths = wasmPaths;\n ort.env.wasm.numThreads = isIOSDevice ? 1 : 4;\n ort.env.wasm.simd = true;\n ort.env.wasm.proxy = false;\n}\n\n// ═══════════════════════════════════════════════════════════════════════════\n// SenseVoice handlers\n// ═══════════════════════════════════════════════════════════════════════════\n\nasync function svLoad(msg) {\n var tokensResponse = await fetch(msg.tokensUrl);\n if (!tokensResponse.ok) throw new Error('Failed to fetch tokens.txt: ' + tokensResponse.status);\n var tokensText = await tokensResponse.text();\n svTokenMap = parseTokensFile(tokensText);\n svLanguageId = msg.language;\n svTextNormId = msg.textNorm;\n\n var sessionOptions = { executionProviders: ['wasm'], graphOptimizationLevel: 'all' };\n if (msg.isIOS) {\n svSession = await ort.InferenceSession.create(msg.modelUrl, sessionOptions);\n } else {\n var modelResponse = await fetch(msg.modelUrl);\n if (!modelResponse.ok) throw new Error('Failed to fetch model: ' + modelResponse.status);\n var modelBuffer = await modelResponse.arrayBuffer();\n svSession = await ort.InferenceSession.create(new Uint8Array(modelBuffer), sessionOptions);\n }\n\n try {\n var metadata = svSession.handler && svSession.handler.metadata;\n if (metadata && metadata.neg_mean && metadata.inv_stddev) {\n var cmvn = parseCMVNFromMetadata(metadata.neg_mean, metadata.inv_stddev);\n svNegMean = cmvn.negMean;\n svInvStddev = cmvn.invStddev;\n }\n } catch (e) { /* CMVN not available */ }\n\n svVocabSize = 0;\n svTokenMap.forEach(function(val, key) { if (key >= svVocabSize) svVocabSize = key + 1; });\n\n return {\n vocabSize: svVocabSize,\n inputNames: svSession.inputNames.slice(),\n outputNames: svSession.outputNames.slice(),\n };\n}\n\nasync function svTranscribe(audio) {\n var preprocessStart = performance.now();\n var fbank = computeKaldiFbank(audio, 16000, 80);\n var numFrames = fbank.length / 80;\n if (numFrames === 0) {\n return { text: '', inferenceTimeMs: performance.now() - preprocessStart, preprocessTimeMs: performance.now() - preprocessStart };\n }\n var lfrFeatures = applyLFR(fbank, 80, 7, 6);\n var numLfrFrames = lfrFeatures.length / 560;\n if (svNegMean && svInvStddev) applyCMVN(lfrFeatures, 560, svNegMean, svInvStddev);\n var preprocessTimeMs = performance.now() - preprocessStart;\n\n var feeds = {\n x: new ort.Tensor('float32', lfrFeatures, [1, numLfrFrames, 560]),\n x_length: new ort.Tensor('int32', new Int32Array([numLfrFrames]), [1]),\n language: new ort.Tensor('int32', new Int32Array([svLanguageId]), [1]),\n text_norm: new ort.Tensor('int32', new Int32Array([svTextNormId]), [1]),\n };\n var results = await svSession.run(feeds);\n var logitsOutput = results['logits'];\n if (!logitsOutput) throw new Error('Model output missing \"logits\" tensor');\n\n var decoded = ctcGreedyDecode(logitsOutput.data, logitsOutput.dims[1], logitsOutput.dims[2], svTokenMap);\n var totalTimeMs = performance.now() - preprocessStart;\n\n return {\n text: decoded.text, language: decoded.language, emotion: decoded.emotion, event: decoded.event,\n inferenceTimeMs: totalTimeMs, preprocessTimeMs: preprocessTimeMs,\n };\n}\n\nasync function svDispose() {\n if (svSession) { await svSession.release(); svSession = null; }\n svTokenMap = null; svNegMean = null; svInvStddev = null;\n}\n\n// ═══════════════════════════════════════════════════════════════════════════\n// Wav2ArkitCpu handlers\n// ═══════════════════════════════════════════════════════════════════════════\n\nasync function cpuLoad(msg) {\n var sessionOptions = { executionProviders: ['wasm'], graphOptimizationLevel: 'all' };\n var dataFilename = msg.externalDataUrl ? msg.externalDataUrl.split('/').pop() : null;\n\n if (msg.isIOS) {\n if (msg.externalDataUrl && dataFilename) {\n sessionOptions.externalData = [{ path: dataFilename, data: msg.externalDataUrl }];\n }\n cpuSession = await ort.InferenceSession.create(msg.modelUrl, sessionOptions);\n } else {\n var graphResponse = await fetch(msg.modelUrl);\n if (!graphResponse.ok) throw new Error('Failed to fetch model graph: ' + graphResponse.status);\n var graphBuffer = await graphResponse.arrayBuffer();\n if (msg.externalDataUrl && dataFilename) {\n var dataResponse = await fetch(msg.externalDataUrl);\n if (!dataResponse.ok) throw new Error('Failed to fetch external data: ' + dataResponse.status);\n var dataBuffer = await dataResponse.arrayBuffer();\n sessionOptions.externalData = [{ path: dataFilename, data: new Uint8Array(dataBuffer) }];\n }\n cpuSession = await ort.InferenceSession.create(new Uint8Array(graphBuffer), sessionOptions);\n }\n\n // Warmup\n var warmupAudio = new Float32Array(16000);\n var warmupTensor = new ort.Tensor('float32', warmupAudio, [1, warmupAudio.length]);\n await cpuSession.run({ audio_waveform: warmupTensor });\n\n return {\n inputNames: cpuSession.inputNames.slice(),\n outputNames: cpuSession.outputNames.slice(),\n };\n}\n\nasync function cpuInfer(audio) {\n var tensor = new ort.Tensor('float32', audio, [1, audio.length]);\n var results = await cpuSession.run({ audio_waveform: tensor });\n var blendshapeOutput = results['blendshapes'];\n if (!blendshapeOutput) throw new Error('Missing blendshapes output from model');\n\n var blendshapeData = blendshapeOutput.data;\n var numFrames = blendshapeOutput.dims[1];\n var numBlendshapes = blendshapeOutput.dims[2];\n\n var flatBuffer = new Float32Array(numFrames * numBlendshapes);\n for (var f = 0; f < numFrames; f++) {\n var offset = f * numBlendshapes;\n var rawFrame = blendshapeData.slice(offset, offset + numBlendshapes);\n var symmetrized = symmetrizeBlendshapes(rawFrame);\n flatBuffer.set(symmetrized, offset);\n }\n return { flatBuffer: flatBuffer, numFrames: numFrames, numBlendshapes: numBlendshapes };\n}\n\nasync function cpuDispose() {\n if (cpuSession) { await cpuSession.release(); cpuSession = null; }\n}\n\n// ═══════════════════════════════════════════════════════════════════════════\n// Silero VAD handlers\n// ═══════════════════════════════════════════════════════════════════════════\n\nasync function vadLoad(msg) {\n vadSampleRate = msg.sampleRate;\n vadChunkSize = vadSampleRate === 16000 ? 512 : 256;\n vadContextSize = vadSampleRate === 16000 ? 64 : 32;\n\n var response = await fetch(msg.modelUrl);\n if (!response.ok) throw new Error('Failed to fetch VAD model: ' + response.status);\n var modelBuffer = await response.arrayBuffer();\n vadSession = await ort.InferenceSession.create(new Uint8Array(modelBuffer), {\n executionProviders: ['wasm'],\n graphOptimizationLevel: 'all',\n });\n\n return {\n inputNames: vadSession.inputNames.slice(),\n outputNames: vadSession.outputNames.slice(),\n };\n}\n\nasync function vadProcess(audio, state, context) {\n var inputSize = vadContextSize + vadChunkSize;\n var inputBuffer = new Float32Array(inputSize);\n inputBuffer.set(context, 0);\n inputBuffer.set(audio, vadContextSize);\n\n var inputTensor = new ort.Tensor('float32', new Float32Array(inputBuffer), [1, inputSize]);\n var stateTensor = new ort.Tensor('float32', new Float32Array(state), [2, 1, 128]);\n var srTensor;\n try {\n srTensor = new ort.Tensor('int64', new BigInt64Array([BigInt(vadSampleRate)]), []);\n } catch (e) {\n srTensor = new ort.Tensor('int64', [BigInt(vadSampleRate)], []);\n }\n\n var feeds = { 'input': inputTensor, 'state': stateTensor, 'sr': srTensor };\n var results = await vadSession.run(feeds);\n var outputTensor = results['output'];\n var newStateTensor = results['stateN'] || results['state'];\n if (!outputTensor) throw new Error('Missing output tensor from VAD model');\n\n return { probability: outputTensor.data[0], newState: new Float32Array(newStateTensor.data) };\n}\n\nfunction vadCreateInitialState() {\n return new Float32Array(2 * 1 * 128);\n}\n\nasync function vadDispose() {\n if (vadSession) { await vadSession.release(); vadSession = null; }\n}\n\n// ═══════════════════════════════════════════════════════════════════════════\n// Message handler\n// ═══════════════════════════════════════════════════════════════════════════\n\nself.onmessage = async function(e) {\n var msg = e.data;\n var requestId = msg.requestId;\n\n try {\n switch (msg.type) {\n case 'init': {\n var startTime = performance.now();\n await loadOrt(msg.wasmPaths, msg.isIOS);\n self.postMessage({ type: 'init:done', requestId: requestId, loadTimeMs: performance.now() - startTime });\n break;\n }\n\n case 'sv:load': {\n var startTime = performance.now();\n var info = await svLoad(msg);\n self.postMessage({\n type: 'sv:loaded', requestId: requestId, vocabSize: info.vocabSize,\n inputNames: info.inputNames, outputNames: info.outputNames,\n loadTimeMs: performance.now() - startTime,\n });\n break;\n }\n\n case 'sv:transcribe': {\n var result = await svTranscribe(msg.audio);\n self.postMessage({\n type: 'sv:result', requestId: requestId,\n text: result.text, language: result.language, emotion: result.emotion, event: result.event,\n inferenceTimeMs: result.inferenceTimeMs, preprocessTimeMs: result.preprocessTimeMs,\n });\n break;\n }\n\n case 'sv:dispose': {\n await svDispose();\n self.postMessage({ type: 'sv:disposed', requestId: requestId });\n break;\n }\n\n case 'cpu:load': {\n var startTime = performance.now();\n var info = await cpuLoad(msg);\n self.postMessage({\n type: 'cpu:loaded', requestId: requestId,\n inputNames: info.inputNames, outputNames: info.outputNames,\n loadTimeMs: performance.now() - startTime,\n });\n break;\n }\n\n case 'cpu:infer': {\n var startTime = performance.now();\n var result = await cpuInfer(msg.audio);\n var inferenceTimeMs = performance.now() - startTime;\n self.postMessage({\n type: 'cpu:result', requestId: requestId,\n blendshapes: result.flatBuffer, numFrames: result.numFrames,\n numBlendshapes: result.numBlendshapes, inferenceTimeMs: inferenceTimeMs,\n }, [result.flatBuffer.buffer]);\n break;\n }\n\n case 'cpu:dispose': {\n await cpuDispose();\n self.postMessage({ type: 'cpu:disposed', requestId: requestId });\n break;\n }\n\n case 'vad:load': {\n var startTime = performance.now();\n var info = await vadLoad(msg);\n self.postMessage({\n type: 'vad:loaded', requestId: requestId,\n inputNames: info.inputNames, outputNames: info.outputNames,\n loadTimeMs: performance.now() - startTime,\n });\n break;\n }\n\n case 'vad:process': {\n var startTime = performance.now();\n var result = await vadProcess(msg.audio, msg.state, msg.context);\n self.postMessage({\n type: 'vad:result', requestId: requestId,\n probability: result.probability, state: result.newState,\n inferenceTimeMs: performance.now() - startTime,\n });\n break;\n }\n\n case 'vad:reset': {\n var state = vadCreateInitialState();\n self.postMessage({ type: 'vad:reset', requestId: requestId, state: state });\n break;\n }\n\n case 'vad:dispose': {\n await vadDispose();\n self.postMessage({ type: 'vad:disposed', requestId: requestId });\n break;\n }\n\n case 'dispose-all': {\n await svDispose();\n await cpuDispose();\n await vadDispose();\n ort = null;\n self.postMessage({ type: 'dispose-all:done', requestId: requestId });\n break;\n }\n\n default:\n self.postMessage({ type: 'error', requestId: requestId, error: 'Unknown message type: ' + msg.type });\n }\n } catch (err) {\n var errorMsg = err.message || String(err);\n if (typeof err === 'number') {\n errorMsg = 'Raw C++ exception pointer (0x' + err.toString(16) + '). Likely OOM in WASM.';\n }\n self.postMessage({ type: 'error', requestId: requestId, error: errorMsg });\n }\n};\n\nself.onerror = function(err) {\n self.postMessage({ type: 'error', requestId: null, error: 'Worker error: ' + (err.message || String(err)) });\n};\n`;\n\n// ═══════════════════════════════════════════════════════════════════════════\n// Main Thread: UnifiedInferenceWorker\n// ═══════════════════════════════════════════════════════════════════════════\n\n/**\n * Unified Inference Worker — single Web Worker for all WASM models\n *\n * Hosts SenseVoice, Wav2ArkitCpu, and Silero VAD in one ORT instance.\n * Eliminates the multi-worker memory problem on iOS.\n */\nexport class UnifiedInferenceWorker {\n private worker: Worker | null = null;\n private pendingRequests = new Map<string, {\n resolve: (value: unknown) => void;\n reject: (error: Error) => void;\n timeout: ReturnType<typeof setTimeout>;\n }>();\n private initialized = false;\n private poisoned = false;\n\n /**\n * Initialize the worker (load ORT WASM from CDN)\n */\n async init(): Promise<void> {\n if (this.initialized) return;\n\n const startTime = performance.now();\n const telemetry = getTelemetry();\n const span = telemetry?.startSpan('UnifiedInferenceWorker.init');\n\n try {\n logger.info('Creating unified inference worker...');\n this.worker = this.createWorker();\n\n await this.sendMessage(\n { type: 'init', wasmPaths: WASM_CDN_PATH, isIOS: isIOS() },\n 'init:done',\n INIT_TIMEOUT_MS,\n );\n\n this.initialized = true;\n const loadTimeMs = performance.now() - startTime;\n logger.info('Unified worker initialized', { loadTimeMs: Math.round(loadTimeMs) });\n\n span?.setAttributes({ 'worker.init_time_ms': loadTimeMs });\n span?.end();\n } catch (error) {\n span?.endWithError(error instanceof Error ? error : new Error(String(error)));\n this.cleanup();\n throw error;\n }\n }\n\n // ── SenseVoice ────────────────────────────────────────────────────────\n\n async loadSenseVoice(config: {\n modelUrl: string;\n tokensUrl: string;\n language: number;\n textNorm: number;\n }): Promise<SenseVoiceModelInfo> {\n this.assertReady();\n\n const startTime = performance.now();\n const result = await this.sendMessage<{\n vocabSize: number;\n inputNames: string[];\n outputNames: string[];\n loadTimeMs: number;\n }>(\n {\n type: 'sv:load',\n modelUrl: resolveUrl(config.modelUrl),\n tokensUrl: resolveUrl(config.tokensUrl),\n isIOS: isIOS(),\n language: config.language,\n textNorm: config.textNorm,\n },\n 'sv:loaded',\n SV_LOAD_TIMEOUT_MS,\n );\n\n const loadTimeMs = performance.now() - startTime;\n return {\n backend: 'wasm',\n loadTimeMs,\n inputNames: result.inputNames,\n outputNames: result.outputNames,\n vocabSize: result.vocabSize,\n };\n }\n\n async transcribe(audio: Float32Array): Promise<SenseVoiceResult> {\n this.assertReady();\n\n const result = await this.sendMessage<{\n text: string;\n language?: string;\n emotion?: string;\n event?: string;\n inferenceTimeMs: number;\n preprocessTimeMs: number;\n }>(\n { type: 'sv:transcribe', audio },\n 'sv:result',\n SV_INFER_TIMEOUT_MS,\n );\n\n return {\n text: result.text,\n language: result.language,\n emotion: result.emotion,\n event: result.event,\n inferenceTimeMs: result.inferenceTimeMs,\n preprocessTimeMs: result.preprocessTimeMs,\n };\n }\n\n async disposeSenseVoice(): Promise<void> {\n if (!this.worker) return;\n await this.sendMessage({ type: 'sv:dispose' }, 'sv:disposed', DISPOSE_TIMEOUT_MS);\n }\n\n // ── Wav2ArkitCpu (Lip Sync) ──────────────────────────────────────────\n\n async loadLipSync(config: {\n modelUrl: string;\n externalDataUrl: string | null;\n }): Promise<LipSyncModelInfo> {\n this.assertReady();\n\n const startTime = performance.now();\n const result = await this.sendMessage<{\n inputNames: string[];\n outputNames: string[];\n loadTimeMs: number;\n }>(\n {\n type: 'cpu:load',\n modelUrl: resolveUrl(config.modelUrl),\n externalDataUrl: config.externalDataUrl ? resolveUrl(config.externalDataUrl) : null,\n isIOS: isIOS(),\n },\n 'cpu:loaded',\n CPU_LOAD_TIMEOUT_MS,\n );\n\n const loadTimeMs = performance.now() - startTime;\n return {\n backend: 'wasm',\n loadTimeMs,\n inputNames: result.inputNames,\n outputNames: result.outputNames,\n };\n }\n\n async inferLipSync(audio: Float32Array): Promise<{\n blendshapes: Float32Array;\n numFrames: number;\n numBlendshapes: number;\n inferenceTimeMs: number;\n }> {\n this.assertReady();\n\n return this.sendMessage(\n { type: 'cpu:infer', audio },\n 'cpu:result',\n CPU_INFER_TIMEOUT_MS,\n );\n }\n\n async disposeLipSync(): Promise<void> {\n if (!this.worker) return;\n await this.sendMessage({ type: 'cpu:dispose' }, 'cpu:disposed', DISPOSE_TIMEOUT_MS);\n }\n\n // ── Silero VAD ────────────────────────────────────────────────────────\n\n async loadVAD(config: {\n modelUrl: string;\n sampleRate: number;\n }): Promise<VADWorkerModelInfo> {\n this.assertReady();\n\n const startTime = performance.now();\n const chunkSize = config.sampleRate === 16000 ? 512 : 256;\n const result = await this.sendMessage<{\n inputNames: string[];\n outputNames: string[];\n loadTimeMs: number;\n }>(\n {\n type: 'vad:load',\n modelUrl: resolveUrl(config.modelUrl),\n sampleRate: config.sampleRate,\n },\n 'vad:loaded',\n VAD_LOAD_TIMEOUT_MS,\n );\n\n const loadTimeMs = performance.now() - startTime;\n return {\n backend: 'wasm',\n loadTimeMs,\n inputNames: result.inputNames,\n outputNames: result.outputNames,\n sampleRate: config.sampleRate,\n chunkSize,\n };\n }\n\n async processVAD(\n audio: Float32Array,\n state: Float32Array,\n context: Float32Array,\n ): Promise<{ probability: number; state: Float32Array; inferenceTimeMs: number }> {\n this.assertReady();\n\n return this.sendMessage(\n { type: 'vad:process', audio, state, context },\n 'vad:result',\n VAD_INFER_TIMEOUT_MS,\n );\n }\n\n async resetVAD(): Promise<Float32Array> {\n this.assertReady();\n\n const result = await this.sendMessage<{ state: Float32Array }>(\n { type: 'vad:reset' },\n 'vad:reset',\n VAD_INFER_TIMEOUT_MS,\n );\n return result.state;\n }\n\n async disposeVAD(): Promise<void> {\n if (!this.worker) return;\n await this.sendMessage({ type: 'vad:dispose' }, 'vad:disposed', DISPOSE_TIMEOUT_MS);\n }\n\n // ── Lifecycle ─────────────────────────────────────────────────────────\n\n async dispose(): Promise<void> {\n if (this.worker) {\n try {\n await this.sendMessage({ type: 'dispose-all' }, 'dispose-all:done', DISPOSE_TIMEOUT_MS);\n } catch {\n // Ignore errors during dispose\n }\n this.worker.terminate();\n this.worker = null;\n }\n this.initialized = false;\n this.poisoned = false;\n this.rejectAllPending('Worker disposed');\n this.pendingRequests.clear();\n }\n\n /** Check if the worker is initialized and not poisoned */\n get isReady(): boolean {\n return this.initialized && !this.poisoned && this.worker !== null;\n }\n\n /** Check if Web Workers are supported */\n static isSupported(): boolean {\n return typeof Worker !== 'undefined';\n }\n\n // ── Private ───────────────────────────────────────────────────────────\n\n private assertReady(): void {\n if (!this.initialized || !this.worker) {\n throw new Error('UnifiedInferenceWorker not initialized. Call init() first.');\n }\n if (this.poisoned) {\n throw new Error('UnifiedInferenceWorker timed out — unavailable until page reload');\n }\n }\n\n private createWorker(): Worker {\n const blob = new Blob([WORKER_SCRIPT], { type: 'application/javascript' });\n const blobUrl = URL.createObjectURL(blob);\n const worker = new Worker(blobUrl);\n URL.revokeObjectURL(blobUrl);\n\n worker.onmessage = (event: MessageEvent) => {\n this.handleWorkerMessage(event.data);\n };\n\n worker.onerror = (error) => {\n logger.error('Unified worker error', { error: error.message });\n this.rejectAllPending(`Worker error: ${error.message}`);\n };\n\n return worker;\n }\n\n private handleWorkerMessage(data: Record<string, unknown>): void {\n const requestId = data.requestId as string | null;\n\n if (data.type === 'error') {\n if (requestId && this.pendingRequests.has(requestId)) {\n const pending = this.pendingRequests.get(requestId)!;\n clearTimeout(pending.timeout);\n this.pendingRequests.delete(requestId);\n pending.reject(new Error(data.error as string));\n } else {\n // Broadcast error — reject all pending\n logger.error('Worker broadcast error', { error: data.error });\n this.rejectAllPending(data.error as string);\n }\n return;\n }\n\n if (requestId && this.pendingRequests.has(requestId)) {\n const pending = this.pendingRequests.get(requestId)!;\n clearTimeout(pending.timeout);\n this.pendingRequests.delete(requestId);\n pending.resolve(data);\n }\n }\n\n private sendMessage<T = unknown>(\n message: Record<string, unknown>,\n expectedType: string,\n timeoutMs: number,\n ): Promise<T> {\n return new Promise((resolve, reject) => {\n if (!this.worker) {\n reject(new Error('Worker not initialized'));\n return;\n }\n\n const requestId = nextRequestId();\n const timeout = setTimeout(() => {\n this.pendingRequests.delete(requestId);\n this.poisoned = true;\n logger.error('CRITICAL: Worker operation timed out — worker is dead', {\n type: message.type,\n timeoutMs,\n });\n reject(new Error(`Worker operation '${message.type}' timed out after ${timeoutMs}ms`));\n }, timeoutMs);\n\n this.pendingRequests.set(requestId, {\n resolve: resolve as (value: unknown) => void,\n reject,\n timeout,\n });\n\n this.worker.postMessage({ ...message, requestId });\n });\n }\n\n private rejectAllPending(reason: string): void {\n for (const [, pending] of this.pendingRequests) {\n clearTimeout(pending.timeout);\n pending.reject(new Error(reason));\n }\n this.pendingRequests.clear();\n }\n\n private cleanup(): void {\n if (this.worker) {\n this.worker.terminate();\n this.worker = null;\n }\n this.initialized = false;\n this.rejectAllPending('Worker cleanup');\n this.pendingRequests.clear();\n }\n}\n\n// ═══════════════════════════════════════════════════════════════════════════\n// Adapter Classes\n// ═══════════════════════════════════════════════════════════════════════════\n\n/**\n * SenseVoice adapter backed by UnifiedInferenceWorker\n *\n * Implements SenseVoiceBackend, delegating all inference to the shared worker.\n */\nexport class SenseVoiceUnifiedAdapter implements SenseVoiceBackend {\n private worker: UnifiedInferenceWorker;\n private config: Required<SenseVoiceWorkerConfig>;\n private _isLoaded = false;\n private languageId: number;\n private textNormId: number;\n private inferenceQueue: Promise<void> = Promise.resolve();\n\n constructor(worker: UnifiedInferenceWorker, config: SenseVoiceWorkerConfig) {\n this.worker = worker;\n const modelDir = config.modelUrl.substring(0, config.modelUrl.lastIndexOf('/'));\n this.config = {\n modelUrl: config.modelUrl,\n tokensUrl: config.tokensUrl ?? `${modelDir}/tokens.txt`,\n language: config.language ?? 'auto',\n textNorm: config.textNorm ?? 'with_itn',\n };\n this.languageId = resolveLanguageId(this.config.language);\n this.textNormId = resolveTextNormId(this.config.textNorm);\n }\n\n get isLoaded(): boolean { return this._isLoaded; }\n get backend(): 'wasm' | null { return this._isLoaded ? 'wasm' : null; }\n\n async load(onProgress?: (loaded: number, total: number) => void): Promise<SenseVoiceModelInfo> {\n const telemetry = getTelemetry();\n const span = telemetry?.startSpan('SenseVoiceUnifiedAdapter.load', {\n 'model.url': this.config.modelUrl,\n });\n\n try {\n const result = await this.worker.loadSenseVoice({\n modelUrl: this.config.modelUrl,\n tokensUrl: this.config.tokensUrl,\n language: this.languageId,\n textNorm: this.textNormId,\n });\n this._isLoaded = true;\n onProgress?.(1, 1);\n\n logger.info('SenseVoice loaded via unified worker', {\n backend: 'wasm',\n loadTimeMs: Math.round(result.loadTimeMs),\n vocabSize: result.vocabSize,\n });\n\n span?.setAttributes({ 'model.backend': 'wasm', 'model.load_time_ms': result.loadTimeMs });\n span?.end();\n telemetry?.recordHistogram('omote.model.load_time', result.loadTimeMs, {\n model: 'sensevoice-unified',\n backend: 'wasm',\n });\n\n return result;\n } catch (error) {\n span?.endWithError(error instanceof Error ? error : new Error(String(error)));\n throw error;\n }\n }\n\n async transcribe(audioSamples: Float32Array): Promise<SenseVoiceResult> {\n if (!this._isLoaded) throw new Error('Model not loaded. Call load() first.');\n\n const audio = new Float32Array(audioSamples);\n\n return new Promise((resolve, reject) => {\n this.inferenceQueue = this.inferenceQueue.then(async () => {\n try {\n const result = await this.worker.transcribe(audio);\n resolve(result);\n } catch (err) {\n reject(err);\n }\n });\n });\n }\n\n async dispose(): Promise<void> {\n if (this._isLoaded) {\n await this.worker.disposeSenseVoice();\n this._isLoaded = false;\n }\n }\n}\n\n/**\n * Wav2ArkitCpu adapter backed by UnifiedInferenceWorker\n *\n * Implements LipSyncBackend, delegating all inference to the shared worker.\n */\nexport class Wav2ArkitCpuUnifiedAdapter implements LipSyncBackend {\n readonly modelId = 'wav2arkit_cpu' as const;\n\n private worker: UnifiedInferenceWorker;\n private config: Wav2ArkitCpuWorkerConfig;\n private _isLoaded = false;\n private inferenceQueue: Promise<void> = Promise.resolve();\n\n constructor(worker: UnifiedInferenceWorker, config: Wav2ArkitCpuWorkerConfig) {\n this.worker = worker;\n this.config = config;\n }\n\n get isLoaded(): boolean { return this._isLoaded; }\n get backend(): RuntimeBackend | null { return this._isLoaded ? 'wasm' : null; }\n\n async load(): Promise<LipSyncModelInfo> {\n const telemetry = getTelemetry();\n const span = telemetry?.startSpan('Wav2ArkitCpuUnifiedAdapter.load', {\n 'model.url': this.config.modelUrl,\n });\n\n try {\n const externalDataUrl = this.config.externalDataUrl !== false\n ? (this.config.externalDataUrl || `${this.config.modelUrl}.data`)\n : null;\n\n const result = await this.worker.loadLipSync({\n modelUrl: this.config.modelUrl,\n externalDataUrl: externalDataUrl || null,\n });\n this._isLoaded = true;\n\n logger.info('Wav2ArkitCpu loaded via unified worker', {\n backend: 'wasm',\n loadTimeMs: Math.round(result.loadTimeMs),\n });\n\n span?.setAttributes({ 'model.backend': 'wasm', 'model.load_time_ms': result.loadTimeMs });\n span?.end();\n telemetry?.recordHistogram('omote.model.load_time', result.loadTimeMs, {\n model: 'wav2arkit_cpu-unified',\n backend: 'wasm',\n });\n\n return result;\n } catch (error) {\n span?.endWithError(error instanceof Error ? error : new Error(String(error)));\n throw error;\n }\n }\n\n async infer(audioSamples: Float32Array, _identityIndex?: number): Promise<LipSyncResult> {\n if (!this._isLoaded) throw new Error('Model not loaded. Call load() first.');\n\n const audioCopy = new Float32Array(audioSamples);\n\n return new Promise((resolve, reject) => {\n this.inferenceQueue = this.inferenceQueue.then(async () => {\n const telemetry = getTelemetry();\n const span = telemetry?.startSpan('Wav2ArkitCpuUnifiedAdapter.infer', {\n 'inference.input_samples': audioCopy.length,\n });\n\n try {\n const startTime = performance.now();\n const result = await this.worker.inferLipSync(audioCopy);\n const inferenceTimeMs = performance.now() - startTime;\n\n // Reconstruct per-frame Float32Array[] from flat buffer\n const flatBuffer = result.blendshapes;\n const { numFrames, numBlendshapes } = result;\n const blendshapes: Float32Array[] = [];\n for (let f = 0; f < numFrames; f++) {\n blendshapes.push(flatBuffer.slice(f * numBlendshapes, (f + 1) * numBlendshapes));\n }\n\n span?.setAttributes({\n 'inference.duration_ms': inferenceTimeMs,\n 'inference.frames': numFrames,\n });\n span?.end();\n\n resolve({ blendshapes, numFrames, inferenceTimeMs });\n } catch (err) {\n span?.endWithError(err instanceof Error ? err : new Error(String(err)));\n reject(err);\n }\n });\n });\n }\n\n async dispose(): Promise<void> {\n if (this._isLoaded) {\n await this.worker.disposeLipSync();\n this._isLoaded = false;\n }\n }\n}\n\n/**\n * Silero VAD adapter backed by UnifiedInferenceWorker\n *\n * Implements SileroVADBackend, delegating all inference to the shared worker.\n */\nexport class SileroVADUnifiedAdapter implements SileroVADBackend {\n private worker: UnifiedInferenceWorker;\n private config: Required<SileroVADConfig>;\n private _isLoaded = false;\n\n // LSTM state (kept in main thread, sent to worker per inference)\n private state: Float32Array;\n private context: Float32Array;\n private readonly chunkSize: number;\n private readonly contextSize: number;\n\n // Inference queue\n private inferenceQueue: Promise<void> = Promise.resolve();\n\n // Pre-speech buffer\n private preSpeechBuffer: Float32Array[] = [];\n private wasSpeaking = false;\n\n constructor(worker: UnifiedInferenceWorker, config: SileroVADConfig) {\n this.worker = worker;\n const sr = config.sampleRate ?? 16000;\n this.config = {\n modelUrl: config.modelUrl,\n backend: config.backend ?? 'wasm',\n sampleRate: sr,\n threshold: config.threshold ?? 0.5,\n preSpeechBufferChunks: config.preSpeechBufferChunks ?? 10,\n };\n this.chunkSize = sr === 16000 ? 512 : 256;\n this.contextSize = sr === 16000 ? 64 : 32;\n this.state = new Float32Array(2 * 1 * 128);\n this.context = new Float32Array(this.contextSize);\n }\n\n get isLoaded(): boolean { return this._isLoaded; }\n get backend(): RuntimeBackend | null { return this._isLoaded ? 'wasm' : null; }\n get sampleRate(): number { return this.config.sampleRate; }\n get threshold(): number { return this.config.threshold; }\n\n getChunkSize(): number { return this.chunkSize; }\n getChunkDurationMs(): number { return (this.chunkSize / this.config.sampleRate) * 1000; }\n\n async load(): Promise<VADWorkerModelInfo> {\n const telemetry = getTelemetry();\n const span = telemetry?.startSpan('SileroVADUnifiedAdapter.load', {\n 'model.url': this.config.modelUrl,\n });\n\n try {\n const result = await this.worker.loadVAD({\n modelUrl: this.config.modelUrl,\n sampleRate: this.config.sampleRate,\n });\n this._isLoaded = true;\n\n logger.info('SileroVAD loaded via unified worker', {\n backend: 'wasm',\n loadTimeMs: Math.round(result.loadTimeMs),\n sampleRate: this.config.sampleRate,\n chunkSize: this.chunkSize,\n });\n\n span?.setAttributes({ 'model.backend': 'wasm', 'model.load_time_ms': result.loadTimeMs });\n span?.end();\n telemetry?.recordHistogram('omote.model.load_time', result.loadTimeMs, {\n model: 'silero-vad-unified',\n backend: 'wasm',\n });\n\n return result;\n } catch (error) {\n span?.endWithError(error instanceof Error ? error : new Error(String(error)));\n throw error;\n }\n }\n\n async process(audioChunk: Float32Array): Promise<VADResult> {\n if (!this._isLoaded) throw new Error('Model not loaded. Call load() first.');\n\n if (audioChunk.length !== this.chunkSize) {\n throw new Error(\n `Audio chunk must be exactly ${this.chunkSize} samples (got ${audioChunk.length}). ` +\n `Use getChunkSize() to get required size.`\n );\n }\n\n const audioChunkCopy = new Float32Array(audioChunk);\n\n return new Promise((resolve, reject) => {\n this.inferenceQueue = this.inferenceQueue.then(async () => {\n try {\n const startTime = performance.now();\n const result = await this.worker.processVAD(audioChunkCopy, this.state, this.context);\n\n // Update local state\n this.state = result.state;\n this.context = audioChunkCopy.slice(-this.contextSize);\n\n const inferenceTimeMs = performance.now() - startTime;\n const isSpeech = result.probability > this.config.threshold;\n\n // Pre-speech buffer logic (same as SileroVADInference)\n let preSpeechChunks: Float32Array[] | undefined;\n\n if (isSpeech && !this.wasSpeaking) {\n preSpeechChunks = [...this.preSpeechBuffer];\n this.preSpeechBuffer = [];\n } else if (!isSpeech && !this.wasSpeaking) {\n this.preSpeechBuffer.push(new Float32Array(audioChunkCopy));\n if (this.preSpeechBuffer.length > this.config.preSpeechBufferChunks) {\n this.preSpeechBuffer.shift();\n }\n } else if (!isSpeech && this.wasSpeaking) {\n this.preSpeechBuffer = [];\n }\n\n this.wasSpeaking = isSpeech;\n\n resolve({\n probability: result.probability,\n isSpeech,\n inferenceTimeMs,\n preSpeechChunks,\n });\n } catch (err) {\n reject(err);\n }\n });\n });\n }\n\n async reset(): Promise<void> {\n if (!this._isLoaded) throw new Error('Model not loaded. Call load() first.');\n\n const newState = await this.worker.resetVAD();\n this.state = newState;\n this.context = new Float32Array(this.contextSize);\n this.preSpeechBuffer = [];\n this.wasSpeaking = false;\n }\n\n async dispose(): Promise<void> {\n if (this._isLoaded) {\n await this.worker.disposeVAD();\n this._isLoaded = false;\n }\n this.state = new Float32Array(2 * 1 * 128);\n this.context = new Float32Array(this.contextSize);\n this.preSpeechBuffer = [];\n this.wasSpeaking = false;\n }\n}\n","/**\r\n * Factory function for SenseVoice ASR with automatic Worker vs main thread selection\r\n *\r\n * Provides a unified API that automatically selects the optimal implementation:\r\n * - Worker supported: Uses SenseVoiceWorker (off-main-thread inference)\r\n * - Worker unsupported: Uses SenseVoiceInference (main thread)\r\n *\r\n * @category Inference\r\n *\r\n * @example Auto-detect (recommended)\r\n * ```typescript\r\n * import { createSenseVoice } from '@omote/core';\r\n *\r\n * const asr = createSenseVoice({\r\n * modelUrl: '/models/sensevoice/model.int8.onnx',\r\n * });\r\n * await asr.load();\r\n * const { text, emotion } = await asr.transcribe(audioSamples);\r\n * ```\r\n *\r\n * @example Force worker\r\n * ```typescript\r\n * const asr = createSenseVoice({\r\n * modelUrl: '/models/sensevoice/model.int8.onnx',\r\n * useWorker: true,\r\n * });\r\n * ```\r\n *\r\n * @example Force main thread\r\n * ```typescript\r\n * const asr = createSenseVoice({\r\n * modelUrl: '/models/sensevoice/model.int8.onnx',\r\n * useWorker: false,\r\n * });\r\n * ```\r\n */\r\n\r\nimport { createLogger } from '../logging';\r\nimport { isIOS } from '../utils/runtime';\r\nimport { SenseVoiceInference } from './SenseVoiceInference';\r\nimport type { SenseVoiceLanguage, SenseVoiceResult, SenseVoiceModelInfo } from './SenseVoiceInference';\r\nimport { SenseVoiceWorker } from './SenseVoiceWorker';\r\nimport type { UnifiedInferenceWorker } from './UnifiedInferenceWorker';\r\nimport { SenseVoiceUnifiedAdapter } from './UnifiedInferenceWorker';\r\n\r\nconst logger = createLogger('createSenseVoice');\r\n\r\n/**\r\n * Common interface for both SenseVoiceInference and SenseVoiceWorker\r\n */\r\nexport interface SenseVoiceBackend {\r\n /** Whether the model is loaded and ready for inference */\r\n readonly isLoaded: boolean;\r\n\r\n /** Current backend type ('wasm' for worker, full backend for main thread, null if not loaded) */\r\n readonly backend: 'wasm' | 'webgpu' | null;\r\n\r\n /**\r\n * Load the ONNX model\r\n * @param onProgress - Optional progress callback (fires once at 100% for worker)\r\n * @returns Model loading information\r\n */\r\n load(onProgress?: (loaded: number, total: number) => void): Promise<SenseVoiceModelInfo>;\r\n\r\n /**\r\n * Transcribe audio samples to text\r\n * @param audioSamples - Float32Array of audio samples at 16kHz\r\n * @returns Transcription result\r\n */\r\n transcribe(audioSamples: Float32Array): Promise<SenseVoiceResult>;\r\n\r\n /**\r\n * Dispose of the model and free resources\r\n */\r\n dispose(): Promise<void>;\r\n}\r\n\r\n/**\r\n * Configuration for the SenseVoice factory\r\n */\r\nexport interface CreateSenseVoiceConfig {\r\n /** Path or URL to model.int8.onnx (239MB) */\r\n modelUrl: string;\r\n /** Path or URL to tokens.txt vocabulary file (default: sibling of modelUrl) */\r\n tokensUrl?: string;\r\n /** Language hint (default: 'auto') */\r\n language?: SenseVoiceLanguage;\r\n /** Text normalization (default: 'with_itn') */\r\n textNorm?: 'with_itn' | 'without_itn';\r\n /**\r\n * Worker mode:\r\n * - 'auto' (default): Use Worker if supported, else main thread\r\n * - true: Force Worker (throws if unsupported)\r\n * - false: Force main thread\r\n */\r\n useWorker?: boolean | 'auto';\r\n /**\r\n * Unified inference worker instance.\r\n * When provided, uses SenseVoiceUnifiedAdapter (shared single-ORT worker).\r\n * Takes precedence over useWorker setting.\r\n */\r\n unifiedWorker?: UnifiedInferenceWorker;\r\n}\r\n\r\n/**\r\n * Create a SenseVoice ASR instance with automatic implementation selection\r\n *\r\n * @param config - Factory configuration\r\n * @returns A SenseVoiceBackend instance (either Worker or main thread)\r\n */\r\nexport function createSenseVoice(config: CreateSenseVoiceConfig): SenseVoiceBackend {\r\n // Unified worker takes precedence\r\n if (config.unifiedWorker) {\r\n logger.info('Creating SenseVoiceUnifiedAdapter (shared unified worker)');\r\n return new SenseVoiceUnifiedAdapter(config.unifiedWorker, {\r\n modelUrl: config.modelUrl,\r\n tokensUrl: config.tokensUrl,\r\n language: config.language,\r\n textNorm: config.textNorm,\r\n }) as SenseVoiceBackend;\r\n }\r\n\r\n const useWorker = config.useWorker ?? 'auto';\r\n\r\n if (useWorker === true) {\r\n if (!SenseVoiceWorker.isSupported()) {\r\n throw new Error('Web Workers are not supported in this environment');\r\n }\r\n logger.info('Creating SenseVoiceWorker (off-main-thread)');\r\n return new SenseVoiceWorker({\r\n modelUrl: config.modelUrl,\r\n tokensUrl: config.tokensUrl,\r\n language: config.language,\r\n textNorm: config.textNorm,\r\n }) as SenseVoiceBackend;\r\n }\r\n\r\n if (useWorker === false) {\r\n logger.info('Creating SenseVoiceInference (main thread)');\r\n return new SenseVoiceInference({\r\n modelUrl: config.modelUrl,\r\n tokensUrl: config.tokensUrl,\r\n language: config.language,\r\n textNorm: config.textNorm,\r\n }) as SenseVoiceBackend;\r\n }\r\n\r\n // 'auto': use Worker if supported AND not on iOS.\r\n // iOS WebKit: each Worker loads its own ORT WASM instance from CDN, creating\r\n // separate WASM memory allocations. With SenseVoice + Wav2ArkitCpu workers,\r\n // that's 2-3 ORT runtimes competing for iOS's ~1-1.5GB tab limit → OOM.\r\n // Main thread shares a single ORT instance across all models.\r\n if (SenseVoiceWorker.isSupported() && !isIOS()) {\r\n logger.info('Auto-detected: creating SenseVoiceWorker (off-main-thread)');\r\n return new SenseVoiceWorker({\r\n modelUrl: config.modelUrl,\r\n tokensUrl: config.tokensUrl,\r\n language: config.language,\r\n textNorm: config.textNorm,\r\n }) as SenseVoiceBackend;\r\n }\r\n\r\n logger.info('Auto-detected: creating SenseVoiceInference (main thread)', {\r\n reason: isIOS() ? 'iOS (shared ORT instance)' : 'Worker unsupported',\r\n });\r\n return new SenseVoiceInference({\r\n modelUrl: config.modelUrl,\r\n tokensUrl: config.tokensUrl,\r\n language: config.language,\r\n textNorm: config.textNorm,\r\n }) as SenseVoiceBackend;\r\n}\r\n","/**\r\n * CPU-optimized lip sync inference using wav2arkit_cpu model\r\n *\r\n * A Safari/iOS-compatible alternative to Wav2Vec2Inference (384MB) designed\r\n * for platforms where WebGPU crashes due to ONNX Runtime JSEP bugs.\r\n *\r\n * The model uses ONNX external data format:\r\n * - wav2arkit_cpu.onnx (1.86MB graph structure)\r\n * - wav2arkit_cpu.onnx.data (402MB weights)\r\n * Both files are fetched and cached automatically.\r\n *\r\n * Key differences from Wav2Vec2Inference:\r\n * - WASM-only backend (CPU-optimized, single-threaded, no WebGPU)\r\n * - No identity input (baked to identity 11)\r\n * - No ASR output (lip sync only)\r\n * - Dynamic input length (not fixed to 16000 samples)\r\n * - Different native blendshape ordering (remapped to LAM_BLENDSHAPES)\r\n *\r\n * @category Inference\r\n *\r\n * @example\r\n * ```typescript\r\n * import { Wav2ArkitCpuInference } from '@omote/core';\r\n *\r\n * const lam = new Wav2ArkitCpuInference({\r\n * modelUrl: '/models/wav2arkit_cpu.onnx',\r\n * });\r\n * await lam.load();\r\n *\r\n * const { blendshapes } = await lam.infer(audioSamples);\r\n * // blendshapes: Float32Array[] in LAM_BLENDSHAPES order, 30fps\r\n * ```\r\n */\r\n\r\nimport type { InferenceSession, Tensor } from 'onnxruntime-common';\r\n\r\nimport { fetchWithCache, getModelCache, formatBytes } from '../cache/ModelCache';\r\nimport { createLogger } from '../logging';\r\nimport { getTelemetry } from '../telemetry';\r\nimport {\r\n getOnnxRuntimeForPreference,\r\n getSessionOptions,\r\n type RuntimeBackend,\r\n type OrtModule,\r\n} from './onnxLoader';\r\nimport { BackendPreference, isIOS } from '../utils/runtime';\r\nimport { symmetrizeBlendshapes } from './blendshapeUtils';\r\nimport type { LipSyncBackend, LipSyncModelInfo, LipSyncResult } from './LipSyncBackend';\r\n\r\nconst logger = createLogger('Wav2ArkitCpu');\r\n\r\nexport interface Wav2ArkitCpuConfig {\r\n /** Path or URL to the wav2arkit_cpu ONNX model (.onnx graph file) */\r\n modelUrl: string;\r\n /**\r\n * Path or URL to external model data file (.onnx.data weights).\r\n * Default: `${modelUrl}.data` (e.g., /models/wav2arkit_cpu.onnx.data)\r\n *\r\n * Set to `false` to skip external data loading (single-file models only).\r\n */\r\n externalDataUrl?: string | false;\r\n /** Preferred backend (default: 'wasm' — this model is CPU-optimized) */\r\n backend?: BackendPreference;\r\n}\r\n\r\nexport class Wav2ArkitCpuInference implements LipSyncBackend {\r\n readonly modelId = 'wav2arkit_cpu' as const;\r\n\r\n private session: InferenceSession | null = null;\r\n private ort: OrtModule | null = null;\r\n private config: Wav2ArkitCpuConfig;\r\n private _backend: RuntimeBackend = 'wasm';\r\n private isLoading = false;\r\n\r\n // Inference queue for handling concurrent calls\r\n private inferenceQueue: Promise<void> = Promise.resolve();\r\n\r\n // Session health: set to true if session.run() times out.\r\n // A timed-out session may have a zombie WASM dispatch still running,\r\n // so all future infer() calls reject immediately to prevent concurrent access.\r\n private poisoned = false;\r\n private static readonly INFERENCE_TIMEOUT_MS = 5_000;\r\n\r\n constructor(config: Wav2ArkitCpuConfig) {\r\n this.config = config;\r\n }\r\n\r\n get backend(): RuntimeBackend | null {\r\n return this.session ? this._backend : null;\r\n }\r\n\r\n get isLoaded(): boolean {\r\n return this.session !== null;\r\n }\r\n\r\n /**\r\n * Load the ONNX model\r\n */\r\n async load(): Promise<LipSyncModelInfo> {\r\n if (this.isLoading) {\r\n throw new Error('Model is already loading');\r\n }\r\n\r\n if (this.session) {\r\n throw new Error('Model already loaded. Call dispose() first.');\r\n }\r\n\r\n this.isLoading = true;\r\n const startTime = performance.now();\r\n const telemetry = getTelemetry();\r\n const span = telemetry?.startSpan('Wav2ArkitCpu.load', {\r\n 'model.url': this.config.modelUrl,\r\n 'model.backend_requested': this.config.backend || 'wasm',\r\n });\r\n\r\n try {\r\n // Default to WASM — this model is CPU-optimized\r\n const preference = this.config.backend || 'wasm';\r\n logger.info('Loading ONNX Runtime...', { preference });\r\n\r\n const { ort, backend } = await getOnnxRuntimeForPreference(preference);\r\n this.ort = ort;\r\n this._backend = backend;\r\n\r\n logger.info('ONNX Runtime loaded', { backend: this._backend });\r\n\r\n const modelUrl = this.config.modelUrl;\r\n const dataUrl = this.config.externalDataUrl !== false\r\n ? (this.config.externalDataUrl || `${modelUrl}.data`)\r\n : null;\r\n const sessionOptions = getSessionOptions(this._backend);\r\n\r\n // iOS: Pass URLs directly to ORT to avoid loading 402MB into JS heap.\r\n // ORT fetches externally into WASM memory, cutting peak JS memory from\r\n // ~800MB to ~2MB (just the graph). Desktop uses cached ArrayBuffers for\r\n // faster reloads via IndexedDB.\r\n if (isIOS()) {\r\n logger.info('iOS: passing model URLs directly to ORT (low-memory path)', {\r\n modelUrl,\r\n dataUrl,\r\n });\r\n\r\n if (dataUrl) {\r\n const dataFilename = dataUrl.split('/').pop()!;\r\n (sessionOptions as Record<string, unknown>).externalData = [{\r\n path: dataFilename,\r\n data: dataUrl, // URL string — ORT fetches directly into WASM\r\n }];\r\n }\r\n\r\n this.session = await this.ort!.InferenceSession.create(modelUrl, sessionOptions);\r\n } else {\r\n // Desktop: fetch + cache in IndexedDB for fast reloads\r\n const cache = getModelCache();\r\n const isCached = await cache.has(modelUrl);\r\n\r\n let modelBuffer: ArrayBuffer;\r\n if (isCached) {\r\n logger.debug('Loading model from cache', { modelUrl });\r\n modelBuffer = (await cache.get(modelUrl))!;\r\n\r\n if (!modelBuffer) {\r\n logger.warn('Cache corruption detected, clearing and retrying', { modelUrl });\r\n await cache.delete(modelUrl);\r\n modelBuffer = await fetchWithCache(modelUrl);\r\n }\r\n } else {\r\n logger.debug('Fetching and caching model graph', { modelUrl });\r\n modelBuffer = await fetchWithCache(modelUrl);\r\n }\r\n\r\n if (!modelBuffer) {\r\n throw new Error(`Failed to load model: ${modelUrl}`);\r\n }\r\n\r\n // Load external data file (.onnx.data weights) if present\r\n let externalDataBuffer: ArrayBuffer | null = null;\r\n if (dataUrl) {\r\n try {\r\n const isDataCached = await cache.has(dataUrl);\r\n if (isDataCached) {\r\n logger.debug('Loading external data from cache', { dataUrl });\r\n externalDataBuffer = (await cache.get(dataUrl))!;\r\n if (!externalDataBuffer) {\r\n logger.warn('Cache corruption for external data, retrying', { dataUrl });\r\n await cache.delete(dataUrl);\r\n externalDataBuffer = await fetchWithCache(dataUrl);\r\n }\r\n } else {\r\n logger.info('Fetching external model data', {\r\n dataUrl,\r\n note: 'This may be a large download (400MB+)',\r\n });\r\n externalDataBuffer = await fetchWithCache(dataUrl);\r\n }\r\n logger.info('External data loaded', {\r\n size: formatBytes(externalDataBuffer.byteLength),\r\n });\r\n } catch (err) {\r\n logger.debug('No external data file found (single-file model)', {\r\n dataUrl,\r\n error: (err as Error).message,\r\n });\r\n }\r\n }\r\n\r\n logger.debug('Creating ONNX session', {\r\n graphSize: formatBytes(modelBuffer.byteLength),\r\n externalDataSize: externalDataBuffer ? formatBytes(externalDataBuffer.byteLength) : 'none',\r\n backend: this._backend,\r\n });\r\n\r\n // Pass external data to session if loaded\r\n if (externalDataBuffer) {\r\n const dataFilename = dataUrl!.split('/').pop()!;\r\n (sessionOptions as Record<string, unknown>).externalData = [{\r\n path: dataFilename,\r\n data: new Uint8Array(externalDataBuffer),\r\n }];\r\n }\r\n\r\n const modelData = new Uint8Array(modelBuffer);\r\n this.session = await this.ort!.InferenceSession.create(modelData, sessionOptions);\r\n }\r\n\r\n const loadTimeMs = performance.now() - startTime;\r\n\r\n logger.info('Model loaded successfully', {\r\n backend: this._backend,\r\n loadTimeMs: Math.round(loadTimeMs),\r\n inputs: this.session.inputNames,\r\n outputs: this.session.outputNames,\r\n });\r\n\r\n span?.setAttributes({\r\n 'model.backend': this._backend,\r\n 'model.load_time_ms': loadTimeMs,\r\n 'model.cached': !isIOS(),\r\n });\r\n span?.end();\r\n telemetry?.recordHistogram('omote.model.load_time', loadTimeMs, {\r\n model: 'wav2arkit_cpu',\r\n backend: this._backend,\r\n });\r\n\r\n // Warmup inference\r\n logger.debug('Running warmup inference');\r\n const warmupStart = performance.now();\r\n const silentAudio = new Float32Array(16000);\r\n await this.infer(silentAudio);\r\n const warmupTimeMs = performance.now() - warmupStart;\r\n logger.info('Warmup inference complete', {\r\n warmupTimeMs: Math.round(warmupTimeMs),\r\n backend: this._backend,\r\n });\r\n telemetry?.recordHistogram('omote.model.warmup_time', warmupTimeMs, {\r\n model: 'wav2arkit_cpu',\r\n backend: this._backend,\r\n });\r\n\r\n return {\r\n backend: this._backend,\r\n loadTimeMs,\r\n inputNames: [...this.session.inputNames],\r\n outputNames: [...this.session.outputNames],\r\n };\r\n } catch (error) {\r\n span?.endWithError(error instanceof Error ? error : new Error(String(error)));\r\n telemetry?.incrementCounter('omote.errors.total', 1, {\r\n model: 'wav2arkit_cpu',\r\n error_type: 'load_failed',\r\n });\r\n throw error;\r\n } finally {\r\n this.isLoading = false;\r\n }\r\n }\r\n\r\n /**\r\n * Run inference on raw audio\r\n *\r\n * Accepts variable-length audio (not fixed to 16000 samples).\r\n * Output frames = ceil(30 * numSamples / 16000).\r\n *\r\n * @param audioSamples - Float32Array of raw audio at 16kHz\r\n * @param _identityIndex - Ignored (identity 11 is baked into the model)\r\n */\r\n async infer(\r\n audioSamples: Float32Array,\r\n _identityIndex?: number\r\n ): Promise<LipSyncResult> {\r\n if (!this.session) {\r\n throw new Error('Model not loaded. Call load() first.');\r\n }\r\n\r\n if (this.poisoned) {\r\n throw new Error('Wav2ArkitCpu session timed out — inference unavailable until page reload');\r\n }\r\n\r\n // Force copy to prevent ArrayBuffer detachment\r\n const audioCopy = new Float32Array(audioSamples);\r\n\r\n const feeds = {\r\n 'audio_waveform': new this.ort!.Tensor('float32', audioCopy, [1, audioCopy.length]),\r\n };\r\n\r\n return this.queueInference(feeds, audioCopy.length);\r\n }\r\n\r\n /**\r\n * Queue inference to serialize ONNX session calls\r\n */\r\n private queueInference(\r\n feeds: Record<string, Tensor>,\r\n inputSamples: number\r\n ): Promise<LipSyncResult> {\r\n return new Promise((resolve, reject) => {\r\n this.inferenceQueue = this.inferenceQueue.then(async () => {\r\n const telemetry = getTelemetry();\r\n const span = telemetry?.startSpan('Wav2ArkitCpu.infer', {\r\n 'inference.backend': this._backend,\r\n 'inference.input_samples': inputSamples,\r\n });\r\n try {\r\n const startTime = performance.now();\r\n let timeoutId: ReturnType<typeof setTimeout>;\r\n const results = await Promise.race([\r\n this.session!.run(feeds).then(r => { clearTimeout(timeoutId); return r; }),\r\n new Promise<never>((_, rej) => {\r\n timeoutId = setTimeout(\r\n () => rej(new Error(`Wav2ArkitCpu inference timed out after ${Wav2ArkitCpuInference.INFERENCE_TIMEOUT_MS}ms`)),\r\n Wav2ArkitCpuInference.INFERENCE_TIMEOUT_MS,\r\n );\r\n }),\r\n ]);\r\n const inferenceTimeMs = performance.now() - startTime;\r\n\r\n const blendshapeOutput = results['blendshapes'];\r\n\r\n if (!blendshapeOutput) {\r\n throw new Error('Missing blendshapes output from model');\r\n }\r\n\r\n const blendshapeData = blendshapeOutput.data as Float32Array;\r\n const numFrames = blendshapeOutput.dims[1] as number;\r\n const numBlendshapes = blendshapeOutput.dims[2] as number;\r\n\r\n // Split into per-frame arrays, symmetrize (no clamp — match Wav2Vec2 behavior)\r\n // wav2arkit_cpu outputs in alphabetical order = LAM_BLENDSHAPES order (no remap needed)\r\n const blendshapes: Float32Array[] = [];\r\n for (let f = 0; f < numFrames; f++) {\r\n const rawFrame = blendshapeData.slice(f * numBlendshapes, (f + 1) * numBlendshapes);\r\n const symmetrized = symmetrizeBlendshapes(rawFrame);\r\n blendshapes.push(symmetrized);\r\n }\r\n\r\n logger.trace('Inference completed', {\r\n inferenceTimeMs: Math.round(inferenceTimeMs * 100) / 100,\r\n numFrames,\r\n inputSamples,\r\n });\r\n\r\n span?.setAttributes({\r\n 'inference.duration_ms': inferenceTimeMs,\r\n 'inference.frames': numFrames,\r\n });\r\n span?.end();\r\n telemetry?.recordHistogram('omote.inference.latency', inferenceTimeMs, {\r\n model: 'wav2arkit_cpu',\r\n backend: this._backend,\r\n });\r\n telemetry?.incrementCounter('omote.inference.total', 1, {\r\n model: 'wav2arkit_cpu',\r\n backend: this._backend,\r\n status: 'success',\r\n });\r\n\r\n resolve({\r\n blendshapes,\r\n numFrames,\r\n inferenceTimeMs,\r\n });\r\n } catch (err) {\r\n // Handle timeout — poison session to prevent zombie dispatches\r\n const errMsg = err instanceof Error ? err.message : String(err);\r\n if (errMsg.includes('timed out')) {\r\n this.poisoned = true;\r\n logger.error('CRITICAL: Inference session timed out — Wav2ArkitCpu is dead. Page reload required.', {\r\n backend: this._backend,\r\n timeoutMs: Wav2ArkitCpuInference.INFERENCE_TIMEOUT_MS,\r\n });\r\n } else if (typeof err === 'number') {\r\n // ORT WASM throws raw C++ exception pointers as bare numbers (not Error objects)\r\n // when C++ throws (e.g., std::bad_alloc OOM). Almost always OOM.\r\n const oomError = new Error(\r\n `Wav2ArkitCpu inference failed with raw C++ exception pointer (0x${err.toString(16)}). ` +\r\n `This is likely an OOM crash in WASM. Try reloading the page.`\r\n );\r\n logger.error('ORT WASM OOM — raw C++ exception pointer', {\r\n pointer: `0x${err.toString(16)}`,\r\n backend: this._backend,\r\n });\r\n span?.endWithError(oomError);\r\n telemetry?.incrementCounter('omote.inference.total', 1, {\r\n model: 'wav2arkit_cpu',\r\n backend: this._backend,\r\n status: 'error',\r\n });\r\n reject(oomError);\r\n return;\r\n } else {\r\n logger.error('Inference failed', { error: errMsg, backend: this._backend });\r\n }\r\n span?.endWithError(err instanceof Error ? err : new Error(String(err)));\r\n telemetry?.incrementCounter('omote.inference.total', 1, {\r\n model: 'wav2arkit_cpu',\r\n backend: this._backend,\r\n status: 'error',\r\n });\r\n reject(err);\r\n }\r\n });\r\n });\r\n }\r\n\r\n /**\r\n * Dispose of the model and free resources\r\n */\r\n async dispose(): Promise<void> {\r\n if (this.session) {\r\n await this.session.release();\r\n this.session = null;\r\n }\r\n }\r\n}\r\n","/**\n * Web Worker-based wav2arkit_cpu lip sync inference\n *\n * Runs wav2arkit_cpu inference in a dedicated Web Worker to prevent main thread blocking.\n * Uses inline worker script (Blob URL pattern) to avoid separate file deployment.\n *\n * Key design decisions:\n * - WASM backend only (WebGPU doesn't work in Workers)\n * - Audio copied (not transferred) to retain main thread access\n * - ONNX Runtime loaded from CDN in worker (no bundler complications)\n * - Blendshape symmetrization inlined in worker (no module imports)\n * - iOS: passes model URLs as strings directly to ORT (avoids 400MB+ JS heap)\n *\n * @category Inference\n *\n * @example\n * ```typescript\n * import { Wav2ArkitCpuWorker } from '@omote/core';\n *\n * const lam = new Wav2ArkitCpuWorker({\n * modelUrl: '/models/wav2arkit_cpu.onnx',\n * });\n * await lam.load();\n *\n * const { blendshapes } = await lam.infer(audioSamples);\n * // blendshapes: Float32Array[] in LAM_BLENDSHAPES order, 30fps\n * ```\n */\n\nimport { createLogger } from '../logging';\nimport { getTelemetry } from '../telemetry';\nimport { isIOS } from '../utils/runtime';\nimport type { LipSyncBackend, LipSyncModelInfo, LipSyncResult } from './LipSyncBackend';\n\nconst logger = createLogger('Wav2ArkitCpuWorker');\n\n// ONNX Runtime CDN path (matches onnxLoader.ts)\nconst WASM_CDN_PATH = 'https://cdn.jsdelivr.net/npm/onnxruntime-web@1.23.2/dist/';\n\n// Worker script timeouts\nconst LOAD_TIMEOUT_MS = 60_000; // 60s for 404MB download + ORT init + warmup\nconst INFERENCE_TIMEOUT_MS = 5_000; // 5s, same as Wav2ArkitCpuInference.INFERENCE_TIMEOUT_MS\n\n/**\n * Messages sent from main thread to worker\n */\nexport type Wav2ArkitCpuWorkerMessage =\n | { type: 'load'; modelUrl: string; externalDataUrl: string | null; wasmPaths: string; isIOS: boolean }\n | { type: 'infer'; audio: Float32Array }\n | { type: 'dispose' };\n\n/**\n * Messages sent from worker to main thread\n */\nexport type Wav2ArkitCpuWorkerResult =\n | { type: 'loaded'; inputNames: string[]; outputNames: string[]; loadTimeMs: number }\n | { type: 'result'; blendshapes: Float32Array; numFrames: number; numBlendshapes: number; inferenceTimeMs: number }\n | { type: 'error'; error: string }\n | { type: 'disposed' };\n\n/**\n * Configuration for Wav2ArkitCpu Worker\n */\nexport interface Wav2ArkitCpuWorkerConfig {\n /** Path or URL to the wav2arkit_cpu ONNX model (.onnx graph file) */\n modelUrl: string;\n /**\n * Path or URL to external model data file (.onnx.data weights).\n * Default: `${modelUrl}.data` (e.g., /models/wav2arkit_cpu.onnx.data)\n *\n * Set to `false` to skip external data loading (single-file models only).\n */\n externalDataUrl?: string | false;\n}\n\n/**\n * Inline worker script for wav2arkit_cpu inference\n *\n * This script is embedded as a string and loaded via Blob URL.\n * It loads ONNX Runtime from CDN and runs lip sync inference.\n * Blendshape symmetrization is inlined (no module imports in workers).\n */\n\n/** Resolve a potentially relative URL to absolute (blob workers have no base URL) */\nfunction resolveUrl(url: string): string {\n if (/^https?:\\/\\//i.test(url) || /^blob:/i.test(url)) return url;\n try {\n return new URL(url, globalThis.location?.origin ?? 'https://localhost').href;\n } catch {\n return url;\n }\n}\n\nconst WORKER_SCRIPT = `\n// Wav2ArkitCpu Worker Script\n// Loaded via Blob URL - no separate file needed\n\nvar ort = null;\nvar session = null;\n\n// Precomputed symmetric index pairs from LAM_BLENDSHAPES alphabetical ordering\n// Used to average left/right blendshape pairs for symmetrized output\nconst SYMMETRIC_INDEX_PAIRS = [\n [23, 25], // jawLeft, jawRight\n [32, 38], // mouthLeft, mouthRight\n [43, 44], // mouthSmileLeft, mouthSmileRight\n [29, 30], // mouthFrownLeft, mouthFrownRight\n [27, 28], // mouthDimpleLeft, mouthDimpleRight\n [45, 46], // mouthStretchLeft, mouthStretchRight\n [35, 36], // mouthPressLeft, mouthPressRight\n [47, 48], // mouthUpperUpLeft, mouthUpperUpRight\n [33, 34], // mouthLowerDownLeft, mouthLowerDownRight\n [49, 50], // noseSneerLeft, noseSneerRight\n [6, 7], // cheekSquintLeft, cheekSquintRight\n [0, 1], // browDownLeft, browDownRight\n [3, 4], // browOuterUpLeft, browOuterUpRight\n [8, 9], // eyeBlinkLeft, eyeBlinkRight\n [16, 17], // eyeLookUpLeft, eyeLookUpRight\n [10, 11], // eyeLookDownLeft, eyeLookDownRight\n [12, 13], // eyeLookInLeft, eyeLookInRight\n [14, 15], // eyeLookOutLeft, eyeLookOutRight\n [18, 19], // eyeSquintLeft, eyeSquintRight\n [20, 21], // eyeWideLeft, eyeWideRight\n];\n\n/**\n * Symmetrize blendshapes by averaging left/right pairs\n * Inlined from blendshapeUtils.ts for worker context\n */\nfunction symmetrizeBlendshapes(frame) {\n const result = new Float32Array(frame);\n for (const [lIdx, rIdx] of SYMMETRIC_INDEX_PAIRS) {\n const avg = (frame[lIdx] + frame[rIdx]) / 2;\n result[lIdx] = avg;\n result[rIdx] = avg;\n }\n return result;\n}\n\n/**\n * Load ONNX Runtime from CDN\n */\nasync function loadOrt(wasmPaths) {\n if (ort) return;\n\n // Import ONNX Runtime from CDN\n const ortUrl = wasmPaths + 'ort.wasm.min.js';\n\n // Load the script by fetching and executing it\n const response = await fetch(ortUrl);\n const scriptText = await response.text();\n\n // Create a blob URL for the script\n const blob = new Blob([scriptText], { type: 'application/javascript' });\n const blobUrl = URL.createObjectURL(blob);\n\n // Import the module\n importScripts(blobUrl);\n URL.revokeObjectURL(blobUrl);\n\n // ort is now available as global\n ort = self.ort;\n\n // Configure WASM settings\n ort.env.wasm.wasmPaths = wasmPaths;\n ort.env.wasm.numThreads = 1; // Single thread in worker\n ort.env.wasm.simd = true;\n ort.env.wasm.proxy = false; // No proxy in worker\n}\n\n/**\n * Load the wav2arkit_cpu model\n */\nasync function loadModel(modelUrl, externalDataUrl, isIOS) {\n const sessionOptions = {\n executionProviders: ['wasm'],\n graphOptimizationLevel: 'all',\n };\n\n const dataFilename = externalDataUrl ? externalDataUrl.split('/').pop() : null;\n\n if (isIOS) {\n // iOS: Pass URLs directly to ORT to avoid loading 402MB into JS heap.\n // ORT fetches externally into WASM memory, cutting peak JS memory from\n // ~800MB to ~2MB (just the graph).\n if (externalDataUrl && dataFilename) {\n sessionOptions.externalData = [{ path: dataFilename, data: externalDataUrl }];\n }\n session = await ort.InferenceSession.create(modelUrl, sessionOptions);\n } else {\n // Desktop: fetch model graph as ArrayBuffer\n const graphResponse = await fetch(modelUrl);\n if (!graphResponse.ok) {\n throw new Error('Failed to fetch model graph: ' + graphResponse.status + ' ' + graphResponse.statusText);\n }\n const graphBuffer = await graphResponse.arrayBuffer();\n\n // Fetch external data file if present\n if (externalDataUrl && dataFilename) {\n const dataResponse = await fetch(externalDataUrl);\n if (!dataResponse.ok) {\n throw new Error('Failed to fetch external data: ' + dataResponse.status + ' ' + dataResponse.statusText);\n }\n const dataBuffer = await dataResponse.arrayBuffer();\n sessionOptions.externalData = [{ path: dataFilename, data: new Uint8Array(dataBuffer) }];\n }\n\n session = await ort.InferenceSession.create(new Uint8Array(graphBuffer), sessionOptions);\n }\n\n // Warmup inference with 16000 silent samples\n const warmupAudio = new Float32Array(16000);\n const warmupTensor = new ort.Tensor('float32', warmupAudio, [1, warmupAudio.length]);\n await session.run({ audio_waveform: warmupTensor });\n\n return {\n inputNames: session.inputNames.slice(),\n outputNames: session.outputNames.slice(),\n };\n}\n\n/**\n * Run lip sync inference\n */\nasync function runInference(audio) {\n const tensor = new ort.Tensor('float32', audio, [1, audio.length]);\n const results = await session.run({ audio_waveform: tensor });\n\n const blendshapeOutput = results['blendshapes'];\n if (!blendshapeOutput) {\n throw new Error('Missing blendshapes output from model');\n }\n\n const blendshapeData = blendshapeOutput.data;\n const numFrames = blendshapeOutput.dims[1];\n const numBlendshapes = blendshapeOutput.dims[2];\n\n // Symmetrize each frame and flatten into a single Float32Array for transfer\n const flatBuffer = new Float32Array(numFrames * numBlendshapes);\n for (let f = 0; f < numFrames; f++) {\n const offset = f * numBlendshapes;\n const rawFrame = blendshapeData.slice(offset, offset + numBlendshapes);\n const symmetrized = symmetrizeBlendshapes(rawFrame);\n flatBuffer.set(symmetrized, offset);\n }\n\n return { flatBuffer, numFrames, numBlendshapes };\n}\n\n// Message handler\nself.onmessage = async function(e) {\n const msg = e.data;\n\n try {\n switch (msg.type) {\n case 'load': {\n const startTime = performance.now();\n await loadOrt(msg.wasmPaths);\n const { inputNames, outputNames } = await loadModel(msg.modelUrl, msg.externalDataUrl, msg.isIOS);\n const loadTimeMs = performance.now() - startTime;\n\n self.postMessage({\n type: 'loaded',\n inputNames,\n outputNames,\n loadTimeMs,\n });\n break;\n }\n\n case 'infer': {\n const startTime = performance.now();\n const { flatBuffer, numFrames, numBlendshapes } = await runInference(msg.audio);\n const inferenceTimeMs = performance.now() - startTime;\n\n self.postMessage({\n type: 'result',\n blendshapes: flatBuffer,\n numFrames,\n numBlendshapes,\n inferenceTimeMs,\n }, [flatBuffer.buffer]);\n break;\n }\n\n case 'dispose': {\n if (session) {\n await session.release();\n session = null;\n }\n ort = null;\n self.postMessage({ type: 'disposed' });\n break;\n }\n\n default:\n self.postMessage({\n type: 'error',\n error: 'Unknown message type: ' + msg.type,\n });\n }\n } catch (err) {\n let errorMessage;\n if (typeof err === 'number') {\n // ORT WASM throws raw C++ exception pointers as bare numbers\n errorMessage = 'ORT WASM C++ exception pointer (0x' + err.toString(16) + ') — likely OOM';\n } else {\n errorMessage = err.message || String(err);\n }\n self.postMessage({\n type: 'error',\n error: errorMessage,\n });\n }\n};\n\n// Error handler\nself.onerror = function(err) {\n self.postMessage({\n type: 'error',\n error: 'Worker error: ' + (err.message || String(err)),\n });\n};\n`;\n\n/**\n * Wav2ArkitCpu Worker - Lip sync inference in a Web Worker\n *\n * Runs wav2arkit_cpu inference off the main thread to prevent UI blocking.\n * Feature parity with Wav2ArkitCpuInference but runs in dedicated worker.\n *\n * @see Wav2ArkitCpuInference for main-thread version\n */\nexport class Wav2ArkitCpuWorker implements LipSyncBackend {\n readonly modelId = 'wav2arkit_cpu' as const;\n\n private worker: Worker | null = null;\n private config: Wav2ArkitCpuWorkerConfig;\n private isLoading = false;\n private _isLoaded = false;\n\n // Inference queue for serialization\n private inferenceQueue: Promise<void> = Promise.resolve();\n\n // Session health: set to true if worker inference times out.\n // A timed-out worker may have a zombie WASM dispatch still running,\n // so all future infer() calls reject immediately to prevent concurrent access.\n private poisoned = false;\n\n // Pending message handlers\n private pendingResolvers: Map<string, { resolve: (value: unknown) => void; reject: (error: Error) => void }> = new Map();\n\n constructor(config: Wav2ArkitCpuWorkerConfig) {\n this.config = config;\n }\n\n get isLoaded(): boolean {\n return this._isLoaded;\n }\n\n /**\n * Backend type (always 'wasm' for Worker, WebGPU not supported in Workers)\n */\n get backend(): 'wasm' | null {\n return this._isLoaded ? 'wasm' : null;\n }\n\n /**\n * Create the worker from inline script\n */\n private createWorker(): Worker {\n const blob = new Blob([WORKER_SCRIPT], { type: 'application/javascript' });\n const blobUrl = URL.createObjectURL(blob);\n const worker = new Worker(blobUrl);\n\n // Revoke blob URL after worker is created (worker has its own copy)\n URL.revokeObjectURL(blobUrl);\n\n // Set up message handler\n worker.onmessage = (event: MessageEvent<Wav2ArkitCpuWorkerResult>) => {\n this.handleWorkerMessage(event.data);\n };\n\n // Set up error handler\n worker.onerror = (error) => {\n logger.error('Worker error', { error: error.message });\n // Reject any pending operations\n for (const [, resolver] of this.pendingResolvers) {\n resolver.reject(new Error(`Worker error: ${error.message}`));\n }\n this.pendingResolvers.clear();\n };\n\n return worker;\n }\n\n /**\n * Handle messages from worker\n */\n private handleWorkerMessage(result: Wav2ArkitCpuWorkerResult): void {\n // Route to pending resolver based on result type\n const resolver = this.pendingResolvers.get(result.type);\n if (resolver) {\n this.pendingResolvers.delete(result.type);\n if (result.type === 'error') {\n resolver.reject(new Error(result.error));\n } else {\n resolver.resolve(result);\n }\n }\n }\n\n /**\n * Send message to worker and wait for response\n */\n private sendMessage<T>(message: Wav2ArkitCpuWorkerMessage, expectedType: string, timeoutMs: number): Promise<T> {\n return new Promise((resolve, reject) => {\n if (!this.worker) {\n reject(new Error('Worker not initialized'));\n return;\n }\n\n // Set up timeout\n const timeoutId = setTimeout(() => {\n this.pendingResolvers.delete(expectedType);\n reject(new Error(`Worker operation timed out after ${timeoutMs}ms`));\n }, timeoutMs);\n\n // Register resolver\n this.pendingResolvers.set(expectedType, {\n resolve: (value) => {\n clearTimeout(timeoutId);\n resolve(value as T);\n },\n reject: (error) => {\n clearTimeout(timeoutId);\n reject(error);\n },\n });\n\n // Also listen for errors\n this.pendingResolvers.set('error', {\n resolve: () => {}, // Never called for errors\n reject: (error) => {\n clearTimeout(timeoutId);\n this.pendingResolvers.delete(expectedType);\n reject(error);\n },\n });\n\n // Send message\n this.worker.postMessage(message);\n });\n }\n\n /**\n * Load the ONNX model in the worker\n */\n async load(): Promise<LipSyncModelInfo> {\n if (this.isLoading) {\n throw new Error('Model is already loading');\n }\n\n if (this._isLoaded) {\n throw new Error('Model already loaded. Call dispose() first.');\n }\n\n this.isLoading = true;\n const startTime = performance.now();\n const telemetry = getTelemetry();\n const span = telemetry?.startSpan('Wav2ArkitCpuWorker.load', {\n 'model.url': this.config.modelUrl,\n 'model.backend_requested': 'wasm',\n });\n\n try {\n logger.info('Creating wav2arkit_cpu worker...');\n\n // Create worker\n this.worker = this.createWorker();\n\n // Resolve external data URL\n const externalDataUrl = this.config.externalDataUrl !== false\n ? (this.config.externalDataUrl || `${this.config.modelUrl}.data`)\n : null;\n\n logger.info('Loading model in worker...', {\n modelUrl: this.config.modelUrl,\n externalDataUrl,\n isIOS: isIOS(),\n });\n\n // Send load message to worker\n const result = await this.sendMessage<{\n type: 'loaded';\n inputNames: string[];\n outputNames: string[];\n loadTimeMs: number;\n }>(\n {\n type: 'load',\n modelUrl: resolveUrl(this.config.modelUrl),\n externalDataUrl: externalDataUrl ? resolveUrl(externalDataUrl) : null,\n wasmPaths: WASM_CDN_PATH,\n isIOS: isIOS(),\n },\n 'loaded',\n LOAD_TIMEOUT_MS\n );\n\n this._isLoaded = true;\n\n const loadTimeMs = performance.now() - startTime;\n\n logger.info('Wav2ArkitCpu worker loaded successfully', {\n backend: 'wasm',\n loadTimeMs: Math.round(loadTimeMs),\n workerLoadTimeMs: Math.round(result.loadTimeMs),\n inputs: result.inputNames,\n outputs: result.outputNames,\n });\n\n span?.setAttributes({\n 'model.backend': 'wasm',\n 'model.load_time_ms': loadTimeMs,\n 'model.worker_load_time_ms': result.loadTimeMs,\n });\n span?.end();\n telemetry?.recordHistogram('omote.model.load_time', loadTimeMs, {\n model: 'wav2arkit_cpu-worker',\n backend: 'wasm',\n });\n\n return {\n backend: 'wasm',\n loadTimeMs,\n inputNames: result.inputNames,\n outputNames: result.outputNames,\n };\n } catch (error) {\n span?.endWithError(error instanceof Error ? error : new Error(String(error)));\n telemetry?.incrementCounter('omote.errors.total', 1, {\n model: 'wav2arkit_cpu-worker',\n error_type: 'load_failed',\n });\n\n // Clean up on failure\n if (this.worker) {\n this.worker.terminate();\n this.worker = null;\n }\n\n throw error;\n } finally {\n this.isLoading = false;\n }\n }\n\n /**\n * Run inference on raw audio\n *\n * Accepts variable-length audio (not fixed to 16000 samples).\n * Output frames = ceil(30 * numSamples / 16000).\n *\n * @param audioSamples - Float32Array of raw audio at 16kHz\n * @param _identityIndex - Ignored (identity 11 is baked into the model)\n */\n async infer(\n audioSamples: Float32Array,\n _identityIndex?: number\n ): Promise<LipSyncResult> {\n if (!this._isLoaded || !this.worker) {\n throw new Error('Model not loaded. Call load() first.');\n }\n\n if (this.poisoned) {\n throw new Error('Wav2ArkitCpu worker session timed out — inference unavailable until page reload');\n }\n\n // Force copy to prevent ArrayBuffer detachment\n const audioCopy = new Float32Array(audioSamples);\n\n return this.queueInference(audioCopy);\n }\n\n /**\n * Queue inference to serialize worker calls\n */\n private queueInference(audioSamples: Float32Array): Promise<LipSyncResult> {\n return new Promise((resolve, reject) => {\n this.inferenceQueue = this.inferenceQueue.then(async () => {\n const telemetry = getTelemetry();\n const span = telemetry?.startSpan('Wav2ArkitCpuWorker.infer', {\n 'inference.backend': 'wasm',\n 'inference.input_samples': audioSamples.length,\n });\n\n try {\n const startTime = performance.now();\n\n // Send infer message to worker (copy audio, don't transfer)\n const result = await this.sendMessage<{\n type: 'result';\n blendshapes: Float32Array;\n numFrames: number;\n numBlendshapes: number;\n inferenceTimeMs: number;\n }>(\n {\n type: 'infer',\n audio: audioSamples,\n },\n 'result',\n INFERENCE_TIMEOUT_MS\n );\n\n const inferenceTimeMs = performance.now() - startTime;\n\n // Reconstruct per-frame Float32Array[] from flat buffer\n const flatBuffer = result.blendshapes;\n const { numFrames, numBlendshapes } = result;\n const blendshapes: Float32Array[] = [];\n for (let f = 0; f < numFrames; f++) {\n blendshapes.push(flatBuffer.slice(f * numBlendshapes, (f + 1) * numBlendshapes));\n }\n\n logger.trace('Worker inference completed', {\n inferenceTimeMs: Math.round(inferenceTimeMs * 100) / 100,\n workerTimeMs: Math.round(result.inferenceTimeMs * 100) / 100,\n numFrames,\n inputSamples: audioSamples.length,\n });\n\n span?.setAttributes({\n 'inference.duration_ms': inferenceTimeMs,\n 'inference.worker_duration_ms': result.inferenceTimeMs,\n 'inference.frames': numFrames,\n });\n span?.end();\n telemetry?.recordHistogram('omote.inference.latency', inferenceTimeMs, {\n model: 'wav2arkit_cpu-worker',\n backend: 'wasm',\n });\n telemetry?.incrementCounter('omote.inference.total', 1, {\n model: 'wav2arkit_cpu-worker',\n backend: 'wasm',\n status: 'success',\n });\n\n resolve({\n blendshapes,\n numFrames,\n inferenceTimeMs,\n });\n } catch (err) {\n // Handle timeout — poison worker to prevent zombie dispatches\n const errMsg = err instanceof Error ? err.message : String(err);\n if (errMsg.includes('timed out')) {\n this.poisoned = true;\n logger.error('CRITICAL: Worker inference timed out — Wav2ArkitCpu worker is dead. Page reload required.', {\n backend: 'wasm',\n timeoutMs: INFERENCE_TIMEOUT_MS,\n });\n } else {\n logger.error('Worker inference failed', { error: errMsg, backend: 'wasm' });\n }\n\n span?.endWithError(err instanceof Error ? err : new Error(String(err)));\n telemetry?.incrementCounter('omote.inference.total', 1, {\n model: 'wav2arkit_cpu-worker',\n backend: 'wasm',\n status: 'error',\n });\n reject(err);\n }\n });\n });\n }\n\n /**\n * Dispose of the worker and free resources\n */\n async dispose(): Promise<void> {\n if (this.worker) {\n try {\n // Ask worker to clean up\n await this.sendMessage({ type: 'dispose' }, 'disposed', INFERENCE_TIMEOUT_MS);\n } catch {\n // Ignore errors during dispose\n }\n\n // Terminate worker\n this.worker.terminate();\n this.worker = null;\n }\n\n this._isLoaded = false;\n this.poisoned = false;\n this.pendingResolvers.clear();\n }\n\n /**\n * Check if Web Workers are supported\n */\n static isSupported(): boolean {\n return typeof Worker !== 'undefined';\n }\n}\n","/**\r\n * Factory function for lip sync with automatic GPU/CPU model selection\r\n *\r\n * Provides a unified API that automatically selects the optimal model:\r\n * - Safari (macOS + iOS): Uses Wav2ArkitCpuInference (404MB, WASM)\r\n * - Chrome/Firefox/Edge: Uses Wav2Vec2Inference (384MB, WebGPU)\r\n * - Fallback: Gracefully falls back to CPU model if GPU model fails to load\r\n *\r\n * Why two separate models?\r\n * Wav2Vec2 (LAM) cannot run on Safari/iOS for two reasons:\r\n * 1. Its dual-head transformer graph needs ~750-950MB peak during ORT session\r\n * creation (graph optimization), exceeding iOS WebKit's ~1-1.5GB tab limit.\r\n * 2. It ships as a single 384MB .onnx file that must load into JS heap before\r\n * ORT can consume it. iOS WebKit OOMs on this allocation.\r\n * wav2arkit_cpu solves both: external data format (1.86MB graph + 402MB weights)\r\n * lets ORT load only the tiny graph, then stream weights via URL pass-through\r\n * directly into WASM memory. JS heap stays at ~2MB.\r\n *\r\n * @category Inference\r\n *\r\n * @example Auto-detect (recommended)\r\n * ```typescript\r\n * import { createLipSync } from '@omote/core';\r\n *\r\n * const lam = createLipSync({\r\n * gpuModelUrl: '/models/unified_wav2vec2_asr_a2e.onnx',\r\n * cpuModelUrl: '/models/wav2arkit_cpu.onnx',\r\n * });\r\n *\r\n * await lam.load();\r\n * const { blendshapes } = await lam.infer(audioSamples);\r\n * ```\r\n *\r\n * @example Force CPU model\r\n * ```typescript\r\n * const lam = createLipSync({\r\n * gpuModelUrl: '/models/unified_wav2vec2_asr_a2e.onnx',\r\n * cpuModelUrl: '/models/wav2arkit_cpu.onnx',\r\n * mode: 'cpu',\r\n * });\r\n * ```\r\n */\r\n\r\nimport { createLogger } from '../logging';\r\nimport { shouldUseCpuLipSync, isSafari, isIOS } from '../utils/runtime';\r\nimport { Wav2Vec2Inference } from './Wav2Vec2Inference';\r\nimport { Wav2ArkitCpuInference } from './Wav2ArkitCpuInference';\r\nimport { Wav2ArkitCpuWorker } from './Wav2ArkitCpuWorker';\r\nimport type { LipSyncBackend, LipSyncModelInfo, LipSyncResult } from './LipSyncBackend';\r\nimport type { RuntimeBackend, BackendPreference } from '../utils/runtime';\r\nimport type { UnifiedInferenceWorker } from './UnifiedInferenceWorker';\r\nimport { Wav2ArkitCpuUnifiedAdapter } from './UnifiedInferenceWorker';\r\n\r\nconst logger = createLogger('createLipSync');\r\n\r\n/**\r\n * Configuration for the lip sync factory\r\n */\r\nexport interface CreateLipSyncConfig {\r\n /** URL for the GPU model (Wav2Vec2, used on Chrome/Firefox/Edge) */\r\n gpuModelUrl: string;\r\n /**\r\n * URL for GPU model external data file (.onnx.data weights).\r\n * Default: `${gpuModelUrl}.data`\r\n *\r\n * Set to `false` to skip external data loading (single-file models only).\r\n */\r\n gpuExternalDataUrl?: string | false;\r\n /** URL for the CPU model (wav2arkit_cpu, used on Safari/iOS) */\r\n cpuModelUrl: string;\r\n /**\r\n * Model selection mode:\r\n * - 'auto': Safari/iOS → CPU, everything else → GPU (default)\r\n * - 'gpu': Force GPU model (Wav2Vec2Inference)\r\n * - 'cpu': Force CPU model (Wav2ArkitCpuInference)\r\n */\r\n mode?: 'auto' | 'gpu' | 'cpu';\r\n /** Backend preference for GPU model (default: 'auto') */\r\n gpuBackend?: BackendPreference;\r\n /** Number of identity classes for GPU model (default: 12) */\r\n numIdentityClasses?: number;\r\n /**\r\n * Fall back to CPU model if GPU model fails to load (default: true)\r\n * Only applies when mode is 'auto' or 'gpu'\r\n */\r\n fallbackOnError?: boolean;\r\n /**\r\n * Use Web Worker for CPU model inference (default: false)\r\n *\r\n * When true, Wav2ArkitCpuWorker is used instead of Wav2ArkitCpuInference,\r\n * running inference off the main thread to prevent UI blocking during\r\n * model loading and inference.\r\n *\r\n * Only applies when the CPU model is selected (mode: 'cpu', auto on Safari/iOS,\r\n * or fallback from GPU).\r\n */\r\n useWorker?: boolean;\r\n /**\r\n * Unified inference worker instance.\r\n * When provided and CPU model is selected, uses Wav2ArkitCpuUnifiedAdapter.\r\n * Takes precedence over useWorker setting for the CPU model path.\r\n * GPU model (Wav2Vec2) always stays on main thread (WebGPU).\r\n */\r\n unifiedWorker?: UnifiedInferenceWorker;\r\n}\r\n\r\n/**\r\n * Create a lip sync instance with automatic GPU/CPU model selection\r\n *\r\n * @param config - Factory configuration\r\n * @returns A LipSyncBackend instance (either GPU or CPU model)\r\n */\r\nexport function createLipSync(config: CreateLipSyncConfig): LipSyncBackend {\r\n const mode = config.mode ?? 'auto';\r\n const fallbackOnError = config.fallbackOnError ?? true;\r\n\r\n // Determine which model to use\r\n let useCpu: boolean;\r\n\r\n if (mode === 'cpu') {\r\n useCpu = true;\r\n logger.info('Forcing CPU lip sync model (wav2arkit_cpu)');\r\n } else if (mode === 'gpu') {\r\n useCpu = false;\r\n logger.info('Forcing GPU lip sync model (Wav2Vec2)');\r\n } else {\r\n // Auto-detect: Safari/iOS → CPU, everything else → GPU\r\n useCpu = shouldUseCpuLipSync();\r\n logger.info('Auto-detected lip sync model', {\r\n useCpu,\r\n isSafari: isSafari(),\r\n });\r\n }\r\n\r\n if (useCpu) {\r\n // Unified worker takes precedence (single ORT instance, iOS-safe)\r\n if (config.unifiedWorker) {\r\n logger.info('Creating Wav2ArkitCpuUnifiedAdapter (404MB, WASM, shared unified worker)');\r\n return new Wav2ArkitCpuUnifiedAdapter(config.unifiedWorker, {\r\n modelUrl: config.cpuModelUrl,\r\n });\r\n }\r\n // Skip Worker on iOS — each Worker loads its own ORT WASM from CDN,\r\n // creating separate memory allocations that exceed iOS's ~1-1.5GB tab limit.\r\n if (config.useWorker && Wav2ArkitCpuWorker.isSupported() && !isIOS()) {\r\n logger.info('Creating Wav2ArkitCpuWorker (404MB, WASM, off-main-thread)');\r\n return new Wav2ArkitCpuWorker({\r\n modelUrl: config.cpuModelUrl,\r\n });\r\n }\r\n logger.info('Creating Wav2ArkitCpuInference (404MB, WASM)');\r\n return new Wav2ArkitCpuInference({\r\n modelUrl: config.cpuModelUrl,\r\n });\r\n }\r\n\r\n // GPU model, optionally with fallback\r\n const gpuInstance = new Wav2Vec2Inference({\r\n modelUrl: config.gpuModelUrl,\r\n externalDataUrl: config.gpuExternalDataUrl,\r\n backend: config.gpuBackend ?? 'auto',\r\n numIdentityClasses: config.numIdentityClasses,\r\n });\r\n\r\n if (fallbackOnError) {\r\n logger.info('Creating Wav2Vec2Inference with CPU fallback');\r\n return new LipSyncWithFallback(gpuInstance, config);\r\n }\r\n\r\n logger.info('Creating Wav2Vec2Inference (no fallback)');\r\n return gpuInstance;\r\n}\r\n\r\n/**\r\n * Wrapper that provides automatic fallback from GPU to CPU model\r\n *\r\n * If the GPU model fails during load(), this wrapper automatically\r\n * creates a Wav2ArkitCpuInference instance instead.\r\n */\r\nclass LipSyncWithFallback implements LipSyncBackend {\r\n private implementation: LipSyncBackend;\r\n private readonly config: CreateLipSyncConfig;\r\n private hasFallenBack = false;\r\n\r\n constructor(gpuInstance: Wav2Vec2Inference, config: CreateLipSyncConfig) {\r\n this.implementation = gpuInstance;\r\n this.config = config;\r\n }\r\n\r\n get modelId(): 'wav2vec2' | 'wav2arkit_cpu' {\r\n return this.implementation.modelId;\r\n }\r\n\r\n get backend(): RuntimeBackend | null {\r\n return this.implementation.backend;\r\n }\r\n\r\n get isLoaded(): boolean {\r\n return this.implementation.isLoaded;\r\n }\r\n\r\n async load(): Promise<LipSyncModelInfo> {\r\n try {\r\n return await this.implementation.load();\r\n } catch (error) {\r\n return this.fallbackToCpu(error instanceof Error ? error.message : String(error));\r\n }\r\n }\r\n\r\n private async fallbackToCpu(reason: string): Promise<LipSyncModelInfo> {\r\n logger.warn('GPU model load failed, falling back to CPU model', { reason });\r\n\r\n // Clean up failed GPU instance\r\n try {\r\n await this.implementation.dispose();\r\n } catch {\r\n // Ignore dispose errors\r\n }\r\n\r\n // Create CPU fallback (unified worker > per-model worker > main thread)\r\n if (this.config.unifiedWorker) {\r\n this.implementation = new Wav2ArkitCpuUnifiedAdapter(this.config.unifiedWorker, {\r\n modelUrl: this.config.cpuModelUrl,\r\n });\r\n logger.info('Fallback to Wav2ArkitCpuUnifiedAdapter successful');\r\n } else if (this.config.useWorker && Wav2ArkitCpuWorker.isSupported() && !isIOS()) {\r\n this.implementation = new Wav2ArkitCpuWorker({\r\n modelUrl: this.config.cpuModelUrl,\r\n });\r\n logger.info('Fallback to Wav2ArkitCpuWorker successful');\r\n } else {\r\n this.implementation = new Wav2ArkitCpuInference({\r\n modelUrl: this.config.cpuModelUrl,\r\n });\r\n logger.info('Fallback to Wav2ArkitCpuInference successful');\r\n }\r\n this.hasFallenBack = true;\r\n return await this.implementation.load();\r\n }\r\n\r\n async infer(audioSamples: Float32Array, identityIndex?: number): Promise<LipSyncResult> {\r\n return this.implementation.infer(audioSamples, identityIndex);\r\n }\r\n\r\n async dispose(): Promise<void> {\r\n return this.implementation.dispose();\r\n }\r\n}\r\n","/**\r\n * Silero VAD (Voice Activity Detection) inference\r\n *\r\n * Neural network-based VAD running in browser via ONNX Runtime Web.\r\n * Much more accurate than RMS-based energy detection.\r\n *\r\n * Uses lazy loading to conditionally load WebGPU or WASM-only bundle:\r\n * - iOS: Loads WASM-only bundle (WebGPU crashes due to Safari bugs)\r\n * - Android/Desktop: Loads WebGPU bundle (with WASM fallback)\r\n *\r\n * @category Inference\r\n *\r\n * @example Basic usage\r\n * ```typescript\r\n * import { SileroVADInference } from '@omote/core';\r\n *\r\n * const vad = new SileroVADInference({\r\n * modelUrl: '/models/silero-vad.onnx'\r\n * });\r\n * await vad.load();\r\n *\r\n * // Process 32ms chunks (512 samples at 16kHz)\r\n * const probability = await vad.process(audioChunk);\r\n * if (probability > 0.5) {\r\n * console.log('Speech detected!');\r\n * }\r\n * ```\r\n *\r\n * @example Streaming with state management\r\n * ```typescript\r\n * // State is automatically maintained between process() calls\r\n * // Call reset() when starting a new audio stream\r\n * vad.reset();\r\n *\r\n * for (const chunk of audioChunks) {\r\n * const prob = await vad.process(chunk);\r\n * // prob is speech probability [0, 1]\r\n * }\r\n * ```\r\n */\r\n\r\n// Type-only import for TypeScript (no runtime code loaded at import time)\r\n// At runtime, we dynamically import either 'onnxruntime-web' or 'onnxruntime-web/webgpu'\r\nimport type { InferenceSession, Tensor } from 'onnxruntime-common';\r\n\r\nimport { fetchWithCache, getModelCache, formatBytes } from '../cache/ModelCache';\r\nimport { createLogger } from '../logging';\r\nimport { getTelemetry } from '../telemetry';\r\nimport {\r\n getOnnxRuntimeForPreference,\r\n getSessionOptions,\r\n isWebGPUAvailable,\r\n type RuntimeBackend,\r\n type OrtModule,\r\n} from './onnxLoader';\r\nimport { BackendPreference } from '../utils/runtime';\r\nimport { calculateRMS } from '../animation/audioEnergy';\r\n\r\nconst logger = createLogger('SileroVAD');\r\n\r\nexport type VADBackend = BackendPreference;\r\n\r\n/**\r\n * Configuration for Silero VAD\r\n */\r\nexport interface SileroVADConfig {\r\n /** Path or URL to the ONNX model */\r\n modelUrl: string;\r\n /** Preferred backend (auto will try WebGPU first, fallback to WASM) */\r\n backend?: VADBackend;\r\n /** Sample rate (8000 or 16000, default: 16000) */\r\n sampleRate?: 8000 | 16000;\r\n /** Speech probability threshold (default: 0.5) */\r\n threshold?: number;\r\n /**\r\n * Number of audio chunks to keep in pre-speech buffer.\r\n * When VAD triggers, these chunks are prepended to the speech buffer\r\n * to capture the beginning of speech that occurred before detection.\r\n *\r\n * At 512 samples/chunk and 16kHz:\r\n * - 10 chunks = 320ms of pre-speech audio\r\n * - 15 chunks = 480ms of pre-speech audio\r\n *\r\n * Default: 10 chunks (320ms)\r\n */\r\n preSpeechBufferChunks?: number;\r\n}\r\n\r\n/**\r\n * VAD model loading information\r\n */\r\nexport interface VADModelInfo {\r\n backend: 'webgpu' | 'wasm';\r\n loadTimeMs: number;\r\n inputNames: string[];\r\n outputNames: string[];\r\n sampleRate: number;\r\n chunkSize: number;\r\n}\r\n\r\n/**\r\n * Result from a single VAD inference\r\n */\r\nexport interface VADResult {\r\n /** Speech probability (0-1) */\r\n probability: number;\r\n /** Whether speech is detected (probability > threshold) */\r\n isSpeech: boolean;\r\n /** Inference time in milliseconds */\r\n inferenceTimeMs: number;\r\n /**\r\n * Pre-speech audio chunks (only present on first speech detection).\r\n * These are the N chunks immediately before VAD triggered, useful for\r\n * capturing the beginning of speech that occurred before detection.\r\n *\r\n * Only populated when transitioning from silence to speech.\r\n */\r\n preSpeechChunks?: Float32Array[];\r\n}\r\n\r\n/**\r\n * Speech segment detected by VAD\r\n */\r\nexport interface SpeechSegment {\r\n /** Start time in seconds */\r\n start: number;\r\n /** End time in seconds */\r\n end: number;\r\n /** Average probability during segment */\r\n avgProbability: number;\r\n}\r\n\r\n/**\r\n * Silero VAD - Neural network voice activity detection\r\n *\r\n * Based on snakers4/silero-vad ONNX model.\r\n * Processes 32ms chunks (512 samples at 16kHz) with LSTM state.\r\n *\r\n * @see https://github.com/snakers4/silero-vad\r\n */\r\nexport class SileroVADInference {\r\n private session: InferenceSession | null = null;\r\n private ort: OrtModule | null = null; // Lazy-loaded ONNX Runtime module\r\n private config: Required<SileroVADConfig>;\r\n private _backend: RuntimeBackend = 'wasm';\r\n private isLoading = false;\r\n\r\n // LSTM state tensors [2, batch_size, 128]\r\n private state: Tensor | null = null;\r\n\r\n // Context buffer (prepended to each chunk)\r\n private context: Float32Array;\r\n\r\n // Chunk sizes based on sample rate\r\n private readonly chunkSize: number;\r\n private readonly contextSize: number;\r\n\r\n // Inference queue for serialization\r\n private inferenceQueue: Promise<void> = Promise.resolve();\r\n\r\n // Pre-speech buffer for capturing beginning of speech\r\n private preSpeechBuffer: Float32Array[] = [];\r\n private wasSpeaking = false;\r\n\r\n // Cached sample rate tensor (int64 scalar, never changes per instance)\r\n private srTensor: Tensor | null = null;\r\n\r\n constructor(config: SileroVADConfig) {\r\n const sampleRate = config.sampleRate ?? 16000;\r\n\r\n if (sampleRate !== 8000 && sampleRate !== 16000) {\r\n throw new Error('Silero VAD only supports 8000 or 16000 Hz sample rates');\r\n }\r\n\r\n this.config = {\r\n modelUrl: config.modelUrl,\r\n backend: config.backend ?? 'auto',\r\n sampleRate,\r\n threshold: config.threshold ?? 0.5,\r\n preSpeechBufferChunks: config.preSpeechBufferChunks ?? 10,\r\n };\r\n\r\n // Set chunk sizes based on sample rate\r\n this.chunkSize = sampleRate === 16000 ? 512 : 256;\r\n this.contextSize = sampleRate === 16000 ? 64 : 32;\r\n this.context = new Float32Array(this.contextSize);\r\n }\r\n\r\n get backend(): RuntimeBackend | null {\r\n return this.session ? this._backend : null;\r\n }\r\n\r\n get isLoaded(): boolean {\r\n return this.session !== null;\r\n }\r\n\r\n get sampleRate(): number {\r\n return this.config.sampleRate;\r\n }\r\n\r\n get threshold(): number {\r\n return this.config.threshold;\r\n }\r\n\r\n /**\r\n * Get required chunk size in samples\r\n */\r\n getChunkSize(): number {\r\n return this.chunkSize;\r\n }\r\n\r\n /**\r\n * Get chunk duration in milliseconds\r\n */\r\n getChunkDurationMs(): number {\r\n return (this.chunkSize / this.config.sampleRate) * 1000;\r\n }\r\n\r\n /**\r\n * Check if WebGPU is available and working\r\n * (iOS returns false even if navigator.gpu exists due to ONNX Runtime bugs)\r\n */\r\n static isWebGPUAvailable = isWebGPUAvailable;\r\n\r\n /**\r\n * Load the ONNX model\r\n */\r\n async load(): Promise<VADModelInfo> {\r\n if (this.isLoading) {\r\n throw new Error('Model is already loading');\r\n }\r\n\r\n if (this.session) {\r\n throw new Error('Model already loaded. Call dispose() first.');\r\n }\r\n\r\n this.isLoading = true;\r\n const startTime = performance.now();\r\n const telemetry = getTelemetry();\r\n const span = telemetry?.startSpan('SileroVAD.load', {\r\n 'model.url': this.config.modelUrl,\r\n 'model.backend_requested': this.config.backend,\r\n 'model.sample_rate': this.config.sampleRate,\r\n });\r\n\r\n try {\r\n // Lazy load ONNX Runtime with appropriate backend\r\n // iOS: Loads WASM-only bundle (smaller, no WebGPU code)\r\n // Android/Desktop: Loads WebGPU bundle (with WASM fallback)\r\n logger.info('Loading ONNX Runtime...', { preference: this.config.backend });\r\n\r\n const { ort, backend } = await getOnnxRuntimeForPreference(this.config.backend);\r\n this.ort = ort;\r\n this._backend = backend;\r\n\r\n logger.info('ONNX Runtime loaded', { backend: this._backend });\r\n\r\n // Load model with caching\r\n const cache = getModelCache();\r\n const modelUrl = this.config.modelUrl;\r\n const isCached = await cache.has(modelUrl);\r\n\r\n let modelBuffer: ArrayBuffer;\r\n if (isCached) {\r\n logger.debug('Loading model from cache', { modelUrl });\r\n modelBuffer = (await cache.get(modelUrl))!;\r\n } else {\r\n logger.debug('Fetching and caching model', { modelUrl });\r\n modelBuffer = await fetchWithCache(modelUrl);\r\n }\r\n\r\n logger.debug('Creating ONNX session', {\r\n size: formatBytes(modelBuffer.byteLength),\r\n backend: this._backend,\r\n });\r\n\r\n // Create session with optimized settings for the backend\r\n // Convert ArrayBuffer to Uint8Array for onnxruntime-common types\r\n const sessionOptions = getSessionOptions(this._backend);\r\n const modelData = new Uint8Array(modelBuffer);\r\n this.session = await ort.InferenceSession.create(modelData, sessionOptions);\r\n\r\n // Initialize state\r\n this.reset();\r\n\r\n const loadTimeMs = performance.now() - startTime;\r\n\r\n logger.info('Model loaded successfully', {\r\n backend: this._backend,\r\n loadTimeMs: Math.round(loadTimeMs),\r\n sampleRate: this.config.sampleRate,\r\n chunkSize: this.chunkSize,\r\n threshold: this.config.threshold,\r\n });\r\n\r\n span?.setAttributes({\r\n 'model.backend': this._backend,\r\n 'model.load_time_ms': loadTimeMs,\r\n 'model.cached': isCached,\r\n });\r\n span?.end();\r\n telemetry?.recordHistogram('omote.model.load_time', loadTimeMs, {\r\n model: 'silero-vad',\r\n backend: this._backend,\r\n });\r\n\r\n return {\r\n backend: this._backend,\r\n loadTimeMs,\r\n inputNames: [...this.session.inputNames],\r\n outputNames: [...this.session.outputNames],\r\n sampleRate: this.config.sampleRate,\r\n chunkSize: this.chunkSize,\r\n };\r\n } catch (error) {\r\n span?.endWithError(error instanceof Error ? error : new Error(String(error)));\r\n telemetry?.incrementCounter('omote.errors.total', 1, {\r\n model: 'silero-vad',\r\n error_type: 'load_failed',\r\n });\r\n throw error;\r\n } finally {\r\n this.isLoading = false;\r\n }\r\n }\r\n\r\n /**\r\n * Reset state for new audio stream\r\n */\r\n reset(): void {\r\n if (!this.ort) {\r\n throw new Error('ONNX Runtime not loaded. Call load() first.');\r\n }\r\n // LSTM state: [2, batch_size=1, 128]\r\n this.state = new this.ort.Tensor('float32', new Float32Array(2 * 1 * 128), [2, 1, 128]);\r\n // Reset context buffer\r\n this.context = new Float32Array(this.contextSize);\r\n // Reset pre-speech buffer\r\n this.preSpeechBuffer = [];\r\n this.wasSpeaking = false;\r\n\r\n // Create cached sr tensor once (int64 scalar, never changes)\r\n if (!this.srTensor) {\r\n try {\r\n this.srTensor = new this.ort.Tensor(\r\n 'int64',\r\n new BigInt64Array([BigInt(this.config.sampleRate)]),\r\n []\r\n );\r\n } catch (e) {\r\n // Fallback: some iOS Safari versions may not support BigInt64Array\r\n // ORT also accepts readonly bigint[] for int64 tensors\r\n logger.warn('BigInt64Array not available, using bigint array fallback', {\r\n error: e instanceof Error ? e.message : String(e),\r\n });\r\n this.srTensor = new this.ort.Tensor(\r\n 'int64',\r\n [BigInt(this.config.sampleRate)] as unknown as BigInt64Array,\r\n []\r\n );\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Process a single audio chunk\r\n *\r\n * @param audioChunk - Float32Array of exactly chunkSize samples (512 for 16kHz, 256 for 8kHz)\r\n * @returns VAD result with speech probability\r\n */\r\n async process(audioChunk: Float32Array): Promise<VADResult> {\r\n if (!this.session) {\r\n throw new Error('Model not loaded. Call load() first.');\r\n }\r\n\r\n if (audioChunk.length !== this.chunkSize) {\r\n throw new Error(\r\n `Audio chunk must be exactly ${this.chunkSize} samples (got ${audioChunk.length}). ` +\r\n `Use getChunkSize() to get required size.`\r\n );\r\n }\r\n\r\n return this.queueInference(audioChunk);\r\n }\r\n\r\n /**\r\n * Process audio and detect speech segments\r\n *\r\n * @param audio - Complete audio buffer\r\n * @param options - Detection options\r\n * @returns Array of speech segments\r\n */\r\n async detectSpeech(\r\n audio: Float32Array,\r\n options: {\r\n /** Minimum speech duration in ms (default: 250) */\r\n minSpeechDurationMs?: number;\r\n /** Minimum silence duration to end segment in ms (default: 300) */\r\n minSilenceDurationMs?: number;\r\n /** Padding to add before/after speech in ms (default: 30) */\r\n speechPadMs?: number;\r\n } = {}\r\n ): Promise<SpeechSegment[]> {\r\n const {\r\n minSpeechDurationMs = 250,\r\n minSilenceDurationMs = 300,\r\n speechPadMs = 30,\r\n } = options;\r\n\r\n this.reset();\r\n\r\n const segments: SpeechSegment[] = [];\r\n const chunkDurationMs = this.getChunkDurationMs();\r\n const minSpeechChunks = Math.ceil(minSpeechDurationMs / chunkDurationMs);\r\n const minSilenceChunks = Math.ceil(minSilenceDurationMs / chunkDurationMs);\r\n const padChunks = Math.ceil(speechPadMs / chunkDurationMs);\r\n\r\n let inSpeech = false;\r\n let speechStart = 0;\r\n let silenceCount = 0;\r\n let speechChunks = 0;\r\n let totalProb = 0;\r\n\r\n // Process in chunks\r\n for (let i = 0; i + this.chunkSize <= audio.length; i += this.chunkSize) {\r\n const chunk = audio.slice(i, i + this.chunkSize);\r\n const result = await this.process(chunk);\r\n const chunkIndex = i / this.chunkSize;\r\n const timeMs = chunkIndex * chunkDurationMs;\r\n\r\n if (result.isSpeech) {\r\n if (!inSpeech) {\r\n // Start of speech\r\n inSpeech = true;\r\n speechStart = Math.max(0, timeMs - speechPadMs);\r\n silenceCount = 0;\r\n speechChunks = 0;\r\n totalProb = 0;\r\n }\r\n silenceCount = 0;\r\n speechChunks++;\r\n totalProb += result.probability;\r\n } else if (inSpeech) {\r\n silenceCount++;\r\n if (silenceCount >= minSilenceChunks) {\r\n // End of speech\r\n if (speechChunks >= minSpeechChunks) {\r\n segments.push({\r\n start: speechStart / 1000,\r\n end: (timeMs + speechPadMs) / 1000,\r\n avgProbability: totalProb / speechChunks,\r\n });\r\n }\r\n inSpeech = false;\r\n }\r\n }\r\n }\r\n\r\n // Handle trailing speech\r\n if (inSpeech && speechChunks >= minSpeechChunks) {\r\n const endMs = (audio.length / this.config.sampleRate) * 1000;\r\n segments.push({\r\n start: speechStart / 1000,\r\n end: endMs / 1000,\r\n avgProbability: totalProb / speechChunks,\r\n });\r\n }\r\n\r\n return segments;\r\n }\r\n\r\n /**\r\n * Queue inference to serialize ONNX session calls\r\n */\r\n private queueInference(audioChunk: Float32Array): Promise<VADResult> {\r\n // CRITICAL: Force copy IMMEDIATELY to prevent ArrayBuffer detachment\r\n // During interruptions, audioChunk's buffer may get detached by ONNX Runtime\r\n // before we access it in the async queue. Copy synchronously to preserve data.\r\n const audioChunkCopy = new Float32Array(audioChunk);\r\n\r\n // Energy pre-filter: skip inference on very quiet audio\r\n // This prevents false positives from blank/silent chunks and saves compute\r\n const MIN_ENERGY_THRESHOLD = 0.001; // Very low threshold - only filters near-silence\r\n const rms = calculateRMS(audioChunkCopy);\r\n if (rms < MIN_ENERGY_THRESHOLD) {\r\n // Update pre-speech buffer even for silent chunks (ring buffer)\r\n if (!this.wasSpeaking) {\r\n this.preSpeechBuffer.push(new Float32Array(audioChunkCopy));\r\n if (this.preSpeechBuffer.length > this.config.preSpeechBufferChunks) {\r\n this.preSpeechBuffer.shift();\r\n }\r\n }\r\n\r\n logger.trace('Skipping VAD inference - audio too quiet', {\r\n rms: Math.round(rms * 10000) / 10000,\r\n threshold: MIN_ENERGY_THRESHOLD,\r\n });\r\n\r\n return Promise.resolve({\r\n probability: 0,\r\n isSpeech: false,\r\n inferenceTimeMs: 0,\r\n });\r\n }\r\n\r\n return new Promise((resolve, reject) => {\r\n this.inferenceQueue = this.inferenceQueue.then(async () => {\r\n const telemetry = getTelemetry();\r\n const span = telemetry?.startSpan('SileroVAD.process', {\r\n 'inference.backend': this._backend,\r\n 'inference.chunk_size': this.chunkSize,\r\n });\r\n try {\r\n const startTime = performance.now();\r\n\r\n // Prepend context to input\r\n const inputSize = this.contextSize + this.chunkSize;\r\n const inputBuffer = new Float32Array(inputSize);\r\n inputBuffer.set(this.context, 0);\r\n inputBuffer.set(audioChunkCopy, this.contextSize);\r\n\r\n // Create tensors\r\n // CRITICAL: Force copy to prevent ArrayBuffer detachment by ONNX Runtime Web workers\r\n // Without copy, WASM backend transfers buffers to workers, causing \"memory access out of bounds\" errors\r\n const inputBufferCopy = new Float32Array(inputBuffer);\r\n const inputTensor = new this.ort!.Tensor('float32', inputBufferCopy, [1, inputSize]);\r\n // Use cached sr tensor (created once in reset(), handles BigInt64Array compatibility)\r\n const srTensor = this.srTensor!;\r\n\r\n // CRITICAL: Also copy state tensor to prevent detachment\r\n // State tensor is reused across inferences and gets detached during interruptions\r\n const stateCopy = new Float32Array(this.state!.data as Float32Array);\r\n const stateTensor = new this.ort!.Tensor('float32', stateCopy, this.state!.dims as number[]);\r\n\r\n const feeds = {\r\n 'input': inputTensor,\r\n 'state': stateTensor,\r\n 'sr': srTensor,\r\n };\r\n\r\n // Run inference\r\n const results = await this.session!.run(feeds);\r\n\r\n // Extract outputs\r\n const outputTensor = results['output'];\r\n const newStateTensor = results['stateN'] || results['state'];\r\n\r\n if (!outputTensor) {\r\n throw new Error('Missing output tensor from VAD model');\r\n }\r\n\r\n const probability = (outputTensor.data as Float32Array)[0];\r\n\r\n // Update state for next call\r\n if (newStateTensor) {\r\n this.state = new this.ort!.Tensor(\r\n 'float32',\r\n new Float32Array(newStateTensor.data as Float32Array),\r\n [2, 1, 128]\r\n );\r\n }\r\n\r\n // Update context (last contextSize samples of input chunk)\r\n // CRITICAL: Use audioChunkCopy (not audioChunk) — original may be detached after session.run()\r\n this.context = audioChunkCopy.slice(-this.contextSize);\r\n\r\n const inferenceTimeMs = performance.now() - startTime;\r\n const isSpeech = probability > this.config.threshold;\r\n\r\n // Pre-speech buffer logic\r\n let preSpeechChunks: Float32Array[] | undefined;\r\n\r\n if (isSpeech && !this.wasSpeaking) {\r\n // Silence→Speech transition: populate preSpeechChunks\r\n preSpeechChunks = [...this.preSpeechBuffer];\r\n this.preSpeechBuffer = [];\r\n logger.debug('Speech started with pre-speech buffer', {\r\n preSpeechChunks: preSpeechChunks.length,\r\n durationMs: Math.round(preSpeechChunks.length * this.getChunkDurationMs()),\r\n });\r\n } else if (!isSpeech && !this.wasSpeaking) {\r\n // Still in silence: maintain ring buffer\r\n // CRITICAL: Use audioChunkCopy (not audioChunk) — original may be detached after session.run()\r\n this.preSpeechBuffer.push(new Float32Array(audioChunkCopy));\r\n if (this.preSpeechBuffer.length > this.config.preSpeechBufferChunks) {\r\n this.preSpeechBuffer.shift();\r\n }\r\n } else if (!isSpeech && this.wasSpeaking) {\r\n // Speech→Silence transition: clear buffer\r\n this.preSpeechBuffer = [];\r\n }\r\n\r\n this.wasSpeaking = isSpeech;\r\n\r\n logger.trace('VAD inference completed', {\r\n probability: Math.round(probability * 1000) / 1000,\r\n isSpeech,\r\n inferenceTimeMs: Math.round(inferenceTimeMs * 100) / 100,\r\n });\r\n\r\n span?.setAttributes({\r\n 'inference.duration_ms': inferenceTimeMs,\r\n 'inference.probability': probability,\r\n 'inference.is_speech': isSpeech,\r\n });\r\n span?.end();\r\n telemetry?.recordHistogram('omote.inference.latency', inferenceTimeMs, {\r\n model: 'silero-vad',\r\n backend: this._backend,\r\n });\r\n telemetry?.incrementCounter('omote.inference.total', 1, {\r\n model: 'silero-vad',\r\n backend: this._backend,\r\n status: 'success',\r\n });\r\n\r\n resolve({\r\n probability,\r\n isSpeech,\r\n inferenceTimeMs,\r\n preSpeechChunks,\r\n });\r\n } catch (err) {\r\n // ORT WASM throws raw C++ exception pointers as bare numbers (not Error objects)\r\n // when C++ throws (e.g., std::bad_alloc OOM). typeof err === 'number' means\r\n // it's a raw memory address — almost always OOM.\r\n if (typeof err === 'number') {\r\n const oomError = new Error(\r\n `SileroVAD inference failed with raw C++ exception pointer (0x${err.toString(16)}). ` +\r\n `This is likely an OOM crash in WASM. Try reducing concurrent model sessions or reloading the page.`\r\n );\r\n logger.error('ORT WASM OOM — raw C++ exception pointer', {\r\n pointer: `0x${err.toString(16)}`,\r\n backend: this._backend,\r\n });\r\n span?.endWithError(oomError);\r\n telemetry?.incrementCounter('omote.inference.total', 1, {\r\n model: 'silero-vad',\r\n backend: this._backend,\r\n status: 'error',\r\n });\r\n reject(oomError);\r\n } else {\r\n span?.endWithError(err instanceof Error ? err : new Error(String(err)));\r\n telemetry?.incrementCounter('omote.inference.total', 1, {\r\n model: 'silero-vad',\r\n backend: this._backend,\r\n status: 'error',\r\n });\r\n reject(err);\r\n }\r\n }\r\n });\r\n });\r\n }\r\n\r\n /**\r\n * Dispose of the model and free resources\r\n */\r\n async dispose(): Promise<void> {\r\n if (this.session) {\r\n await this.session.release();\r\n this.session = null;\r\n }\r\n this.state = null;\r\n this.srTensor = null;\r\n }\r\n}\r\n","/**\r\n * Silero VAD Web Worker implementation\r\n *\r\n * Runs Silero VAD inference in a dedicated Web Worker to prevent main thread blocking.\r\n * Uses inline worker script (Blob URL pattern) to avoid separate file deployment.\r\n *\r\n * Key design decisions:\r\n * - WASM backend only (WebGPU doesn't work in Workers)\r\n * - LSTM state serialized as Float32Array (Tensors can't cross worker boundary)\r\n * - Audio copied (not transferred) to retain main thread access for pre-speech buffer\r\n * - ONNX Runtime loaded from CDN in worker (no bundler complications)\r\n *\r\n * @category Inference\r\n *\r\n * @example Basic usage\r\n * ```typescript\r\n * import { SileroVADWorker } from '@omote/core';\r\n *\r\n * const vad = new SileroVADWorker({\r\n * modelUrl: '/models/silero-vad.onnx'\r\n * });\r\n * await vad.load();\r\n *\r\n * // Process 32ms chunks (512 samples at 16kHz)\r\n * const result = await vad.process(audioChunk);\r\n * if (result.isSpeech) {\r\n * console.log('Speech detected!', result.probability);\r\n * }\r\n * ```\r\n */\r\n\r\nimport { createLogger } from '../logging';\r\nimport { getTelemetry } from '../telemetry';\r\nimport type { VADResult } from './SileroVADInference';\r\n\r\nconst logger = createLogger('SileroVADWorker');\r\n\r\n// ONNX Runtime CDN path (matches onnxLoader.ts)\r\nconst WASM_CDN_PATH = 'https://cdn.jsdelivr.net/npm/onnxruntime-web@1.23.2/dist/';\r\n\r\n// Worker script timeouts\r\nconst LOAD_TIMEOUT_MS = 10000; // 10 seconds for model load\r\nconst INFERENCE_TIMEOUT_MS = 1000; // 1 second for inference\r\n\r\n/**\r\n * Messages sent from main thread to worker\r\n */\r\nexport type VADWorkerMessage =\r\n | { type: 'load'; modelUrl: string; sampleRate: 8000 | 16000; wasmPaths: string }\r\n | { type: 'process'; audio: Float32Array; state: Float32Array; context: Float32Array }\r\n | { type: 'reset' }\r\n | { type: 'dispose' };\r\n\r\n/**\r\n * Messages sent from worker to main thread\r\n */\r\nexport type VADWorkerResult =\r\n | { type: 'loaded'; inputNames: string[]; outputNames: string[]; loadTimeMs: number }\r\n | { type: 'result'; probability: number; state: Float32Array; inferenceTimeMs: number }\r\n | { type: 'reset'; state: Float32Array }\r\n | { type: 'error'; error: string }\r\n | { type: 'disposed' };\r\n\r\n/**\r\n * Configuration for Silero VAD Worker\r\n */\r\nexport interface VADWorkerConfig {\r\n /** Path or URL to the ONNX model */\r\n modelUrl: string;\r\n /** Sample rate (8000 or 16000, default: 16000) */\r\n sampleRate?: 8000 | 16000;\r\n /** Speech probability threshold (default: 0.5) */\r\n threshold?: number;\r\n /**\r\n * Number of audio chunks to keep in pre-speech buffer.\r\n * When VAD triggers, these chunks are prepended to the speech buffer\r\n * to capture the beginning of speech that occurred before detection.\r\n *\r\n * At 512 samples/chunk and 16kHz:\r\n * - 10 chunks = 320ms of pre-speech audio\r\n * - 15 chunks = 480ms of pre-speech audio\r\n *\r\n * Default: 10 chunks (320ms)\r\n */\r\n preSpeechBufferChunks?: number;\r\n}\r\n\r\n/**\r\n * VAD model loading information from worker\r\n */\r\nexport interface VADWorkerModelInfo {\r\n backend: 'wasm'; // Worker always uses WASM (no WebGPU in workers)\r\n loadTimeMs: number;\r\n inputNames: string[];\r\n outputNames: string[];\r\n sampleRate: number;\r\n chunkSize: number;\r\n}\r\n\r\n// VADResult is re-exported from SileroVADInference (canonical definition)\r\nexport type { VADResult } from './SileroVADInference';\r\n\r\n/**\r\n * Inline worker script for VAD inference\r\n *\r\n * This script is embedded as a string and loaded via Blob URL.\r\n * It loads ONNX Runtime from CDN and runs VAD inference.\r\n */\r\n\r\n/** Resolve a potentially relative URL to absolute (blob workers have no base URL) */\r\nfunction resolveUrl(url: string): string {\r\n if (/^https?:\\/\\//i.test(url) || /^blob:/i.test(url)) return url;\r\n try {\r\n return new URL(url, globalThis.location?.origin ?? 'https://localhost').href;\r\n } catch {\r\n return url;\r\n }\r\n}\r\n\r\nconst WORKER_SCRIPT = `\r\n// Silero VAD Worker Script\r\n// Loaded via Blob URL - no separate file needed\r\n\r\nvar ort = null;\r\nvar session = null;\r\nvar sampleRate = 16000;\r\nvar chunkSize = 512;\r\nvar contextSize = 64;\r\n\r\n/**\r\n * Load ONNX Runtime from CDN\r\n */\r\nasync function loadOrt(wasmPaths) {\r\n if (ort) return;\r\n\r\n // Import ONNX Runtime from CDN\r\n // Using dynamic import with full CDN URL\r\n const ortUrl = wasmPaths + 'ort.wasm.min.js';\r\n\r\n // Load the script by fetching and executing it\r\n const response = await fetch(ortUrl);\r\n const scriptText = await response.text();\r\n\r\n // Create a blob URL for the script\r\n const blob = new Blob([scriptText], { type: 'application/javascript' });\r\n const blobUrl = URL.createObjectURL(blob);\r\n\r\n // Import the module\r\n importScripts(blobUrl);\r\n URL.revokeObjectURL(blobUrl);\r\n\r\n // ort is now available as global\r\n ort = self.ort;\r\n\r\n // Configure WASM settings\r\n ort.env.wasm.wasmPaths = wasmPaths;\r\n ort.env.wasm.numThreads = 1; // Single thread in worker\r\n ort.env.wasm.simd = true;\r\n ort.env.wasm.proxy = false; // No proxy in worker\r\n}\r\n\r\n/**\r\n * Load the VAD model\r\n */\r\nasync function loadModel(modelUrl, sr) {\r\n sampleRate = sr;\r\n chunkSize = sr === 16000 ? 512 : 256;\r\n contextSize = sr === 16000 ? 64 : 32;\r\n\r\n // Fetch model data\r\n const response = await fetch(modelUrl);\r\n if (!response.ok) {\r\n throw new Error('Failed to fetch model: ' + response.status + ' ' + response.statusText);\r\n }\r\n const modelBuffer = await response.arrayBuffer();\r\n const modelData = new Uint8Array(modelBuffer);\r\n\r\n // Create session with WASM backend\r\n session = await ort.InferenceSession.create(modelData, {\r\n executionProviders: ['wasm'],\r\n graphOptimizationLevel: 'all',\r\n });\r\n\r\n return {\r\n inputNames: session.inputNames,\r\n outputNames: session.outputNames,\r\n };\r\n}\r\n\r\n/**\r\n * Create initial LSTM state\r\n */\r\nfunction createInitialState() {\r\n return new Float32Array(2 * 1 * 128); // [2, 1, 128]\r\n}\r\n\r\n/**\r\n * Run VAD inference\r\n */\r\nasync function runInference(audio, state, context) {\r\n const inputSize = contextSize + chunkSize;\r\n\r\n // Prepend context to input\r\n const inputBuffer = new Float32Array(inputSize);\r\n inputBuffer.set(context, 0);\r\n inputBuffer.set(audio, contextSize);\r\n\r\n // Create tensors\r\n const inputTensor = new ort.Tensor('float32', new Float32Array(inputBuffer), [1, inputSize]);\r\n const stateTensor = new ort.Tensor('float32', new Float32Array(state), [2, 1, 128]);\r\n // Use BigInt64Array constructor (not .from()) for broader compatibility\r\n let srTensor;\r\n try {\r\n srTensor = new ort.Tensor('int64', new BigInt64Array([BigInt(sampleRate)]), []);\r\n } catch (e) {\r\n // Fallback for environments without BigInt64Array support\r\n srTensor = new ort.Tensor('int64', [BigInt(sampleRate)], []);\r\n }\r\n\r\n const feeds = {\r\n 'input': inputTensor,\r\n 'state': stateTensor,\r\n 'sr': srTensor,\r\n };\r\n\r\n // Run inference\r\n const results = await session.run(feeds);\r\n\r\n // Extract outputs\r\n const outputTensor = results['output'];\r\n const newStateTensor = results['stateN'] || results['state'];\r\n\r\n if (!outputTensor) {\r\n throw new Error('Missing output tensor from VAD model');\r\n }\r\n\r\n const probability = outputTensor.data[0];\r\n const newState = new Float32Array(newStateTensor.data);\r\n\r\n return { probability, newState };\r\n}\r\n\r\n// Message handler\r\nself.onmessage = async function(e) {\r\n const msg = e.data;\r\n\r\n try {\r\n switch (msg.type) {\r\n case 'load': {\r\n const startTime = performance.now();\r\n await loadOrt(msg.wasmPaths);\r\n const { inputNames, outputNames } = await loadModel(msg.modelUrl, msg.sampleRate);\r\n const loadTimeMs = performance.now() - startTime;\r\n\r\n self.postMessage({\r\n type: 'loaded',\r\n inputNames,\r\n outputNames,\r\n loadTimeMs,\r\n });\r\n break;\r\n }\r\n\r\n case 'process': {\r\n const startTime = performance.now();\r\n const { probability, newState } = await runInference(msg.audio, msg.state, msg.context);\r\n const inferenceTimeMs = performance.now() - startTime;\r\n\r\n self.postMessage({\r\n type: 'result',\r\n probability,\r\n state: newState,\r\n inferenceTimeMs,\r\n });\r\n break;\r\n }\r\n\r\n case 'reset': {\r\n const state = createInitialState();\r\n self.postMessage({\r\n type: 'reset',\r\n state,\r\n });\r\n break;\r\n }\r\n\r\n case 'dispose': {\r\n if (session) {\r\n await session.release();\r\n session = null;\r\n }\r\n ort = null;\r\n self.postMessage({ type: 'disposed' });\r\n break;\r\n }\r\n\r\n default:\r\n self.postMessage({\r\n type: 'error',\r\n error: 'Unknown message type: ' + msg.type,\r\n });\r\n }\r\n } catch (err) {\r\n self.postMessage({\r\n type: 'error',\r\n error: err.message || String(err),\r\n });\r\n }\r\n};\r\n\r\n// Error handler\r\nself.onerror = function(err) {\r\n self.postMessage({\r\n type: 'error',\r\n error: 'Worker error: ' + (err.message || String(err)),\r\n });\r\n};\r\n`;\r\n\r\n/**\r\n * Silero VAD Worker - Voice Activity Detection in a Web Worker\r\n *\r\n * Runs Silero VAD inference off the main thread to prevent UI blocking.\r\n * Feature parity with SileroVADInference but runs in dedicated worker.\r\n *\r\n * @see SileroVADInference for main-thread version\r\n */\r\nexport class SileroVADWorker {\r\n private worker: Worker | null = null;\r\n private config: Required<VADWorkerConfig>;\r\n private isLoading = false;\r\n private _isLoaded = false;\r\n\r\n // LSTM state (kept in main thread, sent to worker for each inference)\r\n private state: Float32Array;\r\n\r\n // Context buffer (last 64 samples for 16kHz, 32 for 8kHz)\r\n private context: Float32Array;\r\n\r\n // Chunk sizes based on sample rate\r\n private readonly chunkSize: number;\r\n private readonly contextSize: number;\r\n\r\n // Inference queue for serialization\r\n private inferenceQueue: Promise<void> = Promise.resolve();\r\n\r\n // Pre-speech buffer for capturing beginning of speech\r\n private preSpeechBuffer: Float32Array[] = [];\r\n private wasSpeaking = false;\r\n\r\n // Pending message handlers\r\n private pendingResolvers: Map<string, { resolve: (value: unknown) => void; reject: (error: Error) => void }> = new Map();\r\n private messageId = 0;\r\n\r\n constructor(config: VADWorkerConfig) {\r\n const sampleRate = config.sampleRate ?? 16000;\r\n\r\n if (sampleRate !== 8000 && sampleRate !== 16000) {\r\n throw new Error('Silero VAD only supports 8000 or 16000 Hz sample rates');\r\n }\r\n\r\n this.config = {\r\n modelUrl: config.modelUrl,\r\n sampleRate,\r\n threshold: config.threshold ?? 0.5,\r\n preSpeechBufferChunks: config.preSpeechBufferChunks ?? 10,\r\n };\r\n\r\n // Set chunk sizes based on sample rate\r\n this.chunkSize = sampleRate === 16000 ? 512 : 256;\r\n this.contextSize = sampleRate === 16000 ? 64 : 32;\r\n\r\n // Initialize state and context\r\n this.state = new Float32Array(2 * 1 * 128); // [2, 1, 128]\r\n this.context = new Float32Array(this.contextSize);\r\n }\r\n\r\n get isLoaded(): boolean {\r\n return this._isLoaded;\r\n }\r\n\r\n /**\r\n * Backend type (always 'wasm' for Worker, WebGPU not supported in Workers)\r\n */\r\n get backend(): 'wasm' | null {\r\n return this._isLoaded ? 'wasm' : null;\r\n }\r\n\r\n get sampleRate(): number {\r\n return this.config.sampleRate;\r\n }\r\n\r\n get threshold(): number {\r\n return this.config.threshold;\r\n }\r\n\r\n /**\r\n * Get required chunk size in samples\r\n */\r\n getChunkSize(): number {\r\n return this.chunkSize;\r\n }\r\n\r\n /**\r\n * Get chunk duration in milliseconds\r\n */\r\n getChunkDurationMs(): number {\r\n return (this.chunkSize / this.config.sampleRate) * 1000;\r\n }\r\n\r\n /**\r\n * Create the worker from inline script\r\n */\r\n private createWorker(): Worker {\r\n const blob = new Blob([WORKER_SCRIPT], { type: 'application/javascript' });\r\n const blobUrl = URL.createObjectURL(blob);\r\n const worker = new Worker(blobUrl);\r\n\r\n // Revoke blob URL after worker is created (worker has its own copy)\r\n URL.revokeObjectURL(blobUrl);\r\n\r\n // Set up message handler\r\n worker.onmessage = (event: MessageEvent<VADWorkerResult>) => {\r\n this.handleWorkerMessage(event.data);\r\n };\r\n\r\n // Set up error handler\r\n worker.onerror = (error) => {\r\n logger.error('Worker error', { error: error.message });\r\n // Reject any pending operations\r\n for (const [, resolver] of this.pendingResolvers) {\r\n resolver.reject(new Error(`Worker error: ${error.message}`));\r\n }\r\n this.pendingResolvers.clear();\r\n };\r\n\r\n return worker;\r\n }\r\n\r\n /**\r\n * Handle messages from worker\r\n */\r\n private handleWorkerMessage(result: VADWorkerResult): void {\r\n // Route to pending resolver based on result type\r\n const resolver = this.pendingResolvers.get(result.type);\r\n if (resolver) {\r\n this.pendingResolvers.delete(result.type);\r\n if (result.type === 'error') {\r\n resolver.reject(new Error(result.error));\r\n } else {\r\n resolver.resolve(result);\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Send message to worker and wait for response\r\n */\r\n private sendMessage<T>(message: VADWorkerMessage, expectedType: string, timeoutMs: number): Promise<T> {\r\n return new Promise((resolve, reject) => {\r\n if (!this.worker) {\r\n reject(new Error('Worker not initialized'));\r\n return;\r\n }\r\n\r\n // Set up timeout\r\n const timeoutId = setTimeout(() => {\r\n this.pendingResolvers.delete(expectedType);\r\n reject(new Error(`Worker operation timed out after ${timeoutMs}ms`));\r\n }, timeoutMs);\r\n\r\n // Register resolver\r\n this.pendingResolvers.set(expectedType, {\r\n resolve: (value) => {\r\n clearTimeout(timeoutId);\r\n resolve(value as T);\r\n },\r\n reject: (error) => {\r\n clearTimeout(timeoutId);\r\n reject(error);\r\n },\r\n });\r\n\r\n // Also listen for errors\r\n this.pendingResolvers.set('error', {\r\n resolve: () => {}, // Never called for errors\r\n reject: (error) => {\r\n clearTimeout(timeoutId);\r\n this.pendingResolvers.delete(expectedType);\r\n reject(error);\r\n },\r\n });\r\n\r\n // Send message\r\n this.worker.postMessage(message);\r\n });\r\n }\r\n\r\n /**\r\n * Load the ONNX model in the worker\r\n */\r\n async load(): Promise<VADWorkerModelInfo> {\r\n if (this.isLoading) {\r\n throw new Error('Model is already loading');\r\n }\r\n\r\n if (this._isLoaded) {\r\n throw new Error('Model already loaded. Call dispose() first.');\r\n }\r\n\r\n this.isLoading = true;\r\n const startTime = performance.now();\r\n const telemetry = getTelemetry();\r\n const span = telemetry?.startSpan('SileroVADWorker.load', {\r\n 'model.url': this.config.modelUrl,\r\n 'model.sample_rate': this.config.sampleRate,\r\n });\r\n\r\n try {\r\n logger.info('Creating VAD worker...');\r\n\r\n // Create worker\r\n this.worker = this.createWorker();\r\n\r\n logger.info('Loading model in worker...', {\r\n modelUrl: this.config.modelUrl,\r\n sampleRate: this.config.sampleRate,\r\n });\r\n\r\n // Send load message to worker\r\n const result = await this.sendMessage<{\r\n type: 'loaded';\r\n inputNames: string[];\r\n outputNames: string[];\r\n loadTimeMs: number;\r\n }>(\r\n {\r\n type: 'load',\r\n modelUrl: resolveUrl(this.config.modelUrl),\r\n sampleRate: this.config.sampleRate,\r\n wasmPaths: WASM_CDN_PATH,\r\n },\r\n 'loaded',\r\n LOAD_TIMEOUT_MS\r\n );\r\n\r\n this._isLoaded = true;\r\n\r\n const loadTimeMs = performance.now() - startTime;\r\n\r\n logger.info('VAD worker loaded successfully', {\r\n backend: 'wasm',\r\n loadTimeMs: Math.round(loadTimeMs),\r\n workerLoadTimeMs: Math.round(result.loadTimeMs),\r\n sampleRate: this.config.sampleRate,\r\n chunkSize: this.chunkSize,\r\n threshold: this.config.threshold,\r\n });\r\n\r\n span?.setAttributes({\r\n 'model.backend': 'wasm',\r\n 'model.load_time_ms': loadTimeMs,\r\n 'model.worker_load_time_ms': result.loadTimeMs,\r\n });\r\n span?.end();\r\n telemetry?.recordHistogram('omote.model.load_time', loadTimeMs, {\r\n model: 'silero-vad-worker',\r\n backend: 'wasm',\r\n });\r\n\r\n return {\r\n backend: 'wasm',\r\n loadTimeMs,\r\n inputNames: result.inputNames,\r\n outputNames: result.outputNames,\r\n sampleRate: this.config.sampleRate,\r\n chunkSize: this.chunkSize,\r\n };\r\n } catch (error) {\r\n span?.endWithError(error instanceof Error ? error : new Error(String(error)));\r\n telemetry?.incrementCounter('omote.errors.total', 1, {\r\n model: 'silero-vad-worker',\r\n error_type: 'load_failed',\r\n });\r\n\r\n // Clean up on failure\r\n if (this.worker) {\r\n this.worker.terminate();\r\n this.worker = null;\r\n }\r\n\r\n throw error;\r\n } finally {\r\n this.isLoading = false;\r\n }\r\n }\r\n\r\n /**\r\n * Reset state for new audio stream\r\n */\r\n async reset(): Promise<void> {\r\n if (!this._isLoaded || !this.worker) {\r\n throw new Error('Worker not loaded. Call load() first.');\r\n }\r\n\r\n // Request reset from worker to get fresh state\r\n const result = await this.sendMessage<{ type: 'reset'; state: Float32Array }>(\r\n { type: 'reset' },\r\n 'reset',\r\n INFERENCE_TIMEOUT_MS\r\n );\r\n\r\n // Update local state\r\n this.state = result.state;\r\n this.context = new Float32Array(this.contextSize);\r\n this.preSpeechBuffer = [];\r\n this.wasSpeaking = false;\r\n }\r\n\r\n /**\r\n * Process a single audio chunk\r\n *\r\n * @param audioChunk - Float32Array of exactly chunkSize samples (512 for 16kHz, 256 for 8kHz)\r\n * @returns VAD result with speech probability\r\n */\r\n async process(audioChunk: Float32Array): Promise<VADResult> {\r\n if (!this._isLoaded || !this.worker) {\r\n throw new Error('Worker not loaded. Call load() first.');\r\n }\r\n\r\n if (audioChunk.length !== this.chunkSize) {\r\n throw new Error(\r\n `Audio chunk must be exactly ${this.chunkSize} samples (got ${audioChunk.length}). ` +\r\n `Use getChunkSize() to get required size.`\r\n );\r\n }\r\n\r\n return this.queueInference(audioChunk);\r\n }\r\n\r\n /**\r\n * Queue inference to serialize worker calls\r\n */\r\n private queueInference(audioChunk: Float32Array): Promise<VADResult> {\r\n // CRITICAL: Force copy IMMEDIATELY to prevent ArrayBuffer detachment\r\n const audioChunkCopy = new Float32Array(audioChunk);\r\n\r\n return new Promise((resolve, reject) => {\r\n this.inferenceQueue = this.inferenceQueue.then(async () => {\r\n const telemetry = getTelemetry();\r\n const span = telemetry?.startSpan('SileroVADWorker.process', {\r\n 'inference.backend': 'wasm',\r\n 'inference.chunk_size': this.chunkSize,\r\n });\r\n\r\n try {\r\n const startTime = performance.now();\r\n\r\n // Send process message to worker\r\n const result = await this.sendMessage<{\r\n type: 'result';\r\n probability: number;\r\n state: Float32Array;\r\n inferenceTimeMs: number;\r\n }>(\r\n {\r\n type: 'process',\r\n audio: audioChunkCopy,\r\n state: this.state,\r\n context: this.context,\r\n },\r\n 'result',\r\n INFERENCE_TIMEOUT_MS\r\n );\r\n\r\n // Update local state from worker result\r\n this.state = result.state;\r\n\r\n // Update context (last contextSize samples of input chunk)\r\n this.context = audioChunkCopy.slice(-this.contextSize);\r\n\r\n const inferenceTimeMs = performance.now() - startTime;\r\n const isSpeech = result.probability > this.config.threshold;\r\n\r\n // Pre-speech buffer logic (same as SileroVADInference)\r\n let preSpeechChunks: Float32Array[] | undefined;\r\n\r\n if (isSpeech && !this.wasSpeaking) {\r\n // Silence→Speech transition: populate preSpeechChunks\r\n preSpeechChunks = [...this.preSpeechBuffer];\r\n this.preSpeechBuffer = [];\r\n logger.debug('Speech started with pre-speech buffer', {\r\n preSpeechChunks: preSpeechChunks.length,\r\n durationMs: Math.round(preSpeechChunks.length * this.getChunkDurationMs()),\r\n });\r\n } else if (!isSpeech && !this.wasSpeaking) {\r\n // Still in silence: maintain ring buffer\r\n this.preSpeechBuffer.push(new Float32Array(audioChunkCopy));\r\n if (this.preSpeechBuffer.length > this.config.preSpeechBufferChunks) {\r\n this.preSpeechBuffer.shift();\r\n }\r\n } else if (!isSpeech && this.wasSpeaking) {\r\n // Speech→Silence transition: clear buffer\r\n this.preSpeechBuffer = [];\r\n }\r\n\r\n this.wasSpeaking = isSpeech;\r\n\r\n logger.trace('VAD worker inference completed', {\r\n probability: Math.round(result.probability * 1000) / 1000,\r\n isSpeech,\r\n inferenceTimeMs: Math.round(inferenceTimeMs * 100) / 100,\r\n workerTimeMs: Math.round(result.inferenceTimeMs * 100) / 100,\r\n });\r\n\r\n span?.setAttributes({\r\n 'inference.duration_ms': inferenceTimeMs,\r\n 'inference.worker_duration_ms': result.inferenceTimeMs,\r\n 'inference.probability': result.probability,\r\n 'inference.is_speech': isSpeech,\r\n });\r\n span?.end();\r\n telemetry?.recordHistogram('omote.inference.latency', inferenceTimeMs, {\r\n model: 'silero-vad-worker',\r\n backend: 'wasm',\r\n });\r\n telemetry?.incrementCounter('omote.inference.total', 1, {\r\n model: 'silero-vad-worker',\r\n backend: 'wasm',\r\n status: 'success',\r\n });\r\n\r\n resolve({\r\n probability: result.probability,\r\n isSpeech,\r\n inferenceTimeMs,\r\n preSpeechChunks,\r\n });\r\n } catch (err) {\r\n span?.endWithError(err instanceof Error ? err : new Error(String(err)));\r\n telemetry?.incrementCounter('omote.inference.total', 1, {\r\n model: 'silero-vad-worker',\r\n backend: 'wasm',\r\n status: 'error',\r\n });\r\n reject(err);\r\n }\r\n });\r\n });\r\n }\r\n\r\n /**\r\n * Dispose of the worker and free resources\r\n */\r\n async dispose(): Promise<void> {\r\n if (this.worker) {\r\n try {\r\n // Ask worker to clean up\r\n await this.sendMessage({ type: 'dispose' }, 'disposed', INFERENCE_TIMEOUT_MS);\r\n } catch {\r\n // Ignore errors during dispose\r\n }\r\n\r\n // Terminate worker\r\n this.worker.terminate();\r\n this.worker = null;\r\n }\r\n\r\n this._isLoaded = false;\r\n this.state = new Float32Array(2 * 1 * 128);\r\n this.context = new Float32Array(this.contextSize);\r\n this.preSpeechBuffer = [];\r\n this.wasSpeaking = false;\r\n this.pendingResolvers.clear();\r\n }\r\n\r\n /**\r\n * Check if Web Workers are supported\r\n */\r\n static isSupported(): boolean {\r\n return typeof Worker !== 'undefined';\r\n }\r\n}\r\n","/**\r\n * Factory function for Silero VAD with automatic Worker vs main thread selection\r\n *\r\n * Provides a unified API that automatically selects the optimal implementation:\r\n * - Desktop browsers: Uses SileroVADWorker (off-main-thread inference)\r\n * - Mobile devices: Uses SileroVADInference (main thread, avoids memory overhead)\r\n * - Fallback: Gracefully falls back to main thread if Worker fails\r\n *\r\n * @category Inference\r\n *\r\n * @example Basic usage (auto-detect)\r\n * ```typescript\r\n * import { createSileroVAD } from '@omote/core';\r\n *\r\n * const vad = createSileroVAD({\r\n * modelUrl: '/models/silero-vad.onnx',\r\n * threshold: 0.5,\r\n * });\r\n *\r\n * await vad.load();\r\n * const result = await vad.process(audioChunk);\r\n * if (result.isSpeech) {\r\n * console.log('Speech detected!', result.probability);\r\n * }\r\n * ```\r\n *\r\n * @example Force worker usage\r\n * ```typescript\r\n * const vad = createSileroVAD({\r\n * modelUrl: '/models/silero-vad.onnx',\r\n * useWorker: true, // Force Worker even on mobile\r\n * });\r\n * ```\r\n *\r\n * @example Force main thread\r\n * ```typescript\r\n * const vad = createSileroVAD({\r\n * modelUrl: '/models/silero-vad.onnx',\r\n * useWorker: false, // Force main thread\r\n * });\r\n * ```\r\n */\r\n\r\nimport { createLogger } from '../logging';\r\nimport { isMobile } from '../utils/runtime';\r\nimport { SileroVADInference } from './SileroVADInference';\r\nimport type { SileroVADConfig, VADModelInfo, VADResult } from './SileroVADInference';\r\nimport { SileroVADWorker } from './SileroVADWorker';\r\nimport type { VADWorkerModelInfo } from './SileroVADWorker';\r\nimport type { RuntimeBackend } from '../utils/runtime';\r\nimport type { UnifiedInferenceWorker } from './UnifiedInferenceWorker';\r\nimport { SileroVADUnifiedAdapter } from './UnifiedInferenceWorker';\r\n\r\nconst logger = createLogger('createSileroVAD');\r\n\r\n/**\r\n * Common interface for both SileroVADInference and SileroVADWorker\r\n *\r\n * This interface defines the shared API that both implementations provide,\r\n * allowing consumers to use either interchangeably.\r\n */\r\nexport interface SileroVADBackend {\r\n /** Current backend type (webgpu, wasm, or null if not loaded) */\r\n readonly backend: RuntimeBackend | null;\r\n\r\n /** Whether the model is loaded and ready for inference */\r\n readonly isLoaded: boolean;\r\n\r\n /** Audio sample rate (8000 or 16000 Hz) */\r\n readonly sampleRate: number;\r\n\r\n /** Speech detection threshold (0-1) */\r\n readonly threshold: number;\r\n\r\n /**\r\n * Load the ONNX model\r\n * @returns Model loading information\r\n */\r\n load(): Promise<VADModelInfo | VADWorkerModelInfo>;\r\n\r\n /**\r\n * Process a single audio chunk\r\n * @param audioChunk - Float32Array of exactly chunkSize samples\r\n * @returns VAD result with speech probability\r\n */\r\n process(audioChunk: Float32Array): Promise<VADResult>;\r\n\r\n /**\r\n * Reset state for new audio stream\r\n */\r\n reset(): void | Promise<void>;\r\n\r\n /**\r\n * Dispose of the model and free resources\r\n */\r\n dispose(): Promise<void>;\r\n\r\n /**\r\n * Get required chunk size in samples\r\n */\r\n getChunkSize(): number;\r\n\r\n /**\r\n * Get chunk duration in milliseconds\r\n */\r\n getChunkDurationMs(): number;\r\n}\r\n\r\n/**\r\n * Configuration for the Silero VAD factory\r\n *\r\n * Extends SileroVADConfig with worker-specific options.\r\n */\r\nexport interface SileroVADFactoryConfig extends SileroVADConfig {\r\n /**\r\n * Force worker usage (true), main thread (false), or auto-detect (undefined).\r\n *\r\n * Auto-detection behavior:\r\n * - Desktop: Uses Worker (better responsiveness, off-main-thread)\r\n * - Mobile: Uses main thread (avoids 5MB memory overhead)\r\n *\r\n * You can override this to:\r\n * - `true`: Force Worker even on mobile (if you have memory headroom)\r\n * - `false`: Force main thread even on desktop (for debugging)\r\n *\r\n * Default: undefined (auto-detect)\r\n */\r\n useWorker?: boolean;\r\n\r\n /**\r\n * Fallback to main thread on worker errors.\r\n *\r\n * When true (default), if the Worker fails to load or encounters an error,\r\n * the factory will automatically create a main thread instance instead.\r\n *\r\n * When false, worker errors will propagate as exceptions.\r\n *\r\n * Default: true\r\n */\r\n fallbackOnError?: boolean;\r\n\r\n /**\r\n * Unified inference worker instance.\r\n * When provided, uses SileroVADUnifiedAdapter (shared single-ORT worker).\r\n * Takes precedence over useWorker setting.\r\n */\r\n unifiedWorker?: UnifiedInferenceWorker;\r\n}\r\n\r\n/**\r\n * Check if the current environment supports VAD Web Workers\r\n *\r\n * Requirements:\r\n * - Worker constructor must exist\r\n * - Blob URL support (for inline worker script)\r\n *\r\n * @returns true if VAD Worker is supported\r\n */\r\nexport function supportsVADWorker(): boolean {\r\n // Check Worker constructor exists\r\n if (typeof Worker === 'undefined') {\r\n logger.debug('Worker not supported: Worker constructor undefined');\r\n return false;\r\n }\r\n\r\n // Check Blob URL support (needed for inline worker script)\r\n if (typeof URL === 'undefined' || typeof URL.createObjectURL === 'undefined') {\r\n logger.debug('Worker not supported: URL.createObjectURL unavailable');\r\n return false;\r\n }\r\n\r\n // Check Blob support\r\n if (typeof Blob === 'undefined') {\r\n logger.debug('Worker not supported: Blob constructor unavailable');\r\n return false;\r\n }\r\n\r\n return true;\r\n}\r\n\r\n/**\r\n * Create a Silero VAD instance with automatic implementation selection\r\n *\r\n * This factory function automatically selects between:\r\n * - **SileroVADWorker**: Off-main-thread inference (better for desktop)\r\n * - **SileroVADInference**: Main thread inference (better for mobile)\r\n *\r\n * The selection is based on:\r\n * 1. Explicit `useWorker` config (if provided)\r\n * 2. Platform detection (mobile vs desktop)\r\n * 3. Worker API availability\r\n *\r\n * Both implementations share the same interface (SileroVADBackend),\r\n * so consumers can use either interchangeably.\r\n *\r\n * @param config - Factory configuration\r\n * @returns A SileroVAD instance (either Worker or main thread)\r\n *\r\n * @example\r\n * ```typescript\r\n * // Auto-detect (recommended)\r\n * const vad = createSileroVAD({ modelUrl: '/models/silero-vad.onnx' });\r\n *\r\n * // Force Worker\r\n * const vadWorker = createSileroVAD({ modelUrl: '/models/silero-vad.onnx', useWorker: true });\r\n *\r\n * // Force main thread\r\n * const vadMain = createSileroVAD({ modelUrl: '/models/silero-vad.onnx', useWorker: false });\r\n * ```\r\n */\r\nexport function createSileroVAD(config: SileroVADFactoryConfig): SileroVADBackend {\r\n // Unified worker takes precedence\r\n if (config.unifiedWorker) {\r\n logger.info('Creating SileroVADUnifiedAdapter (shared unified worker)');\r\n return new SileroVADUnifiedAdapter(config.unifiedWorker, config) as SileroVADBackend;\r\n }\r\n\r\n const fallbackOnError = config.fallbackOnError ?? true;\r\n\r\n // Determine whether to use Worker\r\n let useWorker: boolean;\r\n\r\n if (config.useWorker !== undefined) {\r\n // Explicit preference\r\n useWorker = config.useWorker;\r\n logger.debug('Worker preference explicitly set', { useWorker });\r\n } else {\r\n // Auto-detect based on platform and support\r\n const workerSupported = supportsVADWorker();\r\n const onMobile = isMobile();\r\n\r\n // Desktop with Worker support: use Worker\r\n // Mobile: use main thread (memory overhead concern)\r\n useWorker = workerSupported && !onMobile;\r\n\r\n logger.debug('Auto-detected Worker preference', {\r\n useWorker,\r\n workerSupported,\r\n onMobile,\r\n });\r\n }\r\n\r\n // Create the appropriate implementation\r\n if (useWorker) {\r\n logger.info('Creating SileroVADWorker (off-main-thread)');\r\n const worker = new SileroVADWorker({\r\n modelUrl: config.modelUrl,\r\n sampleRate: config.sampleRate,\r\n threshold: config.threshold,\r\n preSpeechBufferChunks: config.preSpeechBufferChunks,\r\n });\r\n\r\n if (fallbackOnError) {\r\n // Wrap with fallback behavior\r\n return new VADWorkerWithFallback(worker, config);\r\n }\r\n\r\n return worker as SileroVADBackend;\r\n }\r\n\r\n logger.info('Creating SileroVADInference (main thread)');\r\n return new SileroVADInference(config) as SileroVADBackend;\r\n}\r\n\r\n/**\r\n * Wrapper that provides automatic fallback from Worker to main thread\r\n *\r\n * If the Worker fails during load(), this wrapper will automatically\r\n * create a main thread SileroVADInference instance instead.\r\n */\r\nclass VADWorkerWithFallback implements SileroVADBackend {\r\n private implementation: SileroVADBackend;\r\n private readonly config: SileroVADFactoryConfig;\r\n private hasFallenBack = false;\r\n\r\n constructor(worker: SileroVADWorker, config: SileroVADFactoryConfig) {\r\n this.implementation = worker as SileroVADBackend;\r\n this.config = config;\r\n }\r\n\r\n get backend(): RuntimeBackend | null {\r\n // Worker always uses WASM, but hasn't loaded yet\r\n if (!this.isLoaded) return null;\r\n return this.hasFallenBack ? (this.implementation as SileroVADInference).backend : 'wasm';\r\n }\r\n\r\n get isLoaded(): boolean {\r\n return this.implementation.isLoaded;\r\n }\r\n\r\n get sampleRate(): number {\r\n return this.implementation.sampleRate;\r\n }\r\n\r\n get threshold(): number {\r\n return this.implementation.threshold;\r\n }\r\n\r\n async load(): Promise<VADModelInfo | VADWorkerModelInfo> {\r\n try {\r\n return await this.implementation.load();\r\n } catch (error) {\r\n logger.warn('Worker load failed, falling back to main thread', {\r\n error: error instanceof Error ? error.message : String(error),\r\n });\r\n\r\n // Clean up failed worker\r\n try {\r\n await this.implementation.dispose();\r\n } catch {\r\n // Ignore dispose errors\r\n }\r\n\r\n // Create main thread fallback\r\n this.implementation = new SileroVADInference(this.config) as SileroVADBackend;\r\n this.hasFallenBack = true;\r\n\r\n logger.info('Fallback to SileroVADInference successful');\r\n return await this.implementation.load();\r\n }\r\n }\r\n\r\n async process(audioChunk: Float32Array): Promise<VADResult> {\r\n return this.implementation.process(audioChunk);\r\n }\r\n\r\n reset(): void | Promise<void> {\r\n return this.implementation.reset();\r\n }\r\n\r\n async dispose(): Promise<void> {\r\n return this.implementation.dispose();\r\n }\r\n\r\n getChunkSize(): number {\r\n return this.implementation.getChunkSize();\r\n }\r\n\r\n getChunkDurationMs(): number {\r\n return this.implementation.getChunkDurationMs();\r\n }\r\n}\r\n","/**\r\n * Safari Web Speech API wrapper for iOS speech recognition\r\n *\r\n * Provides a similar interface to WhisperInference for easy substitution on iOS.\r\n * Uses the native Web Speech API which is significantly faster than Whisper WASM on iOS.\r\n *\r\n * Key differences from WhisperInference:\r\n * - Real-time streaming (not batch processing)\r\n * - No audio buffer input (microphone handled by browser)\r\n * - transcribe() throws error (use start/stop pattern instead)\r\n *\r\n * @category Inference\r\n *\r\n * @example Basic usage\r\n * ```typescript\r\n * import { SafariSpeechRecognition, shouldUseNativeASR } from '@omote/core';\r\n *\r\n * // Use native ASR on iOS, Whisper elsewhere\r\n * if (shouldUseNativeASR()) {\r\n * const speech = new SafariSpeechRecognition({ language: 'en-US' });\r\n *\r\n * speech.onResult((result) => {\r\n * console.log('Transcript:', result.text);\r\n * });\r\n *\r\n * await speech.start();\r\n * // ... user speaks ...\r\n * const finalResult = await speech.stop();\r\n * }\r\n * ```\r\n *\r\n * @example Platform-aware initialization\r\n * ```typescript\r\n * const asr = shouldUseNativeASR()\r\n * ? new SafariSpeechRecognition({ language: 'en-US' })\r\n * : new WhisperInference({ model: 'tiny' });\r\n * ```\r\n */\r\n\r\nimport { createLogger } from '../logging';\r\nimport { getTelemetry } from '../telemetry';\r\nimport { isSpeechRecognitionAvailable } from '../utils/runtime';\r\n\r\nconst logger = createLogger('SafariSpeech');\r\n\r\n/**\r\n * Configuration for Safari Speech Recognition\r\n */\r\nexport interface SafariSpeechConfig {\r\n /** Language code (default: 'en-US') */\r\n language?: string;\r\n /** Continuous mode for ongoing conversation (default: true) */\r\n continuous?: boolean;\r\n /** Interim results before speech ends (default: true) */\r\n interimResults?: boolean;\r\n /** Max alternatives (default: 1) */\r\n maxAlternatives?: number;\r\n}\r\n\r\n/**\r\n * Result from speech recognition (matches WhisperInference TranscriptionResult)\r\n */\r\nexport interface SpeechRecognitionResult {\r\n /** Transcribed text */\r\n text: string;\r\n /** Detected/used language */\r\n language: string;\r\n /** Time since start in ms (not inference time - native API) */\r\n inferenceTimeMs: number;\r\n /** Whether this is a final result or interim */\r\n isFinal: boolean;\r\n /** Confidence score (0-1) if available */\r\n confidence?: number;\r\n}\r\n\r\n/**\r\n * Callback for receiving recognition results\r\n */\r\nexport type SpeechResultCallback = (result: SpeechRecognitionResult) => void;\r\n\r\n/**\r\n * Callback for receiving recognition errors\r\n */\r\nexport type SpeechErrorCallback = (error: Error) => void;\r\n\r\n// Type declarations for Web Speech API (not in lib.dom.d.ts by default)\r\ninterface SpeechRecognitionEvent extends Event {\r\n resultIndex: number;\r\n results: SpeechRecognitionResultList;\r\n}\r\n\r\ninterface SpeechRecognitionResultList {\r\n length: number;\r\n item(index: number): SpeechRecognitionResult;\r\n [index: number]: SpeechRecognitionResultItem;\r\n}\r\n\r\ninterface SpeechRecognitionResultItem {\r\n isFinal: boolean;\r\n length: number;\r\n item(index: number): SpeechRecognitionAlternative;\r\n [index: number]: SpeechRecognitionAlternative;\r\n}\r\n\r\ninterface SpeechRecognitionAlternative {\r\n transcript: string;\r\n confidence: number;\r\n}\r\n\r\ninterface SpeechRecognitionErrorEvent extends Event {\r\n error: string;\r\n message: string;\r\n}\r\n\r\ninterface SpeechRecognitionInterface extends EventTarget {\r\n continuous: boolean;\r\n interimResults: boolean;\r\n lang: string;\r\n maxAlternatives: number;\r\n start(): void;\r\n stop(): void;\r\n abort(): void;\r\n onresult: ((event: SpeechRecognitionEvent) => void) | null;\r\n onerror: ((event: SpeechRecognitionErrorEvent) => void) | null;\r\n onend: (() => void) | null;\r\n onstart: (() => void) | null;\r\n onaudiostart: (() => void) | null;\r\n onaudioend: (() => void) | null;\r\n onspeechstart: (() => void) | null;\r\n onspeechend: (() => void) | null;\r\n}\r\n\r\ndeclare global {\r\n interface Window {\r\n SpeechRecognition?: new () => SpeechRecognitionInterface;\r\n webkitSpeechRecognition?: new () => SpeechRecognitionInterface;\r\n }\r\n}\r\n\r\n/**\r\n * Safari Web Speech API wrapper\r\n *\r\n * Provides native speech recognition on iOS Safari.\r\n * Much faster than Whisper WASM and more battery-efficient.\r\n */\r\nexport class SafariSpeechRecognition {\r\n private config: Required<SafariSpeechConfig>;\r\n private recognition: SpeechRecognitionInterface | null = null;\r\n private isListening = false;\r\n private startTime = 0;\r\n private accumulatedText = '';\r\n\r\n // Callbacks\r\n private resultCallbacks: SpeechResultCallback[] = [];\r\n private errorCallbacks: SpeechErrorCallback[] = [];\r\n\r\n // Promise resolvers for stop()\r\n private stopResolver: ((result: SpeechRecognitionResult) => void) | null = null;\r\n private stopRejecter: ((error: Error) => void) | null = null;\r\n\r\n constructor(config: SafariSpeechConfig = {}) {\r\n this.config = {\r\n language: config.language ?? 'en-US',\r\n continuous: config.continuous ?? true,\r\n interimResults: config.interimResults ?? true,\r\n maxAlternatives: config.maxAlternatives ?? 1,\r\n };\r\n\r\n logger.debug('SafariSpeechRecognition created', {\r\n language: this.config.language,\r\n continuous: this.config.continuous,\r\n });\r\n }\r\n\r\n /**\r\n * Check if Web Speech API is available\r\n */\r\n static isAvailable(): boolean {\r\n return isSpeechRecognitionAvailable();\r\n }\r\n\r\n /**\r\n * Check if currently listening\r\n */\r\n get listening(): boolean {\r\n return this.isListening;\r\n }\r\n\r\n /**\r\n * Get the language being used\r\n */\r\n get language(): string {\r\n return this.config.language;\r\n }\r\n\r\n /**\r\n * Register a callback for receiving results\r\n */\r\n onResult(callback: SpeechResultCallback): void {\r\n this.resultCallbacks.push(callback);\r\n }\r\n\r\n /**\r\n * Register a callback for receiving errors\r\n */\r\n onError(callback: SpeechErrorCallback): void {\r\n this.errorCallbacks.push(callback);\r\n }\r\n\r\n /**\r\n * Remove a result callback\r\n */\r\n offResult(callback: SpeechResultCallback): void {\r\n const index = this.resultCallbacks.indexOf(callback);\r\n if (index !== -1) {\r\n this.resultCallbacks.splice(index, 1);\r\n }\r\n }\r\n\r\n /**\r\n * Remove an error callback\r\n */\r\n offError(callback: SpeechErrorCallback): void {\r\n const index = this.errorCallbacks.indexOf(callback);\r\n if (index !== -1) {\r\n this.errorCallbacks.splice(index, 1);\r\n }\r\n }\r\n\r\n /**\r\n * Start listening for speech\r\n *\r\n * On iOS Safari, this will trigger the microphone permission prompt\r\n * if not already granted.\r\n */\r\n async start(): Promise<void> {\r\n if (this.isListening) {\r\n logger.warn('Already listening');\r\n return;\r\n }\r\n\r\n if (!SafariSpeechRecognition.isAvailable()) {\r\n const error = new Error(\r\n 'Web Speech API not available. ' +\r\n 'This API is supported in Safari (iOS/macOS) and Chrome. ' +\r\n 'On iOS, use Safari for native speech recognition.'\r\n );\r\n this.emitError(error);\r\n throw error;\r\n }\r\n\r\n const telemetry = getTelemetry();\r\n const span = telemetry?.startSpan('SafariSpeech.start', {\r\n 'speech.language': this.config.language,\r\n 'speech.continuous': this.config.continuous,\r\n });\r\n\r\n try {\r\n // Create recognition instance\r\n const SpeechRecognitionClass = window.SpeechRecognition || window.webkitSpeechRecognition;\r\n if (!SpeechRecognitionClass) {\r\n throw new Error('SpeechRecognition constructor not found');\r\n }\r\n\r\n this.recognition = new SpeechRecognitionClass();\r\n this.recognition.continuous = this.config.continuous;\r\n this.recognition.interimResults = this.config.interimResults;\r\n this.recognition.lang = this.config.language;\r\n this.recognition.maxAlternatives = this.config.maxAlternatives;\r\n\r\n // Set up event handlers\r\n this.setupEventHandlers();\r\n\r\n // Start recognition\r\n this.recognition.start();\r\n this.isListening = true;\r\n this.startTime = performance.now();\r\n this.accumulatedText = '';\r\n\r\n logger.info('Speech recognition started', {\r\n language: this.config.language,\r\n });\r\n\r\n span?.end();\r\n } catch (error) {\r\n span?.endWithError(error instanceof Error ? error : new Error(String(error)));\r\n this.emitError(error instanceof Error ? error : new Error(String(error)));\r\n throw error;\r\n }\r\n }\r\n\r\n /**\r\n * Stop listening and return the final transcript\r\n */\r\n async stop(): Promise<SpeechRecognitionResult> {\r\n if (!this.isListening || !this.recognition) {\r\n logger.warn('Not currently listening');\r\n return {\r\n text: this.accumulatedText,\r\n language: this.config.language,\r\n inferenceTimeMs: 0,\r\n isFinal: true,\r\n };\r\n }\r\n\r\n const telemetry = getTelemetry();\r\n const span = telemetry?.startSpan('SafariSpeech.stop');\r\n\r\n return new Promise((resolve, reject) => {\r\n this.stopResolver = resolve;\r\n this.stopRejecter = reject;\r\n\r\n try {\r\n this.recognition!.stop();\r\n // onend handler will resolve the promise\r\n } catch (error) {\r\n span?.endWithError(error instanceof Error ? error : new Error(String(error)));\r\n this.isListening = false;\r\n reject(error);\r\n }\r\n });\r\n }\r\n\r\n /**\r\n * Abort recognition without waiting for final result\r\n */\r\n abort(): void {\r\n if (this.recognition && this.isListening) {\r\n this.recognition.abort();\r\n this.isListening = false;\r\n logger.info('Speech recognition aborted');\r\n }\r\n }\r\n\r\n /**\r\n * NOT SUPPORTED: Transcribe audio buffer\r\n *\r\n * Safari Speech API does not support transcribing pre-recorded audio.\r\n * It only works with live microphone input.\r\n *\r\n * For batch transcription on iOS, use server-side Whisper or a cloud ASR service.\r\n *\r\n * @throws Error always - this method is not supported\r\n */\r\n async transcribe(_audio: Float32Array): Promise<SpeechRecognitionResult> {\r\n throw new Error(\r\n 'SafariSpeechRecognition does not support transcribe() with audio buffers. ' +\r\n 'The Web Speech API only works with live microphone input. ' +\r\n 'Use start() and stop() for real-time recognition, or use WhisperInference/cloud ASR for batch transcription.'\r\n );\r\n }\r\n\r\n /**\r\n * Dispose of recognition resources\r\n */\r\n dispose(): void {\r\n if (this.recognition) {\r\n if (this.isListening) {\r\n this.recognition.abort();\r\n }\r\n this.recognition = null;\r\n }\r\n this.isListening = false;\r\n this.resultCallbacks = [];\r\n this.errorCallbacks = [];\r\n logger.debug('SafariSpeechRecognition disposed');\r\n }\r\n\r\n /**\r\n * Set up event handlers for the recognition instance\r\n */\r\n private setupEventHandlers(): void {\r\n if (!this.recognition) return;\r\n\r\n this.recognition.onresult = (event: SpeechRecognitionEvent) => {\r\n const telemetry = getTelemetry();\r\n const span = telemetry?.startSpan('SafariSpeech.onresult');\r\n\r\n try {\r\n // Process all new results\r\n for (let i = event.resultIndex; i < event.results.length; i++) {\r\n const result = event.results[i];\r\n const alternative = result[0];\r\n\r\n if (alternative) {\r\n const text = alternative.transcript;\r\n const isFinal = result.isFinal;\r\n\r\n // Accumulate final text\r\n if (isFinal) {\r\n this.accumulatedText += text + ' ';\r\n }\r\n\r\n const speechResult: SpeechRecognitionResult = {\r\n text: isFinal ? this.accumulatedText.trim() : text,\r\n language: this.config.language,\r\n inferenceTimeMs: performance.now() - this.startTime,\r\n isFinal,\r\n confidence: alternative.confidence,\r\n };\r\n\r\n // Emit to callbacks\r\n this.emitResult(speechResult);\r\n\r\n logger.trace('Speech result', {\r\n text: text.substring(0, 50),\r\n isFinal,\r\n confidence: alternative.confidence,\r\n });\r\n }\r\n }\r\n\r\n span?.end();\r\n } catch (error) {\r\n span?.endWithError(error instanceof Error ? error : new Error(String(error)));\r\n logger.error('Error processing speech result', { error });\r\n }\r\n };\r\n\r\n this.recognition.onerror = (event: SpeechRecognitionErrorEvent) => {\r\n const error = new Error(`Speech recognition error: ${event.error} - ${event.message}`);\r\n logger.error('Speech recognition error', { error: event.error, message: event.message });\r\n this.emitError(error);\r\n\r\n if (this.stopRejecter) {\r\n this.stopRejecter(error);\r\n this.stopResolver = null;\r\n this.stopRejecter = null;\r\n }\r\n };\r\n\r\n this.recognition.onend = () => {\r\n this.isListening = false;\r\n logger.info('Speech recognition ended', {\r\n totalText: this.accumulatedText.length,\r\n durationMs: performance.now() - this.startTime,\r\n });\r\n\r\n // Resolve stop() promise if pending\r\n if (this.stopResolver) {\r\n const result: SpeechRecognitionResult = {\r\n text: this.accumulatedText.trim(),\r\n language: this.config.language,\r\n inferenceTimeMs: performance.now() - this.startTime,\r\n isFinal: true,\r\n };\r\n this.stopResolver(result);\r\n this.stopResolver = null;\r\n this.stopRejecter = null;\r\n }\r\n };\r\n\r\n this.recognition.onstart = () => {\r\n logger.debug('Speech recognition started by browser');\r\n };\r\n\r\n this.recognition.onspeechstart = () => {\r\n logger.debug('Speech detected');\r\n };\r\n\r\n this.recognition.onspeechend = () => {\r\n logger.debug('Speech ended');\r\n };\r\n }\r\n\r\n /**\r\n * Emit result to all registered callbacks\r\n */\r\n private emitResult(result: SpeechRecognitionResult): void {\r\n for (const callback of this.resultCallbacks) {\r\n try {\r\n callback(result);\r\n } catch (error) {\r\n logger.error('Error in result callback', { error });\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Emit error to all registered callbacks\r\n */\r\n private emitError(error: Error): void {\r\n for (const callback of this.errorCallbacks) {\r\n try {\r\n callback(error);\r\n } catch (callbackError) {\r\n logger.error('Error in error callback', { error: callbackError });\r\n }\r\n }\r\n }\r\n}\r\n","/**\n * Emotion - Helper for creating emotion vectors for avatar animation\n *\n * Provides 10 explicit emotion channels that can be used to control\n * avatar expressions and emotional states.\n *\n * @category Emotion\n *\n * @example Creating emotion vectors\n * ```typescript\n * import { createEmotionVector, EmotionPresets } from '@omote/core';\n *\n * // Named weights\n * const happy = createEmotionVector({ joy: 0.8, amazement: 0.2 });\n *\n * // Use preset\n * const surprised = EmotionPresets.surprised;\n * ```\n *\n * @example Smooth transitions\n * ```typescript\n * import { EmotionController } from '@omote/core';\n *\n * const controller = new EmotionController();\n * controller.setPreset('happy');\n * controller.transitionTo({ sadness: 0.7 }, 500);\n *\n * // In animation loop\n * controller.update();\n * const emotion = controller.emotion;\n * ```\n */\n\n/** The 10 explicit emotion channels */\nexport const EMOTION_NAMES = [\n 'amazement',\n 'anger',\n 'cheekiness',\n 'disgust',\n 'fear',\n 'grief',\n 'joy',\n 'outofbreath',\n 'pain',\n 'sadness',\n] as const;\n\nexport type EmotionName = typeof EMOTION_NAMES[number];\n\n/** Emotion weights by name */\nexport type EmotionWeights = Partial<Record<EmotionName, number>>;\n\n/** Total emotion vector size */\nexport const EMOTION_VECTOR_SIZE = 26;\n\n/** Number of explicit emotion channels */\nexport const EXPLICIT_EMOTION_COUNT = 10;\n\n/**\n * Create an emotion vector from named weights\n *\n * @param weights - Named emotion weights (0-1)\n * @returns Float32Array of emotion values\n *\n * @example\n * ```ts\n * const emotion = createEmotionVector({ joy: 0.8, amazement: 0.3 });\n * ```\n */\nexport function createEmotionVector(weights: EmotionWeights = {}): Float32Array {\n const vector = new Float32Array(EMOTION_VECTOR_SIZE);\n\n for (const [name, value] of Object.entries(weights)) {\n const idx = EMOTION_NAMES.indexOf(name as EmotionName);\n if (idx >= 0) {\n vector[idx] = Math.max(0, Math.min(1, value));\n }\n }\n\n return vector;\n}\n\n/**\n * Pre-built emotion presets for common expressions\n */\nexport const EmotionPresets = {\n /** Neutral/default - no emotional expression */\n neutral: createEmotionVector({}),\n\n /** Happy - joy with slight amazement */\n happy: createEmotionVector({ joy: 0.7, amazement: 0.2 }),\n\n /** Sad - grief and sadness */\n sad: createEmotionVector({ sadness: 0.7, grief: 0.4 }),\n\n /** Angry - anger with disgust */\n angry: createEmotionVector({ anger: 0.8, disgust: 0.3 }),\n\n /** Surprised - high amazement */\n surprised: createEmotionVector({ amazement: 0.9, fear: 0.2 }),\n\n /** Scared - fear with pain */\n scared: createEmotionVector({ fear: 0.8, pain: 0.3 }),\n\n /** Disgusted - disgust with anger */\n disgusted: createEmotionVector({ disgust: 0.8, anger: 0.2 }),\n\n /** Excited - joy with amazement and cheekiness */\n excited: createEmotionVector({ joy: 0.6, amazement: 0.5, cheekiness: 0.4 }),\n\n /** Tired - out of breath with sadness */\n tired: createEmotionVector({ outofbreath: 0.6, sadness: 0.3 }),\n\n /** Playful - cheekiness with joy */\n playful: createEmotionVector({ cheekiness: 0.7, joy: 0.5 }),\n\n /** Pained - pain with grief */\n pained: createEmotionVector({ pain: 0.8, grief: 0.4 }),\n\n /** Contemplative - slight sadness, calm */\n contemplative: createEmotionVector({ sadness: 0.2, grief: 0.1 }),\n} as const;\n\nexport type EmotionPresetName = keyof typeof EmotionPresets;\n\n/**\n * Get an emotion preset by name\n */\nexport function getEmotionPreset(name: EmotionPresetName): Float32Array {\n return EmotionPresets[name].slice();\n}\n\n/**\n * Blend multiple emotion vectors together\n *\n * @param emotions - Array of { vector, weight } pairs\n * @returns Blended emotion vector\n *\n * @example\n * ```ts\n * const blended = blendEmotions([\n * { vector: EmotionPresets.happy, weight: 0.7 },\n * { vector: EmotionPresets.surprised, weight: 0.3 },\n * ]);\n * ```\n */\nexport function blendEmotions(\n emotions: Array<{ vector: Float32Array; weight: number }>\n): Float32Array {\n const result = new Float32Array(EMOTION_VECTOR_SIZE);\n let totalWeight = 0;\n\n for (const { vector, weight } of emotions) {\n totalWeight += weight;\n for (let i = 0; i < EMOTION_VECTOR_SIZE; i++) {\n result[i] += (vector[i] || 0) * weight;\n }\n }\n\n // Normalize if total weight > 0\n if (totalWeight > 0) {\n for (let i = 0; i < EMOTION_VECTOR_SIZE; i++) {\n result[i] /= totalWeight;\n }\n }\n\n return result;\n}\n\n/**\n * Interpolate between two emotion vectors\n *\n * @param from - Starting emotion\n * @param to - Target emotion\n * @param t - Interpolation factor (0-1)\n * @returns Interpolated emotion vector\n */\nexport function lerpEmotion(\n from: Float32Array,\n to: Float32Array,\n t: number\n): Float32Array {\n const result = new Float32Array(EMOTION_VECTOR_SIZE);\n const clampedT = Math.max(0, Math.min(1, t));\n\n for (let i = 0; i < EMOTION_VECTOR_SIZE; i++) {\n result[i] = (from[i] || 0) * (1 - clampedT) + (to[i] || 0) * clampedT;\n }\n\n return result;\n}\n\n/**\n * EmotionController - Manages emotion state with smooth transitions\n */\nexport class EmotionController {\n private currentEmotion = new Float32Array(EMOTION_VECTOR_SIZE);\n private targetEmotion = new Float32Array(EMOTION_VECTOR_SIZE);\n private transitionProgress = 1.0;\n private transitionDuration = 0;\n private transitionStartTime = 0;\n\n /**\n * Get the current emotion vector\n */\n get emotion(): Float32Array {\n if (this.transitionProgress >= 1.0) {\n return this.targetEmotion;\n }\n\n // Interpolate during transition\n return lerpEmotion(this.currentEmotion, this.targetEmotion, this.transitionProgress);\n }\n\n /**\n * Set emotion immediately (no transition)\n */\n set(weights: EmotionWeights): void {\n const newEmotion = createEmotionVector(weights);\n this.targetEmotion.set(newEmotion);\n this.currentEmotion.set(newEmotion);\n this.transitionProgress = 1.0;\n }\n\n /**\n * Set emotion from preset immediately\n */\n setPreset(preset: EmotionPresetName): void {\n const newEmotion = getEmotionPreset(preset);\n this.targetEmotion.set(newEmotion);\n this.currentEmotion.set(newEmotion);\n this.transitionProgress = 1.0;\n }\n\n /**\n * Transition to new emotion over time\n *\n * @param weights - Target emotion weights\n * @param durationMs - Transition duration in milliseconds\n */\n transitionTo(weights: EmotionWeights, durationMs: number): void {\n this.currentEmotion.set(this.emotion);\n this.targetEmotion.set(createEmotionVector(weights));\n this.transitionDuration = durationMs;\n this.transitionStartTime = performance.now();\n this.transitionProgress = 0;\n }\n\n /**\n * Transition to preset over time\n */\n transitionToPreset(preset: EmotionPresetName, durationMs: number): void {\n this.currentEmotion.set(this.emotion);\n this.targetEmotion.set(getEmotionPreset(preset));\n this.transitionDuration = durationMs;\n this.transitionStartTime = performance.now();\n this.transitionProgress = 0;\n }\n\n /**\n * Update transition progress (call each frame)\n */\n update(): void {\n if (this.transitionProgress >= 1.0) return;\n\n const elapsed = performance.now() - this.transitionStartTime;\n this.transitionProgress = Math.min(1.0, elapsed / this.transitionDuration);\n }\n\n /**\n * Check if currently transitioning\n */\n get isTransitioning(): boolean {\n return this.transitionProgress < 1.0;\n }\n\n /**\n * Reset to neutral\n */\n reset(): void {\n this.currentEmotion.fill(0);\n this.targetEmotion.fill(0);\n this.transitionProgress = 1.0;\n }\n}\n","/**\r\n * AWS AgentCore Adapter\r\n *\r\n * Primary AI adapter for the Omote Platform.\r\n *\r\n * Pipeline:\r\n * User Audio -> Whisper ASR (local) -> Text\r\n * Text -> AgentCore (WebSocket) -> Response Text + Audio chunks (TTS handled backend-side)\r\n * Audio chunks -> LAM (local) -> Blendshapes -> Render\r\n *\r\n * @category AI\r\n */\r\n\r\nimport { EventEmitter } from '../../events/EventEmitter';\r\nimport type {\r\n AIAdapter,\r\n AIAdapterEvents,\r\n SessionConfig,\r\n AISessionState,\r\n ConversationMessage,\r\n TenantConfig,\r\n} from '../interfaces/AIAdapter';\r\nimport { SenseVoiceInference } from '../../inference/SenseVoiceInference';\r\nimport { Wav2Vec2Inference, LAM_BLENDSHAPES } from '../../inference/Wav2Vec2Inference';\r\nimport { SileroVADInference } from '../../inference/SileroVADInference';\r\nimport { EmotionController } from '../../emotion/Emotion';\r\nimport { SyncedAudioPipeline } from '../../audio/SyncedAudioPipeline';\r\nimport { int16ToFloat32 } from '../../audio/audioUtils';\r\n\r\n/**\r\n * AgentCore-specific configuration\r\n */\r\nexport interface AgentCoreConfig {\r\n /** AgentCore WebSocket endpoint */\r\n endpoint: string;\r\n /** AWS region */\r\n region?: string;\r\n /** Model URLs */\r\n models?: {\r\n lamUrl?: string;\r\n };\r\n /** Enable observability */\r\n observability?: {\r\n tracing?: boolean;\r\n metrics?: boolean;\r\n };\r\n}\r\n\r\n/**\r\n * AWS AgentCore Adapter\r\n */\r\nexport class AgentCoreAdapter extends EventEmitter<AIAdapterEvents> implements AIAdapter {\r\n readonly name = 'AgentCore';\r\n\r\n private _state: AISessionState = 'disconnected';\r\n private _sessionId: string | null = null;\r\n private _isConnected = false;\r\n\r\n // Sub-components\r\n private asr: SenseVoiceInference | null = null;\r\n private vad: SileroVADInference | null = null;\r\n private lam: Wav2Vec2Inference | null = null;\r\n private emotionController: EmotionController;\r\n private pipeline: SyncedAudioPipeline | null = null;\r\n\r\n // WebSocket connection to AgentCore\r\n private ws: WebSocket | null = null;\r\n private wsReconnectAttempts = 0;\r\n private readonly maxReconnectAttempts = 5;\r\n\r\n // Audio buffers\r\n private audioBuffer: Float32Array[] = [];\r\n\r\n // Conversation state\r\n private history: ConversationMessage[] = [];\r\n private currentConfig: SessionConfig | null = null;\r\n private agentCoreConfig: AgentCoreConfig;\r\n\r\n // Interruption handling\r\n private isSpeaking = false;\r\n private currentTtsAbortController: AbortController | null = null;\r\n\r\n // Auth token cache per tenant\r\n private tokenCache = new Map<string, { token: string; expiresAt: number }>();\r\n\r\n constructor(config: AgentCoreConfig) {\r\n super();\r\n this.agentCoreConfig = config;\r\n this.emotionController = new EmotionController();\r\n }\r\n\r\n get state(): AISessionState {\r\n return this._state;\r\n }\r\n\r\n get sessionId(): string | null {\r\n return this._sessionId;\r\n }\r\n\r\n get isConnected(): boolean {\r\n return this._isConnected;\r\n }\r\n\r\n /**\r\n * Connect to AgentCore with session configuration\r\n */\r\n async connect(config: SessionConfig): Promise<void> {\r\n this.currentConfig = config;\r\n this._sessionId = config.sessionId;\r\n\r\n try {\r\n // 1. Get/refresh auth token for tenant\r\n const authToken = await this.getAuthToken(config.tenant);\r\n\r\n // 2. Initialize local inference components in parallel\r\n await Promise.all([\r\n this.initASR(),\r\n this.initLAM(),\r\n ]);\r\n\r\n // 3. Connect to AgentCore WebSocket\r\n await this.connectWebSocket(authToken, config);\r\n\r\n this._isConnected = true;\r\n this.setState('idle');\r\n\r\n this.emit('connection.opened', { sessionId: this._sessionId, adapter: this.name });\r\n } catch (error) {\r\n this.setState('error');\r\n this.emit('connection.error', {\r\n error: error as Error,\r\n recoverable: true,\r\n });\r\n throw error;\r\n }\r\n }\r\n\r\n /**\r\n * Disconnect and cleanup\r\n */\r\n async disconnect(): Promise<void> {\r\n // Cancel any ongoing TTS\r\n this.currentTtsAbortController?.abort();\r\n\r\n // Stop pipeline\r\n if (this.pipeline) {\r\n this.pipeline.dispose();\r\n this.pipeline = null;\r\n }\r\n\r\n // Close WebSocket\r\n if (this.ws) {\r\n this.ws.close(1000, 'Client disconnect');\r\n this.ws = null;\r\n }\r\n\r\n // Cleanup local components\r\n await Promise.all([\r\n this.asr?.dispose(),\r\n this.vad?.dispose(),\r\n this.lam?.dispose(),\r\n ]);\r\n\r\n this._isConnected = false;\r\n this.setState('disconnected');\r\n\r\n this.emit('connection.closed', { reason: 'Client disconnect' });\r\n }\r\n\r\n /**\r\n * Push user audio for processing\r\n */\r\n pushAudio(audio: Int16Array | Float32Array): void {\r\n if (!this._isConnected) return;\r\n\r\n // Handle interruption detection (async but fire-and-forget)\r\n if (this.isSpeaking) {\r\n this.detectVoiceActivity(audio).then((hasVoiceActivity) => {\r\n if (hasVoiceActivity) {\r\n this.interrupt();\r\n }\r\n }).catch((error) => {\r\n console.error('[AgentCore] VAD error during interruption detection:', error);\r\n });\r\n // Don't return - still buffer the audio for transcription after interruption\r\n }\r\n\r\n // Convert to Float32 if needed\r\n const float32 = audio instanceof Float32Array\r\n ? audio\r\n : int16ToFloat32(audio);\r\n\r\n // Buffer audio chunks\r\n this.audioBuffer.push(float32);\r\n\r\n // Debounce and send to Whisper when we have enough\r\n this.scheduleTranscription();\r\n }\r\n\r\n /**\r\n * Send text directly to AgentCore\r\n */\r\n async sendText(text: string): Promise<void> {\r\n if (!this._isConnected || !this.ws) {\r\n throw new Error('Not connected to AgentCore');\r\n }\r\n\r\n // Add to history\r\n this.addToHistory({\r\n role: 'user',\r\n content: text,\r\n timestamp: Date.now(),\r\n });\r\n\r\n this.setState('thinking');\r\n this.emit('ai.thinking.start', { timestamp: Date.now() });\r\n\r\n // Send to AgentCore\r\n this.ws.send(JSON.stringify({\r\n type: 'user_message',\r\n sessionId: this._sessionId,\r\n content: text,\r\n context: {\r\n history: this.history.slice(-10), // Last 10 messages\r\n emotion: Array.from(this.emotionController.emotion),\r\n },\r\n }));\r\n }\r\n\r\n /**\r\n * Interrupt current AI response\r\n */\r\n interrupt(): void {\r\n if (!this.isSpeaking) return;\r\n\r\n this.emit('interruption.detected', { timestamp: Date.now() });\r\n\r\n // Cancel any pending operations\r\n this.currentTtsAbortController?.abort();\r\n this.currentTtsAbortController = null;\r\n\r\n // Notify AgentCore to stop TTS streaming\r\n if (this.ws?.readyState === WebSocket.OPEN) {\r\n this.ws.send(JSON.stringify({\r\n type: 'interrupt',\r\n sessionId: this._sessionId,\r\n timestamp: Date.now(),\r\n }));\r\n }\r\n\r\n this.isSpeaking = false;\r\n this.setState('listening');\r\n\r\n this.emit('interruption.handled', { timestamp: Date.now(), action: 'stop' });\r\n }\r\n\r\n getHistory(): ConversationMessage[] {\r\n return [...this.history];\r\n }\r\n\r\n clearHistory(): void {\r\n this.history = [];\r\n this.emit('memory.updated', { messageCount: 0 });\r\n }\r\n\r\n async healthCheck(): Promise<boolean> {\r\n if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {\r\n return false;\r\n }\r\n\r\n return new Promise((resolve) => {\r\n const timeout = setTimeout(() => resolve(false), 5000);\r\n\r\n const handler = (event: MessageEvent) => {\r\n const data = JSON.parse(event.data);\r\n if (data.type === 'pong') {\r\n clearTimeout(timeout);\r\n this.ws?.removeEventListener('message', handler);\r\n resolve(true);\r\n }\r\n };\r\n\r\n this.ws?.addEventListener('message', handler);\r\n this.ws?.send(JSON.stringify({ type: 'ping' }));\r\n });\r\n }\r\n\r\n // ==================== Private Methods ====================\r\n\r\n private setState(state: AISessionState): void {\r\n const previousState = this._state;\r\n this._state = state;\r\n this.emit('state.change', { state, previousState });\r\n }\r\n\r\n private async getAuthToken(tenant: TenantConfig): Promise<string> {\r\n const cached = this.tokenCache.get(tenant.tenantId);\r\n if (cached && cached.expiresAt > Date.now() + 60000) {\r\n return cached.token;\r\n }\r\n\r\n // If we have an auth token already, use it\r\n if (tenant.credentials.authToken) {\r\n return tenant.credentials.authToken;\r\n }\r\n\r\n // Skip auth for local dev (ws:// endpoints or localhost)\r\n // The simple voice-agent doesn't have an auth endpoint\r\n const endpoint = this.agentCoreConfig.endpoint;\r\n if (endpoint.startsWith('ws://') || endpoint.includes('localhost')) {\r\n return 'local-dev-token';\r\n }\r\n\r\n // Exchange credentials for token (production)\r\n const httpEndpoint = endpoint.replace('wss://', 'https://').replace('ws://', 'http://');\r\n const response = await fetch(`${httpEndpoint}/auth/token`, {\r\n method: 'POST',\r\n headers: { 'Content-Type': 'application/json' },\r\n body: JSON.stringify({\r\n tenantId: tenant.tenantId,\r\n apiKey: tenant.credentials.apiKey,\r\n }),\r\n });\r\n\r\n if (!response.ok) {\r\n throw new Error(`Auth failed: ${response.statusText}`);\r\n }\r\n\r\n const { token, expiresIn } = await response.json();\r\n\r\n this.tokenCache.set(tenant.tenantId, {\r\n token,\r\n expiresAt: Date.now() + expiresIn * 1000,\r\n });\r\n\r\n return token;\r\n }\r\n\r\n private async initASR(): Promise<void> {\r\n // Initialize SenseVoice and Silero VAD in parallel\r\n await Promise.all([\r\n // SenseVoice ASR\r\n (async () => {\r\n this.asr = new SenseVoiceInference({\r\n modelUrl: '/models/sensevoice/model.int8.onnx',\r\n language: 'auto',\r\n });\r\n await this.asr.load();\r\n })(),\r\n // Silero VAD for accurate voice activity detection\r\n (async () => {\r\n this.vad = new SileroVADInference({\r\n modelUrl: '/models/silero-vad.onnx',\r\n backend: 'webgpu',\r\n sampleRate: 16000,\r\n threshold: 0.5,\r\n });\r\n await this.vad.load();\r\n })(),\r\n ]);\r\n }\r\n\r\n private async initLAM(): Promise<void> {\r\n // LAM (Lip Animation Model) based on wav2vec2\r\n // Outputs 52 ARKit blendshapes directly at 30fps - no PCA solver needed\r\n const lamUrl = this.agentCoreConfig.models?.lamUrl || '/models/unified_wav2vec2_asr_a2e.onnx';\r\n\r\n this.lam = new Wav2Vec2Inference({\r\n modelUrl: lamUrl,\r\n backend: 'auto',\r\n });\r\n\r\n await this.lam.load();\r\n\r\n // Initialize SyncedAudioPipeline for synchronized audio playback + LAM\r\n await this.initPipeline();\r\n }\r\n\r\n private async initPipeline(): Promise<void> {\r\n if (!this.lam) {\r\n throw new Error('LAM must be initialized before pipeline');\r\n }\r\n\r\n this.pipeline = new SyncedAudioPipeline({\r\n lam: this.lam,\r\n sampleRate: 16000,\r\n chunkTargetMs: 200,\r\n });\r\n\r\n await this.pipeline.initialize();\r\n\r\n // Subscribe to pipeline events\r\n this.pipeline.on('frame_ready', (frame: Float32Array) => {\r\n // Emit animation event with synchronized frame\r\n this.emit('animation', {\r\n blendshapes: frame,\r\n get: (name: string) => {\r\n const idx = (LAM_BLENDSHAPES as readonly string[]).indexOf(name);\r\n return idx >= 0 ? frame[idx] : 0;\r\n },\r\n timestamp: Date.now(), // Wall clock for client-side logging only\r\n inferenceMs: 0, // Pipeline handles LAM inference asynchronously\r\n });\r\n });\r\n\r\n this.pipeline.on('playback_complete', () => {\r\n this.isSpeaking = false;\r\n this.setState('idle');\r\n this.emit('audio.output.end', { durationMs: 0 });\r\n });\r\n\r\n this.pipeline.on('error', (error: Error) => {\r\n console.error('[AgentCore] Pipeline error:', error);\r\n this.emit('connection.error', {\r\n error,\r\n recoverable: true,\r\n });\r\n });\r\n }\r\n\r\n private async connectWebSocket(authToken: string, config: SessionConfig): Promise<void> {\r\n return new Promise((resolve, reject) => {\r\n const wsUrl = new URL(`${this.agentCoreConfig.endpoint.replace('http', 'ws')}/ws`);\r\n wsUrl.searchParams.set('sessionId', config.sessionId);\r\n wsUrl.searchParams.set('characterId', config.tenant.characterId);\r\n\r\n this.ws = new WebSocket(wsUrl.toString());\r\n\r\n this.ws.onopen = () => {\r\n // Send auth\r\n this.ws?.send(JSON.stringify({\r\n type: 'auth',\r\n token: authToken,\r\n tenantId: config.tenant.tenantId,\r\n systemPrompt: config.systemPrompt,\r\n }));\r\n };\r\n\r\n this.ws.onmessage = (event) => {\r\n this.handleAgentCoreMessage(JSON.parse(event.data));\r\n };\r\n\r\n this.ws.onerror = () => {\r\n reject(new Error('WebSocket connection failed'));\r\n };\r\n\r\n this.ws.onclose = (event) => {\r\n this.handleDisconnect(event);\r\n };\r\n\r\n // Wait for auth confirmation\r\n const authTimeout = setTimeout(() => {\r\n reject(new Error('Auth timeout'));\r\n }, 10000);\r\n\r\n const authHandler = (event: MessageEvent) => {\r\n const data = JSON.parse(event.data);\r\n if (data.type === 'auth_success') {\r\n clearTimeout(authTimeout);\r\n this.ws?.removeEventListener('message', authHandler);\r\n resolve();\r\n } else if (data.type === 'auth_failed') {\r\n clearTimeout(authTimeout);\r\n reject(new Error(data.message));\r\n }\r\n };\r\n\r\n this.ws.addEventListener('message', authHandler);\r\n });\r\n }\r\n\r\n private handleAgentCoreMessage(data: Record<string, unknown>): void {\r\n switch (data.type) {\r\n case 'response_start':\r\n this.setState('speaking');\r\n this.isSpeaking = true;\r\n this.emit('ai.response.start', {\r\n text: data.text as string | undefined,\r\n emotion: data.emotion as string | undefined,\r\n });\r\n // Update emotion state\r\n if (data.emotion) {\r\n this.emotionController.transitionTo(\r\n { [data.emotion as string]: 0.7 },\r\n 300\r\n );\r\n }\r\n // Start pipeline for synchronized playback\r\n if (this.pipeline) {\r\n this.pipeline.start();\r\n }\r\n break;\r\n\r\n case 'response_chunk':\r\n this.emit('ai.response.chunk', {\r\n text: data.text as string,\r\n isLast: data.isLast as boolean,\r\n });\r\n break;\r\n\r\n case 'audio_chunk':\r\n // TTS audio streamed from backend - feed to synchronized pipeline\r\n if (data.audio && this.pipeline) {\r\n const audioData = this.base64ToArrayBuffer(data.audio as string);\r\n const uint8 = new Uint8Array(audioData);\r\n this.pipeline.onAudioChunk(uint8).catch((error) => {\r\n console.error('[AgentCore] Pipeline chunk error:', error);\r\n });\r\n }\r\n break;\r\n\r\n case 'audio_end':\r\n // Signal end of audio stream to pipeline\r\n if (this.pipeline) {\r\n this.pipeline.end().catch((error) => {\r\n console.error('[AgentCore] Pipeline end error:', error);\r\n });\r\n }\r\n // Note: isSpeaking and state will be set to idle by pipeline.playback_complete event\r\n break;\r\n\r\n case 'response_end':\r\n this.addToHistory({\r\n role: 'assistant',\r\n content: data.fullText as string,\r\n timestamp: Date.now(),\r\n emotion: data.emotion as string | undefined,\r\n });\r\n this.emit('ai.response.end', {\r\n fullText: data.fullText as string,\r\n durationMs: data.durationMs as number || 0,\r\n });\r\n break;\r\n\r\n case 'memory_updated':\r\n this.emit('memory.updated', {\r\n messageCount: data.messageCount as number,\r\n tokenCount: data.tokenCount as number | undefined,\r\n });\r\n break;\r\n\r\n case 'error':\r\n this.emit('connection.error', {\r\n error: new Error(data.message as string),\r\n recoverable: (data.recoverable as boolean) ?? false,\r\n });\r\n break;\r\n }\r\n }\r\n\r\n private scheduleTranscription(): void {\r\n // No debounce - transcribe immediately when we have enough audio\r\n // This reduces latency significantly (was adding 100ms delay)\r\n\r\n if (this.audioBuffer.length === 0) return;\r\n\r\n // Concatenate buffered audio\r\n const totalLength = this.audioBuffer.reduce((sum, buf) => sum + buf.length, 0);\r\n\r\n // Need minimum samples for Whisper (250ms instead of 1 sec)\r\n // Shorter buffer = faster response time\r\n if (totalLength < 4000) return; // 250ms at 16kHz (was 16000 = 1sec)\r\n\r\n const audio = new Float32Array(totalLength);\r\n let offset = 0;\r\n for (const buf of this.audioBuffer) {\r\n audio.set(buf, offset);\r\n offset += buf.length;\r\n }\r\n this.audioBuffer = [];\r\n\r\n // Check for actual audio content (not silence/blank audio)\r\n // This prevents [BLANK_AUDIO] transcriptions\r\n let sum = 0;\r\n for (let i = 0; i < audio.length; i++) {\r\n sum += audio[i] * audio[i];\r\n }\r\n const rms = Math.sqrt(sum / audio.length);\r\n\r\n // Skip silent audio (too low energy)\r\n if (rms < 0.01) {\r\n console.debug('[AgentCore] Skipping silent audio', { rms, samples: audio.length });\r\n return;\r\n }\r\n\r\n // Transcribe with SenseVoice\r\n if (this.asr) {\r\n this.setState('listening');\r\n this.emit('user.speech.start', { timestamp: Date.now() });\r\n\r\n this.asr.transcribe(audio).then((result: { text: string; inferenceTimeMs: number }) => {\r\n this.emit('user.transcript.final', {\r\n text: result.text,\r\n confidence: 1.0,\r\n });\r\n this.emit('user.speech.end', { timestamp: Date.now(), durationMs: result.inferenceTimeMs });\r\n\r\n // Send to AgentCore (skip empty transcriptions)\r\n const cleanText = result.text.trim();\r\n if (cleanText) {\r\n this.sendText(cleanText).catch((error: unknown) => {\r\n console.error('[AgentCore] Send text error:', error);\r\n });\r\n }\r\n }).catch((error: unknown) => {\r\n console.error('[AgentCore] Transcription error:', error);\r\n });\r\n }\r\n }\r\n\r\n // REMOVED: processAudioForAnimation() - now handled by SyncedAudioPipeline\r\n // The pipeline manages audio scheduling, LAM inference, and frame synchronization\r\n // Frames are emitted via pipeline.on('frame_ready') event (see initPipeline())\r\n\r\n /**\r\n * Detect voice activity using Silero VAD\r\n * Falls back to simple RMS if VAD not available\r\n */\r\n private async detectVoiceActivity(audio: Int16Array | Float32Array): Promise<boolean> {\r\n // Convert to Float32 if needed\r\n const float32 = audio instanceof Float32Array\r\n ? audio\r\n : int16ToFloat32(audio);\r\n\r\n // Use Silero VAD if available (much more accurate)\r\n if (this.vad) {\r\n // Silero VAD requires 512-sample chunks (32ms at 16kHz)\r\n const chunkSize = this.vad.getChunkSize();\r\n\r\n // Process available chunks\r\n for (let i = 0; i + chunkSize <= float32.length; i += chunkSize) {\r\n const chunk = float32.slice(i, i + chunkSize);\r\n const result = await this.vad.process(chunk);\r\n\r\n // If any chunk has speech, return true\r\n if (result.isSpeech) {\r\n return true;\r\n }\r\n }\r\n\r\n return false;\r\n }\r\n\r\n // Fallback: Simple RMS-based detection (less accurate)\r\n let sum = 0;\r\n for (let i = 0; i < float32.length; i++) {\r\n sum += float32[i] * float32[i];\r\n }\r\n const rms = Math.sqrt(sum / float32.length);\r\n return rms > 0.02;\r\n }\r\n\r\n private base64ToArrayBuffer(base64: string): ArrayBuffer {\r\n const binaryString = atob(base64);\r\n const bytes = new Uint8Array(binaryString.length);\r\n for (let i = 0; i < binaryString.length; i++) {\r\n bytes[i] = binaryString.charCodeAt(i);\r\n }\r\n return bytes.buffer;\r\n }\r\n\r\n private addToHistory(message: ConversationMessage): void {\r\n this.history.push(message);\r\n this.emit('memory.updated', { messageCount: this.history.length });\r\n }\r\n\r\n private handleDisconnect(event: CloseEvent): void {\r\n this._isConnected = false;\r\n\r\n if (event.code !== 1000) {\r\n // Abnormal close - attempt reconnect\r\n if (this.wsReconnectAttempts < this.maxReconnectAttempts) {\r\n this.wsReconnectAttempts++;\r\n setTimeout(() => {\r\n if (this.currentConfig) {\r\n this.connect(this.currentConfig).catch(() => {\r\n // Will retry if fails\r\n });\r\n }\r\n }, Math.pow(2, this.wsReconnectAttempts) * 1000);\r\n } else {\r\n this.setState('error');\r\n this.emit('connection.error', {\r\n error: new Error('Max reconnection attempts reached'),\r\n recoverable: false,\r\n });\r\n }\r\n }\r\n\r\n this.emit('connection.closed', { reason: event.reason || 'Connection closed' });\r\n }\r\n}\r\n","/**\n * Conversation Orchestrator\n *\n * Manages the conversation pipeline with AgentCore:\n * - Handles session lifecycle and tenant isolation\n * - Manages adapter events and state\n *\n * @category AI\n */\n\nimport { EventEmitter } from '../../events/EventEmitter';\nimport type {\n AIAdapter,\n AIAdapterEvents,\n SessionConfig,\n TenantConfig,\n ConversationMessage,\n AISessionState,\n} from '../interfaces/AIAdapter';\nimport type { ConversationSession, SessionSnapshot } from '../interfaces/ConversationSession';\nimport { AgentCoreAdapter, type AgentCoreConfig } from '../adapters/AgentCoreAdapter';\nimport { EmotionController, type EmotionWeights } from '../../emotion/Emotion';\n\n/**\n * Orchestrator configuration\n */\nexport interface OrchestratorConfig {\n /** AgentCore adapter config */\n adapter: AgentCoreConfig;\n /** Connection timeout in ms */\n connectionTimeoutMs?: number;\n /** Max retry attempts */\n maxRetries?: number;\n}\n\n/**\n * Orchestrator events (extends AI adapter events)\n */\nexport interface OrchestratorEvents extends AIAdapterEvents {\n 'session.created': { sessionId: string; tenantId: string };\n 'session.ended': { sessionId: string; reason: string };\n}\n\n/**\n * Internal session implementation\n */\nclass ConversationSessionImpl implements ConversationSession {\n readonly sessionId: string;\n readonly createdAt: number;\n\n private _adapter: AIAdapter;\n private _config: SessionConfig;\n private _history: ConversationMessage[] = [];\n private _context = new Map<string, string>();\n private _emotionController: EmotionController;\n private _lastActivityAt: number;\n\n constructor(\n config: SessionConfig,\n adapter: AIAdapter,\n ) {\n this.sessionId = config.sessionId;\n this._config = config;\n this._adapter = adapter;\n this.createdAt = Date.now();\n this._lastActivityAt = Date.now();\n this._emotionController = new EmotionController();\n\n if (config.emotion) {\n this._emotionController.setPreset(config.emotion as Parameters<typeof this._emotionController.setPreset>[0]);\n }\n }\n\n get adapter(): AIAdapter {\n return this._adapter;\n }\n\n get config(): SessionConfig {\n return this._config;\n }\n\n get state(): AISessionState {\n return this._adapter.state;\n }\n\n get history(): ConversationMessage[] {\n return [...this._history];\n }\n\n get emotion(): EmotionWeights {\n return {};\n }\n\n get lastActivityAt(): number {\n return this._lastActivityAt;\n }\n\n async start(): Promise<void> {\n await this._adapter.connect(this._config);\n this._lastActivityAt = Date.now();\n }\n\n async end(): Promise<void> {\n await this._adapter.disconnect();\n }\n\n pushAudio(audio: Int16Array | Float32Array): void {\n this._adapter.pushAudio(audio);\n this._lastActivityAt = Date.now();\n }\n\n async sendText(text: string): Promise<void> {\n await this._adapter.sendText(text);\n this._lastActivityAt = Date.now();\n }\n\n interrupt(): void {\n this._adapter.interrupt();\n this._lastActivityAt = Date.now();\n }\n\n setEmotion(emotion: EmotionWeights): void {\n this._emotionController.set(emotion);\n }\n\n addContext(key: string, value: string): void {\n this._context.set(key, value);\n }\n\n removeContext(key: string): void {\n this._context.delete(key);\n }\n\n getContext(): Record<string, string> {\n return Object.fromEntries(this._context);\n }\n\n export(): SessionSnapshot {\n return {\n sessionId: this.sessionId,\n tenantId: this._config.tenant.tenantId,\n characterId: this._config.tenant.characterId,\n history: this._history,\n context: Object.fromEntries(this._context),\n emotion: this.emotion,\n createdAt: this.createdAt,\n lastActivityAt: this._lastActivityAt,\n };\n }\n\n import(snapshot: SessionSnapshot): void {\n this._history = [...snapshot.history];\n this._context = new Map(Object.entries(snapshot.context));\n this._lastActivityAt = snapshot.lastActivityAt;\n }\n\n syncHistory(): void {\n this._history = this._adapter.getHistory();\n }\n}\n\n/**\n * Conversation Orchestrator\n */\nexport class ConversationOrchestrator extends EventEmitter<OrchestratorEvents> {\n private config: Required<OrchestratorConfig>;\n\n // Adapter\n private adapter: AgentCoreAdapter;\n\n // Sessions per tenant\n private sessions = new Map<string, ConversationSessionImpl>();\n\n // Tenant configurations\n private tenants = new Map<string, TenantConfig>();\n\n // Health monitoring\n private healthCheckInterval: ReturnType<typeof setInterval> | null = null;\n private readonly HEALTH_CHECK_INTERVAL_MS = 30000;\n\n constructor(config: OrchestratorConfig) {\n super();\n this.config = {\n connectionTimeoutMs: 5000,\n maxRetries: 3,\n ...config,\n };\n\n // Initialize adapter\n this.adapter = new AgentCoreAdapter(config.adapter);\n }\n\n /**\n * Register a tenant\n */\n registerTenant(tenant: TenantConfig): void {\n this.tenants.set(tenant.tenantId, tenant);\n }\n\n /**\n * Unregister a tenant\n */\n unregisterTenant(tenantId: string): void {\n this.tenants.delete(tenantId);\n }\n\n /**\n * Get tenant config\n */\n getTenant(tenantId: string): TenantConfig | undefined {\n return this.tenants.get(tenantId);\n }\n\n /**\n * Create a new conversation session for a tenant\n */\n async createSession(\n tenantId: string,\n options: Partial<SessionConfig> = {}\n ): Promise<ConversationSession> {\n const tenant = this.tenants.get(tenantId);\n if (!tenant) {\n throw new Error(`Tenant not found: ${tenantId}`);\n }\n\n const sessionId = options.sessionId || this.generateSessionId();\n\n const sessionConfig: SessionConfig = {\n sessionId,\n tenant,\n systemPrompt: options.systemPrompt,\n voice: options.voice,\n emotion: options.emotion,\n language: options.language,\n };\n\n const session = new ConversationSessionImpl(sessionConfig, this.adapter);\n\n this.sessions.set(sessionId, session);\n\n // Forward adapter events\n this.forwardAdapterEvents(this.adapter, sessionId);\n\n // Connect the session\n await session.start();\n\n this.emit('session.created', { sessionId, tenantId });\n\n return session;\n }\n\n /**\n * End a session\n */\n async endSession(sessionId: string): Promise<void> {\n const session = this.sessions.get(sessionId);\n if (session) {\n await session.end();\n this.sessions.delete(sessionId);\n this.emit('session.ended', { sessionId, reason: 'Client requested' });\n }\n }\n\n /**\n * Get session by ID\n */\n getSession(sessionId: string): ConversationSession | undefined {\n return this.sessions.get(sessionId);\n }\n\n /**\n * Get all sessions for a tenant\n */\n getTenantSessions(tenantId: string): ConversationSession[] {\n return Array.from(this.sessions.values())\n .filter(s => s.config.tenant.tenantId === tenantId);\n }\n\n /**\n * Start health monitoring\n */\n startHealthMonitoring(): void {\n if (this.healthCheckInterval) return;\n\n this.healthCheckInterval = setInterval(async () => {\n await this.performHealthCheck();\n }, this.HEALTH_CHECK_INTERVAL_MS);\n }\n\n /**\n * Stop health monitoring\n */\n stopHealthMonitoring(): void {\n if (this.healthCheckInterval) {\n clearInterval(this.healthCheckInterval);\n this.healthCheckInterval = null;\n }\n }\n\n /**\n * Dispose all resources\n */\n async dispose(): Promise<void> {\n this.stopHealthMonitoring();\n\n // End all sessions\n const endPromises = Array.from(this.sessions.values()).map(s => s.end());\n await Promise.all(endPromises);\n this.sessions.clear();\n\n // Disconnect adapter\n await this.adapter.disconnect();\n }\n\n // ==================== Private Methods ====================\n\n private generateSessionId(): string {\n return `sess_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;\n }\n\n private forwardAdapterEvents(adapter: AIAdapter, sessionId: string): void {\n // Forward key events with session context\n const events: (keyof AIAdapterEvents)[] = [\n 'state.change',\n 'user.speech.start',\n 'user.speech.end',\n 'user.transcript.partial',\n 'user.transcript.final',\n 'ai.thinking.start',\n 'ai.response.start',\n 'ai.response.chunk',\n 'ai.response.end',\n 'audio.output.chunk',\n 'audio.output.end',\n 'animation',\n 'memory.updated',\n 'connection.error',\n 'interruption.detected',\n 'interruption.handled',\n ];\n\n for (const event of events) {\n adapter.on(event, (data) => {\n const eventData = data as Record<string, unknown>;\n this.emit(event, { ...eventData, sessionId } as AIAdapterEvents[typeof event]);\n });\n }\n }\n\n private async performHealthCheck(): Promise<void> {\n try {\n await this.adapter.healthCheck();\n } catch {\n // Adapter health check failed\n }\n }\n}\n","/**\n * Tenant Manager\n *\n * Handles multi-tenant isolation for the Omote Platform:\n * - Credential isolation per tenant\n * - Session scoping per tenant\n * - Quota management\n * - Token refresh\n *\n * @category AI\n */\n\nimport type { TenantConfig } from '../interfaces/AIAdapter';\n\n/**\n * Tenant quota configuration\n */\nexport interface TenantQuota {\n /** Max concurrent sessions */\n maxSessions: number;\n /** Requests per minute */\n requestsPerMinute: number;\n /** Max tokens per conversation */\n maxTokensPerConversation: number;\n /** Max audio minutes per day */\n maxAudioMinutesPerDay: number;\n}\n\n/**\n * Tenant usage tracking\n */\nexport interface TenantUsage {\n /** Current active sessions */\n currentSessions: number;\n /** Requests in current minute */\n requestsThisMinute: number;\n /** Total tokens used */\n tokensUsed: number;\n /** Audio minutes used today */\n audioMinutesToday: number;\n /** Last reset timestamp */\n lastMinuteReset: number;\n /** Last daily reset timestamp */\n lastDailyReset: number;\n}\n\n/**\n * Token refresh callback\n */\nexport type TokenRefreshCallback = () => Promise<string>;\n\n/**\n * Tenant Manager\n */\nexport class TenantManager {\n private tenants = new Map<string, TenantConfig>();\n private quotas = new Map<string, TenantQuota>();\n private usage = new Map<string, TenantUsage>();\n private tokenRefreshCallbacks = new Map<string, TokenRefreshCallback>();\n\n /**\n * Default quota for new tenants\n */\n static readonly DEFAULT_QUOTA: TenantQuota = {\n maxSessions: 10,\n requestsPerMinute: 60,\n maxTokensPerConversation: 100000,\n maxAudioMinutesPerDay: 60,\n };\n\n /**\n * Register a tenant with quota\n */\n register(\n tenant: TenantConfig,\n quota: TenantQuota = TenantManager.DEFAULT_QUOTA,\n tokenRefreshCallback?: TokenRefreshCallback\n ): void {\n this.tenants.set(tenant.tenantId, tenant);\n this.quotas.set(tenant.tenantId, quota);\n this.usage.set(tenant.tenantId, {\n currentSessions: 0,\n requestsThisMinute: 0,\n tokensUsed: 0,\n audioMinutesToday: 0,\n lastMinuteReset: Date.now(),\n lastDailyReset: Date.now(),\n });\n\n if (tokenRefreshCallback) {\n this.tokenRefreshCallbacks.set(tenant.tenantId, tokenRefreshCallback);\n }\n }\n\n /**\n * Unregister a tenant\n */\n unregister(tenantId: string): void {\n this.tenants.delete(tenantId);\n this.quotas.delete(tenantId);\n this.usage.delete(tenantId);\n this.tokenRefreshCallbacks.delete(tenantId);\n }\n\n /**\n * Get tenant config\n */\n get(tenantId: string): TenantConfig | undefined {\n return this.tenants.get(tenantId);\n }\n\n /**\n * Check if tenant exists\n */\n has(tenantId: string): boolean {\n return this.tenants.has(tenantId);\n }\n\n /**\n * Get all tenant IDs\n */\n getTenantIds(): string[] {\n return Array.from(this.tenants.keys());\n }\n\n /**\n * Check if tenant can create new session\n */\n canCreateSession(tenantId: string): boolean {\n const quota = this.quotas.get(tenantId);\n const usage = this.usage.get(tenantId);\n\n if (!quota || !usage) return false;\n\n return usage.currentSessions < quota.maxSessions;\n }\n\n /**\n * Check if tenant can make request\n */\n canMakeRequest(tenantId: string): boolean {\n const quota = this.quotas.get(tenantId);\n const usage = this.usage.get(tenantId);\n\n if (!quota || !usage) return false;\n\n // Auto-reset minute counter if needed\n this.checkMinuteReset(tenantId);\n\n return usage.requestsThisMinute < quota.requestsPerMinute;\n }\n\n /**\n * Check if tenant can use audio\n */\n canUseAudio(tenantId: string, minutes: number): boolean {\n const quota = this.quotas.get(tenantId);\n const usage = this.usage.get(tenantId);\n\n if (!quota || !usage) return false;\n\n // Auto-reset daily counter if needed\n this.checkDailyReset(tenantId);\n\n return usage.audioMinutesToday + minutes <= quota.maxAudioMinutesPerDay;\n }\n\n /**\n * Increment session count\n */\n incrementSessions(tenantId: string): void {\n const usage = this.usage.get(tenantId);\n if (usage) {\n usage.currentSessions++;\n }\n }\n\n /**\n * Decrement session count\n */\n decrementSessions(tenantId: string): void {\n const usage = this.usage.get(tenantId);\n if (usage && usage.currentSessions > 0) {\n usage.currentSessions--;\n }\n }\n\n /**\n * Record a request\n */\n recordRequest(tenantId: string): void {\n const usage = this.usage.get(tenantId);\n if (usage) {\n this.checkMinuteReset(tenantId);\n usage.requestsThisMinute++;\n }\n }\n\n /**\n * Record token usage\n */\n recordTokens(tenantId: string, tokens: number): void {\n const usage = this.usage.get(tenantId);\n if (usage) {\n usage.tokensUsed += tokens;\n }\n }\n\n /**\n * Record audio usage\n */\n recordAudioMinutes(tenantId: string, minutes: number): void {\n const usage = this.usage.get(tenantId);\n if (usage) {\n this.checkDailyReset(tenantId);\n usage.audioMinutesToday += minutes;\n }\n }\n\n /**\n * Get fresh auth token for tenant\n */\n async getAuthToken(tenantId: string): Promise<string> {\n const tenant = this.tenants.get(tenantId);\n if (!tenant) {\n throw new Error(`Tenant not found: ${tenantId}`);\n }\n\n // Check if we have a refresh callback\n const callback = this.tokenRefreshCallbacks.get(tenantId);\n if (callback) {\n const token = await callback();\n tenant.credentials.authToken = token;\n return token;\n }\n\n // Return existing token\n if (tenant.credentials.authToken) {\n return tenant.credentials.authToken;\n }\n\n throw new Error(`No auth token available for tenant: ${tenantId}`);\n }\n\n /**\n * Update tenant credentials\n */\n updateCredentials(tenantId: string, credentials: Partial<TenantConfig['credentials']>): void {\n const tenant = this.tenants.get(tenantId);\n if (tenant) {\n tenant.credentials = { ...tenant.credentials, ...credentials };\n }\n }\n\n /**\n * Get usage stats for tenant\n */\n getUsage(tenantId: string): TenantUsage | undefined {\n return this.usage.get(tenantId);\n }\n\n /**\n * Get quota for tenant\n */\n getQuota(tenantId: string): TenantQuota | undefined {\n return this.quotas.get(tenantId);\n }\n\n /**\n * Update quota for tenant\n */\n updateQuota(tenantId: string, quota: Partial<TenantQuota>): void {\n const existing = this.quotas.get(tenantId);\n if (existing) {\n this.quotas.set(tenantId, { ...existing, ...quota });\n }\n }\n\n /**\n * Reset all usage stats for a tenant\n */\n resetUsage(tenantId: string): void {\n const usage = this.usage.get(tenantId);\n if (usage) {\n usage.requestsThisMinute = 0;\n usage.tokensUsed = 0;\n usage.audioMinutesToday = 0;\n usage.lastMinuteReset = Date.now();\n usage.lastDailyReset = Date.now();\n }\n }\n\n // ==================== Private Methods ====================\n\n private checkMinuteReset(tenantId: string): void {\n const usage = this.usage.get(tenantId);\n if (!usage) return;\n\n const now = Date.now();\n if (now - usage.lastMinuteReset >= 60000) {\n usage.requestsThisMinute = 0;\n usage.lastMinuteReset = now;\n }\n }\n\n private checkDailyReset(tenantId: string): void {\n const usage = this.usage.get(tenantId);\n if (!usage) return;\n\n const now = Date.now();\n const MS_PER_DAY = 24 * 60 * 60 * 1000;\n if (now - usage.lastDailyReset >= MS_PER_DAY) {\n usage.audioMinutesToday = 0;\n usage.lastDailyReset = now;\n }\n }\n}\n","/**\n * Audio Sync Manager\n *\n * Synchronizes TTS audio playback with lip sync animation:\n * - Buffers audio for inference\n * - Manages playback timing\n * - Handles audio queue for streaming\n *\n * @category AI\n */\n\nimport { EventEmitter } from '../../events/EventEmitter';\n\n/**\n * Audio sync events\n */\nexport interface AudioSyncEvents {\n [key: string]: unknown;\n 'buffer.ready': { audio: Float32Array };\n 'playback.start': Record<string, never>;\n 'playback.end': Record<string, never>;\n 'sync.drift': { driftMs: number };\n}\n\n/**\n * Audio sync configuration\n */\nexport interface AudioSyncConfig {\n /** Target sample rate (default: 16000) */\n sampleRate?: number;\n /** Buffer size for inference (default: 16640) */\n bufferSize?: number;\n /** Overlap between buffers (default: 4160) */\n overlapSize?: number;\n /** Max drift before correction (default: 100ms) */\n maxDriftMs?: number;\n}\n\n/**\n * Audio Sync Manager\n */\nexport class AudioSyncManager extends EventEmitter<AudioSyncEvents> {\n private config: Required<AudioSyncConfig>;\n private audioBuffer: Float32Array;\n private bufferPosition = 0;\n private playbackQueue: Float32Array[] = [];\n private isPlaying = false;\n private audioContext: AudioContext | null = null;\n private playbackStartTime = 0;\n private samplesPlayed = 0;\n\n constructor(config: AudioSyncConfig = {}) {\n super();\n this.config = {\n sampleRate: 16000,\n bufferSize: 16640,\n overlapSize: 4160,\n maxDriftMs: 100,\n ...config,\n };\n\n this.audioBuffer = new Float32Array(this.config.bufferSize);\n }\n\n /**\n * Initialize audio context\n */\n async initialize(): Promise<void> {\n if (!this.audioContext) {\n this.audioContext = new AudioContext({ sampleRate: this.config.sampleRate });\n }\n\n if (this.audioContext.state === 'suspended') {\n await this.audioContext.resume();\n }\n }\n\n /**\n * Push audio chunk for processing and playback\n */\n pushAudio(audio: Float32Array): void {\n // Add to playback queue\n this.playbackQueue.push(audio);\n\n // Buffer for inference\n this.bufferForInference(audio);\n\n // Start playback if not playing\n if (!this.isPlaying && this.playbackQueue.length > 0) {\n this.startPlayback();\n }\n }\n\n /**\n * Buffer audio for inference\n */\n private bufferForInference(audio: Float32Array): void {\n let offset = 0;\n\n while (offset < audio.length) {\n const remaining = this.config.bufferSize - this.bufferPosition;\n const toCopy = Math.min(remaining, audio.length - offset);\n\n this.audioBuffer.set(audio.subarray(offset, offset + toCopy), this.bufferPosition);\n this.bufferPosition += toCopy;\n offset += toCopy;\n\n // Buffer full - emit for processing\n if (this.bufferPosition >= this.config.bufferSize) {\n this.emit('buffer.ready', { audio: new Float32Array(this.audioBuffer) });\n\n // Shift buffer with overlap for continuity\n const overlapStart = this.config.bufferSize - this.config.overlapSize;\n this.audioBuffer.copyWithin(0, overlapStart);\n this.bufferPosition = this.config.overlapSize;\n }\n }\n }\n\n /**\n * Start audio playback\n */\n private async startPlayback(): Promise<void> {\n if (!this.audioContext || this.isPlaying) return;\n\n this.isPlaying = true;\n this.playbackStartTime = this.audioContext.currentTime;\n this.samplesPlayed = 0;\n\n this.emit('playback.start', {});\n\n await this.processPlaybackQueue();\n }\n\n /**\n * Process playback queue\n */\n private async processPlaybackQueue(): Promise<void> {\n if (!this.audioContext) return;\n\n while (this.playbackQueue.length > 0) {\n const audio = this.playbackQueue.shift()!;\n\n // Create buffer and source\n const buffer = this.audioContext.createBuffer(1, audio.length, this.config.sampleRate);\n buffer.copyToChannel(audio, 0);\n\n const source = this.audioContext.createBufferSource();\n source.buffer = buffer;\n source.connect(this.audioContext.destination);\n\n // Calculate when to play\n const playTime = this.playbackStartTime + this.samplesPlayed / this.config.sampleRate;\n source.start(playTime);\n\n this.samplesPlayed += audio.length;\n\n // Check for drift\n this.checkDrift();\n\n // Wait for chunk to finish before processing next\n await new Promise(resolve => {\n source.onended = resolve;\n });\n }\n\n this.isPlaying = false;\n this.emit('playback.end', {});\n }\n\n /**\n * Check for audio/animation drift\n */\n private checkDrift(): void {\n if (!this.audioContext) return;\n\n const expectedTime = this.playbackStartTime + this.samplesPlayed / this.config.sampleRate;\n const actualTime = this.audioContext.currentTime;\n const driftMs = (actualTime - expectedTime) * 1000;\n\n if (Math.abs(driftMs) > this.config.maxDriftMs) {\n this.emit('sync.drift', { driftMs });\n }\n }\n\n /**\n * Clear playback queue\n */\n clearQueue(): void {\n this.playbackQueue = [];\n this.bufferPosition = 0;\n this.audioBuffer.fill(0);\n }\n\n /**\n * Stop playback\n */\n stop(): void {\n this.clearQueue();\n this.isPlaying = false;\n }\n\n /**\n * Get current playback position in seconds\n */\n getPlaybackPosition(): number {\n if (!this.audioContext) return 0;\n return this.audioContext.currentTime - this.playbackStartTime;\n }\n\n /**\n * Check if currently playing\n */\n getIsPlaying(): boolean {\n return this.isPlaying;\n }\n\n /**\n * Dispose resources\n */\n dispose(): void {\n this.stop();\n this.audioContext?.close();\n this.audioContext = null;\n }\n}\n","/**\n * Interruption Handler\n *\n * VAD-based interruption detection for AI conversations:\n * - Monitors user audio for speech\n * - Detects when user interrupts AI response\n * - Triggers interruption callbacks\n *\n * @category AI\n */\n\nimport { EventEmitter } from '../../events/EventEmitter';\n\n/**\n * Interruption events\n */\nexport interface InterruptionEvents {\n [key: string]: unknown;\n 'speech.detected': { rms: number };\n 'speech.ended': { durationMs: number };\n 'interruption.triggered': { rms: number; durationMs: number };\n}\n\n/**\n * Interruption handler configuration\n *\n * Industry standards applied:\n * - vadThreshold: 0.5 (Silero VAD default)\n * - minSpeechDurationMs: 200ms (Google/Amazon barge-in standard)\n * - silenceTimeoutMs: 500ms (OpenAI Realtime API standard)\n */\nexport interface InterruptionConfig {\n /** VAD probability threshold for speech detection (default: 0.5, Silero standard) */\n vadThreshold?: number;\n /** Minimum speech duration to trigger interruption (default: 200ms, Google/Amazon standard) */\n minSpeechDurationMs?: number;\n /** Silence duration to end speech (default: 500ms, OpenAI standard) */\n silenceTimeoutMs?: number;\n /** Enable interruption detection (default: true) */\n enabled?: boolean;\n}\n\n/**\n * Interruption Handler\n */\nexport class InterruptionHandler extends EventEmitter<InterruptionEvents> {\n private config: Required<InterruptionConfig>;\n private isSpeaking = false;\n private speechStartTime = 0;\n private lastSpeechTime = 0;\n private silenceTimer: ReturnType<typeof setTimeout> | null = null;\n private aiIsSpeaking = false;\n\n // Debouncing: only emit one interruption per speech session\n private interruptionTriggeredThisSession = false;\n\n constructor(config: InterruptionConfig = {}) {\n super();\n this.config = {\n vadThreshold: 0.5, // Silero VAD default\n minSpeechDurationMs: 200, // Google/Amazon barge-in standard\n silenceTimeoutMs: 500, // OpenAI Realtime API standard\n enabled: true,\n ...config,\n };\n }\n\n /**\n * Process VAD result for interruption detection\n * @param vadProbability - Speech probability from VAD (0-1)\n * @param audioEnergy - Optional RMS energy for logging (default: 0)\n */\n processVADResult(vadProbability: number, audioEnergy: number = 0): void {\n if (!this.config.enabled) return;\n\n if (vadProbability > this.config.vadThreshold) {\n this.onSpeechDetected(audioEnergy || vadProbability);\n } else {\n this.onSilenceDetected();\n }\n }\n\n /**\n * @deprecated Use processVADResult() instead. This method uses naive RMS detection.\n * Process audio samples for VAD (legacy - uses simple RMS)\n */\n processAudio(samples: Float32Array | Int16Array): void {\n if (!this.config.enabled) return;\n\n const rms = this.calculateRMS(samples);\n\n // Use RMS as proxy for VAD probability (less accurate)\n // RMS > 0.02 roughly maps to speech probability > 0.5\n const vadProbability = Math.min(rms / 0.02, 1.0);\n\n if (vadProbability > this.config.vadThreshold) {\n this.onSpeechDetected(rms);\n } else {\n this.onSilenceDetected();\n }\n }\n\n /**\n * Notify that AI started speaking\n */\n setAISpeaking(speaking: boolean): void {\n this.aiIsSpeaking = speaking;\n }\n\n /**\n * Enable/disable interruption detection\n */\n setEnabled(enabled: boolean): void {\n this.config.enabled = enabled;\n if (!enabled) {\n this.reset();\n }\n }\n\n /**\n * Update configuration\n */\n updateConfig(config: Partial<InterruptionConfig>): void {\n this.config = { ...this.config, ...config };\n }\n\n /**\n * Reset state\n */\n reset(): void {\n this.isSpeaking = false;\n this.speechStartTime = 0;\n this.lastSpeechTime = 0;\n this.interruptionTriggeredThisSession = false;\n if (this.silenceTimer) {\n clearTimeout(this.silenceTimer);\n this.silenceTimer = null;\n }\n }\n\n /**\n * Get current state\n */\n getState(): { isSpeaking: boolean; speechDurationMs: number } {\n return {\n isSpeaking: this.isSpeaking,\n speechDurationMs: this.isSpeaking ? Date.now() - this.speechStartTime : 0,\n };\n }\n\n // ==================== Private Methods ====================\n\n private calculateRMS(samples: Float32Array | Int16Array): number {\n let sum = 0;\n const scale = samples instanceof Int16Array ? 32768 : 1;\n\n for (let i = 0; i < samples.length; i++) {\n const sample = samples[i] / scale;\n sum += sample * sample;\n }\n\n return Math.sqrt(sum / samples.length);\n }\n\n private onSpeechDetected(rms: number): void {\n const now = Date.now();\n this.lastSpeechTime = now;\n\n // Clear silence timer\n if (this.silenceTimer) {\n clearTimeout(this.silenceTimer);\n this.silenceTimer = null;\n }\n\n // Start of speech\n if (!this.isSpeaking) {\n this.isSpeaking = true;\n this.speechStartTime = now;\n this.emit('speech.detected', { rms });\n }\n\n // Check for interruption (only emit ONCE per speech session)\n if (this.aiIsSpeaking && !this.interruptionTriggeredThisSession) {\n const speechDuration = now - this.speechStartTime;\n if (speechDuration >= this.config.minSpeechDurationMs) {\n this.interruptionTriggeredThisSession = true;\n this.emit('interruption.triggered', { rms, durationMs: speechDuration });\n }\n }\n }\n\n private onSilenceDetected(): void {\n if (!this.isSpeaking) return;\n\n // Start silence timer\n if (!this.silenceTimer) {\n this.silenceTimer = setTimeout(() => {\n const durationMs = this.lastSpeechTime - this.speechStartTime;\n this.isSpeaking = false;\n this.silenceTimer = null;\n // Reset interruption flag for next speech session\n this.interruptionTriggeredThisSession = false;\n this.emit('speech.ended', { durationMs });\n }, this.config.silenceTimeoutMs);\n }\n }\n}\n","/**\n * Animation Graph Types\n *\n * Renderer-agnostic animation state machine with emotion and audio-driven blending.\n *\n * @module animation\n */\n\n/**\n * Emotion labels for animation blending\n * Note: These are the 8 emotion categories used for animation, separate from the\n * internal EmotionName type used by EmotionController.\n */\nexport type EmotionLabel =\n | 'angry'\n | 'calm'\n | 'disgust'\n | 'fearful'\n | 'happy'\n | 'neutral'\n | 'sad'\n | 'surprised';\n\n/**\n * High-level animation states\n */\nexport type AnimationStateName = 'idle' | 'listening' | 'thinking' | 'speaking';\n\n/**\n * Events that trigger state transitions\n */\nexport type AnimationTrigger =\n | 'user_speech_start'\n | 'user_speech_end'\n | 'transcript_ready'\n | 'ai_response_start'\n | 'ai_audio_start'\n | 'ai_response_end'\n | 'timeout'\n | 'interrupt';\n\n/**\n * Animation layer types for blending\n */\nexport type AnimationLayer = 'base' | 'emotion' | 'gesture' | 'additive';\n\n/**\n * A single animation clip reference\n */\nexport interface AnimationClip {\n /** Unique identifier for the clip */\n name: string;\n /** Animation layer this clip belongs to */\n layer: AnimationLayer;\n /** Whether this clip loops */\n loop: boolean;\n /** Default duration in seconds (can be overridden by actual clip) */\n duration?: number;\n}\n\n/**\n * Blend weight for an animation clip\n */\nexport interface BlendWeight {\n /** Clip name */\n clip: string;\n /** Weight 0-1 */\n weight: number;\n /** Playback speed multiplier */\n speed: number;\n /** Current time in the animation (0-1 normalized) */\n time: number;\n}\n\n/**\n * Animation state definition\n */\nexport interface AnimationState {\n /** State name */\n name: AnimationStateName;\n /** Base animation clips for this state */\n baseClips: string[];\n /** Blend weights for base clips */\n baseWeights: number[];\n /** Whether emotion overlay is enabled in this state */\n emotionBlendEnabled: boolean;\n /** Whether gesture layer is enabled in this state */\n gestureBlendEnabled: boolean;\n /** Timeout in ms to auto-transition (0 = no timeout) */\n timeout: number;\n /** State to transition to on timeout */\n timeoutTarget?: AnimationStateName;\n}\n\n/**\n * Transition between states\n */\nexport interface Transition {\n /** Source state */\n from: AnimationStateName;\n /** Target state */\n to: AnimationStateName;\n /** Event that triggers this transition */\n trigger: AnimationTrigger;\n /** Blend duration in ms */\n duration: number;\n /** Optional condition function */\n condition?: () => boolean;\n}\n\n/**\n * Emotion to animation mapping\n */\nexport interface EmotionAnimationMap {\n /** Emotion label */\n emotion: EmotionLabel;\n /** Animation clip to blend */\n clip: string;\n /** Maximum blend weight for this emotion */\n maxWeight: number;\n /** Blend speed (weight change per second) */\n blendSpeed: number;\n}\n\n/**\n * Configuration for AnimationGraph\n */\nexport interface AnimationGraphConfig {\n /** Available animation states */\n states: AnimationState[];\n /** Transitions between states */\n transitions: Transition[];\n /** Emotion to animation mappings */\n emotionMappings: EmotionAnimationMap[];\n /** Gesture clips for audio-driven animation */\n gestureClips: string[];\n /** Initial state */\n initialState: AnimationStateName;\n /** Global blend speed for state transitions (weight/sec) */\n transitionBlendSpeed: number;\n /** Minimum audio energy to trigger gestures (0-1) */\n gestureThreshold: number;\n /** Gesture intensity multiplier */\n gestureIntensity: number;\n}\n\n/**\n * Current output of the animation graph\n */\nexport interface AnimationOutput {\n /** Current state name */\n state: AnimationStateName;\n /** All blend weights to apply */\n blendWeights: BlendWeight[];\n /** Active emotion (if any) */\n activeEmotion: EmotionLabel | null;\n /** Current gesture intensity (0-1) */\n gestureIntensity: number;\n /** Whether currently transitioning between states */\n isTransitioning: boolean;\n /** Transition progress (0-1) if transitioning */\n transitionProgress: number;\n}\n\n/**\n * Events emitted by AnimationGraph\n */\nexport type AnimationGraphEvents = {\n /** State changed */\n 'state.change': {\n from: AnimationStateName;\n to: AnimationStateName;\n trigger: AnimationTrigger;\n };\n /** Transition started */\n 'transition.start': {\n from: AnimationStateName;\n to: AnimationStateName;\n duration: number;\n };\n /** Transition completed */\n 'transition.end': {\n state: AnimationStateName;\n };\n /** Emotion changed */\n 'emotion.change': {\n emotion: EmotionLabel | null;\n confidence: number;\n };\n /** Animation output updated (every frame) */\n 'output.update': AnimationOutput;\n /** Index signature for EventEmitter compatibility */\n [key: string]: unknown;\n};\n\n/**\n * Default animation graph configuration\n */\nexport const DEFAULT_ANIMATION_CONFIG: AnimationGraphConfig = {\n initialState: 'idle',\n transitionBlendSpeed: 4.0, // Full blend in 250ms\n gestureThreshold: 0.1,\n gestureIntensity: 1.0,\n\n states: [\n {\n name: 'idle',\n baseClips: ['idle_breathe'],\n baseWeights: [1.0],\n emotionBlendEnabled: true,\n gestureBlendEnabled: false,\n timeout: 0,\n },\n {\n name: 'listening',\n baseClips: ['idle_attentive'],\n baseWeights: [1.0],\n emotionBlendEnabled: true,\n gestureBlendEnabled: false,\n timeout: 10000, // 10s timeout back to idle\n timeoutTarget: 'idle',\n },\n {\n name: 'thinking',\n baseClips: ['thinking_look_up', 'thinking_hand_chin'],\n baseWeights: [0.6, 0.4],\n emotionBlendEnabled: false,\n gestureBlendEnabled: false,\n timeout: 5000, // 5s max thinking\n timeoutTarget: 'idle',\n },\n {\n name: 'speaking',\n baseClips: ['talking_idle'],\n baseWeights: [1.0],\n emotionBlendEnabled: true,\n gestureBlendEnabled: true,\n timeout: 0,\n },\n ],\n\n transitions: [\n // User starts speaking\n { from: 'idle', to: 'listening', trigger: 'user_speech_start', duration: 300 },\n { from: 'speaking', to: 'listening', trigger: 'user_speech_start', duration: 200 }, // Interrupt\n\n // User stops speaking, processing\n { from: 'listening', to: 'thinking', trigger: 'transcript_ready', duration: 400 },\n\n // AI starts responding\n { from: 'thinking', to: 'speaking', trigger: 'ai_audio_start', duration: 300 },\n { from: 'idle', to: 'speaking', trigger: 'ai_audio_start', duration: 400 },\n\n // AI done\n { from: 'speaking', to: 'idle', trigger: 'ai_response_end', duration: 500 },\n\n // Timeouts\n { from: 'listening', to: 'idle', trigger: 'timeout', duration: 600 },\n { from: 'thinking', to: 'idle', trigger: 'timeout', duration: 400 },\n\n // Interrupts\n { from: 'speaking', to: 'listening', trigger: 'interrupt', duration: 150 },\n ],\n\n emotionMappings: [\n { emotion: 'happy', clip: 'emotion_happy', maxWeight: 0.7, blendSpeed: 2.0 },\n { emotion: 'sad', clip: 'emotion_sad', maxWeight: 0.6, blendSpeed: 1.5 },\n { emotion: 'angry', clip: 'emotion_angry', maxWeight: 0.5, blendSpeed: 2.5 },\n { emotion: 'fearful', clip: 'emotion_fear', maxWeight: 0.5, blendSpeed: 2.0 },\n { emotion: 'surprised', clip: 'emotion_surprised', maxWeight: 0.6, blendSpeed: 2.5 },\n { emotion: 'calm', clip: 'emotion_calm', maxWeight: 0.3, blendSpeed: 1.0 },\n { emotion: 'disgust', clip: 'emotion_disgust', maxWeight: 0.4, blendSpeed: 2.0 },\n { emotion: 'neutral', clip: 'emotion_neutral', maxWeight: 0.0, blendSpeed: 1.0 },\n ],\n\n gestureClips: ['gesture_hand_small', 'gesture_hand_medium', 'gesture_hand_large'],\n};\n","/**\n * Animation Graph\n *\n * State machine for character animation with emotion and audio-driven blending.\n * Renderer-agnostic - outputs blend weights that any 3D engine can consume.\n *\n * @example\n * ```typescript\n * import { AnimationGraph, DEFAULT_ANIMATION_CONFIG } from '@omote/core';\n *\n * const graph = new AnimationGraph(DEFAULT_ANIMATION_CONFIG);\n *\n * // Connect to voice pipeline\n * graph.on('output.update', (output) => {\n * // Apply blend weights to your 3D character\n * for (const { clip, weight } of output.blendWeights) {\n * mixer.getAction(clip).setEffectiveWeight(weight);\n * }\n * });\n *\n * // Drive from voice state\n * voiceState.on('listening', () => graph.trigger('user_speech_start'));\n * voiceState.on('thinking', () => graph.trigger('transcript_ready'));\n * voiceState.on('speaking', () => graph.trigger('ai_audio_start'));\n *\n * // Drive from emotion detection\n * emotion.on('result', ({ emotion, confidence }) => {\n * graph.setEmotion(emotion, confidence);\n * });\n *\n * // Update every frame\n * function animate(deltaTime: number) {\n * graph.update(deltaTime);\n * }\n * ```\n *\n * @module animation\n */\n\nimport { EventEmitter } from '../events';\nimport type {\n EmotionLabel,\n AnimationGraphConfig,\n AnimationGraphEvents,\n AnimationTrigger,\n AnimationOutput,\n AnimationState,\n AnimationStateName,\n BlendWeight,\n Transition,\n} from './types';\nimport { DEFAULT_ANIMATION_CONFIG } from './types';\n\n/**\n * Animation state machine with smooth blending\n */\nexport class AnimationGraph extends EventEmitter<AnimationGraphEvents> {\n private config: AnimationGraphConfig;\n private currentState: AnimationState;\n private previousState: AnimationState | null = null;\n\n // Transition state\n private isTransitioning: boolean = false;\n private transitionProgress: number = 0;\n private transitionDuration: number = 0;\n private transitionStartTime: number = 0;\n\n // Emotion state\n private currentEmotion: EmotionLabel | null = null;\n private emotionConfidence: number = 0;\n private emotionBlendWeight: number = 0;\n private targetEmotionWeight: number = 0;\n\n // Gesture state (audio-driven)\n private audioEnergy: number = 0;\n private gestureWeight: number = 0;\n private currentGestureClip: number = 0;\n\n // Timing\n private stateEnterTime: number = 0;\n private lastUpdateTime: number = 0;\n\n // Blend weights cache\n private cachedOutput: AnimationOutput;\n\n constructor(config: Partial<AnimationGraphConfig> = {}) {\n super();\n this.config = { ...DEFAULT_ANIMATION_CONFIG, ...config };\n\n // Find initial state\n const initialState = this.config.states.find(\n (s) => s.name === this.config.initialState\n );\n if (!initialState) {\n throw new Error(`Initial state '${this.config.initialState}' not found`);\n }\n this.currentState = initialState;\n this.stateEnterTime = Date.now();\n this.lastUpdateTime = Date.now();\n\n // Initialize cached output\n this.cachedOutput = this.computeOutput();\n }\n\n /**\n * Get current state name\n */\n get state(): AnimationStateName {\n return this.currentState.name;\n }\n\n /**\n * Get current animation output\n */\n get output(): AnimationOutput {\n return this.cachedOutput;\n }\n\n /**\n * Trigger an animation event (may cause state transition)\n */\n trigger(event: AnimationTrigger): boolean {\n // Find matching transition\n const transition = this.config.transitions.find(\n (t) =>\n t.from === this.currentState.name &&\n t.trigger === event &&\n (!t.condition || t.condition())\n );\n\n if (!transition) {\n return false;\n }\n\n this.startTransition(transition, event);\n return true;\n }\n\n /**\n * Set current emotion (from DistilHuBERT or manual)\n */\n setEmotion(emotion: EmotionLabel, confidence: number): void {\n const prevEmotion = this.currentEmotion;\n\n this.currentEmotion = emotion;\n this.emotionConfidence = Math.max(0, Math.min(1, confidence));\n\n // Find emotion mapping\n const mapping = this.config.emotionMappings.find(\n (m) => m.emotion === emotion\n );\n if (mapping && this.currentState.emotionBlendEnabled) {\n this.targetEmotionWeight = mapping.maxWeight * this.emotionConfidence;\n } else {\n this.targetEmotionWeight = 0;\n }\n\n if (prevEmotion !== emotion) {\n this.emit('emotion.change', { emotion, confidence });\n }\n }\n\n /**\n * Clear current emotion\n */\n clearEmotion(): void {\n this.currentEmotion = null;\n this.emotionConfidence = 0;\n this.targetEmotionWeight = 0;\n this.emit('emotion.change', { emotion: null, confidence: 0 });\n }\n\n /**\n * Set audio energy for gesture animation (0-1)\n */\n setAudioEnergy(energy: number): void {\n this.audioEnergy = Math.max(0, Math.min(1, energy));\n }\n\n /**\n * Force transition to a specific state\n */\n setState(stateName: AnimationStateName, blendDuration: number = 300): void {\n const targetState = this.config.states.find((s) => s.name === stateName);\n if (!targetState) {\n console.warn(`[AnimationGraph] State '${stateName}' not found`);\n return;\n }\n\n if (targetState.name === this.currentState.name && !this.isTransitioning) {\n return;\n }\n\n // Create a manual transition\n const manualTransition: Transition = {\n from: this.currentState.name,\n to: stateName,\n trigger: 'timeout', // Arbitrary, not used for manual\n duration: blendDuration,\n };\n\n this.startTransition(manualTransition, 'timeout');\n }\n\n /**\n * Update animation graph (call every frame)\n * @param deltaMs Time since last update in milliseconds\n */\n update(deltaMs?: number): AnimationOutput {\n const now = Date.now();\n const dt = deltaMs ?? now - this.lastUpdateTime;\n this.lastUpdateTime = now;\n\n const dtSeconds = dt / 1000;\n\n // Update transition\n if (this.isTransitioning) {\n this.updateTransition(dtSeconds);\n }\n\n // Check timeout\n this.checkTimeout(now);\n\n // Update emotion blend\n this.updateEmotionBlend(dtSeconds);\n\n // Update gesture\n this.updateGesture(dtSeconds);\n\n // Compute and cache output\n this.cachedOutput = this.computeOutput();\n this.emit('output.update', this.cachedOutput);\n\n return this.cachedOutput;\n }\n\n /**\n * Reset to initial state\n */\n reset(): void {\n const initialState = this.config.states.find(\n (s) => s.name === this.config.initialState\n );\n if (initialState) {\n this.currentState = initialState;\n this.previousState = null;\n this.isTransitioning = false;\n this.transitionProgress = 0;\n this.stateEnterTime = Date.now();\n this.emotionBlendWeight = 0;\n this.gestureWeight = 0;\n this.cachedOutput = this.computeOutput();\n }\n }\n\n /**\n * Get all clip names used by this graph\n */\n getRequiredClips(): string[] {\n const clips = new Set<string>();\n\n // Base clips from all states\n for (const state of this.config.states) {\n for (const clip of state.baseClips) {\n clips.add(clip);\n }\n }\n\n // Emotion clips\n for (const mapping of this.config.emotionMappings) {\n clips.add(mapping.clip);\n }\n\n // Gesture clips\n for (const clip of this.config.gestureClips) {\n clips.add(clip);\n }\n\n return Array.from(clips);\n }\n\n // ─────────────────────────────────────────────────────────────────\n // Private methods\n // ─────────────────────────────────────────────────────────────────\n\n private startTransition(transition: Transition, event: AnimationTrigger): void {\n const targetState = this.config.states.find(\n (s) => s.name === transition.to\n );\n if (!targetState) {\n console.warn(`[AnimationGraph] Target state '${transition.to}' not found`);\n return;\n }\n\n const fromState = this.currentState.name;\n\n this.previousState = this.currentState;\n this.currentState = targetState;\n this.isTransitioning = true;\n this.transitionProgress = 0;\n this.transitionDuration = transition.duration;\n this.transitionStartTime = Date.now();\n this.stateEnterTime = Date.now();\n\n // Update emotion target based on new state\n if (!this.currentState.emotionBlendEnabled) {\n this.targetEmotionWeight = 0;\n }\n\n this.emit('state.change', {\n from: fromState,\n to: targetState.name,\n trigger: event,\n });\n\n this.emit('transition.start', {\n from: fromState,\n to: targetState.name,\n duration: transition.duration,\n });\n }\n\n private updateTransition(dtSeconds: number): void {\n if (!this.isTransitioning || this.transitionDuration <= 0) {\n this.isTransitioning = false;\n this.transitionProgress = 1;\n return;\n }\n\n // Linear progress based on time\n const elapsed = Date.now() - this.transitionStartTime;\n this.transitionProgress = Math.min(1, elapsed / this.transitionDuration);\n\n if (this.transitionProgress >= 1) {\n this.isTransitioning = false;\n this.transitionProgress = 1;\n this.previousState = null;\n this.emit('transition.end', { state: this.currentState.name });\n }\n }\n\n private checkTimeout(now: number): void {\n if (this.isTransitioning) return;\n if (this.currentState.timeout <= 0) return;\n\n const elapsed = now - this.stateEnterTime;\n if (elapsed >= this.currentState.timeout) {\n this.trigger('timeout');\n }\n }\n\n private updateEmotionBlend(dtSeconds: number): void {\n if (!this.currentEmotion) {\n // Decay emotion weight\n this.emotionBlendWeight = Math.max(\n 0,\n this.emotionBlendWeight - dtSeconds * 2.0\n );\n return;\n }\n\n const mapping = this.config.emotionMappings.find(\n (m) => m.emotion === this.currentEmotion\n );\n const blendSpeed = mapping?.blendSpeed ?? 2.0;\n\n // Smoothly interpolate to target\n const diff = this.targetEmotionWeight - this.emotionBlendWeight;\n const maxChange = blendSpeed * dtSeconds;\n\n if (Math.abs(diff) <= maxChange) {\n this.emotionBlendWeight = this.targetEmotionWeight;\n } else {\n this.emotionBlendWeight += Math.sign(diff) * maxChange;\n }\n }\n\n private updateGesture(dtSeconds: number): void {\n if (!this.currentState.gestureBlendEnabled) {\n this.gestureWeight = Math.max(0, this.gestureWeight - dtSeconds * 4.0);\n return;\n }\n\n // Map audio energy to gesture weight\n const targetGesture =\n this.audioEnergy > this.config.gestureThreshold\n ? this.audioEnergy * this.config.gestureIntensity\n : 0;\n\n // Smooth the gesture weight\n const diff = targetGesture - this.gestureWeight;\n const blendSpeed = 8.0; // Fast response\n const maxChange = blendSpeed * dtSeconds;\n\n if (Math.abs(diff) <= maxChange) {\n this.gestureWeight = targetGesture;\n } else {\n this.gestureWeight += Math.sign(diff) * maxChange;\n }\n\n // Select gesture clip based on intensity\n const clipCount = this.config.gestureClips.length;\n if (clipCount > 0) {\n this.currentGestureClip = Math.min(\n clipCount - 1,\n Math.floor(this.gestureWeight * clipCount)\n );\n }\n }\n\n private computeOutput(): AnimationOutput {\n const blendWeights: BlendWeight[] = [];\n\n // Smooth transition weight (ease in-out)\n const t = this.transitionProgress;\n const transitionWeight = t * t * (3 - 2 * t); // smoothstep\n\n // Previous state clips (fading out)\n if (this.previousState && this.isTransitioning) {\n const fadeOut = 1 - transitionWeight;\n for (let i = 0; i < this.previousState.baseClips.length; i++) {\n const clip = this.previousState.baseClips[i];\n const baseWeight = this.previousState.baseWeights[i] ?? 1.0;\n blendWeights.push({\n clip,\n weight: baseWeight * fadeOut,\n speed: 1.0,\n time: 0,\n });\n }\n }\n\n // Current state clips (fading in or full)\n const fadeIn = this.isTransitioning ? transitionWeight : 1.0;\n for (let i = 0; i < this.currentState.baseClips.length; i++) {\n const clip = this.currentState.baseClips[i];\n const baseWeight = this.currentState.baseWeights[i] ?? 1.0;\n blendWeights.push({\n clip,\n weight: baseWeight * fadeIn,\n speed: 1.0,\n time: 0,\n });\n }\n\n // Emotion overlay\n if (this.currentEmotion && this.emotionBlendWeight > 0.01) {\n const mapping = this.config.emotionMappings.find(\n (m) => m.emotion === this.currentEmotion\n );\n if (mapping) {\n blendWeights.push({\n clip: mapping.clip,\n weight: this.emotionBlendWeight,\n speed: 1.0,\n time: 0,\n });\n }\n }\n\n // Gesture layer\n if (this.gestureWeight > 0.01 && this.config.gestureClips.length > 0) {\n const gestureClip = this.config.gestureClips[this.currentGestureClip];\n blendWeights.push({\n clip: gestureClip,\n weight: this.gestureWeight,\n speed: 1.0 + this.audioEnergy * 0.5, // Faster with more energy\n time: 0,\n });\n }\n\n return {\n state: this.currentState.name,\n blendWeights,\n activeEmotion: this.emotionBlendWeight > 0.01 ? this.currentEmotion : null,\n gestureIntensity: this.gestureWeight,\n isTransitioning: this.isTransitioning,\n transitionProgress: this.transitionProgress,\n };\n }\n}\n","/**\n * Minimal 2D Simplex Noise\n *\n * Based on Stefan Gustavson's public domain implementation.\n * Returns values in [-1, 1] range. Used by ProceduralLifeLayer\n * for organic, non-repetitive brow drift animation.\n *\n * @module animation\n */\n\n// Permutation table (doubled to avoid wrapping)\nconst perm = new Uint8Array(512);\nconst grad2 = [\n [1, 1], [-1, 1], [1, -1], [-1, -1],\n [1, 0], [-1, 0], [0, 1], [0, -1],\n];\n\n// Seed the permutation table deterministically\nconst p = [\n 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225,\n 140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148,\n 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32,\n 57, 177, 33, 88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175,\n 74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122,\n 60, 211, 133, 230, 220, 105, 92, 41, 55, 46, 245, 40, 244, 102, 143, 54,\n 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169,\n 200, 196, 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64,\n 52, 217, 226, 250, 124, 123, 5, 202, 38, 147, 118, 126, 255, 82, 85, 212,\n 207, 206, 59, 227, 47, 16, 58, 17, 182, 189, 28, 42, 223, 183, 170, 213,\n 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9,\n 129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, 104,\n 218, 246, 97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241,\n 81, 51, 145, 235, 249, 14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157,\n 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93,\n 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180,\n];\nfor (let i = 0; i < 256; i++) {\n perm[i] = p[i];\n perm[i + 256] = p[i];\n}\n\nconst F2 = 0.5 * (Math.sqrt(3) - 1);\nconst G2 = (3 - Math.sqrt(3)) / 6;\n\nfunction dot2(g: number[], x: number, y: number): number {\n return g[0] * x + g[1] * y;\n}\n\n/**\n * 2D Simplex Noise\n *\n * @param x - X coordinate\n * @param y - Y coordinate\n * @returns Noise value in [-1, 1]\n */\nexport function simplex2d(x: number, y: number): number {\n // Skew input space to determine simplex cell\n const s = (x + y) * F2;\n const i = Math.floor(x + s);\n const j = Math.floor(y + s);\n\n const t = (i + j) * G2;\n const X0 = i - t;\n const Y0 = j - t;\n const x0 = x - X0;\n const y0 = y - Y0;\n\n // Determine which simplex we're in\n const i1 = x0 > y0 ? 1 : 0;\n const j1 = x0 > y0 ? 0 : 1;\n\n const x1 = x0 - i1 + G2;\n const y1 = y0 - j1 + G2;\n const x2 = x0 - 1 + 2 * G2;\n const y2 = y0 - 1 + 2 * G2;\n\n const ii = i & 255;\n const jj = j & 255;\n const gi0 = perm[ii + perm[jj]] % 8;\n const gi1 = perm[ii + i1 + perm[jj + j1]] % 8;\n const gi2 = perm[ii + 1 + perm[jj + 1]] % 8;\n\n // Calculate contribution from three corners\n let n0 = 0;\n let t0 = 0.5 - x0 * x0 - y0 * y0;\n if (t0 >= 0) {\n t0 *= t0;\n n0 = t0 * t0 * dot2(grad2[gi0], x0, y0);\n }\n\n let n1 = 0;\n let t1 = 0.5 - x1 * x1 - y1 * y1;\n if (t1 >= 0) {\n t1 *= t1;\n n1 = t1 * t1 * dot2(grad2[gi1], x1, y1);\n }\n\n let n2 = 0;\n let t2 = 0.5 - x2 * x2 - y2 * y2;\n if (t2 >= 0) {\n t2 *= t2;\n n2 = t2 * t2 * dot2(grad2[gi2], x2, y2);\n }\n\n // Scale to [-1, 1]\n return 70 * (n0 + n1 + n2);\n}\n","/**\n * ProceduralLifeLayer - Renderer-agnostic procedural animation system\n *\n * Outputs per-frame blendshape values and head deltas for organic life-like\n * animation. No Three.js, no React, no R3F — just math.\n *\n * Implements research-based eye behavior, blinks, gaze breaks, microsaccades,\n * breathing/postural sway, and simplex noise-driven brow drift.\n *\n * Research sources:\n * - Blink frequency: 15-20/min (every 3-4s), PMC4043155\n * - Saccade latency: ~200ms, duration 20-200ms\n * - Microsaccades: ~1/second, amplitude 0.02-0.05, Scholarpedia\n * - Fixation duration: 200-350ms, Nature Scientific Reports\n * - Brow noise: NVIDIA Audio2Face, Unreal MetaHuman layered procedural animation\n *\n * @category Animation\n *\n * @example\n * ```typescript\n * import { ProceduralLifeLayer } from '@omote/core';\n *\n * const lifeLayer = new ProceduralLifeLayer();\n *\n * // In animation loop:\n * const output = lifeLayer.update(delta, {\n * eyeTargetX: normalizedX, // -1..1 from camera math\n * eyeTargetY: normalizedY,\n * audioEnergy: energy, // 0-1 from AudioEnergyAnalyzer\n * isSpeaking: true,\n * });\n *\n * // Apply blendshapes to mesh\n * for (const [name, value] of Object.entries(output.blendshapes)) {\n * const idx = mesh.morphTargetDictionary?.[name];\n * if (idx !== undefined) mesh.morphTargetInfluences![idx] = value;\n * }\n *\n * // Apply head delta to head bone\n * headBone.rotation.y += output.headDelta.yaw;\n * headBone.rotation.x += output.headDelta.pitch;\n * ```\n */\n\nimport { simplex2d } from './simplex2d';\n\n/**\n * Configuration for ProceduralLifeLayer\n */\nexport interface LifeLayerConfig {\n /** Seconds between blinks [min, max]. Default: [2.5, 6] */\n blinkIntervalRange?: [number, number];\n /** Seconds between gaze breaks [min, max]. Default: [3, 8] */\n gazeBreakIntervalRange?: [number, number];\n /** Gaze break deviation range [min, max]. Default: [0.15, 0.4] */\n gazeBreakAmplitudeRange?: [number, number];\n /** Eye micro-motion noise amplitude (0 to disable). Default: 0.06 */\n eyeNoiseAmplitude?: number;\n /** Base simplex noise amplitude for brow drift. Default: 0.30 */\n browNoiseAmplitude?: number;\n /** Multiply brow noise when speaking. Default: 2.0 */\n browNoiseSpeechMultiplier?: number;\n /** Breathing rate in Hz (0.25 = 15 breaths/min). Default: 0.25 */\n breathingRate?: number;\n /** Postural sway amplitude in radians. Default: 0.002 */\n posturalSwayAmplitude?: number;\n /** Max eye movement from center (0-1). Default: 0.8 */\n eyeMaxDeviation?: number;\n /** Eye smoothing factor (higher = faster response). Default: 15 */\n eyeSmoothing?: number;\n}\n\n/**\n * Per-frame input to the life layer\n */\nexport interface LifeLayerInput {\n /** Normalized eye target X: -1 (left) to 1 (right). Consumer computes from camera. */\n eyeTargetX?: number;\n /** Normalized eye target Y: -1 (down) to 1 (up). Consumer computes from camera. */\n eyeTargetY?: number;\n /** Audio energy 0-1 (from AudioEnergyAnalyzer). Drives brow noise amplitude. */\n audioEnergy?: number;\n /** Whether avatar is speaking. Multiplies brow noise amplitude. */\n isSpeaking?: boolean;\n}\n\n/**\n * Per-frame output from the life layer\n */\nexport interface LifeLayerOutput {\n /** Blendshape values to SET directly on mesh (eyes, brows, cheeks). */\n blendshapes: Record<string, number>;\n /** Head rotation deltas in radians. Consumer adds to head bone rotation. */\n headDelta: { yaw: number; pitch: number };\n}\n\n// Blink phase constants\nconst PHASE_OPEN = 0;\nconst PHASE_CLOSING = 1;\nconst PHASE_CLOSED = 2;\nconst PHASE_OPENING = 3;\n\n// Blink timing (researched: fast close, brief hold, slower open)\nconst BLINK_CLOSE_DURATION = 0.06; // 60ms\nconst BLINK_HOLD_DURATION = 0.04; // 40ms\nconst BLINK_OPEN_DURATION = 0.15; // 150ms\nconst BLINK_ASYMMETRY_DELAY = 0.008; // 8ms offset between eyes\n\n// Gaze break phase timing\nconst GAZE_BREAK_DURATION = 0.12; // 120ms to glance away (~7 frames, smooth onset)\nconst GAZE_BREAK_HOLD_DURATION = 0.3; // 300ms hold\nconst GAZE_BREAK_RETURN_DURATION = 0.15; // 150ms to return\n\n// Eye micro-motion noise frequencies (Hz) — continuous drift, no discrete events\nconst EYE_NOISE_X_FREQ = 0.8;\nconst EYE_NOISE_Y_FREQ = 0.6;\nconst EYE_NOISE_X_PHASE = 73.1; // offset to decorrelate from brow noise\nconst EYE_NOISE_Y_PHASE = 91.7;\n\n// Brow noise frequencies (Hz) — organic drift, fast enough to be visible\nconst BROW_INNER_UP_FREQ = 0.4;\nconst BROW_OUTER_LEFT_FREQ = 0.35;\nconst BROW_OUTER_RIGHT_FREQ = 0.38;\nconst BROW_DOWN_FREQ = 0.3;\n\n// Brow noise phase offsets for each channel\nconst BROW_INNER_UP_PHASE = 0;\nconst BROW_OUTER_LEFT_PHASE = 17.3;\nconst BROW_OUTER_RIGHT_PHASE = 31.7;\nconst BROW_DOWN_LEFT_PHASE = 47.1;\nconst BROW_DOWN_RIGHT_PHASE = 59.3;\n\n// Emphasis detection\nconst EMPHASIS_ENERGY_THRESHOLD = 0.3;\nconst EMPHASIS_DECAY_RATE = 4.0; // per second\n\nfunction clamp(v: number, min: number, max: number): number {\n return v < min ? min : v > max ? max : v;\n}\n\nfunction randomRange(min: number, max: number): number {\n return min + Math.random() * (max - min);\n}\n\nfunction smoothStep(t: number): number {\n return t * t * (3 - 2 * t);\n}\n\nfunction softClamp(v: number, max: number): number {\n return Math.tanh(v / max) * max;\n}\n\n/**\n * ProceduralLifeLayer - Renderer-agnostic procedural animation\n *\n * Generates per-frame blendshape values and head rotation deltas\n * for natural eye behavior, blinks, brow movement, and breathing.\n */\nexport class ProceduralLifeLayer {\n // Config\n private blinkIntervalRange: [number, number];\n private gazeBreakIntervalRange: [number, number];\n private gazeBreakAmplitudeRange: [number, number];\n private eyeNoiseAmplitude: number;\n private browNoiseAmplitude: number;\n private browNoiseSpeechMultiplier: number;\n private breathingRate: number;\n private posturalSwayAmplitude: number;\n private eyeMaxDeviation: number;\n private eyeSmoothing: number;\n\n // Blink state\n private blinkTimer = 0;\n private blinkInterval: number;\n private blinkPhase = PHASE_OPEN;\n private blinkProgress = 0;\n private asymmetryRight = 0.97;\n private smoothedBlinkLeft = 0;\n private smoothedBlinkRight = 0;\n\n // Eye contact (smoothed)\n private smoothedEyeX = 0;\n private smoothedEyeY = 0;\n\n // Eye micro-motion (continuous simplex noise, no discrete events)\n private eyeNoiseTime = 0;\n\n // Gaze break state\n private gazeBreakTimer = 0;\n private gazeBreakInterval: number;\n private gazeBreakPhase = PHASE_OPEN;\n private gazeBreakProgress = 0;\n private gazeBreakTargetX = 0;\n private gazeBreakTargetY = 0;\n private gazeBreakCurrentX = 0;\n private gazeBreakCurrentY = 0;\n\n // Breathing / postural sway\n private microMotionTime = 0;\n private breathingPhase = 0;\n\n // Brow noise\n private noiseTime = 0;\n private previousEnergy = 0;\n private emphasisLevel = 0;\n\n constructor(config?: LifeLayerConfig) {\n this.blinkIntervalRange = config?.blinkIntervalRange ?? [2.5, 6];\n this.gazeBreakIntervalRange = config?.gazeBreakIntervalRange ?? [3, 8];\n this.gazeBreakAmplitudeRange = config?.gazeBreakAmplitudeRange ?? [0.15, 0.4];\n this.eyeNoiseAmplitude = config?.eyeNoiseAmplitude ?? 0.06;\n this.browNoiseAmplitude = config?.browNoiseAmplitude ?? 0.30;\n this.browNoiseSpeechMultiplier = config?.browNoiseSpeechMultiplier ?? 2.0;\n this.breathingRate = config?.breathingRate ?? 0.25;\n this.posturalSwayAmplitude = config?.posturalSwayAmplitude ?? 0.002;\n this.eyeMaxDeviation = config?.eyeMaxDeviation ?? 0.8;\n this.eyeSmoothing = config?.eyeSmoothing ?? 15;\n\n // Initialize with random intervals\n this.blinkInterval = randomRange(...this.blinkIntervalRange);\n this.gazeBreakInterval = randomRange(...this.gazeBreakIntervalRange);\n }\n\n /**\n * Update the life layer and produce output for this frame.\n *\n * @param delta - Time since last frame in seconds\n * @param input - Per-frame input (eye target, audio energy, speaking state)\n * @returns Blendshape values and head rotation deltas\n */\n update(delta: number, input?: LifeLayerInput): LifeLayerOutput {\n const eyeTargetX = input?.eyeTargetX ?? 0;\n const eyeTargetY = input?.eyeTargetY ?? 0;\n const audioEnergy = input?.audioEnergy ?? 0;\n const isSpeaking = input?.isSpeaking ?? false;\n\n // Cap delta to prevent instant snaps when returning from backgrounded tab\n const safeDelta = Math.min(delta, 0.1);\n\n const blendshapes: Record<string, number> = {};\n\n // =====================================================================\n // BLINKS\n // =====================================================================\n this.updateBlinks(delta);\n\n const blinkSmoothing = 45;\n const blinkValues = this.getBlinkValues();\n this.smoothedBlinkLeft += (blinkValues.left - this.smoothedBlinkLeft) * Math.min(1, safeDelta * blinkSmoothing);\n this.smoothedBlinkRight += (blinkValues.right - this.smoothedBlinkRight) * Math.min(1, safeDelta * blinkSmoothing);\n\n blendshapes['eyeBlinkLeft'] = this.smoothedBlinkLeft;\n blendshapes['eyeBlinkRight'] = this.smoothedBlinkRight;\n\n // =====================================================================\n // EYE CONTACT (smooth toward target)\n // =====================================================================\n this.smoothedEyeX += (eyeTargetX - this.smoothedEyeX) * Math.min(1, safeDelta * this.eyeSmoothing);\n this.smoothedEyeY += (eyeTargetY - this.smoothedEyeY) * Math.min(1, safeDelta * this.eyeSmoothing);\n\n // =====================================================================\n // EYE MICRO-MOTION (continuous simplex noise replaces discrete saccades)\n // =====================================================================\n this.eyeNoiseTime += delta;\n const microMotion = this.getEyeMicroMotion();\n\n // =====================================================================\n // GAZE BREAKS\n // =====================================================================\n this.updateGazeBreaks(delta);\n\n // =====================================================================\n // COMBINED EYE DIRECTION\n // =====================================================================\n const finalEyeX = this.smoothedEyeX + this.gazeBreakCurrentX + microMotion.x;\n const finalEyeY = this.smoothedEyeY + this.gazeBreakCurrentY + microMotion.y;\n\n // Soft clamp: tanh compression avoids hard boundary pop\n const clampedX = softClamp(finalEyeX, this.eyeMaxDeviation);\n const clampedY = softClamp(finalEyeY, this.eyeMaxDeviation);\n\n // Convert to blendshape values with smooth zero-crossing\n // Quadratic dead zone prevents hard direction switch when crossing center\n const deadZone = 0.02;\n const lookRight = clampedX > deadZone ? clampedX\n : clampedX > 0 ? clampedX * (clampedX / deadZone) : 0;\n const lookLeft = clampedX < -deadZone ? -clampedX\n : clampedX < 0 ? -clampedX * (-clampedX / deadZone) : 0;\n const lookUp = clampedY > deadZone ? clampedY\n : clampedY > 0 ? clampedY * (clampedY / deadZone) : 0;\n const lookDown = clampedY < -deadZone ? -clampedY\n : clampedY < 0 ? -clampedY * (-clampedY / deadZone) : 0;\n\n // Conjugate pairing: both eyes move together naturally\n blendshapes['eyeLookInLeft'] = lookRight;\n blendshapes['eyeLookOutLeft'] = lookLeft;\n blendshapes['eyeLookInRight'] = lookLeft;\n blendshapes['eyeLookOutRight'] = lookRight;\n blendshapes['eyeLookUpLeft'] = lookUp;\n blendshapes['eyeLookUpRight'] = lookUp;\n blendshapes['eyeLookDownLeft'] = lookDown;\n blendshapes['eyeLookDownRight'] = lookDown;\n\n // =====================================================================\n // BROW NOISE (Simplex-driven organic drift)\n // =====================================================================\n this.updateBrowNoise(delta, audioEnergy, isSpeaking, blendshapes);\n\n // =====================================================================\n // BREATHING + POSTURAL SWAY\n // =====================================================================\n this.microMotionTime += delta;\n this.breathingPhase += delta * this.breathingRate * Math.PI * 2;\n\n const breathingY = Math.sin(this.breathingPhase) * 0.003;\n const swayAmp = this.posturalSwayAmplitude;\n const swayX = Math.sin(this.microMotionTime * 0.7) * swayAmp +\n Math.sin(this.microMotionTime * 1.3) * swayAmp * 0.5;\n const swayY = Math.sin(this.microMotionTime * 0.5) * swayAmp * 0.75 +\n Math.sin(this.microMotionTime * 0.9) * swayAmp * 0.5;\n\n return {\n blendshapes,\n headDelta: {\n yaw: swayX,\n pitch: breathingY + swayY,\n },\n };\n }\n\n /**\n * Reset all internal state to initial values.\n */\n reset(): void {\n // Blink\n this.blinkTimer = 0;\n this.blinkInterval = randomRange(...this.blinkIntervalRange);\n this.blinkPhase = PHASE_OPEN;\n this.blinkProgress = 0;\n this.asymmetryRight = 0.97;\n this.smoothedBlinkLeft = 0;\n this.smoothedBlinkRight = 0;\n\n // Eye contact\n this.smoothedEyeX = 0;\n this.smoothedEyeY = 0;\n\n // Eye micro-motion\n this.eyeNoiseTime = 0;\n\n // Gaze break\n this.gazeBreakTimer = 0;\n this.gazeBreakInterval = randomRange(...this.gazeBreakIntervalRange);\n this.gazeBreakPhase = PHASE_OPEN;\n this.gazeBreakProgress = 0;\n this.gazeBreakTargetX = 0;\n this.gazeBreakTargetY = 0;\n this.gazeBreakCurrentX = 0;\n this.gazeBreakCurrentY = 0;\n\n // Breathing / sway\n this.microMotionTime = 0;\n this.breathingPhase = 0;\n\n // Brow noise\n this.noiseTime = 0;\n this.previousEnergy = 0;\n this.emphasisLevel = 0;\n }\n\n // =====================================================================\n // PRIVATE: Blink system\n // =====================================================================\n\n private updateBlinks(delta: number): void {\n this.blinkTimer += delta;\n\n if (this.blinkTimer >= this.blinkInterval && this.blinkPhase === PHASE_OPEN) {\n this.blinkPhase = PHASE_CLOSING;\n this.blinkProgress = 0;\n this.blinkTimer = 0;\n this.blinkInterval = randomRange(...this.blinkIntervalRange);\n this.asymmetryRight = 0.95 + Math.random() * 0.08;\n }\n\n if (this.blinkPhase > PHASE_OPEN) {\n this.blinkProgress += delta;\n\n if (this.blinkPhase === PHASE_CLOSING) {\n if (this.blinkProgress >= BLINK_CLOSE_DURATION) {\n this.blinkPhase = PHASE_CLOSED;\n this.blinkProgress = 0;\n }\n } else if (this.blinkPhase === PHASE_CLOSED) {\n if (this.blinkProgress >= BLINK_HOLD_DURATION) {\n this.blinkPhase = PHASE_OPENING;\n this.blinkProgress = 0;\n }\n } else if (this.blinkPhase === PHASE_OPENING) {\n if (this.blinkProgress >= BLINK_OPEN_DURATION) {\n this.blinkPhase = PHASE_OPEN;\n this.blinkProgress = 0;\n }\n }\n }\n }\n\n private getBlinkValues(): { left: number; right: number } {\n if (this.blinkPhase === PHASE_OPEN) {\n return { left: 0, right: 0 };\n }\n\n if (this.blinkPhase === PHASE_CLOSING) {\n const t = Math.min(1, this.blinkProgress / BLINK_CLOSE_DURATION);\n const eased = t * t * t; // Cubic ease-in for snappy close\n const tRight = Math.max(0, Math.min(1, (this.blinkProgress - BLINK_ASYMMETRY_DELAY) / BLINK_CLOSE_DURATION));\n return {\n left: eased,\n right: tRight * tRight * tRight * this.asymmetryRight,\n };\n }\n\n if (this.blinkPhase === PHASE_CLOSED) {\n return { left: 1, right: this.asymmetryRight };\n }\n\n // PHASE_OPENING\n const t = Math.min(1, this.blinkProgress / BLINK_OPEN_DURATION);\n const eased = smoothStep(t);\n return {\n left: 1 - eased,\n right: (1 - eased) * this.asymmetryRight,\n };\n }\n\n // =====================================================================\n // PRIVATE: Eye micro-motion (continuous simplex noise)\n // =====================================================================\n\n private getEyeMicroMotion(): { x: number; y: number } {\n const amp = this.eyeNoiseAmplitude;\n const x = simplex2d(this.eyeNoiseTime * EYE_NOISE_X_FREQ, EYE_NOISE_X_PHASE) * amp;\n const y = simplex2d(this.eyeNoiseTime * EYE_NOISE_Y_FREQ, EYE_NOISE_Y_PHASE) * amp * 0.7;\n return { x, y };\n }\n\n // =====================================================================\n // PRIVATE: Gaze breaks\n // =====================================================================\n\n private updateGazeBreaks(delta: number): void {\n this.gazeBreakTimer += delta;\n\n if (this.gazeBreakTimer >= this.gazeBreakInterval && this.gazeBreakPhase === PHASE_OPEN) {\n this.gazeBreakPhase = PHASE_CLOSING; // Reusing: 1 = breaking away\n this.gazeBreakProgress = 0;\n this.gazeBreakTimer = 0;\n\n const amp = randomRange(...this.gazeBreakAmplitudeRange);\n this.gazeBreakTargetX = (Math.random() - 0.5) * 2 * amp;\n this.gazeBreakTargetY = (Math.random() - 0.5) * amp * 0.4;\n this.gazeBreakInterval = randomRange(...this.gazeBreakIntervalRange);\n }\n\n if (this.gazeBreakPhase > PHASE_OPEN) {\n this.gazeBreakProgress += delta;\n\n if (this.gazeBreakPhase === 1) {\n // Breaking away (eased like the return phase)\n const t = Math.min(1, this.gazeBreakProgress / GAZE_BREAK_DURATION);\n const eased = smoothStep(t);\n this.gazeBreakCurrentX = this.gazeBreakTargetX * eased;\n this.gazeBreakCurrentY = this.gazeBreakTargetY * eased;\n if (this.gazeBreakProgress >= GAZE_BREAK_DURATION) {\n this.gazeBreakPhase = 2;\n this.gazeBreakProgress = 0;\n }\n } else if (this.gazeBreakPhase === 2) {\n // Holding glance\n this.gazeBreakCurrentX = this.gazeBreakTargetX;\n this.gazeBreakCurrentY = this.gazeBreakTargetY;\n if (this.gazeBreakProgress >= GAZE_BREAK_HOLD_DURATION) {\n this.gazeBreakPhase = 3;\n this.gazeBreakProgress = 0;\n }\n } else if (this.gazeBreakPhase === 3) {\n // Returning to focus\n const t = Math.min(1, this.gazeBreakProgress / GAZE_BREAK_RETURN_DURATION);\n const eased = smoothStep(t);\n this.gazeBreakCurrentX = this.gazeBreakTargetX * (1 - eased);\n this.gazeBreakCurrentY = this.gazeBreakTargetY * (1 - eased);\n if (this.gazeBreakProgress >= GAZE_BREAK_RETURN_DURATION) {\n this.gazeBreakPhase = PHASE_OPEN;\n this.gazeBreakProgress = 0;\n this.gazeBreakCurrentX = 0;\n this.gazeBreakCurrentY = 0;\n }\n }\n } else {\n this.gazeBreakCurrentX = 0;\n this.gazeBreakCurrentY = 0;\n }\n }\n\n // =====================================================================\n // PRIVATE: Brow noise (simplex-driven organic drift)\n // =====================================================================\n\n private updateBrowNoise(\n delta: number,\n audioEnergy: number,\n isSpeaking: boolean,\n blendshapes: Record<string, number>,\n ): void {\n this.noiseTime += delta;\n\n // Emphasis detection: spike on energy transients\n const energyDelta = audioEnergy - this.previousEnergy;\n if (energyDelta > EMPHASIS_ENERGY_THRESHOLD) {\n this.emphasisLevel = 1.0;\n }\n this.emphasisLevel = Math.max(0, this.emphasisLevel - delta * EMPHASIS_DECAY_RATE);\n this.previousEnergy = audioEnergy;\n\n // Speech multiplier\n const speechMul = (isSpeaking && audioEnergy > 0) ? this.browNoiseSpeechMultiplier : 1.0;\n const amp = this.browNoiseAmplitude * speechMul;\n\n // browInnerUp: slow drift + emphasis spike\n const innerUpNoise = simplex2d(this.noiseTime * BROW_INNER_UP_FREQ, BROW_INNER_UP_PHASE);\n const innerUpBase = (innerUpNoise * 0.5 + 0.5) * amp * 0.83; // 0.10/0.12\n const innerUpEmphasis = this.emphasisLevel * 0.25;\n blendshapes['browInnerUp'] = clamp(innerUpBase + innerUpEmphasis, 0, 1);\n\n // browOuterUpLeft\n const outerLeftNoise = simplex2d(this.noiseTime * BROW_OUTER_LEFT_FREQ, BROW_OUTER_LEFT_PHASE);\n blendshapes['browOuterUpLeft'] = clamp((outerLeftNoise * 0.5 + 0.5) * amp * 0.5, 0, 1);\n\n // browOuterUpRight\n const outerRightNoise = simplex2d(this.noiseTime * BROW_OUTER_RIGHT_FREQ, BROW_OUTER_RIGHT_PHASE);\n blendshapes['browOuterUpRight'] = clamp((outerRightNoise * 0.5 + 0.5) * amp * 0.5, 0, 1);\n\n // browDownLeft (subtle furrow)\n const downLeftNoise = simplex2d(this.noiseTime * BROW_DOWN_FREQ, BROW_DOWN_LEFT_PHASE);\n blendshapes['browDownLeft'] = clamp((downLeftNoise * 0.5 + 0.5) * amp * 0.33, 0, 1);\n\n // browDownRight (subtle furrow)\n const downRightNoise = simplex2d(this.noiseTime * BROW_DOWN_FREQ, BROW_DOWN_RIGHT_PHASE);\n blendshapes['browDownRight'] = clamp((downRightNoise * 0.5 + 0.5) * amp * 0.33, 0, 1);\n }\n}\n","/**\n * Versioned NDJSON streaming protocol for client-server communication.\n *\n * Each line in the NDJSON stream is a JSON object conforming to ProtocolEvent.\n * The `v` field enables backward-compatible protocol evolution.\n *\n * @category Protocol\n */\n\n/** Current protocol version. Increment on breaking changes. */\nexport const PROTOCOL_VERSION = 1;\n\n/** Base shape for all NDJSON protocol events */\ninterface ProtocolEventBase {\n /** Protocol version */\n v: number;\n /** Event type discriminator */\n type: string;\n /** ISO 8601 timestamp */\n ts: string;\n}\n\n// --- Response events (server -> client) ---\n\nexport interface ResponseStartEvent extends ProtocolEventBase {\n type: 'response_start';\n /** Initial emotion for the response */\n emotion?: string;\n}\n\nexport interface ResponseChunkEvent extends ProtocolEventBase {\n type: 'response_chunk';\n /** Text chunk (streamed token-by-token) */\n text: string;\n}\n\nexport interface AudioChunkEvent extends ProtocolEventBase {\n type: 'audio_chunk';\n /** Base64-encoded PCM16 audio at 16kHz */\n audio: string;\n /** Audio format identifier */\n format: 'pcm_16000';\n /** Whether this is the last audio chunk */\n isLast: boolean;\n}\n\nexport interface ResponseEndEvent extends ProtocolEventBase {\n type: 'response_end';\n /** Complete response text */\n fullText: string;\n /** Response generation time in ms */\n durationMs?: number;\n /** Token count for this response */\n tokenCount?: number;\n}\n\nexport interface ErrorEvent extends ProtocolEventBase {\n type: 'error';\n /** Error message */\n message: string;\n /** Error code for programmatic handling */\n code?: string;\n /** Whether the client should retry */\n recoverable?: boolean;\n}\n\n/**\n * Union of all protocol events.\n * Used for type-safe NDJSON parsing on the client.\n */\nexport type ProtocolEvent =\n | ResponseStartEvent\n | ResponseChunkEvent\n | AudioChunkEvent\n | ResponseEndEvent\n | ErrorEvent;\n\n/**\n * Type guard for protocol events.\n */\nexport function isProtocolEvent(obj: unknown): obj is ProtocolEvent {\n return (\n typeof obj === 'object' &&\n obj !== null &&\n 'v' in obj &&\n 'type' in obj &&\n 'ts' in obj\n );\n}\n"],"mappings":"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AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;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;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA6pBf,IAAM,yBAAN,MAA6B;AAAA,EAA7B;AACL,SAAQ,SAAwB;AAChC,SAAQ,kBAAkB,oBAAI,IAI3B;AACH,SAAQ,cAAc;AACtB,SAAQ,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA,EAKnB,MAAM,OAAsB;AAC1B,QAAI,KAAK,YAAa;AAEtB,UAAM,YAAY,YAAY,IAAI;AAClC,UAAM,YAAY,aAAa;AAC/B,UAAM,OAAO,WAAW,UAAU,6BAA6B;AAE/D,QAAI;AACF,MAAAH,QAAO,KAAK,sCAAsC;AAClD,WAAK,SAAS,KAAK,aAAa;AAEhC,YAAM,KAAK;AAAA,QACT,EAAE,MAAM,QAAQ,WAAWC,gBAAe,OAAO,MAAM,EAAE;AAAA,QACzD;AAAA,QACA;AAAA,MACF;AAEA,WAAK,cAAc;AACnB,YAAM,aAAa,YAAY,IAAI,IAAI;AACvC,MAAAD,QAAO,KAAK,8BAA8B,EAAE,YAAY,KAAK,MAAM,UAAU,EAAE,CAAC;AAEhF,YAAM,cAAc,EAAE,uBAAuB,WAAW,CAAC;AACzD,YAAM,IAAI;AAAA,IACZ,SAAS,OAAO;AACd,YAAM,aAAa,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC,CAAC;AAC5E,WAAK,QAAQ;AACb,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA,EAIA,MAAM,eAAe,QAKY;AAC/B,SAAK,YAAY;AAEjB,UAAM,YAAY,YAAY,IAAI;AAClC,UAAM,SAAS,MAAM,KAAK;AAAA,MAMxB;AAAA,QACE,MAAM;AAAA,QACN,UAAUE,YAAW,OAAO,QAAQ;AAAA,QACpC,WAAWA,YAAW,OAAO,SAAS;AAAA,QACtC,OAAO,MAAM;AAAA,QACb,UAAU,OAAO;AAAA,QACjB,UAAU,OAAO;AAAA,MACnB;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAEA,UAAM,aAAa,YAAY,IAAI,IAAI;AACvC,WAAO;AAAA,MACL,SAAS;AAAA,MACT;AAAA,MACA,YAAY,OAAO;AAAA,MACnB,aAAa,OAAO;AAAA,MACpB,WAAW,OAAO;AAAA,IACpB;AAAA,EACF;AAAA,EAEA,MAAM,WAAW,OAAgD;AAC/D,SAAK,YAAY;AAEjB,UAAM,SAAS,MAAM,KAAK;AAAA,MAQxB,EAAE,MAAM,iBAAiB,MAAM;AAAA,MAC/B;AAAA,MACA;AAAA,IACF;AAEA,WAAO;AAAA,MACL,MAAM,OAAO;AAAA,MACb,UAAU,OAAO;AAAA,MACjB,SAAS,OAAO;AAAA,MAChB,OAAO,OAAO;AAAA,MACd,iBAAiB,OAAO;AAAA,MACxB,kBAAkB,OAAO;AAAA,IAC3B;AAAA,EACF;AAAA,EAEA,MAAM,oBAAmC;AACvC,QAAI,CAAC,KAAK,OAAQ;AAClB,UAAM,KAAK,YAAY,EAAE,MAAM,aAAa,GAAG,eAAe,kBAAkB;AAAA,EAClF;AAAA;AAAA,EAIA,MAAM,YAAY,QAGY;AAC5B,SAAK,YAAY;AAEjB,UAAM,YAAY,YAAY,IAAI;AAClC,UAAM,SAAS,MAAM,KAAK;AAAA,MAKxB;AAAA,QACE,MAAM;AAAA,QACN,UAAUA,YAAW,OAAO,QAAQ;AAAA,QACpC,iBAAiB,OAAO,kBAAkBA,YAAW,OAAO,eAAe,IAAI;AAAA,QAC/E,OAAO,MAAM;AAAA,MACf;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAEA,UAAM,aAAa,YAAY,IAAI,IAAI;AACvC,WAAO;AAAA,MACL,SAAS;AAAA,MACT;AAAA,MACA,YAAY,OAAO;AAAA,MACnB,aAAa,OAAO;AAAA,IACtB;AAAA,EACF;AAAA,EAEA,MAAM,aAAa,OAKhB;AACD,SAAK,YAAY;AAEjB,WAAO,KAAK;AAAA,MACV,EAAE,MAAM,aAAa,MAAM;AAAA,MAC3B;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,iBAAgC;AACpC,QAAI,CAAC,KAAK,OAAQ;AAClB,UAAM,KAAK,YAAY,EAAE,MAAM,cAAc,GAAG,gBAAgB,kBAAkB;AAAA,EACpF;AAAA;AAAA,EAIA,MAAM,QAAQ,QAGkB;AAC9B,SAAK,YAAY;AAEjB,UAAM,YAAY,YAAY,IAAI;AAClC,UAAM,YAAY,OAAO,eAAe,OAAQ,MAAM;AACtD,UAAM,SAAS,MAAM,KAAK;AAAA,MAKxB;AAAA,QACE,MAAM;AAAA,QACN,UAAUA,YAAW,OAAO,QAAQ;AAAA,QACpC,YAAY,OAAO;AAAA,MACrB;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAEA,UAAM,aAAa,YAAY,IAAI,IAAI;AACvC,WAAO;AAAA,MACL,SAAS;AAAA,MACT;AAAA,MACA,YAAY,OAAO;AAAA,MACnB,aAAa,OAAO;AAAA,MACpB,YAAY,OAAO;AAAA,MACnB;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,WACJ,OACA,OACA,SACgF;AAChF,SAAK,YAAY;AAEjB,WAAO,KAAK;AAAA,MACV,EAAE,MAAM,eAAe,OAAO,OAAO,QAAQ;AAAA,MAC7C;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,WAAkC;AACtC,SAAK,YAAY;AAEjB,UAAM,SAAS,MAAM,KAAK;AAAA,MACxB,EAAE,MAAM,YAAY;AAAA,MACpB;AAAA,MACA;AAAA,IACF;AACA,WAAO,OAAO;AAAA,EAChB;AAAA,EAEA,MAAM,aAA4B;AAChC,QAAI,CAAC,KAAK,OAAQ;AAClB,UAAM,KAAK,YAAY,EAAE,MAAM,cAAc,GAAG,gBAAgB,kBAAkB;AAAA,EACpF;AAAA;AAAA,EAIA,MAAM,UAAyB;AAC7B,QAAI,KAAK,QAAQ;AACf,UAAI;AACF,cAAM,KAAK,YAAY,EAAE,MAAM,cAAc,GAAG,oBAAoB,kBAAkB;AAAA,MACxF,QAAQ;AAAA,MAER;AACA,WAAK,OAAO,UAAU;AACtB,WAAK,SAAS;AAAA,IAChB;AACA,SAAK,cAAc;AACnB,SAAK,WAAW;AAChB,SAAK,iBAAiB,iBAAiB;AACvC,SAAK,gBAAgB,MAAM;AAAA,EAC7B;AAAA;AAAA,EAGA,IAAI,UAAmB;AACrB,WAAO,KAAK,eAAe,CAAC,KAAK,YAAY,KAAK,WAAW;AAAA,EAC/D;AAAA;AAAA,EAGA,OAAO,cAAuB;AAC5B,WAAO,OAAO,WAAW;AAAA,EAC3B;AAAA;AAAA,EAIQ,cAAoB;AAC1B,QAAI,CAAC,KAAK,eAAe,CAAC,KAAK,QAAQ;AACrC,YAAM,IAAI,MAAM,4DAA4D;AAAA,IAC9E;AACA,QAAI,KAAK,UAAU;AACjB,YAAM,IAAI,MAAM,uEAAkE;AAAA,IACpF;AAAA,EACF;AAAA,EAEQ,eAAuB;AAC7B,UAAM,OAAO,IAAI,KAAK,CAACC,cAAa,GAAG,EAAE,MAAM,yBAAyB,CAAC;AACzE,UAAM,UAAU,IAAI,gBAAgB,IAAI;AACxC,UAAM,SAAS,IAAI,OAAO,OAAO;AACjC,QAAI,gBAAgB,OAAO;AAE3B,WAAO,YAAY,CAAC,UAAwB;AAC1C,WAAK,oBAAoB,MAAM,IAAI;AAAA,IACrC;AAEA,WAAO,UAAU,CAAC,UAAU;AAC1B,MAAAH,QAAO,MAAM,wBAAwB,EAAE,OAAO,MAAM,QAAQ,CAAC;AAC7D,WAAK,iBAAiB,iBAAiB,MAAM,OAAO,EAAE;AAAA,IACxD;AAEA,WAAO;AAAA,EACT;AAAA,EAEQ,oBAAoB,MAAqC;AAC/D,UAAM,YAAY,KAAK;AAEvB,QAAI,KAAK,SAAS,SAAS;AACzB,UAAI,aAAa,KAAK,gBAAgB,IAAI,SAAS,GAAG;AACpD,cAAM,UAAU,KAAK,gBAAgB,IAAI,SAAS;AAClD,qBAAa,QAAQ,OAAO;AAC5B,aAAK,gBAAgB,OAAO,SAAS;AACrC,gBAAQ,OAAO,IAAI,MAAM,KAAK,KAAe,CAAC;AAAA,MAChD,OAAO;AAEL,QAAAA,QAAO,MAAM,0BAA0B,EAAE,OAAO,KAAK,MAAM,CAAC;AAC5D,aAAK,iBAAiB,KAAK,KAAe;AAAA,MAC5C;AACA;AAAA,IACF;AAEA,QAAI,aAAa,KAAK,gBAAgB,IAAI,SAAS,GAAG;AACpD,YAAM,UAAU,KAAK,gBAAgB,IAAI,SAAS;AAClD,mBAAa,QAAQ,OAAO;AAC5B,WAAK,gBAAgB,OAAO,SAAS;AACrC,cAAQ,QAAQ,IAAI;AAAA,IACtB;AAAA,EACF;AAAA,EAEQ,YACN,SACA,cACA,WACY;AACZ,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,UAAI,CAAC,KAAK,QAAQ;AAChB,eAAO,IAAI,MAAM,wBAAwB,CAAC;AAC1C;AAAA,MACF;AAEA,YAAM,YAAY,cAAc;AAChC,YAAM,UAAU,WAAW,MAAM;AAC/B,aAAK,gBAAgB,OAAO,SAAS;AACrC,aAAK,WAAW;AAChB,QAAAA,QAAO,MAAM,8DAAyD;AAAA,UACpE,MAAM,QAAQ;AAAA,UACd;AAAA,QACF,CAAC;AACD,eAAO,IAAI,MAAM,qBAAqB,QAAQ,IAAI,qBAAqB,SAAS,IAAI,CAAC;AAAA,MACvF,GAAG,SAAS;AAEZ,WAAK,gBAAgB,IAAI,WAAW;AAAA,QAClC;AAAA,QACA;AAAA,QACA;AAAA,MACF,CAAC;AAED,WAAK,OAAO,YAAY,EAAE,GAAG,SAAS,UAAU,CAAC;AAAA,IACnD,CAAC;AAAA,EACH;AAAA,EAEQ,iBAAiB,QAAsB;AAC7C,eAAW,CAAC,EAAE,OAAO,KAAK,KAAK,iBAAiB;AAC9C,mBAAa,QAAQ,OAAO;AAC5B,cAAQ,OAAO,IAAI,MAAM,MAAM,CAAC;AAAA,IAClC;AACA,SAAK,gBAAgB,MAAM;AAAA,EAC7B;AAAA,EAEQ,UAAgB;AACtB,QAAI,KAAK,QAAQ;AACf,WAAK,OAAO,UAAU;AACtB,WAAK,SAAS;AAAA,IAChB;AACA,SAAK,cAAc;AACnB,SAAK,iBAAiB,gBAAgB;AACtC,SAAK,gBAAgB,MAAM;AAAA,EAC7B;AACF;AAWO,IAAM,2BAAN,MAA4D;AAAA,EAQjE,YAAY,QAAgC,QAAgC;AAL5E,SAAQ,YAAY;AAGpB,SAAQ,iBAAgC,QAAQ,QAAQ;AAGtD,SAAK,SAAS;AACd,UAAM,WAAW,OAAO,SAAS,UAAU,GAAG,OAAO,SAAS,YAAY,GAAG,CAAC;AAC9E,SAAK,SAAS;AAAA,MACZ,UAAU,OAAO;AAAA,MACjB,WAAW,OAAO,aAAa,GAAG,QAAQ;AAAA,MAC1C,UAAU,OAAO,YAAY;AAAA,MAC7B,UAAU,OAAO,YAAY;AAAA,IAC/B;AACA,SAAK,aAAa,kBAAkB,KAAK,OAAO,QAAQ;AACxD,SAAK,aAAa,kBAAkB,KAAK,OAAO,QAAQ;AAAA,EAC1D;AAAA,EAEA,IAAI,WAAoB;AAAE,WAAO,KAAK;AAAA,EAAW;AAAA,EACjD,IAAI,UAAyB;AAAE,WAAO,KAAK,YAAY,SAAS;AAAA,EAAM;AAAA,EAEtE,MAAM,KAAK,YAAoF;AAC7F,UAAM,YAAY,aAAa;AAC/B,UAAM,OAAO,WAAW,UAAU,iCAAiC;AAAA,MACjE,aAAa,KAAK,OAAO;AAAA,IAC3B,CAAC;AAED,QAAI;AACF,YAAM,SAAS,MAAM,KAAK,OAAO,eAAe;AAAA,QAC9C,UAAU,KAAK,OAAO;AAAA,QACtB,WAAW,KAAK,OAAO;AAAA,QACvB,UAAU,KAAK;AAAA,QACf,UAAU,KAAK;AAAA,MACjB,CAAC;AACD,WAAK,YAAY;AACjB,mBAAa,GAAG,CAAC;AAEjB,MAAAA,QAAO,KAAK,wCAAwC;AAAA,QAClD,SAAS;AAAA,QACT,YAAY,KAAK,MAAM,OAAO,UAAU;AAAA,QACxC,WAAW,OAAO;AAAA,MACpB,CAAC;AAED,YAAM,cAAc,EAAE,iBAAiB,QAAQ,sBAAsB,OAAO,WAAW,CAAC;AACxF,YAAM,IAAI;AACV,iBAAW,gBAAgB,yBAAyB,OAAO,YAAY;AAAA,QACrE,OAAO;AAAA,QACP,SAAS;AAAA,MACX,CAAC;AAED,aAAO;AAAA,IACT,SAAS,OAAO;AACd,YAAM,aAAa,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC,CAAC;AAC5E,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEA,MAAM,WAAW,cAAuD;AACtE,QAAI,CAAC,KAAK,UAAW,OAAM,IAAI,MAAM,sCAAsC;AAE3E,UAAM,QAAQ,IAAI,aAAa,YAAY;AAE3C,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,WAAK,iBAAiB,KAAK,eAAe,KAAK,YAAY;AACzD,YAAI;AACF,gBAAM,SAAS,MAAM,KAAK,OAAO,WAAW,KAAK;AACjD,kBAAQ,MAAM;AAAA,QAChB,SAAS,KAAK;AACZ,iBAAO,GAAG;AAAA,QACZ;AAAA,MACF,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,UAAyB;AAC7B,QAAI,KAAK,WAAW;AAClB,YAAM,KAAK,OAAO,kBAAkB;AACpC,WAAK,YAAY;AAAA,IACnB;AAAA,EACF;AACF;AAOO,IAAM,6BAAN,MAA2D;AAAA,EAQhE,YAAY,QAAgC,QAAkC;AAP9E,SAAS,UAAU;AAInB,SAAQ,YAAY;AACpB,SAAQ,iBAAgC,QAAQ,QAAQ;AAGtD,SAAK,SAAS;AACd,SAAK,SAAS;AAAA,EAChB;AAAA,EAEA,IAAI,WAAoB;AAAE,WAAO,KAAK;AAAA,EAAW;AAAA,EACjD,IAAI,UAAiC;AAAE,WAAO,KAAK,YAAY,SAAS;AAAA,EAAM;AAAA,EAE9E,MAAM,OAAkC;AACtC,UAAM,YAAY,aAAa;AAC/B,UAAM,OAAO,WAAW,UAAU,mCAAmC;AAAA,MACnE,aAAa,KAAK,OAAO;AAAA,IAC3B,CAAC;AAED,QAAI;AACF,YAAM,kBAAkB,KAAK,OAAO,oBAAoB,QACnD,KAAK,OAAO,mBAAmB,GAAG,KAAK,OAAO,QAAQ,UACvD;AAEJ,YAAM,SAAS,MAAM,KAAK,OAAO,YAAY;AAAA,QAC3C,UAAU,KAAK,OAAO;AAAA,QACtB,iBAAiB,mBAAmB;AAAA,MACtC,CAAC;AACD,WAAK,YAAY;AAEjB,MAAAA,QAAO,KAAK,0CAA0C;AAAA,QACpD,SAAS;AAAA,QACT,YAAY,KAAK,MAAM,OAAO,UAAU;AAAA,MAC1C,CAAC;AAED,YAAM,cAAc,EAAE,iBAAiB,QAAQ,sBAAsB,OAAO,WAAW,CAAC;AACxF,YAAM,IAAI;AACV,iBAAW,gBAAgB,yBAAyB,OAAO,YAAY;AAAA,QACrE,OAAO;AAAA,QACP,SAAS;AAAA,MACX,CAAC;AAED,aAAO;AAAA,IACT,SAAS,OAAO;AACd,YAAM,aAAa,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC,CAAC;AAC5E,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEA,MAAM,MAAM,cAA4B,gBAAiD;AACvF,QAAI,CAAC,KAAK,UAAW,OAAM,IAAI,MAAM,sCAAsC;AAE3E,UAAM,YAAY,IAAI,aAAa,YAAY;AAE/C,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,WAAK,iBAAiB,KAAK,eAAe,KAAK,YAAY;AACzD,cAAM,YAAY,aAAa;AAC/B,cAAM,OAAO,WAAW,UAAU,oCAAoC;AAAA,UACpE,2BAA2B,UAAU;AAAA,QACvC,CAAC;AAED,YAAI;AACF,gBAAM,YAAY,YAAY,IAAI;AAClC,gBAAM,SAAS,MAAM,KAAK,OAAO,aAAa,SAAS;AACvD,gBAAM,kBAAkB,YAAY,IAAI,IAAI;AAG5C,gBAAM,aAAa,OAAO;AAC1B,gBAAM,EAAE,WAAW,eAAe,IAAI;AACtC,gBAAM,cAA8B,CAAC;AACrC,mBAAS,IAAI,GAAG,IAAI,WAAW,KAAK;AAClC,wBAAY,KAAK,WAAW,MAAM,IAAI,iBAAiB,IAAI,KAAK,cAAc,CAAC;AAAA,UACjF;AAEA,gBAAM,cAAc;AAAA,YAClB,yBAAyB;AAAA,YACzB,oBAAoB;AAAA,UACtB,CAAC;AACD,gBAAM,IAAI;AAEV,kBAAQ,EAAE,aAAa,WAAW,gBAAgB,CAAC;AAAA,QACrD,SAAS,KAAK;AACZ,gBAAM,aAAa,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC,CAAC;AACtE,iBAAO,GAAG;AAAA,QACZ;AAAA,MACF,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,UAAyB;AAC7B,QAAI,KAAK,WAAW;AAClB,YAAM,KAAK,OAAO,eAAe;AACjC,WAAK,YAAY;AAAA,IACnB;AAAA,EACF;AACF;AAOO,IAAM,0BAAN,MAA0D;AAAA,EAkB/D,YAAY,QAAgC,QAAyB;AAfrE,SAAQ,YAAY;AASpB;AAAA,SAAQ,iBAAgC,QAAQ,QAAQ;AAGxD;AAAA,SAAQ,kBAAkC,CAAC;AAC3C,SAAQ,cAAc;AAGpB,SAAK,SAAS;AACd,UAAM,KAAK,OAAO,cAAc;AAChC,SAAK,SAAS;AAAA,MACZ,UAAU,OAAO;AAAA,MACjB,SAAS,OAAO,WAAW;AAAA,MAC3B,YAAY;AAAA,MACZ,WAAW,OAAO,aAAa;AAAA,MAC/B,uBAAuB,OAAO,yBAAyB;AAAA,IACzD;AACA,SAAK,YAAY,OAAO,OAAQ,MAAM;AACtC,SAAK,cAAc,OAAO,OAAQ,KAAK;AACvC,SAAK,QAAQ,IAAI,aAAa,IAAI,IAAI,GAAG;AACzC,SAAK,UAAU,IAAI,aAAa,KAAK,WAAW;AAAA,EAClD;AAAA,EAEA,IAAI,WAAoB;AAAE,WAAO,KAAK;AAAA,EAAW;AAAA,EACjD,IAAI,UAAiC;AAAE,WAAO,KAAK,YAAY,SAAS;AAAA,EAAM;AAAA,EAC9E,IAAI,aAAqB;AAAE,WAAO,KAAK,OAAO;AAAA,EAAY;AAAA,EAC1D,IAAI,YAAoB;AAAE,WAAO,KAAK,OAAO;AAAA,EAAW;AAAA,EAExD,eAAuB;AAAE,WAAO,KAAK;AAAA,EAAW;AAAA,EAChD,qBAA6B;AAAE,WAAQ,KAAK,YAAY,KAAK,OAAO,aAAc;AAAA,EAAM;AAAA,EAExF,MAAM,OAAoC;AACxC,UAAM,YAAY,aAAa;AAC/B,UAAM,OAAO,WAAW,UAAU,gCAAgC;AAAA,MAChE,aAAa,KAAK,OAAO;AAAA,IAC3B,CAAC;AAED,QAAI;AACF,YAAM,SAAS,MAAM,KAAK,OAAO,QAAQ;AAAA,QACvC,UAAU,KAAK,OAAO;AAAA,QACtB,YAAY,KAAK,OAAO;AAAA,MAC1B,CAAC;AACD,WAAK,YAAY;AAEjB,MAAAA,QAAO,KAAK,uCAAuC;AAAA,QACjD,SAAS;AAAA,QACT,YAAY,KAAK,MAAM,OAAO,UAAU;AAAA,QACxC,YAAY,KAAK,OAAO;AAAA,QACxB,WAAW,KAAK;AAAA,MAClB,CAAC;AAED,YAAM,cAAc,EAAE,iBAAiB,QAAQ,sBAAsB,OAAO,WAAW,CAAC;AACxF,YAAM,IAAI;AACV,iBAAW,gBAAgB,yBAAyB,OAAO,YAAY;AAAA,QACrE,OAAO;AAAA,QACP,SAAS;AAAA,MACX,CAAC;AAED,aAAO;AAAA,IACT,SAAS,OAAO;AACd,YAAM,aAAa,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC,CAAC;AAC5E,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEA,MAAM,QAAQ,YAA8C;AAC1D,QAAI,CAAC,KAAK,UAAW,OAAM,IAAI,MAAM,sCAAsC;AAE3E,QAAI,WAAW,WAAW,KAAK,WAAW;AACxC,YAAM,IAAI;AAAA,QACR,+BAA+B,KAAK,SAAS,iBAAiB,WAAW,MAAM;AAAA,MAEjF;AAAA,IACF;AAEA,UAAM,iBAAiB,IAAI,aAAa,UAAU;AAElD,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,WAAK,iBAAiB,KAAK,eAAe,KAAK,YAAY;AACzD,YAAI;AACF,gBAAM,YAAY,YAAY,IAAI;AAClC,gBAAM,SAAS,MAAM,KAAK,OAAO,WAAW,gBAAgB,KAAK,OAAO,KAAK,OAAO;AAGpF,eAAK,QAAQ,OAAO;AACpB,eAAK,UAAU,eAAe,MAAM,CAAC,KAAK,WAAW;AAErD,gBAAM,kBAAkB,YAAY,IAAI,IAAI;AAC5C,gBAAM,WAAW,OAAO,cAAc,KAAK,OAAO;AAGlD,cAAI;AAEJ,cAAI,YAAY,CAAC,KAAK,aAAa;AACjC,8BAAkB,CAAC,GAAG,KAAK,eAAe;AAC1C,iBAAK,kBAAkB,CAAC;AAAA,UAC1B,WAAW,CAAC,YAAY,CAAC,KAAK,aAAa;AACzC,iBAAK,gBAAgB,KAAK,IAAI,aAAa,cAAc,CAAC;AAC1D,gBAAI,KAAK,gBAAgB,SAAS,KAAK,OAAO,uBAAuB;AACnE,mBAAK,gBAAgB,MAAM;AAAA,YAC7B;AAAA,UACF,WAAW,CAAC,YAAY,KAAK,aAAa;AACxC,iBAAK,kBAAkB,CAAC;AAAA,UAC1B;AAEA,eAAK,cAAc;AAEnB,kBAAQ;AAAA,YACN,aAAa,OAAO;AAAA,YACpB;AAAA,YACA;AAAA,YACA;AAAA,UACF,CAAC;AAAA,QACH,SAAS,KAAK;AACZ,iBAAO,GAAG;AAAA,QACZ;AAAA,MACF,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,QAAuB;AAC3B,QAAI,CAAC,KAAK,UAAW,OAAM,IAAI,MAAM,sCAAsC;AAE3E,UAAM,WAAW,MAAM,KAAK,OAAO,SAAS;AAC5C,SAAK,QAAQ;AACb,SAAK,UAAU,IAAI,aAAa,KAAK,WAAW;AAChD,SAAK,kBAAkB,CAAC;AACxB,SAAK,cAAc;AAAA,EACrB;AAAA,EAEA,MAAM,UAAyB;AAC7B,QAAI,KAAK,WAAW;AAClB,YAAM,KAAK,OAAO,WAAW;AAC7B,WAAK,YAAY;AAAA,IACnB;AACA,SAAK,QAAQ,IAAI,aAAa,IAAI,IAAI,GAAG;AACzC,SAAK,UAAU,IAAI,aAAa,KAAK,WAAW;AAChD,SAAK,kBAAkB,CAAC;AACxB,SAAK,cAAc;AAAA,EACrB;AACF;;;ACv4CA,IAAMI,UAAS,aAAa,kBAAkB;AAiEvC,SAAS,iBAAiB,QAAmD;AAElF,MAAI,OAAO,eAAe;AACxB,IAAAA,QAAO,KAAK,2DAA2D;AACvE,WAAO,IAAI,yBAAyB,OAAO,eAAe;AAAA,MACxD,UAAU,OAAO;AAAA,MACjB,WAAW,OAAO;AAAA,MAClB,UAAU,OAAO;AAAA,MACjB,UAAU,OAAO;AAAA,IACnB,CAAC;AAAA,EACH;AAEA,QAAM,YAAY,OAAO,aAAa;AAEtC,MAAI,cAAc,MAAM;AACtB,QAAI,CAAC,iBAAiB,YAAY,GAAG;AACnC,YAAM,IAAI,MAAM,mDAAmD;AAAA,IACrE;AACA,IAAAA,QAAO,KAAK,6CAA6C;AACzD,WAAO,IAAI,iBAAiB;AAAA,MAC1B,UAAU,OAAO;AAAA,MACjB,WAAW,OAAO;AAAA,MAClB,UAAU,OAAO;AAAA,MACjB,UAAU,OAAO;AAAA,IACnB,CAAC;AAAA,EACH;AAEA,MAAI,cAAc,OAAO;AACvB,IAAAA,QAAO,KAAK,4CAA4C;AACxD,WAAO,IAAI,oBAAoB;AAAA,MAC7B,UAAU,OAAO;AAAA,MACjB,WAAW,OAAO;AAAA,MAClB,UAAU,OAAO;AAAA,MACjB,UAAU,OAAO;AAAA,IACnB,CAAC;AAAA,EACH;AAOA,MAAI,iBAAiB,YAAY,KAAK,CAAC,MAAM,GAAG;AAC9C,IAAAA,QAAO,KAAK,4DAA4D;AACxE,WAAO,IAAI,iBAAiB;AAAA,MAC1B,UAAU,OAAO;AAAA,MACjB,WAAW,OAAO;AAAA,MAClB,UAAU,OAAO;AAAA,MACjB,UAAU,OAAO;AAAA,IACnB,CAAC;AAAA,EACH;AAEA,EAAAA,QAAO,KAAK,6DAA6D;AAAA,IACvE,QAAQ,MAAM,IAAI,8BAA8B;AAAA,EAClD,CAAC;AACD,SAAO,IAAI,oBAAoB;AAAA,IAC7B,UAAU,OAAO;AAAA,IACjB,WAAW,OAAO;AAAA,IAClB,UAAU,OAAO;AAAA,IACjB,UAAU,OAAO;AAAA,EACnB,CAAC;AACH;;;AC1HA,IAAMC,UAAS,aAAa,cAAc;AAgBnC,IAAM,yBAAN,MAAM,uBAAgD;AAAA,EAkB3D,YAAY,QAA4B;AAjBxC,SAAS,UAAU;AAEnB,SAAQ,UAAmC;AAC3C,SAAQ,MAAwB;AAEhC,SAAQ,WAA2B;AACnC,SAAQ,YAAY;AAGpB;AAAA,SAAQ,iBAAgC,QAAQ,QAAQ;AAKxD;AAAA;AAAA;AAAA,SAAQ,WAAW;AAIjB,SAAK,SAAS;AAAA,EAChB;AAAA,EAEA,IAAI,UAAiC;AACnC,WAAO,KAAK,UAAU,KAAK,WAAW;AAAA,EACxC;AAAA,EAEA,IAAI,WAAoB;AACtB,WAAO,KAAK,YAAY;AAAA,EAC1B;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,OAAkC;AACtC,QAAI,KAAK,WAAW;AAClB,YAAM,IAAI,MAAM,0BAA0B;AAAA,IAC5C;AAEA,QAAI,KAAK,SAAS;AAChB,YAAM,IAAI,MAAM,6CAA6C;AAAA,IAC/D;AAEA,SAAK,YAAY;AACjB,UAAM,YAAY,YAAY,IAAI;AAClC,UAAM,YAAY,aAAa;AAC/B,UAAM,OAAO,WAAW,UAAU,qBAAqB;AAAA,MACrD,aAAa,KAAK,OAAO;AAAA,MACzB,2BAA2B,KAAK,OAAO,WAAW;AAAA,IACpD,CAAC;AAED,QAAI;AAEF,YAAM,aAAa,KAAK,OAAO,WAAW;AAC1C,MAAAA,QAAO,KAAK,2BAA2B,EAAE,WAAW,CAAC;AAErD,YAAM,EAAE,KAAK,QAAQ,IAAI,MAAM,4BAA4B,UAAU;AACrE,WAAK,MAAM;AACX,WAAK,WAAW;AAEhB,MAAAA,QAAO,KAAK,uBAAuB,EAAE,SAAS,KAAK,SAAS,CAAC;AAE7D,YAAM,WAAW,KAAK,OAAO;AAC7B,YAAM,UAAU,KAAK,OAAO,oBAAoB,QAC3C,KAAK,OAAO,mBAAmB,GAAG,QAAQ,UAC3C;AACJ,YAAM,iBAAiB,kBAAkB,KAAK,QAAQ;AAMtD,UAAI,MAAM,GAAG;AACX,QAAAA,QAAO,KAAK,6DAA6D;AAAA,UACvE;AAAA,UACA;AAAA,QACF,CAAC;AAED,YAAI,SAAS;AACX,gBAAM,eAAe,QAAQ,MAAM,GAAG,EAAE,IAAI;AAC5C,UAAC,eAA2C,eAAe,CAAC;AAAA,YAC1D,MAAM;AAAA,YACN,MAAM;AAAA;AAAA,UACR,CAAC;AAAA,QACH;AAEA,aAAK,UAAU,MAAM,KAAK,IAAK,iBAAiB,OAAO,UAAU,cAAc;AAAA,MACjF,OAAO;AAEL,cAAM,QAAQ,cAAc;AAC5B,cAAM,WAAW,MAAM,MAAM,IAAI,QAAQ;AAEzC,YAAI;AACJ,YAAI,UAAU;AACZ,UAAAA,QAAO,MAAM,4BAA4B,EAAE,SAAS,CAAC;AACrD,wBAAe,MAAM,MAAM,IAAI,QAAQ;AAEvC,cAAI,CAAC,aAAa;AAChB,YAAAA,QAAO,KAAK,oDAAoD,EAAE,SAAS,CAAC;AAC5E,kBAAM,MAAM,OAAO,QAAQ;AAC3B,0BAAc,MAAM,eAAe,QAAQ;AAAA,UAC7C;AAAA,QACF,OAAO;AACL,UAAAA,QAAO,MAAM,oCAAoC,EAAE,SAAS,CAAC;AAC7D,wBAAc,MAAM,eAAe,QAAQ;AAAA,QAC7C;AAEA,YAAI,CAAC,aAAa;AAChB,gBAAM,IAAI,MAAM,yBAAyB,QAAQ,EAAE;AAAA,QACrD;AAGA,YAAI,qBAAyC;AAC7C,YAAI,SAAS;AACX,cAAI;AACF,kBAAM,eAAe,MAAM,MAAM,IAAI,OAAO;AAC5C,gBAAI,cAAc;AAChB,cAAAA,QAAO,MAAM,oCAAoC,EAAE,QAAQ,CAAC;AAC5D,mCAAsB,MAAM,MAAM,IAAI,OAAO;AAC7C,kBAAI,CAAC,oBAAoB;AACvB,gBAAAA,QAAO,KAAK,gDAAgD,EAAE,QAAQ,CAAC;AACvE,sBAAM,MAAM,OAAO,OAAO;AAC1B,qCAAqB,MAAM,eAAe,OAAO;AAAA,cACnD;AAAA,YACF,OAAO;AACL,cAAAA,QAAO,KAAK,gCAAgC;AAAA,gBAC1C;AAAA,gBACA,MAAM;AAAA,cACR,CAAC;AACD,mCAAqB,MAAM,eAAe,OAAO;AAAA,YACnD;AACA,YAAAA,QAAO,KAAK,wBAAwB;AAAA,cAClC,MAAM,YAAY,mBAAmB,UAAU;AAAA,YACjD,CAAC;AAAA,UACH,SAAS,KAAK;AACZ,YAAAA,QAAO,MAAM,mDAAmD;AAAA,cAC9D;AAAA,cACA,OAAQ,IAAc;AAAA,YACxB,CAAC;AAAA,UACH;AAAA,QACF;AAEA,QAAAA,QAAO,MAAM,yBAAyB;AAAA,UACpC,WAAW,YAAY,YAAY,UAAU;AAAA,UAC7C,kBAAkB,qBAAqB,YAAY,mBAAmB,UAAU,IAAI;AAAA,UACpF,SAAS,KAAK;AAAA,QAChB,CAAC;AAGD,YAAI,oBAAoB;AACtB,gBAAM,eAAe,QAAS,MAAM,GAAG,EAAE,IAAI;AAC7C,UAAC,eAA2C,eAAe,CAAC;AAAA,YAC1D,MAAM;AAAA,YACN,MAAM,IAAI,WAAW,kBAAkB;AAAA,UACzC,CAAC;AAAA,QACH;AAEA,cAAM,YAAY,IAAI,WAAW,WAAW;AAC5C,aAAK,UAAU,MAAM,KAAK,IAAK,iBAAiB,OAAO,WAAW,cAAc;AAAA,MAClF;AAEA,YAAM,aAAa,YAAY,IAAI,IAAI;AAEvC,MAAAA,QAAO,KAAK,6BAA6B;AAAA,QACvC,SAAS,KAAK;AAAA,QACd,YAAY,KAAK,MAAM,UAAU;AAAA,QACjC,QAAQ,KAAK,QAAQ;AAAA,QACrB,SAAS,KAAK,QAAQ;AAAA,MACxB,CAAC;AAED,YAAM,cAAc;AAAA,QAClB,iBAAiB,KAAK;AAAA,QACtB,sBAAsB;AAAA,QACtB,gBAAgB,CAAC,MAAM;AAAA,MACzB,CAAC;AACD,YAAM,IAAI;AACV,iBAAW,gBAAgB,yBAAyB,YAAY;AAAA,QAC9D,OAAO;AAAA,QACP,SAAS,KAAK;AAAA,MAChB,CAAC;AAGD,MAAAA,QAAO,MAAM,0BAA0B;AACvC,YAAM,cAAc,YAAY,IAAI;AACpC,YAAM,cAAc,IAAI,aAAa,IAAK;AAC1C,YAAM,KAAK,MAAM,WAAW;AAC5B,YAAM,eAAe,YAAY,IAAI,IAAI;AACzC,MAAAA,QAAO,KAAK,6BAA6B;AAAA,QACvC,cAAc,KAAK,MAAM,YAAY;AAAA,QACrC,SAAS,KAAK;AAAA,MAChB,CAAC;AACD,iBAAW,gBAAgB,2BAA2B,cAAc;AAAA,QAClE,OAAO;AAAA,QACP,SAAS,KAAK;AAAA,MAChB,CAAC;AAED,aAAO;AAAA,QACL,SAAS,KAAK;AAAA,QACd;AAAA,QACA,YAAY,CAAC,GAAG,KAAK,QAAQ,UAAU;AAAA,QACvC,aAAa,CAAC,GAAG,KAAK,QAAQ,WAAW;AAAA,MAC3C;AAAA,IACF,SAAS,OAAO;AACd,YAAM,aAAa,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC,CAAC;AAC5E,iBAAW,iBAAiB,sBAAsB,GAAG;AAAA,QACnD,OAAO;AAAA,QACP,YAAY;AAAA,MACd,CAAC;AACD,YAAM;AAAA,IACR,UAAE;AACA,WAAK,YAAY;AAAA,IACnB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,MAAM,MACJ,cACA,gBACwB;AACxB,QAAI,CAAC,KAAK,SAAS;AACjB,YAAM,IAAI,MAAM,sCAAsC;AAAA,IACxD;AAEA,QAAI,KAAK,UAAU;AACjB,YAAM,IAAI,MAAM,+EAA0E;AAAA,IAC5F;AAGA,UAAM,YAAY,IAAI,aAAa,YAAY;AAE/C,UAAM,QAAQ;AAAA,MACZ,kBAAkB,IAAI,KAAK,IAAK,OAAO,WAAW,WAAW,CAAC,GAAG,UAAU,MAAM,CAAC;AAAA,IACpF;AAEA,WAAO,KAAK,eAAe,OAAO,UAAU,MAAM;AAAA,EACpD;AAAA;AAAA;AAAA;AAAA,EAKQ,eACN,OACA,cACwB;AACxB,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,WAAK,iBAAiB,KAAK,eAAe,KAAK,YAAY;AACzD,cAAM,YAAY,aAAa;AAC/B,cAAM,OAAO,WAAW,UAAU,sBAAsB;AAAA,UACtD,qBAAqB,KAAK;AAAA,UAC1B,2BAA2B;AAAA,QAC7B,CAAC;AACD,YAAI;AACF,gBAAM,YAAY,YAAY,IAAI;AAClC,cAAI;AACJ,gBAAM,UAAU,MAAM,QAAQ,KAAK;AAAA,YACjC,KAAK,QAAS,IAAI,KAAK,EAAE,KAAK,OAAK;AAAE,2BAAa,SAAS;AAAG,qBAAO;AAAA,YAAG,CAAC;AAAA,YACzE,IAAI,QAAe,CAAC,GAAG,QAAQ;AAC7B,0BAAY;AAAA,gBACV,MAAM,IAAI,IAAI,MAAM,0CAA0C,uBAAsB,oBAAoB,IAAI,CAAC;AAAA,gBAC7G,uBAAsB;AAAA,cACxB;AAAA,YACF,CAAC;AAAA,UACH,CAAC;AACD,gBAAM,kBAAkB,YAAY,IAAI,IAAI;AAE5C,gBAAM,mBAAmB,QAAQ,aAAa;AAE9C,cAAI,CAAC,kBAAkB;AACrB,kBAAM,IAAI,MAAM,uCAAuC;AAAA,UACzD;AAEA,gBAAM,iBAAiB,iBAAiB;AACxC,gBAAM,YAAY,iBAAiB,KAAK,CAAC;AACzC,gBAAM,iBAAiB,iBAAiB,KAAK,CAAC;AAI9C,gBAAM,cAA8B,CAAC;AACrC,mBAAS,IAAI,GAAG,IAAI,WAAW,KAAK;AAClC,kBAAM,WAAW,eAAe,MAAM,IAAI,iBAAiB,IAAI,KAAK,cAAc;AAClF,kBAAM,cAAc,sBAAsB,QAAQ;AAClD,wBAAY,KAAK,WAAW;AAAA,UAC9B;AAEA,UAAAA,QAAO,MAAM,uBAAuB;AAAA,YAClC,iBAAiB,KAAK,MAAM,kBAAkB,GAAG,IAAI;AAAA,YACrD;AAAA,YACA;AAAA,UACF,CAAC;AAED,gBAAM,cAAc;AAAA,YAClB,yBAAyB;AAAA,YACzB,oBAAoB;AAAA,UACtB,CAAC;AACD,gBAAM,IAAI;AACV,qBAAW,gBAAgB,2BAA2B,iBAAiB;AAAA,YACrE,OAAO;AAAA,YACP,SAAS,KAAK;AAAA,UAChB,CAAC;AACD,qBAAW,iBAAiB,yBAAyB,GAAG;AAAA,YACtD,OAAO;AAAA,YACP,SAAS,KAAK;AAAA,YACd,QAAQ;AAAA,UACV,CAAC;AAED,kBAAQ;AAAA,YACN;AAAA,YACA;AAAA,YACA;AAAA,UACF,CAAC;AAAA,QACH,SAAS,KAAK;AAEZ,gBAAM,SAAS,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC9D,cAAI,OAAO,SAAS,WAAW,GAAG;AAChC,iBAAK,WAAW;AAChB,YAAAA,QAAO,MAAM,4FAAuF;AAAA,cAClG,SAAS,KAAK;AAAA,cACd,WAAW,uBAAsB;AAAA,YACnC,CAAC;AAAA,UACH,WAAW,OAAO,QAAQ,UAAU;AAGlC,kBAAM,WAAW,IAAI;AAAA,cACnB,mEAAmE,IAAI,SAAS,EAAE,CAAC;AAAA,YAErF;AACA,YAAAA,QAAO,MAAM,iDAA4C;AAAA,cACvD,SAAS,KAAK,IAAI,SAAS,EAAE,CAAC;AAAA,cAC9B,SAAS,KAAK;AAAA,YAChB,CAAC;AACD,kBAAM,aAAa,QAAQ;AAC3B,uBAAW,iBAAiB,yBAAyB,GAAG;AAAA,cACtD,OAAO;AAAA,cACP,SAAS,KAAK;AAAA,cACd,QAAQ;AAAA,YACV,CAAC;AACD,mBAAO,QAAQ;AACf;AAAA,UACF,OAAO;AACL,YAAAA,QAAO,MAAM,oBAAoB,EAAE,OAAO,QAAQ,SAAS,KAAK,SAAS,CAAC;AAAA,UAC5E;AACA,gBAAM,aAAa,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC,CAAC;AACtE,qBAAW,iBAAiB,yBAAyB,GAAG;AAAA,YACtD,OAAO;AAAA,YACP,SAAS,KAAK;AAAA,YACd,QAAQ;AAAA,UACV,CAAC;AACD,iBAAO,GAAG;AAAA,QACZ;AAAA,MACF,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,UAAyB;AAC7B,QAAI,KAAK,SAAS;AAChB,YAAM,KAAK,QAAQ,QAAQ;AAC3B,WAAK,UAAU;AAAA,IACjB;AAAA,EACF;AACF;AAjXa,uBAgBa,uBAAuB;AAhB1C,IAAM,wBAAN;;;AC/BP,IAAMC,UAAS,aAAa,oBAAoB;AAGhD,IAAMC,iBAAgB;AAGtB,IAAMC,mBAAkB;AACxB,IAAMC,wBAAuB;AA2C7B,SAASC,YAAW,KAAqB;AACvC,MAAI,gBAAgB,KAAK,GAAG,KAAK,UAAU,KAAK,GAAG,EAAG,QAAO;AAC7D,MAAI;AACF,WAAO,IAAI,IAAI,KAAK,WAAW,UAAU,UAAU,mBAAmB,EAAE;AAAA,EAC1E,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,IAAMC,iBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAgPf,IAAM,qBAAN,MAAmD;AAAA,EAmBxD,YAAY,QAAkC;AAlB9C,SAAS,UAAU;AAEnB,SAAQ,SAAwB;AAEhC,SAAQ,YAAY;AACpB,SAAQ,YAAY;AAGpB;AAAA,SAAQ,iBAAgC,QAAQ,QAAQ;AAKxD;AAAA;AAAA;AAAA,SAAQ,WAAW;AAGnB;AAAA,SAAQ,mBAAuG,oBAAI,IAAI;AAGrH,SAAK,SAAS;AAAA,EAChB;AAAA,EAEA,IAAI,WAAoB;AACtB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,UAAyB;AAC3B,WAAO,KAAK,YAAY,SAAS;AAAA,EACnC;AAAA;AAAA;AAAA;AAAA,EAKQ,eAAuB;AAC7B,UAAM,OAAO,IAAI,KAAK,CAACA,cAAa,GAAG,EAAE,MAAM,yBAAyB,CAAC;AACzE,UAAM,UAAU,IAAI,gBAAgB,IAAI;AACxC,UAAM,SAAS,IAAI,OAAO,OAAO;AAGjC,QAAI,gBAAgB,OAAO;AAG3B,WAAO,YAAY,CAAC,UAAkD;AACpE,WAAK,oBAAoB,MAAM,IAAI;AAAA,IACrC;AAGA,WAAO,UAAU,CAAC,UAAU;AAC1B,MAAAL,QAAO,MAAM,gBAAgB,EAAE,OAAO,MAAM,QAAQ,CAAC;AAErD,iBAAW,CAAC,EAAE,QAAQ,KAAK,KAAK,kBAAkB;AAChD,iBAAS,OAAO,IAAI,MAAM,iBAAiB,MAAM,OAAO,EAAE,CAAC;AAAA,MAC7D;AACA,WAAK,iBAAiB,MAAM;AAAA,IAC9B;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKQ,oBAAoB,QAAwC;AAElE,UAAM,WAAW,KAAK,iBAAiB,IAAI,OAAO,IAAI;AACtD,QAAI,UAAU;AACZ,WAAK,iBAAiB,OAAO,OAAO,IAAI;AACxC,UAAI,OAAO,SAAS,SAAS;AAC3B,iBAAS,OAAO,IAAI,MAAM,OAAO,KAAK,CAAC;AAAA,MACzC,OAAO;AACL,iBAAS,QAAQ,MAAM;AAAA,MACzB;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,YAAe,SAAoC,cAAsB,WAA+B;AAC9G,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,UAAI,CAAC,KAAK,QAAQ;AAChB,eAAO,IAAI,MAAM,wBAAwB,CAAC;AAC1C;AAAA,MACF;AAGA,YAAM,YAAY,WAAW,MAAM;AACjC,aAAK,iBAAiB,OAAO,YAAY;AACzC,eAAO,IAAI,MAAM,oCAAoC,SAAS,IAAI,CAAC;AAAA,MACrE,GAAG,SAAS;AAGZ,WAAK,iBAAiB,IAAI,cAAc;AAAA,QACtC,SAAS,CAAC,UAAU;AAClB,uBAAa,SAAS;AACtB,kBAAQ,KAAU;AAAA,QACpB;AAAA,QACA,QAAQ,CAAC,UAAU;AACjB,uBAAa,SAAS;AACtB,iBAAO,KAAK;AAAA,QACd;AAAA,MACF,CAAC;AAGD,WAAK,iBAAiB,IAAI,SAAS;AAAA,QACjC,SAAS,MAAM;AAAA,QAAC;AAAA;AAAA,QAChB,QAAQ,CAAC,UAAU;AACjB,uBAAa,SAAS;AACtB,eAAK,iBAAiB,OAAO,YAAY;AACzC,iBAAO,KAAK;AAAA,QACd;AAAA,MACF,CAAC;AAGD,WAAK,OAAO,YAAY,OAAO;AAAA,IACjC,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,OAAkC;AACtC,QAAI,KAAK,WAAW;AAClB,YAAM,IAAI,MAAM,0BAA0B;AAAA,IAC5C;AAEA,QAAI,KAAK,WAAW;AAClB,YAAM,IAAI,MAAM,6CAA6C;AAAA,IAC/D;AAEA,SAAK,YAAY;AACjB,UAAM,YAAY,YAAY,IAAI;AAClC,UAAM,YAAY,aAAa;AAC/B,UAAM,OAAO,WAAW,UAAU,2BAA2B;AAAA,MAC3D,aAAa,KAAK,OAAO;AAAA,MACzB,2BAA2B;AAAA,IAC7B,CAAC;AAED,QAAI;AACF,MAAAA,QAAO,KAAK,kCAAkC;AAG9C,WAAK,SAAS,KAAK,aAAa;AAGhC,YAAM,kBAAkB,KAAK,OAAO,oBAAoB,QACnD,KAAK,OAAO,mBAAmB,GAAG,KAAK,OAAO,QAAQ,UACvD;AAEJ,MAAAA,QAAO,KAAK,8BAA8B;AAAA,QACxC,UAAU,KAAK,OAAO;AAAA,QACtB;AAAA,QACA,OAAO,MAAM;AAAA,MACf,CAAC;AAGD,YAAM,SAAS,MAAM,KAAK;AAAA,QAMxB;AAAA,UACE,MAAM;AAAA,UACN,UAAUI,YAAW,KAAK,OAAO,QAAQ;AAAA,UACzC,iBAAiB,kBAAkBA,YAAW,eAAe,IAAI;AAAA,UACjE,WAAWH;AAAA,UACX,OAAO,MAAM;AAAA,QACf;AAAA,QACA;AAAA,QACAC;AAAA,MACF;AAEA,WAAK,YAAY;AAEjB,YAAM,aAAa,YAAY,IAAI,IAAI;AAEvC,MAAAF,QAAO,KAAK,2CAA2C;AAAA,QACrD,SAAS;AAAA,QACT,YAAY,KAAK,MAAM,UAAU;AAAA,QACjC,kBAAkB,KAAK,MAAM,OAAO,UAAU;AAAA,QAC9C,QAAQ,OAAO;AAAA,QACf,SAAS,OAAO;AAAA,MAClB,CAAC;AAED,YAAM,cAAc;AAAA,QAClB,iBAAiB;AAAA,QACjB,sBAAsB;AAAA,QACtB,6BAA6B,OAAO;AAAA,MACtC,CAAC;AACD,YAAM,IAAI;AACV,iBAAW,gBAAgB,yBAAyB,YAAY;AAAA,QAC9D,OAAO;AAAA,QACP,SAAS;AAAA,MACX,CAAC;AAED,aAAO;AAAA,QACL,SAAS;AAAA,QACT;AAAA,QACA,YAAY,OAAO;AAAA,QACnB,aAAa,OAAO;AAAA,MACtB;AAAA,IACF,SAAS,OAAO;AACd,YAAM,aAAa,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC,CAAC;AAC5E,iBAAW,iBAAiB,sBAAsB,GAAG;AAAA,QACnD,OAAO;AAAA,QACP,YAAY;AAAA,MACd,CAAC;AAGD,UAAI,KAAK,QAAQ;AACf,aAAK,OAAO,UAAU;AACtB,aAAK,SAAS;AAAA,MAChB;AAEA,YAAM;AAAA,IACR,UAAE;AACA,WAAK,YAAY;AAAA,IACnB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,MAAM,MACJ,cACA,gBACwB;AACxB,QAAI,CAAC,KAAK,aAAa,CAAC,KAAK,QAAQ;AACnC,YAAM,IAAI,MAAM,sCAAsC;AAAA,IACxD;AAEA,QAAI,KAAK,UAAU;AACjB,YAAM,IAAI,MAAM,sFAAiF;AAAA,IACnG;AAGA,UAAM,YAAY,IAAI,aAAa,YAAY;AAE/C,WAAO,KAAK,eAAe,SAAS;AAAA,EACtC;AAAA;AAAA;AAAA;AAAA,EAKQ,eAAe,cAAoD;AACzE,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,WAAK,iBAAiB,KAAK,eAAe,KAAK,YAAY;AACzD,cAAM,YAAY,aAAa;AAC/B,cAAM,OAAO,WAAW,UAAU,4BAA4B;AAAA,UAC5D,qBAAqB;AAAA,UACrB,2BAA2B,aAAa;AAAA,QAC1C,CAAC;AAED,YAAI;AACF,gBAAM,YAAY,YAAY,IAAI;AAGlC,gBAAM,SAAS,MAAM,KAAK;AAAA,YAOxB;AAAA,cACE,MAAM;AAAA,cACN,OAAO;AAAA,YACT;AAAA,YACA;AAAA,YACAG;AAAA,UACF;AAEA,gBAAM,kBAAkB,YAAY,IAAI,IAAI;AAG5C,gBAAM,aAAa,OAAO;AAC1B,gBAAM,EAAE,WAAW,eAAe,IAAI;AACtC,gBAAM,cAA8B,CAAC;AACrC,mBAAS,IAAI,GAAG,IAAI,WAAW,KAAK;AAClC,wBAAY,KAAK,WAAW,MAAM,IAAI,iBAAiB,IAAI,KAAK,cAAc,CAAC;AAAA,UACjF;AAEA,UAAAH,QAAO,MAAM,8BAA8B;AAAA,YACzC,iBAAiB,KAAK,MAAM,kBAAkB,GAAG,IAAI;AAAA,YACrD,cAAc,KAAK,MAAM,OAAO,kBAAkB,GAAG,IAAI;AAAA,YACzD;AAAA,YACA,cAAc,aAAa;AAAA,UAC7B,CAAC;AAED,gBAAM,cAAc;AAAA,YAClB,yBAAyB;AAAA,YACzB,gCAAgC,OAAO;AAAA,YACvC,oBAAoB;AAAA,UACtB,CAAC;AACD,gBAAM,IAAI;AACV,qBAAW,gBAAgB,2BAA2B,iBAAiB;AAAA,YACrE,OAAO;AAAA,YACP,SAAS;AAAA,UACX,CAAC;AACD,qBAAW,iBAAiB,yBAAyB,GAAG;AAAA,YACtD,OAAO;AAAA,YACP,SAAS;AAAA,YACT,QAAQ;AAAA,UACV,CAAC;AAED,kBAAQ;AAAA,YACN;AAAA,YACA;AAAA,YACA;AAAA,UACF,CAAC;AAAA,QACH,SAAS,KAAK;AAEZ,gBAAM,SAAS,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC9D,cAAI,OAAO,SAAS,WAAW,GAAG;AAChC,iBAAK,WAAW;AAChB,YAAAA,QAAO,MAAM,kGAA6F;AAAA,cACxG,SAAS;AAAA,cACT,WAAWG;AAAA,YACb,CAAC;AAAA,UACH,OAAO;AACL,YAAAH,QAAO,MAAM,2BAA2B,EAAE,OAAO,QAAQ,SAAS,OAAO,CAAC;AAAA,UAC5E;AAEA,gBAAM,aAAa,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC,CAAC;AACtE,qBAAW,iBAAiB,yBAAyB,GAAG;AAAA,YACtD,OAAO;AAAA,YACP,SAAS;AAAA,YACT,QAAQ;AAAA,UACV,CAAC;AACD,iBAAO,GAAG;AAAA,QACZ;AAAA,MACF,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,UAAyB;AAC7B,QAAI,KAAK,QAAQ;AACf,UAAI;AAEF,cAAM,KAAK,YAAY,EAAE,MAAM,UAAU,GAAG,YAAYG,qBAAoB;AAAA,MAC9E,QAAQ;AAAA,MAER;AAGA,WAAK,OAAO,UAAU;AACtB,WAAK,SAAS;AAAA,IAChB;AAEA,SAAK,YAAY;AACjB,SAAK,WAAW;AAChB,SAAK,iBAAiB,MAAM;AAAA,EAC9B;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO,cAAuB;AAC5B,WAAO,OAAO,WAAW;AAAA,EAC3B;AACF;;;AC9oBA,IAAMG,WAAS,aAAa,eAAe;AA2DpC,SAAS,cAAc,QAA6C;AACzE,QAAM,OAAO,OAAO,QAAQ;AAC5B,QAAM,kBAAkB,OAAO,mBAAmB;AAGlD,MAAI;AAEJ,MAAI,SAAS,OAAO;AAClB,aAAS;AACT,IAAAA,SAAO,KAAK,4CAA4C;AAAA,EAC1D,WAAW,SAAS,OAAO;AACzB,aAAS;AACT,IAAAA,SAAO,KAAK,uCAAuC;AAAA,EACrD,OAAO;AAEL,aAAS,oBAAoB;AAC7B,IAAAA,SAAO,KAAK,gCAAgC;AAAA,MAC1C;AAAA,MACA,UAAU,SAAS;AAAA,IACrB,CAAC;AAAA,EACH;AAEA,MAAI,QAAQ;AAEV,QAAI,OAAO,eAAe;AACxB,MAAAA,SAAO,KAAK,0EAA0E;AACtF,aAAO,IAAI,2BAA2B,OAAO,eAAe;AAAA,QAC1D,UAAU,OAAO;AAAA,MACnB,CAAC;AAAA,IACH;AAGA,QAAI,OAAO,aAAa,mBAAmB,YAAY,KAAK,CAAC,MAAM,GAAG;AACpE,MAAAA,SAAO,KAAK,4DAA4D;AACxE,aAAO,IAAI,mBAAmB;AAAA,QAC5B,UAAU,OAAO;AAAA,MACnB,CAAC;AAAA,IACH;AACA,IAAAA,SAAO,KAAK,8CAA8C;AAC1D,WAAO,IAAI,sBAAsB;AAAA,MAC/B,UAAU,OAAO;AAAA,IACnB,CAAC;AAAA,EACH;AAGA,QAAM,cAAc,IAAI,kBAAkB;AAAA,IACxC,UAAU,OAAO;AAAA,IACjB,iBAAiB,OAAO;AAAA,IACxB,SAAS,OAAO,cAAc;AAAA,IAC9B,oBAAoB,OAAO;AAAA,EAC7B,CAAC;AAED,MAAI,iBAAiB;AACnB,IAAAA,SAAO,KAAK,8CAA8C;AAC1D,WAAO,IAAI,oBAAoB,aAAa,MAAM;AAAA,EACpD;AAEA,EAAAA,SAAO,KAAK,0CAA0C;AACtD,SAAO;AACT;AAQA,IAAM,sBAAN,MAAoD;AAAA,EAKlD,YAAY,aAAgC,QAA6B;AAFzE,SAAQ,gBAAgB;AAGtB,SAAK,iBAAiB;AACtB,SAAK,SAAS;AAAA,EAChB;AAAA,EAEA,IAAI,UAAwC;AAC1C,WAAO,KAAK,eAAe;AAAA,EAC7B;AAAA,EAEA,IAAI,UAAiC;AACnC,WAAO,KAAK,eAAe;AAAA,EAC7B;AAAA,EAEA,IAAI,WAAoB;AACtB,WAAO,KAAK,eAAe;AAAA,EAC7B;AAAA,EAEA,MAAM,OAAkC;AACtC,QAAI;AACF,aAAO,MAAM,KAAK,eAAe,KAAK;AAAA,IACxC,SAAS,OAAO;AACd,aAAO,KAAK,cAAc,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;AAAA,IAClF;AAAA,EACF;AAAA,EAEA,MAAc,cAAc,QAA2C;AACrE,IAAAA,SAAO,KAAK,oDAAoD,EAAE,OAAO,CAAC;AAG1E,QAAI;AACF,YAAM,KAAK,eAAe,QAAQ;AAAA,IACpC,QAAQ;AAAA,IAER;AAGA,QAAI,KAAK,OAAO,eAAe;AAC7B,WAAK,iBAAiB,IAAI,2BAA2B,KAAK,OAAO,eAAe;AAAA,QAC9E,UAAU,KAAK,OAAO;AAAA,MACxB,CAAC;AACD,MAAAA,SAAO,KAAK,mDAAmD;AAAA,IACjE,WAAW,KAAK,OAAO,aAAa,mBAAmB,YAAY,KAAK,CAAC,MAAM,GAAG;AAChF,WAAK,iBAAiB,IAAI,mBAAmB;AAAA,QAC3C,UAAU,KAAK,OAAO;AAAA,MACxB,CAAC;AACD,MAAAA,SAAO,KAAK,2CAA2C;AAAA,IACzD,OAAO;AACL,WAAK,iBAAiB,IAAI,sBAAsB;AAAA,QAC9C,UAAU,KAAK,OAAO;AAAA,MACxB,CAAC;AACD,MAAAA,SAAO,KAAK,8CAA8C;AAAA,IAC5D;AACA,SAAK,gBAAgB;AACrB,WAAO,MAAM,KAAK,eAAe,KAAK;AAAA,EACxC;AAAA,EAEA,MAAM,MAAM,cAA4B,eAAgD;AACtF,WAAO,KAAK,eAAe,MAAM,cAAc,aAAa;AAAA,EAC9D;AAAA,EAEA,MAAM,UAAyB;AAC7B,WAAO,KAAK,eAAe,QAAQ;AAAA,EACrC;AACF;;;AC7LA,IAAMC,WAAS,aAAa,WAAW;AAkFhC,IAAM,qBAAN,MAAyB;AAAA,EA2B9B,YAAY,QAAyB;AA1BrC,SAAQ,UAAmC;AAC3C,SAAQ,MAAwB;AAEhC,SAAQ,WAA2B;AACnC,SAAQ,YAAY;AAGpB;AAAA,SAAQ,QAAuB;AAU/B;AAAA,SAAQ,iBAAgC,QAAQ,QAAQ;AAGxD;AAAA,SAAQ,kBAAkC,CAAC;AAC3C,SAAQ,cAAc;AAGtB;AAAA,SAAQ,WAA0B;AAGhC,UAAM,aAAa,OAAO,cAAc;AAExC,QAAI,eAAe,OAAQ,eAAe,MAAO;AAC/C,YAAM,IAAI,MAAM,wDAAwD;AAAA,IAC1E;AAEA,SAAK,SAAS;AAAA,MACZ,UAAU,OAAO;AAAA,MACjB,SAAS,OAAO,WAAW;AAAA,MAC3B;AAAA,MACA,WAAW,OAAO,aAAa;AAAA,MAC/B,uBAAuB,OAAO,yBAAyB;AAAA,IACzD;AAGA,SAAK,YAAY,eAAe,OAAQ,MAAM;AAC9C,SAAK,cAAc,eAAe,OAAQ,KAAK;AAC/C,SAAK,UAAU,IAAI,aAAa,KAAK,WAAW;AAAA,EAClD;AAAA,EAEA,IAAI,UAAiC;AACnC,WAAO,KAAK,UAAU,KAAK,WAAW;AAAA,EACxC;AAAA,EAEA,IAAI,WAAoB;AACtB,WAAO,KAAK,YAAY;AAAA,EAC1B;AAAA,EAEA,IAAI,aAAqB;AACvB,WAAO,KAAK,OAAO;AAAA,EACrB;AAAA,EAEA,IAAI,YAAoB;AACtB,WAAO,KAAK,OAAO;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA,EAKA,eAAuB;AACrB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,qBAA6B;AAC3B,WAAQ,KAAK,YAAY,KAAK,OAAO,aAAc;AAAA,EACrD;AAAA;AAAA;AAAA;AAAA,EAWA,MAAM,OAA8B;AAClC,QAAI,KAAK,WAAW;AAClB,YAAM,IAAI,MAAM,0BAA0B;AAAA,IAC5C;AAEA,QAAI,KAAK,SAAS;AAChB,YAAM,IAAI,MAAM,6CAA6C;AAAA,IAC/D;AAEA,SAAK,YAAY;AACjB,UAAM,YAAY,YAAY,IAAI;AAClC,UAAM,YAAY,aAAa;AAC/B,UAAM,OAAO,WAAW,UAAU,kBAAkB;AAAA,MAClD,aAAa,KAAK,OAAO;AAAA,MACzB,2BAA2B,KAAK,OAAO;AAAA,MACvC,qBAAqB,KAAK,OAAO;AAAA,IACnC,CAAC;AAED,QAAI;AAIF,MAAAA,SAAO,KAAK,2BAA2B,EAAE,YAAY,KAAK,OAAO,QAAQ,CAAC;AAE1E,YAAM,EAAE,KAAK,QAAQ,IAAI,MAAM,4BAA4B,KAAK,OAAO,OAAO;AAC9E,WAAK,MAAM;AACX,WAAK,WAAW;AAEhB,MAAAA,SAAO,KAAK,uBAAuB,EAAE,SAAS,KAAK,SAAS,CAAC;AAG7D,YAAM,QAAQ,cAAc;AAC5B,YAAM,WAAW,KAAK,OAAO;AAC7B,YAAM,WAAW,MAAM,MAAM,IAAI,QAAQ;AAEzC,UAAI;AACJ,UAAI,UAAU;AACZ,QAAAA,SAAO,MAAM,4BAA4B,EAAE,SAAS,CAAC;AACrD,sBAAe,MAAM,MAAM,IAAI,QAAQ;AAAA,MACzC,OAAO;AACL,QAAAA,SAAO,MAAM,8BAA8B,EAAE,SAAS,CAAC;AACvD,sBAAc,MAAM,eAAe,QAAQ;AAAA,MAC7C;AAEA,MAAAA,SAAO,MAAM,yBAAyB;AAAA,QACpC,MAAM,YAAY,YAAY,UAAU;AAAA,QACxC,SAAS,KAAK;AAAA,MAChB,CAAC;AAID,YAAM,iBAAiB,kBAAkB,KAAK,QAAQ;AACtD,YAAM,YAAY,IAAI,WAAW,WAAW;AAC5C,WAAK,UAAU,MAAM,IAAI,iBAAiB,OAAO,WAAW,cAAc;AAG1E,WAAK,MAAM;AAEX,YAAM,aAAa,YAAY,IAAI,IAAI;AAEvC,MAAAA,SAAO,KAAK,6BAA6B;AAAA,QACvC,SAAS,KAAK;AAAA,QACd,YAAY,KAAK,MAAM,UAAU;AAAA,QACjC,YAAY,KAAK,OAAO;AAAA,QACxB,WAAW,KAAK;AAAA,QAChB,WAAW,KAAK,OAAO;AAAA,MACzB,CAAC;AAED,YAAM,cAAc;AAAA,QAClB,iBAAiB,KAAK;AAAA,QACtB,sBAAsB;AAAA,QACtB,gBAAgB;AAAA,MAClB,CAAC;AACD,YAAM,IAAI;AACV,iBAAW,gBAAgB,yBAAyB,YAAY;AAAA,QAC9D,OAAO;AAAA,QACP,SAAS,KAAK;AAAA,MAChB,CAAC;AAED,aAAO;AAAA,QACL,SAAS,KAAK;AAAA,QACd;AAAA,QACA,YAAY,CAAC,GAAG,KAAK,QAAQ,UAAU;AAAA,QACvC,aAAa,CAAC,GAAG,KAAK,QAAQ,WAAW;AAAA,QACzC,YAAY,KAAK,OAAO;AAAA,QACxB,WAAW,KAAK;AAAA,MAClB;AAAA,IACF,SAAS,OAAO;AACd,YAAM,aAAa,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC,CAAC;AAC5E,iBAAW,iBAAiB,sBAAsB,GAAG;AAAA,QACnD,OAAO;AAAA,QACP,YAAY;AAAA,MACd,CAAC;AACD,YAAM;AAAA,IACR,UAAE;AACA,WAAK,YAAY;AAAA,IACnB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,QAAc;AACZ,QAAI,CAAC,KAAK,KAAK;AACb,YAAM,IAAI,MAAM,6CAA6C;AAAA,IAC/D;AAEA,SAAK,QAAQ,IAAI,KAAK,IAAI,OAAO,WAAW,IAAI,aAAa,IAAI,IAAI,GAAG,GAAG,CAAC,GAAG,GAAG,GAAG,CAAC;AAEtF,SAAK,UAAU,IAAI,aAAa,KAAK,WAAW;AAEhD,SAAK,kBAAkB,CAAC;AACxB,SAAK,cAAc;AAGnB,QAAI,CAAC,KAAK,UAAU;AAClB,UAAI;AACF,aAAK,WAAW,IAAI,KAAK,IAAI;AAAA,UAC3B;AAAA,UACA,IAAI,cAAc,CAAC,OAAO,KAAK,OAAO,UAAU,CAAC,CAAC;AAAA,UAClD,CAAC;AAAA,QACH;AAAA,MACF,SAAS,GAAG;AAGV,QAAAA,SAAO,KAAK,4DAA4D;AAAA,UACtE,OAAO,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC;AAAA,QAClD,CAAC;AACD,aAAK,WAAW,IAAI,KAAK,IAAI;AAAA,UAC3B;AAAA,UACA,CAAC,OAAO,KAAK,OAAO,UAAU,CAAC;AAAA,UAC/B,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,QAAQ,YAA8C;AAC1D,QAAI,CAAC,KAAK,SAAS;AACjB,YAAM,IAAI,MAAM,sCAAsC;AAAA,IACxD;AAEA,QAAI,WAAW,WAAW,KAAK,WAAW;AACxC,YAAM,IAAI;AAAA,QACR,+BAA+B,KAAK,SAAS,iBAAiB,WAAW,MAAM;AAAA,MAEjF;AAAA,IACF;AAEA,WAAO,KAAK,eAAe,UAAU;AAAA,EACvC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,aACJ,OACA,UAOI,CAAC,GACqB;AAC1B,UAAM;AAAA,MACJ,sBAAsB;AAAA,MACtB,uBAAuB;AAAA,MACvB,cAAc;AAAA,IAChB,IAAI;AAEJ,SAAK,MAAM;AAEX,UAAM,WAA4B,CAAC;AACnC,UAAM,kBAAkB,KAAK,mBAAmB;AAChD,UAAM,kBAAkB,KAAK,KAAK,sBAAsB,eAAe;AACvE,UAAM,mBAAmB,KAAK,KAAK,uBAAuB,eAAe;AACzE,UAAM,YAAY,KAAK,KAAK,cAAc,eAAe;AAEzD,QAAI,WAAW;AACf,QAAI,cAAc;AAClB,QAAI,eAAe;AACnB,QAAI,eAAe;AACnB,QAAI,YAAY;AAGhB,aAAS,IAAI,GAAG,IAAI,KAAK,aAAa,MAAM,QAAQ,KAAK,KAAK,WAAW;AACvE,YAAM,QAAQ,MAAM,MAAM,GAAG,IAAI,KAAK,SAAS;AAC/C,YAAM,SAAS,MAAM,KAAK,QAAQ,KAAK;AACvC,YAAM,aAAa,IAAI,KAAK;AAC5B,YAAM,SAAS,aAAa;AAE5B,UAAI,OAAO,UAAU;AACnB,YAAI,CAAC,UAAU;AAEb,qBAAW;AACX,wBAAc,KAAK,IAAI,GAAG,SAAS,WAAW;AAC9C,yBAAe;AACf,yBAAe;AACf,sBAAY;AAAA,QACd;AACA,uBAAe;AACf;AACA,qBAAa,OAAO;AAAA,MACtB,WAAW,UAAU;AACnB;AACA,YAAI,gBAAgB,kBAAkB;AAEpC,cAAI,gBAAgB,iBAAiB;AACnC,qBAAS,KAAK;AAAA,cACZ,OAAO,cAAc;AAAA,cACrB,MAAM,SAAS,eAAe;AAAA,cAC9B,gBAAgB,YAAY;AAAA,YAC9B,CAAC;AAAA,UACH;AACA,qBAAW;AAAA,QACb;AAAA,MACF;AAAA,IACF;AAGA,QAAI,YAAY,gBAAgB,iBAAiB;AAC/C,YAAM,QAAS,MAAM,SAAS,KAAK,OAAO,aAAc;AACxD,eAAS,KAAK;AAAA,QACZ,OAAO,cAAc;AAAA,QACrB,KAAK,QAAQ;AAAA,QACb,gBAAgB,YAAY;AAAA,MAC9B,CAAC;AAAA,IACH;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKQ,eAAe,YAA8C;AAInE,UAAM,iBAAiB,IAAI,aAAa,UAAU;AAIlD,UAAM,uBAAuB;AAC7B,UAAM,MAAM,aAAa,cAAc;AACvC,QAAI,MAAM,sBAAsB;AAE9B,UAAI,CAAC,KAAK,aAAa;AACrB,aAAK,gBAAgB,KAAK,IAAI,aAAa,cAAc,CAAC;AAC1D,YAAI,KAAK,gBAAgB,SAAS,KAAK,OAAO,uBAAuB;AACnE,eAAK,gBAAgB,MAAM;AAAA,QAC7B;AAAA,MACF;AAEA,MAAAA,SAAO,MAAM,4CAA4C;AAAA,QACvD,KAAK,KAAK,MAAM,MAAM,GAAK,IAAI;AAAA,QAC/B,WAAW;AAAA,MACb,CAAC;AAED,aAAO,QAAQ,QAAQ;AAAA,QACrB,aAAa;AAAA,QACb,UAAU;AAAA,QACV,iBAAiB;AAAA,MACnB,CAAC;AAAA,IACH;AAEA,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,WAAK,iBAAiB,KAAK,eAAe,KAAK,YAAY;AACzD,cAAM,YAAY,aAAa;AAC/B,cAAM,OAAO,WAAW,UAAU,qBAAqB;AAAA,UACrD,qBAAqB,KAAK;AAAA,UAC1B,wBAAwB,KAAK;AAAA,QAC/B,CAAC;AACD,YAAI;AACF,gBAAM,YAAY,YAAY,IAAI;AAGlC,gBAAM,YAAY,KAAK,cAAc,KAAK;AAC1C,gBAAM,cAAc,IAAI,aAAa,SAAS;AAC9C,sBAAY,IAAI,KAAK,SAAS,CAAC;AAC/B,sBAAY,IAAI,gBAAgB,KAAK,WAAW;AAKhD,gBAAM,kBAAkB,IAAI,aAAa,WAAW;AACpD,gBAAM,cAAc,IAAI,KAAK,IAAK,OAAO,WAAW,iBAAiB,CAAC,GAAG,SAAS,CAAC;AAEnF,gBAAM,WAAW,KAAK;AAItB,gBAAM,YAAY,IAAI,aAAa,KAAK,MAAO,IAAoB;AACnE,gBAAM,cAAc,IAAI,KAAK,IAAK,OAAO,WAAW,WAAW,KAAK,MAAO,IAAgB;AAE3F,gBAAM,QAAQ;AAAA,YACZ,SAAS;AAAA,YACT,SAAS;AAAA,YACT,MAAM;AAAA,UACR;AAGA,gBAAM,UAAU,MAAM,KAAK,QAAS,IAAI,KAAK;AAG7C,gBAAM,eAAe,QAAQ,QAAQ;AACrC,gBAAM,iBAAiB,QAAQ,QAAQ,KAAK,QAAQ,OAAO;AAE3D,cAAI,CAAC,cAAc;AACjB,kBAAM,IAAI,MAAM,sCAAsC;AAAA,UACxD;AAEA,gBAAM,cAAe,aAAa,KAAsB,CAAC;AAGzD,cAAI,gBAAgB;AAClB,iBAAK,QAAQ,IAAI,KAAK,IAAK;AAAA,cACzB;AAAA,cACA,IAAI,aAAa,eAAe,IAAoB;AAAA,cACpD,CAAC,GAAG,GAAG,GAAG;AAAA,YACZ;AAAA,UACF;AAIA,eAAK,UAAU,eAAe,MAAM,CAAC,KAAK,WAAW;AAErD,gBAAM,kBAAkB,YAAY,IAAI,IAAI;AAC5C,gBAAM,WAAW,cAAc,KAAK,OAAO;AAG3C,cAAI;AAEJ,cAAI,YAAY,CAAC,KAAK,aAAa;AAEjC,8BAAkB,CAAC,GAAG,KAAK,eAAe;AAC1C,iBAAK,kBAAkB,CAAC;AACxB,YAAAA,SAAO,MAAM,yCAAyC;AAAA,cACpD,iBAAiB,gBAAgB;AAAA,cACjC,YAAY,KAAK,MAAM,gBAAgB,SAAS,KAAK,mBAAmB,CAAC;AAAA,YAC3E,CAAC;AAAA,UACH,WAAW,CAAC,YAAY,CAAC,KAAK,aAAa;AAGzC,iBAAK,gBAAgB,KAAK,IAAI,aAAa,cAAc,CAAC;AAC1D,gBAAI,KAAK,gBAAgB,SAAS,KAAK,OAAO,uBAAuB;AACnE,mBAAK,gBAAgB,MAAM;AAAA,YAC7B;AAAA,UACF,WAAW,CAAC,YAAY,KAAK,aAAa;AAExC,iBAAK,kBAAkB,CAAC;AAAA,UAC1B;AAEA,eAAK,cAAc;AAEnB,UAAAA,SAAO,MAAM,2BAA2B;AAAA,YACtC,aAAa,KAAK,MAAM,cAAc,GAAI,IAAI;AAAA,YAC9C;AAAA,YACA,iBAAiB,KAAK,MAAM,kBAAkB,GAAG,IAAI;AAAA,UACvD,CAAC;AAED,gBAAM,cAAc;AAAA,YAClB,yBAAyB;AAAA,YACzB,yBAAyB;AAAA,YACzB,uBAAuB;AAAA,UACzB,CAAC;AACD,gBAAM,IAAI;AACV,qBAAW,gBAAgB,2BAA2B,iBAAiB;AAAA,YACrE,OAAO;AAAA,YACP,SAAS,KAAK;AAAA,UAChB,CAAC;AACD,qBAAW,iBAAiB,yBAAyB,GAAG;AAAA,YACtD,OAAO;AAAA,YACP,SAAS,KAAK;AAAA,YACd,QAAQ;AAAA,UACV,CAAC;AAED,kBAAQ;AAAA,YACN;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,UACF,CAAC;AAAA,QACH,SAAS,KAAK;AAIZ,cAAI,OAAO,QAAQ,UAAU;AAC3B,kBAAM,WAAW,IAAI;AAAA,cACnB,gEAAgE,IAAI,SAAS,EAAE,CAAC;AAAA,YAElF;AACA,YAAAA,SAAO,MAAM,iDAA4C;AAAA,cACvD,SAAS,KAAK,IAAI,SAAS,EAAE,CAAC;AAAA,cAC9B,SAAS,KAAK;AAAA,YAChB,CAAC;AACD,kBAAM,aAAa,QAAQ;AAC3B,uBAAW,iBAAiB,yBAAyB,GAAG;AAAA,cACtD,OAAO;AAAA,cACP,SAAS,KAAK;AAAA,cACd,QAAQ;AAAA,YACV,CAAC;AACD,mBAAO,QAAQ;AAAA,UACjB,OAAO;AACL,kBAAM,aAAa,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC,CAAC;AACtE,uBAAW,iBAAiB,yBAAyB,GAAG;AAAA,cACtD,OAAO;AAAA,cACP,SAAS,KAAK;AAAA,cACd,QAAQ;AAAA,YACV,CAAC;AACD,mBAAO,GAAG;AAAA,UACZ;AAAA,QACF;AAAA,MACF,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,UAAyB;AAC7B,QAAI,KAAK,SAAS;AAChB,YAAM,KAAK,QAAQ,QAAQ;AAC3B,WAAK,UAAU;AAAA,IACjB;AACA,SAAK,QAAQ;AACb,SAAK,WAAW;AAAA,EAClB;AACF;AAAA;AAAA;AAAA;AAAA;AA/gBa,mBAkFJ,oBAAoB;;;AC3L7B,IAAMC,WAAS,aAAa,iBAAiB;AAG7C,IAAMC,iBAAgB;AAGtB,IAAMC,mBAAkB;AACxB,IAAMC,wBAAuB;AAoE7B,SAASC,YAAW,KAAqB;AACvC,MAAI,gBAAgB,KAAK,GAAG,KAAK,UAAU,KAAK,GAAG,EAAG,QAAO;AAC7D,MAAI;AACF,WAAO,IAAI,IAAI,KAAK,WAAW,UAAU,UAAU,mBAAmB,EAAE;AAAA,EAC1E,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,IAAMC,iBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAgNf,IAAM,kBAAN,MAAsB;AAAA,EA2B3B,YAAY,QAAyB;AA1BrC,SAAQ,SAAwB;AAEhC,SAAQ,YAAY;AACpB,SAAQ,YAAY;AAapB;AAAA,SAAQ,iBAAgC,QAAQ,QAAQ;AAGxD;AAAA,SAAQ,kBAAkC,CAAC;AAC3C,SAAQ,cAAc;AAGtB;AAAA,SAAQ,mBAAuG,oBAAI,IAAI;AACvH,SAAQ,YAAY;AAGlB,UAAM,aAAa,OAAO,cAAc;AAExC,QAAI,eAAe,OAAQ,eAAe,MAAO;AAC/C,YAAM,IAAI,MAAM,wDAAwD;AAAA,IAC1E;AAEA,SAAK,SAAS;AAAA,MACZ,UAAU,OAAO;AAAA,MACjB;AAAA,MACA,WAAW,OAAO,aAAa;AAAA,MAC/B,uBAAuB,OAAO,yBAAyB;AAAA,IACzD;AAGA,SAAK,YAAY,eAAe,OAAQ,MAAM;AAC9C,SAAK,cAAc,eAAe,OAAQ,KAAK;AAG/C,SAAK,QAAQ,IAAI,aAAa,IAAI,IAAI,GAAG;AACzC,SAAK,UAAU,IAAI,aAAa,KAAK,WAAW;AAAA,EAClD;AAAA,EAEA,IAAI,WAAoB;AACtB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,UAAyB;AAC3B,WAAO,KAAK,YAAY,SAAS;AAAA,EACnC;AAAA,EAEA,IAAI,aAAqB;AACvB,WAAO,KAAK,OAAO;AAAA,EACrB;AAAA,EAEA,IAAI,YAAoB;AACtB,WAAO,KAAK,OAAO;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA,EAKA,eAAuB;AACrB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,qBAA6B;AAC3B,WAAQ,KAAK,YAAY,KAAK,OAAO,aAAc;AAAA,EACrD;AAAA;AAAA;AAAA;AAAA,EAKQ,eAAuB;AAC7B,UAAM,OAAO,IAAI,KAAK,CAACA,cAAa,GAAG,EAAE,MAAM,yBAAyB,CAAC;AACzE,UAAM,UAAU,IAAI,gBAAgB,IAAI;AACxC,UAAM,SAAS,IAAI,OAAO,OAAO;AAGjC,QAAI,gBAAgB,OAAO;AAG3B,WAAO,YAAY,CAAC,UAAyC;AAC3D,WAAK,oBAAoB,MAAM,IAAI;AAAA,IACrC;AAGA,WAAO,UAAU,CAAC,UAAU;AAC1B,MAAAL,SAAO,MAAM,gBAAgB,EAAE,OAAO,MAAM,QAAQ,CAAC;AAErD,iBAAW,CAAC,EAAE,QAAQ,KAAK,KAAK,kBAAkB;AAChD,iBAAS,OAAO,IAAI,MAAM,iBAAiB,MAAM,OAAO,EAAE,CAAC;AAAA,MAC7D;AACA,WAAK,iBAAiB,MAAM;AAAA,IAC9B;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKQ,oBAAoB,QAA+B;AAEzD,UAAM,WAAW,KAAK,iBAAiB,IAAI,OAAO,IAAI;AACtD,QAAI,UAAU;AACZ,WAAK,iBAAiB,OAAO,OAAO,IAAI;AACxC,UAAI,OAAO,SAAS,SAAS;AAC3B,iBAAS,OAAO,IAAI,MAAM,OAAO,KAAK,CAAC;AAAA,MACzC,OAAO;AACL,iBAAS,QAAQ,MAAM;AAAA,MACzB;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,YAAe,SAA2B,cAAsB,WAA+B;AACrG,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,UAAI,CAAC,KAAK,QAAQ;AAChB,eAAO,IAAI,MAAM,wBAAwB,CAAC;AAC1C;AAAA,MACF;AAGA,YAAM,YAAY,WAAW,MAAM;AACjC,aAAK,iBAAiB,OAAO,YAAY;AACzC,eAAO,IAAI,MAAM,oCAAoC,SAAS,IAAI,CAAC;AAAA,MACrE,GAAG,SAAS;AAGZ,WAAK,iBAAiB,IAAI,cAAc;AAAA,QACtC,SAAS,CAAC,UAAU;AAClB,uBAAa,SAAS;AACtB,kBAAQ,KAAU;AAAA,QACpB;AAAA,QACA,QAAQ,CAAC,UAAU;AACjB,uBAAa,SAAS;AACtB,iBAAO,KAAK;AAAA,QACd;AAAA,MACF,CAAC;AAGD,WAAK,iBAAiB,IAAI,SAAS;AAAA,QACjC,SAAS,MAAM;AAAA,QAAC;AAAA;AAAA,QAChB,QAAQ,CAAC,UAAU;AACjB,uBAAa,SAAS;AACtB,eAAK,iBAAiB,OAAO,YAAY;AACzC,iBAAO,KAAK;AAAA,QACd;AAAA,MACF,CAAC;AAGD,WAAK,OAAO,YAAY,OAAO;AAAA,IACjC,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,OAAoC;AACxC,QAAI,KAAK,WAAW;AAClB,YAAM,IAAI,MAAM,0BAA0B;AAAA,IAC5C;AAEA,QAAI,KAAK,WAAW;AAClB,YAAM,IAAI,MAAM,6CAA6C;AAAA,IAC/D;AAEA,SAAK,YAAY;AACjB,UAAM,YAAY,YAAY,IAAI;AAClC,UAAM,YAAY,aAAa;AAC/B,UAAM,OAAO,WAAW,UAAU,wBAAwB;AAAA,MACxD,aAAa,KAAK,OAAO;AAAA,MACzB,qBAAqB,KAAK,OAAO;AAAA,IACnC,CAAC;AAED,QAAI;AACF,MAAAA,SAAO,KAAK,wBAAwB;AAGpC,WAAK,SAAS,KAAK,aAAa;AAEhC,MAAAA,SAAO,KAAK,8BAA8B;AAAA,QACxC,UAAU,KAAK,OAAO;AAAA,QACtB,YAAY,KAAK,OAAO;AAAA,MAC1B,CAAC;AAGD,YAAM,SAAS,MAAM,KAAK;AAAA,QAMxB;AAAA,UACE,MAAM;AAAA,UACN,UAAUI,YAAW,KAAK,OAAO,QAAQ;AAAA,UACzC,YAAY,KAAK,OAAO;AAAA,UACxB,WAAWH;AAAA,QACb;AAAA,QACA;AAAA,QACAC;AAAA,MACF;AAEA,WAAK,YAAY;AAEjB,YAAM,aAAa,YAAY,IAAI,IAAI;AAEvC,MAAAF,SAAO,KAAK,kCAAkC;AAAA,QAC5C,SAAS;AAAA,QACT,YAAY,KAAK,MAAM,UAAU;AAAA,QACjC,kBAAkB,KAAK,MAAM,OAAO,UAAU;AAAA,QAC9C,YAAY,KAAK,OAAO;AAAA,QACxB,WAAW,KAAK;AAAA,QAChB,WAAW,KAAK,OAAO;AAAA,MACzB,CAAC;AAED,YAAM,cAAc;AAAA,QAClB,iBAAiB;AAAA,QACjB,sBAAsB;AAAA,QACtB,6BAA6B,OAAO;AAAA,MACtC,CAAC;AACD,YAAM,IAAI;AACV,iBAAW,gBAAgB,yBAAyB,YAAY;AAAA,QAC9D,OAAO;AAAA,QACP,SAAS;AAAA,MACX,CAAC;AAED,aAAO;AAAA,QACL,SAAS;AAAA,QACT;AAAA,QACA,YAAY,OAAO;AAAA,QACnB,aAAa,OAAO;AAAA,QACpB,YAAY,KAAK,OAAO;AAAA,QACxB,WAAW,KAAK;AAAA,MAClB;AAAA,IACF,SAAS,OAAO;AACd,YAAM,aAAa,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC,CAAC;AAC5E,iBAAW,iBAAiB,sBAAsB,GAAG;AAAA,QACnD,OAAO;AAAA,QACP,YAAY;AAAA,MACd,CAAC;AAGD,UAAI,KAAK,QAAQ;AACf,aAAK,OAAO,UAAU;AACtB,aAAK,SAAS;AAAA,MAChB;AAEA,YAAM;AAAA,IACR,UAAE;AACA,WAAK,YAAY;AAAA,IACnB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,QAAuB;AAC3B,QAAI,CAAC,KAAK,aAAa,CAAC,KAAK,QAAQ;AACnC,YAAM,IAAI,MAAM,uCAAuC;AAAA,IACzD;AAGA,UAAM,SAAS,MAAM,KAAK;AAAA,MACxB,EAAE,MAAM,QAAQ;AAAA,MAChB;AAAA,MACAG;AAAA,IACF;AAGA,SAAK,QAAQ,OAAO;AACpB,SAAK,UAAU,IAAI,aAAa,KAAK,WAAW;AAChD,SAAK,kBAAkB,CAAC;AACxB,SAAK,cAAc;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,QAAQ,YAA8C;AAC1D,QAAI,CAAC,KAAK,aAAa,CAAC,KAAK,QAAQ;AACnC,YAAM,IAAI,MAAM,uCAAuC;AAAA,IACzD;AAEA,QAAI,WAAW,WAAW,KAAK,WAAW;AACxC,YAAM,IAAI;AAAA,QACR,+BAA+B,KAAK,SAAS,iBAAiB,WAAW,MAAM;AAAA,MAEjF;AAAA,IACF;AAEA,WAAO,KAAK,eAAe,UAAU;AAAA,EACvC;AAAA;AAAA;AAAA;AAAA,EAKQ,eAAe,YAA8C;AAEnE,UAAM,iBAAiB,IAAI,aAAa,UAAU;AAElD,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,WAAK,iBAAiB,KAAK,eAAe,KAAK,YAAY;AACzD,cAAM,YAAY,aAAa;AAC/B,cAAM,OAAO,WAAW,UAAU,2BAA2B;AAAA,UAC3D,qBAAqB;AAAA,UACrB,wBAAwB,KAAK;AAAA,QAC/B,CAAC;AAED,YAAI;AACF,gBAAM,YAAY,YAAY,IAAI;AAGlC,gBAAM,SAAS,MAAM,KAAK;AAAA,YAMxB;AAAA,cACE,MAAM;AAAA,cACN,OAAO;AAAA,cACP,OAAO,KAAK;AAAA,cACZ,SAAS,KAAK;AAAA,YAChB;AAAA,YACA;AAAA,YACAA;AAAA,UACF;AAGA,eAAK,QAAQ,OAAO;AAGpB,eAAK,UAAU,eAAe,MAAM,CAAC,KAAK,WAAW;AAErD,gBAAM,kBAAkB,YAAY,IAAI,IAAI;AAC5C,gBAAM,WAAW,OAAO,cAAc,KAAK,OAAO;AAGlD,cAAI;AAEJ,cAAI,YAAY,CAAC,KAAK,aAAa;AAEjC,8BAAkB,CAAC,GAAG,KAAK,eAAe;AAC1C,iBAAK,kBAAkB,CAAC;AACxB,YAAAH,SAAO,MAAM,yCAAyC;AAAA,cACpD,iBAAiB,gBAAgB;AAAA,cACjC,YAAY,KAAK,MAAM,gBAAgB,SAAS,KAAK,mBAAmB,CAAC;AAAA,YAC3E,CAAC;AAAA,UACH,WAAW,CAAC,YAAY,CAAC,KAAK,aAAa;AAEzC,iBAAK,gBAAgB,KAAK,IAAI,aAAa,cAAc,CAAC;AAC1D,gBAAI,KAAK,gBAAgB,SAAS,KAAK,OAAO,uBAAuB;AACnE,mBAAK,gBAAgB,MAAM;AAAA,YAC7B;AAAA,UACF,WAAW,CAAC,YAAY,KAAK,aAAa;AAExC,iBAAK,kBAAkB,CAAC;AAAA,UAC1B;AAEA,eAAK,cAAc;AAEnB,UAAAA,SAAO,MAAM,kCAAkC;AAAA,YAC7C,aAAa,KAAK,MAAM,OAAO,cAAc,GAAI,IAAI;AAAA,YACrD;AAAA,YACA,iBAAiB,KAAK,MAAM,kBAAkB,GAAG,IAAI;AAAA,YACrD,cAAc,KAAK,MAAM,OAAO,kBAAkB,GAAG,IAAI;AAAA,UAC3D,CAAC;AAED,gBAAM,cAAc;AAAA,YAClB,yBAAyB;AAAA,YACzB,gCAAgC,OAAO;AAAA,YACvC,yBAAyB,OAAO;AAAA,YAChC,uBAAuB;AAAA,UACzB,CAAC;AACD,gBAAM,IAAI;AACV,qBAAW,gBAAgB,2BAA2B,iBAAiB;AAAA,YACrE,OAAO;AAAA,YACP,SAAS;AAAA,UACX,CAAC;AACD,qBAAW,iBAAiB,yBAAyB,GAAG;AAAA,YACtD,OAAO;AAAA,YACP,SAAS;AAAA,YACT,QAAQ;AAAA,UACV,CAAC;AAED,kBAAQ;AAAA,YACN,aAAa,OAAO;AAAA,YACpB;AAAA,YACA;AAAA,YACA;AAAA,UACF,CAAC;AAAA,QACH,SAAS,KAAK;AACZ,gBAAM,aAAa,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC,CAAC;AACtE,qBAAW,iBAAiB,yBAAyB,GAAG;AAAA,YACtD,OAAO;AAAA,YACP,SAAS;AAAA,YACT,QAAQ;AAAA,UACV,CAAC;AACD,iBAAO,GAAG;AAAA,QACZ;AAAA,MACF,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,UAAyB;AAC7B,QAAI,KAAK,QAAQ;AACf,UAAI;AAEF,cAAM,KAAK,YAAY,EAAE,MAAM,UAAU,GAAG,YAAYG,qBAAoB;AAAA,MAC9E,QAAQ;AAAA,MAER;AAGA,WAAK,OAAO,UAAU;AACtB,WAAK,SAAS;AAAA,IAChB;AAEA,SAAK,YAAY;AACjB,SAAK,QAAQ,IAAI,aAAa,IAAI,IAAI,GAAG;AACzC,SAAK,UAAU,IAAI,aAAa,KAAK,WAAW;AAChD,SAAK,kBAAkB,CAAC;AACxB,SAAK,cAAc;AACnB,SAAK,iBAAiB,MAAM;AAAA,EAC9B;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO,cAAuB;AAC5B,WAAO,OAAO,WAAW;AAAA,EAC3B;AACF;;;ACztBA,IAAMG,WAAS,aAAa,iBAAiB;AAyGtC,SAAS,oBAA6B;AAE3C,MAAI,OAAO,WAAW,aAAa;AACjC,IAAAA,SAAO,MAAM,oDAAoD;AACjE,WAAO;AAAA,EACT;AAGA,MAAI,OAAO,QAAQ,eAAe,OAAO,IAAI,oBAAoB,aAAa;AAC5E,IAAAA,SAAO,MAAM,uDAAuD;AACpE,WAAO;AAAA,EACT;AAGA,MAAI,OAAO,SAAS,aAAa;AAC/B,IAAAA,SAAO,MAAM,oDAAoD;AACjE,WAAO;AAAA,EACT;AAEA,SAAO;AACT;AAgCO,SAAS,gBAAgB,QAAkD;AAEhF,MAAI,OAAO,eAAe;AACxB,IAAAA,SAAO,KAAK,0DAA0D;AACtE,WAAO,IAAI,wBAAwB,OAAO,eAAe,MAAM;AAAA,EACjE;AAEA,QAAM,kBAAkB,OAAO,mBAAmB;AAGlD,MAAI;AAEJ,MAAI,OAAO,cAAc,QAAW;AAElC,gBAAY,OAAO;AACnB,IAAAA,SAAO,MAAM,oCAAoC,EAAE,UAAU,CAAC;AAAA,EAChE,OAAO;AAEL,UAAM,kBAAkB,kBAAkB;AAC1C,UAAM,WAAW,SAAS;AAI1B,gBAAY,mBAAmB,CAAC;AAEhC,IAAAA,SAAO,MAAM,mCAAmC;AAAA,MAC9C;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH;AAGA,MAAI,WAAW;AACb,IAAAA,SAAO,KAAK,4CAA4C;AACxD,UAAM,SAAS,IAAI,gBAAgB;AAAA,MACjC,UAAU,OAAO;AAAA,MACjB,YAAY,OAAO;AAAA,MACnB,WAAW,OAAO;AAAA,MAClB,uBAAuB,OAAO;AAAA,IAChC,CAAC;AAED,QAAI,iBAAiB;AAEnB,aAAO,IAAI,sBAAsB,QAAQ,MAAM;AAAA,IACjD;AAEA,WAAO;AAAA,EACT;AAEA,EAAAA,SAAO,KAAK,2CAA2C;AACvD,SAAO,IAAI,mBAAmB,MAAM;AACtC;AAQA,IAAM,wBAAN,MAAwD;AAAA,EAKtD,YAAY,QAAyB,QAAgC;AAFrE,SAAQ,gBAAgB;AAGtB,SAAK,iBAAiB;AACtB,SAAK,SAAS;AAAA,EAChB;AAAA,EAEA,IAAI,UAAiC;AAEnC,QAAI,CAAC,KAAK,SAAU,QAAO;AAC3B,WAAO,KAAK,gBAAiB,KAAK,eAAsC,UAAU;AAAA,EACpF;AAAA,EAEA,IAAI,WAAoB;AACtB,WAAO,KAAK,eAAe;AAAA,EAC7B;AAAA,EAEA,IAAI,aAAqB;AACvB,WAAO,KAAK,eAAe;AAAA,EAC7B;AAAA,EAEA,IAAI,YAAoB;AACtB,WAAO,KAAK,eAAe;AAAA,EAC7B;AAAA,EAEA,MAAM,OAAmD;AACvD,QAAI;AACF,aAAO,MAAM,KAAK,eAAe,KAAK;AAAA,IACxC,SAAS,OAAO;AACd,MAAAA,SAAO,KAAK,mDAAmD;AAAA,QAC7D,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,MAC9D,CAAC;AAGD,UAAI;AACF,cAAM,KAAK,eAAe,QAAQ;AAAA,MACpC,QAAQ;AAAA,MAER;AAGA,WAAK,iBAAiB,IAAI,mBAAmB,KAAK,MAAM;AACxD,WAAK,gBAAgB;AAErB,MAAAA,SAAO,KAAK,2CAA2C;AACvD,aAAO,MAAM,KAAK,eAAe,KAAK;AAAA,IACxC;AAAA,EACF;AAAA,EAEA,MAAM,QAAQ,YAA8C;AAC1D,WAAO,KAAK,eAAe,QAAQ,UAAU;AAAA,EAC/C;AAAA,EAEA,QAA8B;AAC5B,WAAO,KAAK,eAAe,MAAM;AAAA,EACnC;AAAA,EAEA,MAAM,UAAyB;AAC7B,WAAO,KAAK,eAAe,QAAQ;AAAA,EACrC;AAAA,EAEA,eAAuB;AACrB,WAAO,KAAK,eAAe,aAAa;AAAA,EAC1C;AAAA,EAEA,qBAA6B;AAC3B,WAAO,KAAK,eAAe,mBAAmB;AAAA,EAChD;AACF;;;AC1SA,IAAMC,WAAS,aAAa,cAAc;AAsGnC,IAAM,0BAAN,MAAM,yBAAwB;AAAA,EAenC,YAAY,SAA6B,CAAC,GAAG;AAb7C,SAAQ,cAAiD;AACzD,SAAQ,cAAc;AACtB,SAAQ,YAAY;AACpB,SAAQ,kBAAkB;AAG1B;AAAA,SAAQ,kBAA0C,CAAC;AACnD,SAAQ,iBAAwC,CAAC;AAGjD;AAAA,SAAQ,eAAmE;AAC3E,SAAQ,eAAgD;AAGtD,SAAK,SAAS;AAAA,MACZ,UAAU,OAAO,YAAY;AAAA,MAC7B,YAAY,OAAO,cAAc;AAAA,MACjC,gBAAgB,OAAO,kBAAkB;AAAA,MACzC,iBAAiB,OAAO,mBAAmB;AAAA,IAC7C;AAEA,IAAAA,SAAO,MAAM,mCAAmC;AAAA,MAC9C,UAAU,KAAK,OAAO;AAAA,MACtB,YAAY,KAAK,OAAO;AAAA,IAC1B,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO,cAAuB;AAC5B,WAAO,6BAA6B;AAAA,EACtC;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,YAAqB;AACvB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,WAAmB;AACrB,WAAO,KAAK,OAAO;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA,EAKA,SAAS,UAAsC;AAC7C,SAAK,gBAAgB,KAAK,QAAQ;AAAA,EACpC;AAAA;AAAA;AAAA;AAAA,EAKA,QAAQ,UAAqC;AAC3C,SAAK,eAAe,KAAK,QAAQ;AAAA,EACnC;AAAA;AAAA;AAAA;AAAA,EAKA,UAAU,UAAsC;AAC9C,UAAM,QAAQ,KAAK,gBAAgB,QAAQ,QAAQ;AACnD,QAAI,UAAU,IAAI;AAChB,WAAK,gBAAgB,OAAO,OAAO,CAAC;AAAA,IACtC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,SAAS,UAAqC;AAC5C,UAAM,QAAQ,KAAK,eAAe,QAAQ,QAAQ;AAClD,QAAI,UAAU,IAAI;AAChB,WAAK,eAAe,OAAO,OAAO,CAAC;AAAA,IACrC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,QAAuB;AAC3B,QAAI,KAAK,aAAa;AACpB,MAAAA,SAAO,KAAK,mBAAmB;AAC/B;AAAA,IACF;AAEA,QAAI,CAAC,yBAAwB,YAAY,GAAG;AAC1C,YAAM,QAAQ,IAAI;AAAA,QAChB;AAAA,MAGF;AACA,WAAK,UAAU,KAAK;AACpB,YAAM;AAAA,IACR;AAEA,UAAM,YAAY,aAAa;AAC/B,UAAM,OAAO,WAAW,UAAU,sBAAsB;AAAA,MACtD,mBAAmB,KAAK,OAAO;AAAA,MAC/B,qBAAqB,KAAK,OAAO;AAAA,IACnC,CAAC;AAED,QAAI;AAEF,YAAM,yBAAyB,OAAO,qBAAqB,OAAO;AAClE,UAAI,CAAC,wBAAwB;AAC3B,cAAM,IAAI,MAAM,yCAAyC;AAAA,MAC3D;AAEA,WAAK,cAAc,IAAI,uBAAuB;AAC9C,WAAK,YAAY,aAAa,KAAK,OAAO;AAC1C,WAAK,YAAY,iBAAiB,KAAK,OAAO;AAC9C,WAAK,YAAY,OAAO,KAAK,OAAO;AACpC,WAAK,YAAY,kBAAkB,KAAK,OAAO;AAG/C,WAAK,mBAAmB;AAGxB,WAAK,YAAY,MAAM;AACvB,WAAK,cAAc;AACnB,WAAK,YAAY,YAAY,IAAI;AACjC,WAAK,kBAAkB;AAEvB,MAAAA,SAAO,KAAK,8BAA8B;AAAA,QACxC,UAAU,KAAK,OAAO;AAAA,MACxB,CAAC;AAED,YAAM,IAAI;AAAA,IACZ,SAAS,OAAO;AACd,YAAM,aAAa,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC,CAAC;AAC5E,WAAK,UAAU,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC,CAAC;AACxE,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,OAAyC;AAC7C,QAAI,CAAC,KAAK,eAAe,CAAC,KAAK,aAAa;AAC1C,MAAAA,SAAO,KAAK,yBAAyB;AACrC,aAAO;AAAA,QACL,MAAM,KAAK;AAAA,QACX,UAAU,KAAK,OAAO;AAAA,QACtB,iBAAiB;AAAA,QACjB,SAAS;AAAA,MACX;AAAA,IACF;AAEA,UAAM,YAAY,aAAa;AAC/B,UAAM,OAAO,WAAW,UAAU,mBAAmB;AAErD,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,WAAK,eAAe;AACpB,WAAK,eAAe;AAEpB,UAAI;AACF,aAAK,YAAa,KAAK;AAAA,MAEzB,SAAS,OAAO;AACd,cAAM,aAAa,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC,CAAC;AAC5E,aAAK,cAAc;AACnB,eAAO,KAAK;AAAA,MACd;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,QAAc;AACZ,QAAI,KAAK,eAAe,KAAK,aAAa;AACxC,WAAK,YAAY,MAAM;AACvB,WAAK,cAAc;AACnB,MAAAA,SAAO,KAAK,4BAA4B;AAAA,IAC1C;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,MAAM,WAAW,QAAwD;AACvE,UAAM,IAAI;AAAA,MACR;AAAA,IAGF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,UAAgB;AACd,QAAI,KAAK,aAAa;AACpB,UAAI,KAAK,aAAa;AACpB,aAAK,YAAY,MAAM;AAAA,MACzB;AACA,WAAK,cAAc;AAAA,IACrB;AACA,SAAK,cAAc;AACnB,SAAK,kBAAkB,CAAC;AACxB,SAAK,iBAAiB,CAAC;AACvB,IAAAA,SAAO,MAAM,kCAAkC;AAAA,EACjD;AAAA;AAAA;AAAA;AAAA,EAKQ,qBAA2B;AACjC,QAAI,CAAC,KAAK,YAAa;AAEvB,SAAK,YAAY,WAAW,CAAC,UAAkC;AAC7D,YAAM,YAAY,aAAa;AAC/B,YAAM,OAAO,WAAW,UAAU,uBAAuB;AAEzD,UAAI;AAEF,iBAAS,IAAI,MAAM,aAAa,IAAI,MAAM,QAAQ,QAAQ,KAAK;AAC7D,gBAAM,SAAS,MAAM,QAAQ,CAAC;AAC9B,gBAAM,cAAc,OAAO,CAAC;AAE5B,cAAI,aAAa;AACf,kBAAM,OAAO,YAAY;AACzB,kBAAM,UAAU,OAAO;AAGvB,gBAAI,SAAS;AACX,mBAAK,mBAAmB,OAAO;AAAA,YACjC;AAEA,kBAAM,eAAwC;AAAA,cAC5C,MAAM,UAAU,KAAK,gBAAgB,KAAK,IAAI;AAAA,cAC9C,UAAU,KAAK,OAAO;AAAA,cACtB,iBAAiB,YAAY,IAAI,IAAI,KAAK;AAAA,cAC1C;AAAA,cACA,YAAY,YAAY;AAAA,YAC1B;AAGA,iBAAK,WAAW,YAAY;AAE5B,YAAAA,SAAO,MAAM,iBAAiB;AAAA,cAC5B,MAAM,KAAK,UAAU,GAAG,EAAE;AAAA,cAC1B;AAAA,cACA,YAAY,YAAY;AAAA,YAC1B,CAAC;AAAA,UACH;AAAA,QACF;AAEA,cAAM,IAAI;AAAA,MACZ,SAAS,OAAO;AACd,cAAM,aAAa,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC,CAAC;AAC5E,QAAAA,SAAO,MAAM,kCAAkC,EAAE,MAAM,CAAC;AAAA,MAC1D;AAAA,IACF;AAEA,SAAK,YAAY,UAAU,CAAC,UAAuC;AACjE,YAAM,QAAQ,IAAI,MAAM,6BAA6B,MAAM,KAAK,MAAM,MAAM,OAAO,EAAE;AACrF,MAAAA,SAAO,MAAM,4BAA4B,EAAE,OAAO,MAAM,OAAO,SAAS,MAAM,QAAQ,CAAC;AACvF,WAAK,UAAU,KAAK;AAEpB,UAAI,KAAK,cAAc;AACrB,aAAK,aAAa,KAAK;AACvB,aAAK,eAAe;AACpB,aAAK,eAAe;AAAA,MACtB;AAAA,IACF;AAEA,SAAK,YAAY,QAAQ,MAAM;AAC7B,WAAK,cAAc;AACnB,MAAAA,SAAO,KAAK,4BAA4B;AAAA,QACtC,WAAW,KAAK,gBAAgB;AAAA,QAChC,YAAY,YAAY,IAAI,IAAI,KAAK;AAAA,MACvC,CAAC;AAGD,UAAI,KAAK,cAAc;AACrB,cAAM,SAAkC;AAAA,UACtC,MAAM,KAAK,gBAAgB,KAAK;AAAA,UAChC,UAAU,KAAK,OAAO;AAAA,UACtB,iBAAiB,YAAY,IAAI,IAAI,KAAK;AAAA,UAC1C,SAAS;AAAA,QACX;AACA,aAAK,aAAa,MAAM;AACxB,aAAK,eAAe;AACpB,aAAK,eAAe;AAAA,MACtB;AAAA,IACF;AAEA,SAAK,YAAY,UAAU,MAAM;AAC/B,MAAAA,SAAO,MAAM,uCAAuC;AAAA,IACtD;AAEA,SAAK,YAAY,gBAAgB,MAAM;AACrC,MAAAA,SAAO,MAAM,iBAAiB;AAAA,IAChC;AAEA,SAAK,YAAY,cAAc,MAAM;AACnC,MAAAA,SAAO,MAAM,cAAc;AAAA,IAC7B;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,WAAW,QAAuC;AACxD,eAAW,YAAY,KAAK,iBAAiB;AAC3C,UAAI;AACF,iBAAS,MAAM;AAAA,MACjB,SAAS,OAAO;AACd,QAAAA,SAAO,MAAM,4BAA4B,EAAE,MAAM,CAAC;AAAA,MACpD;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,UAAU,OAAoB;AACpC,eAAW,YAAY,KAAK,gBAAgB;AAC1C,UAAI;AACF,iBAAS,KAAK;AAAA,MAChB,SAAS,eAAe;AACtB,QAAAA,SAAO,MAAM,2BAA2B,EAAE,OAAO,cAAc,CAAC;AAAA,MAClE;AAAA,IACF;AAAA,EACF;AACF;;;ACxcO,IAAM,gBAAgB;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAQO,IAAM,sBAAsB;AAgB5B,SAAS,oBAAoB,UAA0B,CAAC,GAAiB;AAC9E,QAAM,SAAS,IAAI,aAAa,mBAAmB;AAEnD,aAAW,CAAC,MAAM,KAAK,KAAK,OAAO,QAAQ,OAAO,GAAG;AACnD,UAAM,MAAM,cAAc,QAAQ,IAAmB;AACrD,QAAI,OAAO,GAAG;AACZ,aAAO,GAAG,IAAI,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,KAAK,CAAC;AAAA,IAC9C;AAAA,EACF;AAEA,SAAO;AACT;AAKO,IAAM,iBAAiB;AAAA;AAAA,EAE5B,SAAS,oBAAoB,CAAC,CAAC;AAAA;AAAA,EAG/B,OAAO,oBAAoB,EAAE,KAAK,KAAK,WAAW,IAAI,CAAC;AAAA;AAAA,EAGvD,KAAK,oBAAoB,EAAE,SAAS,KAAK,OAAO,IAAI,CAAC;AAAA;AAAA,EAGrD,OAAO,oBAAoB,EAAE,OAAO,KAAK,SAAS,IAAI,CAAC;AAAA;AAAA,EAGvD,WAAW,oBAAoB,EAAE,WAAW,KAAK,MAAM,IAAI,CAAC;AAAA;AAAA,EAG5D,QAAQ,oBAAoB,EAAE,MAAM,KAAK,MAAM,IAAI,CAAC;AAAA;AAAA,EAGpD,WAAW,oBAAoB,EAAE,SAAS,KAAK,OAAO,IAAI,CAAC;AAAA;AAAA,EAG3D,SAAS,oBAAoB,EAAE,KAAK,KAAK,WAAW,KAAK,YAAY,IAAI,CAAC;AAAA;AAAA,EAG1E,OAAO,oBAAoB,EAAE,aAAa,KAAK,SAAS,IAAI,CAAC;AAAA;AAAA,EAG7D,SAAS,oBAAoB,EAAE,YAAY,KAAK,KAAK,IAAI,CAAC;AAAA;AAAA,EAG1D,QAAQ,oBAAoB,EAAE,MAAM,KAAK,OAAO,IAAI,CAAC;AAAA;AAAA,EAGrD,eAAe,oBAAoB,EAAE,SAAS,KAAK,OAAO,IAAI,CAAC;AACjE;AAOO,SAAS,iBAAiB,MAAuC;AACtE,SAAO,eAAe,IAAI,EAAE,MAAM;AACpC;AAgBO,SAAS,cACd,UACc;AACd,QAAM,SAAS,IAAI,aAAa,mBAAmB;AACnD,MAAI,cAAc;AAElB,aAAW,EAAE,QAAQ,OAAO,KAAK,UAAU;AACzC,mBAAe;AACf,aAAS,IAAI,GAAG,IAAI,qBAAqB,KAAK;AAC5C,aAAO,CAAC,MAAM,OAAO,CAAC,KAAK,KAAK;AAAA,IAClC;AAAA,EACF;AAGA,MAAI,cAAc,GAAG;AACnB,aAAS,IAAI,GAAG,IAAI,qBAAqB,KAAK;AAC5C,aAAO,CAAC,KAAK;AAAA,IACf;AAAA,EACF;AAEA,SAAO;AACT;AAUO,SAAS,YACd,MACA,IACA,GACc;AACd,QAAM,SAAS,IAAI,aAAa,mBAAmB;AACnD,QAAM,WAAW,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,CAAC,CAAC;AAE3C,WAAS,IAAI,GAAG,IAAI,qBAAqB,KAAK;AAC5C,WAAO,CAAC,KAAK,KAAK,CAAC,KAAK,MAAM,IAAI,aAAa,GAAG,CAAC,KAAK,KAAK;AAAA,EAC/D;AAEA,SAAO;AACT;AAKO,IAAM,oBAAN,MAAwB;AAAA,EAAxB;AACL,SAAQ,iBAAiB,IAAI,aAAa,mBAAmB;AAC7D,SAAQ,gBAAgB,IAAI,aAAa,mBAAmB;AAC5D,SAAQ,qBAAqB;AAC7B,SAAQ,qBAAqB;AAC7B,SAAQ,sBAAsB;AAAA;AAAA;AAAA;AAAA;AAAA,EAK9B,IAAI,UAAwB;AAC1B,QAAI,KAAK,sBAAsB,GAAK;AAClC,aAAO,KAAK;AAAA,IACd;AAGA,WAAO,YAAY,KAAK,gBAAgB,KAAK,eAAe,KAAK,kBAAkB;AAAA,EACrF;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,SAA+B;AACjC,UAAM,aAAa,oBAAoB,OAAO;AAC9C,SAAK,cAAc,IAAI,UAAU;AACjC,SAAK,eAAe,IAAI,UAAU;AAClC,SAAK,qBAAqB;AAAA,EAC5B;AAAA;AAAA;AAAA;AAAA,EAKA,UAAU,QAAiC;AACzC,UAAM,aAAa,iBAAiB,MAAM;AAC1C,SAAK,cAAc,IAAI,UAAU;AACjC,SAAK,eAAe,IAAI,UAAU;AAClC,SAAK,qBAAqB;AAAA,EAC5B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,aAAa,SAAyB,YAA0B;AAC9D,SAAK,eAAe,IAAI,KAAK,OAAO;AACpC,SAAK,cAAc,IAAI,oBAAoB,OAAO,CAAC;AACnD,SAAK,qBAAqB;AAC1B,SAAK,sBAAsB,YAAY,IAAI;AAC3C,SAAK,qBAAqB;AAAA,EAC5B;AAAA;AAAA;AAAA;AAAA,EAKA,mBAAmB,QAA2B,YAA0B;AACtE,SAAK,eAAe,IAAI,KAAK,OAAO;AACpC,SAAK,cAAc,IAAI,iBAAiB,MAAM,CAAC;AAC/C,SAAK,qBAAqB;AAC1B,SAAK,sBAAsB,YAAY,IAAI;AAC3C,SAAK,qBAAqB;AAAA,EAC5B;AAAA;AAAA;AAAA;AAAA,EAKA,SAAe;AACb,QAAI,KAAK,sBAAsB,EAAK;AAEpC,UAAM,UAAU,YAAY,IAAI,IAAI,KAAK;AACzC,SAAK,qBAAqB,KAAK,IAAI,GAAK,UAAU,KAAK,kBAAkB;AAAA,EAC3E;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,kBAA2B;AAC7B,WAAO,KAAK,qBAAqB;AAAA,EACnC;AAAA;AAAA;AAAA;AAAA,EAKA,QAAc;AACZ,SAAK,eAAe,KAAK,CAAC;AAC1B,SAAK,cAAc,KAAK,CAAC;AACzB,SAAK,qBAAqB;AAAA,EAC5B;AACF;;;ACzOO,IAAM,mBAAN,cAA+B,aAAmD;AAAA,EAkCvF,YAAY,QAAyB;AACnC,UAAM;AAlCR,SAAS,OAAO;AAEhB,SAAQ,SAAyB;AACjC,SAAQ,aAA4B;AACpC,SAAQ,eAAe;AAGvB;AAAA,SAAQ,MAAkC;AAC1C,SAAQ,MAAiC;AACzC,SAAQ,MAAgC;AAExC,SAAQ,WAAuC;AAG/C;AAAA,SAAQ,KAAuB;AAC/B,SAAQ,sBAAsB;AAC9B,SAAiB,uBAAuB;AAGxC;AAAA,SAAQ,cAA8B,CAAC;AAGvC;AAAA,SAAQ,UAAiC,CAAC;AAC1C,SAAQ,gBAAsC;AAI9C;AAAA,SAAQ,aAAa;AACrB,SAAQ,4BAAoD;AAG5D;AAAA,SAAQ,aAAa,oBAAI,IAAkD;AAIzE,SAAK,kBAAkB;AACvB,SAAK,oBAAoB,IAAI,kBAAkB;AAAA,EACjD;AAAA,EAEA,IAAI,QAAwB;AAC1B,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,YAA2B;AAC7B,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,cAAuB;AACzB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,QAAQ,QAAsC;AAClD,SAAK,gBAAgB;AACrB,SAAK,aAAa,OAAO;AAEzB,QAAI;AAEF,YAAM,YAAY,MAAM,KAAK,aAAa,OAAO,MAAM;AAGvD,YAAM,QAAQ,IAAI;AAAA,QAChB,KAAK,QAAQ;AAAA,QACb,KAAK,QAAQ;AAAA,MACf,CAAC;AAGD,YAAM,KAAK,iBAAiB,WAAW,MAAM;AAE7C,WAAK,eAAe;AACpB,WAAK,SAAS,MAAM;AAEpB,WAAK,KAAK,qBAAqB,EAAE,WAAW,KAAK,YAAY,SAAS,KAAK,KAAK,CAAC;AAAA,IACnF,SAAS,OAAO;AACd,WAAK,SAAS,OAAO;AACrB,WAAK,KAAK,oBAAoB;AAAA,QAC5B;AAAA,QACA,aAAa;AAAA,MACf,CAAC;AACD,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,aAA4B;AAEhC,SAAK,2BAA2B,MAAM;AAGtC,QAAI,KAAK,UAAU;AACjB,WAAK,SAAS,QAAQ;AACtB,WAAK,WAAW;AAAA,IAClB;AAGA,QAAI,KAAK,IAAI;AACX,WAAK,GAAG,MAAM,KAAM,mBAAmB;AACvC,WAAK,KAAK;AAAA,IACZ;AAGA,UAAM,QAAQ,IAAI;AAAA,MAChB,KAAK,KAAK,QAAQ;AAAA,MAClB,KAAK,KAAK,QAAQ;AAAA,MAClB,KAAK,KAAK,QAAQ;AAAA,IACpB,CAAC;AAED,SAAK,eAAe;AACpB,SAAK,SAAS,cAAc;AAE5B,SAAK,KAAK,qBAAqB,EAAE,QAAQ,oBAAoB,CAAC;AAAA,EAChE;AAAA;AAAA;AAAA;AAAA,EAKA,UAAU,OAAwC;AAChD,QAAI,CAAC,KAAK,aAAc;AAGxB,QAAI,KAAK,YAAY;AACnB,WAAK,oBAAoB,KAAK,EAAE,KAAK,CAAC,qBAAqB;AACzD,YAAI,kBAAkB;AACpB,eAAK,UAAU;AAAA,QACjB;AAAA,MACF,CAAC,EAAE,MAAM,CAAC,UAAU;AAClB,gBAAQ,MAAM,wDAAwD,KAAK;AAAA,MAC7E,CAAC;AAAA,IAEH;AAGA,UAAM,UAAU,iBAAiB,eAC7B,QACA,eAAe,KAAK;AAGxB,SAAK,YAAY,KAAK,OAAO;AAG7B,SAAK,sBAAsB;AAAA,EAC7B;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,SAAS,MAA6B;AAC1C,QAAI,CAAC,KAAK,gBAAgB,CAAC,KAAK,IAAI;AAClC,YAAM,IAAI,MAAM,4BAA4B;AAAA,IAC9C;AAGA,SAAK,aAAa;AAAA,MAChB,MAAM;AAAA,MACN,SAAS;AAAA,MACT,WAAW,KAAK,IAAI;AAAA,IACtB,CAAC;AAED,SAAK,SAAS,UAAU;AACxB,SAAK,KAAK,qBAAqB,EAAE,WAAW,KAAK,IAAI,EAAE,CAAC;AAGxD,SAAK,GAAG,KAAK,KAAK,UAAU;AAAA,MAC1B,MAAM;AAAA,MACN,WAAW,KAAK;AAAA,MAChB,SAAS;AAAA,MACT,SAAS;AAAA,QACP,SAAS,KAAK,QAAQ,MAAM,GAAG;AAAA;AAAA,QAC/B,SAAS,MAAM,KAAK,KAAK,kBAAkB,OAAO;AAAA,MACpD;AAAA,IACF,CAAC,CAAC;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA,EAKA,YAAkB;AAChB,QAAI,CAAC,KAAK,WAAY;AAEtB,SAAK,KAAK,yBAAyB,EAAE,WAAW,KAAK,IAAI,EAAE,CAAC;AAG5D,SAAK,2BAA2B,MAAM;AACtC,SAAK,4BAA4B;AAGjC,QAAI,KAAK,IAAI,eAAe,UAAU,MAAM;AAC1C,WAAK,GAAG,KAAK,KAAK,UAAU;AAAA,QAC1B,MAAM;AAAA,QACN,WAAW,KAAK;AAAA,QAChB,WAAW,KAAK,IAAI;AAAA,MACtB,CAAC,CAAC;AAAA,IACJ;AAEA,SAAK,aAAa;AAClB,SAAK,SAAS,WAAW;AAEzB,SAAK,KAAK,wBAAwB,EAAE,WAAW,KAAK,IAAI,GAAG,QAAQ,OAAO,CAAC;AAAA,EAC7E;AAAA,EAEA,aAAoC;AAClC,WAAO,CAAC,GAAG,KAAK,OAAO;AAAA,EACzB;AAAA,EAEA,eAAqB;AACnB,SAAK,UAAU,CAAC;AAChB,SAAK,KAAK,kBAAkB,EAAE,cAAc,EAAE,CAAC;AAAA,EACjD;AAAA,EAEA,MAAM,cAAgC;AACpC,QAAI,CAAC,KAAK,MAAM,KAAK,GAAG,eAAe,UAAU,MAAM;AACrD,aAAO;AAAA,IACT;AAEA,WAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,YAAM,UAAU,WAAW,MAAM,QAAQ,KAAK,GAAG,GAAI;AAErD,YAAM,UAAU,CAAC,UAAwB;AACvC,cAAM,OAAO,KAAK,MAAM,MAAM,IAAI;AAClC,YAAI,KAAK,SAAS,QAAQ;AACxB,uBAAa,OAAO;AACpB,eAAK,IAAI,oBAAoB,WAAW,OAAO;AAC/C,kBAAQ,IAAI;AAAA,QACd;AAAA,MACF;AAEA,WAAK,IAAI,iBAAiB,WAAW,OAAO;AAC5C,WAAK,IAAI,KAAK,KAAK,UAAU,EAAE,MAAM,OAAO,CAAC,CAAC;AAAA,IAChD,CAAC;AAAA,EACH;AAAA;AAAA,EAIQ,SAAS,OAA6B;AAC5C,UAAM,gBAAgB,KAAK;AAC3B,SAAK,SAAS;AACd,SAAK,KAAK,gBAAgB,EAAE,OAAO,cAAc,CAAC;AAAA,EACpD;AAAA,EAEA,MAAc,aAAa,QAAuC;AAChE,UAAM,SAAS,KAAK,WAAW,IAAI,OAAO,QAAQ;AAClD,QAAI,UAAU,OAAO,YAAY,KAAK,IAAI,IAAI,KAAO;AACnD,aAAO,OAAO;AAAA,IAChB;AAGA,QAAI,OAAO,YAAY,WAAW;AAChC,aAAO,OAAO,YAAY;AAAA,IAC5B;AAIA,UAAM,WAAW,KAAK,gBAAgB;AACtC,QAAI,SAAS,WAAW,OAAO,KAAK,SAAS,SAAS,WAAW,GAAG;AAClE,aAAO;AAAA,IACT;AAGA,UAAM,eAAe,SAAS,QAAQ,UAAU,UAAU,EAAE,QAAQ,SAAS,SAAS;AACtF,UAAM,WAAW,MAAM,MAAM,GAAG,YAAY,eAAe;AAAA,MACzD,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAC9C,MAAM,KAAK,UAAU;AAAA,QACnB,UAAU,OAAO;AAAA,QACjB,QAAQ,OAAO,YAAY;AAAA,MAC7B,CAAC;AAAA,IACH,CAAC;AAED,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,IAAI,MAAM,gBAAgB,SAAS,UAAU,EAAE;AAAA,IACvD;AAEA,UAAM,EAAE,OAAO,UAAU,IAAI,MAAM,SAAS,KAAK;AAEjD,SAAK,WAAW,IAAI,OAAO,UAAU;AAAA,MACnC;AAAA,MACA,WAAW,KAAK,IAAI,IAAI,YAAY;AAAA,IACtC,CAAC;AAED,WAAO;AAAA,EACT;AAAA,EAEA,MAAc,UAAyB;AAErC,UAAM,QAAQ,IAAI;AAAA;AAAA,OAEf,YAAY;AACX,aAAK,MAAM,IAAI,oBAAoB;AAAA,UACjC,UAAU;AAAA,UACV,UAAU;AAAA,QACZ,CAAC;AACD,cAAM,KAAK,IAAI,KAAK;AAAA,MACtB,GAAG;AAAA;AAAA,OAEF,YAAY;AACX,aAAK,MAAM,IAAI,mBAAmB;AAAA,UAChC,UAAU;AAAA,UACV,SAAS;AAAA,UACT,YAAY;AAAA,UACZ,WAAW;AAAA,QACb,CAAC;AACD,cAAM,KAAK,IAAI,KAAK;AAAA,MACtB,GAAG;AAAA,IACL,CAAC;AAAA,EACH;AAAA,EAEA,MAAc,UAAyB;AAGrC,UAAM,SAAS,KAAK,gBAAgB,QAAQ,UAAU;AAEtD,SAAK,MAAM,IAAI,kBAAkB;AAAA,MAC/B,UAAU;AAAA,MACV,SAAS;AAAA,IACX,CAAC;AAED,UAAM,KAAK,IAAI,KAAK;AAGpB,UAAM,KAAK,aAAa;AAAA,EAC1B;AAAA,EAEA,MAAc,eAA8B;AAC1C,QAAI,CAAC,KAAK,KAAK;AACb,YAAM,IAAI,MAAM,yCAAyC;AAAA,IAC3D;AAEA,SAAK,WAAW,IAAI,oBAAoB;AAAA,MACtC,KAAK,KAAK;AAAA,MACV,YAAY;AAAA,MACZ,eAAe;AAAA,IACjB,CAAC;AAED,UAAM,KAAK,SAAS,WAAW;AAG/B,SAAK,SAAS,GAAG,eAAe,CAAC,UAAwB;AAEvD,WAAK,KAAK,aAAa;AAAA,QACrB,aAAa;AAAA,QACb,KAAK,CAAC,SAAiB;AACrB,gBAAM,MAAO,gBAAsC,QAAQ,IAAI;AAC/D,iBAAO,OAAO,IAAI,MAAM,GAAG,IAAI;AAAA,QACjC;AAAA,QACA,WAAW,KAAK,IAAI;AAAA;AAAA,QACpB,aAAa;AAAA;AAAA,MACf,CAAC;AAAA,IACH,CAAC;AAED,SAAK,SAAS,GAAG,qBAAqB,MAAM;AAC1C,WAAK,aAAa;AAClB,WAAK,SAAS,MAAM;AACpB,WAAK,KAAK,oBAAoB,EAAE,YAAY,EAAE,CAAC;AAAA,IACjD,CAAC;AAED,SAAK,SAAS,GAAG,SAAS,CAAC,UAAiB;AAC1C,cAAQ,MAAM,+BAA+B,KAAK;AAClD,WAAK,KAAK,oBAAoB;AAAA,QAC5B;AAAA,QACA,aAAa;AAAA,MACf,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA,EAEA,MAAc,iBAAiB,WAAmB,QAAsC;AACtF,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,YAAM,QAAQ,IAAI,IAAI,GAAG,KAAK,gBAAgB,SAAS,QAAQ,QAAQ,IAAI,CAAC,KAAK;AACjF,YAAM,aAAa,IAAI,aAAa,OAAO,SAAS;AACpD,YAAM,aAAa,IAAI,eAAe,OAAO,OAAO,WAAW;AAE/D,WAAK,KAAK,IAAI,UAAU,MAAM,SAAS,CAAC;AAExC,WAAK,GAAG,SAAS,MAAM;AAErB,aAAK,IAAI,KAAK,KAAK,UAAU;AAAA,UAC3B,MAAM;AAAA,UACN,OAAO;AAAA,UACP,UAAU,OAAO,OAAO;AAAA,UACxB,cAAc,OAAO;AAAA,QACvB,CAAC,CAAC;AAAA,MACJ;AAEA,WAAK,GAAG,YAAY,CAAC,UAAU;AAC7B,aAAK,uBAAuB,KAAK,MAAM,MAAM,IAAI,CAAC;AAAA,MACpD;AAEA,WAAK,GAAG,UAAU,MAAM;AACtB,eAAO,IAAI,MAAM,6BAA6B,CAAC;AAAA,MACjD;AAEA,WAAK,GAAG,UAAU,CAAC,UAAU;AAC3B,aAAK,iBAAiB,KAAK;AAAA,MAC7B;AAGA,YAAM,cAAc,WAAW,MAAM;AACnC,eAAO,IAAI,MAAM,cAAc,CAAC;AAAA,MAClC,GAAG,GAAK;AAER,YAAM,cAAc,CAAC,UAAwB;AAC3C,cAAM,OAAO,KAAK,MAAM,MAAM,IAAI;AAClC,YAAI,KAAK,SAAS,gBAAgB;AAChC,uBAAa,WAAW;AACxB,eAAK,IAAI,oBAAoB,WAAW,WAAW;AACnD,kBAAQ;AAAA,QACV,WAAW,KAAK,SAAS,eAAe;AACtC,uBAAa,WAAW;AACxB,iBAAO,IAAI,MAAM,KAAK,OAAO,CAAC;AAAA,QAChC;AAAA,MACF;AAEA,WAAK,GAAG,iBAAiB,WAAW,WAAW;AAAA,IACjD,CAAC;AAAA,EACH;AAAA,EAEQ,uBAAuB,MAAqC;AAClE,YAAQ,KAAK,MAAM;AAAA,MACjB,KAAK;AACH,aAAK,SAAS,UAAU;AACxB,aAAK,aAAa;AAClB,aAAK,KAAK,qBAAqB;AAAA,UAC7B,MAAM,KAAK;AAAA,UACX,SAAS,KAAK;AAAA,QAChB,CAAC;AAED,YAAI,KAAK,SAAS;AAChB,eAAK,kBAAkB;AAAA,YACrB,EAAE,CAAC,KAAK,OAAiB,GAAG,IAAI;AAAA,YAChC;AAAA,UACF;AAAA,QACF;AAEA,YAAI,KAAK,UAAU;AACjB,eAAK,SAAS,MAAM;AAAA,QACtB;AACA;AAAA,MAEF,KAAK;AACH,aAAK,KAAK,qBAAqB;AAAA,UAC7B,MAAM,KAAK;AAAA,UACX,QAAQ,KAAK;AAAA,QACf,CAAC;AACD;AAAA,MAEF,KAAK;AAEH,YAAI,KAAK,SAAS,KAAK,UAAU;AAC/B,gBAAM,YAAY,KAAK,oBAAoB,KAAK,KAAe;AAC/D,gBAAM,QAAQ,IAAI,WAAW,SAAS;AACtC,eAAK,SAAS,aAAa,KAAK,EAAE,MAAM,CAAC,UAAU;AACjD,oBAAQ,MAAM,qCAAqC,KAAK;AAAA,UAC1D,CAAC;AAAA,QACH;AACA;AAAA,MAEF,KAAK;AAEH,YAAI,KAAK,UAAU;AACjB,eAAK,SAAS,IAAI,EAAE,MAAM,CAAC,UAAU;AACnC,oBAAQ,MAAM,mCAAmC,KAAK;AAAA,UACxD,CAAC;AAAA,QACH;AAEA;AAAA,MAEF,KAAK;AACH,aAAK,aAAa;AAAA,UAChB,MAAM;AAAA,UACN,SAAS,KAAK;AAAA,UACd,WAAW,KAAK,IAAI;AAAA,UACpB,SAAS,KAAK;AAAA,QAChB,CAAC;AACD,aAAK,KAAK,mBAAmB;AAAA,UAC3B,UAAU,KAAK;AAAA,UACf,YAAY,KAAK,cAAwB;AAAA,QAC3C,CAAC;AACD;AAAA,MAEF,KAAK;AACH,aAAK,KAAK,kBAAkB;AAAA,UAC1B,cAAc,KAAK;AAAA,UACnB,YAAY,KAAK;AAAA,QACnB,CAAC;AACD;AAAA,MAEF,KAAK;AACH,aAAK,KAAK,oBAAoB;AAAA,UAC5B,OAAO,IAAI,MAAM,KAAK,OAAiB;AAAA,UACvC,aAAc,KAAK,eAA2B;AAAA,QAChD,CAAC;AACD;AAAA,IACJ;AAAA,EACF;AAAA,EAEQ,wBAA8B;AAIpC,QAAI,KAAK,YAAY,WAAW,EAAG;AAGnC,UAAM,cAAc,KAAK,YAAY,OAAO,CAACC,MAAK,QAAQA,OAAM,IAAI,QAAQ,CAAC;AAI7E,QAAI,cAAc,IAAM;AAExB,UAAM,QAAQ,IAAI,aAAa,WAAW;AAC1C,QAAI,SAAS;AACb,eAAW,OAAO,KAAK,aAAa;AAClC,YAAM,IAAI,KAAK,MAAM;AACrB,gBAAU,IAAI;AAAA,IAChB;AACA,SAAK,cAAc,CAAC;AAIpB,QAAI,MAAM;AACV,aAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,aAAO,MAAM,CAAC,IAAI,MAAM,CAAC;AAAA,IAC3B;AACA,UAAM,MAAM,KAAK,KAAK,MAAM,MAAM,MAAM;AAGxC,QAAI,MAAM,MAAM;AACd,cAAQ,MAAM,qCAAqC,EAAE,KAAK,SAAS,MAAM,OAAO,CAAC;AACjF;AAAA,IACF;AAGA,QAAI,KAAK,KAAK;AACZ,WAAK,SAAS,WAAW;AACzB,WAAK,KAAK,qBAAqB,EAAE,WAAW,KAAK,IAAI,EAAE,CAAC;AAExD,WAAK,IAAI,WAAW,KAAK,EAAE,KAAK,CAAC,WAAsD;AACrF,aAAK,KAAK,yBAAyB;AAAA,UACjC,MAAM,OAAO;AAAA,UACb,YAAY;AAAA,QACd,CAAC;AACD,aAAK,KAAK,mBAAmB,EAAE,WAAW,KAAK,IAAI,GAAG,YAAY,OAAO,gBAAgB,CAAC;AAG1F,cAAM,YAAY,OAAO,KAAK,KAAK;AACnC,YAAI,WAAW;AACb,eAAK,SAAS,SAAS,EAAE,MAAM,CAAC,UAAmB;AACjD,oBAAQ,MAAM,gCAAgC,KAAK;AAAA,UACrD,CAAC;AAAA,QACH;AAAA,MACF,CAAC,EAAE,MAAM,CAAC,UAAmB;AAC3B,gBAAQ,MAAM,oCAAoC,KAAK;AAAA,MACzD,CAAC;AAAA,IACH;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAc,oBAAoB,OAAoD;AAEpF,UAAM,UAAU,iBAAiB,eAC7B,QACA,eAAe,KAAK;AAGxB,QAAI,KAAK,KAAK;AAEZ,YAAM,YAAY,KAAK,IAAI,aAAa;AAGxC,eAAS,IAAI,GAAG,IAAI,aAAa,QAAQ,QAAQ,KAAK,WAAW;AAC/D,cAAM,QAAQ,QAAQ,MAAM,GAAG,IAAI,SAAS;AAC5C,cAAM,SAAS,MAAM,KAAK,IAAI,QAAQ,KAAK;AAG3C,YAAI,OAAO,UAAU;AACnB,iBAAO;AAAA,QACT;AAAA,MACF;AAEA,aAAO;AAAA,IACT;AAGA,QAAI,MAAM;AACV,aAAS,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;AACvC,aAAO,QAAQ,CAAC,IAAI,QAAQ,CAAC;AAAA,IAC/B;AACA,UAAM,MAAM,KAAK,KAAK,MAAM,QAAQ,MAAM;AAC1C,WAAO,MAAM;AAAA,EACf;AAAA,EAEQ,oBAAoB,QAA6B;AACvD,UAAM,eAAe,KAAK,MAAM;AAChC,UAAM,QAAQ,IAAI,WAAW,aAAa,MAAM;AAChD,aAAS,IAAI,GAAG,IAAI,aAAa,QAAQ,KAAK;AAC5C,YAAM,CAAC,IAAI,aAAa,WAAW,CAAC;AAAA,IACtC;AACA,WAAO,MAAM;AAAA,EACf;AAAA,EAEQ,aAAa,SAAoC;AACvD,SAAK,QAAQ,KAAK,OAAO;AACzB,SAAK,KAAK,kBAAkB,EAAE,cAAc,KAAK,QAAQ,OAAO,CAAC;AAAA,EACnE;AAAA,EAEQ,iBAAiB,OAAyB;AAChD,SAAK,eAAe;AAEpB,QAAI,MAAM,SAAS,KAAM;AAEvB,UAAI,KAAK,sBAAsB,KAAK,sBAAsB;AACxD,aAAK;AACL,mBAAW,MAAM;AACf,cAAI,KAAK,eAAe;AACtB,iBAAK,QAAQ,KAAK,aAAa,EAAE,MAAM,MAAM;AAAA,YAE7C,CAAC;AAAA,UACH;AAAA,QACF,GAAG,KAAK,IAAI,GAAG,KAAK,mBAAmB,IAAI,GAAI;AAAA,MACjD,OAAO;AACL,aAAK,SAAS,OAAO;AACrB,aAAK,KAAK,oBAAoB;AAAA,UAC5B,OAAO,IAAI,MAAM,mCAAmC;AAAA,UACpD,aAAa;AAAA,QACf,CAAC;AAAA,MACH;AAAA,IACF;AAEA,SAAK,KAAK,qBAAqB,EAAE,QAAQ,MAAM,UAAU,oBAAoB,CAAC;AAAA,EAChF;AACF;;;ACroBA,IAAM,0BAAN,MAA6D;AAAA,EAW3D,YACE,QACA,SACA;AARF,SAAQ,WAAkC,CAAC;AAC3C,SAAQ,WAAW,oBAAI,IAAoB;AAQzC,SAAK,YAAY,OAAO;AACxB,SAAK,UAAU;AACf,SAAK,WAAW;AAChB,SAAK,YAAY,KAAK,IAAI;AAC1B,SAAK,kBAAkB,KAAK,IAAI;AAChC,SAAK,qBAAqB,IAAI,kBAAkB;AAEhD,QAAI,OAAO,SAAS;AAClB,WAAK,mBAAmB,UAAU,OAAO,OAAkE;AAAA,IAC7G;AAAA,EACF;AAAA,EAEA,IAAI,UAAqB;AACvB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,SAAwB;AAC1B,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,QAAwB;AAC1B,WAAO,KAAK,SAAS;AAAA,EACvB;AAAA,EAEA,IAAI,UAAiC;AACnC,WAAO,CAAC,GAAG,KAAK,QAAQ;AAAA,EAC1B;AAAA,EAEA,IAAI,UAA0B;AAC5B,WAAO,CAAC;AAAA,EACV;AAAA,EAEA,IAAI,iBAAyB;AAC3B,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,MAAM,QAAuB;AAC3B,UAAM,KAAK,SAAS,QAAQ,KAAK,OAAO;AACxC,SAAK,kBAAkB,KAAK,IAAI;AAAA,EAClC;AAAA,EAEA,MAAM,MAAqB;AACzB,UAAM,KAAK,SAAS,WAAW;AAAA,EACjC;AAAA,EAEA,UAAU,OAAwC;AAChD,SAAK,SAAS,UAAU,KAAK;AAC7B,SAAK,kBAAkB,KAAK,IAAI;AAAA,EAClC;AAAA,EAEA,MAAM,SAAS,MAA6B;AAC1C,UAAM,KAAK,SAAS,SAAS,IAAI;AACjC,SAAK,kBAAkB,KAAK,IAAI;AAAA,EAClC;AAAA,EAEA,YAAkB;AAChB,SAAK,SAAS,UAAU;AACxB,SAAK,kBAAkB,KAAK,IAAI;AAAA,EAClC;AAAA,EAEA,WAAW,SAA+B;AACxC,SAAK,mBAAmB,IAAI,OAAO;AAAA,EACrC;AAAA,EAEA,WAAW,KAAa,OAAqB;AAC3C,SAAK,SAAS,IAAI,KAAK,KAAK;AAAA,EAC9B;AAAA,EAEA,cAAc,KAAmB;AAC/B,SAAK,SAAS,OAAO,GAAG;AAAA,EAC1B;AAAA,EAEA,aAAqC;AACnC,WAAO,OAAO,YAAY,KAAK,QAAQ;AAAA,EACzC;AAAA,EAEA,SAA0B;AACxB,WAAO;AAAA,MACL,WAAW,KAAK;AAAA,MAChB,UAAU,KAAK,QAAQ,OAAO;AAAA,MAC9B,aAAa,KAAK,QAAQ,OAAO;AAAA,MACjC,SAAS,KAAK;AAAA,MACd,SAAS,OAAO,YAAY,KAAK,QAAQ;AAAA,MACzC,SAAS,KAAK;AAAA,MACd,WAAW,KAAK;AAAA,MAChB,gBAAgB,KAAK;AAAA,IACvB;AAAA,EACF;AAAA,EAEA,OAAO,UAAiC;AACtC,SAAK,WAAW,CAAC,GAAG,SAAS,OAAO;AACpC,SAAK,WAAW,IAAI,IAAI,OAAO,QAAQ,SAAS,OAAO,CAAC;AACxD,SAAK,kBAAkB,SAAS;AAAA,EAClC;AAAA,EAEA,cAAoB;AAClB,SAAK,WAAW,KAAK,SAAS,WAAW;AAAA,EAC3C;AACF;AAKO,IAAM,2BAAN,cAAuC,aAAiC;AAAA,EAgB7E,YAAY,QAA4B;AACtC,UAAM;AAVR;AAAA,SAAQ,WAAW,oBAAI,IAAqC;AAG5D;AAAA,SAAQ,UAAU,oBAAI,IAA0B;AAGhD;AAAA,SAAQ,sBAA6D;AACrE,SAAiB,2BAA2B;AAI1C,SAAK,SAAS;AAAA,MACZ,qBAAqB;AAAA,MACrB,YAAY;AAAA,MACZ,GAAG;AAAA,IACL;AAGA,SAAK,UAAU,IAAI,iBAAiB,OAAO,OAAO;AAAA,EACpD;AAAA;AAAA;AAAA;AAAA,EAKA,eAAe,QAA4B;AACzC,SAAK,QAAQ,IAAI,OAAO,UAAU,MAAM;AAAA,EAC1C;AAAA;AAAA;AAAA;AAAA,EAKA,iBAAiB,UAAwB;AACvC,SAAK,QAAQ,OAAO,QAAQ;AAAA,EAC9B;AAAA;AAAA;AAAA;AAAA,EAKA,UAAU,UAA4C;AACpD,WAAO,KAAK,QAAQ,IAAI,QAAQ;AAAA,EAClC;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,cACJ,UACA,UAAkC,CAAC,GACL;AAC9B,UAAM,SAAS,KAAK,QAAQ,IAAI,QAAQ;AACxC,QAAI,CAAC,QAAQ;AACX,YAAM,IAAI,MAAM,qBAAqB,QAAQ,EAAE;AAAA,IACjD;AAEA,UAAM,YAAY,QAAQ,aAAa,KAAK,kBAAkB;AAE9D,UAAM,gBAA+B;AAAA,MACnC;AAAA,MACA;AAAA,MACA,cAAc,QAAQ;AAAA,MACtB,OAAO,QAAQ;AAAA,MACf,SAAS,QAAQ;AAAA,MACjB,UAAU,QAAQ;AAAA,IACpB;AAEA,UAAM,UAAU,IAAI,wBAAwB,eAAe,KAAK,OAAO;AAEvE,SAAK,SAAS,IAAI,WAAW,OAAO;AAGpC,SAAK,qBAAqB,KAAK,SAAS,SAAS;AAGjD,UAAM,QAAQ,MAAM;AAEpB,SAAK,KAAK,mBAAmB,EAAE,WAAW,SAAS,CAAC;AAEpD,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,WAAW,WAAkC;AACjD,UAAM,UAAU,KAAK,SAAS,IAAI,SAAS;AAC3C,QAAI,SAAS;AACX,YAAM,QAAQ,IAAI;AAClB,WAAK,SAAS,OAAO,SAAS;AAC9B,WAAK,KAAK,iBAAiB,EAAE,WAAW,QAAQ,mBAAmB,CAAC;AAAA,IACtE;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,WAAW,WAAoD;AAC7D,WAAO,KAAK,SAAS,IAAI,SAAS;AAAA,EACpC;AAAA;AAAA;AAAA;AAAA,EAKA,kBAAkB,UAAyC;AACzD,WAAO,MAAM,KAAK,KAAK,SAAS,OAAO,CAAC,EACrC,OAAO,OAAK,EAAE,OAAO,OAAO,aAAa,QAAQ;AAAA,EACtD;AAAA;AAAA;AAAA;AAAA,EAKA,wBAA8B;AAC5B,QAAI,KAAK,oBAAqB;AAE9B,SAAK,sBAAsB,YAAY,YAAY;AACjD,YAAM,KAAK,mBAAmB;AAAA,IAChC,GAAG,KAAK,wBAAwB;AAAA,EAClC;AAAA;AAAA;AAAA;AAAA,EAKA,uBAA6B;AAC3B,QAAI,KAAK,qBAAqB;AAC5B,oBAAc,KAAK,mBAAmB;AACtC,WAAK,sBAAsB;AAAA,IAC7B;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,UAAyB;AAC7B,SAAK,qBAAqB;AAG1B,UAAM,cAAc,MAAM,KAAK,KAAK,SAAS,OAAO,CAAC,EAAE,IAAI,OAAK,EAAE,IAAI,CAAC;AACvE,UAAM,QAAQ,IAAI,WAAW;AAC7B,SAAK,SAAS,MAAM;AAGpB,UAAM,KAAK,QAAQ,WAAW;AAAA,EAChC;AAAA;AAAA,EAIQ,oBAA4B;AAClC,WAAO,QAAQ,KAAK,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,OAAO,GAAG,CAAC,CAAC;AAAA,EACtE;AAAA,EAEQ,qBAAqB,SAAoB,WAAyB;AAExE,UAAM,SAAoC;AAAA,MACxC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAEA,eAAW,SAAS,QAAQ;AAC1B,cAAQ,GAAG,OAAO,CAAC,SAAS;AAC1B,cAAM,YAAY;AAClB,aAAK,KAAK,OAAO,EAAE,GAAG,WAAW,UAAU,CAAkC;AAAA,MAC/E,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EAEA,MAAc,qBAAoC;AAChD,QAAI;AACF,YAAM,KAAK,QAAQ,YAAY;AAAA,IACjC,QAAQ;AAAA,IAER;AAAA,EACF;AACF;;;AC9SO,IAAM,iBAAN,MAAM,eAAc;AAAA,EAApB;AACL,SAAQ,UAAU,oBAAI,IAA0B;AAChD,SAAQ,SAAS,oBAAI,IAAyB;AAC9C,SAAQ,QAAQ,oBAAI,IAAyB;AAC7C,SAAQ,wBAAwB,oBAAI,IAAkC;AAAA;AAAA;AAAA;AAAA;AAAA,EAetE,SACE,QACA,QAAqB,eAAc,eACnC,sBACM;AACN,SAAK,QAAQ,IAAI,OAAO,UAAU,MAAM;AACxC,SAAK,OAAO,IAAI,OAAO,UAAU,KAAK;AACtC,SAAK,MAAM,IAAI,OAAO,UAAU;AAAA,MAC9B,iBAAiB;AAAA,MACjB,oBAAoB;AAAA,MACpB,YAAY;AAAA,MACZ,mBAAmB;AAAA,MACnB,iBAAiB,KAAK,IAAI;AAAA,MAC1B,gBAAgB,KAAK,IAAI;AAAA,IAC3B,CAAC;AAED,QAAI,sBAAsB;AACxB,WAAK,sBAAsB,IAAI,OAAO,UAAU,oBAAoB;AAAA,IACtE;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,WAAW,UAAwB;AACjC,SAAK,QAAQ,OAAO,QAAQ;AAC5B,SAAK,OAAO,OAAO,QAAQ;AAC3B,SAAK,MAAM,OAAO,QAAQ;AAC1B,SAAK,sBAAsB,OAAO,QAAQ;AAAA,EAC5C;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,UAA4C;AAC9C,WAAO,KAAK,QAAQ,IAAI,QAAQ;AAAA,EAClC;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,UAA2B;AAC7B,WAAO,KAAK,QAAQ,IAAI,QAAQ;AAAA,EAClC;AAAA;AAAA;AAAA;AAAA,EAKA,eAAyB;AACvB,WAAO,MAAM,KAAK,KAAK,QAAQ,KAAK,CAAC;AAAA,EACvC;AAAA;AAAA;AAAA;AAAA,EAKA,iBAAiB,UAA2B;AAC1C,UAAM,QAAQ,KAAK,OAAO,IAAI,QAAQ;AACtC,UAAM,QAAQ,KAAK,MAAM,IAAI,QAAQ;AAErC,QAAI,CAAC,SAAS,CAAC,MAAO,QAAO;AAE7B,WAAO,MAAM,kBAAkB,MAAM;AAAA,EACvC;AAAA;AAAA;AAAA;AAAA,EAKA,eAAe,UAA2B;AACxC,UAAM,QAAQ,KAAK,OAAO,IAAI,QAAQ;AACtC,UAAM,QAAQ,KAAK,MAAM,IAAI,QAAQ;AAErC,QAAI,CAAC,SAAS,CAAC,MAAO,QAAO;AAG7B,SAAK,iBAAiB,QAAQ;AAE9B,WAAO,MAAM,qBAAqB,MAAM;AAAA,EAC1C;AAAA;AAAA;AAAA;AAAA,EAKA,YAAY,UAAkB,SAA0B;AACtD,UAAM,QAAQ,KAAK,OAAO,IAAI,QAAQ;AACtC,UAAM,QAAQ,KAAK,MAAM,IAAI,QAAQ;AAErC,QAAI,CAAC,SAAS,CAAC,MAAO,QAAO;AAG7B,SAAK,gBAAgB,QAAQ;AAE7B,WAAO,MAAM,oBAAoB,WAAW,MAAM;AAAA,EACpD;AAAA;AAAA;AAAA;AAAA,EAKA,kBAAkB,UAAwB;AACxC,UAAM,QAAQ,KAAK,MAAM,IAAI,QAAQ;AACrC,QAAI,OAAO;AACT,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,kBAAkB,UAAwB;AACxC,UAAM,QAAQ,KAAK,MAAM,IAAI,QAAQ;AACrC,QAAI,SAAS,MAAM,kBAAkB,GAAG;AACtC,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,cAAc,UAAwB;AACpC,UAAM,QAAQ,KAAK,MAAM,IAAI,QAAQ;AACrC,QAAI,OAAO;AACT,WAAK,iBAAiB,QAAQ;AAC9B,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,aAAa,UAAkB,QAAsB;AACnD,UAAM,QAAQ,KAAK,MAAM,IAAI,QAAQ;AACrC,QAAI,OAAO;AACT,YAAM,cAAc;AAAA,IACtB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,mBAAmB,UAAkB,SAAuB;AAC1D,UAAM,QAAQ,KAAK,MAAM,IAAI,QAAQ;AACrC,QAAI,OAAO;AACT,WAAK,gBAAgB,QAAQ;AAC7B,YAAM,qBAAqB;AAAA,IAC7B;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,aAAa,UAAmC;AACpD,UAAM,SAAS,KAAK,QAAQ,IAAI,QAAQ;AACxC,QAAI,CAAC,QAAQ;AACX,YAAM,IAAI,MAAM,qBAAqB,QAAQ,EAAE;AAAA,IACjD;AAGA,UAAM,WAAW,KAAK,sBAAsB,IAAI,QAAQ;AACxD,QAAI,UAAU;AACZ,YAAM,QAAQ,MAAM,SAAS;AAC7B,aAAO,YAAY,YAAY;AAC/B,aAAO;AAAA,IACT;AAGA,QAAI,OAAO,YAAY,WAAW;AAChC,aAAO,OAAO,YAAY;AAAA,IAC5B;AAEA,UAAM,IAAI,MAAM,uCAAuC,QAAQ,EAAE;AAAA,EACnE;AAAA;AAAA;AAAA;AAAA,EAKA,kBAAkB,UAAkB,aAAyD;AAC3F,UAAM,SAAS,KAAK,QAAQ,IAAI,QAAQ;AACxC,QAAI,QAAQ;AACV,aAAO,cAAc,EAAE,GAAG,OAAO,aAAa,GAAG,YAAY;AAAA,IAC/D;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,SAAS,UAA2C;AAClD,WAAO,KAAK,MAAM,IAAI,QAAQ;AAAA,EAChC;AAAA;AAAA;AAAA;AAAA,EAKA,SAAS,UAA2C;AAClD,WAAO,KAAK,OAAO,IAAI,QAAQ;AAAA,EACjC;AAAA;AAAA;AAAA;AAAA,EAKA,YAAY,UAAkB,OAAmC;AAC/D,UAAM,WAAW,KAAK,OAAO,IAAI,QAAQ;AACzC,QAAI,UAAU;AACZ,WAAK,OAAO,IAAI,UAAU,EAAE,GAAG,UAAU,GAAG,MAAM,CAAC;AAAA,IACrD;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,WAAW,UAAwB;AACjC,UAAM,QAAQ,KAAK,MAAM,IAAI,QAAQ;AACrC,QAAI,OAAO;AACT,YAAM,qBAAqB;AAC3B,YAAM,aAAa;AACnB,YAAM,oBAAoB;AAC1B,YAAM,kBAAkB,KAAK,IAAI;AACjC,YAAM,iBAAiB,KAAK,IAAI;AAAA,IAClC;AAAA,EACF;AAAA;AAAA,EAIQ,iBAAiB,UAAwB;AAC/C,UAAM,QAAQ,KAAK,MAAM,IAAI,QAAQ;AACrC,QAAI,CAAC,MAAO;AAEZ,UAAM,MAAM,KAAK,IAAI;AACrB,QAAI,MAAM,MAAM,mBAAmB,KAAO;AACxC,YAAM,qBAAqB;AAC3B,YAAM,kBAAkB;AAAA,IAC1B;AAAA,EACF;AAAA,EAEQ,gBAAgB,UAAwB;AAC9C,UAAM,QAAQ,KAAK,MAAM,IAAI,QAAQ;AACrC,QAAI,CAAC,MAAO;AAEZ,UAAM,MAAM,KAAK,IAAI;AACrB,UAAM,aAAa,KAAK,KAAK,KAAK;AAClC,QAAI,MAAM,MAAM,kBAAkB,YAAY;AAC5C,YAAM,oBAAoB;AAC1B,YAAM,iBAAiB;AAAA,IACzB;AAAA,EACF;AACF;AAAA;AAAA;AAAA;AAtQa,eASK,gBAA6B;AAAA,EAC3C,aAAa;AAAA,EACb,mBAAmB;AAAA,EACnB,0BAA0B;AAAA,EAC1B,uBAAuB;AACzB;AAdK,IAAM,gBAAN;;;ACbA,IAAM,mBAAN,cAA+B,aAA8B;AAAA,EAUlE,YAAY,SAA0B,CAAC,GAAG;AACxC,UAAM;AARR,SAAQ,iBAAiB;AACzB,SAAQ,gBAAgC,CAAC;AACzC,SAAQ,YAAY;AACpB,SAAQ,eAAoC;AAC5C,SAAQ,oBAAoB;AAC5B,SAAQ,gBAAgB;AAItB,SAAK,SAAS;AAAA,MACZ,YAAY;AAAA,MACZ,YAAY;AAAA,MACZ,aAAa;AAAA,MACb,YAAY;AAAA,MACZ,GAAG;AAAA,IACL;AAEA,SAAK,cAAc,IAAI,aAAa,KAAK,OAAO,UAAU;AAAA,EAC5D;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,aAA4B;AAChC,QAAI,CAAC,KAAK,cAAc;AACtB,WAAK,eAAe,IAAI,aAAa,EAAE,YAAY,KAAK,OAAO,WAAW,CAAC;AAAA,IAC7E;AAEA,QAAI,KAAK,aAAa,UAAU,aAAa;AAC3C,YAAM,KAAK,aAAa,OAAO;AAAA,IACjC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,UAAU,OAA2B;AAEnC,SAAK,cAAc,KAAK,KAAK;AAG7B,SAAK,mBAAmB,KAAK;AAG7B,QAAI,CAAC,KAAK,aAAa,KAAK,cAAc,SAAS,GAAG;AACpD,WAAK,cAAc;AAAA,IACrB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,mBAAmB,OAA2B;AACpD,QAAI,SAAS;AAEb,WAAO,SAAS,MAAM,QAAQ;AAC5B,YAAM,YAAY,KAAK,OAAO,aAAa,KAAK;AAChD,YAAM,SAAS,KAAK,IAAI,WAAW,MAAM,SAAS,MAAM;AAExD,WAAK,YAAY,IAAI,MAAM,SAAS,QAAQ,SAAS,MAAM,GAAG,KAAK,cAAc;AACjF,WAAK,kBAAkB;AACvB,gBAAU;AAGV,UAAI,KAAK,kBAAkB,KAAK,OAAO,YAAY;AACjD,aAAK,KAAK,gBAAgB,EAAE,OAAO,IAAI,aAAa,KAAK,WAAW,EAAE,CAAC;AAGvE,cAAM,eAAe,KAAK,OAAO,aAAa,KAAK,OAAO;AAC1D,aAAK,YAAY,WAAW,GAAG,YAAY;AAC3C,aAAK,iBAAiB,KAAK,OAAO;AAAA,MACpC;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,gBAA+B;AAC3C,QAAI,CAAC,KAAK,gBAAgB,KAAK,UAAW;AAE1C,SAAK,YAAY;AACjB,SAAK,oBAAoB,KAAK,aAAa;AAC3C,SAAK,gBAAgB;AAErB,SAAK,KAAK,kBAAkB,CAAC,CAAC;AAE9B,UAAM,KAAK,qBAAqB;AAAA,EAClC;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,uBAAsC;AAClD,QAAI,CAAC,KAAK,aAAc;AAExB,WAAO,KAAK,cAAc,SAAS,GAAG;AACpC,YAAM,QAAQ,KAAK,cAAc,MAAM;AAGvC,YAAM,SAAS,KAAK,aAAa,aAAa,GAAG,MAAM,QAAQ,KAAK,OAAO,UAAU;AACrF,aAAO,cAAc,OAAO,CAAC;AAE7B,YAAM,SAAS,KAAK,aAAa,mBAAmB;AACpD,aAAO,SAAS;AAChB,aAAO,QAAQ,KAAK,aAAa,WAAW;AAG5C,YAAM,WAAW,KAAK,oBAAoB,KAAK,gBAAgB,KAAK,OAAO;AAC3E,aAAO,MAAM,QAAQ;AAErB,WAAK,iBAAiB,MAAM;AAG5B,WAAK,WAAW;AAGhB,YAAM,IAAI,QAAQ,aAAW;AAC3B,eAAO,UAAU;AAAA,MACnB,CAAC;AAAA,IACH;AAEA,SAAK,YAAY;AACjB,SAAK,KAAK,gBAAgB,CAAC,CAAC;AAAA,EAC9B;AAAA;AAAA;AAAA;AAAA,EAKQ,aAAmB;AACzB,QAAI,CAAC,KAAK,aAAc;AAExB,UAAM,eAAe,KAAK,oBAAoB,KAAK,gBAAgB,KAAK,OAAO;AAC/E,UAAM,aAAa,KAAK,aAAa;AACrC,UAAM,WAAW,aAAa,gBAAgB;AAE9C,QAAI,KAAK,IAAI,OAAO,IAAI,KAAK,OAAO,YAAY;AAC9C,WAAK,KAAK,cAAc,EAAE,QAAQ,CAAC;AAAA,IACrC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,aAAmB;AACjB,SAAK,gBAAgB,CAAC;AACtB,SAAK,iBAAiB;AACtB,SAAK,YAAY,KAAK,CAAC;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA,EAKA,OAAa;AACX,SAAK,WAAW;AAChB,SAAK,YAAY;AAAA,EACnB;AAAA;AAAA;AAAA;AAAA,EAKA,sBAA8B;AAC5B,QAAI,CAAC,KAAK,aAAc,QAAO;AAC/B,WAAO,KAAK,aAAa,cAAc,KAAK;AAAA,EAC9C;AAAA;AAAA;AAAA;AAAA,EAKA,eAAwB;AACtB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,UAAgB;AACd,SAAK,KAAK;AACV,SAAK,cAAc,MAAM;AACzB,SAAK,eAAe;AAAA,EACtB;AACF;;;ACpLO,IAAM,sBAAN,cAAkC,aAAiC;AAAA,EAWxE,YAAY,SAA6B,CAAC,GAAG;AAC3C,UAAM;AAVR,SAAQ,aAAa;AACrB,SAAQ,kBAAkB;AAC1B,SAAQ,iBAAiB;AACzB,SAAQ,eAAqD;AAC7D,SAAQ,eAAe;AAGvB;AAAA,SAAQ,mCAAmC;AAIzC,SAAK,SAAS;AAAA,MACZ,cAAc;AAAA;AAAA,MACd,qBAAqB;AAAA;AAAA,MACrB,kBAAkB;AAAA;AAAA,MAClB,SAAS;AAAA,MACT,GAAG;AAAA,IACL;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,iBAAiB,gBAAwB,cAAsB,GAAS;AACtE,QAAI,CAAC,KAAK,OAAO,QAAS;AAE1B,QAAI,iBAAiB,KAAK,OAAO,cAAc;AAC7C,WAAK,iBAAiB,eAAe,cAAc;AAAA,IACrD,OAAO;AACL,WAAK,kBAAkB;AAAA,IACzB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,aAAa,SAA0C;AACrD,QAAI,CAAC,KAAK,OAAO,QAAS;AAE1B,UAAM,MAAM,KAAK,aAAa,OAAO;AAIrC,UAAM,iBAAiB,KAAK,IAAI,MAAM,MAAM,CAAG;AAE/C,QAAI,iBAAiB,KAAK,OAAO,cAAc;AAC7C,WAAK,iBAAiB,GAAG;AAAA,IAC3B,OAAO;AACL,WAAK,kBAAkB;AAAA,IACzB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,cAAc,UAAyB;AACrC,SAAK,eAAe;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA,EAKA,WAAW,SAAwB;AACjC,SAAK,OAAO,UAAU;AACtB,QAAI,CAAC,SAAS;AACZ,WAAK,MAAM;AAAA,IACb;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,aAAa,QAA2C;AACtD,SAAK,SAAS,EAAE,GAAG,KAAK,QAAQ,GAAG,OAAO;AAAA,EAC5C;AAAA;AAAA;AAAA;AAAA,EAKA,QAAc;AACZ,SAAK,aAAa;AAClB,SAAK,kBAAkB;AACvB,SAAK,iBAAiB;AACtB,SAAK,mCAAmC;AACxC,QAAI,KAAK,cAAc;AACrB,mBAAa,KAAK,YAAY;AAC9B,WAAK,eAAe;AAAA,IACtB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,WAA8D;AAC5D,WAAO;AAAA,MACL,YAAY,KAAK;AAAA,MACjB,kBAAkB,KAAK,aAAa,KAAK,IAAI,IAAI,KAAK,kBAAkB;AAAA,IAC1E;AAAA,EACF;AAAA;AAAA,EAIQ,aAAa,SAA4C;AAC/D,QAAI,MAAM;AACV,UAAM,QAAQ,mBAAmB,aAAa,QAAQ;AAEtD,aAAS,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;AACvC,YAAM,SAAS,QAAQ,CAAC,IAAI;AAC5B,aAAO,SAAS;AAAA,IAClB;AAEA,WAAO,KAAK,KAAK,MAAM,QAAQ,MAAM;AAAA,EACvC;AAAA,EAEQ,iBAAiB,KAAmB;AAC1C,UAAM,MAAM,KAAK,IAAI;AACrB,SAAK,iBAAiB;AAGtB,QAAI,KAAK,cAAc;AACrB,mBAAa,KAAK,YAAY;AAC9B,WAAK,eAAe;AAAA,IACtB;AAGA,QAAI,CAAC,KAAK,YAAY;AACpB,WAAK,aAAa;AAClB,WAAK,kBAAkB;AACvB,WAAK,KAAK,mBAAmB,EAAE,IAAI,CAAC;AAAA,IACtC;AAGA,QAAI,KAAK,gBAAgB,CAAC,KAAK,kCAAkC;AAC/D,YAAM,iBAAiB,MAAM,KAAK;AAClC,UAAI,kBAAkB,KAAK,OAAO,qBAAqB;AACrD,aAAK,mCAAmC;AACxC,aAAK,KAAK,0BAA0B,EAAE,KAAK,YAAY,eAAe,CAAC;AAAA,MACzE;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,oBAA0B;AAChC,QAAI,CAAC,KAAK,WAAY;AAGtB,QAAI,CAAC,KAAK,cAAc;AACtB,WAAK,eAAe,WAAW,MAAM;AACnC,cAAM,aAAa,KAAK,iBAAiB,KAAK;AAC9C,aAAK,aAAa;AAClB,aAAK,eAAe;AAEpB,aAAK,mCAAmC;AACxC,aAAK,KAAK,gBAAgB,EAAE,WAAW,CAAC;AAAA,MAC1C,GAAG,KAAK,OAAO,gBAAgB;AAAA,IACjC;AAAA,EACF;AACF;;;ACRO,IAAM,2BAAiD;AAAA,EAC5D,cAAc;AAAA,EACd,sBAAsB;AAAA;AAAA,EACtB,kBAAkB;AAAA,EAClB,kBAAkB;AAAA,EAElB,QAAQ;AAAA,IACN;AAAA,MACE,MAAM;AAAA,MACN,WAAW,CAAC,cAAc;AAAA,MAC1B,aAAa,CAAC,CAAG;AAAA,MACjB,qBAAqB;AAAA,MACrB,qBAAqB;AAAA,MACrB,SAAS;AAAA,IACX;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,WAAW,CAAC,gBAAgB;AAAA,MAC5B,aAAa,CAAC,CAAG;AAAA,MACjB,qBAAqB;AAAA,MACrB,qBAAqB;AAAA,MACrB,SAAS;AAAA;AAAA,MACT,eAAe;AAAA,IACjB;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,WAAW,CAAC,oBAAoB,oBAAoB;AAAA,MACpD,aAAa,CAAC,KAAK,GAAG;AAAA,MACtB,qBAAqB;AAAA,MACrB,qBAAqB;AAAA,MACrB,SAAS;AAAA;AAAA,MACT,eAAe;AAAA,IACjB;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,WAAW,CAAC,cAAc;AAAA,MAC1B,aAAa,CAAC,CAAG;AAAA,MACjB,qBAAqB;AAAA,MACrB,qBAAqB;AAAA,MACrB,SAAS;AAAA,IACX;AAAA,EACF;AAAA,EAEA,aAAa;AAAA;AAAA,IAEX,EAAE,MAAM,QAAQ,IAAI,aAAa,SAAS,qBAAqB,UAAU,IAAI;AAAA,IAC7E,EAAE,MAAM,YAAY,IAAI,aAAa,SAAS,qBAAqB,UAAU,IAAI;AAAA;AAAA;AAAA,IAGjF,EAAE,MAAM,aAAa,IAAI,YAAY,SAAS,oBAAoB,UAAU,IAAI;AAAA;AAAA,IAGhF,EAAE,MAAM,YAAY,IAAI,YAAY,SAAS,kBAAkB,UAAU,IAAI;AAAA,IAC7E,EAAE,MAAM,QAAQ,IAAI,YAAY,SAAS,kBAAkB,UAAU,IAAI;AAAA;AAAA,IAGzE,EAAE,MAAM,YAAY,IAAI,QAAQ,SAAS,mBAAmB,UAAU,IAAI;AAAA;AAAA,IAG1E,EAAE,MAAM,aAAa,IAAI,QAAQ,SAAS,WAAW,UAAU,IAAI;AAAA,IACnE,EAAE,MAAM,YAAY,IAAI,QAAQ,SAAS,WAAW,UAAU,IAAI;AAAA;AAAA,IAGlE,EAAE,MAAM,YAAY,IAAI,aAAa,SAAS,aAAa,UAAU,IAAI;AAAA,EAC3E;AAAA,EAEA,iBAAiB;AAAA,IACf,EAAE,SAAS,SAAS,MAAM,iBAAiB,WAAW,KAAK,YAAY,EAAI;AAAA,IAC3E,EAAE,SAAS,OAAO,MAAM,eAAe,WAAW,KAAK,YAAY,IAAI;AAAA,IACvE,EAAE,SAAS,SAAS,MAAM,iBAAiB,WAAW,KAAK,YAAY,IAAI;AAAA,IAC3E,EAAE,SAAS,WAAW,MAAM,gBAAgB,WAAW,KAAK,YAAY,EAAI;AAAA,IAC5E,EAAE,SAAS,aAAa,MAAM,qBAAqB,WAAW,KAAK,YAAY,IAAI;AAAA,IACnF,EAAE,SAAS,QAAQ,MAAM,gBAAgB,WAAW,KAAK,YAAY,EAAI;AAAA,IACzE,EAAE,SAAS,WAAW,MAAM,mBAAmB,WAAW,KAAK,YAAY,EAAI;AAAA,IAC/E,EAAE,SAAS,WAAW,MAAM,mBAAmB,WAAW,GAAK,YAAY,EAAI;AAAA,EACjF;AAAA,EAEA,cAAc,CAAC,sBAAsB,uBAAuB,oBAAoB;AAClF;;;AC5NO,IAAM,iBAAN,cAA6B,aAAmC;AAAA,EA6BrE,YAAY,SAAwC,CAAC,GAAG;AACtD,UAAM;AA3BR,SAAQ,gBAAuC;AAG/C;AAAA,SAAQ,kBAA2B;AACnC,SAAQ,qBAA6B;AACrC,SAAQ,qBAA6B;AACrC,SAAQ,sBAA8B;AAGtC;AAAA,SAAQ,iBAAsC;AAC9C,SAAQ,oBAA4B;AACpC,SAAQ,qBAA6B;AACrC,SAAQ,sBAA8B;AAGtC;AAAA,SAAQ,cAAsB;AAC9B,SAAQ,gBAAwB;AAChC,SAAQ,qBAA6B;AAGrC;AAAA,SAAQ,iBAAyB;AACjC,SAAQ,iBAAyB;AAO/B,SAAK,SAAS,EAAE,GAAG,0BAA0B,GAAG,OAAO;AAGvD,UAAM,eAAe,KAAK,OAAO,OAAO;AAAA,MACtC,CAAC,MAAM,EAAE,SAAS,KAAK,OAAO;AAAA,IAChC;AACA,QAAI,CAAC,cAAc;AACjB,YAAM,IAAI,MAAM,kBAAkB,KAAK,OAAO,YAAY,aAAa;AAAA,IACzE;AACA,SAAK,eAAe;AACpB,SAAK,iBAAiB,KAAK,IAAI;AAC/B,SAAK,iBAAiB,KAAK,IAAI;AAG/B,SAAK,eAAe,KAAK,cAAc;AAAA,EACzC;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,QAA4B;AAC9B,WAAO,KAAK,aAAa;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,SAA0B;AAC5B,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,QAAQ,OAAkC;AAExC,UAAM,aAAa,KAAK,OAAO,YAAY;AAAA,MACzC,CAAC,MACC,EAAE,SAAS,KAAK,aAAa,QAC7B,EAAE,YAAY,UACb,CAAC,EAAE,aAAa,EAAE,UAAU;AAAA,IACjC;AAEA,QAAI,CAAC,YAAY;AACf,aAAO;AAAA,IACT;AAEA,SAAK,gBAAgB,YAAY,KAAK;AACtC,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,WAAW,SAAuB,YAA0B;AAC1D,UAAM,cAAc,KAAK;AAEzB,SAAK,iBAAiB;AACtB,SAAK,oBAAoB,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,UAAU,CAAC;AAG5D,UAAM,UAAU,KAAK,OAAO,gBAAgB;AAAA,MAC1C,CAAC,MAAM,EAAE,YAAY;AAAA,IACvB;AACA,QAAI,WAAW,KAAK,aAAa,qBAAqB;AACpD,WAAK,sBAAsB,QAAQ,YAAY,KAAK;AAAA,IACtD,OAAO;AACL,WAAK,sBAAsB;AAAA,IAC7B;AAEA,QAAI,gBAAgB,SAAS;AAC3B,WAAK,KAAK,kBAAkB,EAAE,SAAS,WAAW,CAAC;AAAA,IACrD;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,eAAqB;AACnB,SAAK,iBAAiB;AACtB,SAAK,oBAAoB;AACzB,SAAK,sBAAsB;AAC3B,SAAK,KAAK,kBAAkB,EAAE,SAAS,MAAM,YAAY,EAAE,CAAC;AAAA,EAC9D;AAAA;AAAA;AAAA;AAAA,EAKA,eAAe,QAAsB;AACnC,SAAK,cAAc,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,MAAM,CAAC;AAAA,EACpD;AAAA;AAAA;AAAA;AAAA,EAKA,SAAS,WAA+B,gBAAwB,KAAW;AACzE,UAAM,cAAc,KAAK,OAAO,OAAO,KAAK,CAAC,MAAM,EAAE,SAAS,SAAS;AACvE,QAAI,CAAC,aAAa;AAChB,cAAQ,KAAK,2BAA2B,SAAS,aAAa;AAC9D;AAAA,IACF;AAEA,QAAI,YAAY,SAAS,KAAK,aAAa,QAAQ,CAAC,KAAK,iBAAiB;AACxE;AAAA,IACF;AAGA,UAAM,mBAA+B;AAAA,MACnC,MAAM,KAAK,aAAa;AAAA,MACxB,IAAI;AAAA,MACJ,SAAS;AAAA;AAAA,MACT,UAAU;AAAA,IACZ;AAEA,SAAK,gBAAgB,kBAAkB,SAAS;AAAA,EAClD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,OAAO,SAAmC;AACxC,UAAM,MAAM,KAAK,IAAI;AACrB,UAAM,KAAK,WAAW,MAAM,KAAK;AACjC,SAAK,iBAAiB;AAEtB,UAAM,YAAY,KAAK;AAGvB,QAAI,KAAK,iBAAiB;AACxB,WAAK,iBAAiB,SAAS;AAAA,IACjC;AAGA,SAAK,aAAa,GAAG;AAGrB,SAAK,mBAAmB,SAAS;AAGjC,SAAK,cAAc,SAAS;AAG5B,SAAK,eAAe,KAAK,cAAc;AACvC,SAAK,KAAK,iBAAiB,KAAK,YAAY;AAE5C,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,QAAc;AACZ,UAAM,eAAe,KAAK,OAAO,OAAO;AAAA,MACtC,CAAC,MAAM,EAAE,SAAS,KAAK,OAAO;AAAA,IAChC;AACA,QAAI,cAAc;AAChB,WAAK,eAAe;AACpB,WAAK,gBAAgB;AACrB,WAAK,kBAAkB;AACvB,WAAK,qBAAqB;AAC1B,WAAK,iBAAiB,KAAK,IAAI;AAC/B,WAAK,qBAAqB;AAC1B,WAAK,gBAAgB;AACrB,WAAK,eAAe,KAAK,cAAc;AAAA,IACzC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,mBAA6B;AAC3B,UAAM,QAAQ,oBAAI,IAAY;AAG9B,eAAW,SAAS,KAAK,OAAO,QAAQ;AACtC,iBAAW,QAAQ,MAAM,WAAW;AAClC,cAAM,IAAI,IAAI;AAAA,MAChB;AAAA,IACF;AAGA,eAAW,WAAW,KAAK,OAAO,iBAAiB;AACjD,YAAM,IAAI,QAAQ,IAAI;AAAA,IACxB;AAGA,eAAW,QAAQ,KAAK,OAAO,cAAc;AAC3C,YAAM,IAAI,IAAI;AAAA,IAChB;AAEA,WAAO,MAAM,KAAK,KAAK;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA,EAMQ,gBAAgB,YAAwB,OAA+B;AAC7E,UAAM,cAAc,KAAK,OAAO,OAAO;AAAA,MACrC,CAAC,MAAM,EAAE,SAAS,WAAW;AAAA,IAC/B;AACA,QAAI,CAAC,aAAa;AAChB,cAAQ,KAAK,kCAAkC,WAAW,EAAE,aAAa;AACzE;AAAA,IACF;AAEA,UAAM,YAAY,KAAK,aAAa;AAEpC,SAAK,gBAAgB,KAAK;AAC1B,SAAK,eAAe;AACpB,SAAK,kBAAkB;AACvB,SAAK,qBAAqB;AAC1B,SAAK,qBAAqB,WAAW;AACrC,SAAK,sBAAsB,KAAK,IAAI;AACpC,SAAK,iBAAiB,KAAK,IAAI;AAG/B,QAAI,CAAC,KAAK,aAAa,qBAAqB;AAC1C,WAAK,sBAAsB;AAAA,IAC7B;AAEA,SAAK,KAAK,gBAAgB;AAAA,MACxB,MAAM;AAAA,MACN,IAAI,YAAY;AAAA,MAChB,SAAS;AAAA,IACX,CAAC;AAED,SAAK,KAAK,oBAAoB;AAAA,MAC5B,MAAM;AAAA,MACN,IAAI,YAAY;AAAA,MAChB,UAAU,WAAW;AAAA,IACvB,CAAC;AAAA,EACH;AAAA,EAEQ,iBAAiB,WAAyB;AAChD,QAAI,CAAC,KAAK,mBAAmB,KAAK,sBAAsB,GAAG;AACzD,WAAK,kBAAkB;AACvB,WAAK,qBAAqB;AAC1B;AAAA,IACF;AAGA,UAAM,UAAU,KAAK,IAAI,IAAI,KAAK;AAClC,SAAK,qBAAqB,KAAK,IAAI,GAAG,UAAU,KAAK,kBAAkB;AAEvE,QAAI,KAAK,sBAAsB,GAAG;AAChC,WAAK,kBAAkB;AACvB,WAAK,qBAAqB;AAC1B,WAAK,gBAAgB;AACrB,WAAK,KAAK,kBAAkB,EAAE,OAAO,KAAK,aAAa,KAAK,CAAC;AAAA,IAC/D;AAAA,EACF;AAAA,EAEQ,aAAa,KAAmB;AACtC,QAAI,KAAK,gBAAiB;AAC1B,QAAI,KAAK,aAAa,WAAW,EAAG;AAEpC,UAAM,UAAU,MAAM,KAAK;AAC3B,QAAI,WAAW,KAAK,aAAa,SAAS;AACxC,WAAK,QAAQ,SAAS;AAAA,IACxB;AAAA,EACF;AAAA,EAEQ,mBAAmB,WAAyB;AAClD,QAAI,CAAC,KAAK,gBAAgB;AAExB,WAAK,qBAAqB,KAAK;AAAA,QAC7B;AAAA,QACA,KAAK,qBAAqB,YAAY;AAAA,MACxC;AACA;AAAA,IACF;AAEA,UAAM,UAAU,KAAK,OAAO,gBAAgB;AAAA,MAC1C,CAAC,MAAM,EAAE,YAAY,KAAK;AAAA,IAC5B;AACA,UAAM,aAAa,SAAS,cAAc;AAG1C,UAAM,OAAO,KAAK,sBAAsB,KAAK;AAC7C,UAAM,YAAY,aAAa;AAE/B,QAAI,KAAK,IAAI,IAAI,KAAK,WAAW;AAC/B,WAAK,qBAAqB,KAAK;AAAA,IACjC,OAAO;AACL,WAAK,sBAAsB,KAAK,KAAK,IAAI,IAAI;AAAA,IAC/C;AAAA,EACF;AAAA,EAEQ,cAAc,WAAyB;AAC7C,QAAI,CAAC,KAAK,aAAa,qBAAqB;AAC1C,WAAK,gBAAgB,KAAK,IAAI,GAAG,KAAK,gBAAgB,YAAY,CAAG;AACrE;AAAA,IACF;AAGA,UAAM,gBACJ,KAAK,cAAc,KAAK,OAAO,mBAC3B,KAAK,cAAc,KAAK,OAAO,mBAC/B;AAGN,UAAM,OAAO,gBAAgB,KAAK;AAClC,UAAM,aAAa;AACnB,UAAM,YAAY,aAAa;AAE/B,QAAI,KAAK,IAAI,IAAI,KAAK,WAAW;AAC/B,WAAK,gBAAgB;AAAA,IACvB,OAAO;AACL,WAAK,iBAAiB,KAAK,KAAK,IAAI,IAAI;AAAA,IAC1C;AAGA,UAAM,YAAY,KAAK,OAAO,aAAa;AAC3C,QAAI,YAAY,GAAG;AACjB,WAAK,qBAAqB,KAAK;AAAA,QAC7B,YAAY;AAAA,QACZ,KAAK,MAAM,KAAK,gBAAgB,SAAS;AAAA,MAC3C;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,gBAAiC;AACvC,UAAM,eAA8B,CAAC;AAGrC,UAAM,IAAI,KAAK;AACf,UAAM,mBAAmB,IAAI,KAAK,IAAI,IAAI;AAG1C,QAAI,KAAK,iBAAiB,KAAK,iBAAiB;AAC9C,YAAM,UAAU,IAAI;AACpB,eAAS,IAAI,GAAG,IAAI,KAAK,cAAc,UAAU,QAAQ,KAAK;AAC5D,cAAM,OAAO,KAAK,cAAc,UAAU,CAAC;AAC3C,cAAM,aAAa,KAAK,cAAc,YAAY,CAAC,KAAK;AACxD,qBAAa,KAAK;AAAA,UAChB;AAAA,UACA,QAAQ,aAAa;AAAA,UACrB,OAAO;AAAA,UACP,MAAM;AAAA,QACR,CAAC;AAAA,MACH;AAAA,IACF;AAGA,UAAM,SAAS,KAAK,kBAAkB,mBAAmB;AACzD,aAAS,IAAI,GAAG,IAAI,KAAK,aAAa,UAAU,QAAQ,KAAK;AAC3D,YAAM,OAAO,KAAK,aAAa,UAAU,CAAC;AAC1C,YAAM,aAAa,KAAK,aAAa,YAAY,CAAC,KAAK;AACvD,mBAAa,KAAK;AAAA,QAChB;AAAA,QACA,QAAQ,aAAa;AAAA,QACrB,OAAO;AAAA,QACP,MAAM;AAAA,MACR,CAAC;AAAA,IACH;AAGA,QAAI,KAAK,kBAAkB,KAAK,qBAAqB,MAAM;AACzD,YAAM,UAAU,KAAK,OAAO,gBAAgB;AAAA,QAC1C,CAAC,MAAM,EAAE,YAAY,KAAK;AAAA,MAC5B;AACA,UAAI,SAAS;AACX,qBAAa,KAAK;AAAA,UAChB,MAAM,QAAQ;AAAA,UACd,QAAQ,KAAK;AAAA,UACb,OAAO;AAAA,UACP,MAAM;AAAA,QACR,CAAC;AAAA,MACH;AAAA,IACF;AAGA,QAAI,KAAK,gBAAgB,QAAQ,KAAK,OAAO,aAAa,SAAS,GAAG;AACpE,YAAM,cAAc,KAAK,OAAO,aAAa,KAAK,kBAAkB;AACpE,mBAAa,KAAK;AAAA,QAChB,MAAM;AAAA,QACN,QAAQ,KAAK;AAAA,QACb,OAAO,IAAM,KAAK,cAAc;AAAA;AAAA,QAChC,MAAM;AAAA,MACR,CAAC;AAAA,IACH;AAEA,WAAO;AAAA,MACL,OAAO,KAAK,aAAa;AAAA,MACzB;AAAA,MACA,eAAe,KAAK,qBAAqB,OAAO,KAAK,iBAAiB;AAAA,MACtE,kBAAkB,KAAK;AAAA,MACvB,iBAAiB,KAAK;AAAA,MACtB,oBAAoB,KAAK;AAAA,IAC3B;AAAA,EACF;AACF;;;ACrdA,IAAM,OAAO,IAAI,WAAW,GAAG;AAC/B,IAAM,QAAQ;AAAA,EACZ,CAAC,GAAG,CAAC;AAAA,EAAG,CAAC,IAAI,CAAC;AAAA,EAAG,CAAC,GAAG,EAAE;AAAA,EAAG,CAAC,IAAI,EAAE;AAAA,EACjC,CAAC,GAAG,CAAC;AAAA,EAAG,CAAC,IAAI,CAAC;AAAA,EAAG,CAAC,GAAG,CAAC;AAAA,EAAG,CAAC,GAAG,EAAE;AACjC;AAGA,IAAM,IAAI;AAAA,EACR;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAI;AAAA,EAAI;AAAA,EAAI;AAAA,EAAK;AAAA,EAAI;AAAA,EAAK;AAAA,EAAI;AAAA,EAAI;AAAA,EAAI;AAAA,EAAK;AAAA,EAAK;AAAA,EAAG;AAAA,EAClE;AAAA,EAAK;AAAA,EAAI;AAAA,EAAK;AAAA,EAAI;AAAA,EAAI;AAAA,EAAK;AAAA,EAAG;AAAA,EAAI;AAAA,EAAI;AAAA,EAAK;AAAA,EAAI;AAAA,EAAI;AAAA,EAAI;AAAA,EAAK;AAAA,EAAG;AAAA,EAC/D;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAI;AAAA,EAAG;AAAA,EAAI;AAAA,EAAK;AAAA,EAAI;AAAA,EAAI;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAI;AAAA,EAAI;AAAA,EACnE;AAAA,EAAI;AAAA,EAAK;AAAA,EAAI;AAAA,EAAI;AAAA,EAAK;AAAA,EAAK;AAAA,EAAI;AAAA,EAAI;AAAA,EAAK;AAAA,EAAI;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAI;AAAA,EACpE;AAAA,EAAI;AAAA,EAAK;AAAA,EAAI;AAAA,EAAK;AAAA,EAAK;AAAA,EAAI;AAAA,EAAI;AAAA,EAAK;AAAA,EAAI;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAI;AAAA,EAAK;AAAA,EAAK;AAAA,EACrE;AAAA,EAAI;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAI;AAAA,EAAI;AAAA,EAAI;AAAA,EAAI;AAAA,EAAK;AAAA,EAAI;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EACrE;AAAA,EAAI;AAAA,EAAI;AAAA,EAAI;AAAA,EAAK;AAAA,EAAG;AAAA,EAAK;AAAA,EAAI;AAAA,EAAI;AAAA,EAAK;AAAA,EAAI;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAI;AAAA,EAAI;AAAA,EACjE;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAI;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAG;AAAA,EACxE;AAAA,EAAI;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAG;AAAA,EAAK;AAAA,EAAI;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAI;AAAA,EAAI;AAAA,EACrE;AAAA,EAAK;AAAA,EAAK;AAAA,EAAI;AAAA,EAAK;AAAA,EAAI;AAAA,EAAI;AAAA,EAAI;AAAA,EAAI;AAAA,EAAK;AAAA,EAAK;AAAA,EAAI;AAAA,EAAI;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EACpE;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAG;AAAA,EAAI;AAAA,EAAK;AAAA,EAAK;AAAA,EAAI;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAI;AAAA,EAAK;AAAA,EACtE;AAAA,EAAK;AAAA,EAAI;AAAA,EAAI;AAAA,EAAK;AAAA,EAAI;AAAA,EAAI;AAAA,EAAK;AAAA,EAAK;AAAA,EAAI;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EACtE;AAAA,EAAK;AAAA,EAAK;AAAA,EAAI;AAAA,EAAK;AAAA,EAAK;AAAA,EAAI;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAI;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK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","names":["data","module","isBrowser","module","logger","module","options","session","logger","logger","window","logger","logger","WASM_CDN_PATH","logger","WASM_CDN_PATH","resolveUrl","WORKER_SCRIPT","logger","logger","logger","WASM_CDN_PATH","LOAD_TIMEOUT_MS","INFERENCE_TIMEOUT_MS","resolveUrl","WORKER_SCRIPT","logger","logger","logger","WASM_CDN_PATH","LOAD_TIMEOUT_MS","INFERENCE_TIMEOUT_MS","resolveUrl","WORKER_SCRIPT","logger","logger","sum","t","eased"]}