feat: 集成语音识别

This commit is contained in:
2026-05-14 17:42:49 +08:00
parent 29338ec706
commit 11c1a3401d
7 changed files with 735 additions and 5 deletions

View File

@@ -74,6 +74,15 @@
<!-- 录音按钮 -->
<RecordingWaveBtn v-if="visibleWaveBtn" ref="recordingWaveBtnRef" />
<!-- #ifdef APP-PLUS -->
<yao-asdRealSpeech
ref="appSpeechRef"
:options="appSpeechOptions"
@result="handleAppSpeechResult"
@change="handleAppSpeechChange"
/>
<!-- #endif -->
<view class="color-99A0AE font-size-9 text-center text-gray-400">
内容由AI大模型生成请仔细鉴别
</view>
@@ -84,15 +93,25 @@
import { ref, computed, watch, nextTick, onMounted, defineExpose, onUnmounted } from "vue";
import RecordingWaveBtn from "@/components/Speech/RecordingWaveBtn.vue";
import { getCurrentConfig } from "@/constant/base";
import { appSpeechRecognitionOptions } from "@/constant/speech";
let manager = null;
let speechProvider = "";
const isSpeechRecognitionSupported = ref(false);
const appSpeechOptions = appSpeechRecognitionOptions;
// WechatSI 是微信小程序插件App 原生基座没有 requirePlugin。
// #ifdef MP-WEIXIN
const plugin = requirePlugin("WechatSI");
manager = plugin.getRecordRecognitionManager();
isSpeechRecognitionSupported.value = !!manager;
speechProvider = manager ? "wechat" : "";
// #endif
// App 端使用 yao-asdRealSpeechapikey 请在 src/constant/speech.js 中配置。
// #ifdef APP-PLUS
isSpeechRecognitionSupported.value = true;
speechProvider = "app";
// #endif
const props = defineProps({
@@ -112,6 +131,7 @@ const emit = defineEmits([
const textareaRef = ref(null);
const recordingWaveBtnRef = ref(null);
const appSpeechRef = ref(null);
const placeholder = computed(() => {
const config = getCurrentConfig();
return `快告诉${config.name}您在想什么~`;
@@ -122,7 +142,10 @@ const keyboardHeight = ref(0);
const isVoiceMode = ref(false);
const visibleWaveBtn = ref(false);
const isRecording = ref(false);
const appRecognizedText = ref("");
let watchDogTimer = null;
let appStopFallbackTimer = null;
let hasSentAppRecognition = false;
const resetUI = () => {
isRecording.value = false;
@@ -148,6 +171,13 @@ const startWatchDog = (timeout = 10000) => {
}, timeout);
};
const clearAppStopFallback = () => {
if (appStopFallbackTimer) {
clearTimeout(appStopFallbackTimer);
appStopFallbackTimer = null;
}
};
// 保持和父组件同步
watch(
() => props.modelValue,
@@ -172,9 +202,38 @@ const toggleVoiceMode = () => {
// 处理语音按钮长按开始
const handleVoiceTouchStart = () => {
if (!manager) return;
if (!isSpeechRecognitionSupported.value) return;
try {
manager.start({ lang: "zh_CN" });
if (speechProvider === "wechat") {
if (!manager) return;
manager.start({ lang: "zh_CN" });
} else if (speechProvider === "app") {
if (!appSpeechOptions.apikey) {
uni.showToast({
title: "请先配置语音识别API Key",
icon: "none",
});
return;
}
appRecognizedText.value = "";
hasSentAppRecognition = false;
const appSpeech = appSpeechRef.value;
if (!appSpeech || typeof appSpeech.start !== "function") {
uni.showToast({
title: "语音组件未初始化",
icon: "none",
});
return;
}
appSpeech.start();
} else {
return;
}
isRecording.value = true;
visibleWaveBtn.value = true;
@@ -194,7 +253,7 @@ const handleVoiceTouchStart = () => {
// 处理语音按钮长按结束
const handleVoiceTouchEnd = () => {
if (!manager) {
if (!isSpeechRecognitionSupported.value) {
resetUI();
return;
}
@@ -213,7 +272,16 @@ const handleVoiceTouchEnd = () => {
}
try {
manager.stop();
if (speechProvider === "wechat") {
manager?.stop?.();
} else if (speechProvider === "app") {
appSpeechRef.value?.stop?.();
clearAppStopFallback();
appStopFallbackTimer = setTimeout(() => {
appStopFallbackTimer = null;
sendAppRecognizedText();
}, 600);
}
} catch (err) {
console.error("record stop error:", err);
} finally {
@@ -255,6 +323,60 @@ const initRecord = () => {
};
};
const getAppSpeechText = (res) => {
return (
res?.sentence?.text ||
res?.text ||
res?.result ||
res?.payload?.output?.sentence?.text ||
""
);
};
const sendAppRecognizedText = () => {
if (hasSentAppRecognition) return;
const text = appRecognizedText.value.trim();
if (!text) {
console.log("没有说话");
return;
}
hasSentAppRecognition = true;
clearAppStopFallback();
inputMessage.value = text;
emit("send", text);
appRecognizedText.value = "";
};
const handleAppSpeechResult = (res) => {
const text = getAppSpeechText(res);
if (!text) return;
appRecognizedText.value = text;
inputMessage.value = text;
};
const handleAppSpeechChange = (msg) => {
if (!msg || !msg.status) return;
if (msg.status === "START") {
isRecording.value = true;
return;
}
if (msg.status === "STOP") {
resetUI();
sendAppRecognizedText();
return;
}
if (msg.status === "ERROR") {
console.error("app speech recognition error", msg.msg);
resetUI();
}
};
// 监听键盘高度变化
onMounted(() => {
// 监听键盘弹起
@@ -281,6 +403,14 @@ onUnmounted(() => {
manager.onStop = null;
manager.onError = null;
}
if (appSpeechRef.value) {
try {
appSpeechRef.value.stop && appSpeechRef.value.stop();
} catch (e) {
// ignore
}
}
clearAppStopFallback();
resetUI();
});