diff --git a/docs/superpowers/specs/2026-07-08-bilin-p4-interactive-design.md b/docs/superpowers/specs/2026-07-08-bilin-p4-interactive-design.md new file mode 100644 index 0000000..65f1e5e --- /dev/null +++ b/docs/superpowers/specs/2026-07-08-bilin-p4-interactive-design.md @@ -0,0 +1,409 @@ +# 笔临 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 用例 **180–240s**,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 v4:interactive_flows / interactive_scenes │ +│ · chapters.origin 列(manual | interactive) │ +│ · InteractiveWritingService(步骤机 + 持久化) │ +│ · InteractivePromptBuilder(各步 prompt / JSON 约束) │ +│ · AiClientService(复用:JSON 步非流式,场景步流式 SSE) │ +│ · AiContextBuilder(复用) │ +│ · IPC:interactive:* + interactive:streamChunk 事件 │ +└────────────────────────────┬─────────────────────────────────┘ + │ contextBridge +┌────────────────────────────▼─────────────────────────────────┐ +│ 渲染进程 │ +│ · AiPanel:writingMode=interactive 时挂载 InteractivePanel │ +│ · InteractivePanel:步骤 UI(走向卡片、命名暂停、场景预览) │ +│ · useInteractiveStore:IPC + 流式缓冲 │ +│ · ContextEditorModal(复用,上下文确认步) │ +└──────────────────────────────────────────────────────────────┘ +``` + +**约束**(延续 P0–P3): + +- 渲染进程禁止直接 `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(`

` 段落,600–1500 字) +- 交互步 `max_tokens` 覆盖为 **4096**(不改全局 `aiConfig.maxTokens`) + +### 5.3 命名续写(`resolveNaming`) + +- User:用户选定名称 `{chosen}`,请继续完成当前场景 +- 流式输出剩余正文 + +### 5.4 场景微调(`refineScene`) + +- User:在保持情节连贯前提下,按指令修改当前场景:`{instruction}` +- 流式输出完整修订后场景(覆盖当前草稿) + +--- + +## 6. 错误处理 + +| 场景 | 行为 | +|------|------| +| AI 超时 | `IpcError` recoverable;toast「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 E2E(Playwright + 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 中增量修订。*