93 lines
2.4 KiB
JavaScript
93 lines
2.4 KiB
JavaScript
import express from "express";
|
|
import { spawn } from "node:child_process";
|
|
import path from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const __filename = fileURLToPath(import.meta.url);
|
|
const __dirname = path.dirname(__filename);
|
|
const repoRoot = path.resolve(__dirname, "..");
|
|
|
|
const app = express();
|
|
app.use(express.static(path.join(__dirname, "public")));
|
|
|
|
function sseHeaders(res) {
|
|
res.setHeader("Content-Type", "text/event-stream; charset=utf-8");
|
|
res.setHeader("Cache-Control", "no-cache, no-transform");
|
|
res.setHeader("Connection", "keep-alive");
|
|
// Nginx proxies may buffer unless disabled; harmless in dev.
|
|
res.setHeader("X-Accel-Buffering", "no");
|
|
}
|
|
|
|
function sseSend(res, event, data) {
|
|
if (event) res.write(`event: ${event}\n`);
|
|
const lines = String(data).split(/\r?\n/);
|
|
for (const line of lines) res.write(`data: ${line}\n`);
|
|
res.write("\n");
|
|
}
|
|
|
|
app.get("/api/run", (req, res) => {
|
|
const prompt = String(req.query.prompt || "").trim();
|
|
const mock = String(req.query.mock || "1") === "1";
|
|
const configPath = String(req.query.config || "./configs/config.yaml");
|
|
|
|
if (!prompt) {
|
|
res.status(400).json({ error: "missing prompt" });
|
|
return;
|
|
}
|
|
|
|
sseHeaders(res);
|
|
sseSend(res, "status", "starting");
|
|
|
|
// Unified in-container execution: Node spawns python directly.
|
|
const py = process.env.PYTHON_BIN || "python";
|
|
const args = [
|
|
path.join(repoRoot, "main.py"),
|
|
"--prompt",
|
|
prompt,
|
|
"--config",
|
|
configPath,
|
|
"--script-only",
|
|
];
|
|
if (mock) args.push("--mock");
|
|
|
|
const child = spawn(py, args, {
|
|
cwd: repoRoot,
|
|
env: process.env,
|
|
stdio: ["ignore", "pipe", "pipe"],
|
|
});
|
|
|
|
let buf = "";
|
|
child.stdout.setEncoding("utf8");
|
|
child.stdout.on("data", (chunk) => {
|
|
buf += chunk;
|
|
const parts = buf.split(/\r?\n/);
|
|
buf = parts.pop() || "";
|
|
for (const line of parts) {
|
|
if (!line) continue;
|
|
// Forward raw lines. Frontend will parse SCENE_JSON.
|
|
sseSend(res, "line", line);
|
|
}
|
|
});
|
|
|
|
child.stderr.setEncoding("utf8");
|
|
child.stderr.on("data", (chunk) => {
|
|
sseSend(res, "stderr", chunk);
|
|
});
|
|
|
|
req.on("close", () => {
|
|
child.kill("SIGTERM");
|
|
});
|
|
|
|
child.on("exit", (code) => {
|
|
if (buf.trim()) sseSend(res, "line", buf.trim());
|
|
sseSend(res, "done", String(code ?? 0));
|
|
res.end();
|
|
});
|
|
});
|
|
|
|
const port = Number(process.env.PORT || 3000);
|
|
app.listen(port, () => {
|
|
console.log(`[server] http://127.0.0.1:${port}`);
|
|
});
|
|
|