Use local faster-whisper only for transcription.

Drop cloud and Ollama engine choices so audio stays on-machine via Docker Whisper, and tighten orphan detection plus UI around a single local flow.
This commit is contained in:
ben
2026-08-12 15:34:16 +02:00
parent bc6cb5efa4
commit 21b17c7657
10 changed files with 117 additions and 295 deletions
+20 -63
View File
@@ -7,7 +7,6 @@
x-data="transcriptionMonitor(@js([
'statusUrl' => route('recordings.transcription-status', $recording),
'initial' => $recording->transcriptionStatusPayload(),
'driver' => old('driver', $recording->transcription_driver ?: 'cloud'),
]))"
x-init="start()"
>
@@ -67,68 +66,25 @@
<dt class="text-stone-500">Uploaded</dt>
<dd class="font-medium">{{ $recording->created_at?->format('Y-m-d H:i') }}</dd>
</div>
@if ($recording->ollama_url)
<div class="flex justify-between gap-4">
<dt class="text-stone-500">Ollama host</dt>
<dd class="max-w-[60%] break-all text-right font-medium">{{ $recording->ollama_url }}</dd>
</div>
@endif
</dl>
</section>
<section class="rounded border border-stone-200 bg-white p-6 shadow-sm">
<h2 class="text-sm font-semibold uppercase tracking-wide text-stone-500">Transcribe</h2>
<p class="mt-2 text-sm text-stone-600">Choose an engine anytime starting a new run stops the current one.</p>
<p class="mt-2 text-sm text-stone-600">
Audio stays on this machine. Requires
<code class="text-[11px]">docker compose up -d whisper</code>
(port 8090).
</p>
<form method="POST" action="{{ route('recordings.transcribe', $recording) }}" class="mt-4 space-y-4">
<form method="POST" action="{{ route('recordings.transcribe', $recording) }}" class="mt-4">
@csrf
<label class="flex cursor-pointer gap-3 rounded border border-stone-200 p-3 hover:bg-stone-50">
<input type="radio" name="driver" value="cloud" x-model="driver" class="mt-1 text-teal-700 focus:ring-teal-600">
<span>
<span class="block text-sm font-medium">Cloud (OpenAI Whisper)</span>
<span class="block text-xs text-stone-500">Fast; audio is sent to OpenAI.</span>
</span>
</label>
<label class="flex cursor-pointer gap-3 rounded border border-stone-200 p-3 hover:bg-stone-50">
<input type="radio" name="driver" value="local" x-model="driver" class="mt-1 text-teal-700 focus:ring-teal-600">
<span>
<span class="block text-sm font-medium">Local confidential (faster-whisper)</span>
<span class="block text-xs text-stone-500">Runs via Docker Compose (<code class="text-[11px]">docker compose up -d whisper</code>) on port 8090.</span>
</span>
</label>
<label class="flex cursor-pointer gap-3 rounded border border-stone-200 p-3 hover:bg-stone-50">
<input type="radio" name="driver" value="ollama" x-model="driver" class="mt-1 text-teal-700 focus:ring-teal-600">
<span>
<span class="block text-sm font-medium">Ollama host</span>
<span class="block text-xs text-stone-500">
Remote host URL. Must expose OpenAI-compatible <code class="text-[11px]">/v1/audio/transcriptions</code>.
</span>
</span>
</label>
<div x-show="driver === 'ollama'" x-cloak class="space-y-2">
<label for="ollama_url" class="block text-sm font-medium text-stone-700">Host URL</label>
<input
id="ollama_url"
type="url"
name="ollama_url"
value="{{ old('ollama_url', $recording->ollama_url) }}"
placeholder="http://192.168.1.50:8000"
class="w-full rounded border border-stone-300 px-3 py-2 text-sm shadow-sm focus:border-teal-600 focus:outline-none focus:ring-1 focus:ring-teal-600"
>
</div>
<div class="flex flex-wrap items-center gap-3">
<button
type="submit"
class="rounded bg-teal-700 px-4 py-2 text-sm font-medium text-white hover:bg-teal-800"
>
<span x-text="startButtonLabel"></span>
</button>
</div>
<button
type="submit"
class="rounded bg-teal-700 px-4 py-2 text-sm font-medium text-white hover:bg-teal-800"
>
<span x-text="startButtonLabel"></span>
</button>
</form>
<form
@@ -227,11 +183,10 @@
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
<style>[x-cloak]{display:none!important}</style>
<script>
function transcriptionMonitor({ statusUrl, initial, driver }) {
function transcriptionMonitor({ statusUrl, initial }) {
return {
statusUrl,
status: initial,
driver: driver || 'cloud',
pollError: null,
timer: null,
tickTimer: null,
@@ -250,7 +205,7 @@
get startButtonLabel() {
if (this.status.is_active) {
return 'Switch engine / restart';
return 'Restart transcription';
}
return this.status.has_transcript ? 'Re-transcribe' : 'Start transcription';
@@ -313,15 +268,17 @@
},
formatElapsed(seconds) {
const minutes = Math.floor(seconds / 60);
const remain = seconds % 60;
const total = Math.max(0, Math.round(Number(seconds) || 0));
const minutes = Math.floor(total / 60);
const remain = total % 60;
if (minutes === 0) return remain + 's';
return minutes + 'm ' + String(remain).padStart(2, '0') + 's';
},
formatDuration(seconds) {
const minutes = Math.floor(seconds / 60);
const remain = seconds % 60;
const total = Math.max(0, Math.round(Number(seconds) || 0));
const minutes = Math.floor(total / 60);
const remain = total % 60;
return minutes + ':' + String(remain).padStart(2, '0');
},