# 笔临 P5.2 写作日志 + AI 知识抽取 设计规格 **版本**:1.0 **日期**:2026-07-07 **范围**:P5.2 — `writing_logs` 日更追踪 + 驾驶舱热力图/连更 + AI 章节知识自动抽取与审核增强 **前置**:P6 AI 知识上下文已交付(v0.7.0,`aa2c7df`) **参考**:`design/readme.md` v1.1 §6.7 / §8 / §9、`docs/superpowers/specs/2026-07-06-bilin-p5-workflow-knowledge-design.md`、`docs/superpowers/specs/2026-07-07-bilin-p6-ai-context-design.md` **目标版本**:v0.8.0 --- ## 1. 背景与目标 ### 1.1 阶段定位 v0.7.0 已交付知识半自动推荐与四种 AI 模式注入,但知识库条目仍依赖**手动录入**;驾驶舱缺少日更写作数据(`writing_logs`、热力图、连更),`dailyWordGoal` 仅有设置项未驱动 UI。本阶段(P5.2)补齐 **写作数据闭环** 与 **AI 知识抽取上游**,形成「成章/保存 → 抽取 → 审核 → P6 注入」完整链路。 ### 1.2 已确认决策(Brainstorming 2026-07-07) | 维度 | 决策 | |------|------| | 交付范围 | **核心包**:`writing_logs` + 热力图 + 连更 + AI 抽取;**不含**番茄钟 / 里程碑徽章 / 预计完本 | | 抽取触发 | **成章自动**(默认开)+ 手动「抽取本章」+ 全局 `knowledgeAutoExtract` 开关 | | 统计范围 | **当前书籍**:热力图、连更、`dailyWordGoal` 达标均按本书当日净增 | | 日更累计 | **保存差分 + 成章补计**:`CHAPTER_UPDATE` 记录净增;`finishChapter` 补计未入账字数 | | 重复处理 | **AI 合并建议 + 预填更新**:`suggestedMergeId` + `proposedPatch`;确认后 patch 已有条目或另存为新条目 | | 架构 | **方案 2**:全局 `writing_logs` + 每书 `chapter_writing_snapshots` + 独立 `KnowledgeExtractionService` | | schema | **v7** 迁移(每书);全局 `writing_logs.sqlite`(userData) | | AI 测试 | 集成/E2E 含抽取时禁止 Mock,走 LM Studio | ### 1.3 验收标准 用户能完成: 1. 编辑章节并保存后,驾驶舱**今日字数**与 **365 天热力图**更新;`dailyWordGoal > 0` 时状态栏进度条反映本书当日进度(绿≥100%,橙≥80%) 2. 连更天数在驾驶舱展示(仅当 `dailyWordGoal > 0`);中断后归零 3. 交互/自动/向导 **成章落盘**后,若 `knowledgeAutoExtract` 开启,知识库「待审核」出现本批 AI 抽取项(含置信度) 4. 在知识库或章节操作区点击 **「抽取本章」** 手动触发;设置中可关闭自动抽取 5. 待审核项含 **合并建议** 时,打开预填编辑器;确认后更新已有 `approved` 条目,或选择「作为新条目」 6. 「全部采纳高置信度」批量通过 ≥ 阈值(默认 0.8)的纯新建条目;含合并建议的条目不自动批量采纳 7. 采纳后的条目可被 P6 `suggestContext` 推荐并注入 AI 上下文 ### 1.4 本规格不实现(明确排除) | 项 | 归属 | |----|------| | 番茄钟、7/30/100 天里程碑徽章、达标推送通知 | §8 延后 | | 预计完本、写作速度仪表盘、时段热力图 | §9 延后 | | 角色弧线简报、Cytoscape 关系图谱 | P6+ / §20.3 | | 语义 embedding 检索 | 后续 | | 注入历史 / 反向链接 | P6.1 | | 导入导出、平台投稿预设 | P7 | | 关系「建议」自动写入 `settings` 表 | P6+(本阶段仅 `relationship` 类型知识条目) | --- ## 2. 架构 ### 2.1 进程模型 ``` ┌──────────────────────────────────────────────────────────────────┐ │ 主进程 │ │ · WritingLogService(userData/writing_logs.sqlite) │ │ · ChapterWritingTracker(差分 + 成章补计) │ │ · KnowledgeExtractionService(AiClient + 解析 JSON) │ │ · KnowledgeService 扩展(合并 patch、批量高置信度采纳) │ │ · CockpitService 扩展(热力图、连更、今日进度) │ │ · chapter.handler / interactive|auto finishChapter 挂钩 │ └────────────────────────────┬─────────────────────────────────────┘ │ IPC ┌────────────────────────────▼─────────────────────────────────────┐ │ 渲染进程 │ │ · CockpitModal 热力图 + 连更 + 今日进度 │ │ · EditorLayout 状态栏日更进度条 │ │ · KnowledgePanel 抽取批次、合并建议 UI、批量高置信度 │ │ · SettingsPage knowledgeAutoExtract / 置信度阈值 │ │ · 章节列表/编辑器「抽取本章」入口 │ └──────────────────────────────────────────────────────────────────┘ ``` ### 2.2 模块边界 | 模块 | 职责 | 依赖 | |------|------|------| | `WritingLogService` | 按 `(date, bookId)` 读写/累加日字数 | 全局 sqlite | | `ChapterWritingTracker` | 维护 `chapter_writing_snapshots`,计算 delta | 每书 DB、`WritingLogService` | | `KnowledgeExtractionService` | 构建 prompt、调用 AI、解析、落库 pending | `AiClientService`, `KnowledgeRepository` | | `KnowledgeService` | 合并 patch、批量采纳、拒绝 | 现有 CRUD | | `CockpitService` | 扩展 `WritingStats` 聚合 | `WritingLogService`, `GlobalSettings` | **原则**:抽取异步不阻塞成章;抽取失败 toast + 可重试;写作追踪同步、轻量。 --- ## 3. 数据模型 ### 3.1 全局 userData:`writing_logs.sqlite` ```sql CREATE TABLE writing_logs ( date TEXT NOT NULL, -- YYYY-MM-DD,本地时区 book_id TEXT NOT NULL, word_count INTEGER NOT NULL DEFAULT 0, PRIMARY KEY (date, book_id) ); ``` 由 `WritingLogService` 在 `userDataDir` 管理,与 `books_registry.json` 同级。 ### 3.2 每书 schema v7 新文件 `schema-v7.sql`: ```sql CREATE TABLE chapter_writing_snapshots ( chapter_id TEXT PRIMARY KEY, logged_word_count INTEGER NOT NULL DEFAULT 0, finish_supplemented INTEGER NOT NULL DEFAULT 0, FOREIGN KEY (chapter_id) REFERENCES chapters(id) ON DELETE CASCADE ); -- knowledge_entries 扩展列(迁移时 ALTER 或重建后拷贝) -- extraction_confidence REAL -- merge_target_id TEXT -- 建议合并的 approved 条目 id -- extraction_batch_id TEXT -- 同次抽取 UUID,便于 UI 分组 ``` `migrate.ts`:`CURRENT_VERSION = 7`;v6→v7 对已有 `knowledge_entries` 执行 `ALTER TABLE ADD COLUMN`(三列均可 NULL)。 ### 3.3 类型扩展(`src/shared/types.ts`) ```typescript export interface WritingDayLog { date: string bookId: string wordCount: number } export interface WritingStats { todayWords: number dailyGoal: number goalProgress: number // 0–1+,goal=0 时为 0 streakDays: number // goal=0 时为 0 heatmap: WritingDayLog[] // 最近 365 天,含 wordCount=0 的日期 } export interface ExtractedKnowledgeItem { type: KnowledgeType title: string content: string importance?: number confidence: number foreshadowStatus?: ForeshadowStatus suggestedMergeId?: string proposedPatch?: Partial } export interface KnowledgeExtractionResult { batchId: string chapterId: string items: ExtractedKnowledgeItem[] } ``` 扩展 `CockpitSummary`: ```typescript writingStats?: WritingStats ``` 扩展 `KnowledgeEntry`: ```typescript extractionConfidence?: number mergeTargetId?: string extractionBatchId?: string ``` 扩展 `GlobalSettings`: ```typescript knowledgeAutoExtract: boolean // default true knowledgeExtractConfidenceThreshold: number // default 0.8 ``` --- ## 4. 日更追踪 ### 4.1 差分算法(`ChapterWritingTracker`) **`recordDelta(bookId, chapterId, newWordCount)`**(在 `CHAPTER_UPDATE` 成功后调用): 1. 读取 `chapter_writing_snapshots.logged_word_count`(无则 0) 2. `delta = newWordCount - logged` 3. 若 `delta !== 0`:`WritingLogService.addToday(bookId, delta)`(当日累计不低于 0) 4. 更新 snapshot:`logged_word_count = newWordCount` **`supplementOnFinish(bookId, chapterId, wordCount)`**(在 `finishChapter` 创建章节后调用): 1. 若 snapshot 不存在,创建 `logged=0, finish_supplemented=0` 2. `gap = wordCount - snapshot.logged_word_count` 3. 若 `gap > 0`:`addToday(bookId, gap)`,更新 `logged = wordCount` 4. 设 `finish_supplemented = 1` 删除章节时 CASCADE 删除 snapshot;`writing_logs` 历史不回溯修正(接受已知限制)。 ### 4.2 连更计算 - 仅当 `dailyWordGoal > 0` 时计算 - 从今日向前遍历 `writing_logs`(本书),连续天数满足 `word_count >= dailyWordGoal` - 今日未达标时 streak 不计入今日(展示为截至昨日的连更) ### 4.3 时区 使用主进程 `Intl` 或 `toLocaleDateString('sv-SE')` 生成本地 `YYYY-MM-DD`,与作者所在系统时区一致。 --- ## 5. AI 知识抽取 ### 5.1 触发点 | 触发 | 条件 | 行为 | |------|------|------| | 成章落盘 | `finishChapter` 成功且 `knowledgeAutoExtract` | 入队 `extractForChapter(bookId, chapterId)` | | 手动 | `knowledge:extractChapter` IPC | 同上 | | 设置关闭 | `knowledgeAutoExtract === false` | 仅手动触发 | 抽取在 `setImmediate` / 微任务队列异步执行,不延长成章 IPC 响应时间。 ### 5.2 Prompt 与解析 **输入**:章节标题 + 正文纯文本(`stripHtml`,上限 12000 字,超出截断并注明)+ 本书已有 `approved` 条目标题列表(上限 30 条,供合并判断)。 **输出 JSON 数组**(严格解析,失败重试一次「仅返回 JSON」): ```json [ { "type": "foreshadow", "title": "测灵石异常", "content": "入门考核时石头发热,暗示主角特殊灵根", "importance": 4, "confidence": 0.92, "foreshadowStatus": "buried", "suggestedMergeId": null, "proposedPatch": null }, { "type": "character", "title": "林远状态更新", "content": "通过入门考核,声望上升", "importance": 3, "confidence": 0.85, "suggestedMergeId": "uuid-of-existing-entry", "proposedPatch": { "content": "已通过入门考核,同门态度由轻视转为敬畏", "importance": 4, "lastMentionChapterId": "" } } ] ``` **类型**:五种全做 — `character` / `foreshadow` / `relationship` / `location` / `fact`。 **落库**:每条 → `knowledge_entries`,`status=pending`,`source_chapter_id=chapterId`,`extraction_confidence`,`extraction_batch_id`,`merge_target_id`(若有)。 ### 5.3 合并审核 UX(决策 D) | 场景 | UI | |------|-----| | 无 `mergeTargetId` | 待审核列表常规展示;采纳 → `approve` | | 有 `mergeTargetId` | 卡片标注「建议更新:{目标标题}」;点击打开 `KnowledgeEditorDialog` **预填** `proposedPatch` + 目标条目只读摘要 | | 确认更新 | `knowledge:approveMerge` → patch 目标 `approved` 条目,删除或标记本 pending 为 `rejected` | | 作为新条目 | 清除 `mergeTargetId`,保留 pending,用户编辑后采纳 | **批量「全部采纳高置信度」**:仅处理 `mergeTargetId IS NULL` 且 `confidence >= threshold` 的 pending 项。 ### 5.4 IPC 新增 | 通道 | 参数 | 返回 | |------|------|------| | `writing:getStats` | `{ bookId }` | `WritingStats` | | `knowledge:extractChapter` | `{ bookId, chapterId }` | `KnowledgeExtractionResult` | | `knowledge:approveMerge` | `{ bookId, pendingId }` | `KnowledgeEntry`(更新后的目标) | | `knowledge:batchApproveHighConfidence` | `{ bookId, threshold? }` | `number`(采纳条数) | `CHAPTER_UPDATE` handler 内联调用 `ChapterWritingTracker`(无需新 IPC)。 --- ## 6. UI 规格 ### 6.1 驾驶舱(`CockpitModal`) 在现有四卡片下方新增: - **今日进度**:`todayWords / dailyGoal` 文字 + 细进度条(`data-testid="cockpit-today-progress"`) - **连更**:`🔥 {streakDays} 天`(`dailyGoal=0` 时隐藏,`data-testid="cockpit-streak"`) - **热力图**:Canvas 52×7 格,365 天;颜色深浅按 `word_count` 分档(0 / 1–999 / 1000–2999 / ≥3000);今天描边高亮(`data-testid="cockpit-heatmap"`) ### 6.2 状态栏(`EditorLayout` `#editor-statusbar`) 当 `dailyWordGoal > 0` 且当前有打开书籍: - 显示 `今日 1234 / 3000 字` + 迷你进度条 - CSS class:`goal-met`(≥100%)、`goal-near`(≥80%) - `data-testid="status-daily-goal"` ### 6.3 知识库(`KnowledgePanel`) - 待审核 Tab 顶部:本批抽取分组(按 `extractionBatchId` 折叠,显示来源章节) - 条目卡片:置信度徽章、合并建议标签 - 工具栏:「全部采纳高置信度」`data-testid="knowledge-batch-high-confidence"` - 合并项:「查看更新建议」`data-testid="knowledge-merge-review-{id}"` ### 6.4 抽取入口 - 章节列表项右键或编辑器菜单:**「抽取本章知识」** `data-testid="chapter-extract-knowledge"` - 知识库工具栏:**「从当前章抽取」**(无选中章时 disabled) ### 6.5 设置(`SettingsPage`) 写作 / AI 分区: - `knowledgeAutoExtract` 开关 `data-testid="settings-knowledge-auto-extract"` - `knowledgeExtractConfidenceThreshold` 数字输入(0.5–1.0,步进 0.05)`data-testid="settings-extract-threshold"` --- ## 7. 错误处理 | 场景 | 处理 | |------|------| | AI 抽取 JSON 解析失败 | 重试一次;仍失败 toast「抽取失败,请稍后重试」,写 `console.error` | | 章节正文为空 | 跳过抽取,toast「章节无内容」 | | `suggestedMergeId` 指向已删除条目 | 视为无合并建议,按新建处理 | | `addToday` 扣减后当日 < 0 | 钳制为 0 | | 成章时抽取进行中 | 允许并行;同章多次抽取生成新 `batchId`,旧 pending 保留 | --- ## 8. 测试 ### 8.1 测试 ID | ID | 层级 | 场景 | 预期 | |----|------|------|------| | UT-LOG-01 | 单元 | `recordDelta` 保存 +200 字 | 当日 +200 | | UT-LOG-02 | 单元 | `recordDelta` 删 100 字 | 当日 -100(不低于 0) | | UT-LOG-03 | 单元 | `supplementOnFinish` gap=500 | 当日 +500,`finish_supplemented=1` | | UT-LOG-04 | 单元 | 连更 3 天达标 | `streakDays=3` | | UT-EXT-01 | 单元 | 解析合法抽取 JSON | 返回 N 条 `ExtractedKnowledgeItem` | | UT-EXT-02 | 单元 | 含 `suggestedMergeId` | `merge_target_id` 正确写入 | | IT-LOG-01 | 集成 | `CHAPTER_UPDATE` 两次 | `writing_logs` 累计正确 | | IT-EXT-01 | 集成 | `extractChapter` 有正文 | 返回 ≥1 pending | | IT-EXT-02 | 集成 | `approveMerge` | 目标 `approved` 内容更新 | | IT-EXT-03 | 集成 | 抽取 + P6 `suggestContext` | 新采纳条目出现在推荐中 | | E2E-LOG-01 | E2E | 写 100 字保存 | 驾驶舱今日字数更新 | | E2E-LOG-02 | E2E | 热力图可见 | `cockpit-heatmap` 存在 | | E2E-EXT-01 | E2E | 成章后待审核有抽取项 | pending 列表非空 | | E2E-EXT-02 | E2E | 手动抽取本章 | 待审核增加 | | E2E-EXT-03 | E2E | 合并建议确认更新 | 目标条目内容变化 | ### 8.2 超时 - IT-EXT-01 / IT-EXT-02 / IT-EXT-03:**240s** - E2E-EXT-01 / E2E-EXT-03:**300s** ### 8.3 原则 集成/E2E 含 AI 抽取时 **禁止 Mock**,使用 LM Studio 默认配置。 --- ## 9. i18n 键(节选) ```json "cockpit.todayProgress": "今日 {{current}} / {{goal}} 字", "cockpit.streak": "连更 {{days}} 天", "cockpit.heatmap": "写作热力图", "status.dailyGoal": "今日 {{current}} / {{goal}}", "knowledge.extractChapter": "抽取本章知识", "knowledge.extractFromCurrent": "从当前章抽取", "knowledge.batchHighConfidence": "全部采纳高置信度", "knowledge.mergeSuggest": "建议更新:{{title}}", "knowledge.approveMerge": "确认更新", "knowledge.saveAsNew": "作为新条目", "knowledge.extracting": "正在抽取知识…", "knowledge.extractFailed": "知识抽取失败,请稍后重试", "settings.knowledgeAutoExtract": "成章后自动抽取知识", "settings.extractThreshold": "高置信度阈值" ``` --- ## 10. 文件映射 | 文件 | 职责 | |------|------| | `src/main/db/schema-v7.sql` | snapshots + knowledge 扩展列 | | `src/main/db/migrate.ts` | v7 迁移 | | `src/main/db/repositories/writing-log.repo.ts` | 全局 writing_logs CRUD | | `src/main/db/repositories/chapter-writing-snapshot.repo.ts` | 每书 snapshot | | `src/main/services/writing-log.service.ts` | 日更累加、连更、热力图数据 | | `src/main/services/chapter-writing-tracker.service.ts` | 差分 + 补计 | | `src/main/services/knowledge-extraction.service.ts` | AI 抽取 | | `src/main/services/knowledge-extraction-prompt.ts` | prompt 构建 + JSON 解析 | | `src/main/services/cockpit.service.ts` | 扩展 WritingStats | | `src/main/ipc/handlers/writing.handler.ts` | `writing:getStats` | | `src/main/ipc/handlers/knowledge.handler.ts` | 抽取/合并 IPC | | `src/main/ipc/handlers/chapter.handler.ts` | 挂钩 tracker | | `src/renderer/components/cockpit/WritingHeatmap.tsx` | Canvas 热力图 | | `src/renderer/components/cockpit/CockpitModal.tsx` | 扩展布局 | | `src/renderer/components/knowledge/KnowledgePanel.tsx` | 批次/合并 UI | | `tests/main/writing-log.test.ts` | UT-LOG-* | | `tests/main/knowledge-extraction.test.ts` | UT-EXT-* / IT-EXT-* | | `e2e/writing-logs.spec.ts` | E2E-LOG-* | | `e2e/knowledge-extraction.spec.ts` | E2E-EXT-* | --- ## 11. Spec Coverage Checklist | design/readme 要求 | 本规格 | |--------------------|--------| | §6.7 每章自动抽取 + 审核 | §5 | | §6.7 合并/忽略/批量采纳 | §5.3 | | §8 writing_logs + 热力图 + 连更 | §3–§4、§6.1 | | §8 番茄钟/里程碑 | 排除 §1.4 | | §9 驾驶舱内嵌分析 | 热力图 + 今日进度(轻量) | | P5 延后「AI 抽取」 | 本阶段交付 | | P5 延后 writing_logs | 本阶段交付 | | P6 知识注入上游 | §5 + IT-EXT-03 | --- ## 12. 版本与 DoD - `package.json` → **0.8.0** - `README.md` 功能概览追加 P5.2 一行 - `SettingsPage` about 版本号更新 - `npm run build && npm run test && npm run test:e2e` 全绿 - LM Studio 集成测试按 §8.2 超时执行 --- *本规格为笔临 P5.2 子项目。角色弧线、关系图谱、导入导出等各自在后续 spec 中增量修订。*