Spaces:
Paused
Paused
icebear0828 Claude Opus 4.6 (1M context) commited on
Commit Β·
ca241c4
1
Parent(s): afe61e7
fix: dark mode tabs, StableText bilingual width, self-update reliability
Browse files- CodeExamples: fix dark mode tab text invisible (text-dim alpha format
broken with CSS variables, use plain #8b949e; tab background use solid
#0d1117 instead of transparent bg-dark/30)
- StableText: use both en+zh invisible references so button width never
jumps on language switch (root cause of recurring i18n layout bug)
- Header: remove version/commit display (already shown in footer)
- self-update: git checkout -- . before pull to discard local drift;
restart via helper script that waits for port release before starting
new process (fixes EADDRINUSE on Windows)
- apply-update: SSE streaming progress (pull β install β build β restart)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- src/routes/web.ts +16 -2
- src/self-update.ts +61 -10
- web/src/components/CodeExamples.tsx +1 -1
- web/src/components/Header.tsx +0 -6
- web/tailwind.config.ts +1 -1
src/routes/web.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
| 1 |
import { Hono } from "hono";
|
|
|
|
| 2 |
import { serveStatic } from "@hono/node-server/serve-static";
|
| 3 |
import { getConnInfo } from "@hono/node-server/conninfo";
|
| 4 |
import { readFileSync, existsSync } from "fs";
|
|
@@ -285,8 +286,21 @@ export function createWebRoutes(accountPool: AccountPool): Hono {
|
|
| 285 |
c.status(400);
|
| 286 |
return c.json({ started: false, error: "Self-update not available in this deploy mode" });
|
| 287 |
}
|
| 288 |
-
|
| 289 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 290 |
});
|
| 291 |
|
| 292 |
// --- Test connection endpoint ---
|
|
|
|
| 1 |
import { Hono } from "hono";
|
| 2 |
+
import { stream } from "hono/streaming";
|
| 3 |
import { serveStatic } from "@hono/node-server/serve-static";
|
| 4 |
import { getConnInfo } from "@hono/node-server/conninfo";
|
| 5 |
import { readFileSync, existsSync } from "fs";
|
|
|
|
| 286 |
c.status(400);
|
| 287 |
return c.json({ started: false, error: "Self-update not available in this deploy mode" });
|
| 288 |
}
|
| 289 |
+
|
| 290 |
+
// SSE stream for progress updates
|
| 291 |
+
c.header("Content-Type", "text/event-stream");
|
| 292 |
+
c.header("Cache-Control", "no-cache");
|
| 293 |
+
c.header("Connection", "keep-alive");
|
| 294 |
+
|
| 295 |
+
return stream(c, async (s) => {
|
| 296 |
+
const send = (data: Record<string, unknown>) => s.write(`data: ${JSON.stringify(data)}\n\n`);
|
| 297 |
+
|
| 298 |
+
const result = await applyProxySelfUpdate((step, status, detail) => {
|
| 299 |
+
void send({ step, status, detail });
|
| 300 |
+
});
|
| 301 |
+
|
| 302 |
+
await send({ ...result, done: true });
|
| 303 |
+
});
|
| 304 |
});
|
| 305 |
|
| 306 |
// --- Test connection endpoint ---
|
src/self-update.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { existsSync, readFileSync } from "fs";
|
|
| 10 |
import { resolve } from "path";
|
| 11 |
import { promisify } from "util";
|
| 12 |
import { getRootDir, isEmbedded } from "./paths.js";
|
|
|
|
| 13 |
|
| 14 |
// ββ Restart βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 15 |
let _closeHandler: (() => Promise<void>) | null = null;
|
|
@@ -20,18 +21,45 @@ export function setCloseHandler(handler: () => Promise<void>): void {
|
|
| 20 |
}
|
| 21 |
|
| 22 |
/**
|
| 23 |
-
* Restart the server: try graceful close (up to 3s), then spawn
|
| 24 |
-
*
|
| 25 |
*/
|
| 26 |
function hardRestart(cwd: string): void {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
const doRestart = () => {
|
| 28 |
-
console.log("[SelfUpdate] Spawning
|
| 29 |
-
|
| 30 |
detached: true,
|
| 31 |
stdio: "ignore",
|
| 32 |
cwd,
|
| 33 |
-
});
|
| 34 |
-
child.unref();
|
| 35 |
process.exit(0);
|
| 36 |
};
|
| 37 |
|
|
@@ -56,6 +84,16 @@ function hardRestart(cwd: string): void {
|
|
| 56 |
});
|
| 57 |
}
|
| 58 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 59 |
const execFileAsync = promisify(execFile);
|
| 60 |
|
| 61 |
const GITHUB_REPO = "icebear0828/codex-proxy";
|
|
@@ -297,34 +335,47 @@ export async function checkProxySelfUpdate(): Promise<ProxySelfUpdateResult> {
|
|
| 297 |
return result;
|
| 298 |
}
|
| 299 |
|
|
|
|
|
|
|
|
|
|
| 300 |
/**
|
| 301 |
* Apply proxy self-update: git pull + npm install + npm run build.
|
| 302 |
* Only works in git (CLI) mode.
|
|
|
|
| 303 |
*/
|
| 304 |
-
export async function applyProxySelfUpdate(
|
|
|
|
|
|
|
| 305 |
if (_proxyUpdateInProgress) {
|
| 306 |
return { started: false, error: "Update already in progress" };
|
| 307 |
}
|
| 308 |
|
| 309 |
_proxyUpdateInProgress = true;
|
| 310 |
const cwd = process.cwd();
|
|
|
|
| 311 |
|
| 312 |
try {
|
|
|
|
| 313 |
console.log("[SelfUpdate] Pulling latest code...");
|
| 314 |
-
// Discard local modifications (e.g. package-lock.json drift) that would block git pull
|
| 315 |
await execFileAsync("git", ["checkout", "--", "."], { cwd, timeout: 10000 }).catch(() => {});
|
| 316 |
await execFileAsync("git", ["pull", "origin", "master"], { cwd, timeout: 60000 });
|
|
|
|
| 317 |
|
|
|
|
| 318 |
console.log("[SelfUpdate] Installing dependencies...");
|
| 319 |
await execFileAsync("npm", ["install"], { cwd, timeout: 120000, shell: true });
|
|
|
|
| 320 |
|
|
|
|
| 321 |
console.log("[SelfUpdate] Building...");
|
| 322 |
await execFileAsync("npm", ["run", "build"], { cwd, timeout: 120000, shell: true });
|
|
|
|
| 323 |
|
| 324 |
-
|
|
|
|
| 325 |
_proxyUpdateInProgress = false;
|
| 326 |
|
| 327 |
-
// Delay 500ms to let
|
| 328 |
setTimeout(() => hardRestart(cwd), 500);
|
| 329 |
|
| 330 |
return { started: true, restarting: true };
|
|
|
|
| 10 |
import { resolve } from "path";
|
| 11 |
import { promisify } from "util";
|
| 12 |
import { getRootDir, isEmbedded } from "./paths.js";
|
| 13 |
+
import { getConfig } from "./config.js";
|
| 14 |
|
| 15 |
// ββ Restart βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 16 |
let _closeHandler: (() => Promise<void>) | null = null;
|
|
|
|
| 21 |
}
|
| 22 |
|
| 23 |
/**
|
| 24 |
+
* Restart the server: try graceful close (up to 3s), then spawn a helper
|
| 25 |
+
* that waits for the port to be free before starting the new server process.
|
| 26 |
*/
|
| 27 |
function hardRestart(cwd: string): void {
|
| 28 |
+
const nodeExe = process.argv[0];
|
| 29 |
+
const serverArgs = process.argv.slice(1);
|
| 30 |
+
|
| 31 |
+
// Inline script: wait for the port to be free, then start the real server
|
| 32 |
+
const helperScript = `
|
| 33 |
+
const net = require("net");
|
| 34 |
+
const { spawn } = require("child_process");
|
| 35 |
+
const args = ${JSON.stringify(serverArgs)};
|
| 36 |
+
const cwd = ${JSON.stringify(cwd)};
|
| 37 |
+
let attempts = 0;
|
| 38 |
+
function tryStart() {
|
| 39 |
+
attempts++;
|
| 40 |
+
const s = net.createServer();
|
| 41 |
+
s.once("error", () => {
|
| 42 |
+
if (attempts >= 20) process.exit(1);
|
| 43 |
+
setTimeout(tryStart, 500);
|
| 44 |
+
});
|
| 45 |
+
s.once("listening", () => {
|
| 46 |
+
s.close(() => {
|
| 47 |
+
spawn(${JSON.stringify(nodeExe)}, args, { detached: true, stdio: "ignore", cwd }).unref();
|
| 48 |
+
process.exit(0);
|
| 49 |
+
});
|
| 50 |
+
});
|
| 51 |
+
s.listen(${getPort()});
|
| 52 |
+
}
|
| 53 |
+
tryStart();
|
| 54 |
+
`;
|
| 55 |
+
|
| 56 |
const doRestart = () => {
|
| 57 |
+
console.log("[SelfUpdate] Spawning restart helper and exiting...");
|
| 58 |
+
spawn(nodeExe, ["-e", helperScript], {
|
| 59 |
detached: true,
|
| 60 |
stdio: "ignore",
|
| 61 |
cwd,
|
| 62 |
+
}).unref();
|
|
|
|
| 63 |
process.exit(0);
|
| 64 |
};
|
| 65 |
|
|
|
|
| 84 |
});
|
| 85 |
}
|
| 86 |
|
| 87 |
+
/** Read the configured port for the restart helper. */
|
| 88 |
+
function getPort(): number {
|
| 89 |
+
try {
|
| 90 |
+
const config = getConfig();
|
| 91 |
+
return config.server.port ?? 8080;
|
| 92 |
+
} catch {
|
| 93 |
+
return 8080;
|
| 94 |
+
}
|
| 95 |
+
}
|
| 96 |
+
|
| 97 |
const execFileAsync = promisify(execFile);
|
| 98 |
|
| 99 |
const GITHUB_REPO = "icebear0828/codex-proxy";
|
|
|
|
| 335 |
return result;
|
| 336 |
}
|
| 337 |
|
| 338 |
+
/** Progress callback for streaming update status. */
|
| 339 |
+
export type UpdateProgressCallback = (step: string, status: "running" | "done" | "error", detail?: string) => void;
|
| 340 |
+
|
| 341 |
/**
|
| 342 |
* Apply proxy self-update: git pull + npm install + npm run build.
|
| 343 |
* Only works in git (CLI) mode.
|
| 344 |
+
* @param onProgress Optional callback to report step-by-step progress.
|
| 345 |
*/
|
| 346 |
+
export async function applyProxySelfUpdate(
|
| 347 |
+
onProgress?: UpdateProgressCallback,
|
| 348 |
+
): Promise<{ started: boolean; restarting?: boolean; error?: string }> {
|
| 349 |
if (_proxyUpdateInProgress) {
|
| 350 |
return { started: false, error: "Update already in progress" };
|
| 351 |
}
|
| 352 |
|
| 353 |
_proxyUpdateInProgress = true;
|
| 354 |
const cwd = process.cwd();
|
| 355 |
+
const report = onProgress ?? (() => {});
|
| 356 |
|
| 357 |
try {
|
| 358 |
+
report("pull", "running");
|
| 359 |
console.log("[SelfUpdate] Pulling latest code...");
|
|
|
|
| 360 |
await execFileAsync("git", ["checkout", "--", "."], { cwd, timeout: 10000 }).catch(() => {});
|
| 361 |
await execFileAsync("git", ["pull", "origin", "master"], { cwd, timeout: 60000 });
|
| 362 |
+
report("pull", "done");
|
| 363 |
|
| 364 |
+
report("install", "running");
|
| 365 |
console.log("[SelfUpdate] Installing dependencies...");
|
| 366 |
await execFileAsync("npm", ["install"], { cwd, timeout: 120000, shell: true });
|
| 367 |
+
report("install", "done");
|
| 368 |
|
| 369 |
+
report("build", "running");
|
| 370 |
console.log("[SelfUpdate] Building...");
|
| 371 |
await execFileAsync("npm", ["run", "build"], { cwd, timeout: 120000, shell: true });
|
| 372 |
+
report("build", "done");
|
| 373 |
|
| 374 |
+
report("restart", "running");
|
| 375 |
+
console.log("[SelfUpdate] Update complete. Restarting...");
|
| 376 |
_proxyUpdateInProgress = false;
|
| 377 |
|
| 378 |
+
// Delay 500ms to let SSE flush, then restart
|
| 379 |
setTimeout(() => hardRestart(cwd), 500);
|
| 380 |
|
| 381 |
return { started: true, restarting: true };
|
web/src/components/CodeExamples.tsx
CHANGED
|
@@ -190,7 +190,7 @@ export function CodeExamples({ baseUrl, apiKey, model, reasoningEffort, serviceT
|
|
| 190 |
<h2 class="text-[0.95rem] font-bold">{t("integrationExamples")}</h2>
|
| 191 |
<div class="bg-white dark:bg-card-dark border border-gray-200 dark:border-border-dark rounded-xl overflow-hidden shadow-sm transition-colors">
|
| 192 |
{/* Protocol Tabs */}
|
| 193 |
-
<div class="flex border-b border-gray-200 dark:border-border-dark bg-slate-50/50 dark:bg-
|
| 194 |
{protocols.map((p) => (
|
| 195 |
<button
|
| 196 |
key={p.id}
|
|
|
|
| 190 |
<h2 class="text-[0.95rem] font-bold">{t("integrationExamples")}</h2>
|
| 191 |
<div class="bg-white dark:bg-card-dark border border-gray-200 dark:border-border-dark rounded-xl overflow-hidden shadow-sm transition-colors">
|
| 192 |
{/* Protocol Tabs */}
|
| 193 |
+
<div class="flex border-b border-gray-200 dark:border-border-dark bg-slate-50/50 dark:bg-[#0d1117]">
|
| 194 |
{protocols.map((p) => (
|
| 195 |
<button
|
| 196 |
key={p.id}
|
web/src/components/Header.tsx
CHANGED
|
@@ -80,12 +80,6 @@ export function Header({ onAddAccount, onCheckUpdate, onOpenUpdateModal, checkin
|
|
| 80 |
<span class="relative inline-flex rounded-full h-2.5 w-2.5 bg-primary" />
|
| 81 |
</span>
|
| 82 |
<StableText tKey="serverOnline" class="text-xs font-semibold text-primary">{t("serverOnline")}</StableText>
|
| 83 |
-
{version && (
|
| 84 |
-
<span class="text-[0.65rem] font-mono text-primary/70 whitespace-nowrap">v{version}</span>
|
| 85 |
-
)}
|
| 86 |
-
{commit && (
|
| 87 |
-
<span class="text-[0.6rem] font-mono text-primary/40">{commit.slice(0, 7)}</span>
|
| 88 |
-
)}
|
| 89 |
</div>
|
| 90 |
{/* Star on GitHub */}
|
| 91 |
<a
|
|
|
|
| 80 |
<span class="relative inline-flex rounded-full h-2.5 w-2.5 bg-primary" />
|
| 81 |
</span>
|
| 82 |
<StableText tKey="serverOnline" class="text-xs font-semibold text-primary">{t("serverOnline")}</StableText>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 83 |
</div>
|
| 84 |
{/* Star on GitHub */}
|
| 85 |
<a
|
web/tailwind.config.ts
CHANGED
|
@@ -13,7 +13,7 @@ export default {
|
|
| 13 |
"card-dark": "var(--card-dark, #161b22)",
|
| 14 |
"border-dark": "var(--border-dark, #30363d)",
|
| 15 |
"text-main": "#e6edf3",
|
| 16 |
-
"text-dim": "
|
| 17 |
},
|
| 18 |
fontFamily: {
|
| 19 |
display: [
|
|
|
|
| 13 |
"card-dark": "var(--card-dark, #161b22)",
|
| 14 |
"border-dark": "var(--border-dark, #30363d)",
|
| 15 |
"text-main": "#e6edf3",
|
| 16 |
+
"text-dim": "#8b949e",
|
| 17 |
},
|
| 18 |
fontFamily: {
|
| 19 |
display: [
|