// Main app shell — wired to the FastAPI backend at /api/*. const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{ "accent": "warm", "compareMode": "toggle", "showOverlays": false }/*EDITMODE-END*/; // ── API client ────────────────────────────────────────────────────────────── const api = { async samples() { const r = await fetch("/api/samples"); return r.json(); }, async uploadFile(file) { const fd = new FormData(); fd.append("file", file); const r = await fetch("/api/session", { method: "POST", body: fd }); if (!r.ok) throw new Error((await r.json()).detail || r.statusText); return r.json(); }, async loadSample(n) { const r = await fetch(`/api/sample/${n}`, { method: "POST" }); if (!r.ok) throw new Error((await r.json()).detail || r.statusText); return r.json(); }, async warp(sessionId, params) { const r = await fetch("/api/warp", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ session_id: sessionId, ...params }), }); if (!r.ok) throw new Error((await r.json()).detail || r.statusText); return r.json(); }, async recommend(sessionId, region, gender) { const r = await fetch("/api/recommend", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ session_id: sessionId, region, gender }), }); if (!r.ok) throw new Error((await r.json()).detail || r.statusText); return r.json(); }, async heal(sessionId, warpParams, healParams) { const ctrl = new AbortController(); const timer = setTimeout(() => ctrl.abort(), 150000); // hard cap so it never hangs try { const r = await fetch("/api/heal", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ session_id: sessionId, warp: { session_id: sessionId, ...warpParams }, ...healParams, }), signal: ctrl.signal, }); if (!r.ok) throw new Error((await r.json()).detail || r.statusText); return r.json(); } catch (e) { if (e.name === "AbortError") throw new Error("Generation timed out after 150s — please try again."); throw e; } finally { clearTimeout(timer); } }, async logHistory(sessionId, desc, detail, kind = "EVENT") { return fetch("/api/history", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ session_id: sessionId, desc, detail, kind }), }); }, }; window.api = api; // Pick the single most-changed measurement from a warp's deltas, so the live // warp card always reflects what actually moved (hump edits don't touch // projection, alar edits don't touch the bridge, etc.). const DELTA_UNITS = { alar_mm: "mm", dorsal_mm: "mm", bridge_mm: "mm", tip_w_mm: "mm", nose_len_mm: "mm", projection_mm: "mm", nasolabial: "°", tip_rotation: "°", symmetry: "" }; function dominantDelta(deltas) { if (!deltas) return "live"; let best = null; for (const k in DELTA_UNITS) { const v = deltas[k]; if (typeof v === "number" && Math.abs(v) > Math.abs(best ? best.v : 0)) best = { k, v }; } if (best && best.v !== 0) { const digits = best.k === "symmetry" ? 2 : 1; return `Δ ${best.v > 0 ? "+" : ""}${best.v.toFixed(digits)}${DELTA_UNITS[best.k]}`; } // Fallback for silhouette-only profile/3-4 edits that move no named metric: // show how far the nose moved on average so the slider still reads as live. if (typeof deltas.warp_mm === "number" && deltas.warp_mm > 0.05) { return `~${deltas.warp_mm.toFixed(1)}mm moved`; } return "live"; } const emptyWarp = { alar: 0, hump: 0, tipLift: 0, humpProf: 0, tipRot: 0, bridgeWidth: 0, tipWidth: 0, noseLen: 0 }; const emptyHeal = { strength: 0.35, guidance: 3.0, steps: 28, feather: 18 }; const DEFAULT_PROMPT = "photoreal skin texture, natural lighting, preserve identity, seamless integration"; const initialState = () => ({ session: null, loadingSession: false, sessionError: null, warp: { ...emptyWarp }, warpDebounce: null, warping: false, warpImageUrl: null, warpKey: null, deltas: {}, heal: { ...emptyHeal }, healPrompt: DEFAULT_PROMPT, healing: false, results: [], activeResult: null, preset: "Custom", view: "frontal", overlays: { landmarks: false, warp: false, mask: false }, compareMode: "toggle", step: "intake", region: null, gender: null, recommendation: null, intensityProfile: null, selectedStyle: null, selectedVariant: null, }); const App = () => { const tweaksState = window.useTweaks ? window.useTweaks(TWEAK_DEFAULTS) : null; const tweaks = tweaksState ? tweaksState[0] : TWEAK_DEFAULTS; const setTweak = tweaksState ? tweaksState[1] : () => {}; const [showConsent, setShowConsent] = React.useState(true); const [state, setState] = React.useState(initialState); const set = (patch) => setState(s => ({ ...s, ...(typeof patch === "function" ? patch(s) : patch) })); // Warm the face-detection model the moment the page loads, so the first // upload doesn't pay the model-init cold-start. Fire-and-forget. React.useEffect(() => { fetch("/api/warmup").catch(() => {}); }, []); React.useEffect(() => { const palettes = { warm: { bg: "#f4f1ec", bg2: "#ebe7e0", bgCard: "#fbfaf7", ink: "#1a1815", warn: "#b85a2a" }, cool: { bg: "#eef1f4", bg2: "#e2e7ec", bgCard: "#f8fafc", ink: "#15181c", warn: "#2a6fb8" }, dark: { bg: "#1a1815", bg2: "#252220", bgCard: "#2e2a26", ink: "#f4f1ec", warn: "#d68a5a" }, paper: { bg: "#f0ece2", bg2: "#e4dfd2", bgCard: "#faf6ea", ink: "#2a2620", warn: "#8a5a2a" }, }; const p = palettes[tweaks.accent] || palettes.warm; const r = document.documentElement; r.style.setProperty("--bg", p.bg); r.style.setProperty("--bg-2", p.bg2); r.style.setProperty("--bg-card", p.bgCard); r.style.setProperty("--ink", p.ink); r.style.setProperty("--warn", p.warn); if (tweaks.accent === "dark") { r.style.setProperty("--ink-2", "#d8d3ca"); r.style.setProperty("--ink-3", "#9c958a"); r.style.setProperty("--ink-4", "#6a6358"); r.style.setProperty("--hairline", "#3a3530"); r.style.setProperty("--hairline-2", "#4a443d"); } else { r.style.setProperty("--ink-2", "#3d3833"); r.style.setProperty("--ink-3", "#7a736a"); r.style.setProperty("--ink-4", "#aea69a"); r.style.setProperty("--hairline", "#d9d3c8"); r.style.setProperty("--hairline-2", "#c8c1b3"); } }, [tweaks.accent]); const onSessionLoaded = (payload) => { set({ session: payload, loadingSession: false, sessionError: null, view: payload.view.base === "reject" ? "frontal" : payload.view.base, warp: { ...emptyWarp }, deltas: {}, warpImageUrl: null, warpKey: null, recommendation: null, intensityProfile: null, selectedStyle: null, selectedVariant: null, results: [{ id: "orig", kind: "original", label: "Original", url: payload.image_url, time: payload.history[0]?.time || "00:00", metric: "T0", pinned: false, }], activeResult: "orig", step: "analysis", }); }; const loadFile = async (file) => { if (!file) return; set({ loadingSession: true, sessionError: null }); try { const payload = await api.uploadFile(file); onSessionLoaded(payload); } catch (e) { set({ loadingSession: false, sessionError: e.message }); } }; const loadSample = async (n) => { set({ loadingSession: true, sessionError: null }); try { const payload = await api.loadSample(n); onSessionLoaded(payload); } catch (e) { set({ loadingSession: false, sessionError: e.message }); } }; const triggerWarp = (nextWarp) => { if (!state.session) return; if (state.warpDebounce) clearTimeout(state.warpDebounce); const isZero = Object.values(nextWarp).every(v => v === 0); if (isZero) { set({ warp: nextWarp, warpImageUrl: null, warpKey: null, deltas: {}, warping: false }); return; } const t = setTimeout(async () => { set({ warping: true }); try { const out = await api.warp(state.session.session_id, nextWarp); setState(s => { if (JSON.stringify(s.warp) !== JSON.stringify(nextWarp)) return { ...s, warping: false }; let results = s.results.filter(r => r.id !== "warp-live"); results = [...results, { id: "warp-live", kind: "warp", label: "Warp · live", url: out.image_url, time: "now", // Show the most-changed metric, not just projection — otherwise a // hump-only edit (projection Δ 0) misleadingly reads "Δ 0.0 mm". metric: dominantDelta(out.deltas), pinned: false, key: out.key, }]; return { ...s, warping: false, warpImageUrl: out.image_url, warpKey: out.key, deltas: out.deltas, results }; }); } catch (e) { set({ warping: false, sessionError: e.message }); } }, 320); set({ warp: nextWarp, warpDebounce: t }); }; const runHealFor = async (warp, meta) => { if (!state.session) return; set({ healing: true }); try { const out = await api.heal( state.session.session_id, warp, { strength: state.heal.strength, guidance: state.heal.guidance, prompt: state.healPrompt, meta: meta || null }, ); setState(s => { const n = s.results.filter(r => r.kind === "heal").length + 1; const label = meta ? `${meta.style} · ${meta.variant}` : `Heal · v${n}`; const newCard = { id: "heal-" + out.key, kind: "heal", label, url: out.image_url, time: out.history_item.time, metric: `${out.elapsed_s.toFixed(1)}s`, pinned: false, key: out.key, meta: meta || null, }; const history = [out.history_item, ...s.session.history]; return { ...s, healing: false, results: [...s.results, newCard], activeResult: newCard.id, session: { ...s.session, history }, }; }); } catch (e) { set({ healing: false, sessionError: e.message }); } }; const runHeal = () => runHealFor(state.warp, null); const ctx = { state, set, loadFile, loadSample, triggerWarp, runHeal, runHealFor }; return ( <>
Refrakt·sim
Rhinoplasty studio
FAL · {state.session ? "connected" : "ready"} {state.session && VIEW · {state.session.view.tag}} {state.session && LANDMARKS · {state.session.landmarks_count}} {state.session && YAW · {state.session.view.yaw}°}
set({ step: s })} hasSession={!!state.session} />
© 2026 Refrakt · This is a simulation, not a medical recommendation.
Photo processed via fal.ai · encrypted in transit · HIPAA-aligned
{showConsent && setShowConsent(false)} />} {state.sessionError && (
set({ sessionError: null })}> {state.sessionError} · tap to dismiss
)} {window.TweaksPanel && ( setTweak("accent", v)} options={[ ["#f4f1ec", "#1a1815", "#b85a2a"], ["#eef1f4", "#15181c", "#2a6fb8"], ["#1a1815", "#f4f1ec", "#d68a5a"], ["#f0ece2", "#2a2620", "#8a5a2a"], ]} /> setState(initialState())}>Reset session )} ); }; const ConsentDialog = ({ onAccept }) => (

Before you begin.

What you make here is a simulation, not a promise. Refrakt predicts physically honest possibilities — your actual surgical result will be determined by your anatomy, your surgeon's hand, and how you heal.

  • 01Your photo is processed by our AI partner (fal.ai) and is never used for training.
  • 02Results are predictive visualizations. They are not a medical opinion.
  • 03You can share your session with a board-certified clinician at any time.
); ReactDOM.createRoot(document.getElementById("root")).render();