"use client"; import { useEffect, useRef, useState } from "react"; import type { GenerationJob, WaveformPeaks } from "@/lib/types/generation"; import WaveformPreview from "./WaveformPreview"; interface GenerationCardProps { job: GenerationJob; onDelete: (id: string) => void; } function formatDuration(secs: number | null): string { if (secs === null) return "—"; const m = Math.floor(secs / 60); const s = Math.floor(secs % 60); return m > 0 ? `${m}m ${s}s` : `${s}s`; } function formatDate(iso: string): string { return new Date(iso).toLocaleString(undefined, { month: "short", day: "numeric", hour: "2-digit", minute: "2-digit", }); } function truncate(text: string, max: number): string { return text.length > max ? text.slice(0, max) + "…" : text; } export default function GenerationCard({ job, onDelete }: GenerationCardProps) { const [peaks, setPeaks] = useState(null); const [isPlaying, setIsPlaying] = useState(false); const [isDeleting, setIsDeleting] = useState(false); const audioRef = useRef(null); useEffect(() => { if (job.status !== "complete" || !job.waveform_path) return; fetch(`/api/generations/${job.id}/waveform`) .then((r) => r.json()) .then((data: WaveformPeaks) => setPeaks(data)) .catch(() => {}); }, [job.id, job.status, job.waveform_path]); function handlePlayPause() { if (!audioRef.current) { audioRef.current = new Audio(`/api/generations/${job.id}/audio`); audioRef.current.onended = () => setIsPlaying(false); } if (isPlaying) { audioRef.current.pause(); setIsPlaying(false); } else { audioRef.current.play().catch(() => setIsPlaying(false)); setIsPlaying(true); } } async function handleDelete() { if (!confirm("Delete this generation?")) return; setIsDeleting(true); try { await fetch(`/api/generations/${job.id}`, { method: "DELETE" }); onDelete(job.id); } catch { setIsDeleting(false); } } const isComplete = job.status === "complete"; const statusColors: Record = { complete: "var(--success)", generating: "var(--status-loading)", error: "var(--error)", cancelled: "var(--muted)", }; return (
{/* Waveform or placeholder */}
{peaks ? ( ) : (
{job.status === "generating" ? "Generating…" : "No waveform"}
)}
{/* Script preview */}

{truncate(job.script, 120)}

{/* Metadata row */}
{job.speaker} {formatDuration(job.duration_secs)} CFG {job.cfg_scale} {job.status}
{/* Date */}

{formatDate(job.created_at)}

{/* Actions */}
{isComplete && ( <> ↓ Download )}
); }