// app.jsx — application root: auth gate, login screen, shell // (sidebar / topbar / connection banner / route outlet / ⌘K palette). // Depends on globals from icons.jsx, lib.jsx and components.jsx. (() => { const { useState, useEffect, useRef, useMemo, useCallback } = React; const { Icon, api, useLive, ROUTES, useRoute, fmtUptime, timeAgo, Button, IconButton, Badge, Field, Input, Checkbox, Spinner, EmptyState, ToastProvider, useToast, ErrorBoundary, CommandPalette, } = window; const THEME_KEY = "ccp.theme"; /* ──────────────────────────────────────────── Theme ──────────────────────────────────────────── */ function useTheme() { const [theme, setTheme] = useState(() => document.documentElement.dataset.theme === "dark" ? "dark" : "light" ); const toggle = useCallback(() => { setTheme((prev) => { const next = prev === "dark" ? "light" : "dark"; document.documentElement.dataset.theme = next; try { localStorage.setItem(THEME_KEY, next); } catch (e) {} return next; }); }, []); return [theme, toggle]; } function ThemeToggle({ theme, onToggle, bordered }) { return ( ); } /* ──────────────────────────────────────────── Splash (initial auth check) ──────────────────────────────────────────── */ function Splash() { return (
); } /* ──────────────────────────────────────────── Login screen — login + first-run setup variants. URL never changes here; the bookmarked route is restored as soon as the user signs in. ──────────────────────────────────────────── */ function LoginScreen({ auth, expired, onAuthenticated, theme, onToggleTheme }) { const isSetup = auth.configured === false; const [username, setUsername] = useState(auth.username || "admin"); const [password, setPassword] = useState(""); const [remember, setRemember] = useState(true); // default CHECKED const [busy, setBusy] = useState(false); const [error, setError] = useState(""); const submit = async (e) => { e.preventDefault(); if (busy) return; setBusy(true); setError(""); try { if (isSetup) { await api.post("/ui/api/auth/setup", { username, password, remember: true }); } else { await api.post("/ui/api/auth/login", { username, password, remember }); } await onAuthenticated(); } catch (err) { setError((err && err.message) || String(err)); } finally { setBusy(false); } }; return (

{isSetup ? "Create admin access" : "Welcome back"}

{isSetup ? "Set the local admin credentials before opening the control panel." : "Sign in to the Connect AI Proxy control panel."}

{expired && !isSetup && (
Your session expired — sign in again.
)} {error && (
{error}
)} setUsername(e.target.value)} autoFocus autoComplete="username" name="username" /> setPassword(e.target.value)} autoComplete={isSetup ? "new-password" : "current-password"} name="password" /> {!isSetup && ( )}
Connect AI Proxy · local control panel
); } /* ──────────────────────────────────────────── Sidebar ──────────────────────────────────────────── */ function Sidebar({ routeId, navigate, liveStatus, onNavigated }) { const groups = useMemo(() => { const map = new Map(); ROUTES.forEach((r) => { if (!map.has(r.group)) map.set(r.group, []); map.get(r.group).push(r); }); return Array.from(map.entries()); }, []); const modelCount = liveStatus && Array.isArray(liveStatus.models) ? liveStatus.models.length : null; const version = liveStatus && liveStatus.version ? String(liveStatus.version) : null; const update = liveStatus && liveStatus.update; const go = (id) => { navigate(id); if (onNavigated) onNavigated(); }; return ( ); } /* ──────────────────────────────────────────── Topbar ──────────────────────────────────────────── */ function UserMenu({ username, onLogout }) { const [open, setOpen] = useState(false); const ref = useRef(null); useEffect(() => { if (!open) return undefined; const onDoc = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); }; const onKey = (e) => { if (e.key === "Escape") setOpen(false); }; document.addEventListener("mousedown", onDoc); window.addEventListener("keydown", onKey); return () => { document.removeEventListener("mousedown", onDoc); window.removeEventListener("keydown", onKey); }; }, [open]); return (
{open && (
Signed in as {username || "admin"}
API docs
)}
); } function Topbar({ liveStatus, onToggleDrawer, onOpenPalette, theme, onToggleTheme, username, onLogout }) { const toast = useToast(); const hasStatus = !!liveStatus; const running = !!(liveStatus && liveStatus.proxy_running); const url = (liveStatus && (liveStatus.display_url || liveStatus.public_url || liveStatus.local_url)) || "http://127.0.0.1:4000"; const copyUrl = () => { if (navigator.clipboard) { navigator.clipboard.writeText(url) .then(() => toast.success("Endpoint URL copied")) .catch(() => toast.error("Could not copy URL")); } }; return (
{hasStatus && ( {running ? "Running" : "Stopped"} )} {hasStatus && running && liveStatus.uptime_seconds != null && ( up {fmtUptime(liveStatus.uptime_seconds)} )}
); } /* ──────────────────────────────────────────── Connection banner ──────────────────────────────────────────── */ function ConnectionBanner({ reconnecting, lastUpdated, updating }) { if (updating) { return (
Service restarting for update… the panel will reconnect automatically.
); } if (reconnecting) { return (
Reconnecting… last updated {timeAgo(lastUpdated)}
); } return null; } /* ──────────────────────────────────────────── Shell — owns the single 2s /ui/api/status poll ──────────────────────────────────────────── */ function Shell({ username, onLoggedOut, theme, onToggleTheme }) { const { route, routeId, navigate } = useRoute(); const toast = useToast(); const [paletteOpen, setPaletteOpen] = useState(false); const [drawerOpen, setDrawerOpen] = useState(false); const { data: liveStatus, error: statusError, stale: statusStale, reconnecting, lastUpdated, refresh: refreshStatus, } = useLive("/ui/api/status", { interval: 2000, enabled: true }); const liveStatusRef = useRef(liveStatus); liveStatusRef.current = liveStatus; const pushToast = useCallback((msg, tone = "info") => { toast.push(msg, { tone }); }, [toast]); const onLogout = useCallback(async () => { try { await api.post("/ui/api/auth/logout"); } catch (e) {} onLoggedOut(); }, [onLoggedOut]); // Ported proxy/update action handler (start/stop/restart/validate/ // update/check-update/toggle-auto-update) with toasts. const onAction = useCallback(async (action) => { try { if (action === "start") { await api.post("/ui/api/proxy/start"); toast.success("Proxy endpoints started"); } else if (action === "stop") { await api.post("/ui/api/proxy/stop"); toast.info("Proxy endpoints stopped — the control panel stays online"); } else if (action === "restart") { await api.post("/ui/api/proxy/restart"); toast.info("Proxy restarting"); } else if (action === "validate") { const res = await api.get("/ui/api/validate"); const steps = (res && res.steps) || []; const passed = steps.filter((s) => s.ok).length; const allOk = passed === steps.length && steps.length > 0; toast.push(`Validation complete — ${passed}/${steps.length} checks passed`, { tone: allOk ? "success" : "error" }); } else if (action === "update") { await api.post("/ui/api/update/start"); toast.info("Update started — waiting for reconnect"); } else if (action === "check-update") { await api.post("/ui/api/update/check"); toast.success("Update check complete"); } else if (action === "toggle-auto-update") { const cur = liveStatusRef.current; const enabled = !(cur && cur.update && cur.update.auto_update); await api.post("/ui/api/update/settings", { auto_update: enabled }); toast.success(enabled ? "Auto update enabled" : "Auto update disabled"); } refreshStatus(); } catch (e) { if (action === "update") { toast.info("Update is running — reconnecting soon"); return; } toast.error(`Action failed — ${(e && e.message) || e}`); } }, [toast, refreshStatus]); // Keyboard: Ctrl/⌘+K palette; bare digits 1-9 navigate (guarded). useEffect(() => { const onKey = (e) => { if ((e.ctrlKey || e.metaKey) && !e.altKey && e.key.toLowerCase() === "k") { e.preventDefault(); setPaletteOpen((o) => !o); return; } if (e.ctrlKey || e.metaKey || e.altKey) return; const t = e.target; if ( t && (t.tagName === "INPUT" || t.tagName === "TEXTAREA" || t.tagName === "SELECT" || t.isContentEditable) ) return; const idx = "123456789".indexOf(e.key); if (idx >= 0 && idx < ROUTES.length) navigate(ROUTES[idx].id); }; window.addEventListener("keydown", onKey); return () => window.removeEventListener("keydown", onKey); }, [navigate]); const running = !!(liveStatus && liveStatus.proxy_running); const paletteActions = useMemo(() => [ running ? { id: "stop", label: "Stop proxy endpoints", icon: "stop", run: () => onAction("stop") } : { id: "start", label: "Start proxy endpoints", icon: "play", run: () => onAction("start") }, { id: "restart", label: "Restart proxy", icon: "restart", run: () => onAction("restart") }, { id: "validate", label: "Run validation", icon: "shield", run: () => onAction("validate") }, { id: "check-update", label: "Check for updates", icon: "download", run: () => onAction("check-update") }, { id: "toggle-theme", label: theme === "dark" ? "Switch to light theme" : "Switch to dark theme", icon: theme === "dark" ? "sun" : "moon", run: onToggleTheme, }, { id: "logout", label: "Log out", icon: "log-out", run: onLogout }, ], [running, theme, onAction, onToggleTheme, onLogout]); const ScreenComponent = window[route.component]; const updating = !!(liveStatus && liveStatus.update && liveStatus.update.running && statusError); return (
setDrawerOpen(false)} /> {drawerOpen && (
); } /* ──────────────────────────────────────────── App root — auth gate ──────────────────────────────────────────── */ function App() { const [theme, toggleTheme] = useTheme(); const [auth, setAuth] = useState({ loading: true, configured: true, authenticated: false, username: "", expired: false, }); const refreshAuth = useCallback(async () => { try { const res = await api.get("/ui/api/auth/status"); setAuth({ loading: false, expired: false, configured: true, authenticated: false, username: "", ...res }); } catch (e) { setAuth((a) => ({ ...a, loading: false, authenticated: false })); } }, []); useEffect(() => { api.onAuthExpired = () => { setAuth((a) => (a.authenticated ? { ...a, authenticated: false, expired: true } : a)); }; refreshAuth(); return () => { api.onAuthExpired = null; }; }, [refreshAuth]); let body; if (auth.loading) { body = ; } else if (!auth.authenticated) { body = ( ); } else { body = ( ); } return {body}; } const root = ReactDOM.createRoot(document.getElementById("root")); root.render(); })();