bm-aichat v0.3.0
一. 安装
npm i bm-aichat -S
or
yarn add bm-aichat
二. 使用方式
import aiChat from 'bm-aichat'
插件共提供 7 个方法
1. initConfig(options, framework)
初始化配置
(1) options
{
scene: 'baseline',
env: 'dev',
appEnv: 'web',
appid: '',
dataid: '',
service: {
wssService: '',
accountService: '',
articleService: '',
agiAppService:'',
loginApiUrl:'',
articleApiUrl:'',
robotStatusApiUrl:''
},
innerLogin: true,
getLocation: null,
extraDeal: {},
baseline: {
bankSitesStorage: '_BM_AI_BANKSITES',
},
mts: {
trafficLightOpen: true,
trafficLightPrecheck: null,
},
}
参数 | 类型 | 是否必填 | 默认值 | 描述 |
---|---|---|---|---|
scene | String | 否 | mts | 使用场景 |
env | String | 否 | dev | 环境变量 |
appEnv | String | 否 | web | 项目类型 |
appid | String | 是 | / | 产品 ID |
dataid | String | 是 | / | 数据 ID |
service | Object | 是 | / | 服务配置 |
service.wssService | String | 是 | / | WebSocket 服务端地址 |
service.accountService | String | 否 | / | 账号服务地址 |
service.articleService | String | 否 | / | 文章服务地址 |
service.agiAppService | String | 否 | / | appServ 地址 |
service.loginApiUrl | String | 否 | / | 登录接口详细地址 |
service.articleApiUrl | String | 否 | / | 文章信息接口详细地址 |
service.robotStatusApiUrl | String | 否 | / | 查询机器人在线状态接口详细地址 |
innerLogin | Boolean | 否 | true | 是否自动登录 |
getLocation | Function | 否 | / | 获取用户定位的方法 |
extraDeal | Object | 否 | / | 额外处理的消息类型 |
baseline | Object | 否 | / | 基线架构特殊配置 |
baseline.bankSitesStorage | String | 否 | _BM_AI_BANKSITES | 服务网点数据缓存字段名 |
mts | Object | 否 | / | mts 架构特殊配置 |
mts.trafficLightOpen | Boolean | 否 | true | 是否开启红绿灯机制 |
mts.trafficLightPrecheck | Function | 否 | / | 红绿灯机制前置校验 |
scene
使用场景,可选值:
- mts - MTS 架构(默认)
- baseline - 基线架构
当前业务架构:
env
环境变量,可选值:
- dev - 开发环境(默认)
- test - 测试环境
- pre - 体验环境
- prd - 生产环境
非生产环境下,service
中的服务地址失效,需要配置代理。
appEnv
项目类型,可选值:
- web - Web 端(默认)
- h5 - 移动端 H5
- weapp - 微信小程序 H5
appid
产品 ID
在 3.0 服务系统更新后,appid
格式为bmaxxxxxxxxxxxxxxxxxx
。
dataid
数据 ID
在 3.0 服务系统更新后,dataid
格式为bmdxxxxxxxxxxxxxxxxxx
。
service
相关接口服务地址配置
wssService
WebSocket 服务端地址(完整地址)
accountService
文章服务地址
articleService
文章服务地址
agiAppService
agiAppServ 地址
loginApiUrl
登录接口详细地址
articleApiUrl
文章信息接口详细地址
robotStatusApiUrl
查询机器人在线状态接口详细地址
非生产环境下的 Web 端或移动端 H5(appEnv === 'h5' || appEnv === 'web') && env !== 'prd'
,service
中的服务地址不生效,需要在项目中手动配置代理:
proxy: {
'/accservice': {
target: 'xxx',
changeOrigin: true,
},
'/matcharticle': {
target: 'xxx',
changeOrigin: true,
},
'/agiAppServ': {
target: 'xxx',
changeOrigin: true,
},
}
插件内使用到的请求接口:
接口 | 默认路由 | 所属服务 |
---|---|---|
登录接口 | /accservice/v2/userlogin | 账号服务 |
文章信息接口 | /matcharticle | 文章服务 |
查询机器人在线状态接口 | /agiAppServ/mts/getUpDownStatus | appServ |
生产环境打包后的完整路径为服务地址+默认路由
,可根据实际情况配置接口的详细地址,配置后以该详细地址为主。
innerLogin
是否自动登录
如项目内需要手动登录,则传递innerLogin
为false
,同时在拿到用户信息后,调用updateState
方法,将用户信息同步给插件:
import aiChat from 'bm-aichat'
aiChat.initConfig({
...,
innerLogin: false,
});
// 更新用户信息
aiChat.updateState({
userInfo:{
uid:'xxx',
token:'xxx',
uname:'xxx',
sessionCode:'xxx'
}
});
如项目内不需要手动登录,则传递innerLogin
为true
,插件会在创建 ws 连接之前自动调用用户登录接口,这种情况下,配置参数中的accountService
必填,插件会根据appEnv
生成接口参数:
import aiChat from 'bm-aichat'
aiChat.initConfig({
...,
innerLogin: true,
service:{
...,
accountService:'xxx'
}
});
getLocation
获取用户定位的方法
插件不内置定位方法,如有定位需求,请传递getLocation
,如果传了该参数,在首次调用openConnect
方法时,就会执行一次。同时,openConnect
返回一个Promise
,可以使用await
或then
确保在下一步操作之前,定位方法调用完成,定位失败的错误会在内部拦截。
getLocation
需返回Promise
,定位结果格式如下:
{
position: {
lat: '', // 纬度
lng: '', // 经度
},
address: {
nation: '', // 国家,例如:中国
province: '', // 省份,例如:浙江省
province_code: '', // 省份编码,例如:330000
city: '', // 城市,例如:杭州市
city_code: '', // 城市编码,例如:330100
district: '', // 区县,例如:滨江区
district_code: '', // 区县编码,例如:330108
township: '', // 街道,例如:西兴街道
township_code: '', // 街道编码,例如:330108001000
community: '', // 社区,例如:新州社区
community_code: '', // 社区编码,例如:330108001009
},
source: '', // 数据来源,如:高德地图、腾讯地图
}
extraDeal
额外处理的消息类型
如插件内有未处理的消息类型,或需要覆盖掉插件内的处理方式,可以通过extraDeal
参数,例如:
import aiChat from 'bm-aichat'
aiChat.initConfig({
...,
extraDeal:{
"text":(params)=>{}
}
});
baseline
基线架构特殊配置
bankSitesStorage
服务网点数据缓存字段名
用于基线架构网点信息消息的处理。
mts
MTS 架构特殊配置
trafficLightOpen
是否开启红绿灯机制,详见红绿灯机制
trafficLightPrecheck
红绿灯机制前置校验,详见红绿灯机制
(2) framework
项目代码使用的框架对象,如Taro
如不传递该参数,插件内使用到的 API 默认采用原生 API。
请确保传递的框架对象身上有以下 API:
- getStorageSync/setStorageSync
- login
- connectSocket
- request
Taro3 版本的Taro
对象中并没有挂载这些方法,而是通过 babel 插件处理的(issues),所以 Taro3 版本使用本插件时暂时使用如下方法:
import Taro from '@tarojs/taro';
const { getStorageSync, setStorageSync, login, connectSocket, request } = Taro;
aiChat.initConfig(
{...},
{ getStorageSync, setStorageSync, login, connectSocket, request },
);
// 或者
import { getStorageSync, setStorageSync, login, connectSocket, request } from '@tarojs/taro';
aiChat.initConfig(
{...},
{ getStorageSync, setStorageSync, login, connectSocket, request },
);
2. updateState(payload)
更新同步插件状态
(1) payload
{
userInfo:{
uid:'',
token:'',
uname:'',
sessionCode:''
},
isVisible:true,
addressComponent:{},
robotIds:[]
}
参数 | 类型 | 是否必填 | 描述 |
---|---|---|---|
userInfo | Object | 否 | 用户信息 |
isVisible | Boolean | 否 | 当前窗口是否可见 |
addressComponent | Object | 否 | 地理位置信息 |
chosedRobot | String | 否 | 当前选中的机器人 ID |
robotIds | Array | 否 | 机器人 ID 列表 |
userInfo
用户信息
包含uid
、token
、uname
、sessionCode
四个字段。
配合innerLogin
为false
时使用,同步用户信息给插件,用于权限校验等。
isVisible
当前窗口是否可见
这个参数主要针对 websocket 的断连重连,当浏览器窗口不可见时,由于浏览器的休眠机制,发送心跳消息的定时器可能会被暂停,导致服务端一分钟未收到心跳消息而断开连接,又因为插件内部的断连重连机制,又会重新创建连接,如此循环,当浏览器长时间处于后台时,会出现创建了多个 websocket 连接的情况。
一般情况下不需要特别处理,但如果需要让连接更可控,可以通过isVisible
,当isVisible
为false
时,websocket 断连后不会自动重连。
例如:
import aiChat from 'bm-aichat'
// 添加窗口可见监听函数
document.addEventListener('visibilitychange', handleVisibilitychange)
function handleVisibilitychange() {
if (!document.hidden) {
aiChat.updateState({ isVisible: true })
// 如果断连了,重新连接
if (断连) {
aiChat.openConnect()
}
} else {
aiChat.updateState({ isVisible: false })
}
}
addressComponent
地理位置信息
除了在配置项中传递getLocation
以获取地理位置信息,也可通过updateState
更新同步,地理位置信息格式同上。
chosedRobot
当前选中的机器人 ID(该参数仅在 MTS 架构中生效)
robotIds
机器人 ID 列表(该参数仅在 MTS 架构中生效)
chosedRobot
和robotIds
的更新需确保chosedRobot
属于robotIds
。例如:
import aiChat from 'bm-aichat'
// 首次同步时,必须同时更新chosedRobot和robotIds
aiChat.updateState({
chosedRobot: 'robot001',
robotIds: ['robot001', 'robot002', 'robot003'],
})
// 后续同步可只更新chosedRobot或robotIds,但要确保chosedRobot在robotIds之中
aiChat.updateState({
chosedRobot: 'robot002',
})
aiChat.updateState({
robotIds: ['robot004', 'robot002', 'robot003'],
})
// 以下会报错
aiChat.updateState({
chosedRobot: 'robot005', // robot005不在['robot004','robot002','robot003']之中
})
aiChat.updateState({
robotIds: ['robot004', 'robot005', 'robot003'], // robot002不在['robot004','robot005','robot003']之中
})
// 更改为
aiChat.updateState({
chosedRobot: 'robot005',
robotIds: ['robot004', 'robot005', 'robot003'],
})
3. openConnect()/closeConnect()
建立/关闭 websocket 连接
(1) 连接流程
(2) 说明
插件内部实现了 websocket 的异常断连重连
手动创建连接时将
isReconnect
置为true
,如果发生异常断连的情况,会在 2s 后重新建立连接。手动断开连接时将
isReconnect
置为false
,不会触发重连。
4. sendMessage(params)
向 websocket 服务端发送消息
(1) params
{
contents:{
type:'',
data:{
content:'',
stype:'',
status:1,
replyId:'',
meaning:'',
params:{},
}
},
robotId:''
}
参数 | 类型 | 是否必填 | 描述 |
---|---|---|---|
contents | Object | Array | 是 | 发送消息内容 |
type | String | 是 | 消息类型 |
data | Object | 是 | 消息主体 |
data.content | String | 否 | 用户发送内容 |
data.stype | String | 否 | 业务类型 |
data.status | Number | 否 | type 为表达通知 时,固定值为1 |
data.replyId | String | 否 | 回复 ID |
data.meaning | String | 否 | 程序行为含义 |
data.params | Object | 否 | 程序行为具体参数 |
robotId | String | 否 | 机器人 ID |
contents
发送消息内容
contents
可以是对象也可以是数组:
contents:[
{
type:'',
data:{...},
},{
type:'',
data:{...},
},
...
],
如果是数组,MTS 架构下会当做一条消息发送,基线架构则是发送多条消息。
type
消息类型,当前应用到的消息类型有:
- 文本
- M(程序行为)
- 客户端状态
- 应答确认
- 表达通知
data
消息主体
content
用户发送内容
type
为文本
时必填。stype
业务类型,可选值:
- app(移动端)
- pc(pc 端)
- 公众号
- human(虚拟人业务)
type
为客户端状态
时必填。status
固定值为
1
type
为表达通知
时必填。replyId
回复 ID,对应接收到消息中的
replyId
type
为表达通知
或应答确认
时必填。meaning
程序行为含义,如:接口调用、地址选择等
type
为M
时必填。params
程序行为具体参数
type
为M
时必填。
robotId
机器人 ID(该参数仅在 MTS 架构中生效)
若不填写robotId
,默认取当前状态下的chosedRobot
。
(2) 各类型示例
1) 文本
import aiChat from 'bm-aichat'
aiChat.sendMessage({
contents: {
type: '文本',
data: {
content: '你好',
},
},
})
2) M(程序行为)
import aiChat from 'bm-aichat'
aiChat.sendMessage({
contents: {
type: 'M',
data: {
meaning: '接口调用',
params: {
接口名称: '定位',
调用结果: '成功',
},
},
},
})
3) 客户端状态
import aiChat from 'bm-aichat'
aiChat.sendMessage({
contents: {
type: '客户端状态',
data: {
content: '用户上线',
stype: 'human',
},
},
robotId: 'robot001',
})
4) 应答确认
import aiChat from 'bm-aichat'
// 消息数据
const { data, robotId } = params
aiChat.sendMessage({
contents: {
type: '应答确认',
data: {
replyId: data.replyId,
},
},
robotId,
})
5) 表达通知
import aiChat from 'bm-aichat'
// 消息数据
const { data, robotId } = params
aiChat.sendMessage({
contents: {
type: '表达通知',
data: {
status: 1,
replyId: data.replyId,
},
},
robotId,
})
5. openListener(onMessage, onStatusChange, onRobotOnline)
开启监听
参数 | 类型 | 是否必填 | 描述 |
---|---|---|---|
onMessage | Function | 是 | 接收聊天消息的回调函数 |
onStatusChange | Function | 是 | 连接状态监听回调函数 |
onRobotOnline | Function | 否 | 机器人上线回调函数(MTS 架构) |
import aiChat from 'bm-aichat'
aiChat.openListener(
(msg) => {},
({ websocketIsOk, uid, token, uname, sessionCode }) => {},
({ robotId, isOnline }) => {},
)
(1) onMessage
接收聊天消息的回调函数
用于接收消息返回,msg
的基本格式如下:
{
msgType:'',
msgId:nanoid(),
orientation:'',
time:new Date().getTime(),
action:null,
finelly:null,
...
}
参数 | 类型 | 描述 |
---|---|---|
msgType | String | 消息类型 |
msgId | String | 消息 ID |
orientation | String | 消息定位 |
time | Timestamp | 消息回复时间 |
action | 不固定 | 插件处理后的数据 |
finelly | Function | 消息处理完成后的回调函数 |
msgType
消息类型,详见下文消息类型
msgId
消息 ID
采用NanoID
规则生成的唯一标识。
orientation
消息定位,可选值:
- l - 正常的聊天消息
- m - 需要特殊处理的聊天消息
time
消息回复时间
action
插件处理后的数据
不同msgType
处理后的action
类型有所区别。
finelly
消息处理完成后的回调函数
有时候项目在接收到插件传递的消息时,并非立刻处理,可能有一些异步操作,比如每隔一段时间将消息展示到页面上,而插件在消息处理完毕后还有其他操作。为确保流程的准确性,需要插件使用方在处理完消息后,执行msg.finelly()
。
目前只在 MTS 架构文本消息中使用到。
除了msgType、msgId、orientation、time、action、finelly字段,
msg
还会携带源消息数据。
(2) onStatusChange
连接状态监听回调函数
websocket 状态改变时触发该回调函数,同时会携带上用户信息。
(3) onRobotOnline
机器人上线回调函数
机器人上线后触发该回调函数,详见用户上线流程。
(4) 消息类型
当前插件内部集成的消息类型如下:
- 公共的消息类型:
消息类型 | 消息定位 | 源消息类型 | 描述 |
---|---|---|---|
maintain | m | 无 | 服务器维护 |
wordFilter | m | 无 | 触发敏感词 |
kickoff | m | 无 | 触发互踢 |
invalidToken | m | 无 | token 过期 |
loginError | m | 无 | 登录错误 |
- MTS 架构的消息类型:
消息类型 | 消息定位 | 源消息类型 | 描述 |
---|---|---|---|
text | l | 文本、text | 文本消息 |
article | l | 文章 | 文章消息 |
expression | m | 表情 | 表情消息,如”打招呼“等 |
mood | m | 情绪 | 情绪消息,如“生气”等 |
attitude | m | AI 对用户态度 | AI 对用户态度,包括“亲密”和“敌意” |
quickReply | m | AI 快捷设置 | AI 快捷设置,用于隐藏/显示快捷回复按钮 |
- 基线架构的消息类型:
消息类型 | 消息定位 | 源消息类型 | 描述 |
---|---|---|---|
everyoneAskingData | m | 大家都在问 | “大家都在问”数据 |
relatedCaseData | m | 相关案例 | “相关案例”数据 |
guessData | m | 猜你想问、guess-list | “猜你想问”数据 |
text | l | 文本、text、phone | 文本消息,一些特殊文本(拨打电话、名词解释、点击回复)已处理 |
list | l | 列表 | 列表消息 |
menus | l | 菜单消息、multi-options-reply | 菜单消息 |
downloadFile | l | 文书下载 | 文书下载消息 |
image | l | 图片 | 图片消息 |
video | l | 视频 | 视频消息 |
article | l | 文章推送 | 文章消息 |
siteInfo | l | 网点信息 | 网点消息 |
rich-text | l | rich-text | 富文本消息 |
cards | l | card-org | 卡片消息 |
(5) 聊天流程
(6) 说明
以下四种情况都会断开 websocket 连接,但处理又有区别:
token 过期
登录错误
token 过期和登录错误在断开连接后,会判断
innerLogin
,如果为true
,会自动重连;否则,会向外传递invalidToken
和loginError
类型消息。触发互踢
触发互踢在断开连接后,会向外传递
kickoff
类型消息。30s 未收到心跳
30s 未收到心跳在断开连接后,会自动重连。
插件内处理的错误码如下:
错误码 | 说明 | 触发服务 |
---|---|---|
1002 | 服务器维护 | appServ |
1004/7001 | token 过期 | 网关服务token 过期网关错误码为 7001,1004 错误码为 appServ 拦截处理后的错误码 |
8001 | 触发敏感词 | 敏感词服务 |
6. closeListener()
关闭监听
三. MTS 架构相关机制
1. 用户上线流程
在创建 websocket 连接之前,首先调用
updateState
更新当前选中机器人 IDchosedRobot
以及所有机器人 IDrobotIds
,用于初始化机器人状态以及开启 chosedRobot 改变监听。创建 websocket 连接,在接收到鉴权请求返回后,调用接口查询
chosedRobot
的在线状态。接口结果格式如下:{ robotId: 机器人ID, status: 机器人状态, timestamp: 机器人离线秒数, }
机器人状态:
1 - 机器人在线
2 - 机器人离线
- 3 - 机器人状态未知(机器人从未上线过)
若机器人在线,更新全局数据中的机器人在线状态。
若离线或未知,则向服务端发送“用户上线”信息,服务端会返回一条
class=打招呼
的文本消息,收到打招呼消息后,更新机器人在线状态。由于机器人下线需一定时间,若下线未完成就发送“用户上线”,MTS 不会返回打招呼消息,为确保流程准确,需要借助
timestamp
字段,只有离线超过10
秒才允许发送”用户上线“。所以在获取机器人状态为2
的情况下,还要判断timestamp
是否超过10
,如未超过,需等待10-timestamp
秒后发送“用户上线”。
2. 未表达机制(红绿灯机制)
为优化用户体验,增加未表达机制(红绿灯机制)
(1) 三种计时
1) 双方未表达
目的:
- 用于触发 AI 的主动表达。
逻辑:
开启条件:
首次进入页面,调用“机器人在线状态接口”,如果 robot 为“在线”状态,直接开启计时;
如果 robot 为"下线"或“未知”状态,需发送“用户上线”并收到打招呼后,再开启计时。
用户发送聊天消息后开启计时。
AI 回复(展示到界面上)后开启计时。
2) 用户未表达
目的:
- 用于确认用户是否已经离开。
逻辑:
开启条件:
首次进入页面,调用“机器人在线状态接口”,如果 robot 为“在线”状态,直接开启计时;
如果 robot 为"下线"或“未知”状态,需发送“用户上线”并收到打招呼后,再开启计时。
用户发送聊天消息后开启计时。
3) 用户未回应
目的:
- 用于追踪用户是否对 AI 的问题进行了及时的回应。
逻辑:
开启条件:
- 当 AI 回复类型为“自身疑问”或“自身祈使”时,开启该计时。
4) 综合
判断没有是的流程是因为若产生是的结果,则代表流程从左开始重新执行。
(2) 红绿灯机制流程
创建 websocket 连接并且收到鉴权请求返回后,调用接口查询当前选择机器人状态,如果“在线”,开启红绿灯计时;
如果“离线”或“未知”,则在机器人上线后,开启红绿灯计时。
当
chosedRobot
改变后,先去判断全局状态中的在线信息,如果“在线”,直接开启红绿灯计时;否则和上述流程一样去调用接口查询。当用户发送聊天消息并且发送成功后,重置红绿灯计时。
接收到聊天消息并且用户未表达没有超过 60s(如果用户未表达超过 60s,则不再重置计时,除非用户主动表达),重置双方未表达计时,同时,如果
class
是“自身祈使”或者“自身疑问”,开启用户未回应计时。
开启和重置只是表述上的不同,从代码逻辑上看其实是一样的。
(3) 项目使用
1) 基础使用
import aiChat from 'bm-aichat';
// trafficLightOpen控制红绿灯机制的开启/关闭,默认开启
aiChat.initConfig({
...,
mts: {
trafficLightOpen: true,
},
});
// 插件允许项目手动控制红绿灯计时
import { startTrafficLightTimer, pauseTrafficLightTimer, playTrafficLightTimer, stopTrafficLightTimer } from 'bm-aichat';
startTrafficLightTimer(); // 开启(重置)红绿灯计时
pauseTrafficLightTimer(); // 暂停红绿灯计时
playTrafficLightTimer(); // 继续红绿灯计时
stopTrafficLightTimer(); // 停止红绿灯计时
2) 前置校验
插件默认在机器人上线后,即开启红绿灯计时。但有些时候,我们希望在一定条件下才开启红绿灯计时,就可以使用该配置:
import aiChat from 'bm-aichat';
aiChat.initConfig({
...,
mts: {
trafficLightPrecheck:()=>{
if(条件成立){
return true
}else{
return false
}
}
},
});
插件会在每次开启红绿灯计时前,循环调用该方法,直至返回true
。
若使用该配置,为了避免阻塞,请确保条件会成立,即会有true
的结果返回。