# Chat 侧边栏分析与收藏分组方案

> 目标页面：https://xybcloud.online/chat/
> 源码位置：`/var/www/html/chat/index.php`（3255 行单文件 SPA）+ `/var/www/html/chat/sessions_api.php`
> 数据存储：`/var/www/html/data/chat_sessions/<md5(user_id)>/{sessions.json, pinned.json, messages/<sid>.json}`

---

## 一、现有侧边栏功能盘点

### 1.1 结构（基于源码阅读 + DOM 抓取）

```
.sidebar (280px 抽屉, PC 端可 sidebar-pinned 固定为 260px)
├── .sidebar-header            logo + 「My Claude Code / 历史会话」+ 关闭按钮
├── .sidebar-new-chat          新对话按钮（蓝色全宽）
├── .sidebar-search            搜索框 + 清空按钮
└── .sidebar-list              滚动区，分组渲染
    ├── .sidebar-pin-title     收藏组标题（星形图标 + 计数徽章）
    ├── .sidebar-item.is-pinned 收藏项
    ├── .sidebar-group-title   时间组标题（今天 / 昨天 / 过去 7 天 / 过去 30 天 / 更早）
    └── .sidebar-item          普通会话项
```

### 1.2 功能清单

| 模块 | 实现位置 | 行为 |
|---|---|---|
| 抽屉开/关 | `toggleSidebar()` | 移动端滑入；PC 端 `sidebar-pinned` 类永久展开 |
| 新对话 | `insertPrefix()` | 顶部蓝色按钮，立即开启会话 |
| 搜索过滤 | `handleSidebarSearch()` → `applySearchFilter()` | 按标题 substring 匹配；空组标题自动隐藏 |
| 收藏切换 | `togglePin()` | 写入 `pinned.json`（云端）+ `localStorage` 兜底；乐观更新 UI |
| 三点菜单 | `.sidebar-item-dropdown` | 收藏/取消收藏、重命名、删除 |
| 滑动操作 | `addSwipeHandler()` | 移动端左滑出「重命名」「删除」 |
| 时间分组 | `renderConversationList()` | 按 `last_time` 自动归入 5 个时间组 |
| 下拉同步 | 顶栏 🌙/🖥️ | 浅色/深色/跟随系统 |
| 批量删除 | `select-mode` 类 | 消息区多选 + 「删除选中/全部删除」 |

### 1.3 数据模型（关键字段）

```jsonc
// pinned.json — 当前格式（仅 sid 数组，无分组信息）
["sid_abc", "sid_def", "sid_xyz"]

// conversationCache[i] — 会话对象
{
  "session_id": "sid_abc",
  "title": "翻译一段Python代码",
  "last_time": 1722000000,    // unix seconds
  "pinned": true,
  "source": "web" | "user" | "group",
  ...
}
```

### 1.4 痛点

1. **收藏是无序集合**：所有收藏项堆在一个组，超过 ~10 条就难定位
2. **无自定义分类**：没法按主题/项目/客户分文件夹
3. **收藏顺序不可控**：仅按 `last_time` 倒序，无法手动置顶某条重要收藏
4. **删除分组无解**：想批量「整理」收藏时，没有组级操作
5. **跨设备分组不同步**：当前 `pinned.json` 是纯数组，分组信息只有前端可写，云端无落点

---

## 二、收藏分组功能方案

### 2.1 设计目标

- **零破坏**：兼容现有 `pinned.json` 数据，自动迁移
- **轻量后端**：单文件 PHP，最小化表结构变更
- **键盘/触屏/PC 三端一致**：可点可拖可键盘
- **可分享/可导出**：用户数据可控

### 2.2 数据模型（v2）

```jsonc
// pinned.json — 新格式
{
  "version": 2,
  "groups": [
    {
      "id": "g_work",
      "name": "工作",
      "color": "#3b82f6",   // 6 个预设色 + 自定义
      "icon": "💼",          // 可选 emoji，缺省 "📁"
      "order": 0,
      "collapsed": false
    },
    {
      "id": "g_study",
      "name": "学习笔记",
      "color": "#10b981",
      "icon": "📚",
      "order": 1,
      "collapsed": false
    },
    {
      "id": "g_default",
      "name": "默认",
      "color": "#f59e0b",
      "icon": "⭐",
      "order": 99,
      "collapsed": false
    }
  ],
  "items": [
    { "sid": "sid_abc", "groupId": "g_work",     "pinnedAt": 1722000001, "order": 0 },
    { "sid": "sid_def", "groupId": "g_work",     "pinnedAt": 1722000000, "order": 1 },
    { "sid": "sid_xyz", "groupId": "g_default",  "pinnedAt": 1721999999, "order": 0 }
  ]
}
```

> **不破坏性迁移**：旧格式 `["sid_abc", ...]` 首次读取时 → 自动包成 `{version:1, groups:[g_default], items:[{sid, groupId:"g_default"}]}`，写入时升 v2。

### 2.3 后端 API 扩展（`sessions_api.php`）

