import { useState } from 'react' import './App.css' import TextInputSection from './components/TextInputSection' import VoiceAssignmentSection from './components/VoiceAssignmentSection' import ScriptReviewSection from './components/ScriptReviewSection' import AudioPlayerSection from './components/AudioPlayerSection' import type { Role, ScriptSegment } from './api/tts' import { detectRoles, generateScript, generateAudiobook } from './api/tts' function App() { const [text, setText] = useState('') const [phase, setPhase] = useState<'input' | 'assignment' | 'review' | 'generated'>('input') const [isLoading, setIsLoading] = useState(false) const [roles, setRoles] = useState([]) const [voiceAssignments, setVoiceAssignments] = useState>(new Map()) const [scriptSegments, setScriptSegments] = useState([]) const [language, setLanguage] = useState('a') // Default: American English const [speed, setSpeed] = useState(1.0) // Default: 1.0x const [isGeneratingScript, setIsGeneratingScript] = useState(false) const [isGenerating, setIsGenerating] = useState(false) const [error, setError] = useState(null) const [audioUrl, setAudioUrl] = useState(null) const handleTextChange = (newText: string) => { setText(newText) } const handleNext = async () => { setIsLoading(true) setError(null) try { // Detect roles in the text const response = await detectRoles(text) console.log('Detected roles:', response.roles) setRoles(response.roles) // Initialize voice assignments map (empty initially) setVoiceAssignments(new Map()) // Move to assignment phase setPhase('assignment') } catch (err) { setError(err instanceof Error ? err.message : 'Failed to detect roles') console.error('Role detection error:', err) } finally { setIsLoading(false) } } const handleVoiceChange = (roleId: string, voiceId: string) => { setVoiceAssignments(prev => { const newMap = new Map(prev) newMap.set(roleId, voiceId) return newMap }) } const handleLanguageChange = (lang: string) => { setLanguage(lang) } const handleSpeedChange = (newSpeed: number) => { setSpeed(newSpeed) } const handleGenerateScript = async () => { setIsGeneratingScript(true) setError(null) try { // Generate script preview with AI segmentation const roleIds = roles.map(role => role.id) const response = await generateScript({ text, roles: roleIds }) setScriptSegments(response.segments) // Move to review phase setPhase('review') } catch (err) { setError(err instanceof Error ? err.message : 'Failed to generate script') console.error('Script generation error:', err) } finally { setIsGeneratingScript(false) } } const handleSkipReview = async () => { // Skip review and go directly to generation await handleGenerate() } const handleSegmentChange = (segmentId: string, newSpeaker: string) => { setScriptSegments(prev => prev.map(segment => segment.id === segmentId ? { ...segment, speaker: newSpeaker } : segment ) ) } const handleGenerate = async () => { setIsGenerating(true) setError(null) try { // Convert voice assignments to the format expected by the API const rolesArray = Array.from(voiceAssignments.entries()).map(([id, voice]) => ({ id, voice })) // Generate the audiobook // If we have script segments (from review phase), use them // Otherwise, fall back to text-based generation const audioBlob = await generateAudiobook({ segments: scriptSegments.length > 0 ? scriptSegments : undefined, text: scriptSegments.length > 0 ? undefined : text, lang: language, roles: rolesArray, speed }) // Create a blob URL for the audio const url = URL.createObjectURL(audioBlob) setAudioUrl(url) // Move to generated phase setPhase('generated') } catch (err) { setError(err instanceof Error ? err.message : 'Failed to generate audiobook') console.error('Generation error:', err) } finally { setIsGenerating(false) } } // Check if text is long (more than 5000 characters as a threshold) const isLongText = text.length > 5000 const handleDownload = () => { if (!audioUrl) return // Create a temporary anchor element to trigger download const link = document.createElement('a') link.href = audioUrl link.download = 'audiobook.wav' document.body.appendChild(link) link.click() document.body.removeChild(link) } // Check if all roles have assigned voices const allRolesAssigned = roles.length > 0 && roles.every(role => voiceAssignments.has(role.id)) return (
{/* Header */}
🎧

Text to Audiobook

Transform your stories into immersive audio experiences with AI-powered voices

{/* Progress Steps */}
{['Input', 'Voices', 'Review', 'Done'].map((step, index) => { const phases = ['input', 'assignment', 'review', 'generated']; const isActive = phase === phases[index]; const isPast = phases.indexOf(phase) > index; return (
{isPast ? '✓' : index + 1}
{step}
{index < 3 && (
)}
); })}
{error && (
⚠️

Error

{error}

)} {phase === 'input' && ( )} {phase === 'assignment' && ( <> {isLongText && (
⏱️

Generation may take several minutes for very long content.

)} {console.log('Rendering VoiceAssignmentSection with roles:', roles, 'allRolesAssigned:', allRolesAssigned)} )} {phase === 'review' && ( <> {isLongText && (
⏱️

Generation may take several minutes for very long content.

)} )} {phase === 'generated' && audioUrl && ( )} {isGenerating && isLongText && (

Generating Your Audiobook

This may take several minutes. Please be patient...

)} {isGeneratingScript && (

Generating Script Preview

AI is analyzing your text...

)}
) } export default App