东营市城乡建设局网站,wordpress缓存优化,文创产品设计就业前景,做婚纱网站的目的express是基于node.js的一个web框架#xff0c;可以更加简洁的去创建一个后台服务#xff0c;由于项目的需要#xff0c;引入和typescript#xff0c;经过几天的努力实现了chatgpt文字流输出有道智云语音合成的结合#xff08;略有遗憾#xff09;#xff0c;下面我记载…express是基于node.js的一个web框架可以更加简洁的去创建一个后台服务由于项目的需要引入和typescript经过几天的努力实现了chatgpt文字流输出有道智云语音合成的结合略有遗憾下面我记载以下以供参考
后端实现 要出现chatgpt原生接口的流式效果也就是一个字一个字往外面蹦就得只能使用SSEevent-stream和Websocket其实采用轮询短轮询和长轮询也是可以的只是占用资源下面我先来介绍这记得交互方法 轮询是由客户端每隔一段时间向服务器发出HTTP请求服务端接收到请求后向客户端返回最新的数据。 客户端的轮询方式一般分为短轮询和长轮询。 短轮询:一般是由客户端每隔一段时间向服务器发起一次普通HTTP请求。服务端查询当前接口是否有数据更新若有数据更新则向客户端返回最新数据若无则提示客户端无数据更新。 优点比较简单通过定时器在固定的间隔里不断发送请求。 缺点多条请求并不是每条都是有用的会有很多无用请求占据服务器资源和宽带并且维护困难响应的结果没有顺寻因为是异步请求只适用与小型应用。 长轮询:一般是由客户端向服务器发出一个设置较长网络超时时间的HTTP请求并在Http连接超时前不主动断开连接带颗段超时或有数据返回后再次建立一个同样的Http请求重复以上过程。 优点无消息时不会频繁请求占用资源较少。 缺点服务器滞留信息会耗费资源返回信息顺序无法保证维护困难。 SSEevent-stream:SSEServer-Sent Events是一种单向通信协议其中服务器可以将消息推送到客户端。与轮询不同客户端只需发送一个请求服务器可以随时发送新消息。这种方法可以减少网络流量和服务器负载。 Websocket:WebSocket 是一种双向通信协议它允许服务器和客户端在连接打开的情况下实时通信。WebSocket 可以减少网络流量和服务器负载因为它不需要客户端发送大量的 HTTP 请求来获取新消息。
可以看出SSE和Websocket两种协议在实时通讯中起着很大作用下面介绍这两种协议在express的应用
SSE
import { Stream } from node:stream;
import api from ../ToolClass/base.js
async function sendTextBymodel1(req,res){const paramsres.data //获取前端传过来的数据其中包含一个属性Stream,要设置为trueconst {data}await api.postStream(/v1/chat/completions,params,{responseType:stream})res.send(readStream(data)) //这里进行对返回值的处理,可以在前端处理
}
function readStream(decoded) {let responselet decodedArray decoded.split(data: );let longstr ;decodedArray.forEach(decoded {try {decoded decoded.trim();if ( longstr ){JSON.parse(decoded);}else{decoded longstr decoded;longstr ;JSON.parse(decoded);}}catch ( e ){longstr decoded;decoded ;}if(decoded!){if(decoded.trim()[DONE]){return;}else{response JSON.parse(decoded).choices[0].delta.content ? JSON.parse(decoded).choices[0].delta.content : return response}}})return response
}
export {sendTextBymodel1} 返回的就是一个一个字符
前端通过fetch或者EventSource来进行接收对于普通的浏览器还是行的不过使用在uniapp中打包成安卓就不行了此时的解决方案就是Websocket:
下载包
npm i express-ws
注入使用
import expressWs from express-ws
import {sendTextBymodel1} from ./Controller/ChatAI.js
const appexpress()
expressWs(app)
app.ws(/chat,sendTextBymodel1)import { Stream } from node:stream;
import api from ../ToolClass/base.js
async function char(params,ws){ /* Stream */try {// speecher(有道词典API使有道词典API使有道词典API使有)const {data}await api.postStream(/v1/chat/completions,JSON.parse(params),{responseType:stream})data.on(data, async (dat){ await ws.send(dat.toString(utf8))})data.on(close,async () {await ws.close();});} catch (error) { ws.send({status:402,meaasge:Websocket服务出现错误})}
}function sendTextBymodel1(ws,res){// 使用 ws 的 send 方法向连接另一端的客户端发送数据// ws.send(connect to express server with WebSocket success)let flagfalsews.on(message,async (msg){char(msg,ws)})ws.on(close,(e){})
}
export {sendTextBymodel1}
前端实现
uni.connectSocket({url:ws://43.155.177.34:8085/chat,header: {content-type: application/json}
})
uni.onSocketOpen((res){uni.sendSocketMessage({data: param});
});
uni.onSocketError((res){console.log(WebSocket连接打开失败请检查);});
uni.onSocketMessage((res){this.readStream(res.data,_this, currentResLocation,chat); //与上面SSE的后端代码方法一样
})
关于有道智云语音合成API的代码如下
import axios from axios
import { generateUUID } from ./util.js
import { config } from dotenv;
import crypto from crypto
import id3 from node-id3
import fs from fs
config()
const setting{q:,appKey:,salt:,sign:,signType:v3,curtime:,voiceName:youxiaoqin,format:mp3
}
async function speecher(q:string){initData(q)const responseawait axios.post(https://openapi.youdao.com/ttsapi,setting,{headers:{Content-Type: application/x-www-form-urlencoded,},responseType: arraybuffer})let nameMath.floor(Date.now() / 1000)let outputFilePath public/name.mp3;try {fs.writeFileSync(outputFilePath,response.data,binary);// const tags id3.read(outputFilePath);// const durationInSeconds tags tags.duration ? tags.duration : 0;// console.log(durationInSeconds);} catch (error) {console.log(error);}return outputFilePath
}function calculateSHA256(input) {const hash crypto.createHash(sha256);hash.update(input);return hash.digest(hex); // 返回十六进制表示的哈希值
}function initData(q){setting.qBuffer.from(q, utf8).toString();setting.appKey应用keylet saltgenerateUUID()setting.saltsaltsetting.voiceNameyouxiaoqinsetting.curtimeMath.floor(Date.now() / 1000).toString()let inputgetInput(q)const hashedData calculateSHA256(应用keyinputsaltsetting.curtime应用秘钥);setting.signhashedDatasetting.signTypev3
}function getInput(q){if (q.length20) {return q}return q.slice(0, 10)q.lengthq.slice(-10)}
export default speecher
export function generateUUID() {let d new Date().getTime();let uuid xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx.replace(/[xy]/g, function(c) {let r (d Math.random() * 16) % 16 | 0;d Math.floor(d / 16);return (c x ? r : (r 0x3 | 0x8)).toString(16);});return uuid;
}
export const AI_HEAD_IMG_URLhttps://th.bing.com/th?idODL.3e2fbff4543f0d3632d34be6d02adc93w100h100c12pclfaf9f7o6dpr1.5pid13.1
其中有些变量可以不使用硬编码的形式express可以使用环境变量,使用dotenv包
整个demo做下来本想做成流式输出文字将文字流传给流式合成语言然后将语言传给前端达到实时对话但是网上找了一遍支持流式语音的API都是国外的谷歌、微软、亚马逊但是这些调用其API需要进行注册注册过程中需要用到国外信用卡悲痛国内支持的流式传输的有百度阿里的只是是百度和阿里的声音比较简单所以就没做了
本文参考了
短轮询和长轮询_长轮询和短轮询_白鲸ld的博客-CSDN博客