Files
claw/frontend/scene-generator/generator-runner.js

175 lines
4.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const { spawn } = require("child_process");
const path = require("path");
function runGenerator(params, sseWriter, projectRoot) {
const { sourceDir, sceneId, sceneName, outputRoot, lessons } = params;
const normalize = (p) => p.replace(/\\/g, "/");
const args = [
"run",
"--bin",
"sg_scene_generate",
"--",
"--source-dir",
normalize(sourceDir),
"--scene-id",
sceneId,
"--scene-name",
sceneName,
"--output-root",
normalize(outputRoot),
"--lessons",
normalize(lessons),
];
return new Promise((resolve, reject) => {
sseWriter.write(
`event: status\ndata: ${JSON.stringify({
message: "开始生成 skill 包...",
})}\n\n`
);
sseWriter.write(
`event: status\ndata: ${JSON.stringify({
message: `执行: cargo ${args.join(" ")}`,
})}\n\n`
);
const child = spawn("cargo", args, {
cwd: projectRoot,
stdio: ["ignore", "pipe", "pipe"],
env: { ...process.env, RUST_BACKTRACE: "1" },
});
let stdout = "";
let stderr = "";
let timedOut = false;
const timeout = setTimeout(() => {
timedOut = true;
child.kill("SIGTERM");
sseWriter.write(
`event: error\ndata: ${JSON.stringify({
message: "生成超时5分钟",
})}\n\n`
);
resolve({ success: false, error: "timeout" });
}, 5 * 60 * 1000);
child.stdout.on("data", (data) => {
const text = data.toString();
stdout += text;
sseWriter.write(
`event: log\ndata: ${JSON.stringify({ message: text.trim() })}\n\n`
);
});
child.stderr.on("data", (data) => {
const text = data.toString();
stderr += text;
sseWriter.write(
`event: log\ndata: ${JSON.stringify({ message: text.trim() })}\n\n`
);
});
child.on("close", (code) => {
clearTimeout(timeout);
if (timedOut) return;
if (code === 0) {
const match = stdout.match(/generated scene package:\s*(.+)/);
const skillRoot = match ? match[1] : null;
sseWriter.write(
`event: status\ndata: ${JSON.stringify({
message: "✅ 生成成功",
})}\n\n`
);
sseWriter.write(
`event: complete\ndata: ${JSON.stringify({
success: true,
skillRoot,
})}\n\n`
);
resolve({ success: true, skillRoot });
} else {
sseWriter.write(
`event: error\ndata: ${JSON.stringify({
message: `生成失败 (exit code ${code})`,
})}\n\n`
);
if (stderr.trim()) {
sseWriter.write(
`event: error\ndata: ${JSON.stringify({
message: stderr.substring(0, 500),
})}\n\n`
);
}
resolve({ success: false, code, stderr });
}
});
child.on("error", (err) => {
clearTimeout(timeout);
sseWriter.write(
`event: error\ndata: ${JSON.stringify({
message: `无法启动 cargo: ${err.message}`,
})}\n\n`
);
reject(err);
});
});
}
function readDirectory(sourceDir) {
const fs = require("fs");
const p = require("path");
if (!fs.existsSync(sourceDir)) {
throw new Error(`Directory not found: ${sourceDir}`);
}
const stat = fs.statSync(sourceDir);
if (!stat.isDirectory()) {
throw new Error(`Not a directory: ${sourceDir}`);
}
const result = {};
const entries = fs.readdirSync(sourceDir, { withFileTypes: true });
const treeLines = [];
for (const entry of entries) {
treeLines.push(`├── ${entry.name}`);
}
result.tree = treeLines.join("\n");
const sceneTomlPath = p.join(sourceDir, "scene.toml");
if (fs.existsSync(sceneTomlPath)) {
result["scene.toml"] = fs.readFileSync(sceneTomlPath, "utf-8");
}
const skillTomlPath = p.join(sourceDir, "SKILL.toml");
if (fs.existsSync(skillTomlPath)) {
result["SKILL.toml"] = fs.readFileSync(skillTomlPath, "utf-8");
}
const skillMdPath = p.join(sourceDir, "SKILL.md");
if (fs.existsSync(skillMdPath)) {
result["SKILL.md"] = fs.readFileSync(skillMdPath, "utf-8");
}
const scripts = {};
for (const entry of entries) {
if (entry.isFile() && entry.name.endsWith(".js")) {
const scriptPath = p.join(sourceDir, entry.name);
scripts[entry.name] = fs.readFileSync(scriptPath, "utf-8");
}
}
if (Object.keys(scripts).length > 0) {
result.scripts = scripts;
}
return result;
}
module.exports = { runGenerator, readDirectory };