// Instance detail, API keys, metrics, skills, billing views function InstanceDetail({ instance, onNav, allSkills, onReload }) { const [tab, setTab] = React.useState("overview"); const [showOpenAI, setShowOpenAI] = React.useState(false); const [showAnthropic, setShowAnthropic] = React.useState(false); const inst = instance; if (!inst) return
Instance not found.
; const skillsBySparkSeed = inst.skills.map((_, i) => 10 + Math.floor(Math.random() * 40)); const days = Array.from({ length: 30 }, (_, i) => i); const tokSeriesIn = days.map((d) => (inst.tokensIn / 30) + Math.sin(d / 3) * (inst.tokensIn / 100) + Math.random() * (inst.tokensIn / 120)); const tokSeriesOut = days.map((d) => (inst.tokensOut / 30) + Math.cos(d / 4) * (inst.tokensOut / 100) + Math.random() * (inst.tokensOut / 130)); const dayLabels = days.map((d) => { const dt = new Date(2026, 2, 22 + d); return `${dt.getDate()}.${dt.getMonth() + 1}`; }); return (

{inst.name}

{inst.id}.oprt.chat · {inst.industry} · {inst.plan}

{["overview", "messages", "skills", "files", "keys", "billing", "logs", "settings"].map((t) => (
setTab(t)}> {t[0].toUpperCase() + t.slice(1)}
))}
{tab === "overview" && ( <>
v / 10000)}/> v / 5000)}/>

Token usage

· 30d

Customer info

Contact
{inst.contact}
Email
{inst.email}
Industry
{inst.industry}
Plan
{inst.plan}
Launched
{inst.createdAt}
Model
{inst.model}
Provider
{inst.apiProvider}
Telegram
{inst.telegram}

Active skills

· {inst.skills.length}
{inst.skills.map((s) => {s})}

Recent activity

