File size: 2,997 Bytes
5d0a52f
b7d4394
5d0a52f
 
 
0c8b3c0
 
 
 
 
df56b50
 
5d0a52f
df56b50
5d0a52f
 
4a940a5
 
5d0a52f
b94940f
 
 
 
 
 
 
 
edb874f
 
 
 
 
b7d4394
5d0a52f
85aec43
b94940f
edb874f
5d0a52f
85aec43
 
 
b94940f
5d0a52f
 
 
4a940a5
b94940f
edb874f
 
 
 
b94940f
 
 
4a940a5
b94940f
 
edb874f
4a940a5
b94940f
 
 
 
 
 
 
4a940a5
0c8b3c0
 
 
 
 
df56b50
69a8388
5d0a52f
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
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;
}