import { Hono } from "hono"; import { serveStatic } from "@hono/node-server/serve-static"; import { readFileSync, existsSync } from "fs"; import { resolve } from "path"; import type { AccountPool } from "../auth/account-pool.js"; import { getPublicDir, getDesktopPublicDir } from "../paths.js"; import { createHealthRoutes } from "./admin/health.js"; import { createUpdateRoutes } from "./admin/update.js"; import { createConnectionRoutes } from "./admin/connection.js"; import { createSettingsRoutes } from "./admin/settings.js"; import { createUsageStatsRoutes } from "./admin/usage-stats.js"; import type { UsageStatsStore } from "../auth/usage-stats.js"; export function createWebRoutes(accountPool: AccountPool, usageStats: UsageStatsStore): Hono { const app = new Hono(); const publicDir = getPublicDir(); const desktopPublicDir = getDesktopPublicDir(); const desktopIndexPath = resolve(desktopPublicDir, "index.html"); const webIndexPath = resolve(publicDir, "index.html"); const hasDesktopUI = existsSync(desktopIndexPath); const hasWebUI = existsSync(webIndexPath); console.log(`[Web] publicDir: ${publicDir} (exists: ${hasWebUI})`); console.log(`[Web] desktopPublicDir: ${desktopPublicDir} (exists: ${hasDesktopUI})`); // Serve Vite build assets (web) — immutable cache (filenames contain content hash) app.use("/assets/*", async (c, next) => { c.header("Cache-Control", "public, max-age=31536000, immutable"); await next(); }, serveStatic({ root: publicDir })); app.get("/", (c) => { try { const html = readFileSync(webIndexPath, "utf-8"); c.header("Cache-Control", "no-cache"); return c.html(html); } catch (err) { const msg = err instanceof Error ? err.message : String(err); console.error(`[Web] Failed to read HTML file: ${msg}`); return c.html("

Codex Proxy

UI files not found. Run 'npm run build:web' first. The API is still available at /v1/chat/completions

"); } }); // Desktop UI — served at /desktop for Electron if (hasDesktopUI) { app.use("/desktop/assets/*", async (c, next) => { c.header("Cache-Control", "public, max-age=31536000, immutable"); await next(); }, serveStatic({ root: desktopPublicDir, rewriteRequestPath: (path) => path.replace(/^\/desktop/, ""), })); app.get("/desktop", (c) => { const html = readFileSync(desktopIndexPath, "utf-8"); c.header("Cache-Control", "no-cache"); return c.html(html); }); } else { app.get("/desktop", (c) => { console.warn(`[Web] Desktop UI not found at ${desktopIndexPath}, falling back to web UI`); return c.redirect("/"); }); } // Mount admin subroutes app.route("/", createHealthRoutes(accountPool)); app.route("/", createUpdateRoutes()); app.route("/", createConnectionRoutes(accountPool)); app.route("/", createSettingsRoutes()); app.route("/", createUsageStatsRoutes(accountPool, usageStats)); return app; }