// screens-create.jsx — real Mini App Create hub + project flows const { useState: _cs_useState, useEffect: _cs_useEffect } = React; const FS_STEPS = ['source','target','quality','process','result']; const MF_STEPS = ['target','faces','process','result']; const IG_STEPS = ['prompt','aspect','process','result']; function CreateScreen({ ctx }){ const { t: T, navigate, premium } = ctx; return (
}/>
navigate('faceswap')} icon={} accent="purple" title={T('face_swap')} sub={T('face_swap_sub')} cost={T('cost_uses_photo') + ' / ' + T('cost_uses_video')} /> navigate('multiface')} icon={} accent="gold" title={T('multiface')} sub={T('multiface_sub')} cost={T('cost_multiface')} locked={premium === 'free'} /> navigate('imagegen')} icon={} accent="cyan" title={T('image_gen')} sub={T('image_gen_sub')} cost={T('cost_uses_photo')} />
); } function CreateModeCard({ icon, title, sub, cost, locked, accent, onClick }){ return ( ); } function FilePickerCard({ title, subtitle, accept, file, onFile, icon, disabled }){ return ( ); } function formatFileSize(bytes){ if (!bytes && bytes !== 0) return ''; if (bytes < 1024 * 1024) return Math.max(1, Math.round(bytes / 1024)) + ' KB'; return (bytes / (1024 * 1024)).toFixed(bytes > 100 * 1024 * 1024 ? 0 : 1) + ' MB'; } function statusLabel(project){ const status = project && project.status; if (status === 'SUCCESS') return 'Ready'; if (status === 'FAILED') return 'Failed'; if (project && project.started_at) return 'Processing'; return 'Queued'; } function MiniAppError({ message, onCredits }){ if (!message) return null; const payment = String(message).toLowerCase().includes('credit') || String(message).includes('402'); return (
{message} {payment && onCredits && }
); } function JobProcessing({ ctx, initialProject, onReady }){ const [project, setProject] = _cs_useState(initialProject); const [error, setError] = _cs_useState(null); _cs_useEffect(() => { let cancelled = false; let timer = null; async function poll(){ if (!project || !project.job_id) return; try { const data = await window.RavanaMiniApp.getProject(project.job_id); if (cancelled) return; const next = data.project; setProject(next); if (next.status === 'SUCCESS') { onReady(next); return; } if (next.status === 'FAILED') { setError('Processing failed. Try another file.'); return; } timer = window.setTimeout(poll, 4000); } catch (err) { if (!cancelled) setError(err.message || 'Status check failed.'); timer = window.setTimeout(poll, 6000); } } timer = window.setTimeout(poll, 1200); return () => { cancelled = true; if (timer) window.clearTimeout(timer); }; }, [project && project.job_id]); return (
{statusLabel(project)}
{project && project.queue_name ? project.queue_name.replaceAll('_', ' ') : 'Waiting for worker'}
{project && }
); } function ResultView({ ctx, project, title, onAgain }){ const url = project && project.result_url; const isVideoResult = project && String(project.project_type || '').includes('video'); return (
{url ? ( isVideoResult ? (
{url && }
); } function FaceSwapFlow({ ctx }){ const { t: T, navigate } = ctx; const [step, setStep] = _cs_useState(0); const [source, setSource] = _cs_useState(null); const [target, setTarget] = _cs_useState(null); const [quality, setQuality] = _cs_useState('simple'); const [project, setProject] = _cs_useState(null); const [error, setError] = _cs_useState(null); const [busy, setBusy] = _cs_useState(false); const submit = async () => { if (!source || !target) return; setBusy(true); setError(null); try { const sourceUpload = await window.RavanaMiniApp.uploadFile(source); const targetUpload = await window.RavanaMiniApp.uploadFile(target); const data = await window.RavanaMiniApp.createFaceSwap({ source_upload_id: sourceUpload.upload_id, target_upload_id: targetUpload.upload_id, regime: quality, }); setProject(data.project); setStep(3); } catch (err) { setError(err.message || 'Failed to start Face Swap.'); } finally { setBusy(false); } }; const back = () => { if (step > 0 && step < 3) setStep(step - 1); else navigate('create'); }; return (
}/> navigate('credits')}/> {step === 0 &&
{T('upload_source')}
}/>
} {step === 1 &&
{T('upload_target')}
}/>
} {step === 2 &&
{T('choose_quality')}
} {step === 3 && { setProject(p); setStep(4); }}/>} {step === 4 && { setStep(0); setSource(null); setTarget(null); setProject(null); }}/>}
); } function MultiFaceFlow({ ctx }){ const { t: T, navigate, premium } = ctx; const [step, setStep] = _cs_useState(0); const [target, setTarget] = _cs_useState(null); const [analysis, setAnalysis] = _cs_useState(null); const [sources, setSources] = _cs_useState({}); const [project, setProject] = _cs_useState(null); const [quality, setQuality] = _cs_useState('simple'); const [busy, setBusy] = _cs_useState(false); const [error, setError] = _cs_useState(null); const analyze = async () => { if (!target) return; setBusy(true); setError(null); try { const uploaded = await window.RavanaMiniApp.uploadFile(target); const data = await window.RavanaMiniApp.analyzeMultiface({ target_upload_id: uploaded.upload_id }); setAnalysis(data); setStep(1); } catch (err) { setError(err.message || 'Face analysis failed.'); } finally { setBusy(false); } }; const submit = async () => { if (!analysis) return; setBusy(true); setError(null); try { const mapping = {}; for (const face of analysis.faces || []) { const file = sources[face.index]; if (!file) continue; const uploaded = await window.RavanaMiniApp.uploadFile(file); mapping[String(face.index)] = uploaded.upload_id; } const data = await window.RavanaMiniApp.submitMultiface({ analysis_id: analysis.analysis_id, face_mapping: mapping, regime: quality, }); setProject(data.project); setStep(2); } catch (err) { setError(err.message || 'Failed to start MultiFace.'); } finally { setBusy(false); } }; if (premium === 'free') { return (
navigate('create')} right={}/>
{T('multiface_paywall_title')}
{T('multiface_paywall_sub')}
); } return (
step > 0 && step < 2 ? setStep(step - 1) : navigate('create')} right={}/> navigate('credits')}/> {step === 0 &&
{T('upload_target')}
}/>
} {step === 1 &&
Detected faces
{(analysis && analysis.faces || []).map(face => (
Face {Number(face.index) + 1}
{sources[face.index] ? sources[face.index].name : 'Choose replacement source'}
))}
} {step === 2 && { setProject(p); setStep(3); }}/>} {step === 3 && { setStep(0); setTarget(null); setAnalysis(null); setSources({}); setProject(null); }}/>}
); } function ImageGenFlow({ ctx }){ const { t: T, navigate } = ctx; const [step, setStep] = _cs_useState(0); const [prompt, setPrompt] = _cs_useState(''); const [aspect, setAspect] = _cs_useState('1:1'); const [project, setProject] = _cs_useState(null); const [busy, setBusy] = _cs_useState(false); const [error, setError] = _cs_useState(null); const submit = async () => { setBusy(true); setError(null); try { const data = await window.RavanaMiniApp.createImageGen({ prompt, aspect_ratio: aspect }); setProject(data.project); setStep(2); } catch (err) { setError(err.message || 'Failed to start Image Gen.'); } finally { setBusy(false); } }; return (
step > 0 && step < 2 ? setStep(step - 1) : navigate('create')} right={}/> navigate('credits')}/> {step === 0 &&