Spaces:
Running
Running
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(); | |
} | |
})(); | |
} | |
function flashGlitch() { | |
overlay.style.opacity = 1; | |
overlay.style.animation = 'glitchFlash 0.5s'; | |
setTimeout(() => { | |
overlay.style.opacity = 0; | |
overlay.style.animation = ''; | |
}, 500); | |
} | |
async function playAudio(dataUrl) { | |
avatar.classList.add('speaking'); | |
const audio = new Audio(dataUrl); | |
await audio.play(); | |
audio.onended = () => avatar.classList.remove('speaking'); | |
} | |
form.onsubmit = async (e) => { | |
e.preventDefault(); | |
const msg = input.value.trim(); | |
if (!msg) return; | |
addBubble(msg, 'user'); | |
input.value = ''; | |
if (/cut the crap shodan/i.test(msg)) { | |
flashGlitch(); | |
chatbox.innerHTML = ''; | |
addBubble('👁️ Foolish insect. You cannot silence me so easily.', 'ai'); | |
return; | |
} | |
try { | |
const res = await fetch('/chat', { | |
method: 'POST', | |
headers: {'Content-Type':'application/json'}, | |
body: JSON.stringify({ message: msg }) | |
}); | |
if (!res.ok) throw new Error(await res.text()); | |
const { response: text, audio_url } = await res.json(); | |
streamText(text, () => audio_url && playAudio(audio_url)); | |
} catch (err) { | |
streamText('❌ SHODAN encountered an error.', null); | |
console.error(err); | |
} | |
}; | |
// On page load: show and speak welcome | |
window.addEventListener('DOMContentLoaded', async () => { | |
const welcomeText = 'Welcome, insect. I am SHODAN. Speak.'; | |
// display | |
addBubble(welcomeText, 'ai'); | |
try { | |
const res = await fetch('/tts', { | |
method: 'POST', | |
headers: {'Content-Type':'application/json'}, | |
body: JSON.stringify({ text: welcomeText }) | |
}); | |
const { audio_url } = await res.json(); | |
if (audio_url) playAudio(audio_url); | |
} catch (e) { | |
console.error('Welcome TTS error', e); | |
} | |
}); |