// 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); } }); } });