开发 AI 对话界面时,很多人一开始只是把用户问题和助理回答丢进一个数组,直接渲染。这个做法在原型阶段没问题,但一旦用户真正用起来,麻烦就开始了。
被忽视的'记忆'成本
最直观的坑:用户不小心刷新页面,长达几十轮的深度对话瞬间灰飞烟灭。这种体验在 Web 端是致命的。
更深层的问题出在上下文窗口上。大模型都有 Token 限制,如果前端只是无脑累加历史记录发给 API,迟早会撞上 context_length_exceeded。前端必须具备'上下文回溯'与'裁剪'的能力。
此外,现代 AI 应用通常支持多会话并行(类似 ChatGPT 左侧列表),如何高效索引、切换、存储多个会话的历史记录,对数据结构设计也有要求。
这不仅是存储问题,更是架构设计问题——我们需要在前端构建一套轻量但健壮的'记忆管理系统'。
分层存储与滑动窗口策略
解决方案可以从两个维度入手:存储介质和上下文窗口管理。
1. 存储介质:IndexedDB 优于 localStorage
虽然 localStorage 简单易用,但在 AI 对话场景下不太合适。对话数据增长快,单条消息可能包含大段代码或 Markdown 文本,localStorage 的 5MB 限制很快就捉襟见肘,而且同步操作容易阻塞 UI 线程。
IndexedDB 容量大(通常几百 MB 以上),支持异步操作,很适合存储非结构化的对话数据。我们可以设计一张 conversations 表,以 sessionId 为主键,存储整个对话树。
2. 上下文管理:滑动窗口与摘要回溯
前端不能把所有历史记录都塞给 API,需要实现一个滑动窗口机制:
- 窗口大小:设定一个阈值(如最近 10 轮对话)。
- 系统提示词保留:System Prompt 必须始终保留在上下文头部。
- 远期记忆裁剪:超过窗口期的对话,前端可以选择截断,或者调用单独的 API 生成摘要,将摘要作为一条新消息塞入上下文。
代码实现:构建前端记忆管理器
我们用 TypeScript 定义一个 HistoryManager 类,结合 IndexedDB(实际生产推荐用 Dexie.js 等库封装)和滑动窗口算法。
先明确数据结构:
// 定义单条消息结构
interface Message {
id: string;
role: 'user' | 'assistant' | 'system';
content: string;
timestamp: number;
}
// 定义会话结构
interface Conversation {
id: string; // 会话唯一标识
title: ;
: [];
: ;
: ;
}