| 方法 | URL | 用途 | 备注 |
|---|---|---|---|
| GET | `?action=list_pins` | 拉取完整分组结构 | 已存在，扩展返回字段 |
| POST | `?action=pin` | 切换/移动收藏 | body 增加 `group_id` 可选 |
| POST | `?action=create_group` | 新建分组 | `{name, color, icon}` |
| POST | `?action=rename_group` | 重命名/换色 | `{group_id, name?, color?, icon?}` |
| POST | `?action=delete_group` | 删除分组 | `{group_id, fallback: "g_default"}` 把会话移回 |
| POST | `?action=reorder_groups` | 拖拽排序 | `{order: ["g_x", "g_y", ...]}` |
| POST | `?action=assign_group` | 移动单条到分组 | `{session_id, group_id}` |
| POST | `?action=reorder_items` | 分组内排序 | `{group_id, order: [sid, ...]}` |
| POST | `?action=toggle_collapse` | 折叠/展开 | `{group_id, collapsed: bool}` |

所有写操作返回 `{ok: true, pinned: <新完整结构>}`，前端直接拿响应刷新侧边栏，无需再 GET。

### 2.4 前端 UI 改造

#### 2.4.1 侧边栏新结构

```
📂 收藏 (12)                                    [+ 新建分组]
├── 📁 工作 (3)            ⌄             ⋮
│   ├── ⭐ ⭐ Python 排序  ⋮
│   ├── ⭐ ⭐ SQL 优化    ⋮
│   └── ⭐ ⭐ 周报草稿    ⋮
├── 📚 学习笔记 (5)        ⌄             ⋮
│   └── ...
├── 📁 默认 (4)             ⌄             ⋮
│   └── ...
└── ⌃ 折叠全部 / ⌄ 展开全部

─── 今天 / 昨天 / 过去 7 天 ...（原有时间分组保持不变）
```

#### 2.4.2 交互细节

| 操作 | 鼠标/触屏 | 键盘 |
|---|---|---|
| 新建分组 | 收藏区右上 `[+]` → 弹 popover（名称、6 色色板、可选 emoji） | `Ctrl/Cmd + Shift + N` |
| 重命名分组 | 分组标题 `⋮` → 重命名 / 改图标 / 改色 | `F2`（聚焦时） |
| 删除分组 | `⋮` → 删除 → 二次确认 → 会话移回「默认」 | `Delete` |
| 移动会话到分组 | 会话项 `⋮` → 「移动到」→ 子菜单列出分组；或直接拖拽 | `M` 弹出分组选择器 |
| 分组内排序 | 拖拽手柄（≡ 图标，hover 时显示）；移动端长按拖动 | `Alt+↑/↓` |
| 跨组拖拽 | 拖到目标分组标题区域（高亮蓝色边框反馈） | — |
| 折叠/展开 | 点击分组标题或 `⌄/⌃` 箭头 | `Space` |
| 搜索 | 跨所有分组/时间组匹配，组标题根据命中数高亮 | `/` 聚焦搜索框 |

#### 2.4.3 视觉规范

- **分组标题**：复用现有 `.sidebar-pin-title`，加 `.group-icon` + 颜色小圆点
- **分组折叠态**：CSS `height: 0; overflow: hidden;` 过渡 200ms
- **拖拽中**：源项 opacity 0.4；目标组高亮蓝色虚线边框
- **空分组**：「暂无收藏项 · 拖拽至此」引导文案
- **键盘焦点环**：黄/橙 2px outline，对比度 ≥ 3:1

### 2.5 关键代码改动点（参考）

| 文件 | 行/函数 | 改动 |
|---|---|---|
| `sessions_api.php` | POST case 段 | 增加 `create_group/rename_group/delete_group/reorder_groups/assign_group/reorder_items/toggle_collapse` 7 个 action；GET list_pins 返回 v2 结构 |
| `index.php` | `loadPinned()` (2899) | 改为返回 `{groups, items}` 对象，缺省升级 |
| `index.php` | `savePinned()` (2900) | 同步写云端结构 |
| `index.php` | `renderConversationList()` (2694) | 顶部收藏区按分组循环渲染，每组包 `<div class="pin-group" data-gid>` |
| `index.php` | `appendConversationItem()` (2779) | 增加 `groupId` 参数 → 写到 `data-gid`；下拉菜单增加「移动到」子菜单 |
| `index.php` | `applySearchFilter()` (2978) | 跨分组过滤，命中数为 0 的组折叠而非隐藏 |
| `index.php` | 新增 | `createGroup/renameGroup/deleteGroup/moveConvToGroup/toggleGroupCollapse/reorderItems/attachDragHandlers` |
| CSS | 复用 `.sidebar-pin-title` | 新增 `.pin-group`、`.pin-group-header`、`.pin-group-items`、`.pin-group-drag-over`、`.pin-group-empty` |

### 2.6 实施节奏（建议）

