前言:我的最终目的是为了在QQ上集成一个AI机器人,因此在这里先实现一个简单的集成
先上效果图
总体还是很简单的,我在调用websock获取回复内容的基础上另外集成了一个事件总线,让我们在调用获取消息的时候能够更加方便快捷
工具代码如下:
import CryptoJS from 'crypto-js'
export function getWebsocketUrl(API_KEY: string, API_SECRET: string) {
return new Promise((resolve, reject) => {
var apiKey = API_KEY
var apiSecret = API_SECRET
var url = 'wss://spark-api.xf-yun.com/v1.1/chat'
var host = location.host
var date = new Date().toGMTString()
var algorithm = 'hmac-sha256'
var headers = 'host date request-line'
var signatureOrigin = `host: ${host}\ndate: ${date}\nGET /v1.1/chat HTTP/1.1`
var signatureSha = CryptoJS.HmacSHA256(signatureOrigin, apiSecret)
var signature = CryptoJS.enc.Base64.stringify(signatureSha)
var authorizationOrigin = `api_key="${apiKey}", algorithm="${algorithm}", headers="${headers}", signature="${signature}"`
var authorization = btoa(authorizationOrigin)
url = `${url}?authorization=${authorization}&date=${date}&host=${host}`
resolve(url)
})
}
export default class TTSRecorder {
appId: string;
apiKey: string;
apiSecret: string;
status: string;
onWillStatusChange: any;
ttsWS: any;
content: string;
revertText: string;
_events: any[];
constructor(appId: string, API_KEY: string, API_SECRET: string) {
this.appId = appId
this.apiKey = API_KEY
this.apiSecret = API_SECRET
this._events = [];
this.status = 'init'
}
// 修改状态
setStatus(status: string) {
this.onWillStatusChange && this.onWillStatusChange(this.status, status)
this.status = status
}
// 连接websocket
connectWebSocket() {
this.setStatus('ttsing')
return getWebsocketUrl(this.apiKey, this.apiSecret).then(url => {
let ttsWS
if ('WebSocket' in window) {
ttsWS = new WebSocket(url as string)
} else if ('MozWebSocket' in window) {
ttsWS = new MozWebSocket(url)
} else {
alert('浏览器不支持WebSocket')
return
}
this.ttsWS = ttsWS
ttsWS.onopen = e => {
this.webSocketSend()
}
ttsWS.onmessage = e => {
this.result(e.data)
}
ttsWS.onerror = e => {
clearTimeout(this.playTimeout)
this.setStatus('error')
alert('WebSocket报错,请f12查看详情')
console.error(`详情查看:${encodeURI(url.replace('wss:', 'https:'))}`)
}
ttsWS.onclose = e => {
console.log(e)
}
})
}
// websocket发送数据
webSocketSend() {
var params = {
"header": {
"app_id": this.appId,
"uid": "fd3f47e4-d"
},
"parameter": {
"chat": {
"domain": "general",
"temperature": 0.5,
"max_tokens": 1024
}
},
"payload": {
"message": {
"text": [
{
"role": "user",
"content": this.content
}
]
}
}
}
console.log(JSON.stringify(params))
this.ttsWS.send(JSON.stringify(params))
}
start(text: string) {
this.revertText = ""; // 请空回答历史
this.content = text
this.connectWebSocket()
}
// websocket接收数据的处理
result(resultData: string) {
let jsonData = JSON.parse(resultData)
// 提问失败
if (jsonData.header.code !== 0) {
const data = {
code: jsonData.header.code,
content: jsonData.header.message
}
this.emit('error', data)
return
}
if (jsonData.header.code === 0 && jsonData.header.status === 2) {
this.ttsWS.close()
this.setStatus("init")
this.emit('message', {
content: this.revertText,
code: 0
})
this.emit('endRecord', {
content: this.revertText,
code: 0
})
}
// 记录回答
const textArr = jsonData.payload.choices.text && jsonData.payload.choices.text.map(item => item.content) || []
this.revertText = this.revertText + textArr.join('')
}
on(event: string, fn: Function) {
if (Array.isArray(event)) {
for (let i = 0, l = event.length; i < l; i++) {
this.on(event[i], fn)
}
} else {
// 存在直接push, 不存在创建为空数组再push
(this._events[event] || (this._events[event] = [])).push(fn)
}
}
once(event: string, fn: Function) {
let _self = this;
function handler() {
_self.off(event, handler);
fn.apply(null, arguments);//emit里面调用时会给on方法传参
}
handler.fn = fn;//off里面根据这个判断销毁事件
this.on(event, handler);
}
off(event: string, fn: Function) {
//不传参数表示清空所有
if (!arguments.length) {
this._events = [];
}
//数组循环清空
if (Array.isArray(event)) {
for (let i = 0, l = event.length; i < l; i++) {
this.off(event[i], fn)
}
}
const cbs = this._events[event];
if (!cbs) {
return;
}
//不传第二参表示清空某事件所有监听函数
if (arguments.length == 1) {
this._events[event] = null
}
let cb, i = cbs.length
while (i--) {
cb = cbs[i]
if (cb === fn || cb.fn === fn) { //cb.fn===fn用来移除once注册的事件
cbs.splice(i, 1)
break
}
}
}
emit(event: string, ...args: any[]) {
console.log(args, typeof args)
// 不存在event,直接返回
if (!this._events[event]) {
return
}
let cbs = [...this._events[event]];
if (cbs) {
for (let i = 0, l = cbs.length; i < l; i++) {
try {
cbs[i].apply(null, [...arguments].slice(1))
} catch (e) {
new Error(`event handler for "${e}"`)
}
}
}
}
}
不管你是想一次性接收到所有的内容,还是想像官方一样一点一点的接收,都能很方便的使用,视图调用代码如下:
const xfConfig = reactive({
appid: "",
apisecret: "",
apikey: "",
});
function testXfSend() {
if (!sendTest.content) {
ElNotification.error({
title: "请输入发送内容",
});
return;
}
const XfBot = new XfUtil(xfConfig.appid, xfConfig.apikey, xfConfig.apisecret);
sendTest.revert = "";
sendTest.loading = true;
XfBot.start(sendTest.content);
XfBot.on("endRecord", (data) => {
console.log("回复内容", data.content);
sendTest.loading = false;
sendTest.revert = data.content;
});
// XfBot.on("message", (data) => {});
XfBot.on("error", (data) => {
sendTest.loading = false;
ElNotification.error({
title: data.content,
});
});
}