Spaces:
Runtime error
Runtime error
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8" /> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |
| <title>π Emotional Support Assistant</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <style> | |
| body { | |
| transition: background 2s ease, color 1s ease; | |
| background: linear-gradient(135deg, #fef3c7, #fbbf24); | |
| } | |
| .avatar { | |
| animation: breathe 3s ease-in-out infinite; | |
| } | |
| @keyframes breathe { | |
| 0%, 100% { transform: scale(1); } | |
| 50% { transform: scale(1.05); } | |
| } | |
| .message { | |
| animation: fadeIn 0.5s ease-in; | |
| } | |
| @keyframes fadeIn { | |
| from { opacity: 0; transform: translateY(10px); } | |
| to { opacity: 1; transform: translateY(0); } | |
| } | |
| .loading { | |
| display: inline-block; | |
| width: 20px; | |
| height: 20px; | |
| border: 3px solid #fbbf24; | |
| border-radius: 50%; | |
| border-top-color: transparent; | |
| animation: spin 1s ease-in-out infinite; | |
| } | |
| @keyframes spin { | |
| to { transform: rotate(360deg); } | |
| } | |
| @media (max-width: 768px) { | |
| .avatar svg { width: 80px; height: 80px; } | |
| h1 { font-size: 2rem; } | |
| .flex { flex-direction: column; gap: 0.5rem; } | |
| } | |
| </style> | |
| </head> | |
| <body class="text-gray-800 flex flex-col items-center justify-center min-h-screen p-4"> | |
| <div class="avatar mb-4"> | |
| <svg width="100" height="100" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"> | |
| <circle cx="50" cy="50" r="45" fill="#fbbf24" stroke="#f59e0b" stroke-width="5"/> | |
| <circle cx="35" cy="40" r="5" fill="#374151"/> | |
| <circle cx="65" cy="40" r="5" fill="#374151"/> | |
| <path d="M 40 60 Q 50 70 60 60" stroke="#374151" stroke-width="3" fill="none"/> | |
| </svg> | |
| </div> | |
| <h1 class="text-4xl font-bold mb-6 text-center text-gray-900">π Your Empathetic Friend</h1> | |
| <p class="text-center mb-4 text-gray-700">I'm here to listen, support, and share knowledge. Let's connect!</p> | |
| <div class="flex gap-4 mb-4 flex-wrap justify-center"> | |
| <button id="modeBtn" onclick="toggleMode()" class="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-lg shadow-lg transition" aria-label="Toggle modes">Mode: Emotional Support</button> | |
| <button onclick="clearChat()" class="bg-red-500 hover:bg-red-600 text-white px-4 py-2 rounded-lg shadow-lg transition" aria-label="Clear chat">Clear Chat</button> | |
| <select id="voiceSelect" class="bg-white border rounded-lg px-3 py-2 shadow-lg" aria-label="Select voice"> | |
| <option value="calming male">Calming Male</option> | |
| <option value="calming female">Calming Female</option> | |
| <option value="soothing motivating male">Soothing Motivating Male</option> | |
| <option value="soothing motivating female">Soothing Motivating Female</option> | |
| <option value="deep and soar female">Deep and Soar Female</option> | |
| <option value="deep and soar male">Deep and Soar Male</option> | |
| <option value="neutral tone" selected>Neutral Tone</option> | |
| <option value="supportive male">Supportive Male</option> | |
| <option value="supportive female">Supportive Female</option> | |
| </select> | |
| </div> | |
| <div id="chat-box" class="w-full max-w-lg h-96 bg-white rounded-lg shadow-xl overflow-y-auto p-4 mb-4" role="log" aria-live="polite" aria-label="Chat"></div> | |
| <div class="flex gap-2 w-full max-w-lg flex-wrap"> | |
| <input id="message" type="text" class="flex-grow p-3 border-2 border-gray-300 rounded-lg focus:outline-none focus:border-blue-500" placeholder="Share what's on your mind..." aria-label="Type message" /> | |
| <button onclick="sendMessage()" class="bg-green-500 hover:bg-green-600 text-white px-4 py-2 rounded-lg shadow-lg transition" aria-label="Send">Send</button> | |
| </div> | |
| <div class="flex gap-2 mt-4 flex-wrap justify-center"> | |
| <button id="listenBtn" onclick="startListening()" class="bg-purple-500 hover:bg-purple-600 text-white px-4 py-2 rounded-lg shadow-lg transition" aria-label="Voice input" disabled>ποΈ Listen</button> | |
| <button id="stopBtn" onclick="stopListening()" class="bg-red-500 hover:bg-red-600 text-white px-4 py-2 rounded-lg shadow-lg transition" aria-label="Stop voice" disabled>βΉοΈ Stop</button> | |
| </div> | |
| <script> | |
| const chatBox = document.getElementById("chat-box"); | |
| const messageInput = document.getElementById("message"); | |
| const modeBtn = document.getElementById("modeBtn"); | |
| const listenBtn = document.getElementById("listenBtn"); | |
| const stopBtn = document.getElementById("stopBtn"); | |
| const voiceSelect = document.getElementById("voiceSelect"); | |
| let recognition; | |
| let listening = false; | |
| let currentMode = "emotional_support"; | |
| const emotionColors = { | |
| joy: ["bg-gradient-to-r from-yellow-200 to-yellow-300"], | |
| sadness: ["bg-gradient-to-r from-blue-200 to-blue-300"], | |
| anger: ["bg-gradient-to-r from-red-200 to-red-300"], | |
| calm: ["bg-gradient-to-r from-green-200 to-green-300"], | |
| optimism: ["bg-gradient-to-r from-orange-200 to-orange-300"], | |
| surprise: ["bg-gradient-to-r from-purple-200 to-purple-300"], | |
| neutral: ["bg-gradient-to-r from-gray-200 to-gray-300"] | |
| }; | |
| function loadHistory() { | |
| const history = JSON.parse(localStorage.getItem("chatHistory") || "[]"); | |
| history.forEach(msg => appendMessage(msg.sender, msg.text, false)); | |
| } | |
| function saveHistory() { | |
| const messages = Array.from(chatBox.children).map(div => ({ | |
| sender: div.querySelector("strong").textContent.replace(":", ""), | |
| text: div.textContent.split(": ")[1] | |
| })); | |
| localStorage.setItem("chatHistory", JSON.stringify(messages)); | |
| } | |
| async function sendMessage(voiceConfirmed = false) { | |
| const message = messageInput.value.trim(); | |
| if (!message) return; | |
| console.log("Sending message:", message); | |
| appendMessage("You", message); | |
| messageInput.value = ""; | |
| showLoading(true); | |
| try { | |
| const res = await fetch("/chat", { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify({ message, mode: currentMode, voice_tone: voiceSelect.value }), | |
| }); | |
| if (!res.ok) throw new Error(`HTTP ${res.status}: ${res.statusText}`); | |
| const data = await res.json(); | |
| console.log("Received response:", data); | |
| appendMessage("Assistant", data.reply); | |
| updateBackground(data.emotion); | |
| speak(data.reply); | |
| saveHistory(); | |
| } catch (error) { | |
| console.error("Fetch error:", error); | |
| appendMessage("Assistant", "Something went wrong. Check your API key or try again."); | |
| } finally { | |
| showLoading(false); | |
| } | |
| } | |
| function appendMessage(sender, text, save = true) { | |
| const div = document.createElement("div"); | |
| div.className = `message ${sender === "You" ? "text-right mb-2" : "text-left mb-2"}`; | |
| div.innerHTML = `<strong>${sender}:</strong> ${text}`; | |
| chatBox.appendChild(div); | |
| chatBox.scrollTop = chatBox.scrollHeight; | |
| if (save) saveHistory(); | |
| } | |
| function updateBackground(emotion) { | |
| const colors = emotionColors[emotion.toLowerCase()] || emotionColors["neutral"]; | |
| document.body.className = `transition-all min-h-screen flex flex-col items-center justify-center p-4 ${colors[0]}`; | |
| } | |
| function speak(text) { | |
| if ("speechSynthesis" in window) { | |
| const utterance = new SpeechSynthesisUtterance(text); | |
| const voices = speechSynthesis.getVoices(); | |
| let selectedVoice = voices.find(v => v.name.toLowerCase().includes(voiceSelect.value.split(' ')[0])); | |
| if (!selectedVoice) selectedVoice = voices[0]; | |
| utterance.voice = selectedVoice; | |
| utterance.rate = 0.8; | |
| utterance.pitch = 1; | |
| speechSynthesis.speak(utterance); | |
| } | |
| } | |
| function startListening() { | |
| if (!("webkitSpeechRecognition" in window)) { | |
| alert("Voice not supported."); | |
| return; | |
| } | |
| recognition = new webkitSpeechRecognition(); | |
| recognition.continuous = false; | |
| recognition.interimResults = false; | |
| recognition.lang = "en-US"; | |
| recognition.onstart = () => { | |
| listening = true; | |
| listenBtn.disabled = true; | |
| stopBtn.disabled = false; | |
| appendMessage("System", "Listening..."); | |
| }; | |
| recognition.onresult = (event) => { | |
| const transcript = event.results[0][0].transcript; | |
| messageInput.value = transcript; | |
| sendMessage(true); | |
| }; | |
| recognition.onend = () => { | |
| listening = false; | |
| listenBtn.disabled = false; | |
| stopBtn.disabled = true; | |
| }; | |
| recognition.start(); | |
| } | |
| function stopListening() { | |
| if (recognition && listening) { | |
| recognition.stop(); | |
| listening = false; | |
| listenBtn.disabled = false; | |
| stopBtn.disabled = true; | |
| appendMessage("System", "Stopped."); | |
| } | |
| } | |
| function toggleMode() { | |
| currentMode = currentMode === "emotional_support" ? "knowledge" : "emotional_support"; | |
| modeBtn.textContent = `Mode: ${currentMode === "emotional_support" ? "Emotional Support" : "Knowledge"}`; | |
| console.log("Mode switched to:", currentMode); | |
| } | |
| function clearChat() { | |
| chatBox.innerHTML = ""; | |
| localStorage.removeItem("chatHistory"); | |
| } | |
| function showLoading(show) { | |
| const btn = document.querySelector("button[onclick='sendMessage()']"); | |
| if (show) { | |
| btn.innerHTML = '<div class="loading"></div>'; | |
| btn.disabled = true; | |
| } else { | |
| btn.innerHTML = "Send"; | |
| btn.disabled = false; | |
| } | |
| } | |
| messageInput.addEventListener('keypress', function(e) { | |
| if (e.key === 'Enter') sendMessage(); | |
| }); | |
| loadHistory(); | |
| appendMessage("Assistant", "Hi! How are you?"); | |
| </script> | |
| </body> | |
| </html> | |