freddyaboulton HF staff commited on
Commit
fd97f9d
·
verified ·
1 Parent(s): 22a0d8f

Upload folder using huggingface_hub

Browse files
Files changed (2) hide show
  1. app.py +2 -4
  2. index.html +227 -83
app.py CHANGED
@@ -116,9 +116,7 @@ def _(webrtc_id: str):
116
  async def output_stream():
117
  async for output in stream.output_stream(webrtc_id):
118
  chatbot = output.args[0]
119
- if len(chatbot) > 1:
120
- yield f"event: output\ndata: {json.dumps(chatbot[-2])}\n\n"
121
- yield f"event: output\ndata: {json.dumps(chatbot[-1])}\n\n"
122
 
123
  return StreamingResponse(output_stream(), media_type="text/event-stream")
124
 
@@ -127,7 +125,7 @@ if __name__ == "__main__":
127
  import os
128
 
129
  if (mode := os.getenv("MODE")) == "UI":
130
- stream.ui.launch(server_port=7860)
131
  elif mode == "PHONE":
132
  stream.fastphone(host="0.0.0.0", port=7860)
133
  else:
 
116
  async def output_stream():
117
  async for output in stream.output_stream(webrtc_id):
118
  chatbot = output.args[0]
119
+ yield f"event: output\ndata: {json.dumps(chatbot[-1])}\n\n"
 
 
120
 
121
  return StreamingResponse(output_stream(), media_type="text/event-stream")
122
 
 
125
  import os
126
 
127
  if (mode := os.getenv("MODE")) == "UI":
128
+ stream.ui.launch(server_port=7860, server_name="0.0.0.0")
129
  elif mode == "PHONE":
130
  stream.fastphone(host="0.0.0.0", port=7860)
131
  else:
index.html CHANGED
@@ -17,35 +17,51 @@
17
  }
18
 
19
  .container {
20
- display: grid;
21
- grid-template-columns: 1fr 1fr;
22
  gap: 20px;
23
  height: calc(100% - 100px);
24
  margin-bottom: 20px;
25
  }
26
 
27
- .visualization-container {
28
  border: 2px solid #00ff00;
29
  padding: 20px;
30
  display: flex;
31
  flex-direction: column;
 
 
 
 
 
 
 
 
32
  align-items: center;
33
- position: relative;
 
 
34
  }
35
 
36
- #visualizer {
37
- width: 100%;
38
- height: 100%;
39
- background-color: #000;
40
  }
41
 
42
- .chat-container {
43
- border: 2px solid #00ff00;
44
- padding: 20px;
45
  display: flex;
46
- flex-direction: column;
 
 
 
 
 
47
  height: 100%;
48
- box-sizing: border-box;
 
 
 
49
  }
50
 
51
  .chat-messages {
@@ -70,11 +86,9 @@
70
  background-color: #002200;
71
  }
72
 
73
- .controls {
74
- text-align: center;
75
- }
76
-
77
  button {
 
 
78
  background-color: #000;
79
  color: #00ff00;
80
  border: 2px solid #00ff00;
@@ -86,8 +100,7 @@
86
  }
87
 
88
  button:hover {
89
- background-color: #00ff00;
90
- color: #000;
91
  }
92
 
93
  #audio-output {
@@ -108,49 +121,155 @@
108
  transparent 2px);
109
  pointer-events: none;
110
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
111
  </style>
112
  </head>
113
 
114
  <body>
115
  <div class="container">
116
- <div class="visualization-container">
117
- <canvas id="visualizer"></canvas>
118
- <div class="crt-overlay"></div>
119
- </div>
120
  <div class="chat-container">
121
  <div class="chat-messages" id="chat-messages"></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
122
  </div>
123
- </div>
124
- <div class="controls">
125
- <button id="start-button">Start</button>
126
  </div>
127
  <audio id="audio-output"></audio>
128
 
129
  <script>
130
  let audioContext;
131
- let analyser;
132
- let dataArray;
133
- let animationId;
134
  let chatHistory = [];
