Spaces:
Sleeping
Sleeping
| // FINAL VERSION: v30 - Robust error handling and UI updates | |
| // Helper function to show detailed errors | |
| function showDetailedError(title, content) { | |
| wx.showModal({ | |
| title: title, | |
| content: typeof content === 'object' ? JSON.stringify(content) : String(content), | |
| showCancel: false | |
| }); | |
| } | |
| Page({ | |
| data: { | |
| languages: { | |
| 'zh': { name: '中文', flag: 'cn' }, | |
| 'en': { name: 'English', flag: 'us' }, | |
| 'ja': { name: '日本語', flag: 'jp' }, | |
| 'ko': { name: '한국어', flag: 'kr' } | |
| }, | |
| sourceLang: 'zh', | |
| targetLang: 'en', | |
| transcript: '', | |
| outputText: '', | |
| isRecording: false, | |
| hfSpaceUrl: 'https://dazaozi-wechat-translator-app.hf.space', | |
| }, | |
| onLoad: function () { | |
| this.recorderManager = wx.getRecorderManager(); | |
| this.initRecorderManager(); | |
| this.setData({ | |
| sourceLanguages: Object.keys(this.data.languages).map(key => ({ ...this.data.languages[key], langCode: key })), | |
| targetLanguages: Object.keys(this.data.languages).map(key => ({ ...this.data.languages[key], langCode: key })) | |
| }); | |
| }, | |
| // --- Language Selection & UI --- | |
| selectSourceLanguage: function (e) { this.setData({ sourceLang: e.currentTarget.dataset.langCode }); }, | |
| selectTargetLanguage: function (e) { this.setData({ targetLang: e.currentTarget.dataset.langCode }); }, | |
| swapLanguages: function () { | |
| this.setData({ sourceLang: this.data.targetLang, targetLang: this.data.sourceLang, transcript: this.data.outputText, outputText: this.data.transcript }); | |
| }, | |
| // --- Recorder Initialization --- | |
| initRecorderManager: function () { | |
| this.recorderManager.onStart(() => { | |
| this.setData({ isRecording: true, transcript: '正在聆听...', outputText: '' }); | |
| }); | |
| this.recorderManager.onStop((res) => { | |
| this.setData({ isRecording: false }); | |
| if (res.tempFilePath) { | |
| this.uploadAudioForASR(res.tempFilePath); | |
| } else { | |
| this.setData({ transcript: '录音时间太短或无效' }); | |
| } | |
| }); | |
| this.recorderManager.onError((res) => { | |
| this.setData({ isRecording: false }); | |
| showDetailedError('录音发生错误', res); | |
| }); | |
| }, | |
| // --- Record Button Handler --- | |
| handleRecordToggle: function() { | |
| if (this.data.isRecording) { | |
| this.stopRecording(); | |
| return; | |
| } | |
| wx.getSetting({ | |
| success: (res) => { | |
| if (!res.authSetting['scope.record']) { | |
| wx.authorize({ scope: 'scope.record', success: this.startRecording, fail: (err) => showDetailedError('授权失败', err) }); | |
| } else { | |
| this.startRecording(); | |
| } | |
| }, | |
| fail: (err) => showDetailedError('无法获取权限设置', err) | |
| }); | |
| }, | |
| // --- Start/Stop Recording --- | |
| startRecording: function () { | |
| const options = { | |
| duration: 60000, | |
| sampleRate: 16000, | |
| numberOfChannels: 1, | |
| encodeBitRate: 48000, | |
| format: 'mp3' | |
| }; | |
| this.recorderManager.start(options); | |
| }, | |
| stopRecording: function () { | |
| this.recorderManager.stop(); | |
| }, | |
| // --- ASR & Translation Flow --- | |
| uploadAudioForASR: function (filePath) { | |
| this.setData({ transcript: '正在识别...' }); | |
| wx.getFileSystemManager().readFile({ filePath, encoding: 'base64', success: (res) => { | |
| wx.request({ | |
| url: `${this.data.hfSpaceUrl}/api/asr`, | |
| method: 'POST', | |
| data: { "audio_base64": res.data }, | |
| timeout: 120000, | |
| success: (asrRes) => { | |
| console.log("ASR Response:", asrRes); // Log for debugging | |
| if (asrRes.statusCode === 200 && asrRes.data && typeof asrRes.data.text !== 'undefined') { | |
| const transcript = asrRes.data.text; | |
| if (transcript) { | |
| this.setData({ transcript }); | |
| this.translate(transcript); | |
| } else { | |
| // Handle successful response with empty transcript | |
| this.setData({ transcript: '未能识别到语音,请重试。' }); | |
| } | |
| } else { | |
| // Handle non-200 responses or malformed data | |
| this.setData({ transcript: '识别失败,请重试。' }); | |
| showDetailedError('语音识别失败', asrRes.data || '服务器返回异常'); | |
| } | |
| }, | |
| fail: (err) => { | |
| this.setData({ transcript: '识别请求失败。' }); | |
| if (err.errMsg && err.errMsg.includes('timeout')) { | |
| showDetailedError('识别超时', '服务器处理时间过长,请稍后再试或尝试更短的语音。'); | |
| } else { | |
| showDetailedError('识别请求失败', err); | |
| } | |
| } | |
| }); | |
| }}); | |
| }, | |
| translate: function (text) { | |
| if (!text) return; | |
| const { sourceLang, targetLang } = this.data; | |
| if (sourceLang === targetLang) { | |
| return this.setData({ outputText: text }); | |
| } | |
| this.setData({ outputText: '正在翻译...' }); | |
| wx.request({ | |
| url: `${this.data.hfSpaceUrl}/api/translate`, | |
| method: 'POST', | |
| data: { "text": text, "source_lang": sourceLang, "target_lang": targetLang }, | |
| timeout: 45000, | |
| success: (res) => { | |
| if (res.statusCode === 200 && res.data && res.data.translated_text) { | |
| this.setData({ outputText: res.data.translated_text }); | |
| } else { | |
| this.setData({ outputText: '翻译失败' }); | |
| showDetailedError('翻译失败', res.data || '服务器返回异常'); | |
| } | |
| }, | |
| fail: (err) => { | |
| this.setData({ outputText: '翻译请求失败' }); | |
| showDetailedError('翻译请求失败', err); | |
| } | |
| }); | |
| } | |
| }); |