import { ModelServiceConfig, ModelLoadingStatus, ModelProgressEvent, BackendType, TranscriptionResult } from './types'; // Use dynamic import for parakeet.js since it's an ESM module let ParakeetModel: any = null; /** * ModelService handles model loading, backend detection, and transcription. */ export class ModelService { private config: ModelServiceConfig; private model: any = null; private status: ModelLoadingStatus = 'idle'; private backend: BackendType | null = null; constructor(config: ModelServiceConfig = {}) { this.config = config; } /** * Detect available backend (WebGPU preferred, WASM fallback) */ async detectBackend(): Promise { if (this.config.preferredBackend) { return this.config.preferredBackend; } // Check for WebGPU support if (typeof navigator !== 'undefined' && 'gpu' in navigator) { try { const adapter = await (navigator as any).gpu.requestAdapter(); if (adapter) { return 'webgpu'; } } catch { // WebGPU not available } } return 'wasm'; } /** * Load the parakeet.js model */ async loadModel(): Promise { if (this.model) return; this.status = 'loading'; this.notifyProgress({ status: 'loading', progress: 0, message: 'Detecting backend...' }); try { // Detect backend this.backend = await this.detectBackend(); this.notifyProgress({ status: 'loading', progress: 10, message: `Using ${this.backend.toUpperCase()} backend` }); // Dynamic import of parakeet.js const parakeetModule = await import('parakeet.js'); ParakeetModel = parakeetModule.ParakeetModel; this.notifyProgress({ status: 'downloading', progress: 20, message: 'Downloading model...' }); // Create model instance with progress callback this.model = await ParakeetModel.fromPretrained( this.config.modelId || 'nvidia/parakeet-tdt-0.6b-v2', { device: this.backend, progress_callback: (info: any) => { if (info.status === 'progress') { const progress = 20 + (info.progress || 0) * 0.7; // 20-90% this.notifyProgress({ status: 'downloading', progress, bytesLoaded: info.loaded, bytesTotal: info.total, message: `Downloading: ${Math.round(progress)}%`, }); } }, } ); this.status = 'ready'; this.notifyProgress({ status: 'ready', progress: 100, message: 'Model ready' }); } catch (error) { this.status = 'error'; this.notifyProgress({ status: 'error', progress: 0, message: error instanceof Error ? error.message : 'Failed to load model', }); throw error; } } /** * Transcribe audio */ async transcribe(audio: Float32Array, sampleRate: number = 16000): Promise { if (!this.model) { throw new Error('Model not loaded. Call loadModel() first.'); } const result = await this.model.transcribe(audio, sampleRate, { returnTimestamps: true, returnConfidences: true, }); return { text: result.utterance_text || result.text || '', words: result.words, }; } /** * Get current status */ getStatus(): ModelLoadingStatus { return this.status; } /** * Get detected backend */ getBackend(): BackendType | null { return this.backend; } /** * Check if model is ready */ isReady(): boolean { return this.status === 'ready' && this.model !== null; } /** * Get raw model reference for advanced usage */ getModel(): any { return this.model; } private notifyProgress(event: ModelProgressEvent): void { if (this.config.onProgress) { this.config.onProgress(event); } } }