vue-advanced-chat 学习笔记

2024 年 05 月 28 日

最近在做 LLM 相关的 Web 应用,需要一个前端的聊天界面。简单研究了一下,大部分都不太适合。有的跟后端关联比较紧密,有的使用人数太少,有的使用到我不熟悉的框架,各种原因,不一而足。为了做项目,我先简单实现了一个前端界面,但临时赶鸭子上架,不管是功能上还是界面效果上,都难以让自己满意。于是又看了一圈之后,选了 vue-advanced-chat 这个组件。

特点

说起来也奇怪,这个组件目前 star 数有 1.6k,国内但却少有人提及。可能是官网写的太简单,缺乏文档的原因吧。

官方介绍其特点如下(简单翻译加注释):

  • 兼容所有 js 框架或无框架
  • 可定制化的实时聊天信息
  • 后端无关
  • 支持图像、视频、文件、语音消息和 emoji
  • 支持编辑消息和回复其他消息
  • 支持对用户进行标记,以及用 emoji 快捷方式建议
  • 对于 已查看/新的/已删除的/正在输入的/系统的 消息,有对应的 UI 元素
  • 多种文本格式 - 粗体/斜体/删除线/下划线/代码/多行
  • 在线/离线 用户状态
  • 灵活的选项和 slot
  • 浅色/深色主题模式
  • Firestore 范例
  • Typescript, PWA, Web 组件支持

具体样式可以参考其 Demo。对我来说,最重要的就是兼容性以及后端无关这两项了。这意味着我可以选熟悉的 js 框架,并且根据自己的需求实现后端。从实验角度出发,先做一个 playground 项目来熟悉一下这个组件。

注:前几天在微信上看到一个公众号介绍某个 rust 库,基本就是把官方的例子拿过来跑一下就结束了。对这种行为,我的感受是,太水了。所以今天碰巧看到 vue-advanced-chat 这个库,确实有用,开源但文档不多,用起来还有一点难度。正好拿来写一篇不那么水的笔记,也可以顺便充实一下我自己的公众号。

创建 Playground 项目

以下代码都可以在 CliffHan/vue-advanced-chat-playground (github.com) 查看。

先创建工程,随便选一个 js runtime。我最近喜欢用的是 bun(npm 或 yarn 或 pnpm 也都可以),安装使用方法不再赘述。

bun create vue # 创建 vue-advanced-chat-playground 工程,其他选项皆为默认值

然后将组件引入工程。

cd vue-advanced-chat-playground
bun add vue-advanced-chat

接下来需要修改编译选项和代码,即按照官方说明做如下修改(具体可以参考这个 commit):

  • 修改 vite.config.js 中的编译选项。
  • 用官方提供的例子代码替换 App.vue 中的代码。
  • 由于使用 Vue3,可以将对象直接传给组件,避免两次转换。

此时然后运行工程,就可以看到如下界面:

注意:此时组件虽然已经运行,但其中数据都是空的,点击按钮也没有反应,这是正常的!

组件的简单使用方法

整理完整的使用说明需要一些时间。这篇文章只打算让这个组件完成一些简单的动作,以观察并总结其一般性的使用方法。

这些简单动作包括:

  • 点击界面加号按钮,添加一个聊天室。
  • 点击聊天室进入,发送一条消息。

代码可以参考这个 commit

响应界面按钮操作

目前界面上的加号按钮,点击没有任何响应。先分析界面,可以看到按钮被包在 vac-box-search 里面。然后再去看组件源码,很容易就可以找到点击按钮时发生了什么。参考组件源码 src/lib/RoomsList/RoomsSearch/RoomsSearch.vue,相关 html 代码如下:

<div v-if="showAddRoom" class="vac-svg-button vac-add-icon" @click="$emit('add-room')">
    <slot name="add-icon">
        <svg-icon name="add" />
    </slot>
</div>

很明显,点击按钮时就是发送了一个 add-room 消息,只需要在父组件接收这个消息并处理就可以了。

添加聊天室

添加聊天室的本质就是修改控件的 rooms 属性,为其添加一个 room 就可以了。参考 js 代码如下:

addRoom() {
  console.log('addRoom()');
  let rooms = [...this.rooms];
  rooms.push({
    roomId: '1',
    roomName: 'Room 1',
    users: [
      {
        _id: '1234',
        username: 'John Doe',
      },
      {
        _id: '4321',
        username: 'John Snow',
      }
    ],
  });
  this.rooms = rooms;
},

fetchMessages({ room, options }) {
  console.log(`fetchMessages(), room=${JSON.stringify(room)}, options=${JSON.stringify(options)}`);
  this.messagesLoaded = false;
  // use timeout to imitate async server fetched data
  setTimeout(() => {
    this.messages = [];
    this.messagesLoaded = true;
  })
},

这里有几个注意事项:

  • addRoom() 函数中,this.rooms 是个代理对象,因此需要新生成一个对象并赋值。否则界面不会刷新。
  • rooms 改变后,控件会发出 fetch-message 消息,并将 messages-loaded 改为 false。此时需要接收此消息并正确处理。否则界面上会一直显示 loading spin,组件持续处于加载消息状态中。

添加聊天消息

在聊天界面中,按下发送键,会收到 send-message 消息,需要做出适当响应,即将收到的消息放到 messages 中。

sendMessage(payload) {
  console.log(`sendMessage(), payload=${JSON.stringify(payload)}`);
  const messages = [...this.messages];
  payload['_id'] = 1;
  payload['senderId'] = '1234';
  messages.push(payload);
  this.messages = messages;
}

注:输入时回车的操作,移动版和桌面版行为不同。前者是换行,后者是直接输出。参考组件源码 src/lib/Room/RoomFooter/RoomFooter.vue

关于组件使用方法的总结

很明显,该组件的运行逻辑很简单,遵循以下规则:

  • 修改组件的属性对象值,可以改变组件的显示。
  • 当组件接收到用户操作时,通过上报消息通知父组件。

实际开发中,只需要关注:

  • 组件的属性对象值的数据格式。
  • 组件上报消息的参数格式。

后续如果有需求的话,我将进一步整理此组件的完整使用方法。

Top