# 笔临 P2.1 + P3 设计规格 **版本**:1.0 **日期**:2026-07-07 **范围**:P2 遗留补完 + P3 AI 基础(严格 P3-A:对话模式实装,写作模式 Tab 占位) **前置**:P2 已交付(v0.2.0,`aac51bf`) **参考**:`design/readme.md` v1.1 §5.3.8 / §5.6 / §6.1–§6.2 / §6.8–§6.9、`design/ui_pc.html` **目标版本**:v0.3.0 --- ## 1. 背景与目标 ### 1.1 阶段定位 P2.1 补齐 P2 规格中明确延期的交互能力(章节拖拽、搜索替换、灵感语音、`@` 跳转、引用钉住)。P3 在 v0.2.0 信息管理能力之上,交付 **可对话的本地 AI 助手**:会话持久化、上下文注入、快捷命令、离线降级、命名一致性检查,以及 `ai` 类型快照。 ### 1.2 已确认决策(Brainstorming 2026-07-07) | 维度 | 决策 | |------|------| | 范围 | **P2.1 全量** + **P3 严格(选项 A)** | | P3 写作模式 | 「对话」实装;「交互 / 自动 / 向导」Tab **仅 UI 占位** → P4 | | AI 后端 | **LM Studio**(OpenAI 兼容 API),开发/测试/E2E 统一配置 | | 服务地址 | `http://127.0.0.1:1234` | | 模型 | `gemma-4-e4b-it` | | 测试原则 | **禁止 Mock**;不得以「本地服务未启动」为由 skip 测试 | | 架构 | **主进程直连**(`AiClientService` + IPC 流式推送) | | 平台 | Windows 优先(延续 P0–P2) | ### 1.3 验收标准 用户能完成: 1. 左栏章节同卷/跨卷拖拽排序,重启后顺序保持 2. 全局搜索:输入关键词 → 预览替换影响范围 → 确认后批量替换章节内容 3. `Ctrl+Shift+I` 打开灵感捕获模态,文字/语音输入后保存到灵感列表 4. 点击正文中 `@设定名` 跳转到对应设定编辑 5. 引用面板钉住后保持可见,可与 AI 右栏互斥规则共存 6. 设置页配置 LM Studio 地址与模型;右栏 AI 面板新建会话、发送消息、流式收到回复 7. 勾选章节/大纲/设定/灵感作为上下文,点击「更新上下文」后下一轮对话携带最新摘要 8. 使用 `/续写` 等快捷命令触发预设 prompt 9. 云端后端配置下断网时 AI 输入禁用并显示离线横幅;LM Studio 配置下断网仍可用 10. 知识库 Tab 运行命名一致性检查,逐条确认后可批量替换 11. AI 内容写入章节前自动创建 `type='ai'` 快照 ### 1.4 本规格不实现(明确排除) | 项 | 归属 | |----|------| | §6.5 交互 / 自动 / 向导写作全流程 | P4 | | §6.3 文风学习与风格滑杆 | P4 | | §6.4 Skill / MCP 扩展 | P4+ | | §6.6 大纲生成章节骨架 | P4 | | §6.7 知识自动抽取与审核工作流(列表 UI) | P4 | | `knowledge_entries` / `timeline_events` 表 | P4/P5/P6 | | 导入导出(`#importModal`) | P7 | | 驾驶舱、章间衔接、更新计划 | P5 | --- ## 2. 架构 ### 2.1 进程模型(延续 P2) ``` ┌─────────────────────────────────────────────────────────┐ │ 主进程 │ │ · node:sqlite(每书 .sqlite,schema v3) │ │ · AiClientService → fetch LM Studio /v1/chat/completions│ │ · AiSessionRepository / AiContextBuilder │ │ · NetworkMonitor → 推送 online/offline │ │ · 既有 FTS / Search / Snapshot / Chapter 服务 │ │ · IPC Handlers(IpcResult 信封 + ai:stream-chunk 事件) │ └───────────────────────────┬─────────────────────────────┘ │ contextBridge ┌───────────────────────────▼─────────────────────────────┐ │ 渲染进程 │ │ · AiPanel(替换 RightPanel AI 占位) │ │ · ContextEditorModal / NamingCheckModal │ │ · InspirationModal(全局快捷键) │ │ · useAiStore(会话、流式缓冲、disabled) │ │ · SearchModal 替换区 / 章节 drag-and-drop │ └─────────────────────────────────────────────────────────┘ ``` **约束**(延续 P0–P2): - 禁止渲染进程直接 `fetch` AI API;所有推理请求经主进程 - API Key 仅存 `global_settings.json`(主进程读写) - 流式输出通过 `webContents.send(IPC.AI_STREAM_CHUNK, payload)` 推送,不阻塞 IPC invoke ### 2.2 AI 客户端(推荐方案) 主进程 `AiClientService`: - 使用 Node.js 内置 `fetch` 调用 OpenAI Chat Completions 兼容端点 - 支持 `stream: true`;解析 SSE `data: {...}` 行 - `AbortController` 支持 `ai:abort` - 超时默认 60s(`GlobalSettings.aiConfig.timeoutMs`) - 错误映射为 `IpcError`,含 `recoverable: true`(超时/连接拒绝) **默认配置**(`global_settings.json`): ```typescript interface AiConfig { backend: 'lmstudio' | 'openai' | 'anthropic' baseUrl: string // 默认 'http://127.0.0.1:1234' model: string // 默认 'gemma-4-e4b-it' apiKey?: string // openai/anthropic 时使用;lmstudio 可空 timeoutMs: number // 默认 60_000 maxTokens: number // 默认 2048 } ``` **环境变量覆盖**(测试/CI,可选): | 变量 | 作用 | |------|------| | `BILIN_AI_BASE_URL` | 覆盖 `baseUrl` | | `BILIN_AI_MODEL` | 覆盖 `model` | 未设置时使用上述默认值;**测试不得 skip**。 ### 2.3 里程碑 | 阶段 | 交付 | 周期 | |------|------|------| | **P2.1a** | 章节拖拽 + `chapter:move` IPC | ~2 天 | | **P2.1b** | 搜索替换 UI + E2E | ~1 天 | | **P2.1c** | 灵感模态 + 语音 + `@` 跳转 + 引用钉住 | ~2 天 | | **P3.0** | Schema v3 + AiClientService + AI 设置页 | ~2 天 | | **P3.1** | 会话 CRUD + 消息持久化 + 流式 IPC | ~3 天 | | **P3.2** | 上下文编辑器 + 快捷命令 | ~2 天 | | **P3.3** | 离线降级 + 命名检查 + AI 快照 | ~2 天 | | **P3.4** | 全量测试 + v0.3.0 | ~1 天 | --- ## 3. 数据模型 ### 3.1 Schema v3 迁移 `schema_version` 2 → 3。增量 `schema-v3.sql` + `migrate.ts` 扩展。 ```sql CREATE TABLE ai_sessions ( id TEXT PRIMARY KEY, title TEXT NOT NULL DEFAULT '', model TEXT NOT NULL DEFAULT '', archived INTEGER NOT NULL DEFAULT 0, context_json TEXT NOT NULL DEFAULT '{}', created_at TEXT NOT NULL DEFAULT (datetime('now')), updated_at TEXT NOT NULL DEFAULT (datetime('now')) ); CREATE TABLE ai_messages ( id TEXT PRIMARY KEY, session_id TEXT NOT NULL, role TEXT NOT NULL CHECK (role IN ('user', 'assistant', 'system')), content TEXT NOT NULL DEFAULT '', created_at TEXT NOT NULL DEFAULT (datetime('now')), FOREIGN KEY (session_id) REFERENCES ai_sessions(id) ON DELETE CASCADE ); CREATE INDEX idx_ai_messages_session ON ai_messages(session_id, created_at); ``` **说明**:`ai_sessions` 存于**每本书**的 `.sqlite`(与章节等同库),非全局。 **`context_json` 结构**: ```typescript interface AiContextBinding { chapterIds: string[] outlineIds: string[] settingIds: string[] inspirationIds: string[] } ``` **全局 userData**(`global_settings.json` 扩展,非书籍 DB): ```typescript interface GlobalSettings { // ...existing P2 fields aiConfig: AiConfig namingIgnoreWords: string[] } ``` ### 3.2 共享类型扩展(`src/shared/types.ts`) ```typescript export type AiBackend = 'lmstudio' | 'openai' | 'anthropic' export type AiMessageRole = 'user' | 'assistant' | 'system' export type AiWritingMode = 'chat' | 'interactive' | 'auto' | 'wizard' // UI only; P3 仅 chat 实装 export interface AiSession { id: string title: string model: string archived: boolean context: AiContextBinding createdAt: string updatedAt: string } export interface AiMessage { id: string sessionId: string role: AiMessageRole content: string createdAt: string } export interface NamingConflict { termA: string termB: string confidence: number reason: string suggestedCanonical?: string } ``` --- ## 4. IPC 通道 ### 4.1 新增常量(`ipc-channels.ts`) ```typescript // Chapter CHAPTER_MOVE: 'chapter:move', // AI AI_SESSION_LIST: 'ai:sessionList', AI_SESSION_CREATE: 'ai:sessionCreate', AI_SESSION_UPDATE: 'ai:sessionUpdate', AI_SESSION_DELETE: 'ai:sessionDelete', AI_MESSAGE_LIST: 'ai:messageList', AI_CHAT: 'ai:chat', AI_ABORT: 'ai:abort', AI_BUILD_CONTEXT: 'ai:buildContext', AI_NAMING_CHECK: 'ai:namingCheck', AI_STREAM_CHUNK: 'ai:streamChunk', // main → renderer event AI_NETWORK_STATUS: 'ai:networkStatus', // main → renderer event ``` ### 4.2 Handler 契约 | 通道 | 参数 | 返回 | |------|------|------| | `chapter:move` | `{ bookId, chapterId, volumeId, sortOrder }` | `Chapter` | | `ai:sessionList` | `{ bookId, includeArchived? }` | `AiSession[]` | | `ai:sessionCreate` | `{ bookId, title? }` | `AiSession` | | `ai:sessionUpdate` | `{ bookId, sessionId, patch }` | `AiSession` | | `ai:sessionDelete` | `{ bookId, sessionId }` | `void` | | `ai:messageList` | `{ bookId, sessionId }` | `AiMessage[]` | | `ai:chat` | `{ bookId, sessionId, content, slashCommand? }` | `{ messageId }` | | `ai:abort` | `{ messageId }` | `void` | | `ai:buildContext` | `{ bookId, binding }` | `{ preview: ContextPreviewItem[] }` | | `ai:namingCheck` | `{ bookId }` | `NamingConflict[]` | **`ai:chat` 流程**: 1. 持久化 user message 2. `AiContextBuilder` 组装 messages 数组(system + 历史 + user) 3. 调用 `AiClientService.chatStream(...)` 4. 流式推送 `ai:streamChunk`;完成后持久化 assistant message --- ## 5. P2.1 功能规格 ### 5.1 章节拖拽排序 **UI**:左栏章节列表项 `draggable`;拖拽指示线(同卷内)。 **逻辑**: - 同卷:重算 `sort_order`(0..n-1),批量 `chapter:move` 或单次 `chapter.repo.move` - 跨卷:更新 `volume_id` + 目标卷末尾 `sort_order` - 拖拽结束触发 `flushEditorSave()`(若当前章节被移动) **Repository**: ```typescript move(chapterId: string, volumeId: string, sortOrder: number): Chapter ``` ### 5.2 全局搜索替换 对齐 `design/readme.md` §5.6.1 底部替换区。 **UI**(`SearchModal` 扩展): - 折叠区「替换」:替换文本输入 + 「预览变更」+ 「执行替换」 - 预览调用 `search:replace(dryRun: true)` → 列表展示受影响条目 - 确认后 `search:replace(dryRun: false)` → toast 显示替换数量 - 替换后刷新 `useBookStore` 章节列表;若当前章被替换则刷新编辑器 **范围**:P2 已支持全书;本阶段补 UI,不扩展 scope select(可后续加)。 ### 5.3 灵感快速捕获 **UI**:`#inspirationModal`(对齐 `ui_pc.html`) - 标题、内容、标签(逗号分隔) - 语音按钮:`webkitSpeechRecognition`(`zh-CN`),实时填入内容框 - 保存 → `inspiration:create` + 可选 `switchTarget` - 不打断编辑器:关闭后焦点回 `.ProseMirror` **快捷键**:`Ctrl+Shift+I`(`captureInspiration` 已有 handler,改为打开模态而非直接 create) ### 5.4 `@提及` 反向跳转 `SettingMention` Mark 增加 `onClick` / `editorProps` 处理: - 点击带 `data-setting-mention` 的 span → `switchTarget({ kind: 'setting', id })` - 光标样式 `pointer` ### 5.5 引用面板钉住 `useReferenceStore` 扩展: ```typescript pinned: boolean setPinned(open: boolean): void ``` - 钉住按钮(📌)在 `ref-header` - `pinned === true` 时 `setOpen(false)` 不隐藏面板 - 与 AI 右栏互斥:引用打开时仍隐藏 RightPanel(延续 P2);钉住引用 + 需 AI 时用户手动关闭引用 --- ## 6. P3 功能规格 ### 6.1 AI 面板 UI(`AiPanel.tsx`) 对齐 `ui_pc.html` `#ai-chat`: | 区域 | 行为 | |------|------| | 会话栏 | `