// Live app entry — talks to real OPRT API instead of mock data const { useState, useEffect } = React; function adaptInstance(row, i = 0) { const hues = [162, 245, 295, 75, 20, 205, 25, 330]; const h = hues[i % hues.length]; const initials = (row.display_name || row.name || row.agent_id || "?") .split(" ").slice(0, 2).map(w => w[0]).join("").toUpperCase(); return { id: row.agent_id || row.id, uuid: row.id, name: row.display_name || row.name, contact: row.contact, email: row.email, industry: row.industry, telegram: row.telegram_bot_username || "—", status: row.status, createdAt: (row.created_at || "").slice(0, 10), plan: row.plan, skills: row.skills || [], skillDetails: row.skillDetails || [], tokensIn: Number(row.tokens_in ?? 0), tokensOut: Number(row.tokens_out ?? 0), cost: Number(row.cost_eur ?? 0), messages: Number(row.messages ?? 0), apiProvider: row.provider, model: row.model, spark: row.spark || Array(16).fill(0).map((_, i) => 20 + Math.sin(i / 2) * 10 + Math.random() * 10), logoBg: `oklch(0.55 0.15 ${h})`, logoBg2: `oklch(0.65 0.14 ${h})`, initials, }; } function LiveApp() { const [route, setRoute] = useState(() => localStorage.getItem("oprt.route") || "dashboard"); const [instanceId, setInstanceId] = useState(() => localStorage.getItem("oprt.instanceId") || null); const [theme, setTheme] = useState(() => localStorage.getItem("oprt.theme") || "dark"); const [instances, setInstances] = useState([]); const [skills, setSkills] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [editMode, setEditMode] = useState(false); const [showSettings, setShowSettings] = useState(false); useEffect(() => { document.documentElement.classList.toggle("light", theme === "light"); localStorage.setItem("oprt.theme", theme); }, [theme]); useEffect(() => { localStorage.setItem("oprt.route", route); }, [route]); useEffect(() => { if (instanceId) localStorage.setItem("oprt.instanceId", instanceId); }, [instanceId]); const loadData = async () => { setLoading(true); setError(null); try { const [ins, sk] = await Promise.all([ OprtAPI.listInstances(), OprtAPI.listSkills(), ]); const adapted = (ins || []).map(adaptInstance); setInstances(adapted); setSkills(sk || []); window.APP_DATA = { INSTANCES: adapted, SKILLS: (sk || []).map(s => s.name), }; } catch (e) { setError(e.message || String(e)); window.APP_DATA = window.APP_DATA || { INSTANCES: [], SKILLS: [] }; } finally { setLoading(false); } }; useEffect(() => { loadData(); }, []); useEffect(() => { const onMsg = (e) => { if (!e.data) return; if (e.data.type === "__activate_edit_mode") setEditMode(true); if (e.data.type === "__deactivate_edit_mode") setEditMode(false); }; window.addEventListener("message", onMsg); window.parent.postMessage({ type: "__edit_mode_available" }, "*"); return () => window.removeEventListener("message", onMsg); }, []); const [detailInstance, setDetailInstance] = useState(null); const [detailLoading, setDetailLoading] = useState(false); const onNav = (r) => { setRoute(r); if (r !== "instance") { setInstanceId(null); setDetailInstance(null); } }; const openInstance = async (id) => { setInstanceId(id); setRoute("instance"); setDetailLoading(true); try { const listInst = instances.find(i => i.id === id || i.uuid === id); const uuid = listInst ? listInst.uuid : id; const detail = await OprtAPI.getInstance(uuid); const adapted = adaptInstance(detail, instances.findIndex(i => i.uuid === uuid)); setDetailInstance(adapted); } catch (e) { setDetailInstance(instances.find(i => i.id === id || i.uuid === id) || null); } finally { setDetailLoading(false); } }; const currentInstance = detailInstance || instances.find(i => i.id === instanceId || i.uuid === instanceId); let crumbs = [{ label: "OPRT.AI", go: "dashboard" }]; let view = null; if (loading) { view =
Provisioniert eine neue OpenClaw-Instanz über POST /api/instances