import React, { useState, useCallback } from 'react'; import { User, Mic, ChevronDown, Check, Shuffle, Volume2 } from 'lucide-react'; import './CastingView.css'; /** * CastingView — assign voice profiles to speakers for dubbing projects. * * Shows each detected speaker as a row, with a dropdown to pick a voice * profile (from saved profiles or auto-clones from the video). Drag-and-drop * is scaffolded for a future pass. * * Props: * speakers: [{ id, label, segments_count }] * profiles: [{ id, name, type, personality }] * autoClones: { speaker_id: { ref_audio, ref_text } } * assignments: { speaker_id: profile_id | "auto:speaker_id" } * onChange: (assignments) => void * onPreview: (profile_id) => void */ export default function CastingView({ speakers = [], profiles = [], autoClones = {}, assignments = {}, onChange, onPreview, }) { const [openDropdown, setOpenDropdown] = useState(null); const assign = useCallback((speakerId, profileId) => { const next = { ...assignments, [speakerId]: profileId }; onChange?.(next); setOpenDropdown(null); }, [assignments, onChange]); const autoAssignAll = useCallback(() => { const next = {}; speakers.forEach((s) => { // Prefer auto-clone if available, else keep existing assignment if (autoClones[s.id]) { next[s.id] = `auto:${s.id}`; } else if (assignments[s.id]) { next[s.id] = assignments[s.id]; } }); onChange?.(next); }, [speakers, autoClones, assignments, onChange]); if (speakers.length === 0) return null; const allAssigned = speakers.every(s => assignments[s.id]); return (

Speaker Casting

{allAssigned && ( All cast )}
{speakers.map((speaker) => { const currentAssignment = assignments[speaker.id]; const isAuto = currentAssignment?.startsWith('auto:'); let autoName = speaker.label; if (isAuto && currentAssignment) { const match = Object.keys(autoClones || {}).find(spk => `auto:${(spk || '').toLowerCase().replace(/\s+/g, '_')}` === currentAssignment); if (match) autoName = match; } const assignedProfile = isAuto ? { name: `🎤 From video (${autoName})`, type: 'clone' } : profiles.find(p => p.id === currentAssignment); return (
{/* Speaker info */}
{speaker.label?.slice(0, 2).toUpperCase() || 'S'}
{speaker.label || speaker.id} {speaker.segments_count || 0} segments
{/* Arrow */} {/* Voice assignment dropdown */}
{/* Dropdown */} {openDropdown === speaker.id && (
{/* Auto-clone options */} {autoClones && Object.keys(autoClones).length > 0 && ( <> {Object.keys(autoClones).map(spk => { const autoId = `auto:${(spk || '').toLowerCase().replace(/\s+/g, '_')}`; const isActiveAuto = currentAssignment === autoId; return ( ); })} {profiles.length > 0 &&
} )} {/* Saved profiles */} {profiles.map(p => ( ))} {profiles.length === 0 && !autoClones[speaker.id] && (
No voice profiles saved yet.
)}
)}
{/* Preview button */} {currentAssignment && onPreview && ( )}
); })}
); }