更新记录

1.0.2(2026-05-23) 下载此版本

1.0.1(2026-05-23)

  • 新增图片/视频消息发送能力,支持 sendSupportlyMedia
  • 新增附件鉴权访问 URL 生成能力,支持 getSupportlyAttachmentUrl
  • 支持远端图片展示和 uni.previewImage 大图预览。
  • 支持视频消息展示,可配合 <video controls> 播放。
  • 优化断线重连后的历史消息补拉说明。
  • 完善插件 readme.md,补充初始化、消息结构、媒体消息、平台配置和常见问题。

1.0.0(2026-05-08) 下载此版本

  • 首次发布 Supportly UniApp AI 客服 SDK
  • 支持基于 Cloudflare 的免费自部署客服方案
  • 支持 RAG 知识库智能问答
  • 支持 HTTP 消息发送、WebSocket 实时接收、历史同步和断线重连

平台兼容性

uni-app(5.07)

Vue2 Vue3 Chrome Safari app-vue app-nvue Android iOS 鸿蒙
微信小程序 支付宝小程序 抖音小程序 百度小程序 快手小程序 京东小程序 鸿蒙元服务 QQ小程序 飞书小程序 小红书小程序 快应用-华为 快应用-联盟

supportly-ai-chat

Supportly AI Chat 是一个用于 uni-app 的 Headless 客服聊天 SDK。插件只负责会话、消息、WebSocket、历史同步和图片/视频上传,不绑定任何 UI 框架;聊天页面、气泡样式和业务按钮由接入项目自己实现。

在线演示

后台 Demo:

https://supportly.comarket.dev/

默认账号:

邮箱:admin@example.com
密码:admin123

Web Chat Widget Demo:

https://supportly.comarket.dev/demo

Server API:

https://api.supportly.comarket.dev

功能

HTTP 初始化访客会话
HTTP 发送文本消息
HTTP 上传图片 / 视频消息
WebSocket 实时接收新消息
断线重连
重连后按 lastMessageId 补拉历史
本地 visitorId / session 持久化
message.id 去重
连接状态回调
消息回调
附件鉴权访问 URL 生成

依赖的后端接口

插件对接 Supportly Server API 的 Widget 接口:

POST /api/widget/conversations
POST /api/widget/conversations/:conversationId/messages
POST /api/widget/conversations/:conversationId/messages/media
GET  /api/widget/conversations/:conversationId/messages
GET  /api/widget/conversations/:conversationId/messages/:messageId/attachments/:index
GET  /api/widget/ws

媒体文件由 Server API 写入 Cloudflare R2,客户端读取附件时通过 visitor token 鉴权。

快速使用

import {
  initSupportly,
  connectSupportly,
  closeSupportly,
  sendSupportlyText,
  sendSupportlyMedia,
  getSupportlyAttachmentUrl,
  loadSupportlyMessages,
  onSupportlyMessage,
  onSupportlyStatus,
} from "@/uni_modules/supportly-ai-chat";

const removeMessageListener = onSupportlyMessage((message) => {
  console.log("message", message);
});

const removeStatusListener = onSupportlyStatus((status) => {
  console.log("status", status);
});

await initSupportly({
  apiBaseUrl: "https://api.example.com",
  wsBaseUrl: "wss://api.example.com",
  channelAccountId: "ch_xxx",
  visitorId: "user_123",
  pageUrl: "app://support",
  pageTitle: "在线客服",
});

connectSupportly();

await sendSupportlyText({
  content: "你好",
  clientMessageId: "local_001",
});

const messages = await loadSupportlyMessages(null);
console.log(messages);

closeSupportly();
removeMessageListener();
removeStatusListener();

初始化

await initSupportly({
  apiBaseUrl: "https://api.example.com",
  wsBaseUrl: "wss://api.example.com",
  channelAccountId: "ch_xxx",
  visitorId: "user_123",
  pageUrl: "app://support",
  pageTitle: "在线客服",
});

