|
| 1 | +# 会话页交互与效率增强设计(v0.7.1) |
| 2 | + |
| 3 | +> 状态:已获用户批准(设计评审 2026-08-19,第 1/2 节均确认) |
| 4 | +> 对应代码:`app/src-tauri/src/commands_session.rs`、`app/src-tauri/src/db_sessions.rs`、`app/src-tauri/src/db.rs`、`app/src-tauri/src/db_notes.rs`、`app/src-tauri/src/types.rs`、`app/src/pages/SessionsPage.tsx`、`app/src/pages/NotesPage.tsx`、`app/src/App.tsx`、`app/src/types.ts` |
| 5 | +
|
| 6 | +## 1. 背景与目标 |
| 7 | + |
| 8 | +会话页(`SessionsPage.tsx`)当前是"档案浏览"式双栏布局:左侧 300px 列表(标题搜索 + 课程分组 + 段搜索三个功能堆叠),右侧详情(时间轴/OCR/三视图)。v0.6.0 M6 密集叠加了质量报告/大纲/课程分组/段搜索/降级横幅/笔记预览,但交互仍是内联样式 + 原生控件,无空态/加载态/批量/筛选/状态实时性。 |
| 9 | + |
| 10 | +用户确认的核心场景:**会话库是"视频→笔记"的生产流水线**(课堂助手产出会话 → 会话库管理并转化为笔记)。头脑风暴确认的五个痛点: |
| 11 | + |
| 12 | +| # | 痛点 | 根因 | |
| 13 | +|---|------|------| |
| 14 | +| P1 | 转化状态不可见 | 列表无"已转/未转"标记;数据模型根本没有会话↔笔记关联 | |
| 15 | +| P2 | 转化流程繁琐无批量 | 必须进详情 → 切预览视图 → 点转笔记(4 步);逐个操作 | |
| 16 | +| P3 | 会话↔笔记关联弱 | `notes.source='classroom'` 无 session_id,无法互跳 | |
| 17 | +| P4 | 列表不可筛不可批 | 无状态/转化筛选、无排序、无批量删除 | |
| 18 | +| P5 | 会话状态更新缓慢(已完成仍显示"采集中") | 页面 `display:none` 保持挂载(TD-004),列表只在挂载时加载一次,应用生命周期内几乎不刷新 | |
| 19 | + |
| 20 | +### 用户已确认的决策 |
| 21 | + |
| 22 | +- 方向:**交互与效率增强**(不做视觉设计系统重做;详情页阅读体验 L3 层本次不做,留后续)。 |
| 23 | +- 方案:**A+B 合并**(管理控制台 + 转化流水线),即 L1 数据层 + L2 列表层。 |
| 24 | +- 边界:前端 + 少量后端配合(允许改 Rust 命令/数据查询)。 |
| 25 | +- 版本:登记为小版本 **v0.7.1**(会话体验批次),独立版本文档与需求条目。 |
| 26 | + |
| 27 | +## 2. 核心设计 |
| 28 | + |
| 29 | +| 痛点 | 对策 | |
| 30 | +|------|------| |
| 31 | +| 转化状态不可见 | 列表直接显示已转/未转徽标 + 转化状态筛选 | |
| 32 | +| 转化流程繁琐无批量 | 列表内联「转笔记」按钮(4 步→1 步)+ 批量转笔记 | |
| 33 | +| 会话↔笔记关联弱 | 双向互跳:会话→笔记、笔记→来源会话 | |
| 34 | +| 列表不可筛不可批 | 状态/转化筛选 + 排序 + 批量删除 | |
| 35 | +| 状态更新缓慢 | 事件驱动刷新 + 页面激活刷新 | |
| 36 | + |
| 37 | +## 3. L1 数据层(Rust) |
| 38 | + |
| 39 | +### 3.1 `notes` 表加 `session_id` 列(数据模型变更,AGENTS §10 审查项) |
| 40 | + |
| 41 | +- `db.rs`:`ensure_column` 迁移(沿用 profile/region_kind/volume 既有模式)+ `idx_notes_session` 索引。 |
| 42 | +- `types.rs`:`NewNote` 加 `session_id: Option<i64>`;`Note` 加 `session_id: Option<i64>`(serde 可选,旧行 NULL)。 |
| 43 | +- `db_notes.rs`:`create_note` 写入 session_id;新增 `find_note_by_session(id) -> Option<Note>`(按 created_at 取最新)。 |
| 44 | +- **外键语义:`ON DELETE SET NULL`**——删除会话只断开关联、**不删笔记**(笔记是用户资产,级联删除笔记不可接受)。 |
| 45 | +- **旧数据诚实处理**:历史 `source='classroom'` 笔记无关联信息 → 保持 NULL,老会话显示"未转",不猜不填;用户可在详情页重新转换建立关联。 |
| 46 | + |
| 47 | +### 3.2 新契约 `SessionListItem`(不动既有 `Session` 结构,隔离风险) |
| 48 | + |
| 49 | +```rust |
| 50 | +#[derive(Debug, Clone, serde::Serialize)] |
| 51 | +#[serde(rename_all = "camelCase")] |
| 52 | +pub struct SessionListItem { |
| 53 | + pub session: Session, |
| 54 | + pub has_note: bool, // 存在关联笔记(find_note_by_session 非空) |
| 55 | + pub note_id: Option<i64>, // 最新关联笔记 |
| 56 | + pub note_title: Option<String>, |
| 57 | + pub has_content: bool, // 有转写段或 OCR 块 → "待转化"判定依据 |
| 58 | +} |
| 59 | +``` |
| 60 | + |
| 61 | +- `list_sessions` / `list_session_courses` 均返回带标记条目(课程分组模式同样显示转化状态)。 |
| 62 | +- 实现:SQL 子查询(`EXISTS` segments/ocr_blocks + 最新笔记子查询),列表 ≤200 条量级,开销可忽略。 |
| 63 | +- `CourseGroup.sessions` 类型同步为 `Vec<SessionListItem>`。 |
| 64 | + |
| 65 | +### 3.3 批量转笔记 `batch_session_to_note(ids: Vec<i64>)` |
| 66 | + |
| 67 | +- 校验:去重、≤50、逐条 id>0。 |
| 68 | +- 从 `session_to_note` 提取内部 helper `convert_to_note(...)` 共用(延续 REQ-081/082"单一管线"原则:批量与单条输出一致)。 |
| 69 | +- **部分成功语义**:单条失败不阻塞其他;返回 `BatchNoteResult { converted: Vec<{id, note_id}>, skipped: Vec<{id, reason}> }`,原因含"进行中的会话不能生成笔记 / 已转笔记 / 会话不存在"。 |
| 70 | +- 批量转**跳过已有笔记**的会话(防重复笔记);详情页「转为笔记」保留 = 有意的重新生成(新笔记 + 新关联,历史保留)。 |
| 71 | +- `lib.rs` 注册新命令。 |
| 72 | + |
| 73 | +## 4. L2 列表层(前端) |
| 74 | + |
| 75 | +### 4.1 列表项重设计(两行式) |
| 76 | + |
| 77 | +``` |
| 78 | +[☐] 眼影晕染手法(B站教程) [✓已转] / [●录制中] |
| 79 | + #12 · 08-19 14:30 · 42分钟 · 浏览器 [转笔记] / [查看笔记→] |
| 80 | +``` |
| 81 | + |
| 82 | +- 状态徽标:录制中(红点脉冲)/ 已完成 / 异常 / 已转笔记(绿 ✓)。 |
| 83 | +- **内联「转笔记」**:仅 finished/failed + 有内容 + 未转显示;点击直接调 `session_to_note`(快速路径,与详情页按钮同管线);转完行内变「查看笔记 →」。 |
| 84 | +- 时长显示:finished 用 `ended_at - started_at`(mm:ss / h:mm:ss);录制中显示"进行中"。 |
| 85 | +- 复选框 hover 显现 → 批量选择模式。 |
| 86 | + |
| 87 | +### 4.2 筛选、排序、搜索整合 |
| 88 | + |
| 89 | +- 工具条一行:状态筛选(全部/录制中/已完成/异常)+ 转化筛选(全部/未转/已转)+ 排序(时间↓/时间↑/时长)+ 课程分组开关。 |
| 90 | +- 筛选全部**前端本地过滤**(数据已在 SessionListItem)→ 即时响应、零后端改动;与课程分组叠加生效。 |
| 91 | +- 两个搜索框合并为一个 + 模式切换(`标题 | 转写内容`):标题模式本地过滤,内容模式调 `search_session_segments`。 |
| 92 | + |
| 93 | +### 4.3 批量操作 |
| 94 | + |
| 95 | +- 勾选后底部浮动操作栏:批量转笔记(跳过已转/进行中,结果计数提示)+ 批量删除(确认框说明"删除转写/OCR/图集,关联笔记保留")。 |
| 96 | +- 全选当前筛选结果。 |
| 97 | + |
| 98 | +### 4.4 状态实时性 |
| 99 | + |
| 100 | +- `SessionsPage` 加 `active: boolean` prop(App 层传,仿 focusSessionId 模式)→ 切到会话页时刷新列表,根治 display:none 挂载不刷新。 |
| 101 | +- 事件监听增强:`live:status`(payload 非 recording → 刷新)、`session:fused`(刷新 + 详情联动,已有逻辑保留)。 |
| 102 | +- 新完成提示条:刷新前后对比出新 finished 会话 → "📬 N 个会话已完成采集"(点击直达,一次性)。 |
| 103 | +- 操作反馈升级为右上角 toast(3s 自动消失,自绘不引库),替代现有蓝色小字 status。 |
| 104 | + |
| 105 | +### 4.5 空态/加载态 |
| 106 | + |
| 107 | +- 无会话 → 引导去课堂助手;筛选无结果 → 「清除筛选」按钮;首次加载骨架提示。 |
| 108 | + |
| 109 | +### 4.6 会话 ↔ 笔记双向互跳 |
| 110 | + |
| 111 | +- 会话页「查看笔记 →」:`App.setPage("notes")` + `focusNoteId`(复用 focusSessionId 模式)。 |
| 112 | +- 笔记页:`NotesPage` 支持 `focusNoteId` 定位;笔记行显示「来源会话 →」(有 session_id 时),点击 `onOpenSessions(id)` 跳回会话页 + `focusSessionId`(机制已存在,对称复用)。 |
| 113 | +- `App.tsx`:新增 `focusNoteId` 状态与 `onOpenSessions` 透传。 |
| 114 | + |
| 115 | +## 5. 文件改动清单 |
| 116 | + |
| 117 | +**Rust 侧(`app/src-tauri/src/`)** |
| 118 | + |
| 119 | +| 文件 | 改动 | |
| 120 | +|------|------| |
| 121 | +| `db.rs` | notes 表 session_id 列迁移(`ensure_column` + 索引) | |
| 122 | +| `types.rs` | `NewNote`/`Note` 加 session_id;新增 `SessionListItem`、`BatchNoteResult` | |
| 123 | +| `db_notes.rs` | `create_note` 写 session_id;新增 `find_note_by_session` | |
| 124 | +| `db_sessions.rs` | `list_sessions` 返回 `Vec<SessionListItem>`(子查询) | |
| 125 | +| `commands_session.rs` | 提取 `convert_to_note` helper;`session_to_note` 带 session_id;新增 `batch_session_to_note`;`list_session_courses` 返回标记条目 | |
| 126 | +| `lib.rs` | 注册 `batch_session_to_note` | |
| 127 | + |
| 128 | +**前端(`app/src/`)** |
| 129 | + |
| 130 | +| 文件 | 改动 | |
| 131 | +|------|------| |
| 132 | +| `types.ts` | `SessionListItem`、`BatchNoteResult`;`Note` 加 `session_id?`;`CourseGroup.sessions` 同步 | |
| 133 | +| `SessionsPage.tsx` | 列表层重构:徽标/内联转化/筛选排序/批量/toast/空态;`active` prop + 事件刷新 | |
| 134 | +| `App.tsx` | `active` prop 传递;`focusNoteId` 状态;`onOpenSessions` 透传 | |
| 135 | +| `NotesPage.tsx` | `focusNoteId` 定位 + 「来源会话 →」跳转 | |
| 136 | + |
| 137 | +## 6. 测试计划 |
| 138 | + |
| 139 | +**Rust 单测**(AAA,`:memory:` 隔离库): |
| 140 | +1. 迁移:无 session_id 列的旧库 open 后列补齐、旧笔记 NULL。 |
| 141 | +2. `list_sessions` 标记四象限:有笔记无内容 / 有内容无笔记 / 都有 / 都没有。 |
| 142 | +3. 删除会话 → 笔记保留、关联断开(SET NULL 生效)。 |
| 143 | +4. `find_note_by_session`:无 / 单 / 多次转换取最新。 |
| 144 | +5. `batch_session_to_note`:全成功 / 部分失败不阻塞 / 重复 id 去重 / >50 拒绝 / 录制中跳过 / 已转跳过 / 失败原因回传。 |
| 145 | +6. `session_to_note` 落库带 session_id(与 preview 输出一致性回归)。 |
| 146 | +7. 全量 `cargo test` + `cargo clippy` 回归。 |
| 147 | + |
| 148 | +**前端**:无 Vitest 基建 → 手动验证清单(见验收标准)。 |
| 149 | + |
| 150 | +## 7. 验收标准 |
| 151 | + |
| 152 | +- [ ] 会话完成采集后,会话页列表无需任何操作自动变"已完成"(≤2s),不再残留"录制中"。 |
| 153 | +- [ ] 行内「转笔记」→ ≤1s 显示「查看笔记 →」;点击跳笔记页并定位该笔记;笔记页可见「来源会话 →」可跳回。 |
| 154 | +- [ ] 批量转:已转/录制中/失败会话被正确跳过并计数提示;批量删除后笔记仍在(确认框已说明)。 |
| 155 | +- [ ] 筛选(状态/转化)+ 排序 + 课程分组叠加正确;两个搜索模式切换正确。 |
| 156 | +- [ ] 空态引导 / 无结果"清除筛选" / toast 反馈可见。 |
| 157 | +- [ ] 详情页「转为笔记」仍可用(有意重新生成 → 新笔记、关联指向最新)。 |
| 158 | +- [ ] `cargo test` 全绿、clippy 无新增告警、新增/改动文件 ≤300 行。 |
| 159 | + |
| 160 | +## 8. 文档与规范动作 |
| 161 | + |
| 162 | +- 本设计文档(`docs/superpowers/specs/2026-08-19-sessions-ux-efficiency-design.md`)。 |
| 163 | +- **ADR**:notes↔sessions 关联数据模型(AGENTS §10 硬性要求)。 |
| 164 | +- 需求池登记:新增 REQ 条目(转化状态可视化 + 批量转化 + 双向关联),目标版本 **v0.7.1**。 |
| 165 | +- 版本沉淀:`docs/versions/v0.7.1.md`(小版本发版文档)。 |
| 166 | +- CHANGELOG 同步。 |
| 167 | + |
| 168 | +## 9. 边界与诚实声明 |
| 169 | + |
| 170 | +- 历史 `classroom` 笔记无法追溯归属会话 → 显示未转(不猜不填);用户可在详情页重新转换建立关联。 |
| 171 | +- 一个会话多次转换 → has_note 指向最新一篇;历史笔记保留可手动删。 |
| 172 | +- 批量转只对"有内容 + 未转 + 已结束"生效,其余跳过并说明原因(不静默)。 |
| 173 | +- L3 详情层(段搜索命中滚动修复、大纲↔时间轴联动高亮)本次不做,留后续批次。 |
0 commit comments