codex-proxy / src /routes /web.ts
icebear
feat: usage stats page with time-series token tracking (#147)
df56b50 unverified
raw
history blame
3 kB
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("<h1>Codex Proxy</h1><p>UI files not found. Run 'npm run build:web' first. The API is still available at /v1/chat/completions</p>");
}
});
// 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;
}