Implement voice assignment component (Task 10)

- Created VoiceAssignmentSection component with role-to-voice mapping
- Added language selector with American English, British English, French, Japanese
- Added speed slider (0.5x - 1.5x range)
- Implemented Generate Audiobook button with proper disabled state
- Integrated component into App.tsx with state management
- Added role detection API call on text submission
- Validates all roles have assigned voices before enabling generation

Requirements: 3.1, 3.2, 3.3, 3.4, 3.5, 3.6, 4.1, 4.2, 4.3, 4.4, 5.1, 5.2, 5.3
This commit is contained in:
2025-12-09 07:48:19 +00:00
parent 85a71fac2c
commit e92d0349bf
3 changed files with 259 additions and 10 deletions

View File

@@ -1,27 +1,102 @@
import { useState } from 'react'
import './App.css'
import TextInputSection from './components/TextInputSection'
import VoiceAssignmentSection from './components/VoiceAssignmentSection'
import type { Role } from './api/tts'
import { detectRoles } from './api/tts'
function App() {
const [text, setText] = useState('')
const [phase, setPhase] = useState<'input' | 'assignment' | 'generated'>('input')
const [isLoading] = useState(false)
const [isLoading, setIsLoading] = useState(false)
const [roles, setRoles] = useState<Role[]>([])
const [voiceAssignments, setVoiceAssignments] = useState<Map<string, string>>(new Map())
const [language, setLanguage] = useState('a') // Default: American English
const [speed, setSpeed] = useState(1.0) // Default: 1.0x
const [isGenerating, setIsGenerating] = useState(false)
const [error, setError] = useState<string | null>(null)
const handleTextChange = (newText: string) => {
setText(newText)
}
const handleNext = () => {
// TODO: Implement role detection and phase transition
console.log('Moving to voice assignment phase with text:', text)
setPhase('assignment')
const handleNext = async () => {
setIsLoading(true)
setError(null)
try {
// Detect roles in the text
const response = await detectRoles(text)
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 handleGenerate = async () => {
setIsGenerating(true)
setError(null)
try {
// TODO: Implement audio generation
console.log('Generating audiobook with:', {
text,
language,
voiceAssignments: Array.from(voiceAssignments.entries()),
speed
})
// Placeholder - will be implemented in later tasks
setTimeout(() => {
setIsGenerating(false)
setPhase('generated')
}, 1000)
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to generate audiobook')
console.error('Generation error:', err)
setIsGenerating(false)
}
}
// Check if all roles have assigned voices
const allRolesAssigned = roles.length > 0 && roles.every(role => voiceAssignments.has(role.id))
return (
<div className="min-h-screen bg-gray-50">
<div className="container mx-auto py-8">
<h1 className="text-4xl font-bold text-center mb-8">Text to Audiobook</h1>
{error && (
<div className="max-w-4xl mx-auto mb-4 p-4 bg-red-100 border border-red-400 text-red-700 rounded">
{error}
</div>
)}
{phase === 'input' && (
<TextInputSection
text={text}
@@ -33,9 +108,18 @@ function App() {
)}
{phase === 'assignment' && (
<div className="text-center">
<p>Voice assignment phase (to be implemented)</p>
</div>
<VoiceAssignmentSection
roles={roles}
voiceAssignments={voiceAssignments}
onVoiceChange={handleVoiceChange}
language={language}
onLanguageChange={handleLanguageChange}
speed={speed}
onSpeedChange={handleSpeedChange}
onGenerate={handleGenerate}
isGenerating={isGenerating}
allRolesAssigned={allRolesAssigned}
/>
)}
{phase === 'generated' && (