import { useState, useCallback, useRef } from 'react'; import { motion } from 'motion/react'; import { VideoFilled, FileTextFilled } from '../icons/FilledIcons'; import { CHANNEL_OPTIONS, MUSIC_TRACKS, type StudioState, type GenerateOutputType } from '../../types/studio'; import { generateImage, type GenerateResult } from '../../services/geminiImageGen'; interface Props { studioState: StudioState; outputType: GenerateOutputType; onOutputTypeChange: (type: GenerateOutputType) => void; } type GenerateStatus = 'idle' | 'generating' | 'done' | 'error'; export default function GeneratePreviewStep({ studioState, outputType, onOutputTypeChange }: Props) { const [status, setStatus] = useState('idle'); const [result, setResult] = useState(null); const [errorMsg, setErrorMsg] = useState(''); const downloadRef = useRef(null); const activeChannel = CHANNEL_OPTIONS.find(c => c.channel === studioState.channel); const activeFormat = activeChannel?.formats.find(f => f.key === studioState.format); const activeTrack = MUSIC_TRACKS.find(t => t.id === studioState.sound.trackId); const handleGenerate = useCallback(async () => { setStatus('generating'); setErrorMsg(''); setResult(null); if (outputType === 'image') { try { const res = await generateImage(studioState); setResult(res); setStatus('done'); } catch (err) { setErrorMsg(err instanceof Error ? err.message : 'Image generation failed'); setStatus('error'); } } else { // Video: placeholder (Creatomate integration needed) setTimeout(() => setStatus('done'), 4000); } }, [studioState, outputType]); const handleReset = useCallback(() => { setStatus('idle'); setResult(null); setErrorMsg(''); }, []); const handleDownload = useCallback(() => { if (!result?.imageDataUrl || !downloadRef.current) return; const link = downloadRef.current; link.href = result.imageDataUrl; link.download = `INFINITH_${activeChannel?.label ?? 'content'}_${activeFormat?.key ?? 'image'}.png`; link.click(); }, [result, activeChannel, activeFormat]); const aspectClass = activeFormat?.aspectRatio === '9:16' ? 'w-[240px] h-[426px]' : activeFormat?.aspectRatio === '1:1' ? 'w-[340px] h-[340px]' : activeFormat?.aspectRatio === '4:5' ? 'w-[300px] h-[375px]' : 'w-[426px] h-[240px]'; return (
{/* Hidden download anchor */} {/* Output Type Tabs */}
{([ { key: 'image' as const, label: '이미지 생성', Icon: FileTextFilled }, { key: 'video' as const, label: '영상 생성', Icon: VideoFilled }, ]).map(tab => ( ))}
{/* Settings Summary */}

설정 요약

{/* Generate Button */} {(status === 'idle' || status === 'error') && ( )} {status === 'error' && (

{errorMsg}

)} {status === 'done' && (
{result?.imageDataUrl && ( )}
)}
{/* Preview Area */}

프리뷰

{status === 'idle' && (
{outputType === 'image' ? ( ) : ( )}

설정을 확인하고 생성 버튼을 눌러주세요

)} {status === 'generating' && (

{outputType === 'image' ? '이미지' : '영상'} 생성 중...

AI가 콘텐츠를 제작하고 있습니다

)} {status === 'error' && (

{errorMsg}

)} {status === 'done' && result?.imageDataUrl && ( )} {status === 'done' && !result?.imageDataUrl && (

생성 완료

{activeChannel?.label} {activeFormat?.label} {outputType === 'image' ? ' 이미지' : ' 영상'}이 준비되었습니다

{activeFormat?.aspectRatio} | {outputType === 'image' ? 'PNG' : 'MP4'}
)}
); } function SummaryRow({ label, value }: { label: string; value: string }) { return (
{label} {value}
); }