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

198 lines
5.1 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, sceneKind, targetUrl, outputRoot, lessons, sceneInfoJson } = params;
const normalize = (p) => p.replace(/\\/g, "/");
const args = [
"run",
"--bin",
"sg_scene_generate",
"--",
"--source-dir",
normalize(sourceDir),
"--scene-id",
sceneId,
"--scene-name",
sceneName,
];
// 只有明确指定 sceneKind 时才添加参数(否则使用默认值 report_collection
if (sceneKind) {
args.push("--scene-kind", sceneKind);
}
// 如果提供了 targetUrl添加参数
if (targetUrl) {
args.push("--target-url", targetUrl);
}
args.push("--output-root", normalize(outputRoot));
if (lessons) {
args.push("--lessons", normalize(lessons));
}
// Pass scene info JSON if available
if (sceneInfoJson) {
args.push("--scene-info-json", sceneInfoJson);
}
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");
}
// Read index.html
const indexHtmlPath = p.join(sourceDir, "index.html");
if (fs.existsSync(indexHtmlPath)) {
result.indexHtml = fs.readFileSync(indexHtmlPath, "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 };