Improve microphone permission UX: add Dev Log, re-request flow, active Voice Input highlighting, and Puppeteer permission-check script

This commit is contained in:
Chandu
2026-07-05 13:21:40 -04:00
parent c0e3aace8e
commit ac34e8f86a
7 changed files with 786 additions and 87 deletions

115
server.js
View File

@@ -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' });