fix: 优化架构
This commit is contained in:
165
server/index.js
165
server/index.js
@@ -59,6 +59,25 @@ function sseSend(res, event, data) {
|
||||
res.write("\n");
|
||||
}
|
||||
|
||||
function sseStageUpdate(res, payload) {
|
||||
// Unified schema for frontend stage rendering.
|
||||
const safe = {
|
||||
schema_version: 1,
|
||||
stage: String(payload && payload.stage ? payload.stage : "Unknown"),
|
||||
progress:
|
||||
payload && typeof payload.progress === "number" && Number.isFinite(payload.progress)
|
||||
? Math.max(0, Math.min(1, payload.progress))
|
||||
: null,
|
||||
scene_index: payload && Number.isFinite(payload.scene_index) ? Number(payload.scene_index) : null,
|
||||
scene_json: payload && payload.scene_json && typeof payload.scene_json === "object" ? payload.scene_json : null,
|
||||
shot_id: payload && payload.shot_id ? String(payload.shot_id) : null,
|
||||
shot_status: payload && payload.shot_status ? String(payload.shot_status) : null,
|
||||
message: payload && payload.message ? String(payload.message) : "",
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
sseSend(res, "stage_update", JSON.stringify(safe));
|
||||
}
|
||||
|
||||
function newTaskId() {
|
||||
// `crypto.randomUUID()` exists on newer Node versions; fall back for older runtimes.
|
||||
if (crypto && typeof crypto.randomUUID === "function") return crypto.randomUUID();
|
||||
@@ -81,7 +100,19 @@ function ensureTaskDir(taskId) {
|
||||
return dir;
|
||||
}
|
||||
|
||||
function spawnPythonStep({ step, prompt, configPath, mock, globalStyle, character, taskId, sceneIndex }) {
|
||||
function spawnPythonStep({
|
||||
step,
|
||||
prompt,
|
||||
configPath,
|
||||
mock,
|
||||
globalStyle,
|
||||
character,
|
||||
taskId,
|
||||
sceneIndex,
|
||||
llmProvider,
|
||||
imageProvider,
|
||||
imageFallbackProvider,
|
||||
}) {
|
||||
const py = process.env.PYTHON_BIN || "python3.10";
|
||||
const args = [
|
||||
"-m",
|
||||
@@ -99,7 +130,11 @@ function spawnPythonStep({ step, prompt, configPath, mock, globalStyle, characte
|
||||
if (globalStyle) args.push("--global-style", globalStyle);
|
||||
if (character) args.push("--character", character);
|
||||
if (mock) args.push("--mock");
|
||||
return spawn(py, args, { cwd: repoRoot, env: process.env, stdio: ["pipe", "pipe", "pipe"] });
|
||||
const childEnv = { ...process.env };
|
||||
if (llmProvider) childEnv.ENGINE_LLM_PROVIDER = String(llmProvider).trim();
|
||||
if (imageProvider) childEnv.ENGINE_IMAGE_PROVIDER = String(imageProvider).trim();
|
||||
if (imageFallbackProvider) childEnv.ENGINE_IMAGE_FALLBACK_PROVIDER = String(imageFallbackProvider).trim();
|
||||
return spawn(py, args, { cwd: repoRoot, env: childEnv, stdio: ["pipe", "pipe", "pipe"] });
|
||||
}
|
||||
|
||||
app.get("/api/script", (req, res) => {
|
||||
@@ -108,6 +143,9 @@ app.get("/api/script", (req, res) => {
|
||||
const globalStyle = String(req.query.global_style || "").trim();
|
||||
const character = String(req.query.character || "").trim();
|
||||
const configPath = String(req.query.config || "./configs/config.yaml");
|
||||
const llmProvider = String(req.query.llm_provider || "").trim();
|
||||
const imageProvider = String(req.query.image_provider || "").trim();
|
||||
const imageFallbackProvider = String(req.query.image_fallback_provider || "").trim();
|
||||
|
||||
if (!prompt) {
|
||||
res.status(400).json({ error: "missing prompt" });
|
||||
@@ -129,9 +167,13 @@ app.get("/api/script", (req, res) => {
|
||||
globalStyle,
|
||||
character,
|
||||
taskId,
|
||||
llmProvider,
|
||||
imageProvider,
|
||||
imageFallbackProvider,
|
||||
});
|
||||
|
||||
let buf = "";
|
||||
let sceneCount = 0;
|
||||
child.stdout.setEncoding("utf8");
|
||||
child.stdout.on("data", (chunk) => {
|
||||
buf += chunk;
|
||||
@@ -139,15 +181,37 @@ app.get("/api/script", (req, res) => {
|
||||
buf = parts.pop() || "";
|
||||
for (const line of parts) {
|
||||
if (!line) continue;
|
||||
if (line.startsWith("SCENE_JSON ")) sseSend(res, "scene", line.slice("SCENE_JSON ".length));
|
||||
else if (line.startsWith("PROG ")) sseSend(res, "prog", line.slice("PROG ".length));
|
||||
else sseSend(res, "line", line);
|
||||
if (line.startsWith("SCENE_JSON ")) {
|
||||
try {
|
||||
const scene = JSON.parse(line.slice("SCENE_JSON ".length));
|
||||
sceneCount += 1;
|
||||
sseStageUpdate(res, {
|
||||
stage: "Script",
|
||||
scene_index: Number(scene.index || sceneCount) - 1,
|
||||
scene_json: scene,
|
||||
progress: Math.min(0.9, sceneCount / 3),
|
||||
message: "scene_generated",
|
||||
});
|
||||
} catch {
|
||||
sseSend(res, "line", line);
|
||||
}
|
||||
} else if (line.startsWith("PROG ")) {
|
||||
try {
|
||||
const p = JSON.parse(line.slice("PROG ".length));
|
||||
sseStageUpdate(res, { stage: "Script", progress: Number(p.p || 0), message: p.msg || "" });
|
||||
} catch {
|
||||
sseSend(res, "line", line);
|
||||
}
|
||||
} else {
|
||||
sseSend(res, "line", line);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
child.stderr.setEncoding("utf8");
|
||||
child.stderr.on("data", (chunk) => {
|
||||
sseSend(res, "error", chunk);
|
||||
// stderr can contain non-fatal logs/warnings; keep as a normal line event.
|
||||
sseSend(res, "line", "[stderr] " + chunk);
|
||||
});
|
||||
|
||||
req.on("close", () => {
|
||||
@@ -156,6 +220,7 @@ app.get("/api/script", (req, res) => {
|
||||
|
||||
child.on("exit", (code) => {
|
||||
if (buf.trim()) sseSend(res, "line", buf.trim());
|
||||
if (code !== 0) sseSend(res, "error", `[ERROR] python exit_code=${code}`);
|
||||
sseSend(res, "done", String(code != null ? code : 0));
|
||||
res.end();
|
||||
});
|
||||
@@ -170,6 +235,9 @@ app.post("/api/refine", (req, res) => {
|
||||
const globalStyle = String((req.body && req.body.global_style) || "").trim();
|
||||
const character = String((req.body && req.body.character) || "").trim();
|
||||
const configPath = String((req.body && req.body.config) || "./configs/config.yaml");
|
||||
const llmProvider = String((req.body && req.body.llm_provider) || "").trim();
|
||||
const imageProvider = String((req.body && req.body.image_provider) || "").trim();
|
||||
const imageFallbackProvider = String((req.body && req.body.image_fallback_provider) || "").trim();
|
||||
const taskId = String((req.body && req.body.task_id) || "").trim() || newTaskId();
|
||||
|
||||
if (!prompt) return res.status(400).json({ error: "missing prompt" });
|
||||
@@ -178,6 +246,9 @@ app.post("/api/refine", (req, res) => {
|
||||
return res.status(400).json({ error: "missing scene or scenes[]" });
|
||||
}
|
||||
ensureTaskDir(taskId);
|
||||
sseHeaders(res);
|
||||
sseSend(res, "task", JSON.stringify({ task_id: taskId }));
|
||||
sseStageUpdate(res, { stage: "Refine", progress: 0.05, message: "refine_start" });
|
||||
|
||||
const child = spawnPythonStep({
|
||||
step: "refine",
|
||||
@@ -188,6 +259,9 @@ app.post("/api/refine", (req, res) => {
|
||||
character,
|
||||
taskId,
|
||||
sceneIndex,
|
||||
llmProvider,
|
||||
imageProvider,
|
||||
imageFallbackProvider,
|
||||
});
|
||||
if (Array.isArray(scenes)) {
|
||||
child.stdin.end(JSON.stringify({ scenes }));
|
||||
@@ -197,19 +271,53 @@ app.post("/api/refine", (req, res) => {
|
||||
|
||||
let out = "";
|
||||
let err = "";
|
||||
let buf = "";
|
||||
child.stdout.setEncoding("utf8");
|
||||
child.stderr.setEncoding("utf8");
|
||||
child.stdout.on("data", (c) => (out += c));
|
||||
child.stdout.on("data", (chunk) => {
|
||||
out += chunk;
|
||||
buf += chunk;
|
||||
const parts = buf.split(/\r?\n/);
|
||||
buf = parts.pop() || "";
|
||||
for (const line of parts) {
|
||||
if (!line) continue;
|
||||
if (line.startsWith("SCENE_JSON ")) {
|
||||
try {
|
||||
const scenePayload = JSON.parse(line.slice("SCENE_JSON ".length));
|
||||
sseStageUpdate(res, {
|
||||
stage: "Refine",
|
||||
progress: 1,
|
||||
scene_index: Number(scenePayload.index || sceneIndex) - 1,
|
||||
scene_json: scenePayload,
|
||||
message: "scene_refined",
|
||||
});
|
||||
} catch {
|
||||
sseSend(res, "line", line);
|
||||
}
|
||||
} else if (line.startsWith("PROG ")) {
|
||||
try {
|
||||
const p = JSON.parse(line.slice("PROG ".length));
|
||||
sseStageUpdate(res, { stage: "Refine", progress: Number(p.p || 0), message: p.msg || "" });
|
||||
} catch {
|
||||
sseSend(res, "line", line);
|
||||
}
|
||||
} else {
|
||||
sseSend(res, "line", line);
|
||||
}
|
||||
}
|
||||
});
|
||||
child.stderr.on("data", (c) => (err += c));
|
||||
child.on("exit", (code) => {
|
||||
if (code !== 0) return res.status(500).json({ error: "python failed", stderr: err, stdout: out });
|
||||
const line = out
|
||||
.split(/\r?\n/)
|
||||
.map((s) => s.trim())
|
||||
.find((s) => s.startsWith("SCENE_JSON "));
|
||||
if (!line) return res.status(500).json({ error: "no SCENE_JSON", stderr: err, stdout: out });
|
||||
const payload = JSON.parse(line.slice("SCENE_JSON ".length));
|
||||
return res.json({ task_id: taskId, scene: payload, stderr: err });
|
||||
if (buf.trim()) sseSend(res, "line", buf.trim());
|
||||
if (err.trim()) sseSend(res, "line", "[stderr] " + err.trim());
|
||||
if (code !== 0) {
|
||||
sseStageUpdate(res, { stage: "Refine", progress: null, message: `refine_failed(exit=${code})` });
|
||||
sseSend(res, "error", `[ERROR] python exit_code=${code}`);
|
||||
return res.end();
|
||||
}
|
||||
sseStageUpdate(res, { stage: "Refine", progress: 1, message: "refine_done" });
|
||||
sseSend(res, "done", JSON.stringify({ exit_code: code != null ? code : 0, task_id: taskId }));
|
||||
return res.end();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -222,6 +330,9 @@ app.post("/api/render", (req, res) => {
|
||||
const globalStyle = String((req.body && req.body.global_style) || "").trim();
|
||||
const character = String((req.body && req.body.character) || "").trim();
|
||||
const configPath = String((req.body && req.body.config) || "./configs/config.yaml");
|
||||
const llmProvider = String((req.body && req.body.llm_provider) || "").trim();
|
||||
const imageProvider = String((req.body && req.body.image_provider) || "").trim();
|
||||
const imageFallbackProvider = String((req.body && req.body.image_fallback_provider) || "").trim();
|
||||
const taskId = String((req.body && req.body.task_id) || "").trim() || newTaskId();
|
||||
|
||||
if (!prompt) return res.status(400).json({ error: "missing prompt" });
|
||||
@@ -245,6 +356,9 @@ app.post("/api/render", (req, res) => {
|
||||
globalStyle,
|
||||
character,
|
||||
taskId,
|
||||
llmProvider,
|
||||
imageProvider,
|
||||
imageFallbackProvider,
|
||||
});
|
||||
child.stdin.end(JSON.stringify({ scenes }));
|
||||
|
||||
@@ -258,14 +372,26 @@ app.post("/api/render", (req, res) => {
|
||||
buf = parts.pop() || "";
|
||||
for (const line of parts) {
|
||||
if (!line) continue;
|
||||
if (line.startsWith("PROG ")) sseSend(res, "prog", line.slice("PROG ".length));
|
||||
else if (line.startsWith("PROG_SHOT ")) {
|
||||
if (line.startsWith("PROG ")) {
|
||||
try {
|
||||
const p = JSON.parse(line.slice("PROG ".length));
|
||||
sseStageUpdate(res, { stage: "Render", progress: Number(p.p || 0), message: p.msg || "" });
|
||||
} catch {
|
||||
sseSend(res, "line", line);
|
||||
}
|
||||
} else if (line.startsWith("PROG_SHOT ")) {
|
||||
const rest = line.slice("PROG_SHOT ".length).trim();
|
||||
const firstSpace = rest.indexOf(" ");
|
||||
if (firstSpace > 0) {
|
||||
const shotId = rest.slice(0, firstSpace).trim();
|
||||
const status = rest.slice(firstSpace + 1).trim();
|
||||
sseSend(res, "shot_progress", JSON.stringify({ shot_id: shotId, status }));
|
||||
sseStageUpdate(res, {
|
||||
stage: "Render",
|
||||
progress: null,
|
||||
shot_id: shotId,
|
||||
shot_status: status,
|
||||
message: "shot_progress",
|
||||
});
|
||||
} else {
|
||||
sseSend(res, "line", line);
|
||||
}
|
||||
@@ -276,7 +402,8 @@ app.post("/api/render", (req, res) => {
|
||||
});
|
||||
|
||||
child.stderr.on("data", (chunk) => {
|
||||
sseSend(res, "error", chunk);
|
||||
// stderr can contain non-fatal logs/warnings; keep as a normal line event.
|
||||
sseSend(res, "line", "[stderr] " + chunk);
|
||||
});
|
||||
|
||||
req.on("close", () => {
|
||||
|
||||
Reference in New Issue
Block a user