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 && (
)}
);
})}
);
}