import { lazy, Suspense, useEffect, useState } from "react"; import { connectEvents, get, setUnauthorizedHandler } from "./api"; import { useT } from "./i18n"; import { navigate, useRoute, closePane, MAX_PANES } from "./router"; import { applyEvent, loadSessions, resync, setState } from "./store"; import type { Info } from "./types"; import { Sidebar } from "./components/Sidebar"; import { Login } from "./components/Login"; import { ChatView } from "./components/ChatView"; import { NewSession } from "./components/NewSession"; import { Settings } from "./components/Settings"; import { Stats } from "./components/Stats"; import { Images } from "./components/Images"; import { Jobs } from "./components/Jobs"; import { JobEditor } from "./components/JobEditor"; import { Spinner, useTitleActivity } from "./components/Activity"; import { DialogHost } from "./dialogs"; import { startPrefsSync } from "./prefsSync"; import { WhatsNew } from "./components/WhatsNew"; import { ShareTarget } from "./components/ShareTarget"; import { Palette } from "./components/Palette"; import { pendingShare } from "./share"; // The file manager (editor, git) is loaded on demand to keep the chat small. const Files = lazy(() => import("./components/Files").then((m) => ({ default: m.Files }))); const TerminalPage = lazy(() => import("./components/Terminal").then((m) => ({ default: m.TerminalPage }))); export function App() { const t = useT(); const route = useRoute(); const [info, setInfo] = useState(); const [error, setError] = useState(); const [menuOpen, setMenuOpen] = useState(false); const [shared, setShared] = useState(pendingShare); useTitleActivity(); const refresh = async () => { try { const i = await get("/info"); setState(() => ({ me: i.user, update: i.update ?? null })); if (i.user) { await loadSessions(); void startPrefsSync(); } setInfo(i); } catch (e: any) { setError(e.message); } }; useEffect(() => { setUnauthorizedHandler(() => setInfo((i) => (i ? { ...i, user: null } : i))); void refresh(); }, []); const signedIn = !!info?.user; useEffect(() => { if (!signedIn) return; return connectEvents({ onEvent: applyEvent, onOpen: () => { setState(() => ({ connected: true })); void resync().catch(() => undefined); }, onClose: () => setState(() => ({ connected: false })), }); }, [signedIn]); // Close the mobile menu on navigation. useEffect(() => setMenuOpen(false), [route.join("/")]); if (error) return
{error}
; if (!info) return
{t("common.loading")}
; if (!info.user) return ( <> ); const [section, arg] = route; let content; const chatIds = section === "s" && arg ? arg.split("+").filter(Boolean).slice(0, MAX_PANES) : []; if (chatIds.length === 1) content = setMenuOpen(true)} />; else if (chatIds.length > 1) content = (
{chatIds.map((id) => (
setMenuOpen(true)} onClosePane={() => closePane(id)} />
))}
); else if (section === "settings") content = setMenuOpen(true)} />; else if (section === "stats") content = setMenuOpen(true)} />; else if (section === "images") content = setMenuOpen(true)} />; else if (section === "jobs" && arg) content = setMenuOpen(true)} />; else if (section === "jobs") content = setMenuOpen(true)} />; else if (section === "terminal") content = ( }> setMenuOpen(true)} /> ); else if (section === "files") content = ( }> setMenuOpen(true)} /> ); else content = setMenuOpen(true)} onCreated={(id) => navigate(`/s/${id}`)} />; return (
{menuOpen &&
setMenuOpen(false)} />}
{content}
{shared && setShared(null)} />}
); }