Spaces:
Sleeping
Sleeping
| <html lang="en"> | |
| <head> | |
| <title>🎶 Chattifin</title> | |
| <style> | |
| body { | |
| font-family: Arial, sans-serif; | |
| background: #121212; | |
| color: white; | |
| text-align: center; | |
| } | |
| #chat-container { | |
| width: 50%; | |
| margin: auto; | |
| padding: 10px; | |
| } | |
| .message { | |
| padding: 10px; | |
| margin: 5px 0; | |
| border-radius: 10px; | |
| } | |
| .user { | |
| background: #007bff; | |
| color: white; | |
| text-align: right; | |
| } | |
| .bot { | |
| background: #28a745; | |
| color: white; | |
| } | |
| input, | |
| button { | |
| margin-top: 10px; | |
| padding: 10px; | |
| border: none; | |
| border-radius: 5px; | |
| } | |
| input { | |
| width: 40%; | |
| } | |
| button { | |
| background: #ff9800; | |
| color: white; | |
| cursor: pointer; | |
| } | |
| audio { | |
| margin-top: 10px; | |
| width: 100%; | |
| } | |
| #chat-box { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 8px; | |
| } | |
| .message { | |
| padding: 10px 15px; | |
| border-radius: 10px; | |
| max-width: 70%; | |
| word-wrap: break-word; | |
| } | |
| .user-message { | |
| align-self: flex-end; | |
| background-color: #007bff; | |
| color: white; | |
| border-bottom-right-radius: 0; | |
| } | |
| .other-user-message { | |
| align-self: flex-start; | |
| background-color: #e0e0e0; | |
| color: black; | |
| border-bottom-left-radius: 0; | |
| } | |
| .recommendation-message { | |
| align-self: center; | |
| background-color: #28a745; | |
| color: white; | |
| font-weight: bold; | |
| border-radius: 12px; | |
| padding: 15px; | |
| text-align: center; | |
| box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.2); | |
| } | |
| /* MODAL STYLES */ | |
| .modal { | |
| display: block; | |
| /* Show on load */ | |
| position: fixed; | |
| z-index: 1; | |
| left: 0; | |
| top: 0; | |
| width: 100%; | |
| height: 100%; | |
| background-color: rgba(0, 0, 0, 0.6); | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| } | |
| .modal-content { | |
| background: white; | |
| padding: 20px; | |
| border-radius: 8px; | |
| text-align: center; | |
| color: black; | |
| width: 300px; | |
| } | |
| .modal-content input { | |
| width: 80%; | |
| padding: 8px; | |
| margin-top: 10px; | |
| } | |
| .modal-content button { | |
| background: #007bff; | |
| color: white; | |
| padding: 8px 15px; | |
| margin-top: 10px; | |
| cursor: pointer; | |
| border-radius: 5px; | |
| border: none; | |
| } | |
| #voice-btn { | |
| background: #d8e7f8; | |
| color: black; | |
| padding: 8px 15px; | |
| margin-top: 10px; | |
| cursor: pointer; | |
| border-radius: 5px; | |
| border: none; | |
| } | |
| #mic-bubble { | |
| width: 60px; | |
| height: 60px; | |
| border-radius: 50%; | |
| background-color: #555; | |
| margin-left: 10px; | |
| cursor: pointer; | |
| transition: background-color 0.3s ease, transform 0.3s ease; | |
| } | |
| /* Saat recording aktif */ | |
| #mic-bubble.recording { | |
| background-color: red; | |
| animation: pulse-recording 1s infinite; | |
| } | |
| /* Saat suara terdeteksi (speechstart) */ | |
| #mic-bubble.voice-active { | |
| background-color: #00e676; | |
| box-shadow: 0 0 12px 4px rgba(0, 230, 118, 0.6); | |
| animation: pulse-voice 0.5s ease-out; | |
| } | |
| #cafe-mode-btn { | |
| background: linear-gradient(to right, #b47b48, #d5a679); | |
| border: none; | |
| color: white; | |
| padding: 10px 20px; | |
| margin-bottom: 20px; | |
| border-radius: 25px; | |
| font-size: 16px; | |
| font-family: 'Courier New', Courier, monospace; | |
| cursor: pointer; | |
| box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2); | |
| transition: all 0.3s ease; | |
| } | |
| #cafe-mode-btn:hover { | |
| transform: scale(1.05); | |
| box-shadow: 0 6px 14px rgba(0, 0, 0, 0.25); | |
| } | |
| #voice-btn.recording { | |
| animation: pulse 1s infinite; | |
| background-color: red ; | |
| } | |
| @keyframes pulse-recording { | |
| 0% { | |
| box-shadow: 0 0 0 0 rgba(255, 0, 0, 0.4); | |
| } | |
| 70% { | |
| box-shadow: 0 0 0 15px rgba(255, 0, 0, 0); | |
| } | |
| 100% { | |
| box-shadow: 0 0 0 0 rgba(255, 0, 0, 0); | |
| } | |
| } | |
| @keyframes pulse-voice { | |
| 0% { | |
| transform: scale(1); | |
| } | |
| 50% { | |
| transform: scale(1.2); | |
| } | |
| 100% { | |
| transform: scale(1); | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <h1>🎵 Chattifin</h1> | |
| <!-- Chat Box --> | |
| <div id="chat-container"> | |
| <div id="user-list" style="margin-bottom: 10px; text-align: left;"></div> | |
| <div id="chat-box"></div> | |
| <br> | |
| <input type="text" id="message" placeholder="Ketik pesanmu..." disabled> | |
| <div id="mic-bubble" title="~"></div> | |
| <button id="message-send" onclick="sendMessage(null)" disabled>Kirim</button> | |
| <button id="voice-btn" onclick="toggleVoice()" disabled>🎙</button> | |
| <audio hidden="true" id="music-player" controls></audio> | |
| </div> | |
| <!-- MODAL FOR USERNAME INPUT --> | |
| <div id="username-modal" class="modal"> | |
| <div class="modal-content"> | |
| <h2>Masukkan Namamu...</h2> | |
| <input type="text" id="username" placeholder="Your Name"> | |
| <br> | |
| <button onclick="connectChat(false)">Join Chat</button> | |
| <p>Atau</p> | |
| <button id="cafe-mode-btn" onclick="connectChat(true)">Cafe Mode</button> | |
| </div> | |
| </div> | |
| <script> | |
| const chatBox = document.getElementById("chat-box"); | |
| const chatTyping = document.getElementById("message"); | |
| const chatSending = document.getElementById("message-send"); | |
| const chatVoice = document.getElementById("voice-btn") | |
| const userListDiv = document.getElementById("user-list"); | |
| const dialogUserName = document.getElementById("username"); | |
| const dialog = document.getElementById("username-modal"); | |
| const micBubble = document.getElementById("mic-bubble"); | |
| const musicPlayer = document.getElementById("music-player"); | |
| let ws; | |
| let username = ""; | |
| let musicStatus = ""; | |
| let delayedMusic = { | |
| song: "", | |
| genre: "" | |
| }; | |
| let isCafe = false; | |
| const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; | |
| if (!SpeechRecognition) { | |
| alert("Browser tidak mendukung Speech Recognition. Gunakan Google Chrome."); | |
| } | |
| const recognition = new SpeechRecognition(); | |
| function connectChat(pIsCafe) { | |
| isCafe = pIsCafe; | |
| username = dialogUserName.value.trim(); | |
| if (!username && isCafe == false) { | |
| alert("Please enter a username!"); | |
| return; | |
| } | |
| console.log("location host") | |
| console.log(location.host) | |
| // ws = new WebSocket(`${location.protocol === 'https:' ? 'wss' : 'ws'}://${location.host}/chat/${username}`); | |
| if (isCafe == true) { | |
| userListDiv.innerHTML = "<strong>👥 Mulailah ngobrol dan kami sesuaikan alunan irama yang sesuai untuk anda 🎵</strong>" | |
| chatTyping.style.display = "none" | |
| chatSending.style.display = "none" | |
| chatVoice.style.display = "none" | |
| startRecognition() | |
| username = "cafe" | |
| } else { | |
| micBubble.style.display = "none" | |
| chatSending.disabled = false | |
| chatVoice.disabled = false | |
| } | |
| // ws = new WebSocket(`ws://localhost:8000/chat/${username}`); | |
| ws = new WebSocket(`${location.protocol === 'https:' ? 'wss' : 'ws'}://${location.host}/chat/${username}`); | |
| musicPlayer.addEventListener("play", () => { | |
| musicStatus = "playing" | |
| }); | |
| musicPlayer.addEventListener("ended", () => { | |
| musicStatus = "stop" | |
| if (delayedMusic.song != "") playCurrentMusic(delayedMusic) | |
| }); | |
| ws.onmessage = function (event) { | |
| const data = JSON.parse(event.data); | |
| if (data.type === "user_list") { | |
| userListDiv.innerHTML = `<strong>👥 Online:</strong> ${data.users.join(", ")}`; | |
| return; | |
| } | |
| if (data.username === username) { | |
| // Udah di-render pas kirim, skip | |
| return; | |
| } | |
| if (data.recommendations) { | |
| if (musicStatus != "playing") { | |
| console.log("data") | |
| console.log(data) | |
| playCurrentMusic({ | |
| song: data.recommendations[0], | |
| genre: data.genre | |
| }) | |
| } else { | |
| delayedMusic = { | |
| song: data.recommendations[0], | |
| genre: data.genre | |
| } | |
| } | |
| } else { | |
| let messageDiv = document.createElement("div"); | |
| messageDiv.classList.add("message", "other-user-message"); | |
| messageDiv.innerHTML = `<strong>${data.username}</strong>: ${data.message}`; | |
| chatBox.appendChild(messageDiv); | |
| } | |
| chatBox.scrollTop = chatBox.scrollHeight; | |
| }; | |
| ws.onopen = function () { | |
| chatTyping.disabled = false; | |
| chatTyping.nextElementSibling.disabled = false; | |
| // Hide modal | |
| dialog.style.display = "none"; | |
| }; | |
| ws.onerror = function () { | |
| alert("WebSocket connection error! Ensure server is running."); | |
| }; | |
| ws.onclose = function () { | |
| alert("Connection closed!"); | |
| }; | |
| } | |
| function playCurrentMusic(recommendationSong) { | |
| delayedMusic = { | |
| song: "", | |
| genre: "" | |
| }; | |
| let recommendationDiv = document.createElement("div"); | |
| recommendationDiv.classList.add("recommendation-message"); | |
| recommendationDiv.innerHTML = `🎵 ${recommendationSong.genre}`; | |
| chatBox.appendChild(recommendationDiv); | |
| console.log("recommendation song") | |
| console.log(recommendationSong) | |
| musicPlayer.src = recommendationSong.song | |
| musicPlayer.play() | |
| } | |
| function sendMessage(voiceChat) { | |
| let inputField = chatTyping; | |
| console.log("sending") | |
| console.log(voiceChat) | |
| let messageText = voiceChat == undefined || voiceChat == null ? inputField.value.trim() : voiceChat; | |
| if (messageText === "" || !ws || ws.readyState !== WebSocket.OPEN) return; | |
| // Send message to WebSocket server | |
| ws.send(JSON.stringify({ username, message: messageText })); | |
| // Show user message in chat | |
| let messageDiv = document.createElement("div"); | |
| messageDiv.classList.add("message", "user-message"); | |
| messageDiv.innerHTML = `<strong>${username}</strong>: ${messageText}`; | |
| chatBox.appendChild(messageDiv); | |
| inputField.value = ""; | |
| chatBox.scrollTop = chatBox.scrollHeight; | |
| } | |
| function startRecognition() { | |
| recognition.lang = 'id-ID'; | |
| recognition.interimResults = false; | |
| recognition.maxAlternatives = 1; | |
| recognition.start(); | |
| recognition.onstart = function () { | |
| micBubble.classList.add("recording"); | |
| }; | |
| recognition.onspeechstart = function () { | |
| micBubble.classList.add("voice-active"); | |
| }; | |
| recognition.onspeechend = function () { | |
| micBubble.classList.remove("voice-active"); | |
| }; | |
| recognition.onend = function () { | |
| micBubble.classList.remove("recording", "voice-active"); | |
| console.log("cafe") | |
| console.log(isCafe) | |
| if (isCafe == true) { | |
| console.log("start ga sih") | |
| startRecognition(); | |
| } else { | |
| recognition.stop(); | |
| } | |
| }; | |
| recognition.onresult = function (event) { | |
| const transcript = event.results[0][0].transcript; | |
| console.log("transcript") | |
| console.log(transcript) | |
| sendMessage(transcript) | |
| }; | |
| recognition.onerror = function (event) { | |
| micBubble.classList.remove("recording", "voice-active"); | |
| // alert('Terjadi kesalahan: ' + event.error); | |
| }; | |
| } | |
| </script> | |
| </body> | |
| </html> |