{[ { t: "2 min ago", m: "Received Telegram message from +49 176 •••• 412" }, { t: "8 min ago", m: "Skill oprt-calendar · booked meeting for Mo 15:00" }, { t: "14 min ago", m: "Skill oprt-gmail · drafted reply to invoice thread" }, { t: "41 min ago", m: "Model call claude-sonnet-4.5 · 4,218 tokens" }, { t: "1 h ago", m: "Handed conversation to human (Max Mustermann)" }, ].map((r, i) => (
{r.t} {r.m}
))}
)} {tab === "keys" && ( )} {tab === "skills" && ( )} {tab === "files" && ( )} {tab === "messages" && } {tab === "billing" && } {tab === "logs" && } {tab === "settings" && }
); } function SkillsManager({ inst, allSkills, onReload }) { const [saving, setSaving] = React.useState(false); const [showAdd, setShowAdd] = React.useState(false); const [localSkills, setLocalSkills] = React.useState(() => { if (inst.skillDetails && inst.skillDetails.length > 0) { return inst.skillDetails.map(s => ({ name: s.skill_name, enabled: s.enabled, category: s.category })); } return inst.skills.map(s => ({ name: s, enabled: true, category: "" })); }); const assignedNames = new Set(localSkills.map(s => s.name)); const availableSkills = (allSkills || []).filter(s => !assignedNames.has(s.name)); const toggleSkill = async (name, enabled) => { const updated = localSkills.map(s => s.name === name ? { ...s, enabled } : s); setLocalSkills(updated); setSaving(true); try { await OprtAPI.updateInstanceSkills(inst.uuid, updated.map(s => ({ name: s.name, enabled: s.enabled }))); } catch (e) { setLocalSkills(localSkills); } finally { setSaving(false); } }; const addSkill = async (name) => { const skill = allSkills.find(s => s.name === name); const updated = [...localSkills, { name, enabled: true, category: skill ? skill.category : "" }]; setLocalSkills(updated); setShowAdd(false); setSaving(true); try { await OprtAPI.updateInstanceSkills(inst.uuid, updated.map(s => ({ name: s.name, enabled: s.enabled }))); if (onReload) onReload(); } catch (e) { setLocalSkills(localSkills); } finally { setSaving(false); } }; const removeSkill = async (name) => { const updated = localSkills.filter(s => s.name !== name); setLocalSkills(updated); setSaving(true); try { await OprtAPI.updateInstanceSkills(inst.uuid, updated.map(s => ({ name: s.name, enabled: s.enabled }))); if (onReload) onReload(); } catch (e) { setLocalSkills(localSkills); } finally { setSaving(false); } }; return (

Skills

· {localSkills.filter(s => s.enabled).length} active / {localSkills.length} installed
{saving && Saving…}
{localSkills.map((s) => ( ))} {localSkills.length === 0 && ( )}
SkillCategoryStatusActions
{s.name} {s.category}
No skills installed. Click "Add skill" to get started.
{showAdd && (

Available skills

{availableSkills.length === 0 ? (
All skills are already installed.
) : (
{availableSkills.map((s) => (
addSkill(s.name)}>
{s.name}
{s.category} · {s.description || ""}
))}
)}
)}
); } function FilesBrowser({ inst }) { const [files, setFiles] = React.useState([]); const [selectedFile, setSelectedFile] = React.useState(null); const [fileContent, setFileContent] = React.useState(""); const [originalContent, setOriginalContent] = React.useState(""); const [loading, setLoading] = React.useState(true); const [saving, setSaving] = React.useState(false); const [error, setError] = React.useState(null); const [saveMsg, setSaveMsg] = React.useState(null); const loadFiles = async () => { setLoading(true); setError(null); try { const r = await OprtAPI.listFiles(inst.uuid); setFiles(r.files || []); } catch (e) { setError(e.message); setFiles([]); } finally { setLoading(false); } }; React.useEffect(() => { loadFiles(); }, [inst.uuid]); const openFile = async (path) => { setSelectedFile(path); setError(null); setSaveMsg(null); try { const r = await OprtAPI.readFile(inst.uuid, path); setFileContent(r.content); setOriginalContent(r.content); } catch (e) { setError("Failed to load " + path + ": " + e.message); setFileContent(""); setOriginalContent(""); } }; const saveFile = async () => { if (!selectedFile) return; setSaving(true); setSaveMsg(null); setError(null); try { await OprtAPI.writeFile(inst.uuid, selectedFile, fileContent); setOriginalContent(fileContent); setSaveMsg("Saved"); setTimeout(() => setSaveMsg(null), 2000); } catch (e) { setError("Failed to save: " + e.message); } finally { setSaving(false); } }; const hasChanges = fileContent !== originalContent; const buildTree = (paths) => { const tree = {}; for (const p of paths) { const parts = p.split("/"); let node = tree; for (let i = 0; i < parts.length; i++) { const part = parts[i]; if (i === parts.length - 1) { node[part] = p; } else { if (!node[part] || typeof node[part] === "string") node[part] = {}; node = node[part]; } } } return tree; }; const renderTree = (node, depth = 0) => { return Object.keys(node).sort((a, b) => { const aIsDir = typeof node[a] === "object"; const bIsDir = typeof node[b] === "object"; if (aIsDir !== bIsDir) return aIsDir ? -1 : 1; return a.localeCompare(b); }).map((key) => { const val = node[key]; if (typeof val === "object") { return (
{key}
{renderTree(val, depth + 1)}
); } const isSelected = selectedFile === val; return (
openFile(val)}> {key}
); }); }; const tree = buildTree(files); return (

Files

{loading ? (
Loading…
) : files.length === 0 ? (
{error ? error : "No files found. Container may not be running."}
) : (
{renderTree(tree)}
)}

{selectedFile ? selectedFile : "Select a file"}

{saveMsg && {saveMsg}} {error && selectedFile && {error}}
{selectedFile ? (