Spaces:
Sleeping
Sleeping
const plugin = requirePlugin("WechatSI"); | |
const pluginManager = plugin.getRecordRecognitionManager(); // For CN/EN ASR & Translation | |
const nativeRecorderManager = wx.getRecorderManager(); // For JA/KO ASR (recording only) | |
Page({ | |
data: { | |
languages: { | |
'zh': { name: '中文', flag: 'cn', code: 'zh_CN' }, | |
'en': { name: 'English', flag: 'us', code: 'en_US' }, | |
'ja': { name: '日本語', flag: 'jp', code: 'ja_JP' }, | |
'ko': { name: '한국어', flag: 'kr', code: 'ko_KR' } | |
}, | |
langCodes: ['zh', 'en', 'ja', 'ko'], // Use short codes for internal logic | |
sourceLang: 'zh', | |
targetLang: 'en', | |
transcript: '', | |
outputText: '', | |
isRecording: false, | |
sourceLanguages: [], | |
targetLanguages: [], | |
hfSpaceUrl: 'https://dazaozi-wechat-translator-app.hf.space', | |
currentActiveManager: null // To track which manager is active | |
}, | |
onLoad: function () { | |
this.initializeLanguages(); | |
this.initPluginManager(); | |
this.initNativeRecorderManager(); | |
}, | |
// --- Language Selection Logic --- | |
initializeLanguages: function () { | |
const { langCodes, languages, sourceLang, targetLang } = this.data; | |
this.setData({ | |
sourceLanguages: langCodes.map(c => ({ | |
langCode: c, | |
name: languages[c].name, | |
flag: languages[c].flag, | |
selected: c === sourceLang | |
})), | |
targetLanguages: langCodes.map(c => ({ | |
langCode: c, | |
name: languages[c].name, | |
flag: languages[c].flag, | |
selected: c === targetLang | |
})) | |
}); | |
}, | |
selectSourceLanguage: function (e) { | |
const newSourceLang = e.currentTarget.dataset.langCode; | |
this.setData({ sourceLang: newSourceLang }, this.initializeLanguages); | |
}, | |
selectTargetLanguage: function (e) { | |
const newTargetLang = e.currentTarget.dataset.langCode; | |
this.setData({ targetLang: newTargetLang }, this.initializeLanguages); | |
}, | |
swapLanguages: function () { | |
const { sourceLang, targetLang } = this.data; | |
this.setData({ | |
sourceLang: targetLang, | |
targetLang: sourceLang, | |
transcript: this.data.outputText, | |
outputText: this.data.transcript | |
}, this.initializeLanguages); | |
}, | |
// --- Plugin Manager Initialization (for CN/EN) --- | |
initPluginManager: function () { | |
pluginManager.onStart = () => { | |
this.setData({ transcript: '正在聆听...', outputText: '' }); | |
}; | |
pluginManager.onRecognize = (res) => { | |
this.setData({ transcript: res.result }); | |
}; | |
pluginManager.onStop = (res) => { | |
this.setData({ isRecording: false }); | |
if (res.result) { | |
this.setData({ transcript: res.result }); | |
this.translate(res.result); // Explicitly call translate for plugin mode | |
} else { | |
this.setData({ transcript: '识别结果为空', outputText: '' }); | |
} | |
}; | |
pluginManager.onError = (res) => { | |
this.setData({ isRecording: false, transcript: '识别失败', outputText: res.msg }); | |
}; | |
}, | |
// --- Native Recorder Manager Initialization (for JA/KO recording) --- | |
initNativeRecorderManager: function () { | |
nativeRecorderManager.onStart = () => { | |
this.setData({ transcript: '正在聆听...', outputText: '' }); | |
}; | |
nativeRecorderManager.onStop = (res) => { | |
this.setData({ isRecording: false }); | |
if (res.tempFilePath) { | |
this.uploadAudioForASR(res.tempFilePath); | |
} else { | |
this.setData({ transcript: '录音文件获取失败' }); | |
} | |
}; | |
nativeRecorderManager.onError = (res) => { | |
this.setData({ isRecording: false, transcript: '录音失败', outputText: res.errMsg }); | |
}; | |
}, | |
// --- Unified Start Recording --- | |
startRecording: function () { | |
const { sourceLang, targetLang } = this.data; | |
const isChineseEnglish = (sourceLang === 'zh' && targetLang === 'en') || (sourceLang === 'en' && targetLang === 'zh'); | |
this.setData({ isRecording: true }); | |
if (isChineseEnglish) { | |
this.setData({ currentActiveManager: 'plugin' }); | |
pluginManager.start({ | |
lang: this.data.languages[sourceLang].code, | |
trans_lang: this.data.languages[targetLang].code, | |
}); | |
} else { | |
this.setData({ currentActiveManager: 'native' }); | |
nativeRecorderManager.start({ | |
format: 'mp3', // Ensure format is compatible with HF backend | |
sampleRate: 16000, | |
numberOfChannels: 1, | |
encodeBitRate: 96000, | |
}); | |
} | |
}, | |
// --- Unified Stop Recording --- | |
stopRecording: function () { | |
if (this.data.currentActiveManager === 'plugin') { | |
pluginManager.stop(); | |
} else if (this.data.currentActiveManager === 'native') { | |
nativeRecorderManager.stop(); | |
} | |
}, | |
// --- Plugin Translation Function (for CN/EN) --- | |
translate: function (text) { | |
const { sourceLang, targetLang } = this.data; | |
if (sourceLang === targetLang) { | |
this.setData({ outputText: text }); | |
return; | |
} | |
this.setData({ outputText: '正在翻译...' }); | |
plugin.translate({ | |
lfrom: this.data.languages[sourceLang].code, | |
lto: this.data.languages[targetLang].code, | |
content: text, | |
success: (res) => { | |
if (res.retcode === 0) { | |
this.setData({ outputText: res.result }); | |
} else { | |
console.error('翻译失败', res); | |
this.setData({ outputText: '翻译失败' }); | |
} | |
}, | |
fail: (err) => { | |
console.error('翻译接口调用失败', err); | |
this.setData({ outputText: '翻译出错' }); | |
} | |
}); | |
}, | |
// --- HF Bridge Translation Flow (for non-CN/EN pairs) --- | |
uploadAudioForASR: function (filePath) { | |
this.setData({ transcript: '正在识别 (1/3)...' }); | |
const fileSystemManager = wx.getFileSystemManager(); | |
fileSystemManager.readFile({ filePath, encoding: 'base64', success: (res) => { | |
wx.request({ | |
url: `${this.data.hfSpaceUrl}/api/asr`, | |
method: 'POST', | |
header: { 'Content-Type': 'application/json' }, | |
data: { "audio_data_uri": `data:audio/mp3;base64,${res.data}` }, | |
timeout: 60000, | |
success: (asrRes) => { | |
if (asrRes.statusCode === 200 && asrRes.data.transcript) { | |
const transcript = asrRes.data.transcript; | |
this.setData({ transcript }); | |
this.fullBackendBridge(transcript, this.data.sourceLang, this.data.targetLang); | |
} else { this.setData({ transcript: 'HF识别失败' }); } | |
}, | |
fail: () => { this.setData({ transcript: 'HF识别请求失败' }); } | |
}); | |
}}); | |
}, | |
fullBackendBridge: function(text, sourceLang, targetLang) { | |
this.setData({ outputText: '翻译中 (2/3)..' }); | |
// Step 1: Translate source (e.g., JA) to English via HF | |
this.translateViaHF(text, sourceLang, 'en', (englishResult) => { | |
if (englishResult) { | |
// Step 2: Translate English result to final target (e.g., ZH) via HF | |
this.setData({ outputText: '翻译中 (3/3)..' }); | |
this.translateViaHF(englishResult, 'en', targetLang, (finalResult) => { | |
if (finalResult) { | |
this.setData({ outputText: finalResult }); | |
} | |
}); | |
} | |
}); | |
}, | |
translateViaHF: function(text, sourceLang, targetLang, callback) { | |
wx.request({ | |
url: `${this.data.hfSpaceUrl}/api/translate`, | |
method: 'POST', | |
header: { 'Content-Type': 'application/json' }, | |
data: { "text": text, "source_lang": sourceLang, "target_lang": targetLang }, | |
timeout: 30000, | |
success: (res) => { | |
if (res.statusCode === 200 && res.data.translated_text) { | |
callback(res.data.translated_text); | |
} else { | |
this.setData({ outputText: `HF翻译失败 (${sourceLang}->${targetLang})` }); | |
callback(null); | |
} | |
}, | |
fail: () => { | |
this.setData({ outputText: `HF翻译请求失败 (${sourceLang}->${targetLang})` }); | |
callback(null); | |
} | |
}); | |
} | |
}); |