Chatbot-2 / index.html
Shresthh03's picture
Update it.
cb00eb0 verified
raw
history blame
13 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Serenity — Emotional Companion</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
body { transition: background 1.2s ease; background: linear-gradient(135deg,#f2f6ff,#fff0f6); font-family: Inter, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue"; }
.card { max-width:960px; margin:28px auto; display:flex; gap:20px; align-items:flex-start; padding:20px; border-radius:18px; background: rgba(255,255,255,0.55); box-shadow: 0 10px 30px rgba(12,12,20,0.08); backdrop-filter: blur(8px); }
.left { width:220px; display:flex; flex-direction:column; align-items:center; gap:12px; }
.avatarOrb, .avatarHum { width:150px; height:150px; border-radius:50%; display:flex; align-items:center; justify-content:center; }
.avatarOrb { background: radial-gradient(circle at 30% 30%, rgba(255,255,255,0.5), rgba(255,255,255,0.05)), radial-gradient(circle at 60% 60%, rgba(120,170,255,0.5), rgba(80,100,255,0.2)); box-shadow: 0 6px 30px rgba(80,110,255,0.18); animation: breatheOrb 4.5s ease-in-out infinite; }
.avatarHum { background: radial-gradient(circle at 30% 25%, rgba(255,255,255,0.7), rgba(255,255,255,0.05)), linear-gradient(135deg,#fef3c7,#fbcfe8); box-shadow: 0 6px 30px rgba(200,120,200,0.12); animation: breatheHum 5s ease-in-out infinite; display:none; }
@keyframes breatheOrb { 0%{transform:scale(1);opacity:0.9;}50%{transform:scale(1.08);opacity:1;}100%{transform:scale(1);opacity:0.92;} }
@keyframes breatheHum { 0%{transform:scale(1);opacity:0.9;}50%{transform:scale(1.06);opacity:1;}100%{transform:scale(1);opacity:0.92;} }
.status { font-size:14px; color:#334155; text-align:center; }
.controls { display:flex; gap:8px; margin-top:6px; flex-wrap:wrap; }
.controls button, .controls select { padding:8px 12px; border-radius:10px; border:none; cursor:pointer; font-weight:600; background:rgba(255,255,255,0.9); }
.main { flex:1; display:flex; flex-direction:column; gap:12px; }
.chatWindow { height:520px; background:rgba(255,255,255,0.9); border-radius:12px; padding:18px; overflow:auto; box-shadow: inset 0 1px 0 rgba(255,255,255,0.6); }
.bubble { max-width:72%; padding:12px 14px; border-radius:14px; margin:8px 0; line-height:1.35; }
.bubble.user { margin-left:auto; background:linear-gradient(135deg,#e6f0ff,#d7e9ff); color:#07235a; border-bottom-right-radius:4px; text-align:right; }
.bubble.bot { margin-right:auto; background:linear-gradient(135deg,#fdf7ff,#fff1f7); color:#2b2b2b; border-bottom-left-radius:4px; text-align:left; }
.typeIndicator { font-style:italic; color:#64748b; padding:6px 0; text-align:left; }
.inputRow { display:flex; gap:8px; margin-top:10px; }
.inputRow input { flex:1; padding:12px 14px; border-radius:12px; border:1px solid rgba(10,10,10,0.06); outline:none; font-size:16px; }
.inputRow button { padding:10px 14px; border-radius:10px; border:none; cursor:pointer; font-weight:700; }
.footerBtns { margin-top:12px; display:flex; gap:8px; justify-content:flex-end; align-items:center; }
.small { font-size:12px; color:#475569; }
@media (max-width:820px) { .card{flex-direction:column; padding:14px;} .left{width:100%; flex-direction:row; justify-content:space-between;} .avatarOrb,.avatarHum{width:80px;height:80px;} .chatWindow{height:340px;} }
</style>
</head>
<body>
<div class="card">
<div class="left">
<div id="orb" class="avatarOrb" aria-hidden="true"></div>
<div id="hum" class="avatarHum" aria-hidden="true"></div>
<div class="status">
<div id="assistantName" class="text-lg font-semibold">Serenity</div>
<div id="assistantMood" class="small">Calm • Supportive</div>
</div>
<div class="controls">
<button id="switchStyle" class="">Switch Style</button>
<select id="voiceSelect" class="rounded-lg" title="Choose voice/personality"></select>
</div>
<div class="small">Tip: Allow microphone access to speak</div>
</div>
<div class="main">
<div id="chatWindow" class="chatWindow" role="log" aria-live="polite"></div>
<div id="typing" class="typeIndicator" style="display:none">Serenity is typing…</div>
<div class="inputRow">
<input id="messageInput" placeholder="Type your message..." autocomplete="off" />
<button id="sendBtn" class="bg-indigo-600 text-white">Send</button>
<button id="micStart" class="bg-green-500 text-white">🎙️</button>
<button id="micStop" class="bg-red-500 text-white">⏹️</button>
</div>
<div class="footerBtns">
<button id="newChat" class="bg-gray-100 px-3">New Chat</button>
<button id="feedback" class="bg-amber-100 px-3">Feedback</button>
</div>
</div>
</div>
<script>
/* ---------- Session + DOM refs ---------- */
const sessionKey = "serenity_session";
let sessionId = localStorage.getItem(sessionKey);
if(!sessionId){
sessionId = 's_' + Math.random().toString(36).slice(2,12);
localStorage.setItem(sessionKey, sessionId);
}
const chatWindow = document.getElementById("chatWindow");
const messageInput = document.getElementById("messageInput");
const sendBtn = document.getElementById("sendBtn");
const typingIndicator = document.getElementById("typing");
const orb = document.getElementById("orb");
const hum = document.getElementById("hum");
const switchStyle = document.getElementById("switchStyle");
const assistantMood = document.getElementById("assistantMood");
const voiceSelect = document.getElementById("voiceSelect");
const micStart = document.getElementById("micStart");
const micStop = document.getElementById("micStop");
const newChatBtn = document.getElementById("newChat");
const feedbackBtn = document.getElementById("feedback");
let recognition = null;
let listening = false;
let voices = [];
const VOICES = [
{id:'calm_male', label:'Calm Male', pitch:0.85, rate:0.95},
{id:'deep_male', label:'Deep Male', pitch:0.6, rate:0.9},
{id:'soothing_male', label:'Soothing Male', pitch:0.9, rate:0.85},
{id:'gentle_female', label:'Gentle Loving Female', pitch:1.3, rate:0.95},
{id:'feminine_female', label:'Feminine Female', pitch:1.45, rate:1.0},
{id:'deep_female', label:'Deep & Soar Female', pitch:0.9, rate:0.9},
{id:'soothing_female', label:'Soothing Female', pitch:1.2, rate:0.85},
{id:'neutral', label:'Neutral Soothing', pitch:1.0, rate:1.0}
];
/* ---------- small UI helpers ---------- */
function appendBubble(who, text){
const d = document.createElement("div");
d.className = 'bubble ' + (who === "user" ? 'user' : 'bot');
d.innerHTML = `<strong>${who === "user" ? "You" : "Serenity"}:</strong> ${escapeHtml(text)}`;
chatWindow.appendChild(d);
chatWindow.scrollTop = chatWindow.scrollHeight;
}
function escapeHtml(str){ return (str||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;'); }
/* ---------- voice list population ---------- */
function populateVoiceList(){
const avail = speechSynthesis.getVoices() || [];
voiceSelect.innerHTML = "";
VOICES.forEach(v=>{
const opt = document.createElement("option");
opt.value = v.id;
opt.text = v.label;
voiceSelect.appendChild(opt);
});
const saved = localStorage.getItem("serenity_voice") || "neutral";
voiceSelect.value = saved;
}
populateVoiceList();
if(typeof speechSynthesis !== "undefined"){
speechSynthesis.onvoiceschanged = populateVoiceList;
}
voiceSelect.addEventListener("change", ()=> localStorage.setItem("serenity_voice", voiceSelect.value));
/* ---------- typing / avatar ---------- */
function showTyping(){ typingIndicator.style.display = "block"; orb.style.transform = "scale(1.08)"; }
function hideTyping(){ typingIndicator.style.display = "none"; orb.style.transform = ""; }
function speakText(text){
if(!window.speechSynthesis) return;
const utter = new SpeechSynthesisUtterance(text);
const profileId = voiceSelect.value || "neutral";
const profile = VOICES.find(v => v.id === profileId) || VOICES[7];
utter.pitch = profile.pitch;
utter.rate = profile.rate;
// choose a matching voice if possible
const avail = speechSynthesis.getVoices() || [];
let match = null;
if(profileId.includes("male")) {
match = avail.find(v=>/male|man|david|john|alex|daniel|microsoft/i.test(v.name));
} else if(profileId.includes("female")) {
match = avail.find(v=>/female|zira|susan|sarah|microsoft|victoria/i.test(v.name));
}
if(!match) match = avail.find(v=>/en-|en_/.test(v.lang)) || avail[0];
if(match) utter.voice = match;
speechSynthesis.cancel();
speechSynthesis.speak(utter);
}
/* ---------- background gentle cycling ---------- */
const SOFT_GRADIENTS = [
"linear-gradient(135deg,#EFF6FF,#FFF0F6)",
"linear-gradient(135deg,#F0FDF4,#ECFEFF)",
"linear-gradient(135deg,#FEF3C7,#FCE7F3)",
"linear-gradient(135deg,#F3E8FF,#FEF3F3)",
"linear-gradient(135deg,#FDF2F8,#EEF2FF)"
];
let bgIndex = 0;
setInterval(()=>{ bgIndex=(bgIndex+1)%SOFT_GRADIENTS.length; document.body.style.background = SOFT_GRADIENTS[bgIndex]; }, 18000);
/* ---------- mic controls ---------- */
if ('webkitSpeechRecognition' in window || 'SpeechRecognition' in window){
const SR = window.SpeechRecognition || window.webkitSpeechRecognition;
recognition = new SR();
recognition.lang = "en-US";
recognition.interimResults = false;
recognition.onresult = (e)=> {
const txt = e.results[e.results.length-1][0].transcript;
messageInput.value = txt;
sendMessage();
};
recognition.onend = ()=> { listening=false; micStart.disabled=false; micStop.disabled=true; orb.style.transform=""; }
recognition.onerror = ()=> { listening=false; micStart.disabled=false; micStop.disabled=true; orb.style.transform=""; }
micStart.onclick = ()=> { try{ recognition.start(); listening=true; micStart.disabled=true; micStop.disabled=false; orb.style.transform="scale(1.08)"; }catch(e){} };
micStop.onclick = ()=> { if(recognition) recognition.stop(); listening=false; micStart.disabled=false; micStop.disabled=true; speechSynthesis.cancel(); orb.style.transform=""; };
} else { micStart.disabled=true; micStop.disabled=true; }
/* ---------- avatar switch ---------- */
switchStyle.onclick = ()=> {
if(orb.style.display === "none" || getComputedStyle(orb).display === "none") { orb.style.display=""; hum.style.display="none"; }
else { orb.style.display="none"; hum.style.display=""; }
}
/* ---------- reset session ---------- */
newChatBtn.onclick = async ()=> {
chatWindow.innerHTML = "";
try{
await fetch("/reset_session", {method:"POST", headers:{"Content-Type":"application/json"}, body: JSON.stringify({session:sessionId})});
}catch(e){}
appendBubble("bot","New conversation started. How are you feeling today?");
}
/* ---------- send message ---------- */
async function sendMessage(){
const txt = messageInput.value.trim();
if(!txt) return;
appendBubble("user", txt);
messageInput.value = "";
showTyping();
try{
const body = { session: sessionId, message: txt, personality: voiceSelect.value || "neutral" };
const res = await fetch("/chat", {method:"POST", headers:{"Content-Type":"application/json"}, body: JSON.stringify(body)});
const j = await res.json();
hideTyping();
appendBubble("bot", j.reply || j.response || "Sorry — I couldn't form a reply.");
assistantMood.textContent = ((j.emotion||"Calm").charAt(0).toUpperCase() + (j.emotion||"Calm").slice(1)) + " • Supportive";
// gentle background changes by emotion
const emotion = (j.emotion||"neutral").toLowerCase();
if(emotion==="sadness") document.body.style.background="linear-gradient(135deg,#dbeafe,#eef2ff)";
else if(emotion==="joy"||emotion==="love") document.body.style.background="linear-gradient(135deg,#fff7ed,#fff1f2)";
else if(emotion==="anger") document.body.style.background="linear-gradient(135deg,#fff1f0,#ffe7e8)";
else if(emotion==="crisis") document.body.style.background="linear-gradient(135deg,#f8fafc,#fef2f2)";
else document.body.style.background = SOFT_GRADIENTS[bgIndex];
// speak
speakText(j.reply || j.response || "");
}catch(err){
hideTyping();
appendBubble("bot","Oops — couldn't connect. Try again in a moment.");
console.error(err);
}
}
sendBtn.addEventListener("click", sendMessage);
messageInput.addEventListener("keydown", (e)=> { if(e.key==="Enter") sendMessage(); });
/* ---------- init greeting (ask name if needed) ---------- */
(async function init(){
try{
const res = await fetch("/chat", {method:"POST", headers:{"Content-Type":"application/json"}, body: JSON.stringify({session:sessionId, init:true})});
const j = await res.json();
appendBubble("bot", j.reply || "Hey there — I'm Serenity. What's your name?");
assistantMood.textContent = (j.emotion || "Calm") + " • Supportive";
}catch(e){
appendBubble("bot","Hey there — I'm Serenity. What's your name?");
}
})();
</script>
</body>
</html>