MarfinF commited on
Commit
b827b19
Β·
1 Parent(s): 04c0c32

- add voice mode

Browse files
Files changed (2) hide show
  1. backend/app.py +1 -1
  2. frontend/index.html +190 -23
backend/app.py CHANGED
@@ -103,7 +103,7 @@ async def broadcast_user_list():
103
  async def periodic_recommendation():
104
  while True:
105
  user_list = list(clients.keys())
106
- if len(user_list) >= 2:
107
  sleep_task = asyncio.create_task(asyncio.sleep(60)) # Start sleep
108
 
109
  try:
 
103
  async def periodic_recommendation():
104
  while True:
105
  user_list = list(clients.keys())
106
+ if len(user_list) >= 1:
107
  sleep_task = asyncio.create_task(asyncio.sleep(60)) # Start sleep
108
 
109
  try:
frontend/index.html CHANGED
@@ -135,6 +135,91 @@
135
  border-radius: 5px;
136
  border: none;
137
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
138
  </style>
139
  </head>
140
 
@@ -146,22 +231,37 @@
146
  <div id="user-list" style="margin-bottom: 10px; text-align: left;"></div>
147
  <div id="chat-box"></div>
148
  <br>
149
- <input type="text" id="message" placeholder="Type your message..." disabled>
150
- <button onclick="sendMessage()" disabled>Send</button>
 
 
151
  <audio hidden="true" id="music-player" controls></audio>
152
  </div>
153
 
154
  <!-- MODAL FOR USERNAME INPUT -->
155
  <div id="username-modal" class="modal">
156
  <div class="modal-content">
157
- <h2>Enter Your Name</h2>
158
  <input type="text" id="username" placeholder="Your Name">
159
  <br>
160
- <button onclick="connectChat()">Join Chat</button>
 
 
161
  </div>
162
  </div>
163
 
164
  <script>
 
 
 
 
 
 
 
 
 
 
 
165
  let ws;
166
  let username = "";
167
  let musicStatus = "";
@@ -169,34 +269,55 @@
169
  song: "",
170
  genre: ""
171
  };
 
172
 
173
- function connectChat() {
174
- username = document.getElementById("username").value.trim();
175
- if (!username) {
 
 
 
 
 
 
 
 
 
176
  alert("Please enter a username!");
177
  return;
178
  }
179
  console.log("location host")
180
  console.log(location.host)
181
  // ws = new WebSocket(`${location.protocol === 'https:' ? 'wss' : 'ws'}://${location.host}/chat/${username}`);
182
- // ws = new WebSocket(`ws://localhost:8000/chat/${username}`);
 
 
 
 
 
 
 
 
 
 
 
 
183
 
184
- ws = new WebSocket(`${location.protocol === 'https:' ? 'wss' : 'ws'}://${location.host}/chat/${username}`);
185
 
186
- document.getElementById("music-player").addEventListener("play", () => {
187
  musicStatus = "playing"
188
  });
189
- document.getElementById("music-player").addEventListener("ended", () => {
190
  musicStatus = "stop"
191
  if (delayedMusic.song != "") playCurrentMusic(delayedMusic)
192
  });
193
 
194
  ws.onmessage = function (event) {
195
  const data = JSON.parse(event.data);
196
- const chatBox = document.getElementById("chat-box");
197
 
198
  if (data.type === "user_list") {
199
- const userListDiv = document.getElementById("user-list");
200
  userListDiv.innerHTML = `<strong>πŸ‘₯ Online:</strong> ${data.users.join(", ")}`;
201
  return;
202
  }
@@ -227,15 +348,16 @@
227
  chatBox.appendChild(messageDiv);
228
  }
229
 
 
230
  chatBox.scrollTop = chatBox.scrollHeight;
231
  };
232
 
233
  ws.onopen = function () {
234
- document.getElementById("message").disabled = false;
235
- document.getElementById("message").nextElementSibling.disabled = false;
236
 
237
  // Hide modal
238
- document.getElementById("username-modal").style.display = "none";
239
  };
240
 
241
  ws.onerror = function () {
@@ -248,7 +370,6 @@
248
  }
249
 
