feat: implement script review workflow in App component
- Add 'review' phase to application state - Add scriptSegments state variable for storing AI-generated segments - Add isGeneratingScript loading state for script generation - Implement handleGenerateScript() to call API and transition to review phase - Implement handleSkipReview() to bypass review and go directly to generation - Implement handleSegmentChange() to update speaker assignments in segments - Update handleGenerate() to use edited segments when available - Wire up ScriptReviewSection component in review phase - Update phase transitions: input → assignment → review → generated - Add loading indicator for script generation Validates Requirements: 15.1, 15.3, 15.6, 15.7, 15.8 Task: 22. Update App component for script review workflow
This commit is contained in:
@@ -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<Role[]>([])
|
||||
const [voiceAssignments, setVoiceAssignments] = useState<Map<string, string>>(new Map())
|
||||
const [scriptSegments, setScriptSegments] = useState<ScriptSegment[]>([])
|
||||
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<string | null>(null)
|
||||
const [audioUrl, setAudioUrl] = useState<string | null>(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 && (
|
||||
<div className="max-w-4xl mx-auto mb-4 p-3 sm:p-4 bg-yellow-100 border border-yellow-400 text-yellow-800 rounded text-sm sm:text-base">
|
||||
Generation may take several minutes for very long content.
|
||||
</div>
|
||||
)}
|
||||
<ScriptReviewSection
|
||||
segments={scriptSegments}
|
||||
roles={roles}
|
||||
onSegmentChange={handleSegmentChange}
|
||||
onGenerate={handleGenerate}
|
||||
onSkipReview={handleSkipReview}
|
||||
isGenerating={isGenerating}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{phase === 'generated' && audioUrl && (
|
||||
<AudioPlayerSection
|
||||
audioUrl={audioUrl}
|
||||
@@ -168,6 +231,12 @@ function App() {
|
||||
Generation may take several minutes for very long content. Please be patient...
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isGeneratingScript && (
|
||||
<div className="max-w-4xl mx-auto mt-4 p-3 sm:p-4 bg-blue-100 border border-blue-400 text-blue-800 rounded text-center text-sm sm:text-base">
|
||||
Generating script preview... This may take a moment.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user