Spaces:
Running
Running
| | |
| <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="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('/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> |