import React, { useState } from 'react'; import { Grid, Box, Button, Typography, Stack, CircularProgress } from '@mui/material'; import { VideoStudioLayout } from '../../VideoStudioLayout'; import { useAvatarVideo } from './hooks/useAvatarVideo'; import { ImageUpload, AudioUpload, AvatarSettings } from './components'; import { aiApiClient } from '../../../../api/client'; import PlayArrowIcon from '@mui/icons-material/PlayArrow'; export const AvatarVideo: React.FC = () => { const { imageFile, imagePreview, audioFile, audioPreview, resolution, model, prompt, seed, setImageFile, setAudioFile, setResolution, setModel, setPrompt, setSeed, canGenerate, costHint, } = useAvatarVideo(); const [generating, setGenerating] = useState(false); const [taskId, setTaskId] = useState(null); const [progress, setProgress] = useState(0); const [statusMessage, setStatusMessage] = useState(''); const [error, setError] = useState(null); const [result, setResult] = useState<{ video_url: string; cost: number } | null>(null); const handleGenerate = async () => { if (!imageFile || !audioFile) return; setGenerating(true); setError(null); setResult(null); setProgress(0); setStatusMessage('Starting avatar generation...'); try { // Create FormData const formData = new FormData(); formData.append('image', imageFile); formData.append('audio', audioFile); formData.append('resolution', resolution); formData.append('model', model); if (prompt) { formData.append('prompt', prompt); } if (seed !== null) { formData.append('seed', seed.toString()); } // Submit generation request const response = await aiApiClient.post('/api/video-studio/avatar/create-async', formData, { headers: { 'Content-Type': 'multipart/form-data', }, }); const { task_id } = response.data; setTaskId(task_id); setStatusMessage('Avatar generation started. Polling for updates...'); // Poll for status const pollInterval = setInterval(async () => { try { const statusResponse = await aiApiClient.get(`/api/video-studio/task/${task_id}/status`); const status = statusResponse.data; setProgress(status.progress || 0); setStatusMessage(status.message || 'Processing...'); if (status.status === 'completed') { clearInterval(pollInterval); setGenerating(false); setResult(status.result); setStatusMessage('Avatar generation complete!'); } else if (status.status === 'failed') { clearInterval(pollInterval); setGenerating(false); setError(status.error || 'Avatar generation failed'); setStatusMessage('Generation failed'); } } catch (err: any) { console.error('Polling error:', err); // Continue polling on transient errors } }, 2000); // Poll every 2 seconds // Cleanup on unmount return () => clearInterval(pollInterval); } catch (err: any) { setGenerating(false); setError(err.response?.data?.detail || err.message || 'Failed to start avatar generation'); setStatusMessage('Failed to start generation'); } }; return ( {/* Left Panel: Uploads and Settings */} {/* Cost and Generate */} Estimated Cost {costHint} {error && ( {error} )} {generating && ( {statusMessage} {progress > 0 && ( Progress: {progress.toFixed(0)}% )} )} {/* Right Panel: Preview/Result */} {result ? ( Avatar Generated! ) : ( {imagePreview && audioPreview ? 'Upload your photo and audio, then click "Create Avatar" to generate your talking avatar.' : 'Upload a photo and audio to create your talking avatar.'} )} ); }; export default AvatarVideo;