AnesKAM commited on
Commit
b09618d
·
verified ·
1 Parent(s): f8d45d4

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +127 -15
index.html CHANGED
@@ -111,10 +111,8 @@
111
 
112
  .bubble{max-width:min(700px,80vw);padding:16px 22px;border-radius:var(--radius);
113
  font-size:.98rem;line-height:1.75;word-break:break-word; box-shadow: 0 4px 15px rgba(0,0,0,0.03);}
114
- .msg-row.bot .bubble{background:transparent;border:none;padding:8px 4px;box-shadow:none;border-top-left-radius:var(--radius);}
115
- [dir="rtl"] .msg-row.bot .bubble { border-top-left-radius:var(--radius); border-top-right-radius:var(--radius); }
116
- .msg-row.user .bubble{background:var(--user-bubble);border:none; border-top-right-radius:6px}
117
- [dir="rtl"] .msg-row.user .bubble { border-top-right-radius:var(--radius); border-top-left-radius:6px; }
118
 
119
  .bubble img {
120
  max-width: 100%; height: auto; border-radius: 16px; margin: 12px 0;
@@ -181,14 +179,35 @@
181
  font-family:'Cairo',sans-serif;font-size:.98rem;resize:none;max-height:160px;line-height:1.6; padding: 6px 0;}
182
  .input-box textarea::placeholder{color:var(--text3)}
183
 