250
  function playCurrentMusic(recommendationSong) {
251
- const chatBox = document.getElementById("chat-box");
252
  delayedMusic = {
253
  song: "",
254
  genre: ""
@@ -259,14 +380,15 @@
259
  chatBox.appendChild(recommendationDiv);
260
  console.log("recommendation song")
261
  console.log(recommendationSong)
262
- document.getElementById("music-player").src = recommendationSong.song
263
- document.getElementById("music-player").play()
264
  }
265
 
266
- function sendMessage() {
267
- let inputField = document.getElementById("message");
268
- let messageText = inputField.value.trim();
269
- let chatBox = document.getElementById("chat-box");
 
270
 
271
  if (messageText === "" || !ws || ws.readyState !== WebSocket.OPEN) return;
272
 
@@ -282,6 +404,51 @@
282
  inputField.value = "";
283
  chatBox.scrollTop = chatBox.scrollHeight;
284
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
285
  </script>
286
  </body>
287
 
 
135
  border-radius: 5px;
136
  border: none;
137
  }
138
+
139
+ #voice-btn {
140
+ background: #d8e7f8;
141
+ color: black;
142
+ padding: 8px 15px;
143
+ margin-top: 10px;
144
+ cursor: pointer;
145
+ border-radius: 5px;
146
+ border: none;
147
+ }
148
+
149
+ #mic-bubble {
150
+ width: 60px;
151
+ height: 60px;
152
+ border-radius: 50%;
153
+ background-color: #555;
154
+ margin-left: 10px;
155
+ cursor: pointer;
156
+ transition: background-color 0.3s ease, transform 0.3s ease;
157
+ }
158
+
159
+ /* Saat recording aktif */
160
+ #mic-bubble.recording {
161
+ background-color: red;
162
+ animation: pulse-recording 1s infinite;
163
+ }
164
+
165
+ /* Saat suara terdeteksi (speechstart) */
166
+ #mic-bubble.voice-active {
167
+ background-color: #00e676;
168
+ box-shadow: 0 0 12px 4px rgba(0, 230, 118, 0.6);
169
+ animation: pulse-voice 0.5s ease-out;
170
+ }
171
+
172
+ #cafe-mode-btn {
173
+ background: linear-gradient(to right, #b47b48, #d5a679);
174
+ border: none;
175
+ color: white;
176
+ padding: 10px 20px;
177
+ margin-bottom: 20px;
178
+ border-radius: 25px;
179
+ font-size: 16px;
180
+ font-family: 'Courier New', Courier, monospace;
181
+ cursor: pointer;
182
+ box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
183
+ transition: all 0.3s ease;
184
+ }
185
+
186
+ #cafe-mode-btn:hover {
187
+ transform: scale(1.05);
188
+ box-shadow: 0 6px 14px rgba(0, 0, 0, 0.25);
189
+ }
190
+
191
+ #voice-btn.recording {
192
+ animation: pulse 1s infinite;
193
+ background-color: red !important;
194
+ }
195
+
196
+ @keyframes pulse-recording {
197
+ 0% {
198
+ box-shadow: 0 0 0 0 rgba(255, 0, 0, 0.4);
199
+ }
200
+
201
+ 70% {
202
+ box-shadow: 0 0 0 15px rgba(255, 0, 0, 0);
203
+ }
204
+
205
+ 100% {
206
+ box-shadow: 0 0 0 0 rgba(255, 0, 0, 0);
207
+ }
208
+ }
209
+
210
+ @keyframes pulse-voice {
211
+ 0% {
212
+ transform: scale(1);
213
+ }
214
+
215
+ 50% {
216
+ transform: scale(1.2);
217
+ }
218
+
219
+ 100% {
220
+ transform: scale(1);
221
+ }
222
+ }
223
  </style>
224
  </head>
225
 
 
231
  <div id="user-list" style="margin-bottom: 10px; text-align: left;"></div>
232
  <div id="chat-box"></div>
233
  <br>
234
+ <input type="text" id="message" placeholder="Ketik pesanmu..." disabled>
235
+ <div id="mic-bubble" title="~"></div>
236
+ <button id="message-send" onclick="sendMessage(null)" disabled>Kirim</button>
237
+ <button id="voice-btn" onclick="toggleVoice()" disabled>πŸŽ™</button>
238
  <audio hidden="true" id="music-player" controls></audio>
239
  </div>
240
 
241
  <!-- MODAL FOR USERNAME INPUT -->
242
  <div id="username-modal" class="modal">
243
  <div class="modal-content">
244
+ <h2>Masukkan Namamu...</h2>
245
  <input type="text" id="username" placeholder="Your Name">
246
  <br>
247
+ <button onclick="connectChat(false)">Join Chat</button>
248
+ <p>Atau</p>
249
+ <button id="cafe-mode-btn" onclick="connectChat(true)">Cafe Mode</button>
250
  </div>
251
  </div>
252
 
253
  <script>
254
+ const chatBox = document.getElementById("chat-box");
255
+ const chatTyping = document.getElementById("message");
256
+ const chatSending = document.getElementById("message-send");
257
+ const chatVoice = document.getElementById("voice-btn")
258
+ const userListDiv = document.getElementById("user-list");
259
+ const dialogUserName = document.getElementById("username");
260
+ const dialog = document.getElementById("username-modal");
261
+ const micBubble = document.getElementById("mic-bubble");
262
+ const musicPlayer = document.getElementById("music-player");
263
+
264
+
265
  let ws;
266
  let username = "";
267
  let musicStatus = "";
 
269
  song: "",
270
  genre: ""
271
  };
272
+ let isCafe = false;
273
 
