docs/technical/session-management.md
Last updated: 2026-02
本文档描述 Chatbox 的会话管理系统设计,涵盖数据模型、模块拆分、新会话机制、线程历史、消息分叉等核心功能。聚焦于产品设计与架构决策,具体实现请参阅源码 src/renderer/stores/session/。
Chatbox 的会话系统围绕四个核心实体构建:
会话是最顶层的容器,代表一次独立的对话上下文。关键属性包括:
id — 唯一标识符(UUID),新会话创建前使用临时 ID "new"name — 会话名称(支持自动生成)type — 会话类型:chat(对话)或 picture(图片生成)messages — 当前活跃线程的消息列表threads — 历史线程数组(归档的对话分支)threadName — 当前活跃线程的名称messageForksHash — 消息分叉索引(记录每条消息的分支信息)settings — 会话级模型设置(覆盖全局默认值)copilotId — 关联的 Copilot 预设消息是对话的基本单元,每条消息包含角色(user / assistant / system)、内容、时间戳等。消息通过 createMessage() 工厂函数创建,确保格式统一。
线程是会话内的对话分支。当用户在同一会话中开启新话题时,当前消息列表会被归档为一个 SessionThread(存入 session.threads),然后重新开始新的消息列表。每个线程包含:
id — 线程标识符name — 线程名称messages — 该线程的消息快照线程机制允许用户在同一会话中管理多个独立话题,避免上下文混乱。
分叉是消息级别的分支机制。当用户对某条消息重新生成回复或手动创建分支时,系统会在该消息位置创建一个分叉点,存储多个可选的后续消息。通过 messageForksHash 索引管理,用户可以在不同分支之间切换浏览。
关联决策:#10 — Session 模块拆分 详细方案:
docs/session-module-split-plan.md
原始的 sessionActions.ts 文件膨胀至 1799 行,承担了会话 CRUD、消息操作、线程管理、分叉逻辑、AI 生成编排、命名、导出等全部职责。文件过大导致代码可读性下降、维护困难、合并冲突频繁。
按领域职责将单一文件拆分为 11 个专注模块:
| 文件 | 职责 | 导出函数数 |
|---|---|---|
crud.ts | 会话生命周期 — 创建、切换、排序、删除 | 8 |
messages.ts | 消息 CRUD — 插入、修改、删除、提交用户消息 | 5 |
threads.ts | 线程管理 — 创建、切换、归档、压缩、提升为独立会话 | 9 |
forks.ts | 消息分叉 — 创建分支、切换分支、删除、展开 | 5 |
generation.ts | AI 生成编排 — 调用模型、构建上下文、流式响应 | 8 |
naming.ts | 自动命名 — 会话名称和线程名称的防抖生成 | 4 |
export.ts | 导出功能 — 将会话导出为文件 | 1 |
state.ts | 共享状态 — 命名防抖的 Map/Set | — |
types.ts | 内部类型 — MessageForkEntry、MessageLocation | — |
utils.ts | 共享工具 — 事件追踪、消息查找、错误处理 | 4 |
index.ts | 公共 API — 统一重导出全部 40+ 个函数 | 全部 |
generation.ts 导出 generate,messages.ts 导入它;反向不成立。_ 前缀标记的内部辅助函数(如 _generateName、_copySession)仅在模块内部使用。index.ts 作为唯一公共 API,外部模块统一从 stores/session 导入,无需关心内部文件结构。用户打开首页时,系统不立即创建持久化会话,而是使用临时 ID "new" 标识一个尚未持久化的会话状态。只有当用户发送第一条消息时,才真正创建会话并写入存储。
用户打开首页 → 临时会话 (id="new")
↓ 选择模型、知识库、Copilot
↓ 临时状态存储在 newSessionStateAtom
↓
用户发送消息 → 创建真正的会话 (id=UUID)
↓ 转移临时状态到新会话
↓ 清空 newSessionStateAtom
↓ 切换路由到新会话
newSessionStateAtom)与持久状态(sessionKnowledgeBaseMap)分开管理,互不干扰。线程系统为用户提供在同一会话中管理多个话题的能力,避免频繁创建新会话。
startNewThread / refreshContextAndCreateNewThread):将当前消息归档为历史线程,清空消息列表,开始新话题。switchThread):将当前上下文存入历史,恢复目标线程的消息和名称。compressAndCreateThread):对当前对话进行摘要压缩后归档,适用于长对话场景。moveThreadToConversations / moveCurrentThreadToConversations):将线程从当前会话中提取出来,创建为独立的顶级会话。editThread / removeThread / removeCurrentThread):修改线程名称或删除线程。线程切换时,系统执行一次"存-取"操作:
messages + threadName 快照存入 session.threadssession.threads 中取出目标线程的消息messages 和 threadName这确保了线程间的上下文完全隔离,切换不会丢失任何对话内容。
分叉机制让用户可以对同一条消息探索不同的回复方向,类似版本控制中的分支。
| 操作 | 函数 | 行为 |
|---|---|---|
| 创建分叉 | createNewFork | 在指定消息处创建新分支,复制当前消息到新分支 |
| 切换分叉 | switchFork | 在同一分叉点的不同分支间前后切换 |
| 删除分叉 | deleteFork | 移除当前分支,回退到相邻分支 |
| 展开分叉 | expandFork | 将所有分支内容平铺展开 |
| 定位消息 | findMessageLocation | 在根消息列表和线程消息中查找目标消息的位置 |
分叉信息通过 session.messageForksHash 索引存储,键为分叉点消息的 ID,值包含该位置所有分支的消息及当前活跃分支索引。内部使用 applyForkTransform 统一处理分叉变换,computeNextMessageForksHash 计算变换后的索引状态。
generation.ts 是系统中最复杂的模块(约 450 行),负责协调 AI 模型调用:
genMessageContext):收集当前消息、线程历史、系统提示词、知识库检索结果等,构建发送给模型的完整上下文。generate):调用 AI 模型生成回复,支持流式响应、工具调用、图片生成等多种模式。generateMore):在当前消息后继续生成新回复。generateMoreInNewFork / regenerateInNewFork):创建新分支后在分支中生成,保留原始回复。naming.ts 负责会话和线程的自动命名,采用防抖策略避免高频调用:
state.ts 中的 pendingNameGenerations(Map)和 activeNameGenerations(Set)管理待执行和正在执行的命名请求,避免重复调用。scheduleGenerateNameAndThreadName 同时生成会话名称和线程名称;scheduleGenerateThreadName 仅生成线程名称。export.ts 提供 exportSessionChat 函数,将会话内容(包括消息历史和元信息)导出为文件,方便用户备份或分享对话内容。
docs/session-module-split-plan.mddocs/new-session-mechanism.md./key-decisions.md(决策 #10、#11)src/renderer/stores/session/src/renderer/stores/session/index.ts