135
  let peerConnection;
136
  let webrtc_id;
137
 
138
- const visualizer = document.getElementById('visualizer');
139
- const ctx = visualizer.getContext('2d');
140
  const audioOutput = document.getElementById('audio-output');
141
  const startButton = document.getElementById('start-button');
142
  const chatMessages = document.getElementById('chat-messages');
143
 
144
- // Set canvas size
145
- function resizeCanvas() {
146
- visualizer.width = visualizer.offsetWidth;
147
- visualizer.height = visualizer.offsetHeight;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
148
  }
149
 
150
- window.addEventListener('resize', resizeCanvas);
151
- resizeCanvas();
152
-
153
- // Initialize WebRTC
154
  async function setupWebRTC() {
155
  const config = __RTC_CONFIGURATION__;
156
  peerConnection = new RTCPeerConnection(config);
@@ -160,25 +279,51 @@
160
  audio: true
161
  });
162
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
163
  stream.getTracks().forEach(track => {
164
  peerConnection.addTrack(track, stream);
165
  });
166
 
167
- // Audio visualization will be set up when we receive the output stream
 
 
 
 
168
 
169
  // Handle incoming audio
170
  peerConnection.addEventListener('track', (evt) => {
171
- if (audioOutput && audioOutput.srcObject !== evt.streams[0]) {
172
  audioOutput.srcObject = evt.streams[0];
173
  audioOutput.play();
174
 
175
- // Set up audio visualization on the output stream
176
- audioContext = new AudioContext();
177
- analyser = audioContext.createAnalyser();
178
- const source = audioContext.createMediaStreamSource(evt.streams[0]);
179
- source.connect(analyser);
180
- analyser.fftSize = 2048;
181
- dataArray = new Uint8Array(analyser.frequencyBinCount);
182
  }
183
  });
184
 
@@ -220,7 +365,7 @@
220
  await peerConnection.setRemoteDescription(serverResponse);
221
 
222
  // Start visualization
223
- draw();
224
 
225
  // create event stream to receive messages from /output
226
  const eventSource = new EventSource('/outputs?webrtc_id=' + webrtc_id);
@@ -235,6 +380,8 @@
235
 
236
  function handleMessage(event) {
237
  const eventJson = JSON.parse(event.data);
 
 
238
  if (eventJson.type === "send_input") {
239
  fetch('/input_hook', {
240
  method: 'POST',
@@ -246,6 +393,13 @@
246
  chatbot: chatHistory
247
  })
248
  });
 
 
 
 
 
 
 
249
  }
250
  }
251
 
@@ -258,36 +412,26 @@
258
  chatHistory.push({ role, content });
259
  }
260
 
261
- function draw() {
262
- animationId = requestAnimationFrame(draw);
263
-
264
- analyser.getByteTimeDomainData(dataArray);
265
-
266
- ctx.fillStyle = 'rgb(0, 0, 0)';
267
- ctx.fillRect(0, 0, visualizer.width, visualizer.height);
268
-
269
- ctx.lineWidth = 2;
270
- ctx.strokeStyle = 'rgb(0, 255, 0)';
271
- ctx.beginPath();
272
 
273
- const sliceWidth = visualizer.width / dataArray.length;
274
- let x = 0;
 
275
 
276
- for (let i = 0; i < dataArray.length; i++) {
277
- const v = dataArray[i] / 128.0;
278
- const y = v * visualizer.height / 2;
279
 
280
- if (i === 0) {
281
- ctx.moveTo(x, y);
282
- } else {
283
- ctx.lineTo(x, y);
284
- }
285
-
286
- x += sliceWidth;
287
  }
288
-
289
- ctx.lineTo(visualizer.width, visualizer.height / 2);
290
- ctx.stroke();
291
  }
292
 
293
  function stop() {
@@ -306,27 +450,27 @@
306
  });
307
  }
308
 
309
- setTimeout(() => {
310
- peerConnection.close();
311
- }, 500);
312
  }