| 阶段 | 工时 | 产出 |
|---|---|---|
| P0 数据迁移 | 0.5d | `pinned.json` 读写兼容 v1/v2；migration 函数 + 单测 |
| P1 后端 API | 0.5d | 7 个 action 全部接入，统一返回结构 |
| P2 前端分组渲染 | 1d | `renderConversationList` 改造，UI 视觉一致 |
| P3 拖拽 + 快捷键 | 0.5d | HTML5 Drag and Drop + 键盘支持 |
| P4 兼容与打磨 | 0.5d | 搜索跨组、空态、骨架屏、错误提示 |

---

## 三、其他优化建议（顺手提）

### 3.1 性能

1. **虚拟滚动**：会话 > 100 时，sidebar-list 改用虚拟列表（`IntersectionObserver` + 占位高度），避免 4k+ 条时 DOM 卡顿
2. **debounce 搜索**：当前 `oninput` 即时触发，建议 120ms debounce
3. **localStorage 兜底加密**：当前 `session_pins` 明文，可考虑 hash 后再写，避免设备共享时泄漏

### 3.2 可访问性（A11y）

1. 抽屉加 `role="navigation"` + `aria-label="历史会话"`
2. 分组标题用 `<button>` + `aria-expanded`，折叠状态可被屏幕阅读器播报
3. 收藏星标按钮 `aria-pressed="true|false"`
4. 焦点陷阱：抽屉打开时，键盘焦点锁定在内部；`Esc` 关闭
5. 颜色对比度：当前收藏色 `#f59e0b` 在白底仅 2.1:1，建议加深到 `#b45309` 或加底色

### 3.3 交互

1. **批量操作扩展**：当前批量删除只能在消息区，侧边栏项也支持多选（Shift+点击范围选、`Ctrl/Cmd+A` 全选）
2. **撤销删除**：删除会话/分组后，5 秒内右下角 Toast「已删除 · 撤销」
3. **会话预览**：hover 长标题 1s 后浮出气泡显示完整标题 + 最近一条消息
4. **快捷键面板**：`?` 打开所有快捷键 cheat sheet
5. **PWA 离线**：已有 manifest.json，建议加 service worker，让侧边栏在弱网下也能浏览历史

### 3.4 视觉

1. **空状态**：当前收藏空时直接没分组，建议显示空态插画 + 「试试长按星标收藏重要会话」引导
2. **骨架屏**：首次加载列表时显示 5 条 skeleton，避免闪烁
3. **未读标记**：当前无未读计数；如有多端同步需求，可加蓝色小圆点
4. **主题适配**：当前 `body.dark-mode` 有覆盖，但深色下分组色饱和度过高，建议对 6 个预设色定义 dark variant
5. **收藏分组用 emoji 而非图标库**：减小 bundle，可直接用系统 emoji

### 3.5 数据

1. **导出/导入**：右上角菜单增加「导出全部会话（含分组）」→ JSON 文件；导入时去重合并
2. **回收站**：删除的会话进入 7 天回收站，可恢复（前端 + 后端双实现）
3. **跨设备合并**：用 `user_id`（当前 `phase7` 是写死的）→ 接入真实账号体系
4. **搜索增强**：除标题外，把消息正文也纳入搜索（已加载的 messages 缓存即可）

### 3.6 工程

1. **单文件过大**：3255 行 index.php 拆分为 `sidebar.js / chat.js / storage.js / theme.js` 多个 ES module，必要时上 Vite 打包
2. **类型化**：引入 JSDoc typedef 给 `Conversation/PinGroup/PinItem` 写明字段
3. **错误兜底**：`fetch` 失败时当前只 `console.warn`，应弹 Toast 或重试按钮
4. **缓存版本**：API 响应增加 `Cache-Control: no-cache` 已设置，但 `pinned.json` 应加 ETag，减少无效写
5. **测试**：核心逻辑（分组迁移、togglePin 乐观更新、搜索过滤）加 jsdom 单测

---

## 四、落地优先级建议

| 优先级 | 项 | 价值 |
|---|---|---|
| 🔴 高 | 收藏分组 P0-P3 | 解决「收藏多了找不到」核心痛点 |
| 🟠 中 | 拖拽 + 快捷键、撤销删除、骨架屏 | 显著提升操作效率 |
| 🟡 低 | 虚拟滚动、A11y、回收站、导出导入 | 锦上添花，规模化时再做 |

---

## 五、报告落盘地址

- 本地路径：`/var/www/html/share/chat-sidebar-group-plan-20260727.md`
- 在线访问：https://xybcloud.online/share/chat-sidebar-group-plan-20260727.md

---

**附：分析依据**
- 源码：`/var/www/html/chat/index.php` 行 912-940（侧边栏 DOM 模板）、行 2694-2876（渲染逻辑）、行 2905-2943（togglePin）
- API：`/var/www/html/chat/sessions_api.php` 行 49-110（GET）、行 115-142（pin POST）
- 页面抓取：`curl https://xybcloud.online/chat/` → 3168 行 HTML