# 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 组合
