Update index.html
Browse files- index.html +17 -45
index.html
CHANGED
|
@@ -403,6 +403,7 @@ function initSpeechRecognition() {
|
|
| 403 |
const rec = new SpeechRecognition();
|
| 404 |
rec.continuous = false;
|
| 405 |
rec.interimResults = true;
|
|
|
|
| 406 |
const langMap = { en: 'en-US', ar: 'ar-DZ', fr: 'fr-FR', es: 'es-ES' };
|
| 407 |
rec.lang = langMap[currentLang] || 'en-US';
|
| 408 |
|
|
@@ -412,6 +413,7 @@ function initSpeechRecognition() {
|
|
| 412 |
document.getElementById('stt-text').textContent = i18n[currentLang].vListening;
|
| 413 |
const voiceBtn = document.getElementById('voice-btn');
|
| 414 |
voiceBtn.classList.add('recording');
|
|
|
|
| 415 |
lucide.createIcons({ icons: { 'mic-off': lucide.icons['mic-off'] }, attrs: { class: 'icon' } });
|
| 416 |
const micIcon = document.getElementById('mic-icon');
|
| 417 |
micIcon.setAttribute('data-lucide', 'mic-off');
|
|
@@ -440,6 +442,7 @@ function initSpeechRecognition() {
|
|
| 440 |
const micIcon = document.getElementById('mic-icon');
|
| 441 |
micIcon.setAttribute('data-lucide', 'mic');
|
| 442 |
lucide.createIcons({ nameAttr: 'data-lucide', attrs: { class: 'icon' } });
|
|
|
|
| 443 |
const input = document.getElementById('msg-input');
|
| 444 |
if (input.value.trim()) sendMessage();
|
| 445 |
};
|
|
@@ -482,60 +485,27 @@ function saveTtsSetting() {
|
|
| 482 |
localStorage.setItem('genisi_tts', autoTTS);
|
| 483 |
}
|
| 484 |
|
| 485 |
-
/**
|
| 486 |
-
* مـكـتـب تـحـويـل وتـنـقـيـة الـنـص لـلـصـوت
|
| 487 |
-
* يحول النص من Markdown إلى نص بسيط بدون إيموجيات وبدون رموز تشتت الصوت
|
| 488 |
-
*/
|
| 489 |
-
function cleanTextForSpeech(text) {
|
| 490 |
-
if (!text) return "";
|
| 491 |
-
|
| 492 |
-
let cleaned = text;
|
| 493 |
-
|
| 494 |
-
// 1. إزالة كتل الأكواد البرمجية (Code Blocks) بالكامل
|
| 495 |
-
cleaned = cleaned.replace(/```[\s\S]*?```/g, '');
|
| 496 |
-
|
| 497 |
-
// 2. إزالة علامات الماركدوان (Bold, Italic, Links)
|
| 498 |
-
cleaned = cleaned.replace(/`([^`]+)`/g, '$1')
|
| 499 |
-
.replace(/\*\*([^*]+)\*\*/g, '$1')
|
| 500 |
-
.replace(/\*([^*]+)\*/g, '$1')
|
| 501 |
-
.replace(/#{1,6}\s/g, '')
|
| 502 |
-
.replace(/\[([^\]]+)\]\([^)]+\)/g, '$1')
|
| 503 |
-
.replace(/!\[([^\]]+)\]\([^)]+\)/g, '');
|
| 504 |
-
|
| 505 |
-
// 3. إزالة الإيموجيات (Emojis & Symbols)
|
| 506 |
-
// نستخدم regex شامل يغطي معظم نطاقات الرموز التعبيرية
|
| 507 |
-
cleaned = cleaned.replace(/([\u{1F600}-\u{1F64F}\u{1F300}-\u{1F5FF}\u{1F680}-\u{1F6FF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}\u{1F900}-\u{1F9FF}\u{1F1E6}-\u{1F1FF}])/gu, '');
|
| 508 |
-
|
| 509 |
-
// 4. تنقية علامات الترقيم المزعجة صوتياً
|
| 510 |
-
// نزيل: @ # $ % ^ & * ( ) _ + = { } [ ] | \ / < > ` ~
|
| 511 |
-
// لكن نترك النقطة والفصلة والسين وعلامة الاستفهام ليعطي المحرك الصوتي نبرة طبيعية (Pause)
|
| 512 |
-
cleaned = cleaned.replace(/[@#$%\^&\*\(_\+=\{\}\[\]|\\\/<>`~]/g, ' ');
|
| 513 |
-
|
| 514 |
-
// 5. تحويل السطور الجديدة إلى مساحات لتفادي التقطيع المفاجئ
|
| 515 |
-
cleaned = cleaned.replace(/\n+/g, ' ');
|
| 516 |
-
|
| 517 |
-
// 6. تنظيف المسافات الزائدة
|
| 518 |
-
cleaned = cleaned.replace(/\s+/g, ' ').trim();
|
| 519 |
-
|
| 520 |
-
return cleaned;
|
| 521 |
-
}
|
| 522 |
-
|
| 523 |
function speakText(text) {
|
| 524 |
if (!window.speechSynthesis) return;
|
| 525 |
-
|
| 526 |
window.speechSynthesis.cancel();
|
| 527 |
-
|
| 528 |
-
|
| 529 |
-
|
| 530 |
-
|
| 531 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 532 |
|
| 533 |
const utterance = new SpeechSynthesisUtterance(clean);
|
| 534 |
const langMap = { en: 'en-US', ar: 'ar-SA', fr: 'fr-FR', es: 'es-ES' };
|
| 535 |
utterance.lang = langMap[currentLang] || 'en-US';
|
| 536 |
utterance.rate = 1.0;
|
| 537 |
utterance.pitch = 1.0;
|
| 538 |
-
|
| 539 |
currentUtterance = utterance;
|
| 540 |
window.speechSynthesis.speak(utterance);
|
| 541 |
return utterance;
|
|
@@ -740,7 +710,9 @@ async function sendMessage(){
|
|
| 740 |
}
|
| 741 |
botContentDiv.innerHTML=safeMarked(fullBotResponse);
|
| 742 |
renderKaTeX(botContentDiv);
|
|
|
|
| 743 |
appendTTSBadge(botContentDiv, fullBotResponse);
|
|
|
|
| 744 |
if(autoTTS) speakText(fullBotResponse);
|
| 745 |
chat.history.push({user:text,bot:fullBotResponse,uiFiles});
|
| 746 |
saveChats(); appendMemBadge(chat.history.length,chat.history.length);
|
|
|
|
| 403 |
const rec = new SpeechRecognition();
|
| 404 |
rec.continuous = false;
|
| 405 |
rec.interimResults = true;
|
| 406 |
+
// Set language based on current UI language
|
| 407 |
const langMap = { en: 'en-US', ar: 'ar-DZ', fr: 'fr-FR', es: 'es-ES' };
|
| 408 |
rec.lang = langMap[currentLang] || 'en-US';
|
| 409 |
|
|
|
|
| 413 |
document.getElementById('stt-text').textContent = i18n[currentLang].vListening;
|
| 414 |
const voiceBtn = document.getElementById('voice-btn');
|
| 415 |
voiceBtn.classList.add('recording');
|
| 416 |
+
// swap mic icon to mic-off
|
| 417 |
lucide.createIcons({ icons: { 'mic-off': lucide.icons['mic-off'] }, attrs: { class: 'icon' } });
|
| 418 |
const micIcon = document.getElementById('mic-icon');
|
| 419 |
micIcon.setAttribute('data-lucide', 'mic-off');
|
|
|
|
| 442 |
const micIcon = document.getElementById('mic-icon');
|
| 443 |
micIcon.setAttribute('data-lucide', 'mic');
|
| 444 |
lucide.createIcons({ nameAttr: 'data-lucide', attrs: { class: 'icon' } });
|
| 445 |
+
// Auto-send if there's text
|
| 446 |
const input = document.getElementById('msg-input');
|
| 447 |
if (input.value.trim()) sendMessage();
|
| 448 |
};
|
|
|
|
| 485 |
localStorage.setItem('genisi_tts', autoTTS);
|
| 486 |
}
|
| 487 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 488 |
function speakText(text) {
|
| 489 |
if (!window.speechSynthesis) return;
|
| 490 |
+
// Cancel any ongoing speech
|
| 491 |
window.speechSynthesis.cancel();
|
| 492 |
+
// Strip markdown symbols for cleaner speech
|
| 493 |
+
const clean = text
|
| 494 |
+
.replace(/```[\s\S]*?```/g, 'code block.')
|
| 495 |
+
.replace(/`([^`]+)`/g, '$1')
|
| 496 |
+
.replace(/\*\*([^*]+)\*\*/g, '$1')
|
| 497 |
+
.replace(/\*([^*]+)\*/g, '$1')
|
| 498 |
+
.replace(/#{1,6}\s/g, '')
|
| 499 |
+
.replace(/\[([^\]]+)\]\([^)]+\)/g, '$1')
|
| 500 |
+
.replace(/!\[([^\]]+)\]\([^)]+\)/g, 'image.')
|
| 501 |
+
.replace(/\n+/g, '. ')
|
| 502 |
+
.trim();
|
| 503 |
|
| 504 |
const utterance = new SpeechSynthesisUtterance(clean);
|
| 505 |
const langMap = { en: 'en-US', ar: 'ar-SA', fr: 'fr-FR', es: 'es-ES' };
|
| 506 |
utterance.lang = langMap[currentLang] || 'en-US';
|
| 507 |
utterance.rate = 1.0;
|
| 508 |
utterance.pitch = 1.0;
|
|
|
|
| 509 |
currentUtterance = utterance;
|
| 510 |
window.speechSynthesis.speak(utterance);
|
| 511 |
return utterance;
|
|
|
|
| 710 |
}
|
| 711 |
botContentDiv.innerHTML=safeMarked(fullBotResponse);
|
| 712 |
renderKaTeX(botContentDiv);
|
| 713 |
+
// Add TTS play button
|
| 714 |
appendTTSBadge(botContentDiv, fullBotResponse);
|
| 715 |
+
// Auto TTS
|
| 716 |
if(autoTTS) speakText(fullBotResponse);
|
| 717 |
chat.history.push({user:text,bot:fullBotResponse,uiFiles});
|
| 718 |
saveChats(); appendMemBadge(chat.history.length,chat.history.length);
|