import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { Activity, RefreshCw, CheckCircle, AlertCircle, Square, Circle, Trash2, Download, XCircle, Film, Globe, } from 'lucide-react'; import { Panel, Button, Badge, Tabs } from '../ui'; import { listBatchJobs, cancelBatchJob, deleteBatchJob, enqueueBatchJob, } from '../api/batch'; import { API } from '../api/client'; import BatchAddDialog from '../components/BatchAddDialog'; import toast from 'react-hot-toast'; import './BatchQueue.css'; /** * BatchQueue — UI for the /batch/* dubbing pipeline. * * Tabs: Active · Done · Failed. Polls every 3s for active jobs. * Shows real-time progress (extract → transcribe → translate → generate → mix). */ const TABS = [ { id: 'active', label: 'Active', icon: Activity }, { id: 'done', label: 'Completed', icon: CheckCircle }, { id: 'failed', label: 'Failed', icon: AlertCircle }, ]; const STATUS_TONE = { queued: { tone: 'neutral', icon: Circle, label: 'queued' }, running: { tone: 'brand', icon: Activity, label: 'running' }, done: { tone: 'success', icon: CheckCircle, label: 'done' }, failed: { tone: 'danger', icon: AlertCircle, label: 'failed' }, cancelled: { tone: 'warn', icon: Square, label: 'cancelled' }, }; const STAGE_LABELS = { extract: '🎬 Extracting audio…', transcribe: '📝 Transcribing…', translate: '🌐 Translating…', generate: '🗣️ Generating speech…', mix: '🎛️ Mixing audio…', done: '✅ Complete', }; export default function BatchQueue({ onBack }) { const [tab, setTab] = useState('active'); const [jobs, setJobs] = useState([]); const [loading, setLoading] = useState(false); const [addOpen, setAddOpen] = useState(false); const reload = useCallback(async () => { setLoading(true); try { const statusParam = tab === 'active' ? 'active' : tab; setJobs(await listBatchJobs(statusParam, 100)); } catch (e) { console.warn('batch queue load failed', e); } finally { setLoading(false); } }, [tab]); useEffect(() => { reload(); }, [reload]); // Poll active tab every 3s for live progress useEffect(() => { if (tab !== 'active') return; const iv = setInterval(reload, 3000); return () => clearInterval(iv); }, [tab, reload]); const handleEnqueue = useCallback(async (files, settings) => { const langCodes = settings.langs.map(l => l.code); let success = 0; for (const file of files) { try { await enqueueBatchJob(file, langCodes, settings.voiceId || undefined, settings.preserveBg); success++; } catch (e) { toast.error(`Failed to enqueue ${file.name}: ${e.message}`); } } if (success > 0) { toast.success(`${success} video${success > 1 ? 's' : ''} added to queue`); setTab('active'); reload(); } }, [reload]); const handleCancel = useCallback(async (id) => { try { await cancelBatchJob(id); toast.success('Job cancelled'); reload(); } catch (e) { toast.error('Cancel failed: ' + e.message); } }, [reload]); const handleDelete = useCallback(async (id) => { try { await deleteBatchJob(id); toast.success('Job deleted'); reload(); } catch (e) { toast.error('Delete failed: ' + e.message); } }, [reload]); return (
No {tab} jobs.
{tab === 'active' && 'Drop videos above to start batch dubbing.'} {tab === 'done' && 'Nothing has completed recently.'} {tab === 'failed' && 'No failed jobs — enjoy the silence.'}