From f820d9a8948b97460ebdfdaa6b42b6d510e2b3cf Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Sat, 31 Jan 2026 22:19:11 +0100 Subject: refactor services structure --- apps/web/src/services/transcription/index.ts | 186 ------------------------- apps/web/src/services/transcription/service.ts | 186 +++++++++++++++++++++++++ 2 files changed, 186 insertions(+), 186 deletions(-) delete mode 100644 apps/web/src/services/transcription/index.ts create mode 100644 apps/web/src/services/transcription/service.ts (limited to 'apps/web/src/services/transcription') diff --git a/apps/web/src/services/transcription/index.ts b/apps/web/src/services/transcription/index.ts deleted file mode 100644 index 37c650da..00000000 --- a/apps/web/src/services/transcription/index.ts +++ /dev/null @@ -1,186 +0,0 @@ -import type { - TranscriptionResult, - TranscriptionProgress, - TranscriptionModelId, -} from "@/types/transcription"; -import { - DEFAULT_TRANSCRIPTION_MODEL, - TRANSCRIPTION_MODELS, -} from "@/constants/transcription-constants"; -import type { WorkerMessage, WorkerResponse } from "./worker"; -import type { LanguageCode } from "@/types/language"; - -type ProgressCallback = (progress: TranscriptionProgress) => void; - -class TranscriptionService { - private worker: Worker | null = null; - private currentModelId: TranscriptionModelId | null = null; - private isInitialized = false; - private isInitializing = false; - - async transcribe({ - audioData, - language, - modelId = DEFAULT_TRANSCRIPTION_MODEL, - onProgress, - }: { - audioData: Float32Array; - language?: LanguageCode; - modelId?: TranscriptionModelId; - onProgress?: ProgressCallback; - }): Promise { - await this.ensureWorker({ modelId, onProgress }); - - return new Promise((resolve, reject) => { - if (!this.worker) { - reject(new Error("Worker not initialized")); - return; - } - - const handleMessage = (event: MessageEvent) => { - const response = event.data; - - switch (response.type) { - case "transcribe-progress": - onProgress?.({ - status: "transcribing", - progress: response.progress, - message: "Transcribing audio...", - }); - break; - - case "transcribe-complete": - this.worker?.removeEventListener("message", handleMessage); - resolve({ - text: response.text, - segments: response.segments, - language, - }); - break; - - case "transcribe-error": - this.worker?.removeEventListener("message", handleMessage); - reject(new Error(response.error)); - break; - - case "cancelled": - this.worker?.removeEventListener("message", handleMessage); - reject(new Error("Transcription cancelled")); - break; - } - }; - - this.worker.addEventListener("message", handleMessage); - - this.worker.postMessage({ - type: "transcribe", - audio: audioData, - language, - } satisfies WorkerMessage); - }); - } - - cancel() { - this.worker?.postMessage({ type: "cancel" } satisfies WorkerMessage); - } - - private async ensureWorker({ - modelId, - onProgress, - }: { - modelId: TranscriptionModelId; - onProgress?: ProgressCallback; - }): Promise { - const needsNewModel = this.currentModelId !== modelId; - - if (this.worker && this.isInitialized && !needsNewModel) { - return; - } - - if (this.isInitializing && !needsNewModel) { - await this.waitForInit(); - return; - } - - this.terminate(); - this.isInitializing = true; - this.isInitialized = false; - - const model = TRANSCRIPTION_MODELS.find((m) => m.id === modelId); - if (!model) { - throw new Error(`Unknown model: ${modelId}`); - } - - this.worker = new Worker(new URL("./worker.ts", import.meta.url), { - type: "module", - }); - - return new Promise((resolve, reject) => { - if (!this.worker) { - reject(new Error("Failed to create worker")); - return; - } - - const handleMessage = (event: MessageEvent) => { - const response = event.data; - - switch (response.type) { - case "init-progress": - onProgress?.({ - status: "loading-model", - progress: response.progress, - message: `Loading ${model.name} model...`, - }); - break; - - case "init-complete": - this.worker?.removeEventListener("message", handleMessage); - this.isInitialized = true; - this.isInitializing = false; - this.currentModelId = modelId; - resolve(); - break; - - case "init-error": - this.worker?.removeEventListener("message", handleMessage); - this.isInitializing = false; - this.terminate(); - reject(new Error(response.error)); - break; - } - }; - - this.worker.addEventListener("message", handleMessage); - - this.worker.postMessage({ - type: "init", - modelId: model.huggingFaceId, - } satisfies WorkerMessage); - }); - } - - private waitForInit(): Promise { - return new Promise((resolve) => { - const checkInit = () => { - if (this.isInitialized) { - resolve(); - } else if (!this.isInitializing) { - resolve(); - } else { - setTimeout(checkInit, 100); - } - }; - checkInit(); - }); - } - - terminate() { - this.worker?.terminate(); - this.worker = null; - this.isInitialized = false; - this.isInitializing = false; - this.currentModelId = null; - } -} - -export const transcriptionService = new TranscriptionService(); diff --git a/apps/web/src/services/transcription/service.ts b/apps/web/src/services/transcription/service.ts new file mode 100644 index 00000000..c34a82ce --- /dev/null +++ b/apps/web/src/services/transcription/service.ts @@ -0,0 +1,186 @@ +import type { + TranscriptionLanguage, + TranscriptionResult, + TranscriptionProgress, + TranscriptionModelId, +} from "@/types/transcription"; +import { + DEFAULT_TRANSCRIPTION_MODEL, + TRANSCRIPTION_MODELS, +} from "@/constants/transcription-constants"; +import type { WorkerMessage, WorkerResponse } from "./worker"; + +type ProgressCallback = (progress: TranscriptionProgress) => void; + +class TranscriptionService { + private worker: Worker | null = null; + private currentModelId: TranscriptionModelId | null = null; + private isInitialized = false; + private isInitializing = false; + + async transcribe({ + audioData, + language = "auto", + modelId = DEFAULT_TRANSCRIPTION_MODEL, + onProgress, + }: { + audioData: Float32Array; + language?: TranscriptionLanguage; + modelId?: TranscriptionModelId; + onProgress?: ProgressCallback; + }): Promise { + await this.ensureWorker({ modelId, onProgress }); + + return new Promise((resolve, reject) => { + if (!this.worker) { + reject(new Error("Worker not initialized")); + return; + } + + const handleMessage = (event: MessageEvent) => { + const response = event.data; + + switch (response.type) { + case "transcribe-progress": + onProgress?.({ + status: "transcribing", + progress: response.progress, + message: "Transcribing audio...", + }); + break; + + case "transcribe-complete": + this.worker?.removeEventListener("message", handleMessage); + resolve({ + text: response.text, + segments: response.segments, + language, + }); + break; + + case "transcribe-error": + this.worker?.removeEventListener("message", handleMessage); + reject(new Error(response.error)); + break; + + case "cancelled": + this.worker?.removeEventListener("message", handleMessage); + reject(new Error("Transcription cancelled")); + break; + } + }; + + this.worker.addEventListener("message", handleMessage); + + this.worker.postMessage({ + type: "transcribe", + audio: audioData, + language, + } satisfies WorkerMessage); + }); + } + + cancel() { + this.worker?.postMessage({ type: "cancel" } satisfies WorkerMessage); + } + + private async ensureWorker({ + modelId, + onProgress, + }: { + modelId: TranscriptionModelId; + onProgress?: ProgressCallback; + }): Promise { + const needsNewModel = this.currentModelId !== modelId; + + if (this.worker && this.isInitialized && !needsNewModel) { + return; + } + + if (this.isInitializing && !needsNewModel) { + await this.waitForInit(); + return; + } + + this.terminate(); + this.isInitializing = true; + this.isInitialized = false; + + const model = TRANSCRIPTION_MODELS.find((m) => m.id === modelId); + if (!model) { + throw new Error(`Unknown model: ${modelId}`); + } + + this.worker = new Worker(new URL("./worker.ts", import.meta.url), { + type: "module", + }); + + return new Promise((resolve, reject) => { + if (!this.worker) { + reject(new Error("Failed to create worker")); + return; + } + + const handleMessage = (event: MessageEvent) => { + const response = event.data; + + switch (response.type) { + case "init-progress": + onProgress?.({ + status: "loading-model", + progress: response.progress, + message: `Loading ${model.name} model...`, + }); + break; + + case "init-complete": + this.worker?.removeEventListener("message", handleMessage); + this.isInitialized = true; + this.isInitializing = false; + this.currentModelId = modelId; + resolve(); + break; + + case "init-error": + this.worker?.removeEventListener("message", handleMessage); + this.isInitializing = false; + this.terminate(); + reject(new Error(response.error)); + break; + } + }; + + this.worker.addEventListener("message", handleMessage); + + this.worker.postMessage({ + type: "init", + modelId: model.huggingFaceId, + } satisfies WorkerMessage); + }); + } + + private waitForInit(): Promise { + return new Promise((resolve) => { + const checkInit = () => { + if (this.isInitialized) { + resolve(); + } else if (!this.isInitializing) { + resolve(); + } else { + setTimeout(checkInit, 100); + } + }; + checkInit(); + }); + } + + terminate() { + this.worker?.terminate(); + this.worker = null; + this.isInitialized = false; + this.isInitializing = false; + this.currentModelId = null; + } +} + +export const transcriptionService = new TranscriptionService(); -- cgit v1.3.1