From 72c8e132100eaaacb0e783cfae21ff026a795a8f Mon Sep 17 00:00:00 2001 From: kun1h Date: Tue, 7 Jul 2026 16:42:04 +0800 Subject: [PATCH] docs: add P5.2 writing logs and AI knowledge extraction design spec for v0.8.0 Co-authored-by: Cursor --- ...ilin-p52-writing-logs-extraction-design.md | 452 ++++++++++++++++++ 1 file changed, 452 insertions(+) create mode 100644 docs/superpowers/specs/2026-07-07-bilin-p52-writing-logs-extraction-design.md diff --git a/docs/superpowers/specs/2026-07-07-bilin-p52-writing-logs-extraction-design.md b/docs/superpowers/specs/2026-07-07-bilin-p52-writing-logs-extraction-design.md new file mode 100644 index 0000000..d4ad4cd --- /dev/null +++ b/docs/superpowers/specs/2026-07-07-bilin-p52-writing-logs-extraction-design.md @@ -0,0 +1,452 @@ +# 笔临 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 中增量修订。*