From c6e3c33979ab6ed1aa6a834f39c85356320c0f93 Mon Sep 17 00:00:00 2001 From: gerson <1405270578@qq.com> Date: 星期五, 19 七月 2024 23:02:11 +0800 Subject: [PATCH] 语音对话 --- src/components/chat/components/playBar/voicePage/VoicePage.vue | 116 ++++++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 files changed, 108 insertions(+), 8 deletions(-) diff --git a/src/components/chat/components/playBar/voicePage/VoicePage.vue b/src/components/chat/components/playBar/voicePage/VoicePage.vue index 1987afd..62af532 100644 --- a/src/components/chat/components/playBar/voicePage/VoicePage.vue +++ b/src/components/chat/components/playBar/voicePage/VoicePage.vue @@ -24,7 +24,9 @@ ><span :style="{ 'animation-play-state': animationPlayState }"></span> </div> </div> - <div class="mt-5">璇峰紑濮嬭璇�</div> + <div class="mt-5" :class="{ 'cursor-pointer': currentVoiceType === VoiceTipType.Speak }" @click="voiceTipClick"> + {{ voiceTipMap[currentVoiceType] }} + </div> <div class="flex items-center justify-between bottom-16 absolute left-1/2 -translate-x-1/2 space-x-16"> <div class="size-[35px] flex items-center justify-center bg-[#292929] rounded-full cursor-pointer" @click="togglePlayClick"> @@ -43,30 +45,102 @@ </template> <script setup lang="ts"> -import { computed, ref, watch } from 'vue'; +import { computed, nextTick, ref, watch } from 'vue'; +import type { ChatContent } from '../../../model/types'; +import { AnswerType } from '../../../model/types'; +import { VoiceRecognitionErrorType, VoiceTipType, voiceTipMap } from './types'; +import router from '/@/router'; +import { setRoomConfig } from '/@/stores/chatRoom'; const animationPlayState = ref<'paused' | 'running'>('running'); const playIcon = computed(() => (animationPlayState.value === 'running' ? 'icon-zanting' : 'icon-bofang')); - +const isSpeak = ref(false); const togglePlayClick = () => { animationPlayState.value = animationPlayState.value === 'running' ? 'paused' : 'running'; + if (currentVoiceType.value === VoiceTipType.Speak) { + if (isSpeak.value) { + window.speechSynthesis.pause(); + } else { + window.speechSynthesis.resume(); + } + isSpeak.value = !isSpeak.value; + } }; +const props = defineProps(['isHome']); +const emit = defineEmits(['submit', 'updateInputValue']); const isShow = defineModel('isShow', { type: Boolean, }); + +const resetToListenVoice = () => { + currentVoiceType.value = VoiceTipType.NoSpeech; + audioChangeWord(); +}; const isListening = ref(false); -const inputValue = ref(''); const closeClick = () => { isShow.value = false; }; + +let recognition = null; +let speech = null; +const currentVoiceType = ref<VoiceTipType>(VoiceTipType.NoSpeech); +const handleAnswerRes = (res: ChatContent) => { + if (!res) { + return; + } + let text = ''; + + if (res.type === AnswerType.Text || res.type === AnswerType.Knowledge) { + if (res.type === AnswerType.Knowledge) { + text = res.values?.map((item) => item.answer) ?? ''; + } else { + text = res.values; + } + } else { + text = '鎶辨瓑锛屾垜鏃犳硶鍙h堪鍥炵瓟姝ら棶棰樼殑锛岄渶瑕佹煡鐪嬭鍏抽棴姝よ闊冲璇濈晫闈�'; + } + + currentVoiceType.value = VoiceTipType.Speak; + isSpeak.value = true; + var speech = new SpeechSynthesisUtterance(); + speech.text = text; // 鍐呭 + speech.lang = 'zh-cn'; // 璇█ + speech.voiceURI = 'Microsoft Huihui - Chinese (Simplified, PRC)'; // 澹伴煶鍜屾湇鍔� + // eslint-disable-next-line no-irregular-whitespace + speech.volume = 0.7; // 澹伴煶鐨勯煶閲忓尯闂磋寖鍥存槸鈥嬧��0鈥嬧�嬧�嬪埌鈥嬧��1榛樿鏄�嬧��1鈥嬧�� + // eslint-disable-next-line no-irregular-whitespace + speech.rate = 1; // 璇�燂紝鏁板�硷紝榛樿鍊兼槸鈥嬧��1鈥嬧�嬧�嬶紝鑼冨洿鏄�嬧��0.1鈥嬧�嬧�嬪埌鈥嬧��10鈥嬧�嬧�嬶紝琛ㄧず璇�熺殑鍊嶆暟锛屼緥濡傗�嬧��2鈥嬧�嬭〃绀烘甯歌閫熺殑涓ゅ�� + // eslint-disable-next-line no-irregular-whitespace + speech.pitch = 1; // 琛ㄧず璇磋瘽鐨勯煶楂橈紝鏁板�硷紝鑼冨洿浠庘�嬧��0鈥嬧�嬧�嬶紙鏈�灏忥級鍒扳�嬧��2鈥嬧�嬧�嬶紙鏈�澶э級銆傞粯璁ゅ�间负鈥嬧��1鈥嬧�嬨�� + + speech.onend = () => { + resetToListenVoice(); + }; + window.speechSynthesis.speak(speech); +}; + +const voiceTipClick = () => { + switch (currentVoiceType.value) { + case VoiceTipType.Speak: + window.speechSynthesis.cancel(); + setTimeout(() => { + resetToListenVoice(); + }, 0); + + break; + default: + break; + } + window.speechSynthesis.cancel(); +}; const audioChangeWord = () => { - inputValue.value = ''; + emit('updateInputValue', ''); // 鍒涘缓SpeechRecognition瀵硅薄 // eslint-disable-next-line no-undef - var recognition = new webkitSpeechRecognition(); + recognition = new webkitSpeechRecognition(); if (!recognition) { // eslint-disable-next-line no-undef recognition = new SpeechRecognition(); @@ -84,14 +158,35 @@ recognition.onresult = function (event) { var result = event.results[0][0].transcript; console.log('鐩戝惉缁撴灉:', result); - inputValue.value = result; + + emit('updateInputValue', result); + currentVoiceType.value = VoiceTipType.Think; + if (!props.isHome) { + emit('submit', handleAnswerRes); + } else { + setRoomConfig(router.currentRoute.value.query.id as string, 'firstResCb', handleAnswerRes); + emit('submit'); + } + }; + recognition.onspeechstart = (event) => { + currentVoiceType.value = VoiceTipType.Speech; }; // 鐩戝惉閿欒浜嬩欢 recognition.onerror = function (event) { isListening.value = false; - ElMessage.error('鐩戝惉璇煶澶辫触'); + // ElMessage.error('鐩戝惉璇煶澶辫触'); console.error(event.error); + switch (event.error) { + case VoiceRecognitionErrorType.NoSpeech: + if (isShow.value) { + resetToListenVoice(); + } + break; + + default: + break; + } }; // 鐩戝惉缁撴潫浜嬩欢锛堝寘鎷瘑鍒垚鍔熴�佽瘑鍒敊璇拰鐢ㄦ埛鍋滄锛� recognition.onend = function () { @@ -101,7 +196,10 @@ }; const resetStatus = () => { + currentVoiceType.value = VoiceTipType.NoSpeech; animationPlayState.value = 'running'; + recognition?.abort(); + window.speechSynthesis.cancel(); }; watch( @@ -109,6 +207,8 @@ (val) => { if (!val) { resetStatus(); + } else { + resetToListenVoice(); } } ); -- Gitblit v1.9.3