# Scene Skill Generator — Design Document > **Date:** 2026-04-16 > **Status:** Draft — awaiting review > **Author:** Qoder --- ## 1. Goal 提供一个可视化界面,让用户选择场景目录后,自动通过大模型提取 scene-id 和 scene-name,配置输出路径和 lessons 文件,一键调用 `sg_scene_generate` 生成完整的 skill 包,并实时查看生成日志。 --- ## 2. Architecture ``` ┌─────────────────────────────────────────────────────────────────┐ │ sg_scene_generator.html (浏览器) │ │ ┌───────────────────┐ ┌───────────────────────────────┐ │ │ │ 左侧:操作面板 │ │ 右侧:实时日志流 │ │ │ │ │ │ │ │ │ │ 📂 选择场景目录 │───────│ [状态卡片 + 实时滚动日志] │ │ │ │ │ │ │ │ │ │ 自动填充字段: │ │ 分析场景目录... │ │ │ │ - scene-id │ │ 调用大模型提取场景信息... │ │ │ │ - scene-name │ │ scene-id: tq-lineloss-report │ │ │ │ │ │ scene-name: 台区线损报表 │ │ │ │ 可编辑字段: │ │ 生成 skill 包... │ │ │ │ - 输出根路径 │ │ 写入 SKILL.toml... │ │ │ │ - lessons 路径 │ │ 写入 browser_script... │ │ │ │ │ │ ✅ 生成完成 │ │ │ │ [⚙ 设置] │ └───────────────────────────────┘ │ │ │ [🚀 生成 Skill] │ │ │ └───────────────────┘ │ └─────────────────────────────────────────────────────────────────┘ │ │ 1. POST /analyze (选择目录后自动触发) │ → 发送目录路径 + 文件内容 │ 2. SSE /generate (点击生成按钮后触发) │ → 推送实时进度 ▼ ┌─────────────────────────────────────────────────────────────────┐ │ server.js (Node.js, 默认端口 3210) │ │ │ │ POST /analyze │ │ 1. 读取 source-dir 下的关键文件 │ │ - scene.toml (如果存在) │ │ - *.js 脚本文件 │ │ - SKILL.md / SKILL.toml (如果存在) │ │ - 目录结构树 │ │ 2. 构造 prompt,调用 LLM API │ │ - baseUrl + apiKey + model 来自 sgclaw_config.json │ │ 3. 返回 JSON: { sceneId, sceneName } │ │ │ │ POST /generate │ │ 1. 接收 { sourceDir, sceneId, sceneName, outputRoot, lessons } │ │ 2. spawn: cargo run --bin sg_scene_generate \ │ │ --source-dir \ │ │ --scene-id \ │ │ --scene-name \ │ │ --output-root \ │ │ --lessons │ │ 3. 通过 SSE 实时推送 stdout/stderr │ │ 4. 推送完成/失败事件 │ │ │ │ GET /health │ │ → { status: "ok", pid: 12345 } │ │ │ │ GET / │ │ → 服务 sg_scene_generator.html 静态文件 │ └─────────────────────────────────────────────────────────────────┘ │ │ LLM API (OpenAI-compatible format) │ POST {baseUrl}/v1/chat/completions ▼ ┌──────────────────────┐ │ LLM (DeepSeek) │ │ │ │ System: 你是一个场景 │ │ 信息提取助手... │ │ User: 以下是场景目录 │ │ 内容... 请提取 │ │ scene-id 和 │ │ scene-name │ └──────────────────────┘ ``` --- ## 3. File Map ### 新建文件 | 文件 | 说明 | |------|------| | `frontend/scene-generator/sg_scene_generator.html` | 主页面,内联 CSS + JS,复用 service-console 设计风格 | | `frontend/scene-generator/server.js` | Node.js 轻量 HTTP 服务器(零外部依赖) | | `frontend/scene-generator/serve.sh` | 一键启动脚本(Windows 兼容) | | `frontend/scene-generator/serve.cmd` | Windows 一键启动脚本 | | `frontend/scene-generator/config-loader.js` | 读取并解析 `sgclaw_config.json` | | `frontend/scene-generator/llm-client.js` | 封装 LLM API 调用(OpenAI-compatible 格式) | | `frontend/scene-generator/generator-runner.js` | 封装 `sg_scene_generate` 子进程调用 + SSE 推送 | ### 引用文件(不修改) | 文件 | 用途 | |------|------| | `src/bin/sg_scene_generate.rs` | 被 server.js 通过 `cargo run` 调用 | | `src/generated_scene/generator.rs` | 理解生成逻辑和输出结构 | | `sgclaw_config.json` | 读取 LLM 连接配置(apiKey, baseUrl, model) | | `docs/superpowers/references/tq-lineloss-lessons-learned.toml` | 默认 lessons 路径 | | `frontend/service-console/sg_claw_service_console.html` | UI 风格参考 | --- ## 4. UI Design ### 4.1 整体布局 复用 service-console 的双栏布局: - **外层容器 (`.shell`)**:圆角玻璃拟态面板,与 service-console 共享 CSS 变量 - **顶部 (`.hero`)**:标题 "场景 Skill 生成器" + 简短说明 - **内容区 (`.content`)**:`grid` 双栏,左侧操作面板 + 右侧日志流 ### 4.2 左侧操作面板 #### 场景目录选择区 ``` 📂 场景目录 [ 粘贴或输入路径 ____________________________ ] [ 浏览 📁 ] 当前:D:\data\ideaSpace\rust\sgClaw\claw-new\examples\generated_scene_platform\scenarios\tq-lineloss-report ``` 使用文本输入框 + "浏览" 按钮。点击 "浏览" 时,前端调用 `POST /browse`,由 Node.js 弹出系统目录选择对话框(通过 `electron` 风格的 `open-dialog` 不可行 — 改为**用户在输入框中粘贴/输入路径**,服务端通过 `fs.stat` 校验路径合法性)。 为简化实现,采用更务实的方案: - 主输入框:用户粘贴或手动输入场景目录的**绝对路径** - 输入路径后按回车或点击 "分析" 按钮,触发 `/analyze` 请求 - 服务端通过 `fs.statSync(sourceDir).isDirectory()` 校验路径 **可选增强**:如果 Node.js 安装了 `electron`,可通过 `dialog.showOpenDialog` 弹出系统选择框,但这会增加依赖。默认不采用。 #### 自动提取结果(只读展示,可手动修正) ``` scene-id tq-lineloss-report scene-name 台区线损报表 ``` 分析中显示 loading 状态,分析失败时可手动输入。 #### 设置按钮 点击弹出模态框,包含以下字段: | 字段 | 默认值 | 说明 | |------|--------|------| | 输出根路径 | `D:/data/ideaSpace/rust/sgClaw/claw-new/examples/generated_scene_platform` | skill 包输出根目录,实际输出到 `/skills//` | | Lessons 路径 | `D:/data/ideaSpace/rust/sgClaw/claw-new/docs/superpowers/references/tq-lineloss-lessons-learned.toml` | lessons TOML 文件路径 | | LLM 服务地址 | 来自 `sgclaw_config.json` 的 `baseUrl` | 可覆盖 | | LLM 模型 | 来自 `sgclaw_config.json` 的 `model` | 可覆盖 | | Node 服务端口 | `3210` | server.js 监听端口 | #### 生成按钮 ``` [ 🚀 生成 Skill ] (disabled 直到选择了目录且提取完成) ``` ### 4.3 右侧日志流 与 service-console 一致的流式日志展示: - **空状态**:显示提示 "选择场景目录开始生成" - **status 行**:关键阶段标记("开始分析", "提取完成", "开始生成", "生成成功") - **log 行**:cargo run 的 stdout 输出 - **error 行**:stderr 输出或错误信息 - **complete 行**:最终结果,包含生成的 skill 包路径 ### 4.4 状态卡片 左侧面板顶部显示当前状态: ``` [●] 就绪 / 分析中 / 生成中 / 完成 / 错误 ``` 颜色编码: - 就绪:灰色 - 分析中:橙色 - 生成中:青色(accent) - 完成:绿色 - 错误:红色 --- ## 5. API Design ### 5.1 POST /analyze **请求体:** ```json { "sourceDir": "D:/data/ideaSpace/rust/sgClaw/claw-new/examples/generated_scene_platform/scenarios/tq-lineloss-report" } ``` 服务端自行读取目录内容: - 校验路径是否存在且为目录 - 读取 `scene.toml`(如果存在) - 读取 `*.js` 脚本文件 - 读取 `SKILL.md` / `SKILL.toml`(如果存在) - 生成目录结构树 **响应:** ```json { "sceneId": "tq-lineloss-report", "sceneName": "台区线损报表" } ``` **LLM Prompt 设计:** ``` System: 你是一个场景信息提取助手。根据场景目录的内容,提取 scene-id 和 scene-name。 scene-id 规则: - 使用英文短横线连接,如 tq-lineloss-report - 全小写,有业务含义 scene-name 规则: - 使用中文,简短描述性名称 - 如 "台区线损报表"、"知乎热榜导出" User: 以下是场景目录的内容: === scene.toml === [scene content here] === 脚本文件 === [script content here] === 目录结构 === [file tree here] 请以 JSON 格式返回:{"sceneId": "...", "sceneName": "..."} ``` ### 5.2 POST /generate (SSE) **请求体:** ```json { "sourceDir": "/path/to/scenario/dir", "sceneId": "tq-lineloss-report", "sceneName": "台区线损报表", "outputRoot": "/path/to/output/root", "lessons": "/path/to/lessons.toml" } ``` **SSE 事件流:** ``` event: status data: {"message": "开始生成 skill 包..."} event: status data: {"message": "调用 sg_scene_generate..."} event: log data: {"message": "generated scene package: ..."} event: complete data: {"success": true, "skillRoot": "/path/to/skills/tq-lineloss-report"} 或 event: error data: {"message": "生成失败: ..."} ``` ### 5.3 GET /health **响应:** ```json { "status": "ok", "pid": 12345, "configLoaded": true, "configPath": "D:/data/ideaSpace/rust/sgClaw/sgclaw_config.json" } ``` --- ## 6. Server Design (server.js) ### 6.1 模块结构 ``` server.js — HTTP 路由入口,SSE 连接管理 config-loader.js — 读取 sgclaw_config.json,暴露 LLM 配置 + projectRoot llm-client.js — 调用 LLM API,返回 JSON 提取结果 generator-runner.js — spawn 子进程,通过 SSE 推送输出 ``` ### 6.1.1 projectRoot 配置 `cargo run --bin sg_scene_generate` 需要在项目根目录下执行。`projectRoot` 的确定优先级: 1. 环境变量 `SGCLAW_PROJECT_ROOT`(最高优先级) 2. `sgclaw_config.json` 同级目录(常见情况:配置文件在项目根目录) 3. 启动脚本所在目录 ### 6.2 零依赖原则 仅使用 Node.js 内置模块: - `http` — HTTP 服务器 - `fs` — 文件读取 - `path` — 路径处理 - `child_process` — 子进程调用 - `events` — 事件发射 ### 6.3 启动流程 ``` 1. 读取 sgclaw_config.json (路径通过环境变量 SGCLAW_CONFIG_PATH 或默认 ../sgclaw_config.json) 2. 验证必需字段: apiKey, baseUrl, model 3. 启动 HTTP 服务器,监听 0.0.0.0:3210 4. 打印启动信息,包含访问地址 ``` ### 6.4 错误处理 | 场景 | 处理方式 | |------|----------| | sgclaw_config.json 不存在 | 启动失败,提示用户设置环境变量 | | LLM API 调用失败 | 返回 502 + 错误信息,前端允许手动输入 | | cargo run 失败 | SSE 推送 error 事件,显示 stderr | | source-dir 不存在 | 返回 400 | | 端口被占用 | 启动失败,提示更换端口 | --- ## 7. Security Considerations 1. **仅监听 localhost**:server.js 默认绑定 `127.0.0.1`,不暴露到外部网络 2. **API Key 不暴露给前端**:LLM API 调用完全在 Node.js 服务端完成,前端不接触 API Key 3. **路径校验**:`sourceDir` 和 `outputRoot` 需做基本路径合法性检查,防止路径遍历攻击 4. **子进程超时**:`cargo run` 设置 5 分钟超时,防止挂起 --- ## 8. Default Configuration | 配置项 | 默认值 | 来源 | |--------|--------|------| | LLM apiKey | `sgclaw_config.json` 中的 `apiKey` | 启动时读取 | | LLM baseUrl | `sgclaw_config.json` 中的 `baseUrl` | 启动时读取 | | LLM model | `sgclaw_config.json` 中的 `model` | 启动时读取 | | 默认 lessons 路径 | `docs/superpowers/references/tq-lineloss-lessons-learned.toml` | 项目约定 | | 默认输出根路径 | `examples/generated_scene_platform` | 项目约定 | | Node 服务端口 | `3210` | 硬编码,可配置 | --- ## 9. User Flow ``` 1. 用户运行 bash serve.sh (或 node server.js) 2. 浏览器打开 http://127.0.0.1:3210 3. 页面加载,显示 "就绪" 状态 4. 用户在 "场景目录" 输入框中粘贴或输入绝对路径 5. 用户点击 "分析" 按钮(或输入框回车),触发 /analyze 请求 6. server.js 读取目录内容,调用 LLM 提取 scene-id/name 7. 页面自动填充 scene-id 和 scene-name 字段 8. 用户确认/修改字段,点击 "设置" 检查输出路径和 lessons 9. 用户点击 "生成 Skill" 10. server.js 通过 SSE 推送实时进度 11. 页面右侧日志流展示生成过程 12. 生成完成,显示 skill 包路径 13. 用户可前往输出目录查看生成的 skill ``` --- ## 10. Windows Compatibility 由于目标平台是 Windows: - `serve.sh` 同时提供 `serve.cmd` 替代方案 - 路径分隔符统一使用 `/`(Node.js `path` 模块自动处理) - `cargo run` 命令在 Windows 上同样可用 - 路径输入框支持 Windows 格式路径(如 `D:\data\ideaSpace\...`) - 服务端自动将 `\` 转换为 `/` 以兼容 Rust CLI 参数 --- ## 11. Future Extensions (Not in Scope) - 批量生成:一次选择多个场景目录 - 生成后自动注册到 scene.toml manifest - 生成后自动运行 skill 测试 - 历史记录:保存之前的生成记录 - 生成参数模板:保存常用的输出路径/lessons 组合