From 2ffb42c181d4d58278def05dcff64b7e7e43d736 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=A8=E7=82=8E?= <635735027@qq.com> Date: Fri, 17 Apr 2026 10:20:04 +0800 Subject: [PATCH] feat(server): add /analyze-deep endpoint for deep extraction MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Qoder][https://qoder.com] --- frontend/scene-generator/server.js | 90 ++++++++++++++++++++++++++---- 1 file changed, 80 insertions(+), 10 deletions(-) diff --git a/frontend/scene-generator/server.js b/frontend/scene-generator/server.js index 887b096..9a7d72f 100644 --- a/frontend/scene-generator/server.js +++ b/frontend/scene-generator/server.js @@ -6,7 +6,7 @@ const fs = require("fs"); const path = require("path"); const { spawn } = require("child_process"); const { loadConfig, getDefaults } = require("./config-loader"); -const { analyzeScene } = require("./llm-client"); +const { analyzeScene, analyzeSceneDeep } = require("./llm-client"); const { runGenerator, readDirectory } = require("./generator-runner"); 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) { let body; try { @@ -128,12 +177,12 @@ async function handleGenerate(req, res) { } 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.end( JSON.stringify({ error: - "All fields required: sourceDir, sceneId, sceneName, outputRoot, lessons", + "All fields required: sourceDir, sceneId, sceneName, outputRoot", }) ); return; @@ -174,6 +223,7 @@ function handleHealth(req, res) { function openFolderDialog(defaultPath) { return new Promise((resolve) => { const psScript = ` +[Console]::OutputEncoding = [System.Text.Encoding]::UTF8 Add-Type -AssemblyName System.Windows.Forms $dialog = New-Object System.Windows.Forms.FolderBrowserDialog $dialog.Description = "选择文件夹" @@ -189,22 +239,29 @@ if ($dialog.ShowDialog() -eq 'OK') { "-NonInteractive", "-Command", psScript, - ]); + ], { + windowsHide: true, + }); let output = ""; let error = ""; ps.stdout.on("data", (data) => { - output += data.toString(); + output += data.toString("utf8"); }); ps.stderr.on("data", (data) => { - error += data.toString(); + error += data.toString("utf8"); }); ps.on("close", (code) => { 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 { resolve(null); } @@ -239,6 +296,7 @@ async function handleSelectFile(req, res) { const filter = body.filter || "所有文件 (*.*)|*.*"; const psScript = ` +[Console]::OutputEncoding = [System.Text.Encoding]::UTF8 Add-Type -AssemblyName System.Windows.Forms $dialog = New-Object System.Windows.Forms.OpenFileDialog $dialog.Filter = '${filter}' @@ -255,17 +313,23 @@ if ($dialog.ShowDialog() -eq 'OK') { "-NonInteractive", "-Command", psScript, - ]); + ], { + windowsHide: true, + }); let output = ""; ps.stdout.on("data", (data) => { - output += data.toString(); + output += data.toString("utf8"); }); 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.end(JSON.stringify({ path: code === 0 && output.trim() ? output.trim() : null })); + res.end(JSON.stringify({ path: code === 0 && path ? path : null })); resolve(); }); @@ -296,6 +360,8 @@ const server = http.createServer(async (req, res) => { handleHealth(req, res); } else if (pathname === "/analyze" && req.method === "POST") { await handleAnalyze(req, res); + } else if (pathname === "/analyze-deep" && req.method === "POST") { + await handleAnalyzeDeep(req, res); } else if (pathname === "/generate" && req.method === "POST") { await handleGenerate(req, res); } else if (pathname === "/select-folder" && req.method === "POST") { @@ -342,6 +408,10 @@ server.listen(PORT, HOST, () => { }); process.on("SIGINT", () => { + if (server.closing) return; + server.closing = true; console.log("\n[info] Shutting down..."); server.close(() => process.exit(0)); + // 强制退出超时 + setTimeout(() => process.exit(0), 2000); });