Implement ScriptReviewSection component
- Created ScriptReviewSection component with script segment display - Added speaker dropdown for each segment to allow editing assignments - Implemented Generate Audiobook and Skip Review buttons - Styled for readability and mobile responsiveness with Tailwind CSS - Text segments displayed as read-only (only speaker assignment is editable) - Follows existing component patterns from VoiceAssignmentSection - Validates Requirements 15.3, 15.4, 15.5, 15.6, 15.8
This commit is contained in:
111
frontend/src/components/ScriptReviewSection.tsx
Normal file
111
frontend/src/components/ScriptReviewSection.tsx
Normal file
@@ -0,0 +1,111 @@
|
||||
import type { Role, ScriptSegment } from '../api/tts';
|
||||
|
||||
interface ScriptReviewSectionProps {
|
||||
segments: ScriptSegment[];
|
||||
roles: Role[];
|
||||
onSegmentChange: (segmentId: string, newSpeaker: string) => void;
|
||||
onGenerate: () => void;
|
||||
onSkipReview: () => void;
|
||||
isGenerating: boolean;
|
||||
}
|
||||
|
||||
export default function ScriptReviewSection({
|
||||
segments,
|
||||
roles,
|
||||
onSegmentChange,
|
||||
onGenerate,
|
||||
onSkipReview,
|
||||
isGenerating,
|
||||
}: ScriptReviewSectionProps) {
|
||||
const handleSpeakerChange = (segmentId: string, newSpeaker: string) => {
|
||||
onSegmentChange(segmentId, newSpeaker);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full max-w-4xl mx-auto p-4 sm:p-6">
|
||||
<h2 className="text-xl sm:text-2xl font-bold mb-3 sm:mb-4">Review Script</h2>
|
||||
|
||||
<p className="text-sm sm:text-base text-gray-600 mb-4 sm:mb-6">
|
||||
Review the AI-generated script and adjust speaker assignments as needed.
|
||||
</p>
|
||||
|
||||
{/* Script Segments */}
|
||||
<div className="space-y-4 sm:space-y-6 mb-6 sm:mb-8">
|
||||
{segments.map((segment, index) => (
|
||||
<div
|
||||
key={segment.id}
|
||||
className="border-2 border-gray-300 rounded-lg p-3 sm:p-4 bg-white shadow-sm"
|
||||
>
|
||||
{/* Segment Header */}
|
||||
<div className="flex flex-col sm:flex-row sm:items-center gap-2 sm:gap-4 mb-3">
|
||||
<span className="text-xs sm:text-sm font-semibold text-gray-500">
|
||||
Segment {index + 1}
|
||||
</span>
|
||||
|
||||
{/* Speaker Dropdown */}
|
||||
<div className="flex items-center gap-2 flex-1">
|
||||
<label className="text-xs sm:text-sm font-medium text-gray-700 whitespace-nowrap">
|
||||
Speaker:
|
||||
</label>
|
||||
<select
|
||||
value={segment.speaker}
|
||||
onChange={(e) => handleSpeakerChange(segment.id, e.target.value)}
|
||||
className="flex-1 p-2 sm:p-2 min-h-[44px] border-2 border-gray-300 rounded-lg focus:border-blue-500 focus:outline-none text-sm sm:text-base touch-manipulation"
|
||||
disabled={isGenerating}
|
||||
>
|
||||
{roles.map((role) => (
|
||||
<option key={role.id} value={role.id}>
|
||||
{role.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Segment Text */}
|
||||
<div className="w-full min-h-[80px] p-3 border-2 border-gray-200 rounded-lg bg-gray-50 text-sm sm:text-base whitespace-pre-wrap">
|
||||
{segment.text}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Action Buttons */}
|
||||
<div className="flex flex-col sm:flex-row gap-3 sm:gap-4 justify-center">
|
||||
<button
|
||||
onClick={onSkipReview}
|
||||
disabled={isGenerating}
|
||||
className={`w-full sm:w-auto px-6 py-3 min-h-[44px] rounded-lg font-semibold transition-colors touch-manipulation ${
|
||||
isGenerating
|
||||
? 'bg-gray-300 text-gray-500 cursor-not-allowed'
|
||||
: 'bg-gray-500 text-white hover:bg-gray-600 active:bg-gray-700 cursor-pointer'
|
||||
}`}
|
||||
>
|
||||
Skip Review
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={onGenerate}
|
||||
disabled={isGenerating}
|
||||
className={`w-full sm:w-auto px-8 py-3 min-h-[44px] rounded-lg font-semibold text-white transition-colors touch-manipulation ${
|
||||
isGenerating
|
||||
? 'bg-gray-400 cursor-not-allowed'
|
||||
: 'bg-green-600 hover:bg-green-700 active:bg-green-800 cursor-pointer'
|
||||
}`}
|
||||
>
|
||||
{isGenerating ? 'Generating...' : 'Generate Audiobook'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Info Message */}
|
||||
{!isGenerating && (
|
||||
<div className="mt-4 sm:mt-6 p-3 sm:p-4 bg-blue-50 border border-blue-200 text-blue-800 rounded text-sm sm:text-base">
|
||||
<p className="font-semibold mb-1">💡 Tip:</p>
|
||||
<p>
|
||||
Review the AI-generated speaker assignments and make any necessary corrections before generating the audiobook.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user