ChatMessageList 消息列表组件:基于 BubbleList 搭建 AI 对话展示区

ChatMessageList 消息列表组件:基于 BubbleList 搭建 AI 对话展示区

前言

在 AI 工作台里,中间区域通常是用户停留时间最长的地方。

它承担的是最核心的对话体验:

text 复制代码
用户输入需求
AI 流式回复
消息自动向下滚动
不同角色展示不同气泡样式

在当前项目中,这一块被拆成了 ChatMessageList 组件。

它不负责发送请求,也不负责处理 SSE 流式接口,而是专注于一件事:

把当前会话里的消息,以接近真实 AI 产品的方式展示出来。

这篇文章主要介绍:

  • ChatMessageList 在工作台里的位置;
  • 它的整体结构;
  • 使用了哪些组件;
  • 如何区分用户消息和 AI 消息;
  • 当前实现了哪些展示效果。

这里不会过多展开 BubbleList 的源码细节。后面会单独写一篇文章,分析 BubbleList、typing、autoScroll 这些关键能力的内部实现。

组件位置

当前组件文件位于:

text 复制代码
src/features/workbench/components/chat/ChatMessageList.vue

它被 ChatWorkspace 使用:

text 复制代码
src/features/workbench/components/chat/ChatWorkspace.vue

整体页面结构大概是:

text 复制代码
WorkbenchPage
  ├── HistoryPanel       左侧历史对话
  ├── ChatWorkspace      中间聊天工作区
  │   ├── ChatHeader     当前会话标题
  │   ├── ChatMessageList 消息列表
  │   └── ChatComposer   底部输入框
  └── InspectorPanel     右侧辅助面板

ChatMessageList 处在 ChatWorkspace 中间,是整个工作台的消息展示区域。

当前组件做什么

ChatMessageList 当前主要做四件事:

text 复制代码
渲染当前会话的消息列表
区分用户消息和 AI 消息
展示用户头像和 AI 头像
给 AI 消息增加顶部信息和底部操作按钮

它接收的核心数据是:

ts 复制代码
conversation.messages

每一条消息大概长这样:

ts 复制代码
{
  key: 'xxx',
  role: 'assistant',
  content: '这里是 AI 回复内容',
  loading: false,
  typing: { step: 2, interval: 24 },
}

其中最重要的是:

字段 作用
key 消息唯一标识
role 消息角色,当前有 user 和 assistant
content 消息正文
loading 是否处于加载状态
typing 是否启用打字机效果

ChatMessageList 不关心消息从哪里来。

它只关心:

给我一个消息数组,我负责把它展示成对话列表。

使用 BubbleList 渲染消息

当前消息列表没有从零手写,而是使用了 Ant Design X Vue 提供的 BubbleList:

ts 复制代码
import { BubbleList } from 'ant-design-x-vue';

模板里核心结构是:

vue 复制代码
<BubbleList
  :items="conversation.messages"
  :roles="roles"
  :auto-scroll="true"
>
  ...
</BubbleList>

这里用到三个关键参数。

参数 作用
items 消息数据源
roles 不同角色对应的气泡配置
auto-scroll 消息变化时自动滚动到合适位置

在这个组件里,BubbleList 承担了消息列表的基础能力:

text 复制代码
消息排列
角色布局
气泡渲染
自动滚动
打字机内容更新

所以 ChatMessageList 本身不需要关心每条消息怎么布局,只需要把消息数据和角色配置传进去。

roles:区分用户和 AI

AI 对话组件最常见的需求,就是区分不同角色。

用户消息通常在右边,AI 消息通常在左边。

当前组件通过 roles 配置完成这件事:

ts 复制代码
const roles = {
  user: {
    placement: 'end',
    variant: 'filled',
    shape: 'round',
    avatar: {
      src: userAvatar,
      size: 32,
      shape: 'square',
    },
    classNames: { content: 'agent-message-user' },
  },
  assistant: {
    placement: 'start',
    variant: 'borderless',
    avatar: {
      src: aiAvatar,
      size: 32,
      shape: 'square',
    },
    classNames: { content: 'agent-message-ai' },
  },
};

