diff --git a/apps/ui/src/App.tsx b/apps/ui/src/App.tsx index 572111a..e6f9402 100644 --- a/apps/ui/src/App.tsx +++ b/apps/ui/src/App.tsx @@ -40,6 +40,31 @@ if (!root) { throw new Error("Missing #app root"); } +const normalizePath = (path: string) => { + if (path.length <= 1) { + return "/"; + } + return path.replace(/\/+$/, "") || "/"; +}; + +const tabToPath: Record = { + dashboard: "/", + channels: "/channels", + models: "/models", + tokens: "/tokens", + usage: "/usage", + settings: "/settings", +}; + +const pathToTab: Record = { + "/": "dashboard", + "/channels": "channels", + "/models": "models", + "/tokens": "tokens", + "/usage": "usage", + "/settings": "settings", +}; + /** * Renders the admin console application. * @@ -50,7 +75,13 @@ const App = () => { const [token, setToken] = useState(() => localStorage.getItem("admin_token"), ); - const [activeTab, setActiveTab] = useState("dashboard"); + const [activeTab, setActiveTab] = useState(() => { + if (typeof window === "undefined") { + return "dashboard"; + } + const normalized = normalizePath(window.location.pathname); + return pathToTab[normalized] ?? "dashboard"; + }); const [loading, setLoading] = useState(false); const [notice, setNotice] = useState(""); const [data, setData] = useState(initialData); @@ -161,6 +192,17 @@ const App = () => { } }, [token, activeTab, loadTab]); + useEffect(() => { + const handlePopState = () => { + const normalized = normalizePath(window.location.pathname); + setActiveTab(pathToTab[normalized] ?? "dashboard"); + }; + window.addEventListener("popstate", handlePopState); + return () => { + window.removeEventListener("popstate", handlePopState); + }; + }, []); + useEffect(() => { if (!data.settings) { return; @@ -227,6 +269,11 @@ const App = () => { }, []); const handleTabChange = useCallback((tabId: TabId) => { + const nextPath = tabToPath[tabId]; + const normalized = normalizePath(window.location.pathname); + if (normalized !== nextPath) { + history.pushState(null, "", nextPath); + } setActiveTab(tabId); }, []); diff --git a/apps/worker/src/index.ts b/apps/worker/src/index.ts index ffe722a..b73dd24 100644 --- a/apps/worker/src/index.ts +++ b/apps/worker/src/index.ts @@ -111,7 +111,7 @@ app.route("/api/group", newapiGroupRoutes); app.route("/v1", proxyRoutes); -app.notFound((c) => { +app.notFound(async (c) => { const path = c.req.path; if ( path === "/api" || @@ -124,7 +124,25 @@ app.notFound((c) => { const assets = ( c.env as { ASSETS?: { fetch: (input: Request) => Promise } } ).ASSETS; - return assets ? assets.fetch(c.req.raw) : c.text("Not Found", 404); + if (!assets) { + return c.text("Not Found", 404); + } + + const res = await assets.fetch(c.req.raw); + if (res.status !== 404) { + return res; + } + + const accept = c.req.header("accept") ?? ""; + const isHtml = accept.includes("text/html"); + const isFile = path.includes("."); + if (!isHtml || isFile) { + return res; + } + + const url = new URL(c.req.url); + url.pathname = "/index.html"; + return assets.fetch(new Request(url.toString(), c.req.raw)); }); export default app;