313
 
314
- if (animationId) {
315
- cancelAnimationFrame(animationId);
 
 
 
316
  }
317
-
318
  if (audioContext) {
319
  audioContext.close();
320
  }
 
 
321
  }
322
 
323
  startButton.addEventListener('click', () => {
324
  if (startButton.textContent === 'Start') {
325
  setupWebRTC();
326
- startButton.textContent = 'Stop';
327
  } else {
328
  stop();
329
- startButton.textContent = 'Start';
330
  }
331
  });
332
  </script>
 
17
  }
18
 
19
  .container {
20
+ display: flex;
21
+ flex-direction: column;
22
  gap: 20px;
23
  height: calc(100% - 100px);
24
  margin-bottom: 20px;
25
  }
26
 
27
+ .chat-container {
28
  border: 2px solid #00ff00;
29
  padding: 20px;
30
  display: flex;
31
  flex-direction: column;
32
+ flex-grow: 1;
33
+ box-sizing: border-box;
34
+ }
35
+
36
+ .controls-container {
37
+ border: 2px solid #00ff00;
38
+ padding: 20px;
39
+ display: flex;
40
  align-items: center;
41
+ gap: 20px;
42
+ height: 128px;
43
+ box-sizing: border-box;
44
  }
45
 
46
+ .visualization-container {
47
+ flex-grow: 1;
48
+ display: flex;
49
+ align-items: center;
50
  }
51
 
52
+ .box-container {
 
 
53
  display: flex;
54
+ justify-content: space-between;
55
+ height: 64px;
56
+ width: 100%;
57
+ }
58
+
59
+ .box {
60
  height: 100%;
61
+ width: 8px;
62
+ background: #00ff00;
63
+ border-radius: 8px;
64
+ transition: transform 0.05s ease;
65
  }
66
 
67
  .chat-messages {
 
86
  background-color: #002200;
87
  }
88
 
 
 
 
 
89
  button {
90
+ height: 64px;
91
+ min-width: 120px;
92
  background-color: #000;
93
  color: #00ff00;
94
  border: 2px solid #00ff00;
 
100
  }
101
 
102
  button:hover {
103
+ border-width: 3px;
 
104
  }
105
 
106
  #audio-output {
 
121
  transparent 2px);
122
  pointer-events: none;
123
  }
124
+
125
+ /* Add these new styles */
126
+ .icon-with-spinner {
127
+ display: flex;
128
+ align-items: center;
129
+ justify-content: center;
130
+ gap: 12px;
131
+ min-width: 180px;
132
+ }
133
+
134
+ .spinner {
135
+ width: 20px;
136
+ height: 20px;
137
+ border: 2px solid #00ff00;
138
+ border-top-color: transparent;
139
+ border-radius: 50%;
140
+ animation: spin 1s linear infinite;
141
+ flex-shrink: 0;
142
+ }
143
+
144
+ @keyframes spin {
145
+ to {
146
+ transform: rotate(360deg);
147
+ }
148
+ }
149
+
150
+ .pulse-container {
151
+ display: flex;
152
+ align-items: center;
153
+ justify-content: center;
154
+ gap: 12px;
155
+ min-width: 180px;
156
+ }
157
+
158
+ .pulse-circle {
159
+ width: 20px;
160
+ height: 20px;
161
+ border-radius: 50%;
162
+ background-color: #00ff00;
163
+ opacity: 0.2;
164
+ flex-shrink: 0;
165
+ transform: translateX(-0%) scale(var(--audio-level, 1));
166
+ transition: transform 0.1s ease;
167
+ }
168
+
169
+ /* Add styles for typing indicator */
170
+ .typing-indicator {
171
+ padding: 8px;
172
+ background-color: #002200;
173
+ border-radius: 4px;
174
+ margin-bottom: 10px;
175
+ display: none;
176
+ }
177
+
178
+ .dots {
179
+ display: inline-flex;
180
+ gap: 4px;
181
+ }
182
+
183
+ .dot {
184
+ width: 8px;
185
+ height: 8px;
186
+ background-color: #00ff00;
187
+ border-radius: 50%;
188
+ animation: pulse 1.5s infinite;
189
+ opacity: 0.5;
190
+ }
191
+
192
+ .dot:nth-child(2) {
193
+ animation-delay: 0.5s;
194
+ }
195
+
196
+ .dot:nth-child(3) {
197
+ animation-delay: 1s;
198
+ }
199
+
200
+ @keyframes pulse {
201
+
202
+ 0%,
203
+ 100% {
204
+ opacity: 0.5;
205
+ transform: scale(1);
206
+ }
207
+
208
+ 50% {
209
+ opacity: 1;
210
+ transform: scale(1.2);
211
+ }
212
+ }
213
  </style>