参数说明:

参数 必填 说明
apiBaseUrl Server API HTTP 地址,例如 https://api.example.com
wsBaseUrl Server API WebSocket 地址,例如 wss://api.example.com
channelAccountId Supportly 后台创建的 Web Chat 渠道 ID
visitorId 业务侧用户 ID。未传时 SDK 自动生成匿名 visitorId
pageUrl 当前页面 URL 或业务场景标识
pageTitle 当前页面标题

initSupportly 会创建或恢复访客会话,并把 visitorId 和 session 写入 uni storage

连接状态

const unsubscribe = onSupportlyStatus((status) => {
  console.log(status);
});

状态值:

idle
initializing
connecting
connected
reconnecting
disconnected
error

建议 UI 显示:

function formatStatus(status) {
  if (status === "connected") return "在线";
  if (status === "connecting") return "连接中";
  if (status === "reconnecting") return "重连中";
  if (status === "initializing") return "初始化";
  if (status === "disconnected") return "离线";
  if (status === "error") return "异常";
  return "待连接";
}

接收消息

const unsubscribe = onSupportlyMessage((message) => {
  messages.value.push(message);
});

SDK 内部会按 message.id 去重。WebSocket 断开重连后,会自动调用 loadSupportlyMessages(lastMessageId) 补拉断线期间的新消息。

消息结构:

{
  id: "msg_xxx",
  conversationId: "conv_xxx",
  direction: "inbound", // inbound | outbound | internal | system
  senderType: "customer", // customer | agent | ai | system
  messageType: "text", // text | image | video | file | audio | event
  content: "你好",
  attachments: [],
  status: "received", // received | sending | sent | failed
  createdAt: "2026-05-23T10:00:00.000Z"
}

附件结构:

{
  type: "image", // image | video | file | audio
  url: "本地临时 URL 或远端 URL",
  r2Key: "media/conv_xxx/msg_xxx/att_xxx/photo.jpg",
  fileName: "photo.jpg",
  mimeType: "image/jpeg",
  size: 123456
}

发送文本

await sendSupportlyText({
  content: "你好",
  clientMessageId: "local_001",
});

参数说明:

参数 必填 说明
content 文本内容,SDK 会 trim
clientMessageId 客户端消息 ID,用于幂等和替换本地乐观消息

返回值是服务端创建的 inbound message。

发送图片

uni.chooseImage({
  count: 1,
  sizeType: ["compressed"],
  sourceType: ["album", "camera"],
  success: async (result) => {
    const filePath = result.tempFilePaths[0];
    const message = await sendSupportlyMedia({
      filePath,
      content: "图片说明",
      clientMessageId: `local_${Date.now()}`,
      mimeType: "image/jpeg",
    });
    console.log(message);
  },
});

发送视频

uni.chooseVideo({
  sourceType: ["album", "camera"],
  compressed: true,
  success: async (result) => {
    const message = await sendSupportlyMedia({
      filePath: result.tempFilePath,
      content: "视频说明",
      clientMessageId: `local_${Date.now()}`,
      mimeType: "video/mp4",
    });
    console.log(message);
  },
});

sendSupportlyMedia 参数说明:

参数 必填 说明
filePath uni-app 临时文件路径
content 图片/视频说明
clientMessageId 客户端消息 ID
fileName 文件名;未传时 SDK 从 filePath 推断
mimeType MIME;未传时 SDK 从扩展名推断

后端当前支持:

图片:image/jpeg, image/png, image/gif, image/webp,最大 10 MB
视频:video/mp4, video/webm, video/quicktime,最大 50 MB

附件展示

服务端返回的附件可能没有公开 URL。展示远端附件时,用 getSupportlyAttachmentUrl(message, index) 生成带 visitor token 的鉴权 URL。

图片:

