Bläddra i källkod

虚拟实验室中接入语音识别api并调整样式

liyanbo 1 månad sedan
förälder
incheckning
d95e17c934
2 ändrade filer med 250 tillägg och 125 borttagningar
  1. 118 63
      src/views/blockly/Blockly.vue
  2. 132 62
      src/views/blockly/Blockly2.vue

+ 118 - 63
src/views/blockly/Blockly.vue

@@ -1,19 +1,17 @@
 <template>
   <!-- 收音状态显示区域 -->
-  <div v-if="isRecording" class="recording-status-container">
+  <div v-show="isRecording" class="recording-status-container">
     <div class="recording-text">正在收音...</div>
-    <div class="waveform-container">
-      <LiveWaveform
-        :active="isRecording" 
-        :processing="false"
-        :height="50"
-        :barWidth="3"
-        :barGap="1"
-        :barRadius="1"
-        :sensitivity="1.2"
+    <div class="voice-input-wrapper">
+      <VoiceInputApi
+          ref="voiceInputRef"
+          inputSelector="input[type='text']"
+          lang="zh-CN"
+          maxDuration="10"
+          @voiceRecognized="handleVoiceRecognized"
+          @recordingStatusChanged="handleRecordingStatusChanged"
       />
     </div>
-    <div v-if="recordingCountdown <= 5" class="recording-countdown">{{ recordingCountdown }}秒</div>
   </div>
 
   <!-- 智能台灯 -->
@@ -219,6 +217,7 @@ import { globalState } from "@/utils/globalState.js";
 import { playMusic, stopMusic, onMusicEnded } from "@/api/blockly/music.js";
 import {ElButton} from "element-plus";
 import LiveWaveform from '../../components/ai/voice/LiveWaveform.vue';
+import VoiceInputApi from '../../components/ai/voice/VoiceInput_Api.vue';
 
 
 const router = useRouter();
@@ -238,6 +237,11 @@ const recordingCountdown = ref(10);
 let countdownInterval = null;
 let recognition = null;
 
