一、背景:智能体对话的"失忆症"困扰
在使用智能体服务的过程中,我们遇到了一个令人困扰的问题:智能体没有历史会话记录功能。每次刷新页面或重新打开应用,之前与 AI 的对话就消失了,就像智能体患上了"失忆症"。
1.1 痛点分析
痛点 1:对话上下文丢失
用户在与智能体交流时,经常需要进行多轮对话。例如:
- 第一轮:"帮我分析一下企业信用风险评估的流程"
- 第二轮:"刚才提到的第三步能详细说明吗?"(❌ 刷新后无法继续)
- 第三轮:"这个方案在反欺诈场景下怎么应用?"(❌ 上下文全部丢失)
影响:用户需要重新描述问题背景,降低了交互效率,破坏了对话的连贯性。
痛点 2:知识检索效率低
在日常工作中,用户可能会反复查询相似的问题:
- 上周询问过的技术方案,本周需要再次查阅
- 与同事讨论时需要回顾之前与 AI 的对话内容
- 需要整理 AI 给出的多次建议进行对比
影响:没有历史记录意味着每次都要重新提问,浪费 AI 计算资源和用户时间。
痛点 3:用户体验不佳
对比主流的 AI 对话产品(ChatGPT、Claude、文心一言等),用户已经习惯了以下体验:
- 可以随时回顾历史对话
- 可以继续之前的话题
- 可以在多个设备间切换(云端同步)
影响:缺乏历史会话功能让产品的用户体验大打折扣,降低了用户粘性。
1.2 为什么历史会话如此重要?
从产品价值角度看,历史会话功能带来的价值不容忽视:
| 价值维度 | 具体体现 |
|---|---|
| 提升效率 | 快速回顾历史方案,无需重复提问 |
| 增强连贯性 | 支持多轮对话,深度交流 |
| 知识沉淀 | 历史对话成为个人知识库 |
| 优化决策 | 对比多次咨询结果,做出更好的决策 |
| 用户留存 | 符合用户使用习惯,提升满意度 |
二、技术选型:为什么选择前端 IndexedDB 方案?
面对这个痛点,我们有多种技术方案可选:
2.1 方案对比
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 后端数据库存储 | 数据可靠、支持多端同步 | 增加服务器压力、开发周期长、依赖后端服务 | 企业级应用、需要多端同步 |
| LocalStorage | 简单易用 | 容量限制(5-10MB)、无索引、性能差 | 简单配置存储 |
| SessionStorage | 页面级隔离 | 刷新即失效、无法持久化 | 临时数据 |
| IndexedDB(✅ 我们的选择) | 大容量、高性能、支持索引、离线可用 | API 相对复杂 | 大量结构化数据存储 |
2.2 为什么选择前端 IndexedDB 方案?
经过技术调研和业务分析,我们最终选择了基于 IndexedDB 的前端存储方案,主要基于以下考虑:
✅ 优势 1:极致的加载速度
typescript
// 传统后端方案:需要网络请求
用户点击历史对话 → 发送 HTTP 请求 → 等待服务器响应 → 数据返回 → 渲染
⏱️ 耗时:500ms - 2000ms(取决于网络状况)
// IndexedDB 方案:本地读取
用户点击历史对话 → 从本地数据库读取 → 渲染
⏱️ 耗时:10ms - 50ms(纯本地操作)
性能提升:10-200 倍!
✅ 优势 2:天然的用户数据隔离
typescript
// 数据按用户 ERP 存储
interface ConversationRecord {
erp: string; // 用户唯一标识
traceId: string;
historyMessages: HistoryMessage[];
// ...
}
// 查询时自动隔离
await conversationDB.getByErp(currentUserErp);
每个用户的数据存储在各自的浏览器中,天然实现了数据隔离,无需担心数据泄露或混淆。
✅ 优势 3:大幅降低后端压力
让我们用数据说话:
假设场景:
- 1000 个活跃用户
- 每人每天查看历史对话 10 次
- 每次加载 50 条历史消息
后端方案的资源消耗:
ini
每日数据库查询次数:1000 * 10 = 10,000 次/天
每次查询响应大小:~50KB
每日流量消耗:10,000 * 50KB ≈ 488 MB/天
数据库连接开销:高
服务器 CPU/内存占用:显著
IndexedDB 方案的资源消耗:
erlang
后端请求次数:仅在新对话时需要调用 SSE 接口
历史加载请求:0 次(完全本地化)
服务器压力:降低 90% 以上
后端成本:显著降低
成本对比:
| 维度 | 后端方案 | IndexedDB 方案 | 节省比例 |
|---|---|---|---|
| 数据库查询 | 10,000 次/天 | ~100 次/天(仅新对话) | 90% |
| 网络流量 | 488 MB/天 | ~10 MB/天 | 98% |
| 服务器负载 | 高 | 低 | 80%+ |
| 响应延迟 | 500-2000ms | 10-50ms | 95% |
✅ 优势 4:离线可用性
typescript
// 即使在网络断开的情况下
// 用户依然可以:
✅ 查看所有历史对话
✅ 浏览过往聊天记录
✅ 搜索历史内容(规划中)
这对于网络不稳定的场景特别有价值。
✅ 优势 5:敏捷的开发迭代
diff
后端方案开发周期:
- 数据库表设计:1天
- 后端接口开发:2天
- 前端开发:1天
- 接口联调测试:1天
- 测试优化:1天
总计:7天
IndexedDB 方案开发周期:
- 数据库封装:1天
- 前端开发集成:1天
- 测试优化:1天
总计:3天
而且无需协调后端资源,前端独立完成,迭代更加敏捷。
✅ 优势 6:强大的查询能力
typescript
// IndexedDB 支持多维度索引查询
- 按用户查询:getByErp(erp)
- 按模块查询:getByCode(code)
- 按会话查询:getByTraceId(traceId)
- 复合查询:getByErpAndCode(erp, code)
- 时间范围查询:按 createdAt 索引排序
通过合理的索引设计,查询性能可以媲美专业数据库。
三、技术实现:如何构建一个高性能的前端存储方案
3.1 整体架构
scss
┌────────────────────────────────────────────────────────┐
│ 用户界面层 │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ 历史会话列表 │ │ 消息展示区 │ │ 输入框 │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└─────────────┬──────────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────┐
│ 业务逻辑层 │
│ - 会话管理(创建、切换、加载) │
│ - 消息处理(发送、接收、保存) │
│ - 状态管理(React State + Zustand) │
└─────────────┬──────────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────┐
│ 数据存储层 │
│ ┌─────────────────┐ ┌────────────────────┐ │
│ │ IndexedDB │ │ SSE 实时通信 │ │
│ │ (本地持久化) │◄────────┤ (与AI交互) │ │
│ └─────────────────┘ └────────────────────┘ │
└────────────────────────────────────────────────────────┘
3.2 核心技术点
1. 数据库设计:索引优化
typescript
// 创建多维度索引,提升查询性能
objectStore.createIndex("traceId", "traceId", { unique: false });
objectStore.createIndex("erp", "erp", { unique: false });
objectStore.createIndex("code", "code", { unique: false });
objectStore.createIndex("erp_traceId", ["erp", "traceId"], { unique: false });
objectStore.createIndex("erp_code", ["erp", "code"], { unique: false });
索引策略:
- 单字段索引:用于简单查询(按用户、按会话)
- 复合索引:用于多条件查询(按用户+模块)
2. 流式对话 + 增量保存
typescript
// SSE 流式响应实时保存
const controller = airobotApi.chat(params, {
onMessage: async (responseAll, done) => {
// 实时更新 UI(流式显示)
setMessages(prev => prev.map(msg =>
msg.id === assistantMessageId
? { ...msg, keyword: responseAll }
: msg
));
// 响应完成后保存到本地数据库
if (done) {
await conversationDB.appendHistoryMessagesByTraceId(
TraceId,
[{
id: TraceId,
keyword: responseAll,
type: "assistant",
createdAt: Date.now()
}]
);
await loadHistoryChats(); // 刷新历史列表
}
}
});
设计亮点:
- ✅ 用户立即看到 AI 响应(流式体验)
- ✅ 响应完成后自动保存(无需手动操作)
- ✅ 异步保存不阻塞 UI(体验流畅)
3. 智能去重机制
typescript
async add(record: ConversationRecord): Promise<number> {
// 检查是否已存在相同的 traceId
const existing = await this.getByTraceId(record.traceId);
if (existing.length > 0) {
console.warn(`会话 ${record.traceId} 已存在,跳过添加`);
return existing[0].id!;
}
// 不存在才添加
return new Promise((resolve, reject) => {
const transaction = this.db!.transaction([this.storeName], "readwrite");
const request = transaction.objectStore(this.storeName).add({
...record,
createdAt: Date.now(),
updatedAt: Date.now(),
});
// ...
});
}
避免了重复数据,保证数据一致性。
4. 按日期分组展示
typescript
// 将历史会话按日期分组
const groupedChats = records.reduce((groups, record) => {
const date = new Date(record.createdAt!).toLocaleDateString("zh-CN", {
year: "numeric",
month: "2-digit",
day: "2-digit",
}).replace(/\//g, "/");
if (!groups[date]) {
groups[date] = [];
}
groups[date].push({
traceId: record.traceId,
name: record.keyword.slice(0, 20) +
(record.keyword.length > 20 ? "..." : ""),
active: record.traceId === currentTraceId,
createdAt: record.createdAt,
});
return groups;
}, {} as Record<string, HistoryChatItem[]>);
展示效果:
markdown
2024/12/19
- 如何进行企业信用评估?
- 反欺诈模型的核心指标
2024/12/18
- 个人征信报告解读
- 风险预警策略优化
用户可以快速定位到特定日期的对话。
3.3 性能优化策略
优化 1:延迟加载历史消息
typescript
// 历史列表只显示标题,点击时才加载完整消息
const handleHistoryChatClick = async (traceId: string) => {
setSearchParams({ traceId });
setCurrentTraceId(traceId);
// 此时才从 IndexedDB 加载完整的历史消息
const records = await conversationDB.getByTraceId(traceId);
if (records.length > 0) {
setMessages(records[0].historyMessages || []);
}
};
优势:避免一次性加载所有会话的所有消息,节省内存和渲染时间。
优化 2:连接管理
typescript
useEffect(() => {
// 组件卸载时自动关闭 SSE 连接
return () => {
if (sseControllerRef.current) {
sseControllerRef.current.close();
sseControllerRef.current = null;
}
};
}, []);
优势:防止内存泄漏,避免多个 SSE 连接同时存在。
优化 3:批量更新 UI
typescript
// 使用函数式更新,避免重复渲染
setMessages(prev => prev.map(msg =>
msg.id === assistantMessageId
? { ...msg, keyword: responseAll }
: msg
));
优势:React 的批处理机制确保多次状态更新只触发一次渲染。
四、实测数据:量化的性能提升
我们在真实业务场景中进行了对比测试:
测试环境
- 用户数量:100 人
- 会话数量:每人平均 20 个会话
- 消息数量:每个会话平均 10 条消息
- 测试设备:MacBook Pro 2021, Chrome 120
测试结果
| 操作场景 | 后端方案 | IndexedDB 方案 | 提升倍数 |
|---|---|---|---|
| 加载历史列表 | 856 ms | 23 ms | 37x |
| 打开历史对话 | 1240 ms | 18 ms | 69x |
| 搜索会话 | 1560 ms | 45 ms | 35x |
| 切换会话 | 720 ms | 12 ms | 60x |
| 离线访问 | ❌ 不可用 | ✅ 可用 | - |
用户体验指标
| 指标 | 后端方案 | IndexedDB 方案 | 改善幅度 |
|---|---|---|---|
| 首次加载时间 | 2.3s | 0.8s | 65% |
| 操作响应时间 | 1.2s | 0.03s | 97% |
| 离线可用性 | 0% | 100% | +100% |
| 后端 QPS | 2500/分钟 | 250/分钟 | -90% |
成本节约估算
假设:
- 后端服务器成本:$200/月(2核4G)
- 数据库成本:$150/月(基础实例)
- 带宽成本:$50/月
采用 IndexedDB 方案后:
- 后端负载降低 90%,可节省服务器资源
- 数据库查询减少 90%,可降级数据库实例
- 网络流量减少 98%
预计每月节省:$300-400
五、方案的不足与未来规划
5.1 当前局限性
虽然前端 IndexedDB 方案有诸多优势,但也存在一些局限:
| 局限性 | 影响 | 缓解方案 |
|---|---|---|
| 无法多端同步 | 更换设备或浏览器后数据丢失 | 规划云端同步功能(可选) |
| 数据备份困难 | 用户无法主动备份数据 | 提供数据导出功能 |
| 存储空间限制 | 浏览器有存储配额限制(通常 >50MB) | 实现自动清理老旧数据 |
| 隐私模式失效 | 隐私/无痕模式下数据不持久化 | 提示用户切换到普通模式 |
5.2 未来规划
阶段一:功能完善(Q4 2025)
- 对话搜索(全文检索)
- 对话导出(JSON/Markdown)
- 对话删除
- 对话重命名
阶段二:体验优化(Q1 2026)
- 虚拟滚动(支持数千条历史记录)
- 代码块语法高亮
- 消息复制和引用
- 消息反馈(点赞/点踩)
阶段三:高级功能(Q2 2026)
- 云端同步(可选,需后端支持)
- 数据加密存储
- 智能标签和分类
- 会话分享功能
六、总结:前端存储的最佳实践
通过这次实践,我们验证了在特定场景下,前端存储方案可以比传统后端方案更优秀。
核心要点回顾
✅ 选择 IndexedDB 的黄金场景:
- 数据属于单用户使用,无需多端同步
- 数据量适中(几千到几万条记录)
- 读取频率远高于写入频率
- 需要快速响应的用户体验
- 希望降低后端成本和复杂度
✅ 实现高质量方案的关键:
- 合理的索引设计(单字段 + 复合索引)
- 完善的错误处理和降级方案
- 良好的封装和抽象
- 持续的性能监控和优化
✅ 量化的收益:
- 性能提升:37-69 倍加载速度
- 成本节约:90% 后端压力降低
- 用户体验:97% 响应时间缩短
- 开发效率:3-4 天即可上线
技术启示
这个方案的成功告诉我们:
不要迷信"后端万能论"。在移动互联网和前端技术高速发展的今天,前端已经具备了处理复杂业务逻辑和数据存储的能力。合理利用浏览器提供的强大 API(IndexedDB、Service Worker、WebAssembly 等),可以构建出性能卓越、用户体验一流的应用。
前端工程师应该跳出"只做页面展示"的思维定式,主动思考:
- 哪些数据可以放在前端?
- 哪些计算可以在浏览器完成?
- 如何在前后端之间找到最佳平衡点?
写在最后
IndexedDB 方案不是银弹,但在合适的场景下,它是一个极具性价比的选择。希望这个案例能给大家带来启发,在面对类似问题时,多一个解决思路。
技术的本质是解决问题,而不是堆砌工具。选择最适合业务场景的方案,才是优秀工程师的特质。
参考资料:
如果你觉得这篇文章有帮助,欢迎分享给更多前端小伙伴! 💪