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:
@@ -424,7 +424,7 @@
|
|||||||
- Test skip review button works
|
- Test skip review button works
|
||||||
- _Requirements: 15.3, 15.4, 15.5, 15.8_
|
- _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 'review' phase to application state
|
||||||
- Add scriptSegments state variable
|
- Add scriptSegments state variable
|
||||||
- Add isGeneratingScript loading state
|
- Add isGeneratingScript loading state
|
||||||
|
|||||||
@@ -2,18 +2,21 @@ import { useState } from 'react'
|
|||||||
import './App.css'
|
import './App.css'
|
||||||
import TextInputSection from './components/TextInputSection'
|
import TextInputSection from './components/TextInputSection'
|
||||||
import VoiceAssignmentSection from './components/VoiceAssignmentSection'
|
import VoiceAssignmentSection from './components/VoiceAssignmentSection'
|
||||||
|
import ScriptReviewSection from './components/ScriptReviewSection'
|
||||||
import AudioPlayerSection from './components/AudioPlayerSection'
|
import AudioPlayerSection from './components/AudioPlayerSection'
|
||||||
import type { Role } from './api/tts'
|
import type { Role, ScriptSegment } from './api/tts'
|
||||||
import { detectRoles, generateAudiobook } from './api/tts'
|
import { detectRoles, generateScript, generateAudiobook } from './api/tts'
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const [text, setText] = useState('')
|
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 [isLoading, setIsLoading] = useState(false)
|
||||||
const [roles, setRoles] = useState<Role[]>([])
|
const [roles, setRoles] = useState<Role[]>([])
|
||||||
const [voiceAssignments, setVoiceAssignments] = useState<Map<string, string>>(new Map())
|
const [voiceAssignments, setVoiceAssignments] = useState<Map<string, string>>(new Map())
|
||||||
|
const [scriptSegments, setScriptSegments] = useState<ScriptSegment[]>([])
|
||||||
const [language, setLanguage] = useState('a') // Default: American English
|
const [language, setLanguage] = useState('a') // Default: American English
|
||||||
const [speed, setSpeed] = useState(1.0) // Default: 1.0x
|
const [speed, setSpeed] = useState(1.0) // Default: 1.0x
|
||||||
|
const [isGeneratingScript, setIsGeneratingScript] = useState(false)
|
||||||
const [isGenerating, setIsGenerating] = useState(false)
|
const [isGenerating, setIsGenerating] = useState(false)
|
||||||
const [error, setError] = useState<string | null>(null)
|
const [error, setError] = useState<string | null>(null)
|
||||||
const [audioUrl, setAudioUrl] = useState<string | null>(null)
|
const [audioUrl, setAudioUrl] = useState<string | null>(null)
|
||||||
@@ -60,6 +63,45 @@ function App() {
|
|||||||
setSpeed(newSpeed)
|
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 () => {
|
const handleGenerate = async () => {
|
||||||
setIsGenerating(true)
|
setIsGenerating(true)
|
||||||
setError(null)
|
setError(null)
|
||||||
@@ -72,8 +114,11 @@ function App() {
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
// Generate the audiobook
|
// Generate the audiobook
|
||||||
|
// If we have script segments (from review phase), use them
|
||||||
|
// Otherwise, fall back to text-based generation
|
||||||
const audioBlob = await generateAudiobook({
|
const audioBlob = await generateAudiobook({
|
||||||
text,
|
segments: scriptSegments.length > 0 ? scriptSegments : undefined,
|
||||||
|
text: scriptSegments.length > 0 ? undefined : text,
|
||||||
lang: language,
|
lang: language,
|
||||||
roles: rolesArray,
|
roles: rolesArray,
|
||||||
speed
|
speed
|
||||||
@@ -149,13 +194,31 @@ function App() {
|
|||||||
onLanguageChange={handleLanguageChange}
|
onLanguageChange={handleLanguageChange}
|
||||||
speed={speed}
|
speed={speed}
|
||||||
onSpeedChange={handleSpeedChange}
|
onSpeedChange={handleSpeedChange}
|
||||||
onGenerate={handleGenerate}
|
onGenerate={handleGenerateScript}
|
||||||
isGenerating={isGenerating}
|
isGenerating={isGeneratingScript}
|
||||||
allRolesAssigned={allRolesAssigned}
|
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 && (
|
{phase === 'generated' && audioUrl && (
|
||||||
<AudioPlayerSection
|
<AudioPlayerSection
|
||||||
audioUrl={audioUrl}
|
audioUrl={audioUrl}
|
||||||
@@ -168,6 +231,12 @@ function App() {
|
|||||||
Generation may take several minutes for very long content. Please be patient...
|
Generation may take several minutes for very long content. Please be patient...
|
||||||
</div>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user