import React, { useCallback, useState, useEffect } from "react"; import { Box, Stack, Typography, Alert, Paper, alpha, Button, CircularProgress, LinearProgress } from "@mui/material"; import { PlayArrow as PlayArrowIcon, ArrowBack as ArrowBackIcon, VideoLibrary as VideoLibraryIcon, Download as DownloadIcon, CheckCircle as CheckCircleIcon, } from "@mui/icons-material"; import { Script, Knobs, Job } from "./types"; import { SecondaryButton } from "./ui"; import { SceneCard } from "./RenderQueue/SceneCard"; import { SummaryStats } from "./RenderQueue/SummaryStats"; import { GuidancePanel } from "./RenderQueue/GuidancePanel"; import { useRenderQueue } from "./RenderQueue/useRenderQueue"; import { fetchMediaBlobUrl } from "../../utils/fetchMediaBlobUrl"; interface RenderQueueProps { projectId: string; script: Script; knobs: Knobs; jobs: Job[]; bible?: any | null; budgetCap?: number; avatarImageUrl?: string | null; analysis?: any | null; // Add analysis prop onUpdateJob: (sceneId: string, updates: Partial) => void; onUpdateScript?: (script: Script) => void; onBack: () => void; onError: (message: string) => void; } export const RenderQueue: React.FC = ({ projectId, script, knobs, jobs, bible, budgetCap, avatarImageUrl, analysis, onUpdateJob, onUpdateScript, onBack, onError, }) => { const [localError, setLocalError] = useState(""); const { rendering, generatingImage, isBusy, runRender, runImageGeneration, runVideoRender, combiningVideos, combiningProgress, finalVideoUrl, combineFinalVideo, deleteScene, } = useRenderQueue({ script, jobs, knobs, projectId, bible, budgetCap, avatarImageUrl, onUpdateJob, onUpdateScript, onError: (msg) => { setLocalError(msg); onError(msg); }, }); const handleDownloadAudio = useCallback((audioUrl: string, title: string) => { const link = document.createElement("a"); link.href = audioUrl; link.download = `${title.replace(/\s+/g, "-")}.mp3`; link.click(); }, []); const handleDownloadVideo = useCallback((videoUrl: string, title: string) => { const link = document.createElement("a"); link.href = videoUrl; link.download = `${title.replace(/\s+/g, "-")}.mp4`; link.click(); }, []); const handleShare = useCallback(async (audioUrl: string, title: string) => { if (navigator.share && audioUrl) { try { await navigator.share({ title, text: `Check out this podcast episode: ${title}`, url: audioUrl, }); } catch (err) { // User cancelled or error } } else { // Fallback: copy to clipboard await navigator.clipboard.writeText(audioUrl); alert("Audio URL copied to clipboard!"); } }, []); const allScenesCompleted = (jobs.length > 0 && jobs.every((j) => j.status === "completed" && j.imageUrl)) || (script.scenes.length > 0 && script.scenes.every((s) => s.audioUrl && s.imageUrl)); const allVideosReady = jobs.length > 0 && jobs.every((j) => j.videoUrl); // State for final video blob URL const [finalVideoBlobUrl, setFinalVideoBlobUrl] = useState(null); // Load final video as blob when URL changes useEffect(() => { if (finalVideoUrl) { fetchMediaBlobUrl(finalVideoUrl) .then((blobUrl) => { if (blobUrl) { setFinalVideoBlobUrl(blobUrl); } }) .catch((err) => { console.error("Failed to load final video blob:", err); }); } else { setFinalVideoBlobUrl(null); } }, [finalVideoUrl]); return ( {/* Header */} }> Back to Script Render Queue {/* Error Display */} {localError && ( setLocalError("")} sx={{ mb: 3, background: alpha("#ef4444", 0.1), border: "1px solid", borderColor: alpha("#ef4444", 0.3), }} > ❌ {localError} )} {/* Compact Status Dashboard */} {/* Status Chips */} Scenes {script.scenes.length} s.audioUrl) ? alpha("#10b981", 0.1) : alpha("#f59e0b", 0.1), color: script.scenes.every(s => s.audioUrl) ? "#059669" : "#d97706", display: "flex", alignItems: "center", gap: 1, border: "1px solid", borderColor: script.scenes.every(s => s.audioUrl) ? alpha("#10b981", 0.3) : alpha("#f59e0b", 0.3), }} > Audio {script.scenes.every(s => s.audioUrl) ? ( ) : ( {script.scenes.filter(s => s.audioUrl).length}/{script.scenes.length} )} s.imageUrl) ? alpha("#10b981", 0.1) : alpha("#f59e0b", 0.1), color: script.scenes.every(s => s.imageUrl) ? "#059669" : "#d97706", display: "flex", alignItems: "center", gap: 1, border: "1px solid", borderColor: script.scenes.every(s => s.imageUrl) ? alpha("#10b981", 0.3) : alpha("#f59e0b", 0.3), }} > Images {script.scenes.every(s => s.imageUrl) ? ( ) : ( {script.scenes.filter(s => s.imageUrl).length}/{script.scenes.length} )} {/* Dynamic Guidance Message */} {allVideosReady ? "All assets ready. You can combine videos below." : !script.scenes.every(s => s.audioUrl) ? "Generate audio for all scenes to proceed." : !script.scenes.every(s => s.imageUrl) ? "Generate images for video backgrounds." : "Ready to generate scene videos."} {/* Scene Cards */} {script.scenes.map((scene) => { const job = jobs.find((j) => j.sceneId === scene.id); return ( runVideoRender(sceneId, settings)} onDownloadAudio={handleDownloadAudio} onDownloadVideo={handleDownloadVideo} onShare={handleShare} onDelete={deleteScene} onError={onError} /> ); })} {/* Final Export Section - Show when all scene videos are ready */} {allVideosReady && ( {/* Header */} {finalVideoUrl ? ( ) : ( )} {finalVideoUrl ? "🎉 Final Podcast Ready!" : "🎬 Final Podcast Export"} {finalVideoUrl ? "Your complete podcast video is ready to download" : `Combine ${script.scenes.length} scene videos into one final podcast`} {finalVideoUrl ? ( } sx={{ background: alpha("#10b981", 0.1), border: "1px solid", borderColor: alpha("#10b981", 0.3), }} > ✅ Your final podcast video has been created successfully! {/* Video Preview */} {/* Download Button */} ) : ( Ready to export! Click below to combine all {script.scenes.length} scene videos into your final podcast video. {combiningVideos && ( {combiningProgress?.message || "Combining videos..."} {combiningProgress && ( {combiningProgress.progress.toFixed(0)}% )} {combiningProgress && combiningProgress.progress < 100 && ( Video encoding in progress. This may take a few minutes... )} )} )} )} {/* Footer - Video Generation Focus */} }> Back to Script {allVideosReady ? ( 🎉 All scene videos ready! Scroll up to combine them into your final podcast video. ) : allScenesCompleted ? ( 🎉 All scenes ready for video generation! Generate videos for individual scenes above. ) : ( Complete audio and image generation for all scenes to enable video generation. )} ); };