import React, { useState } from 'react'; import { Box, Container, Typography, useTheme, Dialog, DialogTitle, DialogContent, IconButton } from '@mui/material'; import { useStoryWriterState } from '../../hooks/useStoryWriterState'; import { useStoryWriterPhaseNavigation } from '../../hooks/useStoryWriterPhaseNavigation'; import StorySetup from './Phases/StorySetup'; import StoryOutline from './Phases/StoryOutline'; import StoryWriting from './Phases/StoryWriting'; import StoryExport from './Phases/StoryExport'; import PhaseNavigation from './PhaseNavigation'; import { MultimediaToolbar } from './components/MultimediaToolbar'; import { storyWriterApi } from '../../services/storyWriterApi'; import { triggerSubscriptionError } from '../../api/client'; import CloseIcon from '@mui/icons-material/Close'; import { MultimediaSection } from './components/MultimediaSection'; import StoryWriterLanding from './StoryWriterLanding'; export const StoryWriter: React.FC = () => { const theme = useTheme(); // State management const state = useStoryWriterState(); // Multimedia generation state const [isGeneratingAudio, setIsGeneratingAudio] = useState(false); const [isGeneratingVideo, setIsGeneratingVideo] = useState(false); const [isMultimediaDialogOpen, setIsMultimediaDialogOpen] = useState(false); const [landingDismissed, setLandingDismissed] = useState(() => { if (typeof window === 'undefined') return false; return window.localStorage.getItem('storywriter:landingDismissed') === 'true'; }); // Phase navigation const { phases, currentPhase, navigateToPhase, } = useStoryWriterPhaseNavigation({ hasPremise: !!state.premise, hasOutline: !!state.outline, hasStoryContent: !!state.storyContent, isComplete: state.isComplete, }); // Reset handler const handleReset = () => { // Reset story state (this also clears localStorage) state.resetState(); if (typeof window !== 'undefined') { window.localStorage.removeItem('storywriter:landingDismissed'); } // Simplest approach: reload the page to ensure a clean slate if (typeof window !== 'undefined') { window.location.reload(); } }; const handleOpenMultimediaDialog = () => { setIsMultimediaDialogOpen(true); }; const handleCloseMultimediaDialog = () => { setIsMultimediaDialogOpen(false); }; // Audio generation handler const handleGenerateAudio = async () => { if (!state.enableNarration) { return; } if (!state.outlineScenes || state.outlineScenes.length === 0) { return; } setIsGeneratingAudio(true); try { const response = await storyWriterApi.generateSceneAudio({ scenes: state.outlineScenes, provider: state.audioProvider, lang: state.audioLang, slow: state.audioSlow, rate: state.audioRate, }); if (response.success && response.audio_files) { const audioMap = new Map(); response.audio_files.forEach((audio) => { if (audio.audio_url && !audio.error) { audioMap.set(audio.scene_number, audio.audio_url); } }); state.setSceneAudio(audioMap); state.setError(null); } } catch (err: any) { const status = err?.response?.status; if (status === 429 || status === 402) { await triggerSubscriptionError(err); } console.error('Audio generation failed:', err); } finally { setIsGeneratingAudio(false); } }; // Video generation handler const handleGenerateVideo = async () => { if (!state.enableVideoNarration) { return; } if (!state.outlineScenes || state.outlineScenes.length === 0) { return; } if (!state.sceneImages || state.sceneImages.size === 0) { return; } if (!state.sceneAudio || state.sceneAudio.size === 0) { return; } setIsGeneratingVideo(true); try { const imageUrls: string[] = []; const audioUrls: string[] = []; const scenes = state.outlineScenes; for (const scene of scenes) { const sceneNumber = scene.scene_number || scenes.indexOf(scene) + 1; const imageUrl = state.sceneImages?.get(sceneNumber); const audioUrl = state.sceneAudio?.get(sceneNumber); if (imageUrl && audioUrl) { imageUrls.push(imageUrl); audioUrls.push(audioUrl); } } if (imageUrls.length !== scenes.length || audioUrls.length !== scenes.length) { throw new Error('Number of images and audio files must match number of scenes'); } // Switch to async flow so UI can poll progress messages const start = await storyWriterApi.generateStoryVideoAsync({ scenes: scenes, image_urls: imageUrls, audio_urls: audioUrls, story_title: state.storySetting || 'Story', fps: state.videoFps, transition_duration: state.videoTransitionDuration, }); // Optional: set a lightweight spinner; export page shows detailed progress let done = false; while (!done) { await new Promise((r) => setTimeout(r, 1200)); const status = await storyWriterApi.getTaskStatus(start.task_id); if (status.status === 'completed') { const result = await storyWriterApi.getTaskResult(start.task_id); // @ts-ignore: async result includes video dict const video = (result as any).video || (result as any)?.result?.video; const finalUrl: string | undefined = video?.video_url; if (finalUrl) state.setStoryVideo(finalUrl); state.setError(null); done = true; } else if (status.status === 'failed') { throw new Error(status.error || 'Video generation failed'); } } } catch (err: any) { const status = err?.response?.status; if (status === 429 || status === 402) { await triggerSubscriptionError(err); } console.error('Video generation failed:', err); } finally { setIsGeneratingVideo(false); } }; const hasStoryProgress = Boolean(state.premise || state.outline || state.storyContent); const showLanding = !landingDismissed && !hasStoryProgress; const handleLandingStart = () => { setLandingDismissed(true); if (typeof window !== 'undefined') { window.localStorage.setItem('storywriter:landingDismissed', 'true'); } navigateToPhase('setup'); }; // Render phase content const renderPhaseContent = () => { switch (currentPhase) { case 'setup': return navigateToPhase('outline')} />; case 'outline': return navigateToPhase('writing')} />; case 'writing': return navigateToPhase('export')} />; case 'export': return ; default: return navigateToPhase('outline')} />; } }; if (showLanding) { return ( ); } return ( {/* Header with Phase Navigation and Multimedia Toolbar */} Story Writer Create compelling stories with AI assistance {/* Compact Phase Navigation */} {/* Multimedia Toolbar */} handleOpenMultimediaDialog()} /> {/* Phase Content */} {renderPhaseContent()} Multimedia Controls ); }; export default StoryWriter;