Files
claw/docs/superpowers/specs/2026-04-16-scene-skill-generator-design.md
木炎 ea6be128e7 feat: scene skill generator — complete implementation
Adds a web-based UI for generating scene skill packages:
- Node.js HTTP server (zero npm dependencies) on port 3210
- HTML page with glass-morphism UI, dual-panel layout, settings modal
- LLM-powered scene-id/scene-name auto-extraction from directory contents
- Real-time SSE progress streaming during skill generation
- Spawns sg_scene_generate CLI with configurable parameters
- Windows-compatible startup scripts (serve.sh + serve.cmd)
- Rust integration tests for server files and HTML structure

Architecture:
  Browser (HTML/JS) → Node.js server → LLM API + cargo run → sg_scene_generate

Files:
  frontend/scene-generator/{server.js,config-loader.js,llm-client.js,generator-runner.js,sg_scene_generator.html,serve.sh,serve.cmd}
  tests/{scene_generator_server_test.rs,scene_generator_html_test.rs,scene_generator_llm_test.js}
  docs/superpowers/{plans,specs}/2026-04-16-scene-skill-generator*

🤖 Generated with [Qoder][https://qoder.com]
2026-04-16 22:27:41 +08:00

418 lines
16 KiB
Markdown
Raw Permalink 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.
# 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 <sourceDir> \ │
│ --scene-id <sceneId> \ │
│ --scene-name <sceneName> \ │
│ --output-root <outputRoot> \ │
│ --lessons <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 包输出根目录,实际输出到 `<output-root>/skills/<scene-id>/` |
| 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 组合