Improve microphone permission UX: add Dev Log, re-request flow, active Voice Input highlighting, and Puppeteer permission-check script
This commit is contained in:
115
server.js
115
server.js
@@ -7,28 +7,51 @@ const googleTTS = require('google-tts-api');
|
||||
require('dotenv').config();
|
||||
|
||||
const HUGGINGFACE_API_TOKEN = process.env.HUGGINGFACEHUB_API_TOKEN || process.env.HF_API_TOKEN;
|
||||
const huggingFaceLangMap = {
|
||||
'en': 'eng',
|
||||
'es': 'spa',
|
||||
'fr': 'fra',
|
||||
'de': 'deu',
|
||||
'pt': 'por',
|
||||
'it': 'ita',
|
||||
'nl': 'nld',
|
||||
'ru': 'rus',
|
||||
'ar': 'ara',
|
||||
'ja': 'jpn',
|
||||
'ko': 'kor',
|
||||
'zh': 'zho',
|
||||
'hi': 'hin',
|
||||
'ta': 'tam',
|
||||
'te': 'tel',
|
||||
'ml': 'mal',
|
||||
'pa': 'pan',
|
||||
'kn': 'kan',
|
||||
'id': 'ind',
|
||||
'vi': 'vie',
|
||||
'tr': 'tur'
|
||||
const nllbLangMap = {
|
||||
'en': 'eng_Latn',
|
||||
'es': 'spa_Latn',
|
||||
'fr': 'fra_Latn',
|
||||
'de': 'deu_Latn',
|
||||
'pt': 'por_Latn',
|
||||
'it': 'ita_Latn',
|
||||
'nl': 'nld_Latn',
|
||||
'ru': 'rus_Cyrl',
|
||||
'ar': 'arb_Arab',
|
||||
'ja': 'jpn_Jpan',
|
||||
'ko': 'kor_Kore',
|
||||
'zh': 'zho_Hans',
|
||||
'hi': 'hin_Deva',
|
||||
'ta': 'tam_Taml',
|
||||
'te': 'tel_Telu',
|
||||
'ml': 'mal_Mlym',
|
||||
'pa': 'pan_Guru',
|
||||
'kn': 'kan_Knda',
|
||||
'id': 'ind_Latn',
|
||||
'vi': 'vie_Latn',
|
||||
'tr': 'tur_Latn'
|
||||
};
|
||||
const m2m100LangMap = {
|
||||
'en': 'en',
|
||||
'es': 'es',
|
||||
'fr': 'fr',
|
||||
'de': 'de',
|
||||
'pt': 'pt',
|
||||
'it': 'it',
|
||||
'nl': 'nl',
|
||||
'ru': 'ru',
|
||||
'ar': 'ar',
|
||||
'ja': 'ja',
|
||||
'ko': 'ko',
|
||||
'zh': 'zh',
|
||||
'hi': 'hi',
|
||||
'ta': 'ta',
|
||||
'te': 'te',
|
||||
'ml': 'ml',
|
||||
'pa': 'pa',
|
||||
'kn': 'kn',
|
||||
'id': 'id',
|
||||
'vi': 'vi',
|
||||
'tr': 'tr'
|
||||
};
|
||||
|
||||
const app = express();
|
||||
@@ -77,18 +100,35 @@ const translateViaMyMemory = async (text, sourceLang, targetLang) => {
|
||||
}
|
||||
};
|
||||
|
||||
// Hugging Face translation via M2M100
|
||||
// Hugging Face translation via NLLB or M2M100
|
||||
const translateViaHuggingFace = async (text, sourceLang, targetLang) => {
|
||||
if (!HUGGINGFACE_API_TOKEN) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const srcCode = huggingFaceLangMap[sourceLang] || 'eng';
|
||||
const tgtCode = huggingFaceLangMap[targetLang] || 'eng';
|
||||
const isEnglishCentric = sourceLang === 'en' || targetLang === 'en';
|
||||
const m2mSrcCode = m2m100LangMap[sourceLang];
|
||||
const m2mTgtCode = m2m100LangMap[targetLang];
|
||||
const nllbSrcCode = nllbLangMap[sourceLang];
|
||||
const nllbTgtCode = nllbLangMap[targetLang];
|
||||
|
||||
const useM2M100 = isEnglishCentric && m2mSrcCode && m2mTgtCode;
|
||||
const model = useM2M100
|
||||
? 'https://api-inference.huggingface.co/models/facebook/m2m100_418M'
|
||||
: 'https://api-inference.huggingface.co/models/facebook/nllb-200-distilled-600M';
|
||||
const srcCode = useM2M100 ? m2mSrcCode : nllbSrcCode;
|
||||
const tgtCode = useM2M100 ? m2mTgtCode : nllbTgtCode;
|
||||
|
||||
if (!srcCode || !tgtCode) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const chosenModel = useM2M100 ? 'facebook/m2m100_418M' : 'facebook/nllb-200-distilled-600M';
|
||||
console.log(`Hugging Face translation using ${useM2M100 ? 'M2M100' : 'NLLB'} model: ${chosenModel} (${srcCode} -> ${tgtCode})`);
|
||||
|
||||
try {
|
||||
const response = await axios.post(
|
||||
'https://api-inference.huggingface.co/models/facebook/m2m100_418M',
|
||||
model,
|
||||
{
|
||||
inputs: text,
|
||||
parameters: {
|
||||
@@ -106,17 +146,16 @@ const translateViaHuggingFace = async (text, sourceLang, targetLang) => {
|
||||
);
|
||||
|
||||
const data = response.data;
|
||||
let resultText = null;
|
||||
if (Array.isArray(data) && data[0]?.generated_text) {
|
||||
return data[0].generated_text;
|
||||
}
|
||||
if (data?.generated_text) {
|
||||
return data.generated_text;
|
||||
}
|
||||
if (typeof data === 'string') {
|
||||
return data;
|
||||
resultText = data[0].generated_text;
|
||||
} else if (data?.generated_text) {
|
||||
resultText = data.generated_text;
|
||||
} else if (typeof data === 'string') {
|
||||
resultText = data;
|
||||
}
|
||||
|
||||
return null;
|
||||
return resultText ? { translatedText: resultText, model: chosenModel } : null;
|
||||
} catch (error) {
|
||||
console.error('Hugging Face translation error:', error.message || error);
|
||||
return null;
|
||||
@@ -325,16 +364,20 @@ app.post('/api/translate', async (req, res) => {
|
||||
console.log(`Translating "${text.substring(0, 50)}..." from ${sourceLang} to ${targetLang}`);
|
||||
|
||||
// Try Hugging Face first if the key is configured
|
||||
let translatedText = await translateViaHuggingFace(text, sourceLang, targetLang);
|
||||
const hfResult = await translateViaHuggingFace(text, sourceLang, targetLang);
|
||||
let translatedText = hfResult?.translatedText || null;
|
||||
let translatedWith = hfResult?.model ? `Hugging Face (${hfResult.model})` : null;
|
||||
|
||||
// Fallback to LibreTranslate if available locally
|
||||
if (!translatedText) {
|
||||
translatedText = await translateViaLibreTranslate(text, sourceLang, targetLang);
|
||||
translatedWith = translatedText ? 'LibreTranslate' : translatedWith;
|
||||
}
|
||||
|
||||
// Fallback to MyMemory if other services are unavailable
|
||||
if (!translatedText) {
|
||||
translatedText = await translateViaMyMemory(text, sourceLang, targetLang);
|
||||
translatedWith = translatedText ? 'MyMemory' : translatedWith;
|
||||
}
|
||||
|
||||
if (!translatedText) {
|
||||
@@ -344,7 +387,7 @@ app.post('/api/translate', async (req, res) => {
|
||||
});
|
||||
}
|
||||
|
||||
res.json({ translatedText });
|
||||
res.json({ translatedText, translatedWith });
|
||||
} catch (error) {
|
||||
console.error('Translation error:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
|
||||
Reference in New Issue
Block a user