// 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 => (
))}
}
{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 &&
}
{step === 1 &&
{T('aspect_ratio')}
{['1:1','16:9','9:16','4:3','3:4'].map(ratio => (
))}
42 ? '...' : '') },
{ k: 'Aspect', v: aspect, tone: 'gold' },
{ k: 'Cost', v: '1 photo credit' },
]}/>
}
{step === 2 && { setProject(p); setStep(3); }}/>}
{step === 3 && { setStep(0); setPrompt(''); setProject(null); }}/>}
);
}
window.FaceSwapFlow = FaceSwapFlow;
window.MultiFaceFlow = MultiFaceFlow;
window.ImageGenFlow = ImageGenFlow;