Files
bilin/docs/superpowers/specs/2026-07-08-bilin-p7-graph-p9-analytics-design.md
bing e64353f608 docs: add P7 character graph and P9 writing analytics design spec for v1.0.0
Define Cytoscape relationship graph MVP, writing_sessions telemetry, and cockpit analytics cards for the v1.0.0 release.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-08 10:04:56 +08:00

18 KiB
Raw Permalink Blame History

笔临 P7 角色关系图谱 + P9 个人写作分析 设计规格

版本1.0
日期2026-07-08
范围:§7 角色关系图谱 MVP + §9 个人写作分析 MVP(含 writing_sessions 精确采集)
前置P5.3 + P6.1 已交付(v0.9.0d4122c8);P5.2 写作日志 / 热力图 / 番茄(v0.8.0)
参考design/readme.md v1.1 §7 / §9、design/ui_pc.html #graphModal / #dashboardModal

目标版本v1.0.0


1. 背景与目标

1.1 阶段定位

v0.9.0 已完成作家工作流核心链路(驾驶舱、日志、番茄、知识库、AI 上下文、注入历史)。design/readme.md §7 角色关系图谱 与 §9 个人写作分析 仍为概念/原型状态(#graphModal 为静态 demo,分析卡片无真实数据源)。

本阶段(v1.0.0)统一 MVP 交付:

  • P7(§7:基于 settings 角色设定 + properties.relationships 的可视化关系图谱
  • P9(§9:基于 writing_sessions + 现有 writing_logs / pomodoro_daily 的驾驶舱分析区

1.2 已确认决策(Brainstorming 2026-07-08

维度 决策
交付策略 方案 3C + D 统一 MVP,单版 v1.0.0
分析数据采集 方案 A:新增 writing_sessions 表,编辑活跃时精确记录
关系存储 settings.properties.relationships[] JSON,不新增每书关系表
每书 schema 保持 v8,不迁移;chapters.pov_character_id 已有,补 UI
全局 sqlite 扩展 writing_logs.sqlite(与 writing_logs / pomodoro_daily 同库)
图谱库 Cytoscape.js + cose-bilkent(渲染进程)
Session 合并 同一书籍连续写作,间隔 ≤ 5 分钟 合并为同一 session
图谱上限 100 角色节点;超出 toast 提示,仍可按「仅有关系的节点」筛选展示
边去重 单向存储;渲染时 A↔B 去重,取较高 intimacy
分析入口 仅驾驶舱内嵌卡片,不做独立全屏分析页
POV 章节元数据下拉绑定 pov_character_id → 分析 POV 字数分布

1.3 验收标准

用户能完成:

  1. 在设定面板为角色添加/编辑/删除关系(目标角色、描述、亲密度 1–5),保存后在关系图谱中可见对应边
  2. 打开 角色关系图谱 全屏模态,看到力导向布局下的角色节点与关系边;可切换 grid / circle 布局
  3. 点击节点 → 侧栏显示角色名与「编辑设定」跳转;点击边 → 侧栏编辑关系描述与亲密度
  4. 在章节编辑区设置 POV 角色,驾驶舱 POV 分布反映该角色章节字数
  5. 在章节编辑器写作并保存后,驾驶舱 今日写作速度(字/分钟)> 024 小时时段热力有数据
  6. 驾驶舱展示:7 日字数趋势、番茄今日次数与均字/次、按卷字数占比(与现有字数聚合一致)

1.4 本规格不实现(明确排除)

归属
AI 章节分析后自动关系建议(§7.2-3) v1.1+
图谱中右键拖拽连线创建关系 v1.1+
知识抽取面板「关系建议」一键添加 v1.1+
预计完本日期 §9 后续
角色弧线简报三节时间线(§20.3 v1.1+
独立全屏「数据分析」页、书籍设置分析 Tab 后续
导入导出、章节模板(§10 / §11) 其他阶段
故事时间线模态(§5.7 其他阶段
设定条目(非 character)进入关系图谱 排除

2. 架构

2.1 进程模型

┌─────────────────────────────────────────────────────────────────────┐
│ 主进程(userData writing_logs.sqlite 扩展)                          │
│  · WritingSessionRepository / WritingSessionService                  │
│  · WritingAnalyticsService(聚合 sessions + logs + pomodoro + 章节) │
│  · ChapterWritingTracker.recordDelta → session.recordActivity         │
└──────────────────────────────┬──────────────────────────────────────┘
                               │
┌──────────────────────────────▼──────────────────────────────────────┐
│ 主进程(每书 .sqliteschema v8                                      │
│  · CharacterGraphService(读 settings,写 properties.relationships  │
│  · SettingRepository(已有)                                           │
│  · ChapterRepository.pov_character_id(已有)                          │
└──────────────────────────────┬──────────────────────────────────────┘
                               │ IPC
┌──────────────────────────────▼──────────────────────────────────────┐
│ 渲染进程                                                             │
│  · CharacterGraphModalCytoscape + 侧栏)                           │
│  · SettingRelationshipsEditor(角色设定页关系 CRUD                  │
│  · CockpitModal 扩展 analytics 区块                                  │
│  · ChapterHeader / ChapterListPOV 下拉                             │
└─────────────────────────────────────────────────────────────────────┘

2.2 模块边界

模块 职责 依赖
WritingSessionService 活跃 session 状态机;5 分钟 idle 切分;recordActivity WritingSessionRepository
WritingAnalyticsService getSummary(bookId) 聚合分析指标 sessions, logs, pomodoro, chapters, settings
CharacterGraphService 构建 nodes/edgesCRUD 关系;边去重 SettingRepository
ChapterWritingTracker 已有字数 delta → 扩展调用 session WritingLogService, WritingSessionService
CharacterGraphModal Cytoscape 渲染与布局切换 preload graph.*
CockpitModal 展示 WritingAnalyticsSummary preload analytics.getSummary

原则

  • Session 写入与 writing_logs 共用 recordDelta 路径,不重复计字
  • 图谱数据以 settings 为唯一真相源;Cytoscape 元素由 IPC 返回的 DTO 构建
  • 分析服务只读;失败时驾驶舱分析区显示占位/空态,不阻断驾驶舱其他卡片

3. 数据模型

3.1 全局 userData:扩展 writing_logs.sqlite

WritingLogRepository.migrate() 追加:

CREATE TABLE IF NOT EXISTS writing_sessions (
  id          TEXT PRIMARY KEY,
  book_id     TEXT NOT NULL,
  chapter_id  TEXT,
  started_at  TEXT NOT NULL,
  ended_at    TEXT NOT NULL,
  word_delta  INTEGER NOT NULL DEFAULT 0,
  date        TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_sessions_book_date
  ON writing_sessions (book_id, date);
CREATE INDEX IF NOT EXISTS idx_sessions_book_started
  ON writing_sessions (book_id, started_at);

Session 状态机WritingSessionService):

事件 行为
recordActivity(bookId, chapterId, delta) 若无活跃 session 或距上次 ended_at > 5min → INSERT 新 session;否则 UPDATE ended_at, word_delta += delta
endActive(bookId) 书籍切换 / 应用退出前结束活跃 session
delta === 0 忽略(不创建空 session

活跃 session 可在主进程内存缓存 { bookId, sessionId, lastEndedAt }DB 为持久真相。

3.2 角色关系 JSON(每书 settings.properties

interface CharacterRelationship {
  id: string
  targetId: string
  label: string
  intimacy: 1 | 2 | 3 | 4 | 5
}

// SettingEntry.properties.relationships?: CharacterRelationship[]
// 仅 type === 'character' 时读写

边去重规则(渲染):对无序对 {A,B} 仅输出一条边;若 A→B 与 B→A 均存在,取 max(intimacy)label 优先取较高亲密度源的 label。

CRUD

  • upsertRelationship(sourceId, { id?, targetId, label, intimacy }) — 更新源角色 properties.relationships
  • deleteRelationship(sourceId, relationshipId)
  • 禁止 targetId === sourceId;禁止指向非 character 设定

3.3 POV(已有)

  • DB 列:chapters.pov_character_idschema v8 已有 tryAlter
  • UI:章节编辑器工具栏或章节列表元数据区,select 绑定 type='character' 设定
  • IPC:复用 chapter.update patch { povCharacterId }

3.4 类型扩展(src/shared/types.ts

export interface CharacterGraphNode {
  id: string
  label: string
}

export interface CharacterGraphEdge {
  id: string
  source: string
  target: string
  label: string
  intimacy: 1 | 2 | 3 | 4 | 5
}

export interface CharacterGraphData {
  nodes: CharacterGraphNode[]
  edges: CharacterGraphEdge[]
  truncated: boolean
  nodeCount: number
}

export interface WritingAnalyticsSummary {
  todayWords: number
  todaySpeedWpm: number | null
  weekTrend: { date: string; words: number }[]
  hourlyHeatmap: number[]   // length 24
  pomodoroToday: number
  pomodoroAvgWords: number | null
  povDistribution: { characterId: string; name: string; words: number }[]
  volumeDistribution: { volumeId: string; title: string; words: number }[]
}

4. 服务层设计

4.1 WritingAnalyticsService

getSummary(bookId: string): WritingAnalyticsSummary
字段 计算方式
todayWords WritingLogService.getStats(bookId).todayWords
todaySpeedWpm 今日 sessionssum(word_delta) / sum(minutes(ended_at - started_at));无 session 或零时长 → null
weekTrend 近 7 天 writing_logs 按 date
hourlyHeatmap 近 30 天 sessions,按 started_at 小时 bucket 累加 word_delta24 槽)
pomodoroToday PomodoroDailyRepository.getTodayCount
pomodoroAvgWords 今日 total_words / completed_countcount=0 → null
povDistribution chapters GROUP BY pov_character_id SUM word_countJOIN character name
volumeDistribution chapters GROUP BY volume_id SUM word_count

未设置 POV 的章节计入 { characterId: '__none__', name: i18n未指定 } 或单独过滤(实现时选 单独「未指定」桶)。

4.2 CharacterGraphService

getData(bookId: string, filter?: 'all' | 'connected'): CharacterGraphData
upsertRelationship(bookId, sourceId, input): CharacterRelationship
deleteRelationship(bookId, sourceId, relationshipId): void
  • filter: 'connected':仅包含有至少一条边的节点(用于超 100 节点时的降级展示)
  • truncated: true 当 character 总数 > 100

4.3 ChapterWritingTracker 扩展

recordDelta(chapterId: string, newWordCount: number): void {
  // 现有 writing_logs delta 逻辑不变
  if (delta !== 0) {
    this.writingLogs.addToday(this.bookId, delta)
    this.sessions.recordActivity(this.bookId, chapterId, delta)
  }
  // snapshot upsert 不变
}

5. IPC 与 preload

// ipc-channels.ts
GRAPH_GET_DATA: 'graph:getData',
GRAPH_UPSERT_RELATIONSHIP: 'graph:upsertRelationship',
GRAPH_DELETE_RELATIONSHIP: 'graph:deleteRelationship',
ANALYTICS_SUMMARY: 'analytics:summary',
通道 参数 返回
graph:getData { bookId, filter? } IpcResult<CharacterGraphData>
graph:upsertRelationship { bookId, sourceId, relationship } IpcResult<CharacterRelationship>
graph:deleteRelationship { bookId, sourceId, relationshipId } IpcResult<void>
analytics:summary { bookId } IpcResult<WritingAnalyticsSummary>

cockpit:getSummary 不合并 analytics(保持单一职责);驾驶舱 UI 并行请求 cockpit.getSummary + analytics.getSummary,或在 renderer 层 Promise.all


6. UI 规格

6.1 角色关系图谱(CharacterGraphModal

参考 design/ui_pc.html #graphModal

元素 testid 说明
模态 character-graph-modal 宽屏 modal-content--wide
Cytoscape 容器 character-graph-cy 最小高度 480px
布局按钮 graph-layout-cose / grid / circle
侧栏 character-graph-sidebar 节点/边详情
设定入口按钮 setting-open-graph 设定面板工具栏

亲密度配色(边 line-color):

intimacy 颜色
1 #4ea8de
2 #45b7aa
3 #3fb950 绿
4 #f0883e
5 #e0556a

Cytoscape 依赖(渲染进程):cytoscapecytoscape-cose-bilkentdestroy 于 modal 关闭。

6.2 设定面板关系编辑(SettingRelationshipsEditor

  • 嵌入角色设定编辑区(TipTap 下方或侧栏,当 type === 'character'
  • 列表:目标角色名、label、亲密度、删除
  • 添加:目标角色下拉(排除自身)、label 输入、intimacy range 15
  • testidsetting-relationshipssetting-relationship-add

6.3 驾驶舱分析区(CockpitModal 扩展)

卡片 testid 内容
分析区容器 cockpit-analytics
今日速度 cockpit-speed-today {{wpm}} 字/分钟
时段热力 cockpit-hourly-heatmap 24 格横条
POV 分布 cockpit-pov-chart Top 5 横向条
按卷占比 cockpit-volume-chart 卷名字 + 字数
7 日趋势 cockpit-week-trend 7 柱 mini chart

驾驶舱底部增加「打开关系图谱」按钮:cockpit-open-graph

6.4 章节 POV

  • 位置:章节编辑器标题栏或 #editor-statusbar 附近
  • testidchapter-pov-select
  • 选项:空(未指定)+ 所有 character 设定
  • 变更时 debounce 调用 chapter.update

7. i18n 键(节选)

"graph.title": "角色关系图谱",
"graph.layout.cose": "力导向",
"graph.layout.grid": "网格",
"graph.layout.circle": "圆形",
"graph.truncated": "角色过多,仅显示前 {{count}} 个",
"graph.sidebar.node": "角色:{{name}}",
"graph.sidebar.edge": "关系:{{label}}",
"graph.editSetting": "编辑设定",
"relationship.title": "角色关系",
"relationship.add": "添加关系",
"relationship.target": "目标角色",
"relationship.label": "关系描述",
"relationship.intimacy": "亲密度",
"analytics.title": "写作分析",
"analytics.speedToday": "今日 {{wpm}} 字/分钟",
"analytics.speedUnknown": "暂无速度数据",
"analytics.hourlyHeatmap": "写作时段(近30天)",
"analytics.povDistribution": "视角字数分布",
"analytics.volumeDistribution": "按卷字数",
"analytics.weekTrend": "近 7 日字数",
"analytics.povUnassigned": "未指定视角",
"chapter.pov": "视角角色",
"cockpit.openGraph": "角色关系图谱"

8. 测试要求

8.1 单元 / 集成

用例ID 层级 场景 预期
UT-SESSION-01 单元 5min 内两次 recordActivity 同一 sessionword_delta 累加
UT-SESSION-02 单元 间隔 6min 两次 recordActivity 两个 session
UT-SESSION-03 单元 delta=0 不创建 session
UT-GRAPH-01 单元 3 角色 2 关系 getData nodes=3, edges=2
UT-GRAPH-02 单元 A→B 与 B→A 双向存储 渲染 1 条边,intimacy 取 max
UT-GRAPH-03 单元 101 个 character truncated=true
UT-ANALYTICS-01 单元 2 个 session 共 100 字 / 10 分钟 todaySpeedWpm=10
UT-ANALYTICS-02 单元 hourlyHeatmap 24 长度,对应小时有值
IT-ANALYTICS-01 集成 chapter update → recordDelta → getSummary todaySpeedWpm > 0
IT-GRAPH-01 集成 upsertRelationship → getData 边可见

8.2 E2E

用例ID 场景 预期
E2E-GRAPH-01 打开关系图谱 力导向图可见节点
E2E-GRAPH-02 点击节点 侧栏显示角色名
E2E-GRAPH-03 切换圆形布局 布局变化
E2E-GRAPH-04 设定面板添加关系 图谱出现新边
E2E-ANALYTICS-01 写作保存后打开驾驶舱 cockpit-speed-today 有数值
E2E-ANALYTICS-02 设置 POV 后刷新驾驶舱 POV chart 含该角色

E2E 图谱:不依赖 AI / LM Studio。


9. 文件清单(实现阶段参考)

路径 说明
src/main/db/repositories/writing-session.repo.ts sessions CRUD
src/main/services/writing-session.service.ts 状态机
src/main/services/writing-analytics.service.ts 分析聚合
src/main/services/character-graph.service.ts 图谱 DTO + 关系 CRUD
src/main/ipc/handlers/graph.handler.ts 图谱 IPC
src/main/ipc/handlers/analytics.handler.ts 分析 IPC
src/renderer/components/graph/CharacterGraphModal.tsx Cytoscape UI
src/renderer/components/setting/SettingRelationshipsEditor.tsx 关系 CRUD
src/renderer/components/cockpit/CockpitAnalytics.tsx 分析卡片
src/renderer/components/cockpit/HourlyHeatmap.tsx 24 槽热力
tests/main/writing-session.test.ts UT-SESSION-*
tests/main/character-graph.test.ts UT-GRAPH-*
tests/main/writing-analytics.test.ts UT-ANALYTICS-*
e2e/character-graph.spec.ts E2E-GRAPH-*
e2e/writing-analytics.spec.ts E2E-ANALYTICS-*

10. Definition of Done

  • writing_sessions 表与 Session 状态机落地
  • 角色关系 CRUD + Cytoscape 图谱模态
  • 驾驶舱分析区(速度 / 时段 / POV / 卷 / 7 日)
  • 章节 POV 选择 UI
  • UT / IT / E2E 用例通过
  • i18n zh-CN / en 键齐全
  • npm run build + npm run test 全绿
  • 版本号 1.0.0package.json / README / Settings 关于页)

11. Spec Coveragedesign/readme.md

设计章节 本 spec 覆盖
§7.1 力导向图、节点/边、布局 MVP
§7.2-1 设定面板手动添加
§7.2-2 图谱拖拽连线 v1.1
§7.2-3 AI 关系建议 v1.1
§7.3 E2E-GRAPH-01~04 04 改为设定面板添加)
§9 写作速度仪表盘 今日速度 + 时段热力
§9 习惯追踪 部分(连更/番茄已有;session 时长留待后续展示)
§9 POV 字数分布
§9 角色弧线简报 v1.1
§20.3 角色弧线 v1.1