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
。
关于组件使用方法的总结
很明显,该组件的运行逻辑很简单,遵循以下规则:
- 修改组件的属性对象值,可以改变组件的显示。
- 当组件接收到用户操作时,通过上报消息通知父组件。
实际开发中,只需要关注:
- 组件的属性对象值的数据格式。
- 组件上报消息的参数格式。
后续如果有需求的话,我将进一步整理此组件的完整使用方法。