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