|
|
|
<!DOCTYPE html> |
|
<html lang="en" dir="ltr"> |
|
<head> |
|
<meta charset="utf-8"> |
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
|
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Rounded:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200" /> |
|
<style> |
|
|
|
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600&display=swap'); |
|
* { |
|
margin: 0; |
|
padding: 0; |
|
box-sizing: border-box; |
|
font-family: "Poppins", sans-serif; |
|
} |
|
:root { |
|
--text-color: #FFFFFF; |
|
--icon-color: #ACACBE; |
|
--icon-hover-bg: #5b5e71; |
|
--placeholder-color: #dcdcdc; |
|
--outgoing-chat-bg: #343541; |
|
--incoming-chat-bg: #444654; |
|
--outgoing-chat-border: #343541; |
|
--incoming-chat-border: #444654; |
|
} |
|
.light-mode { |
|
--text-color: #343541; |
|
--icon-color: #a9a9bc; |
|
--icon-hover-bg: #f1f1f3; |
|
--placeholder-color: #6c6c6c; |
|
--outgoing-chat-bg: #FFFFFF; |
|
--incoming-chat-bg: #F7F7F8; |
|
--outgoing-chat-border: #FFFFFF; |
|
--incoming-chat-border: #D9D9E3; |
|
} |
|
body { |
|
background: var(--outgoing-chat-bg); |
|
} |
|
.top-bar { |
|
background: var(--outgoing-chat-bg); |
|
border-bottom: 1px solid var(--incoming-chat-border); |
|
padding: 10px; |
|
text-align: center; |
|
color: var(--text-color); |
|
} |
|
|
|
.chat-container { |
|
overflow-y: auto; |
|
max-height: 100vh; |
|
padding-bottom: 150px; |
|
} |
|
:where(.chat-container, textarea)::-webkit-scrollbar { |
|
width: 6px; |
|
} |
|
:where(.chat-container, textarea)::-webkit-scrollbar-track { |
|
background: var(--incoming-chat-bg); |
|
border-radius: 25px; |
|
} |
|
:where(.chat-container, textarea)::-webkit-scrollbar-thumb { |
|
background: var(--icon-color); |
|
border-radius: 25px; |
|
} |
|
.default-text { |
|
display: flex; |
|
align-items: center; |
|
justify-content: center; |
|
flex-direction: column; |
|
height: 70vh; |
|
padding: 0 10px; |
|
text-align: center; |
|
color: var(--text-color); |
|
} |
|
.default-text h1 { |
|
font-size: 3.3rem; |
|
} |
|
.default-text p { |
|
margin-top: 10px; |
|
font-size: 1.1rem; |
|
} |
|
.chat-container .chat { |
|
padding: 25px 10px; |
|
display: flex; |
|
justify-content: center; |
|
color: var(--text-color); |
|
position: relative; |
|
} |
|
.chat-container .chat.outgoing { |
|
background: var(--outgoing-chat-bg); |
|
border: 1px solid var(--outgoing-chat-border); |
|
} |
|
.chat-container .chat.incoming { |
|
background: var(--incoming-chat-bg); |
|
border: 1px solid var(--incoming-chat-border); |
|
} |
|
.chat .chat-content { |
|
display: flex; |
|
max-width: 1200px; |
|
width: 100%; |
|
align-items: flex-start; |
|
justify-content: space-between; |
|
} |
|
span.material-symbols-rounded { |
|
user-select: none; |
|
cursor: pointer; |
|
} |
|
.chat .chat-content span { |
|
cursor: pointer; |
|
font-size: 1.3rem; |
|
color: var(--icon-color); |
|
visibility: hidden; |
|
} |
|
.chat:hover .chat-content:not(:has(.typing-animation), :has(.error)) span { |
|
visibility: visible; |
|
} |
|
.chat .chat-details { |
|
display: flex; |
|
align-items: center; |
|
} |
|
.chat .chat-details img { |
|
width: 35px; |
|
height: 35px; |
|
align-self: flex-start; |
|
object-fit: cover; |
|
border-radius: 2px; |
|
} |
|
.chat .chat-details p { |
|
white-space: pre-wrap; |
|
font-size: 1.05rem; |
|
padding: 0 50px 0 25px; |
|
color: var(--text-color); |
|
word-break: break-word; |
|
} |
|
.chat .chat-details p.error { |
|
color: #e55865; |
|
} |
|
.chat .typing-animation { |
|
padding-left: 25px; |
|
display: inline-flex; |
|
} |
|
.typing-animation .typing-dot { |
|
height: 7px; |
|
width: 7px; |
|
border-radius: 50%; |
|
margin: 0 3px; |
|
opacity: 0.7; |
|
background: var(--text-color); |
|
animation: animateDots 1.5s var(--delay) ease-in-out infinite; |
|
} |
|
.typing-animation .typing-dot:first-child { |
|
margin-left: 0; |
|
} |
|
@keyframes animateDots { |
|
0%, |
|
44% { |
|
transform: translateY(0px); |
|
} |
|
28% { |
|
opacity: 0.4; |
|
transform: translateY(-6px); |
|
} |
|
44% { |
|
opacity: 0.2; |
|
} |
|
} |
|
|
|
.typing-container { |
|
position: fixed; |
|
bottom: 0; |
|
width: 100%; |
|
display: flex; |
|
padding: 20px 10px; |
|
justify-content: center; |
|
background: var(--outgoing-chat-bg); |
|
border-top: 1px solid var(--incoming-chat-border); |
|
} |
|
.typing-container .typing-content { |
|
display: flex; |
|
max-width: 950px; |
|
width: 100%; |
|
align-items: flex-end; |
|
} |
|
.typing-container .typing-textarea { |
|
width: 100%; |
|
display: flex; |
|
position: relative; |
|
} |
|
.typing-textarea textarea { |
|
resize: none; |
|
height: 55px; |
|
width: 100%; |
|
border: none; |
|
padding: 15px 45px 15px 20px; |
|
color: var(--text-color); |
|
font-size: 1rem; |
|
border-radius: 4px; |
|
max-height: 250px; |
|
overflow-y: auto; |
|
background: var(--incoming-chat-bg); |
|
outline: 1px solid var(--incoming-chat-border); |
|
} |
|
.typing-textarea textarea::placeholder { |
|
color: var(--placeholder-color); |
|
} |
|
.typing-content span { |
|
width: 55px; |
|
height: 55px; |
|
display: flex; |
|
border-radius: 4px; |
|
font-size: 1.35rem; |
|
align-items: center; |
|
justify-content: center; |
|
color: var(--icon-color); |
|
} |
|
.typing-textarea span { |
|
position: absolute; |
|
right: 0; |
|
bottom: 0; |
|
visibility: hidden; |
|
} |
|
.typing-textarea textarea:valid ~ span { |
|
visibility: visible; |
|
} |
|
.typing-controls { |
|
display: flex; |
|
} |
|
.typing-controls span { |
|
margin-left: 7px; |
|
font-size: 1.4rem; |
|
background: var(--incoming-chat-bg); |
|
outline: 1px solid var(--incoming-chat-border); |
|
} |
|
.typing-controls span:hover { |
|
background: var(--icon-hover-bg); |
|
} |
|
|
|
@media screen and (max-width: 600px) { |
|
.default-text h1 { |
|
font-size: 2.3rem; |
|
} |
|
:where(.default-text p, textarea, .chat p) { |
|
font-size: 0.95rem!important; |
|
} |
|
.chat-container .chat { |
|
padding: 20px 10px; |
|
} |
|
.chat-container .chat img { |
|
height: 32px; |
|
width: 32px; |
|
} |
|
.chat-container .chat p { |
|
padding: 0 20px; |
|
} |
|
.chat .chat-content:not(:has(.typing-animation), :has(.error)) span { |
|
visibility: visible; |
|
} |
|
.typing-container { |
|
padding: 15px 10px; |
|
} |
|
.typing-container .typing-content { |
|
max-width: 100%; |
|
} |
|
.typing-controls span { |
|
margin-left: 5px; |
|
font-size: 1.2rem; |
|
} |
|
.typing-textarea textarea { |
|
padding: 14px 45px 14px 15px; |
|
font-size: 0.95rem; |
|
} |
|
} |
|
</style> |
|
</head> |
|
<body> |
|
<div class="top-bar"> |
|
<img src="img/chatbot.jpg"> |
|
<span>Hello there! <br> How can I help you today.</span> |
|
</div> |
|
<div class="chat-container"> |
|
<div class="default-text"> |
|
<h1>Welcome</h1> |
|
<p>Start a conversation with the AI assistant.</p> |
|
</div> |
|
</div> |
|
<div class="typing-container"> |
|
<div class="typing-content"> |
|
<div class="typing-textarea"> |
|
<textarea spellcheck="false" placeholder="Type your message.."></textarea> |
|
<span class="material-symbols-rounded">send</span> |
|
</div> |
|
<div class="typing-controls"> |
|
<span class="material-symbols-rounded">dark_mode</span> |
|
</div> |
|
</div> |
|
</div> |
|
<script> |
|
const textarea = document.querySelector("textarea"), |
|
chatContainer = document.querySelector(".chat-container"), |
|
sendChatBtn = document.querySelector("span.material-symbols-rounded"), |
|
toggleIcon = document.querySelector(".typing-controls span"), |
|
defaultText = document.querySelector(".default-text"); |
|
|
|
let userMessage = null; |
|
|
|
toggleIcon.onclick = () => { |
|
document.body.classList.toggle("light-mode"); |
|
}; |
|
|
|
const createTypingAnimation = () => { |
|
const html = ` |
|
<div class="chat-content"> |
|
<div class="chat-details"> |
|
<img src="img/chatbot.jpg" alt=""> |
|
<div class="typing-animation"> |
|
<div class="typing-dot" style="--delay: 0s"></div> |
|
<div class="typing-dot" style="--delay: 0.15s"></div> |
|
<div class="typing-dot" style="--delay: 0.3s"></div> |
|
</div> |
|
</div> |
|
</div>`; |
|
const incomingChat = document.createElement("div"); |
|
incomingChat.classList.add("chat", "incoming"); |
|
incomingChat.innerHTML = html; |
|
return incomingChat; |
|
}; |
|
|
|
const generateResponse = (chatElement) => { |
|
const API_URL = "/generate/"; |
|
|
|
const requestOptions = { |
|
method: "POST", |
|
headers: { |
|
"Content-Type": "application/x-www-form-urlencoded" |
|
}, |
|
body: new URLSearchParams({ |
|
prompt: userMessage, |
|
history: "[]", |
|
temperature: "0.9", |
|
max_new_tokens: "512", |
|
top_p: "0.95", |
|
repetition_penalty: "1.0" |
|
}) |
|
}; |
|
|
|
fetch(API_URL, requestOptions).then(res => res.json()).then(data => { |
|
chatElement.querySelector(".typing-animation").remove(); |
|
const pElement = document.createElement("p"); |
|
pElement.textContent = data.response; |
|
chatElement.querySelector(".chat-details").appendChild(pElement); |
|
|
|
|
|
const regenerateIcon = document.createElement("span"); |
|
regenerateIcon.classList.add("material-symbols-rounded"); |
|
regenerateIcon.textContent = "refresh"; |
|
regenerateIcon.addEventListener("click", () => regenerateResponse(chatElement, userMessage)); |
|
chatElement.querySelector(".chat-content").appendChild(regenerateIcon); |
|
|
|
}).catch(() => { |
|
chatElement.querySelector(".typing-animation").remove(); |
|
const pElement = document.createElement("p"); |
|
pElement.classList.add("error"); |
|
pElement.textContent = "Oops! Something went wrong. Please try again."; |
|
chatElement.querySelector(".chat-details").appendChild(pElement); |
|
}).finally(() => chatContainer.scrollTo(0, chatContainer.scrollHeight)); |
|
}; |
|
|
|
const regenerateResponse = (chatElement, message) => { |
|
const typingAnimation = createTypingAnimation(); |
|
chatElement.replaceWith(typingAnimation); |
|
generateResponse(typingAnimation); |
|
}; |
|
|
|
const copyMessage = (element) => { |
|
navigator.clipboard.writeText(element.innerText); |
|
}; |
|
|
|
const createChatElement = (message, className) => { |
|
const chatElement = document.createElement("div"); |
|
chatElement.classList.add("chat", className); |
|
|
|
const html = ` |
|
<div class="chat-content"> |
|
<div class="chat-details"> |
|
<img src="${className === "outgoing" ? "img/user.jpg" : "img/chatbot.jpg"}" alt=""> |
|
<p>${message}</p> |
|
</div> |
|
<span class="material-symbols-rounded">content_copy</span> |
|
${className === "outgoing" ? '<span class="material-symbols-rounded">edit</span>' : ''} |
|
</div>`; |
|
chatElement.innerHTML = html; |
|
|
|
if (className === "outgoing") { |
|
const editIcon = chatElement.querySelector(".material-symbols-rounded:nth-child(3)"); |
|
editIcon.addEventListener("click", () => { |
|
textarea.value = message; |
|
chatElement.remove(); |
|
textarea.focus(); |
|
}); |
|
} |
|
|
|
const copyIcon = chatElement.querySelector(".material-symbols-rounded:nth-child(2)"); |
|
copyIcon.addEventListener("click", () => copyMessage(chatElement.querySelector("p"))); |
|
|
|
return chatElement; |
|
}; |
|
|
|
const handleChat = () => { |
|
userMessage = textarea.value.trim(); |
|
if (!userMessage) return; |
|
|
|
textarea.value = ""; |
|
textarea.style.height = `${textarea.scrollHeight}px`; |
|
|
|
if (!chatContainer.querySelector(".chat")) defaultText.style.display = "none"; |
|
|
|
chatContainer.appendChild(createChatElement(userMessage, "outgoing")); |
|
chatContainer.scrollTo(0, chatContainer.scrollHeight); |
|
|
|
const incomingChat = createTypingAnimation(); |
|
chatContainer.appendChild(incomingChat); |
|
chatContainer.scrollTo(0, chatContainer.scrollHeight); |
|
|
|
generateResponse(incomingChat); |
|
}; |
|
|
|
textarea.addEventListener("input", () => { |
|
textarea.style.height = "auto"; |
|
textarea.style.height = `${textarea.scrollHeight}px`; |
|
}); |
|
|
|
textarea.addEventListener("keydown", (e) => { |
|
if (e.key === "Enter" && !e.shiftKey) { |
|
e.preventDefault(); |
|
handleChat(); |
|
} |
|
}); |
|
|
|
sendChatBtn.addEventListener("click", handleChat); |
|
</script> |
|
</body> |
|
</html> |
|
|
|
|