"use client"; import { useState } from "react"; import { StreamActionLog } from "yt-dlp-transcript-common/components/StreamActionLog"; import { QueueControl } from "../../../../../../components/QueueControl"; import { cancelJobAction } from "../../../../../../jobs/actions"; import { transcribeOneAction, } from "../../videoActions"; import { Heading } from "./Heading"; import type { VideoFile } from "./videoFiles"; export function PerFileTranscribeRow({ slug, videoId, file, existingQueues, hasTranscript, replaceAutoSubs = false, }: { slug: string; videoId: string; file: VideoFile; existingQueues: string[]; hasTranscript: boolean; // The existing transcript is YouTube ASR: transcribing is offered (and // labelled as a replacement) rather than blocked. replaceAutoSubs?: boolean; }) { const [transcribeQueue, setTranscribeQueue] = useState(""); // `hasTranscript` is SERVER-derived (VideoPanel, the Transcribe card) and this row's own run // is what writes transcript.json — so the run's own router.refresh() used to // swap this panel for the "already present" line and take the log with it. // Same shape as the two banners: the row survives (it is keyed by file name), // the message and the run panel are separate slots, and the panel stays // mounted — disabled, since the transcript now exists — once a run has // started here. With no run it is still absent whenever a transcript is // already present, so the DOM is unchanged. const [ranHere, setRanHere] = useState(false); return (
{hasTranscript ? (

Transcribe disabled — transcript already present.

) : null} {hasTranscript && !ranHere ? null : ( { setRanHere(true); return transcribeOneAction( slug, videoId, file.name, transcribeQueue, ); }} cancelAction={cancelJobAction} buttonLabel={ replaceAutoSubs ? `Replace auto-captions (${file.name})` : `Transcribe ${file.name}` } runningLabel="Transcribing…" label={`Transcribe ${file.name}`} extraControls={ } /> )}
); }