|
|
| <!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> |
|
|