|
|
@@ -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;
|