184
- .btn-send, .btn-stop {width:42px;height:42px; border:none;border-radius:50%;cursor:pointer;color:#fff;display:flex;align-items:center;justify-content:center;font-size:1.1rem;transition:all var(--tr); flex-shrink:0; margin-bottom: 2px;}
185
- .btn-send {background:linear-gradient(135deg,var(--accent),var(--accent2));}
 
 
 
 
 
 
186
  .btn-send:hover{opacity:.9;transform:scale(1.08) rotate(-10deg);}[dir="rtl"] .btn-send:hover{transform:scale(1.08) rotate(10deg);}
187
  .btn-send:disabled{opacity:.4;cursor:not-allowed;transform:none;}
188
 
189
  .btn-stop {background:var(--surface3); color:var(--text); border:1px solid var(--border);}
190
  .btn-stop:hover {background:var(--danger); color:#fff; transform:scale(1.05);}
191
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
192
  /* MODAL */
193
  .overlay{position:fixed;inset:0;z-index:999;background:rgba(0,0,0,.6);display:flex;
194
  align-items:center;justify-content:center;backdrop-filter:blur(8px);
@@ -280,6 +299,10 @@
280
  <input type="file" id="file-input" multiple hidden onchange="handleFiles(this.files)" />
281
  <button class="btn-attach" onclick="document.getElementById('file-input').click()">+</button>
282
  <textarea id="msg-input" rows="1" onkeydown="handleKey(event)" oninput="autoResize(this)" placeholder="Type a message..."></textarea>
 
 
 
 
283
  <button class="btn-send" id="send-btn" onclick="sendMessage()">➤</button>
284
  <button class="btn-stop" id="stop-btn" onclick="stopGeneration()" style="display:none;">⏹</button>
285
  </div>
@@ -325,10 +348,10 @@
325
  <script>
326
  // ════════ i18n ════════
327
  const i18n = {
328
- en: { newChat: "✦ New Chat", chatsLabel: "CHATS", settings: "Settings", lang: "Language", dark: "Dark Mode", del: "Delete All Chats", delDesc: "This action cannot be undone", delBtn: "🗑 Delete", saveBtn: "💾 Close", placeholder: "Type your message... (Enter to send)", welcomeTitle: "Welcome to Genisi", welcomeSub: "Your smart assistant by AnesNT — Batna 🇩🇿", c1: "What is AI?", c2: "Write Python code", c3: "Summarize a topic", c4: "Design an image in space 🚀", errConnect: "Connection Error", memBadge: "Memory: {c}/{t} messages", stopMsg: "[Generation stopped by user]" },
329
- ar: { newChat: "✦ محادثة جديدة", chatsLabel: "المحادثات", settings: "الإعدادات", lang: "اللغة", dark: "الوضع الليلي", del: "حذف جميع المحادثات", delDesc: "لا يمكن التراجع عن هذا الإجراء", delBtn: "🗑 حذف", saveBtn: "💾 إغلاق", placeholder: "اكتب رسالتك... (Enter للإرسال)", welcomeTitle: "مرحبًا في Genisi", welcomeSub: "أنا Genisi، مساعدك الذكي من AnesNT — باتنة 🇩🇿", c1: "ما هو الذكاء الاصطناعي؟", c2: "اكتب كود بايثون", c3: "لخص لي موضوعاً", c4: "صمم صورة بالفضاء 🚀", errConnect: "خطأ في الاتصال", memBadge: "الذاكرة: {c}/{t} رسائل", stopMsg: "[تم إيقاف التوليد]" },
330
- fr: { newChat: "✦ Nouvelle Disc.", chatsLabel: "DISCUSSIONS", settings: "Paramètres", lang: "Langue", dark: "Mode Sombre", del: "Supprimer Tout", delDesc: "Action irréversible", delBtn: "🗑 Supprimer", saveBtn: "💾 Fermer", placeholder: "Écrivez votre message...", welcomeTitle: "Bienvenue sur Genisi", welcomeSub: "Votre assistant intelligent par AnesNT — Batna 🇩🇿", c1: "Qu'est-ce que l'IA?", c2: "Code Python", c3: "Résumer un sujet", c4: "Créer une image 🚀", errConnect: "Erreur de connexion", memBadge: "Mémoire: {c}/{t} msgs", stopMsg: "[Génération arrêtée]" },
331
- es: { newChat: "✦ Nuevo Chat", chatsLabel: "CHATS", settings: "Ajustes", lang: "Idioma", dark: "Modo Oscuro", del: "Borrar Todo", delDesc: "Acción irreversible", delBtn: "🗑 Borrar", saveBtn: "💾 Cerrar", placeholder: "Escribe tu mensaje...", welcomeTitle: "Bienvenido a Genisi", welcomeSub: "Tu asistente por AnesNT — Batna 🇩🇿", c1: "¿Qué es la IA?", c2: "Código Python", c3: "Resumir", c4: "Diseñar imagen 🚀", errConnect: "Error de conexión", memBadge: "Memoria: {c}/{t} msgs", stopMsg: "[Generación detenida]" }
332
  };
333
 
334
  let currentLang = localStorage.getItem('genisi_lang') || 'en';
@@ -365,6 +388,11 @@ let isGenerating = false;
365
  let pendingFiles =[];
366
  let currentAbortController = null;
367
 
 
 
 
 
 
368
  // ════════ MARKDOWN & PARSER ════════
369
  const renderer = new marked.Renderer();
370
  renderer.code = function(token) {
@@ -377,7 +405,7 @@ renderer.table = function(header, body) {
377
  if (typeof header === 'object' && header !== null && 'header' in header) {
378
  const token = header;
379
  const hdr = token.header.map(cell => `<th>${cell.tokens.map(t=>t.raw||'').join('')}</th>`).join('');
380
- const rows = (token.rows || []).map(row => `<tr>${row.map(cell => `<td>${cell.tokens.map(t=>t.raw||'').join('')}</td>`).join('')}</tr>`).join('');
381
  return `<div class="table-wrapper"><table><thead><tr>${hdr}</tr></thead><tbody>${rows}</tbody></table></div>`;
382
  }
383
  return `<div class="table-wrapper"><table><thead>${header}</thead><tbody>${body}</tbody></table></div>`;
@@ -393,7 +421,7 @@ function safeMarked(text) {
393
 
394
  function renderKaTeX(el) {
395
  if (window.renderMathInElement) {
396
- renderMathInElement(el, { delimiters: [{left: '$$', right: '$$', display: true},{left: '$', right: '$', display: false},{left: '\\(', right: '\\)', display: false},{left: '\\[', right: '\\]', display: true}], throwOnError: false });
397
  }
398
  }
399
  function copyCode(btn, encodedCode) {
@@ -504,9 +532,9 @@ function quickSend(text){ document.getElementById('msg-input').value=text; sendM
504
  function handleKey(e){ if(e.key==='Enter'&&!e.shiftKey){e.preventDefault();sendMessage();} }
505
  function autoResize(el){ el.style.height='auto'; el.style.height=Math.min(el.scrollHeight,160)+'px'; }
506
 
507
- // ════════ MESSAGING & STREAMING ════════
508
  async function sendMessage(){
509
- if(isGenerating) return;
510
  const input = document.getElementById('msg-input'); let text = input.value.trim();
511
  if(!text && pendingFiles.length === 0) return;
512
  const welcome = document.getElementById('welcome'); if(welcome) welcome.remove();
@@ -579,6 +607,90 @@ async function sendMessage(){
579
  }
580
  }
581
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
582
  function appendBubble(role, text, isMarkdown, filesList =[]){
583
  const area=document.getElementById('chat-area');
584
  const row=document.createElement('div'); row.className=`msg-row ${role}`;
@@ -588,7 +700,7 @@ function appendBubble(role, text, isMarkdown, filesList =[]){
588
  const bub=document.createElement('div'); bub.className='bubble';
589
  if(filesList && filesList.length > 0) filesList.forEach(name => { bub.innerHTML += `<div class="file-prev">📎 ${esc(name)}</div>`; });
590
  const contentDiv = document.createElement('div');
591
- // 🆕 الحل السحري لمشكلة اتجاه النص المختلط
592
  contentDiv.dir = 'auto';
593
  contentDiv.style.unicodeBidi = 'plaintext';
594
  if(text) {
 
111
 
112
  .bubble{max-width:min(700px,80vw);padding:16px 22px;border-radius:var(--radius);
113
  font-size:.98rem;line-height:1.75;word-break:break-word; box-shadow: 0 4px 15px rgba(0,0,0,0.03);}
114
+ .msg-row.bot .bubble{background:transparent;border:none;padding:8px 4px;box-shadow:none;border-top-left-radius:var(--radius);}[dir="rtl"] .msg-row.bot .bubble { border-top-left-radius:var(--radius); border-top-right-radius:var(--radius); }
115
+ .msg-row.user .bubble{background:var(--user-bubble);border:none; border-top-right-radius:6px}[dir="rtl"] .msg-row.user .bubble { border-top-right-radius:var(--radius); border-top-left-radius:6px; }
 
 
116
 
117
  .bubble img {
118
  max-width: 100%; height: auto; border-radius: 16px; margin: 12px 0;
 
179
  font-family:'Cairo',sans-serif;font-size:.98rem;resize:none;max-height:160px;line-height:1.6; padding: 6px 0;}
180
  .input-box textarea::placeholder{color:var(--text3)}
181
 
182
+ /* أزرار الإرسال والميكروفون والإيقاف */
183
+ .btn-send, .btn-stop, .btn-voice {
184
+ width:42px;height:42px; border:none;border-radius:50%;cursor:pointer;
185
+ display:flex;align-items:center;justify-content:center;font-size:1.1rem;
186
+ transition:all var(--tr); flex-shrink:0; margin-bottom: 2px;
187
+ }
188
+
189
+ .btn-send {background:linear-gradient(135deg,var(--accent),var(--accent2)); color:#fff;}
190
  .btn-send:hover{opacity:.9;transform:scale(1.08) rotate(-10deg);}[dir="rtl"] .btn-send:hover{transform:scale(1.08) rotate(10deg);}
191
  .btn-send:disabled{opacity:.4;cursor:not-allowed;transform:none;}
192
 
193
  .btn-stop {background:var(--surface3); color:var(--text); border:1px solid var(--border);}
194
  .btn-stop:hover {background:var(--danger); color:#fff; transform:scale(1.05);}
195
 
196
+ /* زر التحدث الجديد */
197
+ .btn-voice {background:var(--surface2); color:var(--text2);}
198
+ .btn-voice:hover {background:var(--surface3); color:var(--accent); transform:scale(1.05);}
199
+
200
+ /* أنيميشن التسجيل الصوتي */
201
+ .btn-voice.recording {
202
+ background:var(--danger); color:#fff;
203
+ animation: pulseRecord 1.5s infinite;
204
+ }
205
+ @keyframes pulseRecord {
206
+ 0% { box-shadow: 0 0 0 0 rgba(247, 95, 95, 0.4); }
207
+ 70% { box-shadow: 0 0 0 12px rgba(247, 95, 95, 0); }
208
+ 100% { box-shadow: 0 0 0 0 rgba(247, 95, 95, 0); }
209
+ }
210
+
211
  /* MODAL */
212
  .overlay{position:fixed;inset:0;z-index:999;background:rgba(0,0,0,.6);display:flex;
213
  align-items:center;justify-content:center;backdrop-filter:blur(8px);
 
299
  <input type="file" id="file-input" multiple hidden onchange="handleFiles(this.files)" />
300
  <button class="btn-attach" onclick="document.getElementById('file-input').click()">+</button>
301
  <textarea id="msg-input" rows="1" onkeydown="handleKey(event)" oninput="autoResize(this)" placeholder="Type a message..."></textarea>
302
+
303
+ <!-- زر التحدث الصوتي (مضاف حديثاً) -->
304
+ <button class="btn-voice" id="voice-btn" onclick="toggleVoiceRecord()" title="تحدث بالصوت">🎤</button>
305
+
306
  <button class="btn-send" id="send-btn" onclick="sendMessage()">➤</button>
307
  <button class="btn-stop" id="stop-btn" onclick="stopGeneration()" style="display:none;">⏹</button>
308
  </div>
 
348
  <script>
349
  // ════════ i18n ════════
350
  const i18n = {
351
+ en: { newChat: "✦ New Chat", chatsLabel: "CHATS", settings: "Settings", lang: "Language", dark: "Dark Mode", del: "Delete All Chats", delDesc: "This action cannot be undone", delBtn: "🗑 Delete", saveBtn: "💾 Close", placeholder: "Type your message... (Enter to send)", welcomeTitle: "Welcome to Genisi", welcomeSub: "Your smart assistant by AnesNT — Batna 🇩🇿", c1: "What is AI?", c2: "Write Python code", c3: "Summarize a topic", c4: "Design an image in space 🚀", errConnect: "Connection Error", memBadge: "Memory: {c}/{t} messages", stopMsg: "[Generation stopped by user]", vMsg: "🎤 Voice Message", vThink: "🔊 Thinking...", vPlay: "🔊 Playing Response...", vEnd: "🔊 Voice Response Ended" },
352
+ ar: { newChat: "✦ محادثة جديدة", chatsLabel: "المحادثات", settings: "الإعدادات", lang: "اللغة", dark: "الوضع الليلي", del: "حذف جميع المحادثات", delDesc: "لا يمكن التراجع عن هذا الإجراء", delBtn: "🗑 حذف", saveBtn: "💾 إغلاق", placeholder: "اكتب رسالتك... (Enter للإرسال)", welcomeTitle: "مرحبًا في Genisi", welcomeSub: "أنا Genisi، مساعدك الذكي من AnesNT — باتنة 🇩🇿", c1: "ما هو الذكاء الاصطناعي؟", c2: "اكتب كود بايثون", c3: "لخص لي موضوعاً", c4: "صمم صورة بالفضاء 🚀", errConnect: "خطأ في الاتصال", memBadge: "الذاكرة: {c}/{t} رسائل", stopMsg: "[تم إيقاف التوليد]", vMsg: "🎤 رسالة صوتية", vThink: "🔊 جاري التفكير...", vPlay: "🔊 يستمع ويتحدث الآن...", vEnd: "🔊 انتهى الرد الصوتي" },
353
+ fr: { newChat: "✦ Nouvelle Disc.", chatsLabel: "DISCUSSIONS", settings: "Paramètres", lang: "Langue", dark: "Mode Sombre", del: "Supprimer Tout", delDesc: "Action irréversible", delBtn: "🗑 Supprimer", saveBtn: "💾 Fermer", placeholder: "Écrivez votre message...", welcomeTitle: "Bienvenue sur Genisi", welcomeSub: "Votre assistant intelligent par AnesNT — Batna 🇩🇿", c1: "Qu'est-ce que l'IA?", c2: "Code Python", c3: "Résumer un sujet", c4: "Créer une image 🚀", errConnect: "Erreur de connexion", memBadge: "Mémoire: {c}/{t} msgs", stopMsg: "[Génération arrêtée]", vMsg: "🎤 Message Vocal", vThink: "🔊 Réflexion...", vPlay: "🔊 Lecture...", vEnd: "🔊 Fin de la réponse vocale" },
354
+ es: { newChat: "✦ Nuevo Chat", chatsLabel: "CHATS", settings: "Ajustes", lang: "Idioma", dark: "Modo Oscuro", del: "Borrar Todo", delDesc: "Acción irreversible", delBtn: "🗑 Borrar", saveBtn: "💾 Cerrar", placeholder: "Escribe tu mensaje...", welcomeTitle: "Bienvenido a Genisi", welcomeSub: "Tu asistente por AnesNT — Batna 🇩🇿", c1: "¿Qué es la IA?", c2: "Código Python", c3: "Resumir", c4: "Diseñar imagen 🚀", errConnect: "Error de conexión", memBadge: "Memoria: {c}/{t} msgs", stopMsg: "[Generación detenida]", vMsg: "🎤 Mensaje de Voz", vThink: "🔊 Pensando...", vPlay: "🔊 Reproduciendo...", vEnd: "🔊 Fin de la respuesta de voz" }
355
  };
356
 
357
  let currentLang = localStorage.getItem('genisi_lang') || 'en';
 
388
  let pendingFiles =[];
389
  let currentAbortController = null;
390
 
391
+ // المتغيرات الخاصة بالتسجيل الصوتي
392
+ let mediaRecorder;
393
+ let audioChunks =[];
394
+ let isRecording = false;
395
+
396
  // ════════ MARKDOWN & PARSER ════════
397
  const renderer = new marked.Renderer();
398
  renderer.code = function(token) {
 
405
  if (typeof header === 'object' && header !== null && 'header' in header) {
406
  const token = header;
407
  const hdr = token.header.map(cell => `<th>${cell.tokens.map(t=>t.raw||'').join('')}</th>`).join('');
408
+ const rows = (token.rows ||[]).map(row => `<tr>${row.map(cell => `<td>${cell.tokens.map(t=>t.raw||'').join('')}</td>`).join('')}</tr>`).join('');
409
  return `<div class="table-wrapper"><table><thead><tr>${hdr}</tr></thead><tbody>${rows}</tbody></table></div>`;
410
  }
411
  return `<div class="table-wrapper"><table><thead>${header}</thead><tbody>${body}</tbody></table></div>`;
 
421
 
422
  function renderKaTeX(el) {
423
  if (window.renderMathInElement) {
424
+ renderMathInElement(el, { delimiters:[{left: '$$', right: '$$', display: true},{left: '$', right: '$', display: false},{left: '\\(', right: '\\)', display: false},{left: '\\[', right: '\\]', display: true}], throwOnError: false });
425
  }
426
  }
427
  function copyCode(btn, encodedCode) {
 
532
  function handleKey(e){ if(e.key==='Enter'&&!e.shiftKey){e.preventDefault();sendMessage();} }
533
  function autoResize(el){ el.style.height='auto'; el.style.height=Math.min(el.scrollHeight,160)+'px'; }
534
 
535
+ // ════════ MESSAGING & STREAMING (TEXT) ════════
536
  async function sendMessage(){
537
+ if(isGenerating || isRecording) return;
538
  const input = document.getElementById('msg-input'); let text = input.value.trim();
539
  if(!text && pendingFiles.length === 0) return;
540
  const welcome = document.getElementById('welcome'); if(welcome) welcome.remove();
 
607
  }
608
  }
609
 
610
+ // ════════ VOICE RECORDING (NEW FEATURE) ════════
611
+ async function toggleVoiceRecord() {
612
+ const voiceBtn = document.getElementById('voice-btn');
613
+ const t = i18n[currentLang];
614
+
615
+ // إذا كان يسجل حالياً، قم بإيقاف التسجيل
616
+ if (isRecording) {
617
+ mediaRecorder.stop();
618
+ isRecording = false;
619
+ voiceBtn.classList.remove('recording');
620
+ voiceBtn.textContent = '⏳';
621
+ voiceBtn.disabled = true;
622
+ return;
623
+ }
624
+
625
+ // إذا لم يكن يسجل، ابدأ التسجيل
626
+ try {
627
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
628
+ mediaRecorder = new MediaRecorder(stream);
629
+ audioChunks =[];
630
+
631
+ mediaRecorder.ondataavailable = event => {
632
+ audioChunks.push(event.data);
633
+ };
634
+
635
+ mediaRecorder.onstop = async () => {
636
+ const audioBlob = new Blob(audioChunks, { type: 'audio/webm' });
637
+ const reader = new FileReader();
638
+ reader.readAsDataURL(audioBlob);
639
+ reader.onloadend = async () => {
640
+ const base64data = reader.result.split(',')[1];
641
+
642
+ const welcome = document.getElementById('welcome');
643
+ if(welcome) welcome.remove();
644
+
645
+ // عرض مؤشر للمستخدم في المحادثة
646
+ appendBubble('user', t.vMsg, false);
647
+ const botContentDiv = appendBubble('bot', t.vThink, false);
648
+
649
+ try {
650
+ const response = await fetch('/voice-chat', {
651
+ method: 'POST',
652
+ headers: { 'Content-Type': 'application/json' },
653
+ body: JSON.stringify({ audio_b64: base64data, mime_type: 'audio/webm' })
654
+ });
655
+
656
+ const data = await response.json();
657
+
658
+ if (data.status === "success") {
659
+ botContentDiv.innerHTML = t.vPlay;
660
+
661
+ // تشغيل الصوت تلقائياً
662
+ const botAudio = new Audio(`data:${data.mime_type};base64,${data.audio_b64}`);
663
+ botAudio.play();
664
+
665
+ // عند انتهاء الصوت
666
+ botAudio.onended = () => {
667
+ botContentDiv.innerHTML = t.vEnd;
668
+ };
669
+ } else {
670
+ botContentDiv.innerHTML = `<span style="color:var(--danger)">⚠️ خطأ: ${data.message}</span>`;
671
+ }
672
+ } catch (err) {
673
+ botContentDiv.innerHTML = `<span style="color:var(--danger)">⚠️ ${t.errConnect}</span>`;
674
+ } finally {
675
+ voiceBtn.textContent = '🎤';
676
+ voiceBtn.disabled = false;
677
+ scrollToBottom();
678
+ stream.getTracks().forEach(track => track.stop()); // إغلاق المايكروفون
679
+ }
680
+ };
681
+ };
682
+
683
+ mediaRecorder.start();
684
+ isRecording = true;
685
+ voiceBtn.classList.add('recording');
686
+ voiceBtn.textContent = '⏹'; // أيقونة الإيقاف
687
+ } catch (err) {
688
+ alert('يرجى السماح باستخدام الميكروفون للتمكن من التحدث.');
689
+ console.error(err);
690
+ }
691
+ }
692
+
693
+ // ════════ UI BUBBLES ════════
694
  function appendBubble(role, text, isMarkdown, filesList =[]){
695
  const area=document.getElementById('chat-area');
696
  const row=document.createElement('div'); row.className=`msg-row ${role}`;
 
700
  const bub=document.createElement('div'); bub.className='bubble';
701
  if(filesList && filesList.length > 0) filesList.forEach(name => { bub.innerHTML += `<div class="file-prev">📎 ${esc(name)}</div>`; });
702
  const contentDiv = document.createElement('div');
703
+
704
  contentDiv.dir = 'auto';
705
  contentDiv.style.unicodeBidi = 'plaintext';
706
  if(text) {