docs: add P5.2 writing logs and AI knowledge extraction design spec for v0.8.0

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-07 16:42:04 +08:00
parent aa2c7dfed3
commit 72c8e13210
@@ -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 进程模型
```
┌──────────────────────────────────────────────────────────────────┐
│ 主进程 │
│ · WritingLogServiceuserData/writing_logs.sqlite
│ · ChapterWritingTracker(差分 + 成章补计) │
│ · KnowledgeExtractionServiceAiClient + 解析 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 // 01+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<CreateKnowledgeInput>
}
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": "<chapterId>"
}
}
]
```
**类型**:五种全做 — `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 / 1999 / 10002999 / ≥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 中增量修订。*