fix: 录音可靠性修复 + ASR 后端优化

前端:
- recorder.onError 时重置 recorder = null,避免错误后录音永久失效
- 增加 asrLoading 状态,ASR 处理中输入框 disabled 并显示'语音识别中...'
- 增加 skipAsr 标志,<1s 的录音跳过 ASR 上传
- uploadFile 增加 30s timeout

后端:
- ASR: OpenAI 失败后 fallback 到本地 whisper,日志级别降为 warn
- try/catch/finally 重构,确保临时文件在所有路径都清理
- OpenAI 和 whisper 结果分别 try,互不影响
This commit is contained in:
yuzhiran
2026-07-05 17:45:03 +08:00
parent 8152278b86
commit 48354e634b
2 changed files with 43 additions and 20 deletions
+27 -18
View File
@@ -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: '' }
}
}