Files
bilin/docs/superpowers/specs/2026-07-08-bilin-p4-interactive-design.md
T
bing 5cfc7c10b9 docs: add P4 interactive writing design spec for v0.4.0
Define state machine, schema v4, IPC, prompts, UI, and LM Studio test timeouts for interactive writing mode only.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-06 16:48:26 +08:00

410 lines
16 KiB
Markdown
Raw 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.
# 笔临 P4 交互式写作设计规格
**版本**1.0
**日期**2026-07-08
**范围**:P4 严格(选项 A)— 仅实装「交互式写作」全流程(§6.5.1)
**前置**P2.1 + P3 已交付(v0.3.0`8ce35a1`
**参考**`design/readme.md` v1.1 §6.5.1、`design/ui_pc.html` AI 面板交互写作原型
**目标版本**v0.4.0
---
## 1. 背景与目标
### 1.1 阶段定位
v0.3.0 已交付 AI 对话模式;「交互 / 自动 / 向导」写作 Tab 仍为占位。本阶段实装 **交互式写作**:以场景为单位的剧情走向建议 → 场景生成 → 命名暂停 → 微调确认 → 循环 → 成章落盘,全流程状态持久化并可恢复。
### 1.2 已确认决策(Brainstorming 2026-07-08
| 维度 | 决策 |
|------|------|
| 范围 | **P4 严格**:仅 §6.5.1 交互式写作;自动 / 向导仍占位 |
| 实现方案 | **方案 1**:专用状态机 + 独立 DB 表 + 逐步 IPC |
| 成章落盘 | 当前活跃卷 **新建章节**`origin=interactive`,合并全部场景后自动打开编辑器 |
| 命名暂停 | **完整实装**:生成中暂停,5 选项 + 自定义输入,选定后续写 |
| 状态持久化 | **schema v4**`interactive_flows` / `interactive_scenes`,重启可恢复 |
| 启动门槛 | **硬拦截**:无章节正文且无大纲/设定时 Tab 禁用 |
| AI 后端 | **LM Studio**(延续 P3),`http://127.0.0.1:1234``gemma-4-e4b-it` |
| 测试原则 | **禁止 Mock**;集成 / E2E 打真实 LM Studio |
| 测试超时 | 本地模型慢,Vitest AI 用例 **180240s**Playwright 全局 **180s**,交互 E2E **300s** |
| 工程 | `test-results/` 加入 `.gitignore` |
### 1.3 验收标准
用户能完成:
1. 在有足够上下文的书籍中进入「交互写作」Tab(门槛不满足时 Tab 禁用并提示)
2. 确认 / 编辑 AI 上下文后,收到 3 条剧情走向建议(每条 ≥50 字说明)
3. 选择走向(可附补充指令)后,流式生成一个场景(600–1500 字)
4. 生成中若 AI 要求命名,流程暂停并展示 5 个命名选项;选定后自动续写完成场景
5. 对场景输入微调指令,AI 针对当前场景修订(不重写全章)
6. 满意后进入下一场景循环,或点击「结束本章」
7. 结束时在当前活跃卷创建新章节,内容为全部场景合并 HTML,标记为交互初稿,编辑器自动打开
8. 关闭应用并重启后,未完成的交互流程恢复到上次步骤
9. 切换回「对话」模式时,同会话聊天历史仍可见;再次进入交互模式时流程按规格重新初始化
### 1.4 本规格不实现(明确排除)
| 项 | 归属 |
|----|------|
| §6.5.2 自动写作 | P4.1+ |
| §6.5.3 向导式写作 | P4.1+ |
| §6.3 文风学习与风格滑杆 | P4.1+ |
| §6.6 大纲生成章节骨架 | P4.1+ |
| §6.7 知识抽取与审核工作流 | P4.1+ |
| §6.4 Skill / MCP | P4+ |
| 命名结果自动写入设定库 | 本阶段仅写入正文 |
| 导入导出、驾驶舱等 | P5+ |
---
## 2. 架构
### 2.1 进程模型
```
┌──────────────────────────────────────────────────────────────┐
│ 主进程 │
│ · schema v4interactive_flows / interactive_scenes │
│ · chapters.origin 列(manual | interactive
│ · InteractiveWritingService(步骤机 + 持久化) │
│ · InteractivePromptBuilder(各步 prompt / JSON 约束) │
│ · AiClientService(复用:JSON 步非流式,场景步流式 SSE) │
│ · AiContextBuilder(复用) │
│ · IPCinteractive:* + interactive:streamChunk 事件 │
└────────────────────────────┬─────────────────────────────────┘
│ contextBridge
┌────────────────────────────▼─────────────────────────────────┐
│ 渲染进程 │
│ · AiPanelwritingMode=interactive 时挂载 InteractivePanel │
│ · InteractivePanel:步骤 UI(走向卡片、命名暂停、场景预览) │
│ · useInteractiveStoreIPC + 流式缓冲 │
│ · ContextEditorModal(复用,上下文确认步) │
└──────────────────────────────────────────────────────────────┘
```
**约束**(延续 P0P3):
- 渲染进程禁止直接 `fetch` AI API
- 流式通过 `webContents.send` 推送,不阻塞 IPC invoke
- 交互流程绑定 `ai_session_id``ai_messages` 仍记录关键轮次作审计
### 2.2 步骤机
| Step | 名称 | AI | 持久化字段 |
|------|------|-----|-----------|
| `context_confirm` | 上下文确认 | 无 | `context_json` |
| `plot_suggest` | 剧情走向 | JSON ×3 | — |
| `plot_select` | 用户选择 | 无 | `plot_selection_json` |
| `scene_generate` | 场景生成 | 流式 | 可能写入 `naming_pending_json` |
| `naming_pause` | 命名暂停 | 流式续写 | `naming_pending_json` 清空后 |
| `scene_review` | 场景确认/微调 | 流式(可选) | — |
| `loop` | 下一场景 | 回到 `plot_suggest` | `interactive_scenes` 追加 |
| `completed` | 成章完成 | 无 | `status=completed` |
切换到交互模式:调用 `interactive:start`,创建或重置 `flow``status=in_progress`)。切换离开交互模式不删除 flow;再次 `start` 时重新初始化。
### 2.3 Schema v4(每本书 `.sqlite`
```sql
-- schema-v4.sql
ALTER TABLE chapters ADD COLUMN origin TEXT NOT NULL DEFAULT 'manual';
CREATE TABLE IF NOT EXISTS interactive_flows (
id TEXT PRIMARY KEY,
session_id TEXT NOT NULL,
step TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'in_progress',
context_json TEXT NOT NULL DEFAULT '{}',
plot_selection_json TEXT,
naming_pending_json TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
FOREIGN KEY (session_id) REFERENCES ai_sessions(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS interactive_scenes (
id TEXT PRIMARY KEY,
flow_id TEXT NOT NULL,
sort_order INTEGER NOT NULL DEFAULT 0,
content_html TEXT NOT NULL DEFAULT '',
plot_label TEXT NOT NULL DEFAULT '',
refined_count INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
FOREIGN KEY (flow_id) REFERENCES interactive_flows(id) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_interactive_flows_session ON interactive_flows(session_id);
CREATE INDEX IF NOT EXISTS idx_interactive_scenes_flow ON interactive_scenes(flow_id, sort_order);
```
`migrate.ts``version 3 → 4` 应用上述 DDL。
---
## 3. 共享类型
```typescript
export type InteractiveStep =
| 'context_confirm'
| 'plot_suggest'
| 'plot_select'
| 'scene_generate'
| 'naming_pause'
| 'scene_review'
| 'completed'
export type InteractiveFlowStatus = 'in_progress' | 'completed' | 'abandoned'
export type ChapterOrigin = 'manual' | 'interactive'
export interface PlotOption {
id: 'A' | 'B' | 'C'
label: string
summary: string
}
export interface NamingPause {
elementType: string
context: string
options: string[] // 长度 5
}
export interface PlotSelection {
optionIds: Array<'A' | 'B' | 'C'>
userNote?: string
}
export interface InteractiveScene {
id: string
sortOrder: number
contentHtml: string
plotLabel: string
refinedCount: number
}
export interface InteractiveFlow {
id: string
sessionId: string
step: InteractiveStep
status: InteractiveFlowStatus
scenes: InteractiveScene[]
namingPending?: NamingPause | null
contextSummary?: string
}
export interface InteractiveGateResult {
eligible: boolean
reason?: 'no_chapter' | 'no_outline_or_setting'
}
export interface InteractiveStreamChunkEvent {
flowId: string
phase: 'scene' | 'refine' | 'naming_resume'
delta: string
done: boolean
}
```
`Chapter` 接口增加可选 `origin?: ChapterOrigin`(读取时填充,默认 `manual`)。
---
## 4. IPC 接口
### 4.1 通道列表
| 通道 | 说明 |
|------|------|
| `interactive:checkGate` | 门槛检查 |
| `interactive:getFlow` | 按 session 恢复 flow |
| `interactive:start` | 初始化 / 重置流程 |
| `interactive:confirmContext` | 保存上下文 binding,进入 plot_suggest |
| `interactive:suggestPlots` | AI 返回 3 条走向 |
| `interactive:selectPlot` | 记录用户选择 |
| `interactive:generateScene` | 开始场景流式生成 |
| `interactive:resolveNaming` | 命名选择后续写 |
| `interactive:refineScene` | 场景微调(流式) |
| `interactive:acceptScene` | 确认场景,写入 scenes 表 |
| `interactive:finishChapter` | 合并场景 → 新建章节 |
| `interactive:abort` | 中止当前 AI 请求 |
事件:`interactive:streamChunk``InteractiveStreamChunkEvent`
### 4.2 门槛逻辑(`checkGate`
`eligible = true` 当满足任一:
- 存在至少 1 章,`stripHtml(content).trim().length > 0`
- `outline_items` 表非空
- `settings` 表非空
否则 `eligible = false``reason` 区分无章节正文 vs 无大纲设定。
### 4.3 成章逻辑(`finishChapter`
1. 校验 `activeVolumeId` 存在(来自调用方或书籍默认第一卷)
2.`sort_order` 合并 `interactive_scenes.content_html`,段落间插入空行
3. `chapter.create(volumeId, title ?? '交互初稿')``origin='interactive'``status='draft'`
4. `flow.status = 'completed'``step = 'completed'`
5. 创建 `type='ai'` 快照(标签「交互写作成章」)
6. 返回 `{ chapterId }`
---
## 5. Prompt 策略
`InteractivePromptBuilder` 为各步构建 `ChatMessage[]`
### 5.1 走向建议(`suggestPlots`
- System:你是小说创作助手;基于上下文给出 3 个互斥剧情走向;仅返回 JSON
- 格式:`{ "plots": [{ "id": "A", "label": "...", "summary": "..." }] }`
- 约束:恰好 3 条;`summary` ≥50 汉字
- 非流式;解析失败重试 1 次(追加「仅返回 JSON,不要 markdown」)
### 5.2 场景生成(`generateScene`
- 注入:上下文块 + 已确认场景摘要 + 选定走向 + 用户补充
- 若需新命名:先返回单行 JSON
`{"type":"naming_required","elementType":"角色","context":"...","options":["名1",...,"名5"]}`
- 否则流式输出场景 HTML`<p>` 段落,6001500 字)
- 交互步 `max_tokens` 覆盖为 **4096**(不改全局 `aiConfig.maxTokens`
### 5.3 命名续写(`resolveNaming`
- User:用户选定名称 `{chosen}`,请继续完成当前场景
- 流式输出剩余正文
### 5.4 场景微调(`refineScene`
- User:在保持情节连贯前提下,按指令修改当前场景:`{instruction}`
- 流式输出完整修订后场景(覆盖当前草稿)
---
## 6. 错误处理
| 场景 | 行为 |
|------|------|
| AI 超时 | `IpcError` recoverabletoast「AI 响应超时」;留在当前步,可重试 |
| JSON 解析失败 | 自动重试 1 次;仍失败则 toast + 留在 `plot_suggest` |
| 流式中断 | 保留已收 delta;提供「重新生成」 |
| 无活跃卷 | `finishChapter` 返回错误码,提示创建卷 |
| 切换书籍 | DB 状态保留;`getFlow` 恢复 |
| 云端后端 + 离线 | 与 P3 一致禁用输入;LM Studio 不受影响 |
| `interactive:abort` | 中止 fetch;不清除 flow 状态 |
---
## 7. UI 规格
### 7.1 入口
- `AiPanel` 模式 Tab`interactive` 可点击;`auto` / `wizard``feature.comingSoon`
- 挂载时 `checkGate``!eligible` → Tab `disabled` + `title` 提示
- `writingMode === 'interactive'` 时主区渲染 `InteractivePanel`
### 7.2 InteractivePanel 布局
1. **步骤指示器** — 上下文 → 走向 → 场景 → 确认
2. **主内容**(按 `flow.step`
- `context_confirm`:摘要 +「编辑上下文」+「开始交互」
- `plot_suggest` / `plot_select``PlotOptionCard` ×3,多选 + 补充输入
- `scene_generate` / `naming_pause`:流式预览;命名时覆盖为选项列表 + 自定义
- `scene_review`:全文预览 + 微调输入 +「下一场景」/「结束本章」
3. **底栏** — 主操作按钮;与 `ai-offline-banner` 联动
样式参考 `design/ui_pc.html``.ai-mode-tab``.plot-opt``.opt-label`
### 7.3 成章后导航
`finishChapter` 成功 → `openBook``setSelectedChapter(chapterId)``setView('editor')` → toast `interactive.chapterCreated`
---
## 8. 工程配套
### 8.1 `.gitignore`
```
test-results/
```
### 8.2 测试超时配置
| 位置 | 值 |
|------|-----|
| `playwright.config.ts` `timeout` | `180_000` |
| E2E 交互用例 `test.setTimeout` | `300_000` |
| E2E 助手回复 `expect` | `120_000` |
| Vitest `IT-INT-01/02/03` | `180_000` / `240_000` / `240_000` |
| 现有 `IT-AI-01` | 保持 `90_000` 或升至 `180_000` |
| 现有 `IT-NAME-01` | 保持 `180_000` |
### 8.3 LM Studio 前置
与 P3 相同:`BILIN_AI_BASE_URL` / `BILIN_AI_MODEL` 环境变量由主进程 `global-settings` 读取;渲染进程不使用 `process.env`
---
## 9. 测试要求
### 9.1 单元 / 集成(Vitest,禁止 Mock
| 用例 ID | 场景 | 超时 |
|---------|------|------|
| IT-INT-01 | `suggestPlots` 返回 3 条合法 JSON | 180s |
| IT-INT-02 | `generateScene` 流式正文非空 | 240s |
| IT-INT-03 | naming JSON → `resolveNaming` 后续有内容 | 240s |
| IT-INT-04 | `acceptScene` ×2 + `finishChapter` 创建 `origin=interactive` 章节 | 60s |
| IT-INT-05 | `checkGate` 门槛 true/false 分支 | 5s |
| IT-MIG-04 | schema v3→v4 迁移 | 5s |
### 9.2 E2EPlaywright + Electron
| 用例 ID | 场景 | 超时 |
|---------|------|------|
| E2E-INT-01 | 选走向 → 生成 → 接受 → 结束本章 → 编辑器有内容 | 300s |
| E2E-INT-02 | 空书交互 Tab 禁用 | 30s |
| E2E-INT-03 | 重启后恢复 flow 步骤 | 300s |
文件:`e2e/interactive-writing.spec.ts`
---
## 10. 文件清单(实现参考)
| 文件 | 职责 |
|------|------|
| `src/main/db/schema-v4.sql` | v4 DDL |
| `src/main/db/migrate.ts` | v3→v4 |
| `src/main/db/repositories/interactive.repo.ts` | flow / scene CRUD |
| `src/main/services/interactive-writing.service.ts` | 步骤机 |
| `src/main/services/interactive-prompt-builder.service.ts` | Prompt |
| `src/main/ipc/handlers/interactive.handler.ts` | IPC |
| `src/shared/ipc-channels.ts` | 新通道 |
| `src/shared/types.ts` | 交互类型 + `ChapterOrigin` |
| `src/preload/index.ts` | API + 事件 |
| `src/renderer/components/ai/InteractivePanel.tsx` | 主 UI |
| `src/renderer/components/ai/PlotOptionCard.tsx` | 走向卡片 |
| `src/renderer/stores/useInteractiveStore.ts` | 状态 |
| `e2e/interactive-writing.spec.ts` | E2E |
| `tests/main/interactive-*.test.ts` | 集成测试 |
---
## 11. 规格自检
| 检查项 | 结果 |
|--------|------|
| Placeholder 扫描 | 自动/向导明确排除至 P4.1+;无 TBD |
| 内部一致性 | 步骤机、IPC、DB、UI 步骤名称一致 |
| 范围 | 单阶段可交付 v0.4.0 |
| 歧义 | 成章=新建章节+interactive origin;门槛=硬拦截 |
---
*本规格为笔临 P4 子项目。自动写作、知识抽取等各自在后续 spec 中增量修订。*