375 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			375 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| const chatIcon = document.getElementById('chatIcon');
 | ||
| const chatDialog = document.getElementById('chatDialog');
 | ||
| const closeChat = document.getElementById('closeChat');
 | ||
| const voiceInputBtn = document.getElementById('voiceInputBtn');
 | ||
| const voiceStatus = document.getElementById('voiceStatus');
 | ||
| const notificationBadge = document.querySelector('.notification-badge');
 | ||
| var APPID = "43341b7e";
 | ||
| var API_SECRET = "MWQzMWEyYTAyYzVlZWRjOTM1NjE0MjI4";
 | ||
| var API_KEY = "a46e1fc5d062a14b28cde0ff2c0046e1";
 | ||
| let btnStatus = "UNDEFINED"; // "UNDEFINED" "CONNECTING" "OPEN" "CLOSING" "CLOSED"
 | ||
| 
 | ||
| //大模型调用部分
 | ||
| const apiPassword = "xozAvYSDDAUrZtmZAMHe:BesWfhINSVtAumsVcxCt";
 | ||
| // 注意:请将 123456 替换为你自己的 APIPassword
 | ||
| let chatList = [
 | ||
|     {
 | ||
|         "role": "system",
 | ||
|         "content": "请你扮演一名幼师,跟我聊聊天吧!"
 | ||
|     }
 | ||
| ]
 | ||
| async function sendRequest(str,callback) {
 | ||
|     chatList.push({
 | ||
|         "role": "user",
 | ||
|         "content": str
 | ||
|     })
 | ||
| 
 | ||
|     const body =
 | ||
|     {
 | ||
|         "apiPassword": apiPassword,
 | ||
|         "chatList": chatList
 | ||
|     }
 | ||
|     let data= await fetch('http://w.textbox.wang/index.php/english/Index/xhchat', {
 | ||
|         method: 'POST', // 或者使用 'GET' 取决于API的要求
 | ||
|         headers: {
 | ||
|             'Content-Type': 'application/json',
 | ||
|         },
 | ||
| 
 | ||
|         body: JSON.stringify(body)
 | ||
|     })
 | ||
|         .then(response => response.json()) // 解析JSON响应
 | ||
|         .then(data => {
 | ||
|             // 处理响应数据
 | ||
|             console.log(data.choices[0].message.content);
 | ||
|              chatList.push({
 | ||
|                 "role": "assistant",
 | ||
|                 "content": data.choices[0].message.content
 | ||
|             })
 | ||
|             callback(data.choices[0].message.content)
 | ||
|             
 | ||
|             // document.getElementById('output').innerText = data.text; // 假设响应中包含文本字段
 | ||
|         })
 | ||
|         .catch(error => console.error('Error:', error)); // 捕获并处理错误
 | ||
| 
 | ||
| }
 | ||
| 
 | ||
| // 执行请求
 | ||
| 
 | ||
| //语音播报部分
 | ||
| let preText = "";
 | ||
| function speakFly(text) {
 | ||
|     if (preText == text) {
 | ||
|         return
 | ||
|     }
 | ||
|     preText = text;
 | ||
| 
 | ||
|     const audioPlayer = document.getElementById('chatAudio');
 | ||
|     const vcn = ["x4_lingxiaoqi_cts", "x4_lingyouyou", "x4_lingfeizhe_zl", "aisjinger", "aisbabyxu"];
 | ||
| 
 | ||
|     $.ajax({
 | ||
|         url: 'http://rgclass.iflysse.com:8200/text2audio',
 | ||
|         type: 'POST',
 | ||
|         contentType: 'application/x-www-form-urlencoded',
 | ||
|         data: {
 | ||
|             text: text,
 | ||
|             vcn: vcn[1],
 | ||
|             speed: 50
 | ||
|         },
 | ||
|         headers: {
 | ||
|             'Cache-Control': 'no-store'
 | ||
|         },
 | ||
|         success: function (data) {
 | ||
| 
 | ||
|             let timer01 = setTimeout(function () {
 | ||
|                  $(".message-content:last").text(text)
 | ||
|                 $(".chat-content").animate({ scrollTop: $(".chat-content")[0].scrollHeight }, 1000);
 | ||
|                 const url = `http://rgclass.iflysse.com:8200${data}`;
 | ||
|                 audioPlayer.src = url;
 | ||
|                 audioPlayer.load();
 | ||
|                 audioPlayer.play();
 | ||
|                 audioPlayer.onerror = function (e) {
 | ||
|                     setTimeout(function () {
 | ||
|                         audioPlayer.load();
 | ||
|                         audioPlayer.play();
 | ||
|                     }, 500);
 | ||
| 
 | ||
|                     console.error(e);
 | ||
|                 };
 | ||
| 				audioPlayer.addEventListener('ended', () => {//连续对话
 | ||
| 			 	    voiceInputBtn.click();
 | ||
| 				});
 | ||
|                 clearTimeout(timer01);
 | ||
|                 // console.log("text2audio success", url, audioPlayer);
 | ||
|             }, 1000);
 | ||
| 
 | ||
|         },
 | ||
|         error: function (jqXHR, textStatus, errorThrown) {
 | ||
|             console.error('text2audio error:', textStatus, errorThrown);
 | ||
|         }
 | ||
|     });
 | ||
| 
 | ||
|     let timer02 = setTimeout(function () {
 | ||
|         preText = "";
 | ||
|         clearTimeout(timer02);
 | ||
|     }, 5000);
 | ||
| }
 | ||
