diff --git a/.kiro/specs/text-to-audiobook/tasks.md b/.kiro/specs/text-to-audiobook/tasks.md index e12135e..b47a364 100644 --- a/.kiro/specs/text-to-audiobook/tasks.md +++ b/.kiro/specs/text-to-audiobook/tasks.md @@ -424,7 +424,7 @@ - Test skip review button works - _Requirements: 15.3, 15.4, 15.5, 15.8_ -- [ ] 22. Update App component for script review workflow +- [x] 22. Update App component for script review workflow - Add 'review' phase to application state - Add scriptSegments state variable - Add isGeneratingScript loading state diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index e433e0a..ca873e0 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -2,18 +2,21 @@ 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 } from './api/tts' -import { detectRoles, generateAudiobook } from './api/tts' +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' | 'generated'>('input') + 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) @@ -60,6 +63,45 @@ function App() { 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) @@ -72,8 +114,11 @@ function App() { })) // Generate the audiobook + // If we have script segments (from review phase), use them + // Otherwise, fall back to text-based generation const audioBlob = await generateAudiobook({ - text, + segments: scriptSegments.length > 0 ? scriptSegments : undefined, + text: scriptSegments.length > 0 ? undefined : text, lang: language, roles: rolesArray, speed @@ -149,13 +194,31 @@ function App() { onLanguageChange={handleLanguageChange} speed={speed} onSpeedChange={handleSpeedChange} - onGenerate={handleGenerate} - isGenerating={isGenerating} + onGenerate={handleGenerateScript} + isGenerating={isGeneratingScript} allRolesAssigned={allRolesAssigned} /> )} + {phase === 'review' && ( + <> + {isLongText && ( +
+ Generation may take several minutes for very long content. +
+ )} + + + )} + {phase === 'generated' && audioUrl && ( )} + + {isGeneratingScript && ( +
+ Generating script preview... This may take a moment. +
+ )} )