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');
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|