# 笔临 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 中增量修订。*