274
+ const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
275
+
276
+ if (!SpeechRecognition) {
277
+ alert("Browser tidak mendukung Speech Recognition. Gunakan Google Chrome.");
278
+ }
279
+
280
+ const recognition = new SpeechRecognition();
281
+
282
+ function connectChat(pIsCafe) {
283
+ isCafe = pIsCafe;
284
+ username = dialogUserName.value.trim();
285
+ if (!username && isCafe == false) {
286
  alert("Please enter a username!");
287
  return;
288
  }
289
  console.log("location host")
290
  console.log(location.host)
291
  // ws = new WebSocket(`${location.protocol === 'https:' ? 'wss' : 'ws'}://${location.host}/chat/${username}`);
292
+ if (isCafe == true) {
293
+ userListDiv.innerHTML = "<strong>πŸ‘₯ Mulailah ngobrol dan kami sesuaikan alunan irama yang sesuai untuk anda 🎡</strong>"
294
+ chatTyping.style.display = "none"
295
+ chatSending.style.display = "none"
296
+ chatVoice.style.display = "none"
297
+ startRecognition()
298
+ username = "cafe"
299
+ } else {
300
+ micBubble.style.display = "none"
301
+ chatSending.disabled = false
302
+ chatVoice.disabled = false
303
+ }
304
+ ws = new WebSocket(`ws://localhost:8000/chat/${username}`);
305
 
306
+ // ws = new WebSocket(`${location.protocol === 'https:' ? 'wss' : 'ws'}://${location.host}/chat/${username}`);
307
 
308
+ musicPlayer.addEventListener("play", () => {
309
  musicStatus = "playing"
310
  });
311
+ musicPlayer.addEventListener("ended", () => {
312
  musicStatus = "stop"
313
  if (delayedMusic.song != "") playCurrentMusic(delayedMusic)
314
  });
315
 
316
  ws.onmessage = function (event) {
317
  const data = JSON.parse(event.data);
318
+
319
 
320
  if (data.type === "user_list") {
 
321
  userListDiv.innerHTML = `<strong>πŸ‘₯ Online:</strong> ${data.users.join(", ")}`;
322
  return;
323
  }
 
348
  chatBox.appendChild(messageDiv);
349
  }
350
 
351
+
352
  chatBox.scrollTop = chatBox.scrollHeight;
353
  };
354
 
355
  ws.onopen = function () {
356
+ chatTyping.disabled = false;
357
+ chatTyping.nextElementSibling.disabled = false;
358
 
359
  // Hide modal
360
+ dialog.style.display = "none";
361
  };
362
 
363
  ws.onerror = function () {
 
370
  }
371
 
372
  function playCurrentMusic(recommendationSong) {
 
373
  delayedMusic = {
374
  song: "",
375
  genre: ""
 
380
  chatBox.appendChild(recommendationDiv);
381
  console.log("recommendation song")
382
  console.log(recommendationSong)
383
+ musicPlayer.src = recommendationSong.song
384
+ musicPlayer.play()
385
  }
386
 
387
+ function sendMessage(voiceChat) {
388
+ let inputField = chatTyping;
389
+ console.log("sending")
390
+ console.log(voiceChat)
391
+ let messageText = voiceChat == undefined || voiceChat == null ? inputField.value.trim() : voiceChat;
392
 
393
  if (messageText === "" || !ws || ws.readyState !== WebSocket.OPEN) return;
394
 
 
404
  inputField.value = "";
405
  chatBox.scrollTop = chatBox.scrollHeight;
406
  }
407
+
408
+ function startRecognition() {
409
+ recognition.lang = 'id-ID';
410
+ recognition.interimResults = false;
411
+ recognition.maxAlternatives = 1;
412
+
413
+ recognition.start();
414
+
415
+ recognition.onstart = function () {
416
+ micBubble.classList.add("recording");
417
+ };
418
+
419
+ recognition.onspeechstart = function () {
420
+ micBubble.classList.add("voice-active");
421
+ };
422
+
423
+
424
+ recognition.onspeechend = function () {
425
+ micBubble.classList.remove("voice-active");
426
+ };
427
+
428
+ recognition.onend = function () {
429
+ micBubble.classList.remove("recording", "voice-active");
430
+ console.log("cafe")
431
+ console.log(isCafe)
432
+ if (isCafe == true) {
433
+ console.log("start ga sih")
434
+ startRecognition();
435
+ } else {
436
+ recognition.stop();
437
+ }
438
+ };
439
+
440
+ recognition.onresult = function (event) {
441
+ const transcript = event.results[0][0].transcript;
442
+ console.log("transcript")
443
+ console.log(transcript)
444
+ sendMessage(transcript)
445
+ };
446
+
447
+ recognition.onerror = function (event) {
448
+ micBubble.classList.remove("recording", "voice-active");
449
+ alert('Terjadi kesalahan: ' + event.error);
450
+ };
451
+ }
452
  </script>
453
  </body>
454