import * as React from 'react'; import { Avatar, Voice, VideoScene, VideoProject, AIGCVideo, AIGCSettings, MediaSource, ScriptContent, ScriptVersion } from '../types'; import { Icon } from './ui/Icon'; import { Tabs } from './ui/Tabs'; import { polishScript } from '../services/geminiService'; // --- MOCK DATA --- const mockAvatars: Avatar[] = [ { id: 'av1', name: 'Robotix', imageUrl: 'https://api.dicebear.com/8.x/bottts-neutral/svg?seed=robotix' }, { id: 'av2', name: 'Cyber', imageUrl: 'https://api.dicebear.com/8.x/bottts-neutral/svg?seed=cyber' }, { id: 'av3', name: 'Gizmo', imageUrl: 'https://api.dicebear.com/8.x/bottts-neutral/svg?seed=gizmo' }, ]; const mockVoices: Voice[] = [ { id: 'vo1', name: 'Alloy', accent: 'Neutral' }, { id: 'vo2', name: 'Echo', accent: 'Warm' }, { id: 'vo3', name: 'Fable', accent: 'Professional' }]; const mockBackgrounds = { colors: ['#111827', '#1f2937', '#374151', '#4b5563', '#6b7280', '#9ca3af', '#d1d5db', '#f3f4f6'], images: [ { type: 'url', value: 'https://picsum.photos/seed/bg1/800/450' }, { type: 'url', value: 'https://picsum.photos/seed/bg2/800/450' }, { type: 'url', value: 'https://picsum.photos/seed/bg3/800/450' }, { type: 'url', value: 'https://picsum.photos/seed/bg4/800/450' } ] as MediaSource[] }; const initialScriptVersionId = `v-init-${Date.now()}`; const initialProject: VideoProject = { id: `proj-${Date.now()}`, name: 'My First Video', aspectRatio: '16:9', scenes: [{ id: `scene-${Date.now()}`, script: { versions: [{ id: initialScriptVersionId, text: 'Welcome to our presentation!' }], selectedVersionId: initialScriptVersionId }, avatarId: 'av1', voiceId: 'vo1', background: { type: 'color', value: '#1f2937' }, avatarPosition: { x: 50, y: 10 }, avatarScale: 1, }] }; // --- Child Components --- const GenerationProgress: React.FC<{ progress: number; totalVideos: number; currentVideo: number }> = ({ progress, totalVideos, currentVideo }) => (

Generating Videos...

{`Processing video ${currentVideo} of ${totalVideos}. This may take a moment.`}

); const VideoCanvas: React.FC<{ scene: VideoScene; aspectRatio: '16:9' | '9:16'; onAvatarMove: (pos: { x: number, y: number }) => void; onAvatarScale: (newScale: number) => void; }> = ({ scene, aspectRatio, onAvatarMove, onAvatarScale }) => { const canvasRef = React.useRef(null); const avatarRef = React.useRef(null); // Interaction state const [isSelected, setIsSelected] = React.useState(false); const [interactionState, setInteractionState] = React.useState<'idle' | 'dragging' | 'resizing'>('idle'); // Refs to store start positions for calculations const interactionStartRef = React.useRef({ dragOffsetX: 0, dragOffsetY: 0, resizeStartX: 0, resizeStartWidth: 0, resizeStartScale: 1, }); const activeAvatar = mockAvatars.find(a => a.id === scene.avatarId); // Mouse Down on Avatar -> start dragging const handleAvatarMouseDown = (e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); if (!avatarRef.current || !canvasRef.current) return; setIsSelected(true); setInteractionState('dragging'); const avatarRect = avatarRef.current.getBoundingClientRect(); interactionStartRef.current = { ...interactionStartRef.current, dragOffsetX: e.clientX - avatarRect.left, dragOffsetY: e.clientY - avatarRect.top, }; }; // Mouse Down on Resize Handle -> start resizing const handleResizeMouseDown = (e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); if (!avatarRef.current) return; setInteractionState('resizing'); interactionStartRef.current = { ...interactionStartRef.current, resizeStartX: e.clientX, resizeStartWidth: avatarRef.current.clientWidth, resizeStartScale: scene.avatarScale, }; }; // Effect for global mouse move and mouse up events React.useEffect(() => { const handleMouseMove = (e: MouseEvent) => { if (interactionState === 'idle' || !canvasRef.current) return; e.preventDefault(); if (interactionState === 'dragging') { if (!avatarRef.current) return; const canvasRect = canvasRef.current.getBoundingClientRect(); const newAvatarLeft = e.clientX - canvasRect.left - interactionStartRef.current.dragOffsetX; const newAvatarTop = e.clientY - canvasRect.top - interactionStartRef.current.dragOffsetY; const avatarWidth = avatarRef.current.offsetWidth; const avatarHeight = avatarRef.current.offsetHeight; const newXPercent = ((newAvatarLeft + avatarWidth / 2) / canvasRect.width) * 100; const newYFromBottom = canvasRect.height - newAvatarTop - avatarHeight; const newYPercent = (newYFromBottom / canvasRect.height) * 100; const clampedX = Math.max((avatarWidth / 2 / canvasRect.width) * 100, Math.min(100 - (avatarWidth / 2 / canvasRect.width) * 100, newXPercent)); const clampedY = Math.max(0, Math.min(100 - (avatarHeight / canvasRect.height) * 100, newYPercent)); onAvatarMove({ x: clampedX, y: clampedY }); } if (interactionState === 'resizing') { const deltaX = e.clientX - interactionStartRef.current.resizeStartX; const newWidth = interactionStartRef.current.resizeStartWidth + deltaX; const baseWidth = interactionStartRef.current.resizeStartWidth / interactionStartRef.current.resizeStartScale; if (baseWidth === 0) return; const newScale = newWidth / baseWidth; onAvatarScale(Math.max(0.2, Math.min(3.0, newScale))); } }; const handleMouseUp = () => { setInteractionState('idle'); }; window.addEventListener('mousemove', handleMouseMove); window.addEventListener('mouseup', handleMouseUp); return () => { window.removeEventListener('mousemove', handleMouseMove); window.removeEventListener('mouseup', handleMouseUp); }; }, [interactionState, onAvatarMove, onAvatarScale]); // Deselect avatar when clicking on canvas background const handleCanvasMouseDown = (e: React.MouseEvent) => { if (e.target === e.currentTarget) { setIsSelected(false); } }; const isVideoBg = scene.background.type === 'video'; const backgroundStyle = scene.background.type !== 'video' ? { background: scene.background.type === 'color' ? scene.background.value : `url(${scene.background.value}) center/cover` } : {}; return (
{isVideoBg && (