# 书籍生成模块 (Book Generator)

基于 LangGraph 的三级目录书籍生成模块。

## 核心设计

### 三级目录结构

```
章 (level=1, parentId=null)
 └─ 节 (level=2, parentId=章ID)
      └─ 小节 (level=3, parentId=节ID) ← 存储实际正文内容
```

### 内容存储规则

| 层级 | level | parentId | content | 说明 |
|------|-------|----------|---------|------|
| 章 | 1 | null | 不存储 | 只做目录 |
| 节 | 2 | 章ID | 不存储 | 只做目录 |
| 小节 | 3 | 节ID | **存储** | 唯一内容存储 |

**设计原则**：正文只存在于小节，章/节只做目录导航，无冗余数据。

## 数据库结构

### BookChapter 表

```prisma
model BookChapter {
  id              Int       @id @default(autoincrement())
  bookId          Int
  parentId        Int?      // 父节点ID（null = 章这一级）
  level           Int       @default(1)  // 1=章, 2=节, 3=小节
  number          Int       // 同级排序序号
  title           String    // 章节标题
  summary         String?   @db.Text
  keyPoints       String?   @db.Text  // JSON数组
  estimatedWords  Int       @default(1000)
  content         String?   @db.LongText  // 仅小节存储正文
  wordCount       Int       @default(0)
  status          String    @default("pending")

  book            Book      @relation(fields: [bookId], references: [id], onDelete: Cascade)
  parent          BookChapter? @relation("ChapterChildren", fields: [parentId], references: [id])
  children        BookChapter[] @relation("ChapterChildren")

  @@unique([bookId, level, number])
  @@index([bookId])
  @@index([bookId, parentId])
  @@index([bookId, level])
}
```

### 示例数据

| id | bookId | parentId | level | number | title |
|----|--------|----------|-------|--------|-------|
| 1 | 1 | null | 1 | 1 | 第一章：人工智能概述 |
| 5 | 1 | 1 | 2 | 1 | 1.1 人工智能的历史 |
| 10 | 1 | 5 | 3 | 1 | 1.1.1 早期发展 |
| 11 | 1 | 5 | 3 | 2 | 1.1.2 符号主义与统计方法 |

## 生成流程

```
1. generateOutlineNode
   └─ 生成章标题 → 入库 level=1

2. generateFullOutlineNode
   └─ 为每章生成节 → 入库 level=2
   └─ 为每节生成小节 → 入库 level=3

3. writeChaptersNode
   └─ 逐小节生成 content → 写入 level=3
   └─ 不再合并到 level=1/2

4. writeForewordNode → 写入 Book.foreword

5. writeAfterwordNode → 写入 Book.afterword
```

## 状态流转

```
draft → generating → planning → generating → completed
         (创建)      (大纲)    (内容)     (完成)
```

| 状态 | 说明 |
|------|------|
| draft | 草稿 |
| generating | 生成中（创建/内容阶段） |
| planning | 规划中（大纲生成阶段） |
| completed | 已完成 |
| failed | 失败 |

## API 接口

### 创建书籍（LangGraph 异步）

```
POST /api/book-generator/langgraph/books
```

### 获取书籍详情

```
GET /api/book-generator/langgraph/books/:id
```

返回章节数据示例：

```json
{
  "book": {
    "id": "1",
    "title": "人工智能入门",
    "chapters": [
      {
        "id": "1",
        "number": 1,
        "title": "第一章：人工智能概述",
        "level": 1,
        "parentId": null,
        "content": ""
      },
      {
        "id": "5",
        "number": 1,
        "title": "1.1 人工智能的历史",
        "level": 2,
        "parentId": 1,
        "content": ""
      },
      {
        "id": "10",
        "number": 1,
        "title": "1.1.1 早期发展",
        "level": 3,
        "parentId": 5,
        "content": "正文内容..."
      }
    ]
  }
}
```

## 前端渲染

### 按 parentId 分组构建树

