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) => (
{r.variant}
{r.caption_preset} · {r.duration_sec.toFixed(1)}s
))}
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 && (
)}
Tip: trending audio must be added in the YouTube/Instagram app at posting time — platform-licensed sounds are never embedded here.
)
}