@m2lan/ai-chat-widget
v0.1.6
Published
可嵌入的 AI 对话组件,适配 AgentX 后端,任何 Vue 3 项目一行引入即可使用
Readme
@m2lan/ai-chat-widget
开箱即用的 AI 聊天 Widget,适用于任何 Vue 3 项目。支持流式响应、实体链接、图片/文件上传、会话管理、暗黑模式、国际化、消息反馈、Emoji 选择器、拖拽上传、图片预览。
安装
npm install @m2lan/ai-chat-widget快速开始
<script setup>
import { AiChatWidget, createAgentxAdapter } from '@m2lan/ai-chat-widget';
import '@m2lan/ai-chat-widget/style.css';
const config = {
api: createAgentxAdapter({
baseUrl: '/dev-api',
getHeaders: () => ({ Authorization: 'Bearer ' + token })
}),
assistantName: 'ERP 助手',
quickActions: [
{ label: '新增客户', prompt: '帮我新增一个客户' }
]
};
</script>
<template>
<AiChatWidget :config="config" />
</template>也可以 headless 使用 — 只用 composables 不用 UI 组件,自己画界面。ChatApiAdapter 接口是唯一的 SPI,宿主实现这个接口就能对接任何后端。
ChatWidgetConfig 完整配置
| 配置 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| api | ChatApiAdapter | 必填 | API 适配器 |
| entityRoutes | Record<string, string> | — | 实体链接路由映射 |
| onEntityClick | (id, type) => void | — | 实体链接点击回调 |
| assistantName | string | 'AI 助手' | 助手名称 |
| assistantAvatar | string | — | 助手头像 URL |
| welcomeMessage | string | — | 欢迎语 |
| quickActions | QuickAction[] | — | 快捷操作按钮 |
| streaming | boolean | true | 是否启用流式响应 |
| storageKey | string \| false | 'agentx_sessions' | 会话持久化 key,false 禁用 |
| height | string | '600px' | 抽屉高度 |
| hideFab | boolean | false | 隐藏右下角悬浮按钮 |
| theme | 'light' \| 'dark' \| 'auto' | 'auto' | 主题模式 |
| locale | 'zh' \| 'en' | 'zh' | 语言 |
| 附件上传 | | | |
| enableImages | boolean | 自动检测 | 是否启用图片上传 |
| enableFiles | boolean | 自动检测 | 是否启用文件上传 |
| maxImages | number | 5 | 单次最多上传图片数 |
| maxFiles | number | 5 | 单次最多上传文件数 |
| maxFileSize | number | 52428800 (50MB) | 单个文件大小上限(字节) |
| acceptFileTypes | string | .pdf,.doc,.docx,... | 文件选择器 accept 属性 |
| maxMessageLength | number | 4000 | 消息最大字符数 |
| maxRetries | number | 2 | 网络请求失败自动重试次数 |
| 事件回调 | | | |
| onMessageSent | (msg: ChatMessage) => void | — | 用户发送消息后触发 |
| onMessageReceived | (msg: ChatMessage) => void | — | 收到 AI 回复后触发 |
| onError | (error: string) => void | — | 发生错误时触发 |
| onSessionChange | (sessionId: string \| null) => void | — | 切换/创建/删除会话时触发 |
| onOpen | () => void | — | 打开面板时触发 |
| onClose | () => void | — | 关闭面板时触发 |
| onFeedback | (messageId, feedback) => void | — | 用户点击反馈按钮时触发 |
功能详解
暗黑模式
支持三种模式:light(浅色)、dark(深色)、auto(跟随系统 prefers-color-scheme)。
const config = {
api: adapter,
theme: 'dark', // 或 'light'、'auto'
};所有样式通过 CSS 变量实现,宿主页面也可覆盖:
:root {
--acw-primary: #409eff;
--acw-bg: #fff;
--acw-text: #303133;
/* ...完整变量列表见 style.css */
}国际化 (i18n)
内置中文 (zh) 和英文 (en) 两套翻译。
const config = {
api: adapter,
locale: 'en', // 切换为英文
};消息反馈(点赞/点踩)
AI 回复的操作栏自动显示 👍👎 按钮。点击切换,再次点击取消。通过 onFeedback 回调收集用户满意度:
const config = {
api: adapter,
onFeedback: (messageId, feedback) => {
// feedback: 'up' | 'down'
analytics.track('message_feedback', { messageId, feedback });
},
};反馈数据存储在 ChatMessage.feedback 字段,随会话一起持久化。
事件回调系统
const config = {
api: adapter,
onMessageSent: (msg) => console.log('用户发送:', msg.content),
onMessageReceived: (msg) => console.log('AI 回复:', msg.content),
onError: (err) => console.error('错误:', err),
onSessionChange: (id) => console.log('切换会话:', id),
onOpen: () => console.log('面板打开'),
onClose: () => console.log('面板关闭'),
};拖拽上传
将文件拖拽到聊天区域即可上传。自动识别图片和文档类型,复用已有的上传逻辑。
Emoji 选择器
输入框工具栏左侧新增表情按钮,提供 4 个分类(表情、手势、物体、符号),点击插入到光标位置。
图片 Lightbox(预览)
点击消息中的图片弹出全屏预览,支持:
- 左右箭头切换
- 键盘 ESC 关闭
- 多图计数器显示
打字动画
流式接收 AI 回复时,消息末尾显示闪烁光标 ▊,接收完成后自动消失。
消息分页
当单个会话消息数超过 50 条时,自动启用分页,只渲染最近的消息。顶部显示"加载更早消息"按钮,点击加载更多。
附件上传
ChatInput 支持两种附件类型,各自独立开关:
- 📷 图片上传:支持点击上传和剪贴板粘贴,预览缩略图
- 📎 文件上传:支持 PDF、Word、Excel、PPT、TXT、CSV,显示文件名和大小
前提条件:适配器需实现 uploadFile 方法,否则上传按钮自动隐藏。
interface ChatApiAdapter {
uploadFile?(file: File): Promise<string>;
}使用示例
// 默认行为:有 uploadFile 就自动启用
const config = { api: createAgentxAdapter({ baseUrl: '/dev-api', getHeaders }) };
// 只开图片,关闭文件上传
const config = { api: adapter, enableImages: true, enableFiles: false };
// 自定义文件限制
const config = {
api: adapter,
maxFiles: 3,
maxFileSize: 10 * 1024 * 1024,
acceptFileTypes: '.pdf,.docx',
};适配器
AgentX 适配器(预置)
对接 AgentX 后端(agent-spring-boot-starter 端点):
import { createAgentxAdapter } from '@m2lan/ai-chat-widget';
const adapter = createAgentxAdapter({
baseUrl: '/dev-api',
getHeaders: () => ({ Authorization: 'Bearer ' + token }),
});Chat Service 适配器(预置)
对接通用 Chat API(/api/v1/chat 端点 + SSE):
import { createChatServiceAdapter } from '@m2lan/ai-chat-widget';
const adapter = createChatServiceAdapter({
baseUrl: '/api/v1',
getHeaders: () => ({ Authorization: 'Bearer ' + token }),
});自定义适配器
实现 ChatApiAdapter 接口即可对接任意后端:
import type { ChatApiAdapter } from '@m2lan/ai-chat-widget';
const adapter: ChatApiAdapter = {
async sendMessage(req) {
const res = await fetch('/api/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ message: req.message, sessionId: req.sessionId }),
signal: req.signal,
});
return res.json();
},
async sendMessageStream(req) {
return fetch('/api/chat/stream', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ message: req.message, sessionId: req.sessionId }),
signal: req.signal,
});
},
};Headless 使用
不用 UI 组件,只用 composables 自己画界面:
import { createSessionManager, useChat, useStream, renderMessageContent } from '@m2lan/ai-chat-widget';
const sm = createSessionManager({ storageKey: 'my_sessions' });
const chat = useChat({ api: adapter }, sm);
// 发送消息
await chat.send('你好');
// 当前会话
console.log(sm.activeSession.value?.messages);实体链接
AI 回复中的 [客户名称](entity:customer:123) 格式会自动渲染为可点击的实体标签。
配置路由映射:
const config = {
api: adapter,
entityRoutes: {
customer: '/system/customer/detail',
order: '/system/order/detail',
},
};或使用回调:
const config = {
api: adapter,
onEntityClick: (id, type) => router.push(`/${type}/${id}`),
};导出清单
| 导出 | 类型 | 说明 |
| --- | --- | --- |
| AiChatWidget | 组件 | 主组件(悬浮按钮 + 抽屉面板) |
| MessageBubble | 组件 | 消息气泡 |
| ChatInput | 组件 | 输入组件 |
| ErrorBoundary | 组件 | 错误边界 |
| EmojiPicker | 组件 | Emoji 选择器 |
| ImageLightbox | 组件 | 图片预览 |
| createSessionManager | 函数 | 会话管理 composable |
| useChat | 函数 | 聊天逻辑 composable |
| useStream | 函数 | SSE 流式解析 composable |
| useVirtualScroll | 函数 | 消息分页 composable |
| renderMessageContent | 函数 | Markdown 渲染 + XSS 防护 |
| sanitizeHtml | 函数 | HTML 消毒 |
| createEntityClickHandler | 函数 | 实体链接点击处理 |
| copyToClipboard | 函数 | 复制到剪贴板 |
| provideConfig / useConfig | 函数 | provide/inject 配置注入 |
| useT | 函数 | 国际化翻译函数 |
| createAgentxAdapter | 函数 | AgentX 适配器 |
| createChatServiceAdapter | 函数 | Chat Service 适配器 |
本地开发
# 构建 widget
cd ai-chat-widget && npm run build
# 在消费方 package.json 中引用本地路径
# "@m2lan/ai-chat-widget": "file:../ai-chat-widget"
# 重新安装
cd wl95-home-ui && npm install目录结构
ai-chat-widget/
├── src/
│ ├── adapters/
│ │ ├── agentx.ts # AgentX 后端预置适配器
│ │ ├── chat.ts # Chat Service 适配器
│ │ └── erp-agent.ts # ERP Agent 适配器
│ ├── components/
│ │ ├── AiChatWidget.vue # 主组件(悬浮按钮+抽屉+侧边栏)
│ │ ├── ChatInput.vue # 输入组件(图片/文件/Emoji)
│ │ ├── MessageBubble.vue # 消息气泡(反馈/打字动画/Lightbox)
│ │ ├── ErrorBoundary.vue # 错误边界
│ │ ├── EmojiPicker.vue # Emoji 选择器
│ │ └── ImageLightbox.vue # 图片预览
│ ├── composables/
│ │ ├── useChat.ts # 核心聊天逻辑(同步+流式+重试+中断)
│ │ ├── useEntityLink.ts # 实体链接渲染 + XSS 防护 + 代码块增强
│ │ ├── useSession.ts # 会话管理 + localStorage 持久化
│ │ ├── useStream.ts # SSE 流式解析(兼容多种后端格式)
│ │ └── useVirtualScroll.ts # 消息分页
│ ├── config.ts # provide/inject 配置注入
│ ├── i18n.ts # 国际化
│ ├── types.ts # 所有类型定义 + ChatApiAdapter SPI
│ ├── style.css # 全量样式(acw- 前缀,暗黑模式支持)
│ └── index.ts # 包导出入口
└── package.json