| <!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"> |
|
|
| |
| <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"> |
|
|
| |
| <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> |
| |
| </style> |
| </head> |
|
|
| <body> |
| <div class="app-layout"> |
| |
| <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> |
| |
| <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 class="grok-main"> |
| |
| <header class="main-header"> |
| <span class="model-badge">Babel v2.0</span> |
| </header> |
|
|
| |
| <div id="chat-stage" class="chat-stage"> |
| |
| <div class="empty-state" id="greeting"> |
| <div class="grok-logo-hero">Babel</div> |
| <p class="grok-hero-text">Intelligence Vocale Instantanee</p> |
| </div> |
|
|
| |
| <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> |
|
|
| |
| <div class="grok-input-wrapper"> |
|
|
| |
| <div class="grok-input-bar"> |
| <div class="input-icon left"> |
| <i class="fa-solid fa-paperclip"></i> |
| </div> |
|
|
| |
| <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> |
|
|
| |
| <div class="input-icon right action" id="record-btn"> |
| <i class="fa-solid fa-microphone"></i> |
| </div> |
| </div> |
|
|
| |
| <div class="grok-chips"> |
|
|
| |
| <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> |
|
|
| |
| <button id="swap-langs" class="chip-icon" title="Swap"> |
| <i class="fa-solid fa-arrow-right-arrow-left"></i> |
| </button> |
|
|
| |
| <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> |
|
|
| |
| <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> |
|
|
| |
| <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 id="audio-player" style="display: none;"></audio> |
| </div> |
|
|
| |
| <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> |
| |
| <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> |
|
|
| |
| <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> |
| |
| function log(m) { console.log(m); } |
| |
| |
| 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'); |
| }; |
| |
| |
| |
| 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 }); |
| |
| |
| 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); |
| |
| |
| 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'; |
| }); |
| |
| |
| 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(); |
| } |
| |
| |
| 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(); |
| } |
| |
| |
| |
| const savedAiCorrection = localStorage.getItem('aiCorrectionEnabled'); |
| let aiCorrectionEnabled = savedAiCorrection === '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); |
| |
| |
| if (aiCorrectionHidden) aiCorrectionHidden.value = aiCorrectionEnabled ? 'true' : 'false'; |
| |
| |
| if (aiCorrectionCheckbox) aiCorrectionCheckbox.checked = aiCorrectionEnabled; |
| |
| |
| 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(); |
| |
| |
| aiCorrectionEnabled = !aiCorrectionEnabled; |
| |
| |
| localStorage.setItem('aiCorrectionEnabled', aiCorrectionEnabled.toString()); |
| |
| |
| updateAiCorrectionState(); |
| |
| |
| console.log('🧠 AI Correction TOGGLED to:', aiCorrectionEnabled ? 'ENABLED ✓' : 'DISABLED ✗'); |
| |
| |
| 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); |
| }); |
| } |
| |
| |
| updateAiCorrectionState(); |
| console.log('✅ AI Correction Toggle initialized:', aiCorrectionEnabled ? 'ON' : 'OFF'); |
| |
| |
| const swapBtn = document.getElementById('swap-langs'); |
| |
| const srcLang = document.getElementById('source-lang-quick'); |
| const tgtLang = document.getElementById('target-lang-quick'); |
| |
| if (swapBtn && srcLang && tgtLang) { |
| swapBtn.addEventListener('click', (e) => { |
| e.preventDefault(); |
| |
| |
| const currentSource = srcLang.value; |
| const currentTarget = tgtLang.value; |
| |
| |
| if (currentSource !== 'auto') { |
| |
| 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 { |
| |
| srcLang.value = 'auto'; |
| tgtLang.value = currentSource === 'auto' ? 'fr-FR' : currentSource; |
| } |
| } else { |
| |
| const oldTarget = currentTarget; |
| tgtLang.value = oldTarget === 'fr-FR' ? 'ar-SA' : 'fr-FR'; |
| } |
| |
| |
| localStorage.setItem('sourceLangQuick', srcLang.value); |
| localStorage.setItem('targetLangQuick', tgtLang.value); |
| |
| |
| swapBtn.style.transform = 'rotate(180deg)'; |
| setTimeout(() => swapBtn.style.transform = '', 300); |
| |
| console.log(`🔄 Languages swapped: ${srcLang.value} → ${tgtLang.value}`); |
| }); |
| } |
| |
| |
| |
| |
| |
| 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); |
| }); |
| } |
| |
| |
| 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); |
| |
| |
| this.style.backgroundColor = this.value === 'seamless' ? '#4CAF50' : '#2196F3'; |
| setTimeout(() => this.style.backgroundColor = '', 500); |
| }); |
| } |
| |
| |
| 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); |
| }); |
| } |
| |
| |
| |
| const smartModeToggle = document.getElementById('smart-mode-toggle'); |
| |
| |
| |
| const replayBtn = document.getElementById('replay-trigger'); |
| if (replayBtn) { |
| replayBtn.addEventListener('click', () => { |
| if (window.lastBotAudio) { |
| console.log('”„ Replaying last audio...'); |
| |
| 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 { |
| |
| console.log('š ï¸ No audio to replay'); |
| replayBtn.style.animation = 'shake 0.4s cubic-bezier(.36,.07,.19,.97) both'; |
| setTimeout(() => replayBtn.style.animation = '', 400); |
| } |
| }); |
| } |
| |
| |
| 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); |
| |
| |
| 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(); |
| } |
| |
| |
| 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'); |
| |
| |
| 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; |
| |
| |
| if (sourceLangQuick) { |
| sourceLangQuick.addEventListener('change', function () { |
| localStorage.setItem('sourceLangQuick', this.value); |
| console.log('ޤ Source language set to:', this.value); |
| |
| |
| fetch('/clear_cache', { method: 'POST' }); |
| |
| |
| const langName = this.options[this.selectedIndex].text; |
| statusPlaceholder.innerText = langName; |
| setTimeout(() => { |
| if (statusPlaceholder.innerText === langName) { |
| statusPlaceholder.innerText = 'Pret'; |
| } |
| }, 1500); |
| }); |
| } |
| |
| |
| if (targetLangQuick) { |
| targetLangQuick.addEventListener('change', function () { |
| localStorage.setItem('targetLangQuick', this.value); |
| localStorage.setItem('targetLang', this.value); |
| |
| |
| if (quickLangSelector) quickLangSelector.value = this.value; |
| |
| console.log('✨ Target language set to:', this.value); |
| |
| |
| fetch('https://instant-translat-production.up.railway.app/clear_cache', { method: 'POST' }); |
| }); |
| } |
| |
| |
| if (swapLangsBtn && sourceLangQuick && targetLangQuick) { |
| swapLangsBtn.addEventListener('click', function () { |
| |
| 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; |
| |
| |
| const newSource = targetToSource[currentTarget] || 'auto'; |
| const newTarget = sourceToTarget[currentSource] || 'French'; |
| |
| sourceLangQuick.value = newSource; |
| targetLangQuick.value = newTarget; |
| |
| |
| localStorage.setItem('sourceLangQuick', newSource); |
| localStorage.setItem('targetLangQuick', newTarget); |
| localStorage.setItem('targetLang', newTarget); |
| |
| if (quickLangSelector) quickLangSelector.value = newTarget; |
| |
| |
| this.style.transform = 'rotate(180deg)'; |
| setTimeout(() => { |
| this.style.transform = 'rotate(0deg)'; |
| }, 300); |
| |
| console.log('”„ Languages swapped:', newSource, '†”', newTarget); |
| |
| |
| fetch('/clear_cache', { method: 'POST' }); |
| }); |
| } |
| </script> |
| </body> |
|
|
| </html> |