const form = document.getElementById('chat-form'); const input = form.querySelector('input'); const chatbox = document.getElementById('chatbox'); const avatar = document.getElementById('avatar'); const overlay = document.getElementById('overlay'); function addBubble(text, sender = 'user') { const bubble = document.createElement('div'); bubble.className = 'bubble ' + sender; bubble.innerText = text; chatbox.appendChild(bubble); chatbox.scrollTop = chatbox.scrollHeight; } function streamText(text, onDone) { const bubble = document.createElement('div'); bubble.className = 'bubble ai'; chatbox.appendChild(bubble); chatbox.scrollTop = chatbox.scrollHeight; let i = 0; function stream() { if (i < text.length) { bubble.innerText += text[i++]; chatbox.scrollTop = chatbox.scrollHeight; setTimeout(stream, 20); } else { onDone && onDone(); } } stream(); } function playAudio(url) { avatar.classList.add('speaking'); const audio = new Audio(url); audio.play(); audio.onended = () => { avatar.classList.remove('speaking'); fetch(`/delete_audio?path=${encodeURIComponent(url)}`); }; } form.onsubmit = async (e) => { e.preventDefault(); const msg = input.value.trim(); if (!msg) return; addBubble(msg, 'user'); input.value = ''; try { const response = await fetch('/ask', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ message: msg }) }); const { text, audio_url } = await response.json(); streamText(text, () => { if (audio_url) playAudio(audio_url); }); } catch (err) { streamText("❌ SHODAN encountered an error.", null); console.error(err); } };