// remix-screen.jsx — Remix flow (2 steps): // Step 1: YouTube URL → transcript + explanation (lang A). // Step 2: rewrite a new script (lang B) with optional user instructions. // Self-contained. Reuses the AutoCut shell + CSS design tokens. // Registered as a global component, rendered when appMode === "remix". const TXT_REMIX = { vi: { title: "Viết lại kịch bản từ YouTube", subtitle: "Bước 1: lấy transcript + nội dung video. Bước 2: viết lại kịch bản theo ý bạn.", // step 1 urlLabel: "Link YouTube", urlPlaceholder: "https://www.youtube.com/watch?v=…", langA: "Ngôn ngữ giải thích (A)", analyze: "Phân tích video", analyzing: "Đang lấy transcript & phân tích nội dung…", // results transcript: "Transcript gốc", sourceLang: "Ngôn ngữ gốc", explanation: "Nội dung video", newScript: "Kịch bản mới", // step 2 step2Title: "Bước 2 — Viết lại kịch bản", langB: "Ngôn ngữ kịch bản mới (B)", instructions: "Hướng dẫn viết lại (tuỳ chọn)", instructionsPlaceholder: "VD: viết ngắn gọn hơn, giọng hài hước, nhắm Gen Z, mở đầu bằng câu hỏi gây tò mò…", rewrite: "Viết lại", rewriting: "Đang viết lại kịch bản…", rewriteAgain: "Viết lại lần nữa", // actions copy: "Copy", copied: "Đã copy!", download: "Tải .txt", sendToStudio: "Đưa sang Studio", newJob: "Làm video khác", back: "Quay lại", noTranscript: "Video này không có transcript. Hãy thử video khác có phụ đề.", failed: "Thất bại", }, en: { title: "Remix a script from YouTube", subtitle: "Step 1: grab the transcript + video content. Step 2: rewrite the script your way.", urlLabel: "YouTube link", urlPlaceholder: "https://www.youtube.com/watch?v=…", langA: "Explanation language (A)", analyze: "Analyze video", analyzing: "Fetching transcript & analyzing content…", transcript: "Original transcript", sourceLang: "Source language", explanation: "Video content", newScript: "New script", step2Title: "Step 2 — Rewrite the script", langB: "New script language (B)", instructions: "Rewrite instructions (optional)", instructionsPlaceholder: "e.g. make it shorter, funny tone, target Gen Z, open with a curious question…", rewrite: "Rewrite", rewriting: "Rewriting the script…", rewriteAgain: "Rewrite again", copy: "Copy", copied: "Copied!", download: "Download .txt", sendToStudio: "Send to Studio", newJob: "New video", back: "Back", noTranscript: "This video has no transcript. Try another video that has captions.", failed: "Failed", }, }; const REMIX_LANG_OPTS = ["Vietnamese", "English", "Spanish", "French", "Japanese", "Korean", "Chinese"]; // ── small styled primitives (matching enhance-screen) ───────────────────────── const rmxInput = { background: "var(--bg-1)", border: "1px solid var(--line)", color: "var(--fg-0)", borderRadius: "var(--radius)", padding: "8px 10px", fontSize: 13, fontFamily: "var(--font-sans)", width: "100%", boxSizing: "border-box", }; const rmxBtn = (primary) => ({ padding: "10px 18px", fontSize: 13.5, fontWeight: 600, borderRadius: "var(--radius)", cursor: "pointer", border: "none", background: primary ? "var(--accent)" : "var(--bg-3)", color: primary ? "var(--accent-fg)" : "var(--fg-1)", }); function RmxField({ label, children }) { return ( ); } function RmxSelect({ value, options, onChange }) { return ( ); } // A result block with a header, copy/download actions and scrollable body. function RmxResultCard({ title, body, T, mono, maxHeight, accent, extraActions }) { const [copied, setCopied] = React.useState(false); const copy = async () => { try { await navigator.clipboard.writeText(body || ""); setCopied(true); setTimeout(() => setCopied(false), 1400); } catch (e) {} }; const download = () => { const blob = new Blob([body || ""], { type: "text/plain;charset=utf-8" }); const a = document.createElement("a"); a.href = URL.createObjectURL(blob); a.download = `${title.replace(/\s+/g, "_").toLowerCase()}.txt`; a.click(); URL.revokeObjectURL(a.href); }; return (

{title}

{extraActions}
{body}
); } function RmxSpinner({ label }) { return (
{label}
); } function RemixScreen({ uiLang, onSendToStudio }) { const T = TXT_REMIX[uiLang] || TXT_REMIX.vi; // input | analyzing | analyzed | rewriting | done | error const [phase, setPhase] = React.useState("input"); const [url, setUrl] = React.useState(""); const [langA, setLangA] = React.useState(uiLang === "vi" ? "Vietnamese" : "English"); const [langB, setLangB] = React.useState(uiLang === "vi" ? "English" : "Vietnamese"); const [instructions, setInstructions] = React.useState(""); const [analysis, setAnalysis] = React.useState(null); // {transcript, source_lang, explanation} const [script, setScript] = React.useState(""); const [error, setError] = React.useState(null); const wrap = { maxWidth: 880, margin: "0 auto", padding: "0 24px" }; const readDetail = async (res) => { let detail = `${res.status}`; try { const j = await res.json(); detail = j.detail || detail; } catch (e) {} if (res.status === 422) detail = T.noTranscript; return detail; }; // ── Step 1: analyze ──────────────────────────────────────────────────────── const analyze = async () => { if (!url.trim()) return; setError(null); setScript(""); setAnalysis(null); setPhase("analyzing"); try { const res = await fetch("/api/remix/analyze", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ youtube_url: url.trim(), language_a: langA }), }); if (!res.ok) throw new Error(await readDetail(res)); setAnalysis(await res.json()); setPhase("analyzed"); } catch (e) { setError(String(e.message || e)); setPhase("error"); } }; // ── Step 2: rewrite ──────────────────────────────────────────────────────── const rewrite = async () => { if (!analysis) return; setError(null); setPhase("rewriting"); try { const res = await fetch("/api/remix/rewrite", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ transcript: analysis.transcript, explanation: analysis.explanation, language_b: langB, instructions: instructions, }), }); if (!res.ok) throw new Error(await readDetail(res)); const data = await res.json(); setScript(data.script); setPhase("done"); } catch (e) { setError(String(e.message || e)); setPhase("error"); } }; const reset = () => { setPhase("input"); setUrl(""); setInstructions(""); setAnalysis(null); setScript(""); setError(null); }; // ── ANALYZING / REWRITING ────────────────────────────────────────────────── if (phase === "analyzing") return
; if (phase === "rewriting") return
; // ── ERROR ────────────────────────────────────────────────────────────────── if (phase === "error") { return (
⚠ {T.failed}
{error}
{analysis && }
); } // ── STEP 1: INPUT ────────────────────────────────────────────────────────── if (phase === "input") { return (

{T.title}

{T.subtitle}

setUrl(e.target.value)} placeholder={T.urlPlaceholder} style={rmxInput} onKeyDown={(e) => { if (e.key === "Enter") analyze(); }} />
); } // ── STEP 2 panel (reused by analyzed + done) ─────────────────────────────── const a = analysis || {}; const step2Panel = (

{T.step2Title}