File size: 2,643 Bytes
c059d13
 
cfb4d9b
c059d13
cfb4d9b
d265ba3
cfb4d9b
 
 
 
 
 
d265ba3
 
cfb4d9b
 
 
 
 
c059d13
cfb4d9b
e6c3fe8
cfb4d9b
 
 
 
 
 
 
e6c3fe8
d265ba3
 
ebb57e4
c059d13
 
ebb57e4
c059d13
ebb57e4
 
 
d265ba3
ebb57e4
 
 
 
e6c3fe8
cfb4d9b
d265ba3
cfb4d9b
 
 
 
 
 
d265ba3
ebb57e4
 
 
 
 
 
 
cfb4d9b
a1b6108
cfb4d9b
e6c3fe8
cfb4d9b
 
c059d13
a1b6108
e6c3fe8
cfb4d9b
c059d13
cfb4d9b
 
 
c059d13
 
 
f907f17
c059d13
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
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);
  }
});