+// 语音输入组件
+const voiceInputRef = ref(null);
+const voiceResult = ref('');
+const isVoiceRecording = ref(false);
+
 // 返回虚拟实验室
 const goLabShow = () => {
   showLampPreview.value = true;
@@ -252,6 +256,18 @@ const toggleLight = () => {
     runCode();
 };
 
+// 语音识别结果处理
+const handleVoiceRecognized = (text) => {
+  console.log("语音识别结果:", text);
+  voiceResult.value = text;
+};
+
+// 录音状态变化处理
+const handleRecordingStatusChanged = (status) => {
+  console.log("录音状态变化:", status);
+  isVoiceRecording.value = status;
+};
+
 // 添加开始录音状态函数
 const handleMusicEnded = () => {
   onMusicEnded(state);
@@ -463,65 +479,68 @@ const aiService = {
   // 前端语音采集
   captureVoice(language, promptText) {
     return new Promise((resolve) => {
-      if (
-          !"webkitSpeechRecognition" in window &&
-          !"SpeechRecognition" in window
-      ) {
-        showStatus("您的浏览器不支持语音识别功能");
+      // 显示提示信息
+      const messageText = promptText ? `${promptText}\n请开始说话...(10秒)` : `请开始说话...(10秒)`;
+      showStatus(messageText);
+
+      // 重置语音结果
+      voiceResult.value = "";
+      
+      // 确保语音输入组件已加载
+      if (!voiceInputRef.value) {
+        console.error("语音输入组件未加载");
+        showStatus("语音输入组件未加载", 'error');
+        endRecordingStatus();
         resolve("");
         return;
       }
 
-      const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
-      recognition = new SpeechRecognition();
-
-      recognition.lang = language;
-      recognition.interimResults = false;
-      recognition.maxAlternatives = 1;
+      // 直接调用组件的toggleSpeechInput方法开始录音
+      voiceInputRef.value.toggleSpeechInput();
 
-      let countdown = 10;
-
-      // 固定的消息提示框
-      const messageText = promptText ? `${promptText}\n请开始说话...(${countdown}秒)` : `请开始说话...(${countdown}秒)`;
-      showStatus(messageText);
-
-      // 倒计时
-      const timer = setInterval(() => {
-        countdown--;
-        if (countdown > 0) {
-          const newText = promptText ? `${promptText}\n请开始说话...(${countdown}秒)` : `请开始说话...(${countdown}秒)`;
-          showStatus(newText);
-        } else {
-          clearInterval(timer);
-          // 倒计时结束时也要调用endRecordingStatus
+      // 设置超时,10秒后自动结束
+      const timeoutId = setTimeout(() => {
+        // 检查是否仍在录音
+        if (isVoiceRecording.value) {
+          // 停止录音
+          voiceInputRef.value.toggleSpeechInput();
+        }
+        // 等待一小段时间确保结果已处理
+        setTimeout(() => {
+          showStatus("语音识别完成");
+          endRecordingStatus();
+          resolve(voiceResult.value || "");
+        }, 500);
+      }, 10000);
+
+      // 监听录音状态变化,当录音结束时返回结果
+      let checkCount = 0;
+      const maxChecks = 40; // 最多检查20秒
+      const checkRecordingStatus = setInterval(() => {
+        checkCount++;
+        
+        // 只有当录音已经开始过,并且现在已经停止时才返回结果
+        if (isVoiceRecording.value === false && checkCount > 2) {
+          clearInterval(checkRecordingStatus);
+          clearTimeout(timeoutId);
+          showStatus("语音识别完成");
           endRecordingStatus();
+          resolve(voiceResult.value || "");
         }
-      }, 1000);
-
-      recognition.onresult = (event) => {
-        const speechResult = event.results[0][0].transcript;
-        console.log("语音识别结果:", speechResult);
-        showStatus("语音识别完成");
-        endRecordingStatus(); // 添加这行,在识别成功时结束语音状态
-        resolve(speechResult);
-      };
-
-      recognition.onerror = (event) => {
-        console.error("语音识别错误:", event.error);
-        Message().warning('请检查麦克风是否正常工作!', true);
-        showStatus("语音识别发生错误: " + event.error, 'error');
-        endRecordingStatus(); // 添加这行,在识别错误时结束语音状态
-        resolve("");
-      };
-
-      // 添加onend事件处理程序,确保语音识别无论如何结束都会清除状态
-      recognition.onend = () => {
-        console.log("语音识别结束");
-        endRecordingStatus(); // 确保在识别结束时调用
-        clearInterval(timer); // 确保清除倒计时定时器
-      };
-
-      recognition.start();
+        
+        // 防止无限循环
+        if (checkCount >= maxChecks) {
+          clearInterval(checkRecordingStatus);
+          clearTimeout(timeoutId);
+          // 停止录音
+          if (isVoiceRecording.value) {
+            voiceInputRef.value.toggleSpeechInput();
+          }
+          showStatus("语音识别完成");
+          endRecordingStatus();
+          resolve(voiceResult.value || "");
+        }
+      }, 500);
     });
   },
 
@@ -2060,6 +2079,42 @@ textarea {
   margin-right: auto;
 }
 
+.voice-input-wrapper {
+  margin-top: 15px;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
+
+/* 针对Blockly页面的语音输入按钮样式覆盖 */
+.recording-status-container .voice-input-wrapper ::v-deep(.voice-input-container .speech-btn) {
+  background: rgba(255, 255, 255, 0.2) !important;
+  border: 1px solid rgba(255, 255, 255, 0.4) !important;
+  color: white !important;
+}
+
+.recording-status-container .voice-input-wrapper ::v-deep(.voice-input-container .speech-btn:hover) {
+  background: rgba(255, 255, 255, 0.3) !important;
+  border-color: rgba(255, 255, 255, 0.6) !important;
+}
+
+.recording-status-container .voice-input-wrapper ::v-deep(.voice-input-container .speech-btn.recording) {
+  background: rgba(255, 238, 186, 0.3) !important;
+  border-color: rgba(255, 193, 7, 0.6) !important;
+}
+
+.recording-status-container .voice-input-wrapper ::v-deep(.voice-input-container .speech-btn .el-icon) {
+  color: white !important;
+}
+
+.recording-status-container .voice-input-wrapper ::v-deep(.voice-input-container .speech-btn.recording .el-icon) {
+  color: #dc3545 !important;
+}
+
+.recording-status-container .voice-input-wrapper ::v-deep(.voice-input-container .countdown-text) {
+  color: white !important;
+}
+
 .equalizer {
   display: flex;
   justify-content: center;

+ 132 - 62
src/views/blockly/Blockly2.vue

@@ -1,19 +1,17 @@
 <template>
   <!-- 收音状态显示区域 -->
-  <div v-if="isRecording" class="recording-status-container">
+  <div v-show="isRecording" class="recording-status-container">
     <div class="recording-text">正在收音...</div>
-    <div class="waveform-container">
-      <LiveWaveform
-        :active="isRecording" 
-        :processing="false"
-        :height="50"
-        :barWidth="3"
-        :barGap="1"
-        :barRadius="1"
-        :sensitivity="1.2"
+    <div class="voice-input-wrapper">
+      <VoiceInputApi
+          ref="voiceInputRef"
+          inputSelector="input[type='text']"
+          lang="zh-CN"
+          maxDuration="10"
+          @voiceRecognized="handleVoiceRecognized"
+          @recordingStatusChanged="handleRecordingStatusChanged"
       />
     </div>
-    <div v-if="recordingCountdown <= 5" class="recording-countdown">{{ recordingCountdown }}秒</div>
   </div>
 
   <!-- 智能台灯 -->
@@ -237,8 +235,10 @@ import television from '@/assets/images/smart-home/television.png' // 电视画
 import curtainFront from '@/assets/images/smart-home/curtain-front.png' // 前面窗帘遮挡
 
 import LiveWaveform from '../../components/ai/voice/LiveWaveform.vue';
+import VoiceInputApi from "@/components/ai/voice/VoiceInput_Api.vue";
 
 const router = useRouter();
+const voiceInputRef = ref(null);
 
 // 设备信息
 const device = ref({
@@ -254,6 +254,11 @@ const isRecording = ref(false);
 const recordingCountdown = ref(10);
 let countdownInterval = null;
 let recognition = null;
+// 语音输入状态跟踪
+const isVoiceRecording = ref(false); // 当前是否正在录音
+const voiceRecognizedText = ref(""); // 实时语音识别结果
+// 存储语音识别结果
+const voiceResult = ref("");
 
 // 返回虚拟实验室
 const goLabShow = () => {
@@ -302,6 +307,32 @@ function endRecordingStatus() {
     countdownInterval = null;
   }
 }
+
+// 语音输入识别结果处理
+const handleVoiceRecognized = (data) => {
+  if (isVoiceRecording.value) {
+    // 在同一次录音过程中,实时更新文本框内容
+    voiceRecognizedText.value = data.originalText;
+    voiceResult.value = data.processedText;
+  } else {
+    // 在录音结束时,将最终的语音内容保存
+    voiceResult.value = data.processedText;
+    // 清空临时变量
+    voiceRecognizedText.value = "";
+  }
+};
+
+// 处理录音状态变化
+const handleRecordingStatusChanged = (isRecording) => {
+  const wasRecording = isVoiceRecording.value;
+  isVoiceRecording.value = isRecording;
+
+  // 如果是从录音状态切换到非录音状态,清空临时变量
+  if (wasRecording && !isRecording) {
+    // 清空临时变量
+    voiceRecognizedText.value = "";
+  }
+};
 // 查看代码编程界面显示状态
 const handleViewCode = () => {
   showLampPreview.value = false;
@@ -483,64 +514,68 @@ const aiService = {
   // 前端语音采集
   captureVoice(language, promptText) {
     return new Promise((resolve) => {
-      if (
-          !"webkitSpeechRecognition" in window &&
-          !"SpeechRecognition" in window
-      ) {
-        showStatus("您的浏览器不支持语音识别功能");
+      // 显示提示信息
+      const messageText = promptText ? `${promptText}\n请开始说话...(10秒)` : `请开始说话...(10秒)`;
+      showStatus(messageText);
+
+      // 重置语音结果
+      voiceResult.value = "";
+      
+      // 确保语音输入组件已加载
+      if (!voiceInputRef.value) {
+        console.error("语音输入组件未加载");
+        showStatus("语音输入组件未加载", 'error');
+        endRecordingStatus();
         resolve("");
         return;
       }
 
-      const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
-      recognition = new SpeechRecognition();
+      // 直接调用组件的toggleSpeechInput方法开始录音
+      voiceInputRef.value.toggleSpeechInput();
 
-      recognition.lang = language;
-      recognition.interimResults = false;
-      recognition.maxAlternatives = 1;
-
-      let countdown = 10;
-
-      // 固定的消息提示框
-      const messageText = promptText ? `${promptText}\n请开始说话...(${countdown}秒)` : `请开始说话...(${countdown}秒)`;
-      showStatus(messageText);
-
-      // 倒计时
-      const timer = setInterval(() => {
-        countdown--;
-        if (countdown > 0) {
-          const newText = promptText ? `${promptText}\n请开始说话...(${countdown}秒)` : `请开始说话...(${countdown}秒)`;
-          showStatus(newText);
-        } else {
-          clearInterval(timer);
-          // 倒计时结束时也要调用endRecordingStatus
+      // 设置超时,10秒后自动结束
+      const timeoutId = setTimeout(() => {
+        // 检查是否仍在录音
+        if (isVoiceRecording.value) {
+          // 停止录音
+          voiceInputRef.value.toggleSpeechInput();
+        }
+        // 等待一小段时间确保结果已处理
+        setTimeout(() => {
+          showStatus("语音识别完成");
           endRecordingStatus();
+          resolve(voiceResult.value || "");
+        }, 500);
+      }, 10000);
+
+      // 监听录音状态变化,当录音结束时返回结果
+      let checkCount = 0;
+      const maxChecks = 40; // 最多检查20秒
+      const checkRecordingStatus = setInterval(() => {
+        checkCount++;
+        
+        // 只有当录音已经开始过,并且现在已经停止时才返回结果
+        if (isVoiceRecording.value === false && checkCount > 2) {
+          clearInterval(checkRecordingStatus);
+          clearTimeout(timeoutId);
+          showStatus("语音识别完成");
+          endRecordingStatus();
+          resolve(voiceResult.value || "");
         }
-      }, 1000);
-
-      recognition.onresult = (event) => {
-        const speechResult = event.results[0][0].transcript;
-        console.log("语音识别结果:", speechResult);
-        showStatus("语音识别完成");
-        endRecordingStatus(); // 在识别成功时结束语音状态
-        resolve(speechResult);
-      };
-
-      recognition.onerror = (event) => {
-        console.error("语音识别错误:", event.error);
-        Message().warning('请检查麦克风是否正常工作!', true);
-        showStatus("语音识别发生错误: " + event.error, 'error');
-        endRecordingStatus(); // 在识别错误时结束语音状态
-        resolve("");
-      };
-
-      // onend事件处理程序,确保语音识别无论如何结束都会清除状态
-      recognition.onend = () => {
-        console.log("语音识别结束");
-        endRecordingStatus(); // 确保在识别结束时调用
-        clearInterval(timer); // 确保清除倒计时定时器
-      };
-      recognition.start();
+        
+        // 防止无限循环
+        if (checkCount >= maxChecks) {
+          clearInterval(checkRecordingStatus);
+          clearTimeout(timeoutId);
+          // 停止录音
+          if (isVoiceRecording.value) {
+            voiceInputRef.value.toggleSpeechInput();
+          }
+          showStatus("语音识别完成");
+          endRecordingStatus();
+          resolve(voiceResult.value || "");
+        }
+      }, 500);
     });
   },
 
@@ -2385,6 +2420,41 @@ textarea {
   margin-left: auto;
   margin-right: auto;
 }
+.voice-input-wrapper {
+  margin-top: 15px;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
+
+/* 针对Blockly2页面的语音输入按钮样式覆盖 */
+.recording-status-container .voice-input-wrapper ::v-deep(.voice-input-container .speech-btn) {
+  background: rgba(255, 255, 255, 0.2) !important;
+  border: 1px solid rgba(255, 255, 255, 0.4) !important;
+  color: white !important;
+}
+
+.recording-status-container .voice-input-wrapper ::v-deep(.voice-input-container .speech-btn:hover) {
+  background: rgba(255, 255, 255, 0.3) !important;
+  border-color: rgba(255, 255, 255, 0.6) !important;
+}
+
+.recording-status-container .voice-input-wrapper ::v-deep(.voice-input-container .speech-btn.recording) {
+  background: rgba(255, 255, 255, 0.3) !important;
+  border-color: rgba(255, 255, 255, 0.6) !important;
+}
+
+.recording-status-container .voice-input-wrapper ::v-deep(.voice-input-container .speech-btn .el-icon) {
+  color: white !important;
+}
+
+.recording-status-container .voice-input-wrapper ::v-deep(.voice-input-container .speech-btn.recording .el-icon) {
+  color: #dc3545 !important;
+}
+
+.recording-status-container .voice-input-wrapper ::v-deep(.voice-input-container .countdown-text) {
+  color: white !important;
+}
 
 .equalizer {
   display: flex;