From 48354e634b72f119cbe6ef36fee44900de25ec60 Mon Sep 17 00:00:00 2001 From: yuzhiran Date: Sun, 5 Jul 2026 17:45:03 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=BD=95=E9=9F=B3=E5=8F=AF=E9=9D=A0?= =?UTF-8?q?=E6=80=A7=E4=BF=AE=E5=A4=8D=20+=20ASR=20=E5=90=8E=E7=AB=AF?= =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 前端: - recorder.onError 时重置 recorder = null,避免错误后录音永久失效 - 增加 asrLoading 状态,ASR 处理中输入框 disabled 并显示'语音识别中...' - 增加 skipAsr 标志,<1s 的录音跳过 ASR 上传 - uploadFile 增加 30s timeout 后端: - ASR: OpenAI 失败后 fallback 到本地 whisper,日志级别降为 warn - try/catch/finally 重构,确保临时文件在所有路径都清理 - OpenAI 和 whisper 结果分别 try,互不影响 --- backend/src/modules/tts/tts.controller.ts | 45 ++++++++++++-------- zhiyin-app/src/pages/interview/interview.vue | 18 +++++++- 2 files changed, 43 insertions(+), 20 deletions(-) diff --git a/backend/src/modules/tts/tts.controller.ts b/backend/src/modules/tts/tts.controller.ts index 2a5ea62..2e796ae 100644 --- a/backend/src/modules/tts/tts.controller.ts +++ b/backend/src/modules/tts/tts.controller.ts @@ -69,29 +69,38 @@ export class TtsController { } try { + let text = '' if (process.env.OPENAI_API_KEY) { - const result = execSync( - `curl -s -X POST https://api.openai.com/v1/audio/transcriptions \ - -H "Authorization: Bearer ${process.env.OPENAI_API_KEY}" \ - -H "Content-Type: multipart/form-data" \ - -F "file=@${wavPath}" \ - -F "model=whisper-1" \ - -F "language=zh"`, - { encoding: 'utf8', timeout: 30000 }, + try { + const result = execSync( + `curl -s -X POST https://api.openai.com/v1/audio/transcriptions \ + -H "Authorization: Bearer ${process.env.OPENAI_API_KEY}" \ + -H "Content-Type: multipart/form-data" \ + -F "file=@${wavPath}" \ + -F "model=whisper-1" \ + -F "language=zh"`, + { encoding: 'utf8', timeout: 30000 }, + ) + const parsed = JSON.parse(result) + if (parsed.text) text = parsed.text.trim() + } catch (e: any) { + this.logger.warn(`OpenAI ASR failed, falling back to local: ${e.message}`) + } + } + if (!text) { + const whisperResult = execSync( + `python3 -c 'import sys, whisper; model = whisper.load_model("tiny"); print(model.transcribe(sys.argv[1], language="zh")["text"].strip())' "${wavPath}"`, + { encoding: 'utf8', timeout: 60000 }, ) - const parsed = JSON.parse(result) - if (parsed.text) return { text: parsed.text.trim() } - } - const whisperResult = execSync(`python3 -c 'import sys, whisper; model = whisper.load_model("tiny"); print(model.transcribe(sys.argv[1], language="zh")["text"].strip())' "${wavPath}"`, { encoding: 'utf8', timeout: 60000 }) - if (whisperResult && whisperResult.trim()) { - return { text: whisperResult.trim() } + if (whisperResult?.trim()) text = whisperResult.trim() } + return { text } } catch (e: any) { this.logger.error(`ASR failed: ${e?.message || e}`) + return { text: '' } + } finally { + try { if (dest) fs.unlinkSync(dest) } catch {} + try { if (wavPath && wavPath !== dest) fs.unlinkSync(wavPath) } catch {} } - // 清理临时文件 - try { if (dest) fs.unlinkSync(dest) } catch {} - try { if (wavPath && wavPath !== dest) fs.unlinkSync(wavPath) } catch {} - return { text: '' } } } diff --git a/zhiyin-app/src/pages/interview/interview.vue b/zhiyin-app/src/pages/interview/interview.vue index 7c1dfb0..5bd9946 100644 --- a/zhiyin-app/src/pages/interview/interview.vue +++ b/zhiyin-app/src/pages/interview/interview.vue @@ -65,7 +65,7 @@ {{ isRecording ? recordingDuration + 's' : '按住' }} -