```typescript
function buildChapterTree(chapters: Chapter[]) {
  const map = new Map<number | null, Chapter[]>();
  chapters.forEach(c => {
    const key = c.parentId;
    if (!map.has(key)) map.set(key, []);
    map.get(key)!.push(c);
  });
  return {
    chapters: map.get(null) || [],  // 章 level=1
    getSections: (chapterId: number) => map.get(chapterId) || [],  // 节 level=2
    getSubsections: (sectionId: number) => map.get(sectionId) || [],  // 小节 level=3
  };
}
```

### Vue 模板示例

```vue
<view v-for="chapter in chapters.filter(c => c.level === 1)" :key="chapter.id">
  <!-- 章 -->
  <text class="chapter-title">{{ chapter.title }}</text>

  <view v-for="section in chapters.filter(c => c.parentId === chapter.id)" :key="section.id">
    <!-- 节 -->
    <text class="section-title">{{ section.title }}</text>

    <view v-for="sub in chapters.filter(c => c.parentId === section.id)" :key="sub.id">
      <!-- 小节 -->
      <text class="subsection-title">{{ sub.title }}</text>
      <text v-if="sub.content">{{ sub.content }}</text>
    </view>
  </view>
</view>
```

## 核心文件

| 文件 | 说明 |
|------|------|
| `langgraph-generator.ts` | LangGraph 工作流定义，5个节点 |
| `book-generator.store.ts` | 数据库操作，BookStore 类 |
| `book-generator.types.ts` | 类型定义 |
| `book-type-config.ts` | 书籍类型配置 |
| `langgraph-controller.ts` | API 路由 |

## LangGraph 节点

| 节点 | 说明 |
|------|------|
| generateOutlineNode | 生成章大纲 |
| generateFullOutlineNode | 生成节和小节，存入 DB |
| writeChaptersNode | 生成小节 content，存入 DB |
| writeForewordNode | 生成前言 |
| writeAfterwordNode | 生成后记 |

## 大纲输出格式（大纲策略）

### 当前方案：JSON（主力）

- **格式**：`{"chapters":[{"writingInstructions":{...}}]}`
- **模型**：一次性生成全部章节，利用上下文全局视野保证跨章一致性
- **容错**：jsonrepair 自动修复 + 4 层兜底策略 + Markdown 解析(策略5)
- **maxTokens**：大纲专用 48000 tokens（`richOutlineNode` 中配置）
- **跨章避重**：prompt 明确要求 `mustNotRepeat` 注明章号（如"不要重复第3章的XX"）

### 备选方案：Markdown（未来可切换）

与 JSON 方案做过 4 章 / 65 章全量对比测试，Markdown 格式有以下优势：

| 维度 | JSON | Markdown |
|------|------|----------|
| LLM 原生性 | 低（训练语料少） | **高**（母语格式） |
| 格式容错 | 依赖 jsonrepair | **天生容错**（单点故障只影响当前章节） |
| 文笔质感 | 学院派，精准 | **更自然，对话感强** |
| 跨章引用 | 依赖 prompt 约束 | **天然写"不要与第13章混淆"** |
| 人可读性 | 需解析 | **直接阅读** |
| 下游兼容 | ✅ 无需改动 | ❌ 需改造 quality_check / rewrite 等节点 |
| 切换方式 | 默认 | `process.env.OUTLINE_FORMAT='markdown'` |

**切换方法**：
```bash
# 环境变量方式（整个服务生效）
OUTLINE_FORMAT=markdown npm run dev

# 代码方式（单次调用）
process.env.OUTLINE_FORMAT = 'markdown';
```

**切换前提**（暂未实施）：
1. `quality-check.node.ts`：解析 `writingInstructions` 时改用 Markdown 正则
2. `rewrite.node.ts`：同上
3. `content.node.ts`：同上
4. 前端大纲预览组件：支持渲染 Markdown 大纲

**结论**：JSON 当前是最佳选择（零成本适配下游），Markdown 已作为策略5兜底 + 未来切换方案就绪。

## 扩展方向

1. **并行生成**：章→节→小节三层可并行生成
2. **增量更新**：支持单独更新某个小节内容
3. **版本管理**：记录每次生成的历史版本
4. **导出格式**：支持导出 PDF、EPUB、DOCX
