import React from 'react'; import { Box, Button, Dialog, DialogActions, DialogContent, DialogTitle, TextField, Divider, CircularProgress, Typography, Tooltip, IconButton, Slider, FormControl, InputLabel, Select, MenuItem, FormHelperText, ToggleButtonGroup, ToggleButton } from '@mui/material'; import VolumeUpIcon from '@mui/icons-material/VolumeUp'; import SmartToyIcon from '@mui/icons-material/SmartToy'; import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined'; import { OperationButton } from '../../../shared/OperationButton'; interface AudioScriptModalProps { open: boolean; sceneNumber: number; value: string; onChange: (v: string) => void; onClose: () => void; onSave: () => void; // audio settings audioProvider: string; audioLang: string; audioSlow: boolean; audioRate: number; onChangeProvider: (v: string) => void; onChangeLang: (v: string) => void; onChangeSlow: (v: boolean) => void; onChangeRate: (v: number) => void; audioUrl?: string | null; // audio generation callbacks - now with full parameters onGenerateAI?: (params: { text: string; voice_id?: string; speed?: number; volume?: number; pitch?: number; emotion?: string; }) => Promise; onGenerateFree?: (text: string) => Promise; } // Available voice IDs from WaveSpeed Minimax const AVAILABLE_VOICES = [ { value: 'Wise_Woman', label: 'Wise Woman', description: 'Warm, authoritative female voice' }, { value: 'Friendly_Person', label: 'Friendly Person', description: 'Approachable and conversational' }, { value: 'Inspirational_girl', label: 'Inspirational Girl', description: 'Energetic and motivating' }, { value: 'Deep_Voice_Man', label: 'Deep Voice Man', description: 'Rich, deep male voice' }, { value: 'Calm_Woman', label: 'Calm Woman', description: 'Peaceful and soothing' }, { value: 'Casual_Guy', label: 'Casual Guy', description: 'Relaxed and informal' }, { value: 'Lively_Girl', label: 'Lively Girl', description: 'Vibrant and enthusiastic' }, { value: 'Patient_Man', label: 'Patient Man', description: 'Steady and reassuring' }, { value: 'Young_Knight', label: 'Young Knight', description: 'Brave and confident' }, { value: 'Determined_Man', label: 'Determined Man', description: 'Strong and resolute' }, { value: 'Lovely_Girl', label: 'Lovely Girl', description: 'Sweet and charming' }, { value: 'Decent_Boy', label: 'Decent Boy', description: 'Polite and well-mannered' }, { value: 'Imposing_Manner', label: 'Imposing Manner', description: 'Commanding and powerful' }, { value: 'Elegant_Man', label: 'Elegant Man', description: 'Sophisticated and refined' }, { value: 'Abbess', label: 'Abbess', description: 'Dignified and wise' }, { value: 'Sweet_Girl_2', label: 'Sweet Girl 2', description: 'Gentle and kind' }, { value: 'Exuberant_Girl', label: 'Exuberant Girl', description: 'Joyful and energetic' }, ]; const EMOTIONS = [ { value: 'happy', label: 'Happy', description: 'Cheerful and upbeat tone' }, { value: 'sad', label: 'Sad', description: 'Melancholic and somber tone' }, { value: 'angry', label: 'Angry', description: 'Intense and forceful tone' }, { value: 'fear', label: 'Fear', description: 'Anxious and nervous tone' }, { value: 'surprised', label: 'Surprised', description: 'Astonished and amazed tone' }, { value: 'neutral', label: 'Neutral', description: 'Calm and balanced tone (default)' }, ]; const AudioScriptModal: React.FC = ({ open, sceneNumber, value, onChange, onClose, onSave, audioProvider, audioLang, audioSlow, audioRate, onChangeProvider, onChangeLang, onChangeSlow, onChangeRate, audioUrl, onGenerateAI, onGenerateFree, }) => { const [isGeneratingAI, setIsGeneratingAI] = React.useState(false); const [isGeneratingFree, setIsGeneratingFree] = React.useState(false); const [generateError, setGenerateError] = React.useState(null); // Audio type toggle - default to 'free' const [audioType, setAudioType] = React.useState<'free' | 'ai'>('free'); // AI Audio generation parameters with intelligent defaults const [voiceId, setVoiceId] = React.useState('Wise_Woman'); const [customVoiceId, setCustomVoiceId] = React.useState(''); const [useCustomVoice, setUseCustomVoice] = React.useState(false); const [emotion, setEmotion] = React.useState('happy'); const [speed, setSpeed] = React.useState(1.0); const [volume, setVolume] = React.useState(1.0); const [pitch, setPitch] = React.useState(0.0); const handleGenerateAI = async () => { if (!onGenerateAI || !value.trim()) { return; } setIsGeneratingAI(true); setGenerateError(null); try { await onGenerateAI({ text: value.trim(), voice_id: useCustomVoice ? customVoiceId : voiceId, emotion: emotion, speed: speed, volume: volume, pitch: pitch, }); // Optionally close modal after successful generation // onClose(); } catch (err: any) { setGenerateError(err?.response?.data?.detail || err?.message || 'Failed to generate AI audio'); } finally { setIsGeneratingAI(false); } }; const handleGenerateFree = async () => { if (!onGenerateFree || !value.trim()) { return; } setIsGeneratingFree(true); setGenerateError(null); try { await onGenerateFree(value.trim()); // Optionally close modal after successful generation // onClose(); } catch (err: any) { setGenerateError(err?.response?.data?.detail || err?.message || 'Failed to generate free audio'); } finally { setIsGeneratingFree(false); } }; return ( Edit Audio Narration Script (Scene {sceneNumber}) {audioUrl ? ( ) : null} onChange(e.target.value)} multiline minRows={6} fullWidth placeholder="Enter the narration text for this scene..." sx={{ '& .MuiInputBase-input': { color: '#2C2416', }, }} /> {generateError && ( {generateError} )} {/* Audio Type Toggle */} Audio Type { if (newValue !== null) { setAudioType(newValue); setGenerateError(null); } }} aria-label="audio type" fullWidth sx={{ '& .MuiToggleButton-root': { textTransform: 'none', borderColor: 'rgba(0, 0, 0, 0.23)', color: '#5D4037', '&.Mui-selected': { backgroundColor: 'primary.main', color: '#fff', '&:hover': { backgroundColor: 'primary.dark', }, }, '&:hover': { backgroundColor: 'rgba(0, 0, 0, 0.04)', }, }, }} > Free Audio (gTTS) AI Audio (Minimax) {/* Generate Button - Context aware based on audio type */} {audioType === 'ai' && onGenerateAI && ( } showCost={true} checkOnHover={true} checkOnMount={false} onClick={handleGenerateAI} disabled={isGeneratingAI || isGeneratingFree || !value.trim()} loading={isGeneratingAI} sx={{ flex: 1, minWidth: '200px' }} /> )} {audioType === 'free' && onGenerateFree && ( )} {/* Settings - Conditionally shown based on audio type */} {audioType === 'ai' && ( AI Audio Generation Settings {/* Voice Selection */} Voice Choose a voice that matches your story's tone Current Voice ID: {voiceId} You can use system voices above or enter a custom voice ID from voice cloning. Learn more:{' '} Voice Cloning Guide } arrow placement="top" > {/* Custom Voice ID Input (shown when custom voice is selected) */} {useCustomVoice && ( setCustomVoiceId(e.target.value)} helperText="Enter your custom voice ID from voice cloning" placeholder="your-custom-voice-id" /> )} {/* Emotion Selection */} Emotion Select the emotional tone for the narration {/* Speed Slider */} Speed setSpeed(newValue as number)} min={0.5} max={2.0} step={0.1} valueLabelDisplay="auto" valueLabelFormat={(value) => `${value}x`} sx={{ flex: 1 }} /> {speed.toFixed(1)}x Speech speed (0.5x = slow, 1.0x = normal, 2.0x = fast) {/* Volume Slider */} Volume setVolume(newValue as number)} min={0.1} max={10.0} step={0.1} valueLabelDisplay="auto" valueLabelFormat={(value) => `${value.toFixed(1)}`} sx={{ flex: 1 }} /> {volume.toFixed(1)} Audio volume level (0.1 = quiet, 1.0 = normal, 10.0 = loud) {/* Pitch Slider */} Pitch setPitch(newValue as number)} min={-12} max={12} step={1} valueLabelDisplay="auto" valueLabelFormat={(value) => `${value > 0 ? '+' : ''}${value}`} marks={[ { value: -12, label: '-12' }, { value: 0, label: '0' }, { value: 12, label: '+12' }, ]} sx={{ flex: 1 }} /> {pitch > 0 ? '+' : ''}{pitch} Voice pitch adjustment (-12 = lower, 0 = normal, +12 = higher) )} {audioType === 'free' && ( Free Audio (gTTS) Settings onChangeProvider(e.target.value)} SelectProps={{ native: true }} helperText="Text-to-speech engine for free audio generation" > onChangeLang(e.target.value)} helperText="Language code (e.g., en for English, hi for Hindi)" placeholder="en" /> onChangeSlow(e.target.value === 'true')} SelectProps={{ native: true }} helperText="Whether to speak slowly (useful for clarity)" > onChangeRate(Number(e.target.value))} inputProps={{ min: 50, max: 300, step: 10 }} helperText="Words per minute (50-300, default: 150)" /> )} ); }; export default AudioScriptModal;