158 lines
8.5 KiB
PHP
158 lines
8.5 KiB
PHP
@extends('layouts.app')
|
|
|
|
@section('title', $recording->title)
|
|
|
|
@section('content')
|
|
<div class="mb-6 flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
|
|
<div>
|
|
<a href="{{ route('recordings.index') }}" class="text-sm text-stone-500 hover:text-stone-800">← Recordings</a>
|
|
<h1 class="mt-2 text-2xl font-semibold tracking-tight">{{ $recording->title }}</h1>
|
|
<div class="mt-2 flex flex-wrap items-center gap-2 text-sm text-stone-600">
|
|
@include('recordings.partials.status-badge', ['status' => $recording->transcription_status])
|
|
@if ($recording->transcription_driver)
|
|
<span class="text-xs uppercase tracking-wide text-stone-500">{{ $recording->transcription_driver }}</span>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
<form method="POST" action="{{ route('recordings.destroy', $recording) }}" onsubmit="return confirm('Delete this recording and its file?')">
|
|
@csrf
|
|
@method('DELETE')
|
|
<button type="submit" class="rounded border border-red-200 bg-white px-3 py-1.5 text-sm text-red-700 hover:bg-red-50">
|
|
Delete
|
|
</button>
|
|
</form>
|
|
</div>
|
|
|
|
<div class="grid gap-6 lg:grid-cols-2">
|
|
<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">Metadata</h2>
|
|
<dl class="mt-4 space-y-3 text-sm">
|
|
<div class="flex justify-between gap-4">
|
|
<dt class="text-stone-500">Original file</dt>
|
|
<dd class="text-right font-medium">{{ $recording->original_filename }}</dd>
|
|
</div>
|
|
<div class="flex justify-between gap-4">
|
|
<dt class="text-stone-500">Duration</dt>
|
|
<dd class="font-medium">{{ $recording->duration_formatted }}</dd>
|
|
</div>
|
|
<div class="flex justify-between gap-4">
|
|
<dt class="text-stone-500">Artist</dt>
|
|
<dd class="font-medium">{{ $recording->artist ?: '—' }}</dd>
|
|
</div>
|
|
<div class="flex justify-between gap-4">
|
|
<dt class="text-stone-500">Album</dt>
|
|
<dd class="font-medium">{{ $recording->album ?: '—' }}</dd>
|
|
</div>
|
|
<div class="flex justify-between gap-4">
|
|
<dt class="text-stone-500">Recorded</dt>
|
|
<dd class="font-medium">{{ $recording->recorded_at?->format('Y-m-d') ?: '—' }}</dd>
|
|
</div>
|
|
<div class="flex justify-between gap-4">
|
|
<dt class="text-stone-500">Size</dt>
|
|
<dd class="font-medium">{{ number_format($recording->file_size_bytes / 1024, 1) }} KB</dd>
|
|
</div>
|
|
<div class="flex justify-between gap-4">
|
|
<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
|
|
x-data="{ driver: '{{ old('driver', $recording->transcription_driver ?: 'cloud') }}' }"
|
|
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 how to convert this MP3 to text.</p>
|
|
|
|
<form method="POST" action="{{ route('recordings.transcribe', $recording) }}" class="mt-4 space-y-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">Requires faster-whisper-server on this machine (Docker).</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>
|
|
(e.g. Whisper beside Ollama).
|
|
</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>
|
|
|
|
<button
|
|
type="submit"
|
|
@disabled($recording->transcription_status === 'processing')
|
|
class="rounded bg-teal-700 px-4 py-2 text-sm font-medium text-white hover:bg-teal-800 disabled:cursor-not-allowed disabled:opacity-50"
|
|
>
|
|
{{ $recording->transcript ? 'Re-transcribe' : 'Start transcription' }}
|
|
</button>
|
|
</form>
|
|
</section>
|
|
</div>
|
|
|
|
<section class="mt-6 rounded border border-stone-200 bg-white p-6 shadow-sm">
|
|
<div class="flex items-center justify-between gap-4">
|
|
<h2 class="text-sm font-semibold uppercase tracking-wide text-stone-500">Transcript</h2>
|
|
@if ($recording->transcript)
|
|
<button
|
|
type="button"
|
|
onclick="navigator.clipboard.writeText(@js($recording->transcript))"
|
|
class="text-sm text-teal-700 hover:underline"
|
|
>
|
|
Copy
|
|
</button>
|
|
@endif
|
|
</div>
|
|
|
|
@if ($recording->transcription_status === 'processing')
|
|
<p class="mt-4 text-sm text-amber-700">Transcription in progress… Refresh shortly.</p>
|
|
@elseif ($recording->transcription_status === 'failed')
|
|
<p class="mt-4 text-sm text-red-700">Transcription failed. Check the logs and try again with another engine.</p>
|
|
@elseif ($recording->transcript)
|
|
<p class="mt-4 whitespace-pre-wrap text-sm leading-relaxed text-stone-800">{{ $recording->transcript }}</p>
|
|
@if ($recording->transcribed_at)
|
|
<p class="mt-4 text-xs text-stone-500">Transcribed {{ $recording->transcribed_at->format('Y-m-d H:i') }}</p>
|
|
@endif
|
|
@else
|
|
<p class="mt-4 text-sm text-stone-500">No transcript yet. Choose an engine above to start.</p>
|
|
@endif
|
|
</section>
|
|
|
|
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
|
|
<style>[x-cloak]{display:none!important}</style>
|
|
@endsection
|