From 3cc7deb10dd85d85193a06ab84a485bee83b6734 Mon Sep 17 00:00:00 2001 From: "1054425342@qq.com" <1054425342@qq.com> Date: Mon, 15 Sep 2025 09:44:54 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=BF=E6=8D=A2agent?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/GPT/.DS_Store | Bin 8196 -> 8196 bytes components/GPT/index.vue | 25 ++- components/GPT/utils/.DS_Store | Bin 0 -> 6148 bytes components/GPT/utils/audio2.js | 247 +++++++++++++++++++++++++++++ components/GPT/utils/pcm-player.js | 37 ++++- components/GPT/utils/util.js | 13 ++ subPackages/other/ipPoster.vue | 15 +- 7 files changed, 320 insertions(+), 17 deletions(-) create mode 100644 components/GPT/utils/.DS_Store create mode 100644 components/GPT/utils/audio2.js diff --git a/components/GPT/.DS_Store b/components/GPT/.DS_Store index ec3037306b99d744f825e28d77f9e993f59ec6c9..199efde94b9fb9e0fe1dc7da68a8c1952e08995c 100644 GIT binary patch delta 52 zcmZp1XmQw}D!{m7vYJ34w?uWdk%h62f~mR1 import Socket from './utils/socket' import Audio from './utils/audio' - import PCMPlayer from './utils/pcm-player' -import { splitTextForTTS } from './utils/util' import { getHistroyMsg, setMsgData @@ -404,6 +402,8 @@ export default { } this.player && this.player.destroy() + // this.audioOBJ.emit('send', txt) + // return uni.request({ method: "post", data: { @@ -636,6 +636,25 @@ export default { this.recorderManager = null }, inputSend() { + // const params = new FormData(); + // params.append('tts_text', '欢迎使用语音合成系统') + // params.append('spk_id', '中文男') + // params.append('speed', '1.0') + // wx.uploadFile({ + // url: 'http://110.42.225.206:8100/inference_sft_file', + // method: 'POST', + // // data: params, + // header: { 'content-type': 'multipart/form-data' }, + // formData: { + // tts_text: '欢迎使用语音合成系统', + // spk_id: '中文男', + // speed: '1.0' + // }, + // success: (res) => { + // console.log(111111, res) + // }, + // }) + // return console.log('【inputSend------>】',); this.onSendQuestion(this.inputValue) this.scrollToBottom() @@ -767,7 +786,7 @@ export default { flex-direction: column; width: 100%; height: 100%; - min-height: 700px; + min-height: 400px; overflow: hidden; background: #f8f8f8; border-radius: 12px; diff --git a/components/GPT/utils/.DS_Store b/components/GPT/utils/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6 GIT binary patch literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0 { + console.log("-----开始请求token-----"); + uni.request({ + method: "GET", + dataType: "json", + // url: `http://192.168.124.118:8083/xcx/framework/agent/${this._options.agentId}`, + url: `https://des.js-dyyj.com/getDemoToken?id=${this._options.agentId}`, + success: (res) => { + console.log("请求token成功", res); + resolve(res); + }, + fail: (err) => { + wx.showToast({ + title: "创建失败", + icon: "none", + }); + console.log("请求token失败", err); + reject(); + }, + }); + }); + } + async init() { + return this.createSocket(); + } + async createSocket(options) { + console.log("开始创建socket", options); + return new Promise(async (resolve, reject) => { + const origin = `wss://nls-gateway-cn-beijing.aliyuncs.com/ws/v1?token=${this.token}`; + // 建立连接 + const socket = wx.connectSocket({ + url: `${origin}`, + binaryType: 'arraybuffer', + success: (e) => { + console.log("创建语音长链接成功", e); + }, + complete: (e) => { + console.log("socket - complete", e); + }, + }); + this.socket = socket; + + socket.onOpen((e) => { + console.log("socket.onOpen", e); + // 监听发送 + options && options.complete && options.complete(); + + const socketParams = { + header: { + name: 'StartSynthesis', + appkey: this.appkey, + message_id: '1c17d4f8e7894a20aecb9f774b54ba06', //generateRequestId1(), + task_id: '1c17d4f8e7894a20aecb9f774b54ba06',//generateRequestId1(), + namespace: 'FlowingSpeechSynthesizer' + }, + payload: { + voice: 'xiaoyun', + format: 'pcm', + sample_rate: 16000 + } + }; + this.send({ data: JSON.stringify(socketParams) }, 'txt'); + }); + + socket.onMessage((e) => { + const { data } = e; + + if (data instanceof ArrayBuffer) { + this._options.onMessage && this._options.onMessage(data); + } else { + if ( Object.prototype.toString.call(data) === "[object String]" ) { + const tmp = JSON.parse(data); + if (Object.prototype.toString.call(tmp.type) === "[object Number]") { + this._options.onMessage && this._options.onMessage(tmp); + } else { + if (!this.session_id) { + this.session_id = tmp.data + } + } + } + } + // this.on(type, e); + this.createInter(); + resolve(); + }); + // 失败 + socket.onError((e) => { + console.log("websocket error 创建语音长链接报错", e); + // + }); + // 关闭 + socket.onClose((e) => { + console.log("websocket close 语音长链接关闭", e); + this.connectSocketTimeOut && clearTimeout(this.connectSocketTimeOut); + //非自动关闭重连 + if (e.code != 1000) { + this.doConnectTimeout(); + } + }); + }); + } + + doConnectTimeout() { + if (this.selfCloseStatus){ + // 主动退出 不在重连 + return + } + // 重连一次 + console.log("websocket 异常关闭 开始重连"); + this.connectSocketTimeOut = setTimeout(() => { + this.createSocket({ + complete: function (res) { + // this.reconnectLock = false; + }, + }); + }, RECONNECT_TIME); + } + + send(e, t) { + console.log("开始请求 websocket send", e); + this.socket && this.socket.send(e); + } + + getMsgData(e) { + if (e && typeof e == "string") { + const status = e.indexOf("42"); + const index = e.indexOf("["); + if (status > -1 && index > -1) { + const txt = e.substring(index); + const item = JSON.parse(txt); + const [type, obj] = item; + const payload = obj.payload ? obj.payload : {}; + const params = { + chatId: this._options.agentId, + type, + contentType: "text", //默认文字 + timestamp: new Date().getTime(), + ...payload, + }; + // 缓存聊天记录 + return params; + } + } + return null; + } + + emit(type, text, contentType) { + console.log("emit", type, text, generateRequestId1()); + switch (type) { + case "send": + // 发送消息 + // 发送消息 + // const socketParams = { + // request_id: generateRequestId1() + // }; + const socketParams = { + header: { + name: 'RunSynthesis', + appkey: this.appkey, + namespace: 'FlowingSpeechSynthesizer', + message_id: '1c17d4f8e7894a20aecb9f774b54ba06', + task_id: '1c17d4f8e7894a20aecb9f774b54ba06', // generateRequestId1(), + }, + payload: { + text: text + } + // action: "ACTION_SYNTHESIS", + // data: text, + }; + this.send({ data: JSON.stringify(socketParams) }, contentType); + break; + } + } + // 监听 + on(type, params) { + switch (type) { + case "reply": + // 回复结束 + const tmpParams = { + ...params, + name: this.robotObj.name, + headImage: this.robotObj.headImage, + }; + if ( + params && + params.type === "reply" && + !params.is_from_self && + params.can_rating + ) { + // 回复消息 + console.log("reply回复内容", tmpParams.content, tmpParams); + this._options.onMessage && this._options.onMessage(tmpParams); + } + + if (!params.is_from_self && params.is_final) { + if (this.failMsg[tmpParams.request_id]) { + this.failMsg[tmpParams.request_id].status = true; + // 已回复 状态为true + } + + // 回复结束 写入缓存 + // setMsgData(tmpParams); + } + // 回复 + break; + } + } + + createInter() { + if (this.timeoutObj) { + clearTimeout(this.timeoutObj); + } + this.timeoutObj = setTimeout(() => { + console.log(111111, this.socket) + this.socket && this.socket.send && this.socket.send({ data: 3 }); + }, HEART_BEAT_TIME); + } + + // 关闭socket + destroy() { + if (this.socket && this.socket.readyState == 1) { + this.socket && this.socket.close(); + this.socket = null; + } + } +} diff --git a/components/GPT/utils/pcm-player.js b/components/GPT/utils/pcm-player.js index 5333158..d591f49 100644 --- a/components/GPT/utils/pcm-player.js +++ b/components/GPT/utils/pcm-player.js @@ -4,26 +4,55 @@ export default class PcmPlayer { this.audioCtx = null; this.rate = this._options.rate ?? 16000; this.ch = this._options.ch ?? 1; - this.samplesPerFrame = (16000 * 20) / 1000; + this.fadeMs = this._options.fadeMs ?? 20; + this.fadeSamples = (this.rate * this.fadeMs) / 1000; this.queue = []; this.isPlaying = false; this.startTime = 0; this._t = null; this.AllTexts = []; + this.totalLen = 0 } feed(data) { if (!this.audioCtx) { this.audioCtx = wx.createWebAudioContext(); } + this.queue.push(new Int16Array(data)); if (!this.isPlaying) this._play(); } + loadAudio = (url) => { + return new Promise((resolve) => { + wx.request({ + url, + responseType: "arraybuffer", + success: (res) => { + console.log("res.data", res.data); + audioCtx.decodeAudioData( + res.data, + (buffer) => { + resolve(buffer); + }, + (err) => { + console.error("decodeAudioData fail", err); + reject(); + } + ); + }, + fail: (res) => { + console.error("request fail", res); + reject(); + }, + }); + }); + }; + async _play() { this.isPlaying = true; if (!this.audioCtx) { - return + return; } // 开始播放 this.startTime = this.audioCtx.currentTime ?? 0; @@ -45,7 +74,7 @@ export default class PcmPlayer { } // 2. 帧边界淡入淡出(防不连续滋滋) - const fadeSamples = Math.min(80, len); // 5ms@16kHz + const fadeSamples = Math.min(this.fadeSamples, len); // 5ms@16kHz const data = buf.getChannelData(0); for (let j = 0; j < fadeSamples; j++) { const gain = j / fadeSamples; @@ -69,7 +98,7 @@ export default class PcmPlayer { syncLoop() { const loop = () => { if (!this.audioCtx) { - return + return; } const now = this.audioCtx.currentTime - this.startTime; // 找当前字幕 diff --git a/components/GPT/utils/util.js b/components/GPT/utils/util.js index cec7565..e7ba260 100644 --- a/components/GPT/utils/util.js +++ b/components/GPT/utils/util.js @@ -53,6 +53,19 @@ export const arrayUnique = (arr, replaceKey, holdKey) => { }, []); }; +export const generateRequestId1 = (length = 32) => { + const data = + ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', + 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', + 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', + 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']; + let nums = ''; + for (let i = 0; i < length; i++) { + const r = parseInt(Math.random() * 61, 10); + nums += data[r]; + } + return nums //+ '-' + parseInt(Math.random() * 10000000000, 10); +}; export const generateRequestId = (length = 10) => { const data = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', diff --git a/subPackages/other/ipPoster.vue b/subPackages/other/ipPoster.vue index 23b9f78..4dc99e0 100644 --- a/subPackages/other/ipPoster.vue +++ b/subPackages/other/ipPoster.vue @@ -14,9 +14,9 @@ - - - + + + @@ -79,14 +79,9 @@ } .action-box{ position: absolute; - top: 0; z-index: 9; - width: 100vw; - height: 100vh; - display: flex; - align-items: center; - justify-content: center; - flex-direction: column; + bottom: 170rpx; + left: 123rpx; } .action-img{