214
  </head>
215
 
216
  <body>
217
  <div class="container">
 
 
 
 
218
  <div class="chat-container">
219
  <div class="chat-messages" id="chat-messages"></div>
220
+ <!-- Move typing indicator outside the chat messages -->
221
+ <div class="typing-indicator" id="typing-indicator">
222
+ <div class="dots">
223
+ <div class="dot"></div>
224
+ <div class="dot"></div>
225
+ <div class="dot"></div>
226
+ </div>
227
+ </div>
228
+ </div>
229
+ <div class="controls-container">
230
+ <div class="visualization-container">
231
+ <div class="box-container">
232
+ <!-- Boxes will be dynamically added here -->
233
+ </div>
234
+ </div>
235
+ <button id="start-button">Start</button>
236
  </div>
 
 
 
237
  </div>
238
  <audio id="audio-output"></audio>
239
 
240
  <script>
241
  let audioContext;
242
+ let analyser_input, analyser_output;
243
+ let dataArray_input, dataArray_output;
244
+ let animationId_input, animationId_output;
245
  let chatHistory = [];
246
  let peerConnection;
247
  let webrtc_id;
248
 
 
 
249
  const audioOutput = document.getElementById('audio-output');
250
  const startButton = document.getElementById('start-button');
251
  const chatMessages = document.getElementById('chat-messages');
252
 
253
+ function updateButtonState() {
254
+ if (peerConnection && (peerConnection.connectionState === 'connecting' || peerConnection.connectionState === 'new')) {
255
+ startButton.innerHTML = `
256
+ <div class="icon-with-spinner">
257
+ <div class="spinner"></div>
258
+ <span>Connecting...</span>
259
+ </div>
260
+ `;
261
+ } else if (peerConnection && peerConnection.connectionState === 'connected') {
262
+ startButton.innerHTML = `
263
+ <div class="pulse-container">
264
+ <div class="pulse-circle"></div>
265
+ <span>Stop</span>
266
+ </div>
267
+ `;
268
+ } else {
269
+ startButton.innerHTML = 'Start';
270
+ }
271
  }
272
 
 
 
 
 
273
  async function setupWebRTC() {
274
  const config = __RTC_CONFIGURATION__;
275
  peerConnection = new RTCPeerConnection(config);
 
279
  audio: true
280
  });
281
 
282
+ // Set up input visualization
283
+ audioContext = new AudioContext();
284
+ analyser_input = audioContext.createAnalyser();
285
+ const inputSource = audioContext.createMediaStreamSource(stream);
286
+ inputSource.connect(analyser_input);
287
+ analyser_input.fftSize = 64;
288
+ dataArray_input = new Uint8Array(analyser_input.frequencyBinCount);
289
+
290
+ function updateAudioLevel() {
291
+ analyser_input.getByteFrequencyData(dataArray_input);
292
+ const average = Array.from(dataArray_input).reduce((a, b) => a + b, 0) / dataArray_input.length;
293
+ const audioLevel = average / 255;
294
+
295
+ const pulseCircle = document.querySelector('.pulse-circle');
296
+ if (pulseCircle) {
297
+ pulseCircle.style.setProperty('--audio-level', 1 + audioLevel);
298
+ }
299
+
300
+ animationId_input = requestAnimationFrame(updateAudioLevel);
301
+ }
302
+ updateAudioLevel();
303
+
304
  stream.getTracks().forEach(track => {
305
  peerConnection.addTrack(track, stream);
306
  });
