transcription_driver) { 'cloud' => $this->viaCloud($recording), 'local' => $this->viaLocal($recording), 'ollama' => $this->viaRemoteCompatible($recording), default => throw new RuntimeException('Unknown transcription driver: '.$recording->transcription_driver), }; } private function viaCloud(Recording $recording): string { $transcript = Transcription::fromStorage($recording->file_path) ->timeout((int) config('ai.transcription_timeout', 600)) ->generate('openai', 'whisper-1'); return (string) $transcript; } private function viaLocal(Recording $recording): string { $model = config('ai.local_whisper_model', 'Systran/faster-whisper-base'); $transcript = Transcription::fromStorage($recording->file_path) ->timeout((int) config('ai.transcription_timeout', 600)) ->generate('local-whisper', $model); return (string) $transcript; } /** * Call an OpenAI-compatible /v1/audio/transcriptions endpoint at a user-supplied host URL. */ private function viaRemoteCompatible(Recording $recording): string { if (! filled($recording->ollama_url)) { throw new RuntimeException('Ollama host URL is required for remote transcription.'); } $base = $this->normalizeBaseUrl($recording->ollama_url); $model = config('ai.remote_whisper_model', config('ai.local_whisper_model', 'Systran/faster-whisper-base')); $path = $recording->absolutePath(); if (! is_readable($path)) { throw new RuntimeException('Recording audio file is not readable.'); } $response = Http::timeout((int) config('ai.transcription_timeout', 600)) ->attach( 'file', fopen($path, 'r'), $recording->original_filename ?: basename($path), ) ->post($base.'/audio/transcriptions', [ 'model' => $model, 'response_format' => 'json', ]); if (! $response->successful()) { throw new RuntimeException( 'Remote transcription failed (HTTP '.$response->status().'): '.$response->body() ); } $text = $response->json('text'); if (! is_string($text) || $text === '') { throw new RuntimeException('Remote transcription returned an empty transcript. Ensure the host exposes OpenAI-compatible /v1/audio/transcriptions.'); } return $text; } /** * Normalize a user URL to an OpenAI-style base ending in /v1. */ private function normalizeBaseUrl(string $url): string { $url = rtrim(trim($url), '/'); if (Str::endsWith($url, '/v1')) { return $url; } return $url.'/v1'; } }