这段配置可以理解成:

text 复制代码
role = user       使用用户气泡样式
role = assistant  使用 AI 气泡样式

用户消息:

text 复制代码
靠右展示
使用填充色气泡
展示用户头像

AI 消息:

text 复制代码
靠左展示
使用白色卡片气泡
展示 AI 头像

这样消息数据里只需要写:

ts 复制代码
role: 'user'

或者:

ts 复制代码
role: 'assistant'

页面就会自动使用不同的展示方式。

自定义头像

当前组件使用了两张本地图片作为头像:

ts 复制代码
import userAvatar from '@/assets/avatar/user.png';
import aiAvatar from '@/assets/avatar/ai.png';

然后在 roles 中配置:

ts 复制代码
avatar: {
  src: userAvatar,
  size: 32,
  shape: 'square',
}

这里的 avatar 使用的是组件内置头像能力。

也就是说,我们不需要自己额外写一个头像 DOM,只要传入头像配置,BubbleList 内部会帮我们把头像渲染到正确的位置。

当前效果大概是:

这种布局很接近常见 AI 聊天产品。

AI 消息顶部信息

除了头像和内容,当前组件还给 AI 消息增加了一行顶部信息:

vue 复制代码
<template #header="{ item }">
  <div v-if="item.role === 'assistant'" class="agent-message-meta">
    <span>Zero Code Agent</span>
  </div>
</template>

也就是说,只有 AI 消息会展示:

text 复制代码
Zero Code Agent

用户消息不会展示这行内容。

这样做的好处是:

text 复制代码
用户可以更清楚地知道这条消息来自当前 Agent

后续如果系统里有多个 Agent,也可以在这里展示不同的 Agent 名称。

比如:

text 复制代码
Zero Code Agent
Code Review Agent
Design Agent

当前只是先保留了一个简单的展示位。

AI 消息底部操作

当前组件还给 AI 消息增加了底部操作区:

vue 复制代码
<template #footer="{ item }">
  <div v-if="item.role === 'assistant' && !item.loading" class="agent-message-actions">
    <button type="button"><CopyOutlined />复制</button>
    <button type="button"><ReloadOutlined />重试</button>
    <button type="button"><LikeOutlined /></button>
    <button type="button"><DislikeOutlined /></button>
  </div>
</template>

展示效果大概是:

这几个按钮当前主要是 UI 展示:

操作 作用
复制 复制 AI 回复内容
重试 重新生成当前回复
点赞 反馈回复质量较好
点踩 反馈回复质量较差

后续可以继续接入真实逻辑。

比如:

text 复制代码
复制按钮接 Clipboard API
重试按钮重新发起当前消息请求
点赞点踩上报反馈数据

这里先把交互入口留出来。

自动滚动

消息列表开启了 auto-scroll:

vue 复制代码
<BubbleList
  :items="conversation.messages"
  :roles="roles"
  :auto-scroll="true"
>

它的作用是:

text 复制代码
当消息增加或 AI 回复持续变化时,消息列表自动滚动到合适的位置。

这对 AI 流式输出很重要。

因为 AI 回复不是一次性展示出来,而是不断追加内容:

text 复制代码
你
你好
你好,
你好,我
你好,我可以

如果没有自动滚动,用户可能需要手动向下滑,才能看到最新回复。

当前组件把这部分交给 BubbleList 自己处理。

组件外部只负责保证消息列表区域有明确高度:

css 复制代码
.chat-message-list {
  height: 100%;
  min-height: 0;
  overflow: hidden;
}

.chat-message-list :deep(.ant-bubble-list) {
  height: 100%;
  min-height: 0;
  box-sizing: border-box;
  padding: 22px 26px;
}