| 
 | ||
| 
 | ||
| const recorder = new RecorderManager("./dist");
 | ||
| recorder.onStart = () => {
 | ||
|     changeBtnStatus("OPEN");
 | ||
| }
 | ||
| let iatWS;
 | ||
| let resultText = "";
 | ||
| let resultTextTemp = "";
 | ||
| let countdownInterval;
 | ||
| let isListening = false;
 | ||
| /**
 | ||
|  * 获取websocket url
 | ||
|  * 该接口需要后端提供,这里为了方便前端处理
 | ||
|  */
 | ||
| function getWebSocketUrl() {
 | ||
|     // 请求地址根据语种不同变化
 | ||
|     var url = "wss://iat-api.xfyun.cn/v2/iat";
 | ||
|     var host = "iat-api.xfyun.cn";
 | ||
|     var apiKey = API_KEY;
 | ||
|     var apiSecret = API_SECRET;
 | ||
|     var date = new Date().toGMTString();
 | ||
|     var algorithm = "hmac-sha256";
 | ||
|     var headers = "host date request-line";
 | ||
|     var signatureOrigin = `host: ${host}\ndate: ${date}\nGET /v2/iat 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}`;
 | ||
|     return url;
 | ||
| }
 | ||
| 
 | ||
| function toBase64(buffer) {
 | ||
|     var binary = "";
 | ||
|     var bytes = new Uint8Array(buffer);
 | ||
|     var len = bytes.byteLength;
 | ||
|     for (var i = 0; i < len; i++) {
 | ||
|         binary += String.fromCharCode(bytes[i]);
 | ||
|     }
 | ||
|     return window.btoa(binary);
 | ||
| }
 | ||
| function changeBtnStatus(status) {
 | ||
|     btnStatus = status;
 | ||
|     if (status === "CONNECTING") {
 | ||
|         //   btnControl.innerText = "建立连接中";
 | ||
|         //   document.getElementById("partial-result").innerText = "";
 | ||
|         resultText = "";
 | ||
|         resultTextTemp = "";
 | ||
|     }
 | ||
| }
 | ||
| let lydjs=null;
 | ||