<image
  v-if="message.messageType === 'image'"
  :src="getSupportlyAttachmentUrl(message, 0)"
  mode="aspectFit"
  @click="previewImage(message, 0)"
/>

视频:

<video
  v-if="message.messageType === 'video'"
  :src="getSupportlyAttachmentUrl(message, 0)"
  controls
/>

点击图片查看大图:

function previewImage(message, index) {
  const url = getSupportlyAttachmentUrl(message, index);
  uni.previewImage({
    current: url,
    urls: [url],
  });
}

乐观消息建议

发送前可以先把本地消息插入列表:

const clientMessageId = `local_${Date.now()}`;

messages.value.push({
  id: clientMessageId,
  direction: "inbound",
  senderType: "customer",
  messageType: "text",
  content,
  attachments: [],
  status: "sending",
  createdAt: new Date().toISOString(),
});

try {
  const remoteMessage = await sendSupportlyText({ content, clientMessageId });
  replaceLocalMessage(clientMessageId, remoteMessage);
} catch (error) {
  markMessageFailed(clientMessageId);
}

图片/视频乐观消息的 attachments[0].url 可以使用本地 filePath,上传成功后用服务端消息替换。

API

initSupportly(config): Promise<session>
connectSupportly(): void
closeSupportly(): void
sendSupportlyText({ content, clientMessageId }): Promise<message>
sendSupportlyMedia({ filePath, content, clientMessageId, fileName, mimeType }): Promise<message>
loadSupportlyMessages(afterMessageId): Promise<message[]>
getSupportlyAttachmentUrl(message, attachmentIndex): string
onSupportlyMessage(callback): () => void
onSupportlyStatus(callback): () => void

平台配置

H5:

生产环境使用 https:// 和 wss://。
如果浏览器跨域访问 API,Server API 需要允许对应 Origin。

App:

使用 uni.request / uni.uploadFile / uni.connectSocket / uni storage。
图片视频选择由业务页面调用 uni.chooseImage / uni.chooseVideo。

小程序:

需要在小程序后台配置合法 request 域名、uploadFile 域名和 socket 域名。
apiBaseUrl 对应 request/uploadFile 域名。
wsBaseUrl 对应 socket 域名。

生命周期建议

页面加载:

await initSupportly(config);
connectSupportly();

页面卸载:

closeSupportly();
unsubscribeMessage();
unsubscribeStatus();

常见问题

为什么提示 Supportly is not initialized?

在调用 sendSupportlyTextsendSupportlyMedialoadSupportlyMessagesconnectSupportly 前,必须先调用并等待 initSupportly(config) 成功。

为什么 WebSocket 连不上?

检查:

wsBaseUrl 是否是 wss://
小程序 socket 域名是否合法
Server API /api/widget/ws 是否可访问
conversationId 和 visitorToken 是否有效

为什么图片或视频上传失败?

检查:

apiBaseUrl 是否正确
小程序 uploadFile 域名是否合法
filePath 是否存在
mimeType 是否在后端允许列表中
图片是否超过 10 MB
视频是否超过 50 MB
Server API 是否配置 MEDIA_BUCKET

为什么附件图片打不开?

远端附件需要 visitor token 鉴权,请使用:

getSupportlyAttachmentUrl(message, 0)

不要直接拼 R2 地址,R2 bucket 默认应保持私有。

为什么消息重复?

SDK 会按远端 message.id 去重。业务侧如果做乐观消息,需要在发送成功后用服务端返回的消息替换本地 clientMessageId 消息。

AI 为什么没有回复图片/视频?

当前图片/视频消息不会触发 AI 自动回复。AI 回复只处理文本内容。

交流反馈

QQ 交流群:1081883123

Supportly QQ 交流群二维码

隐私、权限声明

1. 本插件需要申请的系统权限列表:

2. 本插件采集的数据、发送的服务器地址、以及数据用途说明:

3. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率:

许可协议

MIT协议

暂无用户评论。