atlury commited on
Commit
b663964
·
verified ·
1 Parent(s): 1d25e49

Create index.html

Browse files
Files changed (1) hide show
  1. index.html +366 -0
index.html ADDED
@@ -0,0 +1,366 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Voice Chat Bot</title>
7
+ <script src="https://cdn.jsdelivr.net/npm/onnxruntime-web/dist/ort.js"></script>
8
+ <script src="https://cdn.jsdelivr.net/npm/@ricky0123/[email protected]/dist/bundle.min.js"></script>
9
+ <script src="https://cdn.jsdelivr.net/npm/@xenova/[email protected]"></script>
10
+ <style>
11
+ body {
12
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
13
+ margin: 0;
14
+ padding: 20px;
15
+ background-color: #1a1a1a;
16
+ color: #f0f0f0;
17
+ }
18
+ .container {
19
+ max-width: 800px;
20
+ margin: 0 auto;
21
+ }
22
+ h1 {
23
+ color: #ffd700;
24
+ text-align: center;
25
+ margin-bottom: 10px;
26
+ }
27
+ .subtitle {
28
+ text-align: center;
29
+ color: #ffd700;
30
+ margin-bottom: 20px;
31
+ }
32
+ #chat-container {
33
+ display: flex;
34
+ flex-direction: column;
35
+ height: 70vh;
36
+ }
37
+ #conversation {
38
+ flex-grow: 1;
39
+ border: 1px solid #444;
40
+ padding: 10px;
41
+ overflow-y: scroll;
42
+ background-color: #2a2a2a;
43
+ border-radius: 5px;
44
+ margin-bottom: 20px;
45
+ }
46
+ #controls {
47
+ display: flex;
48
+ justify-content: center;
49
+ margin-bottom: 20px;
50
+ }
51
+ button {
52
+ font-size: 18px;
53
+ padding: 10px 20px;
54
+ background-color: #ffd700;
55
+ color: #1a1a1a;
56
+ border: none;
57
+ border-radius: 5px;
58
+ cursor: pointer;
59
+ transition: background-color 0.3s;
60
+ }
61
+ button:hover {
62
+ background-color: #ffec8b;
63
+ }
64
+ button:disabled {
65
+ background-color: #666;
66
+ cursor: not-allowed;
67
+ }
68
+ #visualizer {
69
+ width: 100%;
70
+ height: 100px;
71
+ background-color: #2a2a2a;
72
+ border-radius: 5px;
73
+ overflow: hidden;
74
+ margin-bottom: 20px;
75
+ }
76
+ .bar {
77
+ width: 5px;
78
+ height: 100%;
79
+ background-color: #ffd700;
80
+ display: inline-block;
81
+ margin-right: 1px;
82
+ }
83
+ #loading {
84
+ position: fixed;
85
+ top: 0;
86
+ left: 0;
87
+ width: 100%;
88
+ height: 100%;
89
+ background-color: rgba(0, 0, 0, 0.8);
90
+ display: flex;
91
+ justify-content: center;
92
+ align-items: center;
93
+ z-index: 1000;
94
+ }
95
+ .spinner {
96
+ width: 50px;
97
+ height: 50px;
98
+ border: 5px solid #f3f3f3;
99
+ border-top: 5px solid #ffd700;
100
+ border-radius: 50%;
101
+ animation: spin 1s linear infinite;
102
+ }
103
+ @keyframes spin {
104
+ 0% { transform: rotate(0deg); }
105
+ 100% { transform: rotate(360deg); }
106
+ }
107
+ #configuration {
108
+ margin-bottom: 20px;
109
+ }
110
+ select {
111
+ width: 100%;
112
+ padding: 10px;
113
+ font-size: 16px;
114
+ background-color: #2a2a2a;
115
+ color: #f0f0f0;
116
+ border: 1px solid #444;
117
+ border-radius: 5px;
118
+ }
119
+ #model-info {
120
+ margin-top: 10px;
121
+ font-size: 14px;
122
+ color: #aaa;
123
+ }
124
+ #logs {
125
+ background-color: #2a2a2a;
126
+ border: 1px solid #444;
127
+ border-radius: 5px;
128
+ padding: 10px;
129
+ height: 200px;
130
+ overflow-y: scroll;
131
+ font-family: monospace;
132
+ font-size: 14px;
133
+ }
134
+ #clear-logs {
135
+ margin-top: 10px;
136
+ font-size: 14px;
137
+ padding: 5px 10px;
138
+ }
139
+ </style>
140
+ </head>
141
+ <body>
142
+ <div id="loading">
143
+ <div class="spinner"></div>
144
+ </div>
145
+ <div class="container">
146
+ <h1>Voice Chat Bot Demo</h1>
147
+ <p class="subtitle">For best results, use headphones.</p>
148
+ <div id="chat-container">
149
+ <div id="controls">
150
+ <button id="startButton" disabled>Begin Call</button>
151
+ </div>
152
+ <div id="configuration">
153
+ <select id="configSelect">
154
+ <option value="fastest">Fastest</option>
155
+ <option value="balanced">Balanced</option>
156
+ <option value="quality">Highest Quality</option>
157
+ </select>
158
+ <div id="model-info">
159
+ TTS: Xenova/mms-tts-eng / STT: Xenova/whisper-tiny.en / LLM: Placeholder
160
+ </div>
161
+ </div>
162
+ <div id="visualizer"></div>
163
+ <div id="conversation"></div>
164
+ </div>
165
+ <h2>Logs</h2>
166
+ <div id="logs"></div>
167
+ <button id="clear-logs">Clear</button>
168
+ </div>
169
+
170
+ <script type="module">
171
+ import { pipeline, env } from 'https://cdn.jsdelivr.net/npm/@xenova/[email protected]';
172
+
173
+ env.localModelPath = './models';
174
+
175
+ const conversationDiv = document.getElementById('conversation');
176
+ const startButton = document.getElementById('startButton');
177
+ const visualizer = document.getElementById('visualizer');
178
+ const loadingDiv = document.getElementById('loading');
179
+ const logsDiv = document.getElementById('logs');
180
+ const clearLogsButton = document.getElementById('clear-logs');
181
+
182
+ let myvad;
183
+ let sttPipeline;
184
+ let ttsPipeline;
185
+ let audioContext;
186
+ let analyser;
187
+ let dataArray;
188
+ let bars;
189
+ let animationId;
190
+ let isListening = false;
191
+ let mediaStream;
192
+ let audioSource;
193
+
194
+ function createVisualizer() {
195
+ const barCount = 64;
196
+ for (let i = 0; i < barCount; i++) {
197
+ const bar = document.createElement('div');
198
+ bar.className = 'bar';
199
+ visualizer.appendChild(bar);
200
+ }
201
+ bars = visualizer.getElementsByClassName('bar');
202
+ }
203
+
204
+ function updateVisualizer() {
205
+ analyser.getByteFrequencyData(dataArray);
206
+ for (let i = 0; i < bars.length; i++) {
207
+ const barHeight = dataArray[i] / 2;
208
+ bars[i].style.height = barHeight + 'px';
209
+ }
210
+ animationId = requestAnimationFrame(updateVisualizer);
211
+ }
212
+
213
+ async function initializePipelines() {
214
+ try {
215
+ sttPipeline = await pipeline('automatic-speech-recognition', 'Xenova/whisper-tiny.en');
216
+ ttsPipeline = await pipeline('text-to-speech', 'Xenova/mms-tts-eng', {
217
+ quantized: false,
218
+ });
219
+ addLog('System: Voice Chat Bot initialized. Click "Begin Call" to start.');
220
+ startButton.disabled = false;
221
+ loadingDiv.style.display = 'none';
222
+ } catch (error) {
223
+ console.error('Error initializing pipelines:', error);
224
+ addLog('System: Error initializing Voice Chat Bot. Please check the console for details.');
225
+ loadingDiv.style.display = 'none';
226
+ }
227
+ }
228
+
229
+ async function processSpeech(audio) {
230
+ try {
231
+ if (!sttPipeline || !ttsPipeline) {
232
+ throw new Error('Pipelines not initialized');
233
+ }
234
+
235
+ const transcription = await sttPipeline(audio);
236
+ addLog(`User: ${transcription.text}`);
237
+
238
+ const botResponse = `I heard you say: "${transcription.text}". This is a placeholder response.`;
239
+ addLog(`Bot: ${botResponse}`);
240
+
241
+ const speechOutput = await ttsPipeline(botResponse);
242
+ playAudio(speechOutput.audio);
243
+ } catch (error) {
244
+ console.error('Error processing speech:', error);
245
+ addLog('System: Error processing speech. Please try again.');
246
+ }
247
+ }
248
+
249
+ function addLog(message) {
250
+ const now = new Date();
251
+ const timestamp = now.toLocaleTimeString();
252
+ const logMessage = `[${timestamp}] ${message}`;
253
+ const messageElement = document.createElement('div');
254
+ messageElement.textContent = logMessage;
255
+ logsDiv.appendChild(messageElement);
256
+ logsDiv.scrollTop = logsDiv.scrollHeight;
257
+ }
258
+
259
+ function playAudio(audioArray) {
260
+ const audioBuffer = audioContext.createBuffer(1, audioArray.length, 16000);
261
+ const channelData = audioBuffer.getChannelData(0);
262
+ channelData.set(audioArray);
263
+
264
+ const source = audioContext.createBufferSource();
265
+ source.buffer = audioBuffer;
266
+ source.connect(analyser);
267
+ analyser.connect(audioContext.destination);
268
+ source.start();
269
+ }
270
+
271
+ async function toggleListening() {
272
+ if (isListening) {
273
+ await stopListening();
274
+ } else {
275
+ await startListening();
276
+ }
277
+ }
278
+
279
+ async function startListening() {
280
+ try {
281
+ audioContext = new (window.AudioContext || window.webkitAudioContext)();
282
+ analyser = audioContext.createAnalyser();
283
+ analyser.fftSize = 128;
284
+ dataArray = new Uint8Array(analyser.frequencyBinCount);
285
+
286
+ mediaStream = await navigator.mediaDevices.getUserMedia({ audio: true });
287
+ audioSource = audioContext.createMediaStreamSource(mediaStream);
288
+ audioSource.connect(analyser);
289
+
290
+ myvad = await vad.MicVAD.new({
291
+ onSpeechStart: () => {
292
+ addLog('--- vad: speech start');
293
+ updateVisualizer();
294
+ },
295
+ onSpeechEnd: (audio) => {
296
+ addLog('--- vad: speech end');
297
+ cancelAnimationFrame(animationId);
298
+ processSpeech(audio);
299
+ },
300
+ onVADMisfire: () => {
301
+ addLog('--- vad: misfire (false positive)');
302
+ }
303
+ });
304
+
305
+ await myvad.start();
306
+ startButton.textContent = 'End Call';
307
+ isListening = true;
308
+ addLog('System: Listening... (VAD active)');
309
+ } catch (error) {
310
+ console.error('Error starting VAD:', error);
311
+ addLog('System: Error starting voice detection. Please check your microphone and try again.');
312
+ }
313
+ }
314
+
315
+ async function stopListening() {
316
+ if (myvad) {
317
+ try {
318
+ await myvad.stop(); // Changed from pause() to stop()
319
+ addLog('System: VAD stopped.');
320
+ } catch (error) {
321
+ console.error('Error stopping VAD:', error);
322
+ addLog('System: Error stopping VAD.');
323
+ }
324
+ }
325
+
326
+ // Stop all tracks in the media stream
327
+ if (mediaStream) {
328
+ mediaStream.getTracks().forEach(track => {
329
+ track.stop();
330
+ addLog(`System: Audio track ${track.kind} stopped.`);
331
+ });
332
+ }
333
+
334
+ // Disconnect the audio source from the analyser
335
+ if (audioSource) {
336
+ audioSource.disconnect();
337
+ addLog('System: Audio source disconnected.');
338
+ }
339
+
340
+ // Close the audio context
341
+ if (audioContext) {
342
+ try {
343
+ await audioContext.close();
344
+ addLog('System: Audio context closed.');
345
+ } catch (error) {
346
+ console.error('Error closing audio context:', error);
347
+ addLog('System: Error closing audio context.');
348
+ }
349
+ }
350
+
351
+ cancelAnimationFrame(animationId);
352
+ startButton.textContent = 'Begin Call';
353
+ isListening = false;
354
+ addLog('System: Call ended. All audio resources released.');
355
+ }
356
+
357
+ startButton.addEventListener('click', toggleListening);
358
+ clearLogsButton.addEventListener('click', () => {
359
+ logsDiv.innerHTML = '';
360
+ });
361
+
362
+ createVisualizer();
363
+ initializePipelines();
364
+ </script>
365
+ </body>
366
+ </html>