import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { api, assetFileURL, cancelJob, connectWS, deleteAsset, deleteJob, getMe, getSettings, listAssets, listJobs, listOperators, logout, retryJob, runJob, uploadFile, } from "./api"; import Viewer from "./Viewer"; import SplatViewer from "./SplatViewer"; import Settings from "./Settings"; import Login from "./Login"; import Dashboard from "./Dashboard"; const MODEL_EXTS = ["glb", "gltf", "obj", "fbx", "ply", "stl"]; const fmtSize = (n) => n > 1e9 ? (n / 1e9).toFixed(1) + " GB" : n > 1e6 ? (n / 1e6).toFixed(1) + " MB" : n > 1e3 ? (n / 1e3).toFixed(0) + " KB" : n + " B"; const KIND_ICON = { video: "๐ŸŽฌ", image: "๐Ÿ–ผ", model: "๐ŸงŠ", splat: "โœจ", frames: "๐ŸŽž", colmap_dataset: "๐Ÿ“", folder: "๐Ÿ“", other: "๐Ÿ“„" }; const isViewable = (a) => a && (a.kind === "splat" || MODEL_EXTS.includes(a.name.split(".").pop().toLowerCase())); function ParamForm({ schema, values, onChange }) { const props = schema?.properties || {}; if (!Object.keys(props).length) return

No parameters.

; return (
{Object.entries(props).map(([key, def]) => { const val = values[key] ?? def.default ?? ""; const set = (v) => onChange({ ...values, [key]: v }); return ( ); })}
); } const maybeNum = (def, v) => (def.type === "integer" ? parseInt(v) : def.type === "number" ? parseFloat(v) : v); function AssetPreview({ asset }) { const [folder, setFolder] = useState(null); const ext = asset ? asset.name.split(".").pop().toLowerCase() : ""; useEffect(() => { setFolder(null); if (asset && (asset.kind === "frames" || asset.kind === "folder" || asset.kind === "colmap_dataset")) { api(assetFileURL(asset.id)).then(setFolder).catch(() => {}); } }, [asset?.id]); if (!asset) return
Select an asset โ€” or drop / paste files anywhere
; if (asset.kind === "splat") return ; if (MODEL_EXTS.includes(ext)) return ; if (asset.kind === "image") return
{asset.name}
; if (asset.kind === "video") return
; if (folder?.files) { const imgs = folder.files.filter((f) => /\.(jpg|jpeg|png)$/i.test(f)); return (
{imgs.slice(0, 60).map((f) => {f})} {imgs.length > 60 && +{imgs.length - 60} more} {!imgs.length && {folder.files.length} files (no image preview)}
); } if (ext === "json") return ; return
No preview for .{ext} โ€” download to use it
; } function JsonPreview({ url }) { const [text, setText] = useState("โ€ฆ"); useEffect(() => { fetch(url).then((r) => r.text()).then((t) => setText(t.slice(0, 20000))); }, [url]); return
{text}
; } function CompareGrid({ assets }) { return (
{assets.map((a) => (
{a.name} ยท {fmtSize(a.size)}{a.meta?.tool ? ` ยท ${a.meta.tool}` : ""}
{a.kind === "splat" ? : }
))}
); } export default function App() { const [user, setUser] = useState(undefined); // undefined=loading, null=logged out const [view, setView] = useState("workspace"); // workspace | dashboard const [assets, setAssets] = useState([]); const [operators, setOperators] = useState([]); const [jobs, setJobs] = useState([]); const [settings, setSettings] = useState({ _env_set: [] }); const [selected, setSelected] = useState(null); const [opId, setOpId] = useState(null); const [params, setParams] = useState({}); const [openJob, setOpenJob] = useState(null); const [showSettings, setShowSettings] = useState(false); const [compareMode, setCompareMode] = useState(false); const [compareSel, setCompareSel] = useState([]); const fileInput = useRef(null); const wsRef = useRef(null); const refreshAssets = useCallback(() => listAssets().then(setAssets).catch(() => {}), []); const refreshJobs = useCallback(() => listJobs().then(setJobs).catch(() => {}), []); const refreshSettings = useCallback(() => getSettings().then(setSettings).catch(() => {}), []); // auth bootstrap useEffect(() => { getMe().then(setUser).catch(() => setUser(null)); const onUnauth = () => setUser(null); window.addEventListener("mb-unauth", onUnauth); return () => window.removeEventListener("mb-unauth", onUnauth); }, []); // load workspace data + connect the live socket once, after login useEffect(() => { if (!user) return; refreshAssets(); refreshJobs(); if (user.is_owner) refreshSettings(); listOperators().then(setOperators).catch(() => {}); if (!wsRef.current) { wsRef.current = connectWS((msg) => { if (msg.type === "job") { setJobs((js) => { const i = js.findIndex((j) => j.id === msg.job.id); if (i === -1) return [msg.job, ...js]; const copy = [...js]; copy[i] = msg.job; return copy; }); } if (msg.type === "assets_changed") refreshAssets(); }); } }, [user?.username]); const doLogout = async () => { try { await logout(); } catch { /* ignore */ } window.location.reload(); }; useEffect(() => { const drop = async (e) => { e.preventDefault(); for (const f of e.dataTransfer?.files || []) await uploadFile(f); refreshAssets(); }; const paste = async (e) => { for (const it of e.clipboardData?.items || []) { const f = it.getAsFile?.(); if (f) await uploadFile(f); } refreshAssets(); }; const prevent = (e) => e.preventDefault(); window.addEventListener("drop", drop); window.addEventListener("dragover", prevent); window.addEventListener("paste", paste); return () => { window.removeEventListener("drop", drop); window.removeEventListener("dragover", prevent); window.removeEventListener("paste", paste); }; }, [refreshAssets]); const selectedAsset = assets.find((a) => a.id === selected) || null; const envSet = settings._env_set || []; // no-input operators (accepts: []) are always available; others filter by asset kind const usableOps = useMemo(() => operators.filter((op) => !op.accepts.length || !selectedAsset || op.accepts.includes(selectedAsset.kind)), [operators, selectedAsset]); const opGroups = useMemo(() => { const groups = {}; for (const op of usableOps) (groups[op.category || "other"] ||= []).push(op); return groups; }, [usableOps]); const activeOp = operators.find((o) => o.id === opId); const needsInput = (activeOp?.accepts?.length ?? 0) > 0; const gatedEnv = (activeOp?.requires_env || []).filter((e) => !envSet.includes(e)); const gated = gatedEnv.length > 0; useEffect(() => { if (activeOp) { const defaults = {}; for (const [k, d] of Object.entries(activeOp.params_schema?.properties || {})) if (d.default !== undefined) defaults[k] = d.default; setParams(defaults); } }, [opId]); useEffect(() => { if (usableOps.length && !usableOps.find((o) => o.id === opId)) setOpId(usableOps[0].id); }, [usableOps]); const launch = async () => { if (!activeOp || gated || (needsInput && !selectedAsset)) return; await runJob(activeOp.id, needsInput ? selectedAsset.id : null, params); refreshJobs(); }; const clickAsset = (a) => { if (compareMode && isViewable(a)) { setCompareSel((s) => s.includes(a.id) ? s.filter((x) => x !== a.id) : [...s, a.id]); } else { setSelected(a.id); } }; const compareAssets = compareSel.map((id) => assets.find((a) => a.id === id)).filter(Boolean); if (user === undefined) return

โ€ฆ

; if (user === null) return ; return (

MODELBEAST

M3 Ultra ยท 256GB ยท local-first 3D factory
{user.is_owner && } {user.username}{user.is_owner ? " ยท owner" : ""} { for (const f of e.target.files) await uploadFile(f); e.target.value = ""; refreshAssets(); }} />
{view === "dashboard" ? : compareMode && compareAssets.length ? : }
{showSettings && user.is_owner && setShowSettings(false)} onSaved={setSettings} />}
); }