这里不是自己实现滚动逻辑,而是让 BubbleList 内部的滚动容器能够正常工作。

打字机效果

AI 回复的打字机效果不是在 ChatMessageList 里直接写动画实现的。

ChatMessageList 只负责把消息交给 BubbleList:

vue 复制代码
<BubbleList :items="conversation.messages" />

真正的打字机配置在消息对象上:

ts 复制代码
{
  role: 'assistant',
  content: '',
  typing: { step: 2, interval: 24 },
}

其中:

字段 作用
step 每次展示几个字符
interval 每次展示的间隔时间

配合 SSE 流式接口,最终效果就是:

text 复制代码
后端一段段返回内容
前端持续更新 message.content
BubbleList 根据 typing 做打字机展示

这也是比较常见的分层方式:

text 复制代码
SSE 负责数据传输
消息状态负责保存内容
BubbleList 负责视觉展示

当前页面效果

最终 ChatMessageList 实现的效果可以概括成:

text 复制代码
用户消息在右侧
AI 消息在左侧
双方使用不同头像
AI 消息带 Agent 名称
AI 消息底部有复制、重试、点赞、点踩操作
AI 回复支持流式展示和打字机效果
消息列表支持自动滚动

整体效果类似一个基础版 AI 对话窗口:

组件边界

当前 ChatMessageList 的边界很清晰。

它负责:

text 复制代码
展示消息
展示头像
展示气泡样式
展示 AI 消息操作区
承载 BubbleList 的 auto-scroll 和 typing 效果

它不负责:

text 复制代码
发送消息
创建应用
连接 SSE
保存历史记录
处理后端接口

这些逻辑放在更上层的 composable 中。

比如当前项目里,流式请求和消息更新主要在:

text 复制代码
src/features/workbench/composables/useWorkbenchChat.ts

这样拆分后,组件职责会比较干净:

text 复制代码
ChatMessageList 只关心展示
useWorkbenchChat 负责业务和状态
api 层负责接口请求

这也是我在这个工作台里比较想保持的结构。

小结

这篇文章简单介绍了 ChatMessageList 的整体实现。

它不是一个复杂的业务组件,而是一个典型的"展示型组件"。

核心思路是:

text 复制代码
用 BubbleList 承接消息列表能力
用 roles 区分用户和 AI
用 avatar 展示双方头像
用 header 展示 AI 名称
用 footer 放置消息操作
用 auto-scroll 和 typing 优化 AI 对话体验

目前这篇只介绍了组件结构和页面效果。

后续会深入结合源码分析各个部分如何实现,比如打字机等效果。

相关推荐
知了学历孙老师2 小时前
小自考的校考和统考,出题方和难度差在哪
java·大数据·前端
ShineWinsu4 小时前
对于JavaScript:操作元素的解析
开发语言·前端·javascript·面试·web·dom·元素
福兮说5 小时前
JSON 格式正确,数据就可靠吗?我用一份自编配置检查类型与空值
java·前端·json
程序员老赵7 小时前
Docker 部署青龙面板:轻松搭建定时任务管理平台
前端·docker·开源
Swot7 小时前
Asciidoctor 公式数字下标错位问题:原因分析与解决方案
前端
Swot7 小时前
leo-editor 中将 Markdown 内容转成 asciidoc 格式
前端
狒狒AI7 小时前
Remotion / GSAP+Three / HyperFrames / Skia 四栈实测:代码渲染知识视频的选栈方法(附开源技能)
前端·人工智能·aigc
修炼的dance7 小时前
浏览器里导出H.265,8.5秒和55.5秒差在硬件
前端
补码缩写7 小时前
WebCodecs选编码器,探测之后还要验解码和播放
前端
大文说跨境7 小时前
亚马逊多账号管理浏览器怎么选?云登/紫鸟/AdsPower 对比
java·前端·自动化