import { useState } from 'react' import { api, runJob } from '../api' import { useJob, ProgressBox, ErrorBox } from '../hooks' const PRESETS = ['bold-center-karaoke', 'clean-lower-third', 'minimal', 'typewriter', 'punch'] const DUB_LANGS = [['es', 'Spanish'], ['hi', 'Hindi'], ['fr', 'French'], ['de', 'German'], ['pt', 'Portuguese'], ['ja', 'Japanese'], ['ar', 'Arabic'], ['it', 'Italian'], ['ko', 'Korean'], ['zh', 'Chinese'], ['ru', 'Russian'], ['id', 'Indonesian']] export default function ReviewStep({ reelData, refreshReel, setStep, projectId, reelId }) { const base = `/api/projects/${projectId}/reels/${reelId}` const results = reelData?.renders?.results || [] const [picked, setPicked] = useState(0) const [tweakPreset, setTweakPreset] = useState(null) const [swapping, setSwapping] = useState(false) const [error, setError] = useState('') const rerenderJob = useJob() const [dubLang, setDubLang] = useState('es') const [dubBusy, setDubBusy] = useState(false) const [dubMsg, setDubMsg] = useState('') const [dubbedUrl, setDubbedUrl] = useState('') if (!results.length) { return (

Review

Nothing rendered yet.

) } const chosen = results[Math.min(picked, results.length - 1)] // Tweak: re-render only the picked variant, optionally with a new caption preset. const rerender = async () => { const spec = { name: chosen.variant, caption_preset: tweakPreset || chosen.caption_preset, clip_offset: results.indexOf(chosen), } try { await rerenderJob.start(`${base}/render`, { variants: [spec] }) await refreshReel() setPicked(0) setTweakPreset(null) } catch { /* shown */ } } const swapClip = async (lineIndex) => { setSwapping(true) try { await api.post(`${base}/media/swap`, { line_index: lineIndex }) await refreshReel() } catch (e) { setError(e.message) } finally { setSwapping(false) } } const dubIt = async () => { setDubBusy(true); setError(''); setDubbedUrl(''); setDubMsg('Starting…') try { const r = await runJob(`${base}/dub`, { result_index: results.indexOf(chosen), target_lang: dubLang }, (p) => setDubMsg(p[p.length - 1] || 'Dubbing…')) setDubbedUrl(r.url) } catch (e) { setError(e.message) } finally { setDubBusy(false); setDubMsg('') } } return (

Review & export

Preview the variants side by side, pick one, tweak and re-render, then download.

{rerenderJob.running && }
{results.map((r, i) => (
))}
Tweak “{chosen.variant}”
{reelData?.media && ( <>

Swap a scene’s clip (then re-render):

{reelData.media.scenes.map((sm) => ( ))}
)}
🌍 Dub to another language (ElevenLabs)

Re-voice “{chosen.variant}” in another language, keeping the delivery. Uses credits; takes a few minutes.

{dubMsg && {dubMsg}}
{dubbedUrl && (
)}
⬇ Download {chosen.variant}

Tip: trending audio must be added in the YouTube/Instagram app at posting time — platform-licensed sounds are never embedded here.

) }