|
@@ -51,7 +51,7 @@
|
|
|
{{expandIndexList.includes(index)?'折叠':'展开'}}
|
|
|
</el-button>
|
|
|
</div>
|
|
|
- <el-button>播放</el-button>
|
|
|
+ <el-button @click="ttsStartPlay(item.content)">播放</el-button>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
@@ -72,10 +72,10 @@
|
|
|
<el-button style="width:140px">退出</el-button>
|
|
|
</div>
|
|
|
<div class="control-button">
|
|
|
- <el-button style="width:140px" @click="startVoice">开始</el-button>
|
|
|
+ <el-button style="width:140px" @click="iatStartVoice">开始</el-button>
|
|
|
</div>
|
|
|
<div class="control-button">
|
|
|
- <el-button style="width:140px" @click="closeVoiceOpen">结束</el-button>
|
|
|
+ <el-button style="width:140px" @click="iatCloseVoiceOpen">结束</el-button>
|
|
|
</div>
|
|
|
<div class="control-button">
|
|
|
<el-button style="width:140px" @click="stopMessage">停止回答</el-button>
|
|
@@ -98,6 +98,8 @@
|
|
|
// 语音识别
|
|
|
import CryptoJS from 'crypto-js';
|
|
|
import * as RecorderManager from "/public/ai/iat/dist/index.umd.js"
|
|
|
+ // 语音播放
|
|
|
+ import {Base64} from 'js-base64'
|
|
|
import * as AudioPlayer from "/public/ai/tts/dist/index.umd.js"
|
|
|
|
|
|
const loadingHistoryRecord = ref(false)
|
|
@@ -132,7 +134,7 @@
|
|
|
aiChatRecordList(queryForm).then(resp =>{
|
|
|
chatRecordList.value = resp.rows;
|
|
|
loadingHistoryRecord.value = false
|
|
|
- setInterval(()=>{
|
|
|
+ setTimeout(()=>{
|
|
|
scrollToBottom();
|
|
|
}, 100);
|
|
|
if(resp.total == 0){
|
|
@@ -268,22 +270,22 @@
|
|
|
|
|
|
/* 语音识别 ↓*/
|
|
|
// 控制录音弹窗
|
|
|
- let voiceOpen = false
|
|
|
+ let iatVoiceOpen = false
|
|
|
// 是否开始录音
|
|
|
- let startVoiceStatus = false
|
|
|
+ let iatStartVoiceStatus = false
|
|
|
// 识别中状态
|
|
|
- let identifyStatus = false
|
|
|
+ let iatIdentifyStatus = false
|
|
|
// 录音对象
|
|
|
- let recorder = null
|
|
|
- let transcription = ''
|
|
|
- let btnStatus = ''
|
|
|
- let resultText = ''
|
|
|
- let resultTextTemp = ''
|
|
|
- let countdownInterval = null
|
|
|
+ let iatRecorder = null
|
|
|
+ let iatTranscription = ''
|
|
|
+ let iatBtnStatus = ''
|
|
|
+ let iatResultText = ''
|
|
|
+ let iatResultTextTemp = ''
|
|
|
+ let iatCountdownInterval = null
|
|
|
let iatWS = null
|
|
|
- let recognition = null
|
|
|
- let buttonDisabled = true
|
|
|
- let loading = false
|
|
|
+ let iatRecognition = null
|
|
|
+ let iatButtonDisabled = true
|
|
|
+ let iatLoading = false
|
|
|
let xfIatKeys = {
|
|
|
APPID: '5a2643f4',
|
|
|
APISecret: 'MTg4MWIzY2VmYTg2YTEwMjliMDY1N2Iz',
|
|
@@ -292,12 +294,12 @@
|
|
|
// 初始化语音识别
|
|
|
function initSpeechRecognition(){
|
|
|
// 初始化录音
|
|
|
- recorder = new window.RecorderManager('/ai/iat/dist');
|
|
|
+ iatRecorder = new window.RecorderManager('/ai/iat/dist');
|
|
|
// 开始录音
|
|
|
- recorder.onStart = () => {
|
|
|
- changeBtnStatus('OPEN');
|
|
|
+ iatRecorder.onStart = () => {
|
|
|
+ iatChangeBtnStatus('OPEN');
|
|
|
};
|
|
|
- recorder.onFrameRecorded = ({ isLastFrame, frameBuffer }) => {
|
|
|
+ iatRecorder.onFrameRecorded = ({ isLastFrame, frameBuffer }) => {
|
|
|
if (iatWS.readyState === iatWS.OPEN) {
|
|
|
iatWS.send(
|
|
|
JSON.stringify({
|
|
@@ -310,63 +312,63 @@
|
|
|
}),
|
|
|
);
|
|
|
if (isLastFrame) {
|
|
|
- changeBtnStatus('CLOSING');
|
|
|
+ iatChangeBtnStatus('CLOSING');
|
|
|
}
|
|
|
}
|
|
|
};
|
|
|
- recorder.onStop = () => {
|
|
|
+ iatRecorder.onStop = () => {
|
|
|
console.log('录音结束,停止定时器');
|
|
|
- clearInterval(countdownInterval);
|
|
|
- startVoiceStatus = false;
|
|
|
+ clearInterval(iatCountdownInterval);
|
|
|
+ iatStartVoiceStatus = false;
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
- async function startVoice() {
|
|
|
- if (loading) {
|
|
|
+ async function iatStartVoice() {
|
|
|
+ if (iatLoading) {
|
|
|
return;
|
|
|
}
|
|
|
- voiceOpen = true;
|
|
|
- await playIatVoice();
|
|
|
+ iatVoiceOpen = true;
|
|
|
+ await iatPlayIatVoice();
|
|
|
}
|
|
|
|
|
|
- async function playIatVoice() {
|
|
|
- if (loading) {
|
|
|
+ async function iatPlayIatVoice() {
|
|
|
+ if (iatLoading) {
|
|
|
return;
|
|
|
}
|
|
|
- startVoiceStatus = !startVoiceStatus;
|
|
|
+ iatStartVoiceStatus = !iatStartVoiceStatus;
|
|
|
// 浏览器自带的识别
|
|
|
- if (recognition) {
|
|
|
- if (startVoiceStatus) {
|
|
|
- recognition.start();
|
|
|
+ if (iatRecognition) {
|
|
|
+ if (iatStartVoiceStatus) {
|
|
|
+ iatRecognition.start();
|
|
|
} else {
|
|
|
- recognition.stop();
|
|
|
+ iatRecognition.stop();
|
|
|
}
|
|
|
return;
|
|
|
}
|
|
|
- if (startVoiceStatus) {
|
|
|
- connectWebSocket();
|
|
|
+ if (iatStartVoiceStatus) {
|
|
|
+ iatConnectWebSocket();
|
|
|
} else {
|
|
|
- recorder.stop();
|
|
|
+ iatRecorder.stop();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 关闭录音弹窗
|
|
|
*/
|
|
|
- function closeVoiceOpen() {
|
|
|
- voiceOpen = false;
|
|
|
- startVoiceStatus = false;
|
|
|
- if (recorder) {
|
|
|
- recorder.stop();
|
|
|
+ function iatCloseVoiceOpen() {
|
|
|
+ iatVoiceOpen = false;
|
|
|
+ iatStartVoiceStatus = false;
|
|
|
+ if (iatRecorder) {
|
|
|
+ iatRecorder.stop();
|
|
|
}
|
|
|
- if (recognition) {
|
|
|
- recognition.stop();
|
|
|
+ if (iatRecognition) {
|
|
|
+ iatRecognition.stop();
|
|
|
}
|
|
|
- transcription = '';
|
|
|
+ iatTranscription = '';
|
|
|
}
|
|
|
|
|
|
- function renderResult(resultData) {
|
|
|
+ function iatRenderResult(resultData) {
|
|
|
// 识别结束
|
|
|
const jsonData = JSON.parse(resultData);
|
|
|
if (jsonData.data && jsonData.data.result) {
|
|
@@ -381,15 +383,14 @@
|
|
|
if (data.pgs) {
|
|
|
if (data.pgs === 'apd') {
|
|
|
// 将resultTextTemp同步给resultText
|
|
|
- resultText = resultTextTemp;
|
|
|
+ iatResultText = iatResultTextTemp;
|
|
|
}
|
|
|
// 将结果存储在resultTextTemp中
|
|
|
- resultTextTemp = resultText + str;
|
|
|
+ iatResultTextTemp = iatResultText + str;
|
|
|
} else {
|
|
|
- resultText += str;
|
|
|
+ iatResultText += str;
|
|
|
}
|
|
|
- inputMessage.value = resultTextTemp || resultText || '';
|
|
|
- console.log("识别:"+inputMessage.value);
|
|
|
+ inputMessage.value = iatResultTextTemp || iatResultText || '';
|
|
|
}
|
|
|
if (jsonData.code === 0 && jsonData.data.status === 2) {
|
|
|
iatWS.close();
|
|
@@ -400,8 +401,8 @@
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- function connectWebSocket() {
|
|
|
- const websocketUrl = getWebSocketUrl();
|
|
|
+ function iatConnectWebSocket() {
|
|
|
+ const websocketUrl = iatGetWebSocketUrl();
|
|
|
if ('WebSocket' in window) {
|
|
|
iatWS = new window.WebSocket(websocketUrl);
|
|
|
} else if ('MozWebSocket' in window) {
|
|
@@ -410,11 +411,11 @@
|
|
|
message.error('浏览器不支持WebSocket');
|
|
|
return;
|
|
|
}
|
|
|
- changeBtnStatus('CONNECTING');
|
|
|
+ iatChangeBtnStatus('CONNECTING');
|
|
|
iatWS.onopen = e => {
|
|
|
console.log('iatWS.onopen', e);
|
|
|
// 开始录音
|
|
|
- recorder.start({
|
|
|
+ iatRecorder.start({
|
|
|
sampleRate: 16000,
|
|
|
frameSize: 1280,
|
|
|
});
|
|
@@ -440,21 +441,21 @@
|
|
|
iatWS.send(JSON.stringify(params));
|
|
|
};
|
|
|
iatWS.onmessage = e => {
|
|
|
- renderResult(e.data);
|
|
|
+ iatRenderResult(e.data);
|
|
|
};
|
|
|
iatWS.onerror = e => {
|
|
|
console.error(e);
|
|
|
- recorder.stop();
|
|
|
- changeBtnStatus('CLOSED');
|
|
|
+ iatRecorder.stop();
|
|
|
+ iatChangeBtnStatus('CLOSED');
|
|
|
};
|
|
|
iatWS.onclose = e => {
|
|
|
console.log(e);
|
|
|
- recorder.stop();
|
|
|
- changeBtnStatus('CLOSED');
|
|
|
+ iatRecorder.stop();
|
|
|
+ iatChangeBtnStatus('CLOSED');
|
|
|
};
|
|
|
}
|
|
|
|
|
|
- function getWebSocketUrl() {
|
|
|
+ function iatGetWebSocketUrl() {
|
|
|
const { APIKey, APISecret } = xfIatKeys;
|
|
|
if (!APIKey) {
|
|
|
message.error('语音识别配置未生效');
|
|
@@ -477,29 +478,29 @@
|
|
|
return url;
|
|
|
}
|
|
|
|
|
|
- function countdown() {
|
|
|
+ function iatCountdown() {
|
|
|
let seconds = 60;
|
|
|
console.log(`录音中(${seconds}s)`);
|
|
|
- countdownInterval = setInterval(() => {
|
|
|
+ iatCountdownInterval = setInterval(() => {
|
|
|
seconds -= 1;
|
|
|
if (seconds <= 0) {
|
|
|
- clearInterval(countdownInterval);
|
|
|
- recorder.stop();
|
|
|
+ clearInterval(iatCountdownInterval);
|
|
|
+ iatRecorder.stop();
|
|
|
} else {
|
|
|
console.log(`录音中(${seconds}s)`);
|
|
|
}
|
|
|
}, 1000);
|
|
|
}
|
|
|
|
|
|
- function changeBtnStatus(status) {
|
|
|
- btnStatus = status;
|
|
|
+ function iatChangeBtnStatus(status) {
|
|
|
+ iatBtnStatus = status;
|
|
|
if (status === 'CONNECTING') {
|
|
|
console.log('建立连接中');
|
|
|
- resultText = '';
|
|
|
- resultTextTemp = '';
|
|
|
+ iatResultText = '';
|
|
|
+ iatResultTextTemp = '';
|
|
|
} else if (status === 'OPEN') {
|
|
|
- if (recorder) {
|
|
|
- countdown();
|
|
|
+ if (iatRecorder) {
|
|
|
+ iatCountdown();
|
|
|
}
|
|
|
} else if (status === 'CLOSING') {
|
|
|
console.log('关闭连接中');
|
|
@@ -518,31 +519,185 @@
|
|
|
return window.btoa(binary);
|
|
|
}
|
|
|
|
|
|
- // 发送消息
|
|
|
- function sendMsg(text) {
|
|
|
- // $emit('sendMsg', text);
|
|
|
- // transcription = '';
|
|
|
- }
|
|
|
/* 语音识别 ↑*/
|
|
|
|
|
|
/*语音合成 ↓*/
|
|
|
+ // 合成对象
|
|
|
+ let audioPlayer = null
|
|
|
+ // 按钮状态:"UNDEFINED" "CONNECTING" "PLAY" "STOP"
|
|
|
+ let btnStatus = "UNDEFINED";
|
|
|
+ //
|
|
|
+ let ttsWS = null;
|
|
|
+
|
|
|
+ function initSpeechSynthesis(){
|
|
|
+ // 初始化语音播放
|
|
|
+ audioPlayer = new window.AudioPlayer("/ai/tts/dist");
|
|
|
+ // 开始播放
|
|
|
+ audioPlayer.onPlay = () => {
|
|
|
+ changeBtnStatus("PLAY");
|
|
|
+ };
|
|
|
+ audioPlayer.onStop = (audioDatas) => {
|
|
|
+ console.log('=======>',audioDatas);
|
|
|
+ btnStatus === "PLAY" && changeBtnStatus("STOP");
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ function changeBtnStatus(status) {
|
|
|
+ btnStatus = status;
|
|
|
+ if (status === "UNDEFINED") {
|
|
|
+ console.log("立即合成");
|
|
|
+ } else if (status === "CONNECTING") {
|
|
|
+ console.log("正在合成");
|
|
|
+ } else if (status === "PLAY") {
|
|
|
+ console.log("停止播放");
|
|
|
+ } else if (status === "STOP") {
|
|
|
+ console.log("重新播放");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ function getWebSocketUrl() {
|
|
|
+ const { APIKey, APISecret } = xfIatKeys;
|
|
|
+ if (!APIKey) {
|
|
|
+ message.error('语音合成配置未生效');
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ var url = "wss://tts-api.xfyun.cn/v2/tts";
|
|
|
+ var host = location.host;
|
|
|
+ const apiKey = APIKey;
|
|
|
+ const apiSecret = APISecret;
|
|
|
+ var date = new Date().toGMTString();
|
|
|
+ var algorithm = "hmac-sha256";
|
|
|
+ var headers = "host date request-line";
|
|
|
+ var signatureOrigin = `host: ${host}\ndate: ${date}\nGET /v2/tts 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 encodeText(text, type) {
|
|
|
+ if (type === "unicode") {
|
|
|
+ let buf = new ArrayBuffer(text.length * 4);
|
|
|
+ let bufView = new Uint16Array(buf);
|
|
|
+ for (let i = 0, strlen = text.length; i < strlen; i++) {
|
|
|
+ bufView[i] = text.charCodeAt(i);
|
|
|
+ }
|
|
|
+ let binary = "";
|
|
|
+ let bytes = new Uint8Array(buf);
|
|
|
+ let len = bytes.byteLength;
|
|
|
+ for (let i = 0; i < len; i++) {
|
|
|
+ binary += String.fromCharCode(bytes[i]);
|
|
|
+ }
|
|
|
+ return window.btoa(binary);
|
|
|
+ } else {
|
|
|
+ return Base64.encode(text);
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- function initSpeechsynthesis(){
|
|
|
+ function ttsStartPlay(text) {
|
|
|
+ if (btnStatus === "UNDEFINED") {
|
|
|
+ // 开始合成
|
|
|
+ connectWebSocket(text.replace(/<\/?.+?\/?>/g,''));
|
|
|
+ } else if (btnStatus === "CONNECTING") {
|
|
|
+ // 停止合成
|
|
|
+ changeBtnStatus("UNDEFINED");
|
|
|
+ ttsWS?.close();
|
|
|
+ audioPlayer.reset();
|
|
|
+ return;
|
|
|
+ } else if (btnStatus === "PLAY") {
|
|
|
+ audioPlayer.stop();
|
|
|
+ } else if (btnStatus === "STOP") {
|
|
|
+ audioPlayer.play();
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ function connectWebSocket(text) {
|
|
|
+ const url = getWebSocketUrl();
|
|
|
+ if ("WebSocket" in window) {
|
|
|
+ ttsWS = new WebSocket(url);
|
|
|
+ } else if ("MozWebSocket" in window) {
|
|
|
+ ttsWS = new MozWebSocket(url);
|
|
|
+ } else {
|
|
|
+ alert("浏览器不支持WebSocket");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ changeBtnStatus("CONNECTING");
|
|
|
|
|
|
+ ttsWS.onopen = (e) => {
|
|
|
+ audioPlayer.start({
|
|
|
+ autoPlay: true,
|
|
|
+ sampleRate: 16000,
|
|
|
+ resumePlayDuration: 1000
|
|
|
+ });
|
|
|
+ changeBtnStatus("PLAY");
|
|
|
+ var tte = "UTF8";
|
|
|
+ var params = {
|
|
|
+ common: {
|
|
|
+ app_id: xfIatKeys.APPID,
|
|
|
+ },
|
|
|
+ business: {
|
|
|
+ aue: "raw",
|
|
|
+ auf: "audio/L16;rate=16000",
|
|
|
+ vcn: "xiaoyan",
|
|
|
+ // vcn: "aisbabyxu",
|
|
|
+ // vcn: "aisjinger",
|
|
|
+ // vcn: "aisxping",
|
|
|
+ // vcn: "aisjinger",
|
|
|
+ // vcn: "x4_lingxiaoyao_em",
|
|
|
+ // vcn: "x4_lingxiaoyao_en",
|
|
|
+ speed: +50,
|
|
|
+ volume: +50,
|
|
|
+ pitch: +50,
|
|
|
+ bgs: 1,
|
|
|
+ tte,
|
|
|
+ },
|
|
|
+ data: {
|
|
|
+ status: 2,
|
|
|
+ text: encodeText(text, tte),
|
|
|
+ },
|
|
|
+ };
|
|
|
+ ttsWS.send(JSON.stringify(params));
|
|
|
+ };
|
|
|
+ ttsWS.onmessage = (e) => {
|
|
|
+ let jsonData = JSON.parse(e.data);
|
|
|
+ // 合成失败
|
|
|
+ if (jsonData.code !== 0) {
|
|
|
+ console.error(jsonData);
|
|
|
+ changeBtnStatus("UNDEFINED");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ audioPlayer.postMessage({
|
|
|
+ type: "base64",
|
|
|
+ data: jsonData.data.audio,
|
|
|
+ isLastData: jsonData.data.status === 2,
|
|
|
+ });
|
|
|
+ if (jsonData.code === 0 && jsonData.data.status === 2) {
|
|
|
+ ttsWS.close();
|
|
|
+ }
|
|
|
+ };
|
|
|
+ ttsWS.onerror = (e) => {
|
|
|
+ console.error(e);
|
|
|
+ };
|
|
|
+ ttsWS.onclose = (e) => {
|
|
|
+ // console.log(e);
|
|
|
+ };
|
|
|
}
|
|
|
|
|
|
/*语音合成 ↑*/
|
|
|
|
|
|
let chatContainerRef = ref(null)
|
|
|
function scrollToBottom(){
|
|
|
- chatContainerRef.value.scrollTop = chatContainerRef.value.scrollHeight+20;
|
|
|
+ if(chatContainerRef.value.scrollHeight){
|
|
|
+ chatContainerRef.value.scrollTop = chatContainerRef.value.scrollHeight+20;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
onMounted(()=>{
|
|
|
nextTick(()=>{
|
|
|
initSpeechRecognition();
|
|
|
- initSpeechsynthesis();
|
|
|
+ initSpeechSynthesis();
|
|
|
list();
|
|
|
})
|
|
|
|