| function renderResult(resultData) {
 | ||
| 	clearTimeout(lydjs);
 | ||
| 	lydjs=setTimeout(function(){
 | ||
| 		iatWS.close();
 | ||
| 	},3000);
 | ||
|     // 识别结束
 | ||
|     let jsonData = JSON.parse(resultData);
 | ||
|     if (jsonData.data && jsonData.data.result) {
 | ||
|         let data = jsonData.data.result;
 | ||
|         let str = "";
 | ||
|         let ws = data.ws;
 | ||
|         for (let i = 0; i < ws.length; i++) {
 | ||
|             str = str + ws[i].cw[0].w;
 | ||
|         }
 | ||
| 		
 | ||
|         console.log(str);
 | ||
| 		
 | ||
|         // 开启wpgs会有此字段(前提:在控制台开通动态修正功能)
 | ||
|         // 取值为 "apd"时表示该片结果是追加到前面的最终结果;取值为"rpl" 时表示替换前面的部分结果,替换范围为rg字段
 | ||
|         if (data.pgs) {
 | ||
|             if (data.pgs === "apd") {
 | ||
|                 // 将resultTextTemp同步给resultText
 | ||
|                 resultText = resultTextTemp;
 | ||
|                 // 移除打字指示器
 | ||
|             }
 | ||
|             // 将结果存储在resultTextTemp中
 | ||
|             resultTextTemp = resultText + str;
 | ||
|         } else {
 | ||
|             resultText = resultText + str;
 | ||
|         }
 | ||
| 		$(".typing-indicator").text(resultTextTemp);
 | ||
|         //   document.getElementById("partial-result").innerText =
 | ||
|         //     resultTextTemp || resultText || "";
 | ||
|     }
 | ||
|     if (jsonData.code === 0 && jsonData.data.status === 2) {
 | ||
| 
 | ||
|         iatWS.close();
 | ||
|     }
 | ||
|     if (jsonData.code !== 0) {
 | ||
|         iatWS.close();
 | ||
|         console.error(jsonData);
 | ||
|     }
 | ||
| }
 | ||
| function connectWebSocket() {
 | ||
|     const websocketUrl = getWebSocketUrl();
 | ||
|     if ("WebSocket" in window) {
 | ||
|         iatWS = new WebSocket(websocketUrl);
 | ||
|     } else if ("MozWebSocket" in window) {
 | ||
|         iatWS = new MozWebSocket(websocketUrl);
 | ||
|     } else {
 | ||
|         alert("浏览器不支持WebSocket");
 | ||
|         return;
 | ||
|     }
 | ||
|     changeBtnStatus("CONNECTING");
 | ||
| 	console.log(iatWS);
 | ||
|     iatWS.onopen = (e) => {
 | ||
|         isListening = true;
 | ||
|         voiceInputBtn.classList.add('active');
 | ||
|         voiceInputBtn.innerHTML = '<i class="fa fa-stop"></i>';
 | ||
|         voiceStatus.textContent = '正在聆听...';
 | ||
| 
 | ||
|         // 添加用户正在说话的UI反馈
 | ||
|         addMessage('user', '<div class="typing-indicator"><span></span><span></span><span></span></div>');
 | ||
|         // 开始录音
 | ||
|         recorder.start({
 | ||
|             sampleRate: 16000,
 | ||
|             frameSize: 1280,
 | ||
|         });
 | ||
|         var params = {
 | ||
|             common: {
 | ||
|                 app_id: APPID,
 | ||
|             },
 | ||
|             business: {
 | ||
|                 language: "zh_cn",
 | ||
|                 domain: "iat",
 | ||
|                 accent: "mandarin",
 | ||
|                 vad_eos: 5000,
 | ||
|                 dwa: "wpgs",
 | ||
|             },
 | ||
|             data: {
 | ||
|                 status: 0,
 | ||
|                 format: "audio/L16;rate=16000",
 | ||
|                 encoding: "raw",
 | ||
|             },
 | ||
|         };
 | ||
| 		console.log("asd");
 | ||
|         iatWS.send(JSON.stringify(params));
 | ||
| 		clearTimeout(lydjs);
 | ||
| 		lydjs=setTimeout(function(){
 | ||
| 			iatWS.close();
 | ||
| 		},3000);
 | ||
|     };
 | ||
|     iatWS.onmessage = (e) => {
 | ||
| 		console.log("asdas")
 | ||
|         renderResult(e.data);
 | ||
|         console.log(e.data);
 | ||
|     };
 | ||
|     iatWS.onerror = (e) => {
 | ||
|         console.error(e);
 | ||
|         recorder.stop();
 | ||
|         changeBtnStatus("CLOSED");
 | ||
|     };
 | ||
|     iatWS.onclose = (e) => {
 | ||
|         removeLastUserMessage();
 | ||
| 		if(resultTextTemp!=''){
 | ||
| 			// 显示用户的语音输入
 | ||
| 			addMessage('user', resultTextTemp);
 | ||
| 			// 模拟AI回复
 | ||
| 			// setTimeout(() => {
 | ||
| 			addMessage('bot', '我正在处理你的请求...');
 | ||
| 			// }, 1000);
 | ||
| 			sendRequest(resultTextTemp,function(response){           
 | ||
| 			    speakFly(response)
 | ||
| 			});
 | ||
| 		}
 | ||
| 			
 | ||
| 		
 | ||
|        voiceInputBtn.innerHTML = '<i class="fa fa-microphone"></i>';
 | ||
| 	   voiceInputBtn.classList.remove('active');
 | ||
| 	   
 | ||
| 	   voiceStatus.textContent = '';
 | ||
| isListening = false;
 | ||
|         recorder.stop();
 | ||
|         changeBtnStatus("CLOSED");
 | ||
|     };
 | ||
| }
 | ||
