|
|
|
<!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); |
|
overflow: auto; |
|
} |
|
.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: calc(100vh - 220px); |
|
padding-top: 60px; |
|
padding-bottom: 150px; |
|
position: relative; |
|
} |
|
: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; |
|
word-break: break-word; |
|
overflow-wrap: break-word; |
|
} |
|
.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; |
|
word-break: break-word; |
|
overflow-wrap: break-word; |
|
} |
|
span.material-symbols-rounded { |
|
user-select: none; |
|
cursor: pointer; |
|
color: var(--icon-color); |
|
} |
|
.chat .chat-content span { |
|
cursor: pointer; |
|
font-size: 1.3rem; |
|
color: var(--icon-color); |
|
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; |
|
overflow-wrap: break-word; |
|
max-width: 100%; |
|
} |
|
.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: 5px; |
|
width: 100%; |
|
display: flex; |
|
flex-direction: column; |
|
padding: 20px 10px; |
|
justify-content: center; |
|
background: var(--outgoing-chat-bg); |
|
border-top: 1px solid var(--incoming-chat-border); |
|
z-index: 1000; |
|
} |
|
.typing-content { |
|
display: flex; |
|
max-width: 950px; |
|
width: 100%; |
|
align-items: flex-end; |
|
margin-bottom: 10px; |
|
} |
|
.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-textarea span { |
|
position: absolute; |
|
right: 10px; |
|
bottom: 10px; |
|
font-size: 1.5rem; |
|
cursor: pointer; |
|
} |
|
.typing-controls { |
|
display: flex; |
|
justify-content: center; |
|
} |
|
.typing-controls span { |
|
margin-left: 7px; |
|
font-size: 1.4rem; |
|
background: var(--incoming-chat-bg); |
|
outline: 1px solid var(--incoming-chat-border); |
|
padding: 10px; |
|
border-radius: 4px; |
|
} |
|
.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 span { |
|
visibility: visible; |
|
} |
|
.typing-container { |
|
padding: 15px 10px; |
|
bottom: 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; |
|
} |
|
} |
|
.chat .icon-container { |
|
display: flex; |
|
justify-content: flex-end; |
|
margin-top: 10px; |
|
} |
|
.top-bar img { |
|
width: 50px; |
|
height: 50px; |
|
border-radius: 30%; |
|
margin-right: 10px; |
|
text-align: left; |
|
} |
|
.top-bar span { |
|
font-size: 16px; |
|
text-align: center; |
|
line-height: 1.5; |
|
} |
|
.code-block { |
|
position: relative; |
|
background: #2d2d2d; |
|
color: #f8f8f2; |
|
padding: 15px; |
|
border-radius: 5px; |
|
margin: 10px 0; |
|
} |
|
|
|
.code-block pre { |
|
margin: 0; |
|
white-space: pre-wrap; |
|
word-break: break-word; |
|
font-family: 'Courier New', Courier, monospace; |
|
} |
|
|
|
.code-block .copy-code { |
|
position: absolute; |
|
top: 10px; |
|
right: 10px; |
|
cursor: pointer; |
|
color: #f8f8f2; |
|
} |
|
|
|
.footer { |
|
text-align: center; |
|
padding: 1px; |
|
font-size: 0.5rem; |
|
color: var(--icon-color); |
|
background: var(--outgoing-chat-bg); |
|
border-top: 1px solid var(--incoming-chat-border); |
|
position: fixed; |
|
bottom: 0; |
|
width: 100%; |
|
} |
|
</style> |
|
</head> |
|
<body> |
|
<div class="top-bar"> |
|
<img src="img/chatbo.jpg"> |
|
<span>Hello there! Greetings from AIConvert's team</span> |
|
</div> |
|
<div class="chat-container"> |
|
<div class="default-text"> |
|
<h1>Welcome</h1> |
|
<p>Start a conversation with the Llama3 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> |
|
<div class="typing-controls"> |
|
<span id="theme-toggle-btn" class="material-symbols-rounded">light_mode</span> |
|
<span id="delete-btn" class="material-symbols-rounded">delete</span> |
|
<span id="download-btn" class="material-symbols-rounded">download</span> |
|
</div> |
|
</div> |
|
<footer class="footer"> |
|
it can produce some errors. therefore, important information must be verified. |
|
</footer> |
|
<script> |
|
document.addEventListener("DOMContentLoaded", () => { |
|
const textarea = document.querySelector("textarea"), |
|
chatContainer = document.querySelector(".chat-container"), |
|
sendChatBtn = document.querySelector(".typing-textarea .material-symbols-rounded"), |
|
deleteBtn = document.getElementById("delete-btn"), |
|
downloadBtn = document.getElementById("download-btn"), |
|
toggleIcon = document.getElementById("theme-toggle-btn"), |
|
defaultText = document.querySelector(".default-text"); |
|
|
|
let userMessage = null; |
|
let chatHistory = []; |
|
|
|
if (!textarea || !chatContainer || !sendChatBtn || !deleteBtn || !downloadBtn || !toggleIcon || !defaultText) { |
|
console.error("One or more elements are not found in the DOM."); |
|
return; |
|
} |
|
|
|
toggleIcon.onclick = () => { |
|
document.body.classList.toggle("light-mode"); |
|
toggleIcon.innerText = document.body.classList.contains("light-mode") ? "dark_mode" : "light_mode"; |
|
}; |
|
|
|
const createTypingAnimation = () => { |
|
const html = ` |
|
<div class="chat-content" style="display: flex; flex-direction: column;"> |
|
<div class="chat-details" style="flex-grow: 1;"> |
|
<img src="img/chatbo.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 formData = new FormData(); |
|
formData.append("prompt", userMessage); |
|
formData.append("history", JSON.stringify(chatHistory)); |
|
|
|
const requestOptions = { |
|
method: "POST", |
|
body: formData |
|
}; |
|
|
|
fetch(API_URL, requestOptions).then(res => res.json()).then(data => { |
|
chatElement.querySelector(".typing-animation").remove(); |
|
const pElement = document.createElement("p"); |
|
pElement.innerHTML = formatBotResponse(data.response); |
|
chatElement.querySelector(".chat-details").appendChild(pElement); |
|
|
|
|
|
const iconContainer = document.createElement("div"); |
|
iconContainer.style.display = "flex"; |
|
iconContainer.style.justifyContent = "flex-end"; |
|
iconContainer.style.marginTop = "10px"; |
|
|
|
|
|
const regenerateIcon = document.createElement("span"); |
|
regenerateIcon.classList.add("material-symbols-rounded"); |
|
regenerateIcon.textContent = "refresh"; |
|
regenerateIcon.style.marginLeft = "10px"; |
|
regenerateIcon.addEventListener("click", () => regenerateResponse(chatElement, userMessage)); |
|
iconContainer.appendChild(regenerateIcon); |
|
|
|
|
|
const copyIcon = document.createElement("span"); |
|
copyIcon.classList.add("material-symbols-rounded"); |
|
copyIcon.textContent = "content_copy"; |
|
copyIcon.style.marginLeft = "10px"; |
|
copyIcon.addEventListener("click", () => copyMessage(pElement)); |
|
iconContainer.appendChild(copyIcon); |
|
|
|
|
|
chatElement.querySelector(".chat-content").appendChild(iconContainer); |
|
|
|
|
|
const codeBlocks = chatElement.querySelectorAll(".code-block .copy-code"); |
|
codeBlocks.forEach(copyIcon => { |
|
addCopyFunctionality(copyIcon); |
|
}); |
|
|
|
|
|
chatHistory.push([userMessage, data.response]); |
|
|
|
}).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) => { |
|
const originalText = element.textContent; |
|
navigator.clipboard.writeText(element.innerText).then(() => { |
|
element.textContent = "Copied"; |
|
setTimeout(() => { |
|
element.textContent = originalText; |
|
}, 1000); |
|
}); |
|
}; |
|
|
|
const formatBotResponse = (response) => { |
|
return response.replace(/```(.*?)```/gs, (match, code) => { |
|
const escapedCode = escapeHTML(code); |
|
return ` |
|
<div class="code-block"> |
|
<pre><code>${escapedCode}</code></pre> |
|
<span class="material-symbols-rounded copy-code">content_copy</span> |
|
</div>`; |
|
}); |
|
}; |
|
|
|
const escapeHTML = (str) => { |
|
return str.replace(/[&<>'"]/g, tag => { |
|
const charsToReplace = { |
|
'&': '&', |
|
'<': '<', |
|
'>': '>', |
|
"'": ''', |
|
'"': '"' |
|
}; |
|
return charsToReplace[tag] || tag; |
|
}); |
|
}; |
|
|
|
const addCopyFunctionality = (element) => { |
|
element.addEventListener("click", () => { |
|
const codeElement = element.previousElementSibling; |
|
navigator.clipboard.writeText(codeElement.innerText).then(() => { |
|
const originalText = element.textContent; |
|
element.textContent = "Copied"; |
|
setTimeout(() => { |
|
element.textContent = originalText; |
|
}, 1000); |
|
}); |
|
}); |
|
}; |
|
|
|
const createChatElement = (message, className) => { |
|
const chatElement = document.createElement("div"); |
|
chatElement.classList.add("chat", className); |
|
|
|
const html = ` |
|
<div class="chat-content" style="display: flex; flex-direction: column;"> |
|
<div class="chat-details" style="flex-grow: 1;"> |
|
<img src="${className === "outgoing" ? "img/user.jpg" : "img/chatbo.jpg"}" alt=""> |
|
<p></p> |
|
</div> |
|
<div style="display: flex; justify-content: flex-end; margin-top: 10px;"> |
|
<span class="material-symbols-rounded">content_copy</span> |
|
${className === "outgoing" ? '<span class="material-symbols-rounded" style="margin-left: 10px;">edit</span>' : ''} |
|
</div> |
|
</div>`; |
|
chatElement.innerHTML = html; |
|
|
|
const pElement = chatElement.querySelector("p"); |
|
pElement.textContent = message; |
|
|
|
if (className === "outgoing") { |
|
const editIcon = chatElement.querySelector(".material-symbols-rounded:nth-child(2)"); |
|
editIcon.addEventListener("click", () => { |
|
textarea.value = message; |
|
chatElement.remove(); |
|
textarea.focus(); |
|
}); |
|
} |
|
|
|
const copyIcon = chatElement.querySelector(".material-symbols-rounded:nth-child(1)"); |
|
copyIcon.addEventListener("click", () => copyMessage(pElement)); |
|
|
|
return chatElement; |
|
}; |
|
|
|
const handleChat = () => { |
|
userMessage = textarea.value.trim(); |
|
if (!userMessage) return; |
|
|
|
textarea.value = ""; |
|
textarea.style.height = "55px"; |
|
|
|
if (defaultText.style.display !== "none") { |
|
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); |
|
|
|
|
|
deleteBtn.addEventListener("click", () => { |
|
chatContainer.innerHTML = ""; |
|
chatHistory = []; |
|
defaultText.style.display = "block"; |
|
}); |
|
|
|
|
|
downloadBtn.addEventListener("click", () => { |
|
let chatText = ""; |
|
const chats = chatContainer.querySelectorAll(".chat"); |
|
chats.forEach((chat) => { |
|
const pElement = chat.querySelector("p"); |
|
if (chat.classList.contains("outgoing")) { |
|
chatText += `User ⬆️ : ${pElement.textContent}\n`; |
|
} else if (chat.classList.contains("incoming")) { |
|
chatText += `AI 🤖 : ${pElement.textContent}\n`; |
|
} |
|
}); |
|
|
|
const blob = new Blob([chatText], { type: "text/plain" }); |
|
const url = URL.createObjectURL(blob); |
|
const a = document.createElement("a"); |
|
a.href = url; |
|
a.download = "chat.txt"; |
|
document.body.appendChild(a); |
|
a.click(); |
|
document.body.removeChild(a); |
|
URL.revokeObjectURL(url); |
|
}); |
|
}); |
|
</script> |
|
</body> |
|
</html> |
|
|