diff --git a/frontend/scene-generator/sg_scene_generator.html b/frontend/scene-generator/sg_scene_generator.html
index 37d4e1d..279d333 100644
--- a/frontend/scene-generator/sg_scene_generator.html
+++ b/frontend/scene-generator/sg_scene_generator.html
@@ -114,6 +114,102 @@
.hint { font-size: 0.8rem; color: var(--muted); margin-top: 4px; }
.divider { height: 1px; background: var(--line); margin: 12px 0; }
@media (max-width: 900px) { body { padding: 16px; } .content { grid-template-columns: 1fr; } .sidebar { border-right: 0; border-bottom: 1px solid var(--line); } .stream { max-height: none; } }
+ /* Preview panel styles */
+ .preview-panel {
+ background: rgba(255, 255, 255, 0.6);
+ border-radius: 16px;
+ border: 1px solid var(--line);
+ overflow: hidden;
+ margin-top: 16px;
+ }
+ .preview-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 14px 16px;
+ cursor: pointer;
+ background: rgba(255, 255, 255, 0.5);
+ }
+ .preview-header h3 {
+ margin: 0;
+ font-size: 0.95rem;
+ font-weight: 700;
+ color: var(--text);
+ }
+ .preview-header:hover {
+ background: rgba(255, 255, 255, 0.7);
+ }
+ .preview-content {
+ padding: 16px;
+ }
+ .preview-section {
+ margin-bottom: 16px;
+ }
+ .preview-section:last-child {
+ margin-bottom: 0;
+ }
+ .preview-section h4 {
+ margin: 0 0 8px 0;
+ font-size: 0.85rem;
+ font-weight: 700;
+ color: var(--accent);
+ }
+ .preview-row {
+ display: flex;
+ margin-bottom: 6px;
+ }
+ .preview-row .label {
+ min-width: 80px;
+ color: var(--muted);
+ flex-shrink: 0;
+ font-size: 0.88rem;
+ }
+ .preview-row .value {
+ color: var(--text);
+ font-size: 0.88rem;
+ }
+ .preview-list {
+ max-height: 120px;
+ overflow-y: auto;
+ background: rgba(255, 255, 255, 0.8);
+ border-radius: 12px;
+ padding: 8px 12px;
+ border: 1px solid var(--line);
+ }
+ .preview-list-item {
+ padding: 5px 0;
+ border-bottom: 1px solid var(--line);
+ font-size: 0.85rem;
+ }
+ .preview-list-item:last-child {
+ border-bottom: none;
+ }
+ .preview-code {
+ background: rgba(0, 0, 0, 0.04);
+ padding: 10px;
+ border-radius: 10px;
+ font-family: "Consolas", "Monaco", monospace;
+ font-size: 0.8rem;
+ overflow-x: auto;
+ white-space: pre-wrap;
+ color: var(--text);
+ }
+ .btn-group {
+ display: flex;
+ gap: 8px;
+ margin-top: 12px;
+ }
+ .btn-group button {
+ flex: 1;
+ }
+ .secondary-btn {
+ background: rgba(15, 118, 110, 0.1);
+ color: var(--accent);
+ border: 1px solid rgba(15, 118, 110, 0.3);
+ }
+ .secondary-btn:hover:not(:disabled) {
+ background: rgba(15, 118, 110, 0.15);
+ }
@@ -157,6 +253,67 @@
场景要访问的目标页面地址,留空则使用自动提取的域名
+
+
+
+
+
+
+
+
Settings
@@ -166,13 +323,6 @@
-
-
-
-
-
-
-
@@ -201,9 +351,7 @@
targetUrl: document.getElementById("targetUrl"),
browseSourceDir: document.getElementById("browseSourceDir"),
browseOutputRoot: document.getElementById("browseOutputRoot"),
- browseLessons: document.getElementById("browseLessons"),
settingOutputRoot: document.getElementById("settingOutputRoot"),
- settingLessons: document.getElementById("settingLessons"),
generateBtn: document.getElementById("generateBtn"),
validationText: document.getElementById("validationText"),
stateChip: document.getElementById("stateChip"),
@@ -212,6 +360,8 @@
emptyState: document.getElementById("emptyState"),
};
let defaultsLoaded = false;
+ let currentSceneInfo = null; // Stores deep extraction results
+ let previewExpanded = false;
function setState(state, text) {
els.stateChip.textContent = text;
@@ -281,7 +431,6 @@
if (health.projectRoot) {
const root = health.projectRoot.replace(/\\/g, "/");
els.settingOutputRoot.value = root + "/examples/generated_scene_platform";
- els.settingLessons.value = root + "/docs/superpowers/references/tq-lineloss-lessons-learned.toml";
}
updateGenerateBtn();
} catch (err) {
@@ -291,6 +440,144 @@
}
}
+ async function analyzeSourceDir(sourceDir) {
+ if (!sourceDir) return;
+ setState("analyzing", "正在分析场景目录...");
+ appendRow("status", "开始分析场景目录...");
+ try {
+ const res = await fetch(`${SERVER_URL}/analyze`, {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ sourceDir }),
+ });
+ const data = await res.json();
+ if (!res.ok) throw new Error(data.error || "Analyze failed");
+ if (data.sceneId) {
+ els.sceneId.value = data.sceneId;
+ }
+ if (data.sceneName) {
+ els.sceneName.value = data.sceneName;
+ }
+ appendRow("status", `分析完成: ${data.sceneId || ""} ${data.sceneName || ""}`.trim());
+ } catch (err) {
+ appendRow("error", `分析失败: ${err.message}`);
+ } finally {
+ setState("ready", "就绪");
+ updateGenerateBtn();
+ }
+ }
+
+ function togglePreview() {
+ const content = document.getElementById("previewContent");
+ const icon = document.getElementById("previewToggleIcon");
+ previewExpanded = !previewExpanded;
+ content.style.display = previewExpanded ? "block" : "none";
+ icon.textContent = previewExpanded ? "▲" : "▼";
+ }
+
+ function showExtractionPreview(data) {
+ const panel = document.getElementById("extractionPreview");
+ panel.style.display = "block";
+ previewExpanded = true;
+ document.getElementById("previewContent").style.display = "block";
+ document.getElementById("previewToggleIcon").textContent = "▲";
+
+ // Basic info
+ document.getElementById("previewSceneId").textContent = data.sceneId || "-";
+ document.getElementById("previewSceneName").textContent = data.sceneName || "-";
+ document.getElementById("previewSceneKind").textContent = data.sceneKind || "-";
+ document.getElementById("previewExpectedDomain").textContent = data.expectedDomain || "-";
+
+ // API endpoints
+ const apiList = document.getElementById("previewApiEndpoints");
+ const apiCount = document.getElementById("previewApiCount");
+ if (data.apiEndpoints && data.apiEndpoints.length > 0) {
+ apiCount.textContent = data.apiEndpoints.length;
+ apiList.innerHTML = data.apiEndpoints.map(ep =>
+ `${escapeHtml(ep)}
`
+ ).join("");
+ } else {
+ apiCount.textContent = "0";
+ apiList.innerHTML = '无
';
+ }
+
+ // Column definitions
+ const colList = document.getElementById("previewColumnDefs");
+ const colCount = document.getElementById("previewColumnCount");
+ if (data.columnDefs && data.columnDefs.length > 0) {
+ colCount.textContent = data.columnDefs.length;
+ colList.innerHTML = data.columnDefs.map(col =>
+ `${escapeHtml(col)}
`
+ ).join("");
+ } else {
+ colCount.textContent = "0";
+ colList.innerHTML = '无
';
+ }
+
+ // Static params
+ const staticParams = document.getElementById("previewStaticParams");
+ if (data.staticParams) {
+ staticParams.textContent = JSON.stringify(data.staticParams, null, 2);
+ } else {
+ staticParams.textContent = "{}";
+ }
+
+ // Business logic
+ const biz = data.businessLogic || {};
+ document.getElementById("previewDataFetch").textContent = biz.dataFetch || "-";
+ document.getElementById("previewDataTransform").textContent = biz.dataTransform || "-";
+ }
+
+ function escapeHtml(str) {
+ if (!str) return "";
+ return str
+ .replace(/&/g, "&")
+ .replace(//g, ">")
+ .replace(/"/g, """);
+ }
+
+ async function analyzeDeep() {
+ const sourceDir = els.sourceDir.value.trim().replace(/\\/g, "/");
+ if (!sourceDir) {
+ setValidation("请先选择场景目录");
+ return;
+ }
+ setValidation("");
+ setState("analyzing", "正在深度分析...");
+ appendRow("status", "开始深度分析场景...");
+
+ try {
+ const res = await fetch(`${SERVER_URL}/analyze-deep`, {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ sourceDir }),
+ });
+ const data = await res.json();
+ if (!res.ok) throw new Error(data.error || "Deep analysis failed");
+
+ // Store the scene info for generation
+ currentSceneInfo = data;
+
+ // Fill in form fields if not already set
+ if (data.sceneId && !els.sceneId.value.trim()) {
+ els.sceneId.value = data.sceneId;
+ }
+ if (data.sceneName && !els.sceneName.value.trim()) {
+ els.sceneName.value = data.sceneName;
+ }
+
+ // Show preview
+ showExtractionPreview(data);
+ appendRow("status", `深度分析完成: 找到 ${data.apiEndpoints?.length || 0} 个 API 端点, ${data.columnDefs?.length || 0} 个列定义`);
+ } catch (err) {
+ appendRow("error", `深度分析失败: ${err.message}`);
+ } finally {
+ setState("ready", "就绪");
+ updateGenerateBtn();
+ }
+ }
+
async function generate() {
const sourceDir = els.sourceDir.value.trim().replace(/\\/g, "/");
const sceneId = els.sceneId.value.trim();
@@ -298,18 +585,29 @@
const sceneKind = els.sceneKind.value;
const targetUrl = els.targetUrl.value.trim();
const outputRoot = els.settingOutputRoot.value.trim().replace(/\\/g, "/");
- const lessons = els.settingLessons.value.trim().replace(/\\/g, "/");
- if (!sourceDir || !sceneId || !sceneName || !outputRoot || !lessons) { setValidation("所有字段均为必填"); return; }
+ if (!sourceDir || !sceneId || !sceneName || !outputRoot) { setValidation("场景目录、scene-id、scene-name、输出根路径为必填"); return; }
setValidation("");
setState("generating", "正在生成 skill 包...");
els.generateBtn.disabled = true;
appendRow("status", "开始生成 skill 包...");
try {
+ const requestBody = {
+ sourceDir,
+ sceneId,
+ sceneName,
+ sceneKind,
+ targetUrl: targetUrl || null,
+ outputRoot,
+ };
+ // Include deep extraction results if available
+ if (currentSceneInfo) {
+ requestBody.sceneInfoJson = JSON.stringify(currentSceneInfo);
+ }
const res = await fetch(`${SERVER_URL}/generate`, {
method: "POST",
headers: { "Content-Type": "application/json" },
- body: JSON.stringify({ sourceDir, sceneId, sceneName, sceneKind, targetUrl: targetUrl || null, outputRoot, lessons }),
+ body: JSON.stringify(requestBody),
});
if (!res.ok) { const err = await res.json(); throw new Error(err.error || "Generation failed"); }
@@ -359,13 +657,13 @@
const path = await selectFolder(els.sourceDir.value || null);
if (path) {
els.sourceDir.value = path;
- // Auto-fill scene-id from folder name
const parts = path.replace(/\\/g, "/").split("/");
const folderName = parts[parts.length - 1];
if (folderName && !els.sceneId.value) {
els.sceneId.value = folderName;
}
updateGenerateBtn();
+ await analyzeSourceDir(path.replace(/\\/g, "/"));
}
});
@@ -377,14 +675,6 @@
}
});
- els.browseLessons.addEventListener("click", async () => {
- const path = await selectFile(els.settingLessons.value || null, "TOML 文件 (*.toml)|*.toml|所有文件 (*.*)|*.*");
- if (path) {
- els.settingLessons.value = path;
- updateGenerateBtn();
- }
- });
-
els.generateBtn.addEventListener("click", generate);
els.sceneId.addEventListener("input", updateGenerateBtn);
els.sceneName.addEventListener("input", updateGenerateBtn);