STTR / index.html
Babel Deployer
Deploy Railway Backend Connection
0a5aa16
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
<!-- ✨ WORLD-CHANGING MOBILE EXPERIENCE (PWA) -->
<meta name="theme-color" content="#0a0a0b">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="application-name" content="Babel">
<meta name="apple-mobile-web-app-title" content="Babel">
<!-- DISABLE BROWSER CACHING for instant loading -->
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0">
<title>Babel</title>
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Noto+Sans+Arabic:wght@400;500;600&family=JetBrains+Mono:wght@400;500&display=swap"
rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<link rel="stylesheet" href="./enhanced.css">
<link rel="manifest" href="./manifest.json">
<style>
/* CSS moved to static/enhanced.css */
</style>
</head>
<body>
<div class="app-layout">
<!-- –✨ GROK SIDEBAR -->
<aside class="grok-sidebar">
<div class="sidebar-top">
<div class="sidebar-logo">
<i class="fa-solid fa-cube"></i>
</div>
<button class="new-chat" onclick="location.reload()" title="New Session">
<i class="fa-solid fa-plus"></i>
</button>
</div>
<nav class="sidebar-menu">
<div class="menu-item active">
<i class="fa-solid fa-comment-dots"></i>
<span>Chat</span>
</div>
<div class="menu-item" id="sidebar-voice-mode">
<i class="fa-solid fa-microphone-lines"></i>
<span>Voice</span>
</div>
<!-- Settings Trigger Moved Here -->
<div class="menu-item" id="settings-trigger">
<i class="fa-solid fa-sliders"></i>
<span>Settings</span>
</div>
</nav>
<div class="sidebar-footer">
<div class="user-profile">
<div class="avatar">U</div>
<div class="info">
<span class="name">User</span>
<span class="status">Pro</span>
</div>
</div>
</div>
</aside>
<!-- –✨ MAIN CANVAS -->
<main class="grok-main">
<!-- Header for Mobile/Context -->
<header class="main-header">
<span class="model-badge">Babel v2.0</span>
</header>
<!-- CHAT STAGE -->
<div id="chat-stage" class="chat-stage">
<!-- Empty State (Centered Logo) -->
<div class="empty-state" id="greeting">
<div class="grok-logo-hero">Babel</div>
<p class="grok-hero-text">Intelligence Vocale Instantanee</p>
</div>
<!-- RESULT DISPLAY (Fallback if chat doesn't show) -->
<div id="result-display" style="display: none; padding: 20px; text-align: center;">
<div id="original-display" style="font-size: 1.1em; color: #aaa; margin-bottom: 10px;"></div>
<div id="translation-display" style="font-size: 1.5em; color: #fff; font-weight: bold;"></div>
</div>
<div id="chat-history" class="chat-history"></div>
</div>
<!-- GROK INPUT BAR AREA -->
<div class="grok-input-wrapper">
<!-- The Main Input Pill -->
<div class="grok-input-bar">
<div class="input-icon left">
<i class="fa-solid fa-paperclip"></i>
</div>
<!-- Status Text acting as Placeholder -->
<div class="input-status-area">
<span id="status-placeholder" class="status-text">Prêt à traduire...</span>
<span id="latency-badge"
style="display:none; font-size: 0.7em; color: #666; margin-left: 10px;">0ms</span>
</div>
<!-- THE ORB (Record Button) -->
<div class="input-icon right action" id="record-btn">
<i class="fa-solid fa-microphone"></i>
</div>
</div>
<!-- SUGGESTION CHIPS (Controls) -->
<div class="grok-chips">
<!-- Source Language -->
<div class="chip-select-wrapper">
<select id="source-lang-quick" class="chip-select">
<option value="auto">🎯 Auto</option>
<option value="ar-SA">🇲🇦 Darija</option>
<option value="fr-FR">🇫🇷 Français</option>
<option value="en-US">🇬🇧 English</option>
<option value="es-ES">🇪🇸 Español</option>
</select>
<i class="fa-solid fa-chevron-down"></i>
</div>
<!-- Swap -->
<button id="swap-langs" class="chip-icon" title="Swap">
<i class="fa-solid fa-arrow-right-arrow-left"></i>
</button>
<!-- Target Language (SeamlessM4T TTS supported) -->
<div class="chip-select-wrapper">
<select id="target-lang-quick" class="chip-select">
<option value="en-US" selected>🇬🇧 English</option>
<option value="fr-FR">🇫🇷 Français</option>
<option value="ar-SA">🇲🇦 Darija</option>
<option value="es-ES">🇪🇸 Español</option>
<option value="de-DE">🇩🇪 Deutsch</option>
<option value="it-IT">🇮🇹 Italiano</option>
<option value="pt-PT">🇵🇹 Português</option>
<option value="zh-CN">🇨🇳 中文</option>
<option value="ja-JP">🇯🇵 日本語</option>
<option value="ko-KR">🇰🇷 한국어</option>
<option value="ru-RU">🇷🇺 Русский</option>
<option value="tr-TR">🇹🇷 Türkçe</option>
</select>
<i class="fa-solid fa-chevron-down"></i>
</div>
<div class="chip-divider"></div>
<!-- Features -->
<button id="smart-mode-toggle" class="chip-pill active"
title="Auto-detect language and smart target">
<i class="fa-solid fa-brain"></i> Smart
</button>
<!-- Voice Cloning Toggle -->
<button id="voice-clone-toggle" class="chip-pill active" title="Clone voice from input audio">
<i class="fa-solid fa-user-secret"></i> Clone Voice
</button>
</div>
</div>
</main>
<!-- AUDIO PLAYER (Hidden - for TTS playback) -->
<audio id="audio-player" style="display: none;"></audio>
</div>
<!-- SETTINGS MODAL (Preserved ID) -->
<div class="modal" id="settings-modal">
<button id="close-settings" class="close-modal-btn">
<i class="fa-solid fa-xmark"></i>
</button>
<div class="modal-content">
<h2>Parametres</h2>
<div class="form-group">
<label>Votre Langue</label>
<select id="source-lang-selector" class="form-control" style="height: auto; max-height: 200px;">
<option value="auto">Detection Automatique</option>
<optgroup label="Langues Principales">
<option value="fr">Français</option>
<option value="ar">Darija / Arabe</option>
<option value="en">Anglais</option>
<option value="es">Espagnol</option>
<option value="de">Allemand</option>
<option value="it">Italien</option>
<option value="zh">Chinois</option>
<option value="ja">Japonais</option>
<option value="ru">Russe</option>
</optgroup>
<optgroup label="✨ Toutes les Langues">
<option value="af">Afrikaans</option>
<option value="sq">Albanian (Albanais)</option>
<option value="am">Amharic (Amharique)</option>
<option value="hy">Armenian (Arménien)</option>
<option value="az">Azerbaijani (Azéri)</option>
<option value="eu">Basque</option>
<option value="be">Belarusian (Biélorusse)</option>
<option value="bn">Bengali</option>
<option value="bs">Bosnian (Bosnien)</option>
<option value="bg">Bulgarian (Bulgare)</option>
<option value="ca">Catalan</option>
<option value="ceb">Cebuano</option>
<option value="co">Corsican (Corse)</option>
<option value="hr">Croatian (Croate)</option>
<option value="cs">Czech (Tchèque)</option>
<option value="da">Danish (Danois)</option>
<option value="nl">Dutch (Néerlandais)</option>
<option value="et">Estonian (Estonien)</option>
<option value="fi">Finnish (Finnois)</option>
<option value="gl">Galician (Galicien)</option>
<option value="ka">Georgian (Géorgien)</option>
<option value="el">Greek (Grec)</option>
<option value="gu">Gujarati</option>
<option value="ht">Haitian Creole (Créole Haïtien)</option>
<option value="ha">Hausa</option>
<option value="haw">Hawaiian (Hawaïen)</option>
<option value="he">Hebrew (Hébreu)</option>
<option value="hi">Hindi</option>
<option value="hmn">Hmong</option>
<option value="hu">Hungarian (Hongrois)</option>
<option value="is">Icelandic (Islandais)</option>
<option value="ig">Igbo</option>
<option value="id">Indonesian (Indonésien)</option>
<option value="ga">Irish (Irlandais)</option>
<option value="jw">Javanese (Javanais)</option>
<option value="kn">Kannada</option>
<option value="kk">Kazakh</option>
<option value="km">Khmer</option>
<option value="ko">Korean (Coréen)</option>
<option value="ku">Kurdish (Kurde)</option>
<option value="ky">Kyrgyz (Kirghize)</option>
<option value="lo">Lao</option>
<option value="la">Latin</option>
<option value="lv">Latvian (Letton)</option>
<option value="lt">Lithuanian (Lituanien)</option>
<option value="lb">Luxembourgish (Luxembourgeois)</option>
<option value="mk">Macedonian (Macédonien)</option>
<option value="mg">Malagasy (Malgache)</option>
<option value="ms">Malay (Malais)</option>
<option value="ml">Malayalam</option>
<option value="mt">Maltese (Maltais)</option>
<option value="mi">Maori</option>
<option value="mr">Marathi</option>
<option value="mn">Mongolian (Mongol)</option>
<option value="my">Myanmar (Birman)</option>
<option value="ne">Nepali (Népalais)</option>
<option value="no">Norwegian (Norvégien)</option>
<option value="ny">Nyanja (Chichewa)</option>
<option value="ps">Pashto (Pachto)</option>
<option value="fa">Persian (Persan/Farsi)</option>
<option value="pl">Polish (Polonais)</option>
<option value="pa">Punjabi</option>
<option value="ro">Romanian (Roumain)</option>
<option value="sm">Samoan</option>
<option value="gd">Scottish Gaelic</option>
<option value="sr">Serbian (Serbe)</option>
<option value="sn">Shona</option>
<option value="sd">Sindhi</option>
<option value="si">Sinhala (Cingalais)</option>
<option value="sk">Slovak (Slovaque)</option>
<option value="sl">Slovenian (Slovêne)</option>
<option value="so">Somali</option>
<option value="su">Sundanese (Soundanais)</option>
<option value="sw">Swahili</option>
<option value="sv">Swedish (Suédois)</option>
<option value="tl">Tagalog (Tagalog/Filipino)</option>
<option value="tg">Tajik (Tadjik)</option>
<option value="ta">Tamil</option>
<option value="te">Telugu</option>
<option value="th">Thai (Thaï)</option>
<option value="tr">Turkish (Turc)</option>
<option value="uk">Ukrainian (Ukrainien)</option>
<option value="ur">Urdu</option>
<option value="uz">Uzbek (Ouzbek)</option>
<option value="vi">Vietnamese (Vietnamien)</option>
<option value="cy">Welsh (Gallois)</option>
<option value="xh">Xhosa</option>
<option value="yi">Yiddish</option>
<option value="yo">Yoruba</option>
<option value="zu">Zulu</option>
<div class="form-group">
<label>Langue Cible</label>
<!-- ✨ UNIVERSAL LANGUAGE SELECTOR -->
<select id="target-lang-quick" class="lang-quick-select">
<option value="French">‡«‡· French</option>
<option value="Arabic">‡¸‡¦ Arabic</option>
<option value="English">‡¬‡§ English</option>
<option value="Darija">‡²‡¦ Darija</option>
<option value="Spanish">‡ª‡¸ Spanish</option>
<optgroup label="Ϭ African & Middle Eastern">
<option value="Amharic">‡ª‡¹ Amharic</option>
<option value="Arabic">‡¸‡¦ Arabic (Standard)</option>
<option value="Darija">‡²‡¦ Arabic (Moroccan Darija)</option>
<option value="Berber">™“ Amazigh (Berber)</option>
<option value="Egyptian">‡ª‡¬ Arabic (Egyptian)</option>
<option value="Hausa">‡³‡¬ Hausa</option>
<option value="Hebrew">‡®‡± Hebrew</option>
<option value="Igbo">‡³‡¬ Igbo</option>
<option value="Persian">‡®‡· Persian (Farsi)</option>
<option value="Somali">‡¸‡´ Somali</option>
<option value="Swahili">‡°‡ª Swahili</option>
<option value="Turkish">‡¹‡· Turkish</option>
<option value="Yoruba">‡³‡¬ Yoruba</option>
<option value="Zulu">‡¿‡¦ Zulu</option>
</optgroup>
<optgroup label="✨ Asian & Pacific">
<option value="Bengali">‡§‡© Bengali</option>
<option value="Chinese">‡¨‡³ Chinese (Mandarin)</option>
<option value="Cantonese">‡­‡° Chinese (Cantonese)</option>
<option value="Filipino">‡µ‡­ Filipino (Tagalog)</option>
<option value="Gujarati">‡®‡³ Gujarati</option>
<option value="Hindi">‡®‡³ Hindi</option>
<option value="Indonesian">‡®‡© Indonesian</option>
<option value="Japanese">‡¯‡µ Japanese</option>
<option value="Javanese">‡®‡© Javanese</option>
<option value="Kannada">‡®‡³ Kannada</option>
<option value="Khmer">‡°‡­ Khmer</option>
<option value="Korean">‡°‡· Korean</option>
<option value="Lao">‡±‡¦ Lao</option>
<option value="Malay">‡²‡¾ Malay</option>
<option value="Malayalam">‡®‡³ Malayalam</option>
<option value="Marathi">‡®‡³ Marathi</option>
<option value="Myanmar">‡²‡² Myanmar (Burmese)</option>
<option value="Nepali">‡³‡µ Nepali</option>
<option value="Punjabi">‡®‡³ Punjabi</option>
<option value="Sinhala">‡±‡° Sinhala</option>
<option value="Tamil">‡®‡³ Tamil</option>
<option value="Telugu">‡®‡³ Telugu</option>
<option value="Thai">‡¹‡­ Thai</option>
<option value="Urdu">‡µ‡° Urdu</option>
<option value="Vietnamese">‡»‡³ Vietnamese</option>
</optgroup>
<optgroup label="✨ European">
<option value="Albanian">‡¦‡± Albanian</option>
<option value="Armenian">‡¦‡² Armenian</option>
<option value="Azerbaijani">‡¦‡¿ Azerbaijani</option>
<option value="Bosnian">‡§‡¦ Bosnian</option>
<option value="Bulgarian">‡§‡¬ Bulgarian</option>
<option value="Catalan">‡ª‡¸ Catalan</option>
<option value="Croatian">‡­‡· Croatian</option>
<option value="Czech">‡¨‡¿ Czech</option>
<option value="Danish">‡©‡° Danish</option>
<option value="Dutch">‡³‡± Dutch</option>
<option value="Estonian">‡ª‡ª Estonian</option>
<option value="Finnish">‡«‡® Finnish</option>
<option value="French">‡«‡· French</option>
<option value="Georgian">‡¬‡ª Georgian</option>
<option value="German">‡©‡ª German</option>
<option value="Greek">‡¬‡· Greek</option>
<option value="Hungarian">‡­‡º Hungarian</option>
<option value="Icelandic">‡®‡¸ Icelandic</option>
<option value="Irish">‡®‡ª Irish</option>
<option value="Italian">‡®‡¹ Italian</option>
<option value="Latvian">‡±‡» Latvian</option>
<option value="Lithuanian">‡±‡¹ Lithuanian</option>
<option value="Macedonian">‡²‡° Macedonian</option>
<option value="Maltese">‡²‡¹ Maltese</option>
<option value="Norwegian">‡³‡´ Norwegian</option>
<option value="Polish">‡µ‡± Polish</option>
<option value="Portuguese">‡µ‡¹ Portuguese</option>
<option value="Romanian">‡·‡´ Romanian</option>
<option value="Russian">‡·‡º Russian</option>
<option value="Serbian">‡·‡¸ Serbian</option>
<option value="Slovak">‡¸‡° Slovak</option>
<option value="Slovenian">‡¸‡® Slovenian</option>
<option value="Spanish">‡ª‡¸ Spanish</option>
<option value="Swedish">‡¸‡ª Swedish</option>
<option value="Ukrainian">‡º‡¦ Ukrainian</option>
<option value="Welsh">´ó §ó ¢ó ·ó ¬ó ³ó ¿ Welsh</option>
</optgroup>
</select>
</div>
<!-- §  AI MODEL SELECTOR (New) -->
<div class="form-group" style="margin-top: 16px;">
<label>Mode d'Intelligence (Cerveau)</label>
<select id="ai-model-selector" class="form-control">
<option value="gpt-4o-mini">Ÿ¢ OpenAI GPT-4o (Stable & Illimité)</option>
<option value="gemini-flash-latest">š¡ Google Gemini Flash (Experimental)</option>
<option value="gemini-pro">’Ž Google Gemini Pro (Balanced)</option>
</select>
</div>
<audio id="audio-player"></audio>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script src="/static/enhanced.js"></script>
<script src="/static/script.js"></script>
<script>
// Console log helper
function log(m) { console.log(m); }
// Settings Modal
const modal = document.getElementById('settings-modal');
document.getElementById('settings-trigger').onclick = () => modal.classList.add('open');
document.getElementById('close-settings').onclick = () => modal.classList.remove('open');
document.getElementById('save-settings').onclick = () => {
if (window.saveModalSettings) window.saveModalSettings();
modal.classList.remove('open');
};
// UI SYNC - Chat messages are handled by script.js createChatMessage()
// This observer is only for real-time status updates
const greeting = document.getElementById('greeting');
const status = document.getElementById('status-placeholder');
const observer = new MutationObserver((mutations) => {
mutations.forEach(m => {
if (m.target.id === 'original-text') {
const txt = m.target.innerText;
if (txt && txt !== '...') {
status.innerText = txt.substring(0, 50) + (txt.length > 50 ? '...' : '');
greeting.style.display = 'none';
}
}
});
});
observer.observe(document.getElementById('original-text'), { childList: true, characterData: true, subtree: true });
// SMART CONVERSATION MODE TOGGLE
const smartModeToggle = document.getElementById('smart-mode-toggle');
const statusPlaceholder = document.getElementById('status-placeholder');
const savedSmartMode = localStorage.getItem('smartModeEnabled');
if (savedSmartMode === 'false') {
smartModeToggle.classList.remove('active');
} else {
smartModeToggle.classList.add('active');
localStorage.setItem('smartModeEnabled', 'true');
}
smartModeToggle.onclick = () => {
smartModeToggle.classList.toggle('active');
const isActive = smartModeToggle.classList.contains('active');
localStorage.setItem('smartModeEnabled', isActive);
// Update status
if (isActive) {
console.log('Smart Conversation Mode ENABLED');
statusPlaceholder.innerText = 'Mode intelligent';
setTimeout(() => {
if (statusPlaceholder.innerText === 'Mode intelligent') {
statusPlaceholder.innerText = 'Pret';
}
}, 2000);
} else {
console.log('Smart Mode DISABLED - Using fixed target');
statusPlaceholder.innerText = 'Cible fixe';
setTimeout(() => {
if (statusPlaceholder.innerText === 'Cible fixe') {
statusPlaceholder.innerText = 'Pret';
}
}, 2000);
}
};
document.addEventListener('reset-ui', () => {
document.getElementById('status-placeholder').innerText = 'Pret';
});
// VOICE CLONING TOGGLE
let voiceCloneEnabled = localStorage.getItem('voiceCloneEnabled') === 'true';
const cloneToggle = document.getElementById('clone-toggle');
if (cloneToggle) {
function updateCloneToggle() {
if (voiceCloneEnabled) {
cloneToggle.classList.add('active');
cloneToggle.title = 'Clonage de Voix: Active';
} else {
cloneToggle.classList.remove('active');
cloneToggle.title = 'Clonage de Voix: Desactive';
}
}
cloneToggle.addEventListener('click', () => {
voiceCloneEnabled = !voiceCloneEnabled;
localStorage.setItem('voiceCloneEnabled', voiceCloneEnabled);
updateCloneToggle();
});
updateCloneToggle();
}
// GRAMMAR CORRECTION TOGGLE
let grammarCorrectionEnabled = localStorage.getItem('grammarCorrectionEnabled') !== 'false';
const grammarToggle = document.getElementById('grammar-toggle');
if (grammarToggle) {
function updateGrammarToggle() {
if (grammarCorrectionEnabled) {
grammarToggle.classList.add('active');
grammarToggle.title = 'Correction Intelligente: Active';
} else {
grammarToggle.classList.remove('active');
grammarToggle.title = 'Correction Intelligente: Desactive';
}
}
grammarToggle.addEventListener('click', () => {
grammarCorrectionEnabled = !grammarCorrectionEnabled;
localStorage.setItem('grammarCorrectionEnabled', grammarCorrectionEnabled);
updateGrammarToggle();
});
updateGrammarToggle();
}
// AI CORRECTION TOGGLE (Claude/GPT Reasoning)
// Read from localStorage - default is FALSE (disabled by default)
const savedAiCorrection = localStorage.getItem('aiCorrectionEnabled');
let aiCorrectionEnabled = savedAiCorrection === 'true'; // Only true if explicitly set to 'true'
console.log('📦 AI Correction from localStorage:', savedAiCorrection, '→', aiCorrectionEnabled);
const aiCorrectionToggle = document.getElementById('ai-correction-toggle');
const aiCorrectionHidden = document.getElementById('ai-correction');
const aiCorrectionCheckbox = document.getElementById('ai-correction-checkbox');
function updateAiCorrectionState() {
console.log('🔄 AI Correction State Update:', aiCorrectionEnabled);
// Update hidden input
if (aiCorrectionHidden) aiCorrectionHidden.value = aiCorrectionEnabled ? 'true' : 'false';
// Update checkbox in settings
if (aiCorrectionCheckbox) aiCorrectionCheckbox.checked = aiCorrectionEnabled;
// Update toggle button visual state
if (aiCorrectionToggle) {
if (aiCorrectionEnabled) {
aiCorrectionToggle.classList.add('active');
aiCorrectionToggle.style.background = 'linear-gradient(135deg, #667eea, #764ba2)';
aiCorrectionToggle.style.color = '#fff';
aiCorrectionToggle.title = '🧠 AI Correction: ACTIVÉ (Claude/GPT)';
aiCorrectionToggle.innerHTML = '<i class="fa-solid fa-sparkles"></i> AI ✓';
} else {
aiCorrectionToggle.classList.remove('active');
aiCorrectionToggle.style.background = 'rgba(255,255,255,0.1)';
aiCorrectionToggle.style.color = 'rgba(255,255,255,0.5)';
aiCorrectionToggle.title = '❌ AI Correction: DÉSACTIVÉ';
aiCorrectionToggle.innerHTML = '<i class="fa-solid fa-sparkles"></i> AI ✗';
}
}
}
if (aiCorrectionToggle) {
aiCorrectionToggle.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
// Toggle the state
aiCorrectionEnabled = !aiCorrectionEnabled;
// Save to localStorage as string
localStorage.setItem('aiCorrectionEnabled', aiCorrectionEnabled.toString());
// Update UI
updateAiCorrectionState();
// Log for debugging
console.log('🧠 AI Correction TOGGLED to:', aiCorrectionEnabled ? 'ENABLED ✓' : 'DISABLED ✗');
// Visual feedback
aiCorrectionToggle.style.transform = 'scale(0.95)';
setTimeout(() => aiCorrectionToggle.style.transform = '', 150);
});
}
if (aiCorrectionCheckbox) {
aiCorrectionCheckbox.addEventListener('change', () => {
aiCorrectionEnabled = aiCorrectionCheckbox.checked;
localStorage.setItem('aiCorrectionEnabled', aiCorrectionEnabled.toString());
updateAiCorrectionState();
console.log('🧠 AI Correction (checkbox) set to:', aiCorrectionEnabled);
});
}
// Initialize state on page load
updateAiCorrectionState();
console.log('✅ AI Correction Toggle initialized:', aiCorrectionEnabled ? 'ON' : 'OFF');
// 🔄 SWAP LANGUAGES BUTTON
const swapBtn = document.getElementById('swap-langs');
// Using getElementById directly to avoid redeclaration
const srcLang = document.getElementById('source-lang-quick');
const tgtLang = document.getElementById('target-lang-quick');
if (swapBtn && srcLang && tgtLang) {
swapBtn.addEventListener('click', (e) => {
e.preventDefault();
// Get current values
const currentSource = srcLang.value;
const currentTarget = tgtLang.value;
// Swap them
if (currentSource !== 'auto') {
// Find matching option in target
const targetOptions = Array.from(tgtLang.options).map(o => o.value);
const sourceOptions = Array.from(srcLang.options).map(o => o.value);
if (targetOptions.includes(currentSource) && sourceOptions.includes(currentTarget)) {
srcLang.value = currentTarget;
tgtLang.value = currentSource;
} else {
// Fallback: just swap to auto and the target
srcLang.value = 'auto';
tgtLang.value = currentSource === 'auto' ? 'fr-FR' : currentSource;
}
} else {
// Source is auto - swap target with a default
const oldTarget = currentTarget;
tgtLang.value = oldTarget === 'fr-FR' ? 'ar-SA' : 'fr-FR';
}
// Save to localStorage
localStorage.setItem('sourceLangQuick', srcLang.value);
localStorage.setItem('targetLangQuick', tgtLang.value);
// Visual feedback
swapBtn.style.transform = 'rotate(180deg)';
setTimeout(() => swapBtn.style.transform = '', 300);
console.log(`🔄 Languages swapped: ${srcLang.value}${tgtLang.value}`);
});
}
// 🧠 SMART AUTO-SWAP: When language is detected, auto-swap target
// This is handled in processAudio response
// STT ENGINE SELECTOR
const sttSelector = document.getElementById('stt-selector-settings');
const sttEngineHidden = document.getElementById('stt-engine');
const savedSttEngine = localStorage.getItem('sttEngine') || 'seamless-m4t';
if (sttSelector) {
sttSelector.value = savedSttEngine;
if (sttEngineHidden) sttEngineHidden.value = savedSttEngine;
sttSelector.addEventListener('change', function () {
localStorage.setItem('sttEngine', this.value);
if (sttEngineHidden) sttEngineHidden.value = this.value;
console.log('🎤 STT Engine set to:', this.value);
});
}
// 🔊 TTS ENGINE SELECTOR
const ttsSelector = document.getElementById('tts-selector');
const ttsEngineHidden = document.getElementById('tts-engine');
const savedTtsEngine = localStorage.getItem('ttsEngine') || 'seamless';
if (ttsSelector) {
ttsSelector.value = savedTtsEngine;
if (ttsEngineHidden) ttsEngineHidden.value = savedTtsEngine;
ttsSelector.addEventListener('change', function () {
localStorage.setItem('ttsEngine', this.value);
if (ttsEngineHidden) ttsEngineHidden.value = this.value;
console.log('🔊 TTS Engine set to:', this.value);
// Visual feedback
this.style.backgroundColor = this.value === 'seamless' ? '#4CAF50' : '#2196F3';
setTimeout(() => this.style.backgroundColor = '', 500);
});
}
// 🌍 TRANSLATION ENGINE SELECTOR (NLLB vs MarianMT)
const translationEngineSelector = document.getElementById('translation-engine-selector');
const translationEngineHidden = document.getElementById('translation-engine');
const nllbModelSizeSelector = document.getElementById('nllb-model-size-selector');
const nllbModelSizeHidden = document.getElementById('nllb-model-size');
const nllbModelSizeGroup = document.getElementById('nllb-model-size-group');
const savedTranslationEngine = localStorage.getItem('translationEngine') || 'nllb';
const savedNllbModelSize = localStorage.getItem('nllbModelSize') || 'fast';
function updateNllbModelSizeVisibility() {
if (nllbModelSizeGroup) {
nllbModelSizeGroup.style.display =
translationEngineSelector && translationEngineSelector.value === 'nllb' ? 'block' : 'none';
}
}
if (translationEngineSelector) {
translationEngineSelector.value = savedTranslationEngine;
if (translationEngineHidden) translationEngineHidden.value = savedTranslationEngine;
updateNllbModelSizeVisibility();
translationEngineSelector.addEventListener('change', function () {
localStorage.setItem('translationEngine', this.value);
if (translationEngineHidden) translationEngineHidden.value = this.value;
console.log('🌍 Translation Engine set to:', this.value);
updateNllbModelSizeVisibility();
});
}
if (nllbModelSizeSelector) {
nllbModelSizeSelector.value = savedNllbModelSize;
if (nllbModelSizeHidden) nllbModelSizeHidden.value = savedNllbModelSize;
nllbModelSizeSelector.addEventListener('change', function () {
localStorage.setItem('nllbModelSize', this.value);
if (nllbModelSizeHidden) nllbModelSizeHidden.value = this.value;
console.log('🧠 NLLB Model Size set to:', this.value);
});
}
// SMART CONVERSATION MODE TOGGLE
const smartModeToggle = document.getElementById('smart-mode-toggle');
// ... (existing code for smart mode)
// REPLAY BUTTON LOGIC
const replayBtn = document.getElementById('replay-trigger');
if (replayBtn) {
replayBtn.addEventListener('click', () => {
if (window.lastBotAudio) {
console.log('”„ Replaying last audio...');
// Add visual effect
const icon = replayBtn.querySelector('i');
icon.style.transition = 'transform 0.5s ease';
icon.style.transform = 'rotate(-360deg)';
window.lastBotAudio.currentTime = 0;
window.lastBotAudio.play().catch(e => console.error("Replay failed:", e));
setTimeout(() => {
icon.style.transition = 'none';
icon.style.transform = 'rotate(0deg)';
}, 500);
} else {
// No audio to replay - shake animation
console.log('š ï¸ No audio to replay');
replayBtn.style.animation = 'shake 0.4s cubic-bezier(.36,.07,.19,.97) both';
setTimeout(() => replayBtn.style.animation = '', 400);
}
});
}
// Add Shake Animation Keyframes
const styleSheet = document.createElement("style");
styleSheet.innerText = `
@keyframes shake {
10%, 90% { transform: translate3d(-1px, 0, 0); }
20%, 80% { transform: translate3d(2px, 0, 0); }
30%, 50%, 70% { transform: translate3d(-4px, 0, 0); }
40%, 60% { transform: translate3d(4px, 0, 0); }
}
`;
document.head.appendChild(styleSheet);
// VOICE GENDER SELECTOR (3 states: auto, male, female)
let voiceGenderPreference = localStorage.getItem('voiceGenderPreference') || 'auto';
const voiceGenderToggle = document.getElementById('voice-gender-toggle');
if (voiceGenderToggle) {
const icons = {
'auto': 'fa-user-gear',
'male': 'fa-person',
'female': 'fa-person-dress'
};
const titles = {
'auto': 'Voix: Auto',
'male': 'Voix: Masculine',
'female': 'Voix: Feminine'
};
function updateVoiceGenderToggle() {
voiceGenderToggle.classList.remove('auto', 'male', 'female');
voiceGenderToggle.classList.add(voiceGenderPreference);
const iconElement = voiceGenderToggle.querySelector('i');
iconElement.className = `fa-solid ${icons[voiceGenderPreference]}`;
voiceGenderToggle.title = titles[voiceGenderPreference];
}
voiceGenderToggle.addEventListener('click', () => {
if (voiceGenderPreference === 'auto') {
voiceGenderPreference = 'male';
} else if (voiceGenderPreference === 'male') {
voiceGenderPreference = 'female';
} else {
voiceGenderPreference = 'auto';
}
localStorage.setItem('voiceGenderPreference', voiceGenderPreference);
updateVoiceGenderToggle();
});
updateVoiceGenderToggle();
}
// QUICK LANGUAGE SELECTORS
const sourceLangQuick = document.getElementById('source-lang-quick');
const targetLangQuick = document.getElementById('target-lang-quick');
const swapLangsBtn = document.getElementById('swap-langs');
const quickLangSelector = document.getElementById('quick-lang-selector');
// Load saved preferences
const savedSourceLang = localStorage.getItem('sourceLangQuick') || 'auto';
const savedTargetLang = localStorage.getItem('targetLangQuick') || 'French';
if (sourceLangQuick) sourceLangQuick.value = savedSourceLang;
if (targetLangQuick) targetLangQuick.value = savedTargetLang;
if (quickLangSelector) quickLangSelector.value = savedTargetLang;
// Sync source language with script.js
if (sourceLangQuick) {
sourceLangQuick.addEventListener('change', function () {
localStorage.setItem('sourceLangQuick', this.value);
console.log('ޤ Source language set to:', this.value);
// Clear cache for new language
fetch('/clear_cache', { method: 'POST' });
// Update status
const langName = this.options[this.selectedIndex].text;
statusPlaceholder.innerText = langName;
setTimeout(() => {
if (statusPlaceholder.innerText === langName) {
statusPlaceholder.innerText = 'Pret';
}
}, 1500);
});
}
// Sync target language with existing selector
if (targetLangQuick) {
targetLangQuick.addEventListener('change', function () {
localStorage.setItem('targetLangQuick', this.value);
localStorage.setItem('targetLang', this.value);
// Sync with modal selector
if (quickLangSelector) quickLangSelector.value = this.value;
console.log('✨ Target language set to:', this.value);
// Clear cache for new language
fetch('https://instant-translat-production.up.railway.app/clear_cache', { method: 'POST' });
});
}
// SWAP LANGUAGES BUTTON
if (swapLangsBtn && sourceLangQuick && targetLangQuick) {
swapLangsBtn.addEventListener('click', function () {
// Map between source codes and target names
const sourceToTarget = {
'ar-SA': 'Arabic',
'fr-FR': 'French',
'en-US': 'English',
'es-ES': 'Spanish',
'de-DE': 'German',
'auto': 'auto'
};
const targetToSource = {
'Arabic': 'ar-SA',
'Moroccan Darija': 'ar-SA',
'French': 'fr-FR',
'English': 'en-US',
'Spanish': 'es-ES',
'German': 'de-DE'
};
const currentSource = sourceLangQuick.value;
const currentTarget = targetLangQuick.value;
// Swap
const newSource = targetToSource[currentTarget] || 'auto';
const newTarget = sourceToTarget[currentSource] || 'French';
sourceLangQuick.value = newSource;
targetLangQuick.value = newTarget;
// Save
localStorage.setItem('sourceLangQuick', newSource);
localStorage.setItem('targetLangQuick', newTarget);
localStorage.setItem('targetLang', newTarget);
if (quickLangSelector) quickLangSelector.value = newTarget;
// Visual feedback
this.style.transform = 'rotate(180deg)';
setTimeout(() => {
this.style.transform = 'rotate(0deg)';
}, 300);
console.log('”„ Languages swapped:', newSource, '†”', newTarget);
// Clear cache
fetch('/clear_cache', { method: 'POST' });
});
}
</script>
</body>
</html>