| 
 | ||
| recorder.onFrameRecorded = ({ isLastFrame, frameBuffer }) => {
 | ||
|     if (iatWS.readyState === iatWS.OPEN) {
 | ||
|         iatWS.send(
 | ||
|             JSON.stringify({
 | ||
|                 data: {
 | ||
|                     status: isLastFrame ? 2 : 1,
 | ||
|                     format: "audio/L16;rate=16000",
 | ||
|                     encoding: "raw",
 | ||
|                     audio: toBase64(frameBuffer),
 | ||
|                 },
 | ||
|             })
 | ||
|         );
 | ||
|         if (isLastFrame) {
 | ||
|             changeBtnStatus("CLOSING");
 | ||
|         }
 | ||
|     }
 | ||
| };
 | ||
| recorder.onStop = () => {
 | ||
|     clearInterval(countdownInterval);
 | ||
| };
 | ||
| 
 | ||
| voiceInputBtn.onclick = function () {
 | ||
|     if (btnStatus === "UNDEFINED" || btnStatus === "CLOSED") {
 | ||
|         connectWebSocket();
 | ||
|     } else if (btnStatus === "CONNECTING" || btnStatus === "OPEN") {
 | ||
|         // 结束录音
 | ||
|         recorder.stop();
 | ||
|     }
 | ||
| };
 | ||
| // 打开/关闭聊天对话框
 | ||
| chatIcon.addEventListener('click', () => {
 | ||
|     chatDialog.classList.toggle('open');
 | ||
| 
 | ||
|     // 打开对话框后移除通知徽章
 | ||
|     // if (chatDialog.classList.contains('open')) {
 | ||
|     //     notificationBadge.classList.remove('active');
 | ||
|     //     setTimeout(() => {
 | ||
|     //         notificationBadge.style.display = 'none';
 | ||
|     //     }, 300);
 | ||
|     // }
 | ||
| });
 | ||
| 
 | ||
| closeChat.addEventListener('click', () => {
 | ||
|     chatDialog.classList.remove('open');
 | ||
| });
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
| // 添加消息到聊天界面
 | ||
| function addMessage(type, content) {
 | ||
|     const chatContent = document.querySelector('.chat-content');
 | ||
|     const messageDiv = document.createElement('div');
 | ||
|     messageDiv.className = `message ${type}`;
 | ||
|     messageDiv.innerHTML = `<div class="message-content">${content}</div>`;
 | ||
|     chatContent.appendChild(messageDiv);
 | ||
| 
 | ||
|     // 滚动到底部
 | ||
|     chatContent.scrollTop = chatContent.scrollHeight;
 | ||
| }
 | ||
| 
 | ||
| // 移除最后一条用户消息(用于移除打字指示器)
 | ||
| function removeLastUserMessage() {
 | ||
|     const chatContent = document.querySelector('.chat-content');
 | ||
|     const messages = chatContent.querySelectorAll('.message');
 | ||
| 
 | ||
|     if (messages.length > 0) {
 | ||
|         const lastMessage = messages[messages.length - 1];
 | ||
|         if (lastMessage.classList.contains('user')) {
 | ||
|             chatContent.removeChild(lastMessage);
 | ||
|         }
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| // 添加窗口点击事件,点击外部关闭聊天框
 | ||
| window.addEventListener('click', (event) => {
 | ||
|     if (event.target === chatDialog) {
 | ||
|         chatDialog.classList.remove('open');
 | ||
|     }
 | ||
| });
 | ||
| 
 |