先说结论:把一长条 AI 对话消息变成清爽的时间线,核心就两步------按会话(session)分组 、再按日期(今天/昨天/更早)打标签 。数据层用 reduce 归并,渲染层做粘性日期头,不用引第三方库。下面是我前两周真踩过的活儿,代码能直接抄。
起因
我在给一个自己搭的 AI 小助手做 web 端聊天界面。后端把消息一股脑按时间正序吐回来,前端就一条接一条往下堆。测了两天,消息攒到三百多条的时候,整个面板糊成一坨------你根本分不清哪句是今早问的,哪句是上周那次会话留下的。产品同事(其实就是我自己兼)看了一眼说:太脏了。
行吧。
第一步:数据先分好组,别在 JSX 里硬塞
我见过有人直接在模板里 if (上一条日期 !== 这条日期) 插分隔线,能跑,但乱。先在数据层把扁平数组捏成分组结构。
scss
// 假设每条 msg: { id, sessionId, content, role, createdAt }
function groupBySession(messages) {
const map = new Map();
for (const m of messages) {
if (!map.has(m.sessionId)) map.set(m.sessionId, []);
map.get(m.sessionId).push(m);
}
// Map 保插入顺序,会话天然按首次出现排好
return [...map.entries()].map(([sessionId, list]) => ({
sessionId,
list,
startAt: list[0].createdAt,
}));
}
会话内再不用动,组与组之间按 startAt 倒序就是最近的会话在最上面。
第二步:日期标签,别去算"几天前"
这步是我栽过的坑。一开始我吭哧吭哧写 Math.floor((now - t) / 86400000) 去算差几天,结果跨午夜就错------晚上23:50和凌晨0:10明明隔了20分钟,算出来"0天",可它俩分属两天,得分到两组。
正解是比日历日,不是比时间差:
ini
function dayLabel(ts) {
const d = new Date(ts);
const today = new Date();
const ymd = x => x.getFullYear() * 10000 + (x.getMonth() + 1) * 100 + x.getDate();
const diff = ymd(today) - ymd(d);
if (diff === 0) return '今天';
if (diff === 1) return '昨天';
if (diff < 7) return d.toLocaleDateString('zh-CN', { weekday: 'long' }); // 周三
return `${d.getMonth() + 1}月${d.getDate()}日`;
}
把日期戳压成 年*10000+月*100+日 这个整数再相减,天数差就稳了,不受具体几点几分干扰。
第三步:粘性日期头,滚动时吸顶
时间线的"清爽感"一大半来自滚动时那个日期标签会黏在顶上。纯 CSS 就够:
css
.day-label {
position: sticky;
top: 0;
z-index: 2;
/* 别忘了背景色,不然下面的消息会透上来糊一起 */
background: #f7f8fa;
}
background 那行我忘了写,调了十分钟以为是 z-index 的事,结果是文字和下面气泡叠透了。记一下。
一点真实的取舍
会话分组我没做"自动归并相近时间的消息"那种智能合并------比如隔了2小时的两次提问算不算一个会话。试过,阈值定多少都有人觉得不对,干脆老老实实信后端给的 sessionId,前端只管展示。少做一点反而稳。
另外说句实话,难看的不是布局,是内容本身。我那个 AI 小助手第一版回答又干又短,时间线做得再漂亮,点开每条都是两行废话也白搭。后来我给它配了点私有资料喂进去(RAG 那套),回答才有了血肉,时间线翻起来才像回事。
对了,搭这个小助手我基本没写后端------拖一拖配一配,挂个现成模型、传几份文档当知识库,发布成一个 API,前端直接调。我一个偏前端的人,零代码就把"智能体"那部分糊出来了,省下的精力全砸在这个时间线交互上了。
收尾
分组的活儿听着小,做到"一眼就清爽"挺费心思的------比 ymd 那个整数 trick 还琐碎的细节有七八处。但用户滑动时那个日期标签稳稳吸在顶上的瞬间,值。
你们做聊天记录的日期分组,是信后端 sessionId 还是前端自己按时间切?评论区聊聊,我那套阈值方案就是被同行劝退的。
(背后那个智能体我挂的是讯飞的 MaaS,现成大模型直接调 API,没自己部署算力,省心。)