Files
DeepSeek-Reasonix/desktop/scripts/dev-desktop.mjs
T
SivanCola 2580db2426 Improve desktop model and workspace navigation / 优化桌面端模型与工作区导航 (#10196)
* Improve the desktop model switcher

Problem:
The desktop model picker was narrow, provider-scoped during search, and offered no way to keep frequently used models visible.

Root cause:
The picker rendered one flat grouped list and stored no user-level model preference.

Fix:
Add a provider rail, catalog-wide search, persisted favorites, provider metadata, responsive sizing, and localized labels.

Verification:
Run the focused model switcher and favorites tests, desktop typecheck, frontend production build, and browser interaction checks.

* Open Overview for empty workspace docks

Problem:
Opening a session with an expanded but empty workspace dock showed a tab picker instead of useful context.

Root cause:
Workspace restoration deliberately left an empty dock unseeded and did not distinguish first session display from a user's later tab close.

Fix:
Seed the Overview tab once for each visible session while preserving deliberate tab closure until the session changes.

Verification:
Run the workspace panel command regression test, browser dock mode test, desktop typecheck, and frontend production build.

* Add one-command desktop development

Problem:
Local contributors needed multiple manual commands to start the browser UI or complete Electron application.

Root cause:
The desktop workspace had build commands but no coordinated development entry points.

Fix:
Add pnpm dev for the browser frontend and pnpm dev:desktop to build the Go service, start or reuse Vite, launch Electron, and clean up child processes. Document both commands.

Verification:
Run the launcher syntax and help checks, desktop typecheck, Electron tests, and frontend production build.

* Update browser gates for default Overview

Problem:
The desktop browser gate expected a fresh expanded dock to show the empty tab picker, which no longer matches the new default Overview behavior.

Root cause:
The end-to-end fixtures selected Files only through the empty-state picker and treated every recently closed tab as interchangeable.

Fix:
Assert the default Overview tab, open Files through the add-tab menu, and target the closed Files entry explicitly after Overview also enters tab history.

Verification:
Run the complete frontend app browser gate, including app lifecycle, dock view state, and runtime state scenarios.
2026-09-12 21:22:55 +08:00

109 lines
3.5 KiB
JavaScript

#!/usr/bin/env node
import { spawn } from "node:child_process";
import { mkdir } from "node:fs/promises";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
const desktopRoot = dirname(dirname(fileURLToPath(import.meta.url)));
const pnpm = process.platform === "win32" ? "pnpm.cmd" : "pnpm";
const serviceName = process.platform === "win32"
? "reasonix-desktop-service.exe"
: "reasonix-desktop-service";
const servicePath = resolve(desktopRoot, "build/bin", serviceName);
const port = process.env.REASONIX_DESKTOP_VITE_PORT || "5173";
const devUrl = process.env.REASONIX_ELECTRON_DEV_URL || `http://127.0.0.1:${port}`;
if (process.argv.includes("--help")) {
console.log("Build the Reasonix service, start Vite, and launch the Electron development shell.");
console.log("Usage: pnpm dev:desktop");
process.exit(0);
}
function start(command, args, env = process.env) {
return spawn(command, args, { cwd: desktopRoot, env, stdio: "inherit" });
}
function waitForExit(child, label) {
return new Promise((resolveExit, rejectExit) => {
child.once("error", rejectExit);
child.once("exit", (code, signal) => {
if (code === 0) resolveExit();
else rejectExit(new Error(`${label} exited with ${code ?? signal ?? "an unknown error"}`));
});
});
}
async function servesReasonix(url) {
try {
const response = await fetch(url);
if (!response.ok) return false;
const html = await response.text();
return /<title>\s*Reasonix\s*<\/title>/i.test(html);
} catch {
return false;
}
}
async function waitForVite(frontend) {
const deadline = Date.now() + 30_000;
while (Date.now() < deadline) {
if (frontend.exitCode !== null || frontend.signalCode) {
throw new Error(`Vite exited with ${frontend.exitCode ?? frontend.signalCode}`);
}
if (await servesReasonix(devUrl)) return;
await new Promise(resolveWait => setTimeout(resolveWait, 150));
}
throw new Error(`Vite did not become ready at ${devUrl}`);
}
let frontend;
let electron;
let exiting = false;
function stopChildren() {
if (electron?.exitCode === null) electron.kill("SIGTERM");
if (frontend?.exitCode === null) frontend.kill("SIGTERM");
}
function fail(error) {
console.error(error instanceof Error ? error.message : error);
stopChildren();
process.exitCode = 1;
}
for (const signal of ["SIGINT", "SIGTERM"]) {
process.once(signal, () => {
exiting = true;
stopChildren();
});
}
try {
await mkdir(dirname(servicePath), { recursive: true });
console.log("==> Building Reasonix desktop service");
await waitForExit(start("go", ["build", "-o", servicePath, "."]), "Go build");
if (await servesReasonix(devUrl)) {
console.log(`==> Reusing Reasonix Vite server at ${devUrl}`);
} else {
console.log(`==> Starting Reasonix Vite server at ${devUrl}`);
frontend = start(pnpm, ["--dir", "frontend", "dev", "--host", "127.0.0.1", "--port", port, "--strictPort"]);
await waitForVite(frontend);
}
console.log("==> Launching Reasonix Electron development shell");
electron = start(pnpm, ["--dir", "electron", "dev"], {
...process.env,
REASONIX_DESKTOP_SERVICE: servicePath,
REASONIX_ELECTRON_DEV_URL: devUrl,
});
const electronExit = waitForExit(electron, "Electron");
const frontendExit = frontend
? waitForExit(frontend, "Vite").then(() => { throw new Error("Vite stopped before Electron"); })
: new Promise(() => {});
await Promise.race([electronExit, frontendExit]);
if (frontend?.exitCode === null) frontend.kill("SIGTERM");
} catch (error) {
if (!exiting) fail(error);
}