diff --git a/docs/superpowers/specs/2026-07-07-bilin-p21-p3-design.md b/docs/superpowers/specs/2026-07-07-bilin-p21-p3-design.md new file mode 100644 index 0000000..4fb2026 --- /dev/null +++ b/docs/superpowers/specs/2026-07-07-bilin-p21-p3-design.md @@ -0,0 +1,545 @@ +# 笔临 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`: + +| 区域 | 行为 | +|------|------| +| 会话栏 | `