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>
16 KiB
笔临 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 验收标准
用户能完成:
- 在有足够上下文的书籍中进入「交互写作」Tab(门槛不满足时 Tab 禁用并提示)
- 确认 / 编辑 AI 上下文后,收到 3 条剧情走向建议(每条 ≥50 字说明)
- 选择走向(可附补充指令)后,流式生成一个场景(600–1500 字)
- 生成中若 AI 要求命名,流程暂停并展示 5 个命名选项;选定后自动续写完成场景
- 对场景输入微调指令,AI 针对当前场景修订(不重写全章)
- 满意后进入下一场景循环,或点击「结束本章」
- 结束时在当前活跃卷创建新章节,内容为全部场景合并 HTML,标记为交互初稿,编辑器自动打开
- 关闭应用并重启后,未完成的交互流程恢复到上次步骤
- 切换回「对话」模式时,同会话聊天历史仍可见;再次进入交互模式时流程按规格重新初始化
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):
- 渲染进程禁止直接
fetchAI 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)
-- 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. 共享类型
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)
- 校验
activeVolumeId存在(来自调用方或书籍默认第一卷) - 按
sort_order合并interactive_scenes.content_html,段落间插入空行 chapter.create(volumeId, title ?? '交互初稿'),origin='interactive',status='draft'flow.status = 'completed',step = 'completed'- 创建
type='ai'快照(标签「交互写作成章」) - 返回
{ 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>段落,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→ Tabdisabled+title提示 writingMode === 'interactive'时主区渲染InteractivePanel
7.2 InteractivePanel 布局
- 步骤指示器 — 上下文 → 走向 → 场景 → 确认
- 主内容(按
flow.step)context_confirm:摘要 +「编辑上下文」+「开始交互」plot_suggest/plot_select:PlotOptionCard×3,多选 + 补充输入scene_generate/naming_pause:流式预览;命名时覆盖为选项列表 + 自定义scene_review:全文预览 + 微调输入 +「下一场景」/「结束本章」
- 底栏 — 主操作按钮;与
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 中增量修订。