307
 
308
+ // Add connection state change listener
309
+ peerConnection.addEventListener('connectionstatechange', () => {
310
+ console.log('Connection state:', peerConnection.connectionState);
311
+ updateButtonState();
312
+ });
313
 
314
  // Handle incoming audio
315
  peerConnection.addEventListener('track', (evt) => {
316
+ if (audioOutput.srcObject !== evt.streams[0]) {
317
  audioOutput.srcObject = evt.streams[0];
318
  audioOutput.play();
319
 
320
+ // Set up output visualization
321
+ analyser_output = audioContext.createAnalyser();
322
+ const outputSource = audioContext.createMediaStreamSource(evt.streams[0]);
323
+ outputSource.connect(analyser_output);
324
+ analyser_output.fftSize = 2048;
325
+ dataArray_output = new Uint8Array(analyser_output.frequencyBinCount);
326
+ updateVisualization();
327
  }
328
  });
329
 
 
365
  await peerConnection.setRemoteDescription(serverResponse);
366
 
367
  // Start visualization
368
+ updateVisualization();
369
 
370
  // create event stream to receive messages from /output
371
  const eventSource = new EventSource('/outputs?webrtc_id=' + webrtc_id);
 
380
 
381
  function handleMessage(event) {
382
  const eventJson = JSON.parse(event.data);
383
+ const typingIndicator = document.getElementById('typing-indicator');
384
+
385
  if (eventJson.type === "send_input") {
386
  fetch('/input_hook', {
387
  method: 'POST',
 
393
  chatbot: chatHistory
394
  })
395
  });
396
+ } else if (eventJson.type === "log") {
397
+ if (eventJson.data === "pause_detected") {
398
+ typingIndicator.style.display = 'block';
399
+ chatMessages.scrollTop = chatMessages.scrollHeight;
400
+ } else if (eventJson.data === "response_starting") {
401
+ typingIndicator.style.display = 'none';
402
+ }
403
  }
404
  }
405
 
 
412
  chatHistory.push({ role, content });
413
  }
414
 
415
+ // Add this after other const declarations
416
+ const boxContainer = document.querySelector('.box-container');
417
+ const numBars = 32;
418
+ for (let i = 0; i < numBars; i++) {
419
+ const box = document.createElement('div');
420
+ box.className = 'box';
421
+ boxContainer.appendChild(box);
422
+ }
 
 
 
423
 
424
+ // Replace the draw function with updateVisualization
425
+ function updateVisualization() {
426
+ animationId_output = requestAnimationFrame(updateVisualization);
427
 
428
+ analyser_output.getByteFrequencyData(dataArray_output);
429
+ const bars = document.querySelectorAll('.box');
 
430
 
431
+ for (let i = 0; i < bars.length; i++) {
432
+ const barHeight = (dataArray_output[i] / 255) * 2;
433
+ bars[i].style.transform = `scaleY(${Math.max(0.1, barHeight)})`;
 
 
 
 
434
  }
 
 
 
435
  }
436
 
437
  function stop() {
 
450
  });
451
  }
452
 
453
+ peerConnection.close();
 
 
454
  }
455
 
456
+ if (animationId_input) {
457
+ cancelAnimationFrame(animationId_input);
458
+ }
459
+ if (animationId_output) {
460
+ cancelAnimationFrame(animationId_output);
461
  }
 
462
  if (audioContext) {
463
  audioContext.close();
464
  }
465
+
466
+ updateButtonState();
467
  }
468
 
469
  startButton.addEventListener('click', () => {
470
  if (startButton.textContent === 'Start') {
471
  setupWebRTC();
 
472
  } else {
473
  stop();
 
474
  }
475
  });
476
  </script>