feat(server): add /analyze-deep endpoint for deep extraction
🤖 Generated with [Qoder][https://qoder.com]
This commit is contained in:
@@ -6,7 +6,7 @@ const fs = require("fs");
|
|||||||
const path = require("path");
|
const path = require("path");
|
||||||
const { spawn } = require("child_process");
|
const { spawn } = require("child_process");
|
||||||
const { loadConfig, getDefaults } = require("./config-loader");
|
const { loadConfig, getDefaults } = require("./config-loader");
|
||||||
const { analyzeScene } = require("./llm-client");
|
const { analyzeScene, analyzeSceneDeep } = require("./llm-client");
|
||||||
const { runGenerator, readDirectory } = require("./generator-runner");
|
const { runGenerator, readDirectory } = require("./generator-runner");
|
||||||
|
|
||||||
let config;
|
let config;
|
||||||
@@ -117,6 +117,55 @@ async function handleAnalyze(req, res) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handleAnalyzeDeep(req, res) {
|
||||||
|
let body;
|
||||||
|
try {
|
||||||
|
body = await parseBody(req);
|
||||||
|
} catch {
|
||||||
|
res.writeHead(400, { "Content-Type": "application/json" });
|
||||||
|
res.end(JSON.stringify({ error: "Invalid JSON body" }));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sourceDir = (body.sourceDir || "").replace(/\\/g, "/");
|
||||||
|
if (!sourceDir) {
|
||||||
|
res.writeHead(400, { "Content-Type": "application/json" });
|
||||||
|
res.end(JSON.stringify({ error: "sourceDir is required" }));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let dirContents;
|
||||||
|
try {
|
||||||
|
dirContents = readDirectory(sourceDir);
|
||||||
|
} catch (err) {
|
||||||
|
res.writeHead(400, { "Content-Type": "application/json" });
|
||||||
|
res.end(JSON.stringify({ error: err.message }));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const indexHtmlContent = dirContents.indexHtml || null;
|
||||||
|
const result = await analyzeSceneDeep(sourceDir, dirContents, indexHtmlContent, config);
|
||||||
|
|
||||||
|
// Log extraction results for debugging
|
||||||
|
console.log(`[analyze-deep] Extracted scene: ${result.sceneId} / ${result.sceneName}`);
|
||||||
|
console.log(`[analyze-deep] API endpoints: ${result.apiEndpoints?.length || 0}`);
|
||||||
|
console.log(`[analyze-deep] Column defs: ${result.columnDefs?.length || 0}`);
|
||||||
|
|
||||||
|
res.writeHead(200, { "Content-Type": "application/json" });
|
||||||
|
res.end(JSON.stringify(result));
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`[analyze-deep] Error: ${err.message}`);
|
||||||
|
res.writeHead(502, { "Content-Type": "application/json" });
|
||||||
|
res.end(
|
||||||
|
JSON.stringify({
|
||||||
|
error: `Deep analysis failed: ${err.message}`,
|
||||||
|
hint: "You can still use basic analysis or enter data manually",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function handleGenerate(req, res) {
|
async function handleGenerate(req, res) {
|
||||||
let body;
|
let body;
|
||||||
try {
|
try {
|
||||||
@@ -128,12 +177,12 @@ async function handleGenerate(req, res) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { sourceDir, sceneId, sceneName, sceneKind, targetUrl, outputRoot, lessons } = body;
|
const { sourceDir, sceneId, sceneName, sceneKind, targetUrl, outputRoot, lessons } = body;
|
||||||
if (!sourceDir || !sceneId || !sceneName || !outputRoot || !lessons) {
|
if (!sourceDir || !sceneId || !sceneName || !outputRoot) {
|
||||||
res.writeHead(400, { "Content-Type": "application/json" });
|
res.writeHead(400, { "Content-Type": "application/json" });
|
||||||
res.end(
|
res.end(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
error:
|
error:
|
||||||
"All fields required: sourceDir, sceneId, sceneName, outputRoot, lessons",
|
"All fields required: sourceDir, sceneId, sceneName, outputRoot",
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
@@ -174,6 +223,7 @@ function handleHealth(req, res) {
|
|||||||
function openFolderDialog(defaultPath) {
|
function openFolderDialog(defaultPath) {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
const psScript = `
|
const psScript = `
|
||||||
|
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
||||||
Add-Type -AssemblyName System.Windows.Forms
|
Add-Type -AssemblyName System.Windows.Forms
|
||||||
$dialog = New-Object System.Windows.Forms.FolderBrowserDialog
|
$dialog = New-Object System.Windows.Forms.FolderBrowserDialog
|
||||||
$dialog.Description = "选择文件夹"
|
$dialog.Description = "选择文件夹"
|
||||||
@@ -189,22 +239,29 @@ if ($dialog.ShowDialog() -eq 'OK') {
|
|||||||
"-NonInteractive",
|
"-NonInteractive",
|
||||||
"-Command",
|
"-Command",
|
||||||
psScript,
|
psScript,
|
||||||
]);
|
], {
|
||||||
|
windowsHide: true,
|
||||||
|
});
|
||||||
|
|
||||||
let output = "";
|
let output = "";
|
||||||
let error = "";
|
let error = "";
|
||||||
|
|
||||||
ps.stdout.on("data", (data) => {
|
ps.stdout.on("data", (data) => {
|
||||||
output += data.toString();
|
output += data.toString("utf8");
|
||||||
});
|
});
|
||||||
|
|
||||||
ps.stderr.on("data", (data) => {
|
ps.stderr.on("data", (data) => {
|
||||||
error += data.toString();
|
error += data.toString("utf8");
|
||||||
});
|
});
|
||||||
|
|
||||||
ps.on("close", (code) => {
|
ps.on("close", (code) => {
|
||||||
if (code === 0 && output.trim()) {
|
if (code === 0 && output.trim()) {
|
||||||
resolve(output.trim());
|
// 移除可能的 BOM 标记
|
||||||
|
let path = output.trim();
|
||||||
|
if (path.charCodeAt(0) === 0xFEFF) {
|
||||||
|
path = path.slice(1);
|
||||||
|
}
|
||||||
|
resolve(path);
|
||||||
} else {
|
} else {
|
||||||
resolve(null);
|
resolve(null);
|
||||||
}
|
}
|
||||||
@@ -239,6 +296,7 @@ async function handleSelectFile(req, res) {
|
|||||||
|
|
||||||
const filter = body.filter || "所有文件 (*.*)|*.*";
|
const filter = body.filter || "所有文件 (*.*)|*.*";
|
||||||
const psScript = `
|
const psScript = `
|
||||||
|
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
||||||
Add-Type -AssemblyName System.Windows.Forms
|
Add-Type -AssemblyName System.Windows.Forms
|
||||||
$dialog = New-Object System.Windows.Forms.OpenFileDialog
|
$dialog = New-Object System.Windows.Forms.OpenFileDialog
|
||||||
$dialog.Filter = '${filter}'
|
$dialog.Filter = '${filter}'
|
||||||
@@ -255,17 +313,23 @@ if ($dialog.ShowDialog() -eq 'OK') {
|
|||||||
"-NonInteractive",
|
"-NonInteractive",
|
||||||
"-Command",
|
"-Command",
|
||||||
psScript,
|
psScript,
|
||||||
]);
|
], {
|
||||||
|
windowsHide: true,
|
||||||
|
});
|
||||||
|
|
||||||
let output = "";
|
let output = "";
|
||||||
|
|
||||||
ps.stdout.on("data", (data) => {
|
ps.stdout.on("data", (data) => {
|
||||||
output += data.toString();
|
output += data.toString("utf8");
|
||||||
});
|
});
|
||||||
|
|
||||||
ps.on("close", (code) => {
|
ps.on("close", (code) => {
|
||||||
|
let path = output.trim();
|
||||||
|
if (path.charCodeAt(0) === 0xFEFF) {
|
||||||
|
path = path.slice(1);
|
||||||
|
}
|
||||||
res.writeHead(200, { "Content-Type": "application/json" });
|
res.writeHead(200, { "Content-Type": "application/json" });
|
||||||
res.end(JSON.stringify({ path: code === 0 && output.trim() ? output.trim() : null }));
|
res.end(JSON.stringify({ path: code === 0 && path ? path : null }));
|
||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -296,6 +360,8 @@ const server = http.createServer(async (req, res) => {
|
|||||||
handleHealth(req, res);
|
handleHealth(req, res);
|
||||||
} else if (pathname === "/analyze" && req.method === "POST") {
|
} else if (pathname === "/analyze" && req.method === "POST") {
|
||||||
await handleAnalyze(req, res);
|
await handleAnalyze(req, res);
|
||||||
|
} else if (pathname === "/analyze-deep" && req.method === "POST") {
|
||||||
|
await handleAnalyzeDeep(req, res);
|
||||||
} else if (pathname === "/generate" && req.method === "POST") {
|
} else if (pathname === "/generate" && req.method === "POST") {
|
||||||
await handleGenerate(req, res);
|
await handleGenerate(req, res);
|
||||||
} else if (pathname === "/select-folder" && req.method === "POST") {
|
} else if (pathname === "/select-folder" && req.method === "POST") {
|
||||||
@@ -342,6 +408,10 @@ server.listen(PORT, HOST, () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
process.on("SIGINT", () => {
|
process.on("SIGINT", () => {
|
||||||
|
if (server.closing) return;
|
||||||
|
server.closing = true;
|
||||||
console.log("\n[info] Shutting down...");
|
console.log("\n[info] Shutting down...");
|
||||||
server.close(() => process.exit(0));
|
server.close(() => process.exit(0));
|
||||||
|
// 强制退出超时
|
||||||
|
setTimeout(() => process.exit(0), 2000);
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user