Files
bilin/docs/superpowers/specs/2026-07-07-bilin-p52-writing-logs-extraction-design.md

453 lines
18 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 笔临 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 中增量修订。*