| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <title>Tap Bot</title> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <script src="https://cdn.jsdelivr.net/npm/showdown@2.1.0/dist/showdown.min.js"></script> |
| <style> |
| body { |
| margin: 0; |
| padding: 0; |
| font-family: 'Segoe UI', sans-serif; |
| background: linear-gradient(-45deg, #1f1c2c, #928dab, #2b5876, #4e4376); |
| background-size: 400% 400%; |
| animation: gradientBG 15s ease infinite; |
| color: #fff; |
| display: flex; |
| flex-direction: column; |
| align-items: center; |
| justify-content: center; |
| min-height: 100vh; |
| text-align: center; |
| } |
| |
| @keyframes gradientBG { |
| 0% { |
| background-position: 0% 50%; |
| } |
| 50% { |
| background-position: 100% 50%; |
| } |
| 100% { |
| background-position: 0% 50%; |
| } |
| } |
| |
| h1 { |
| font-size: 3em; |
| margin-bottom: 0.3em; |
| } |
| |
| .store-buttons { |
| display: flex; |
| gap: 20px; |
| flex-wrap: wrap; |
| justify-content: center; |
| margin: 20px 0; |
| } |
| |
| .store-buttons img { |
| width: 160px; |
| height: auto; |
| } |
| |
| .try-btn { |
| background-color: #fff; |
| color: #2980b9; |
| border: none; |
| padding: 10px 20px; |
| font-size: 1.2em; |
| border-radius: 8px; |
| cursor: pointer; |
| transition: 0.3s ease; |
| } |
| |
| .try-btn:hover { |
| background-color: #e0e0e0; |
| } |
| |
| .chat-container { |
| position: fixed; |
| bottom: 20px; |
| right: 20px; |
| background: white; |
| color: black; |
| border-radius: 12px; |
| box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); |
| width: 350px; |
| height: 500px; |
| max-height: 90vh; |
| overflow: hidden; |
| display: none; |
| flex-direction: column; |
| |
| transform: scale(0); |
| opacity: 0; |
| transition: transform 0.3s ease, opacity 0.3s ease; |
| pointer-events: none; |
| } |
| |
| .chat-container.open { |
| transform: scale(1); |
| opacity: 1; |
| pointer-events: auto; |
| } |
| |
| .chat-header { |
| background-color: #2980b9; |
| color: white; |
| padding: 10px; |
| font-weight: bold; |
| display: flex; |
| justify-content: space-between; |
| align-items: center; |
| } |
| |
| .chat-body { |
| padding: 10px; |
| overflow-y: auto; |
| flex-grow: 1; |
| } |
| |
| .chat-input { |
| display: flex; |
| border-top: 1px solid #ccc; |
| } |
| |
| .chat-input input { |
| flex-grow: 1; |
| border: none; |
| padding: 10px; |
| font-size: 1em; |
| } |
| |
| .chat-input button { |
| border: none; |
| padding: 10px 15px; |
| background-color: #2980b9; |
| color: white; |
| cursor: pointer; |
| } |
| |
| .msg.user { |
| text-align: right; |
| margin: 5px 0; |
| } |
| |
| .msg.bot { |
| text-align: left; |
| margin: 5px 0; |
| } |
| |
| .msg .bubble { |
| display: inline-block; |
| padding: 10px; |
| border-radius: 10px; |
| max-width: 85%; |
| } |
| |
| .msg.user .bubble { |
| background: #d1eaff; |
| color: #000; |
| } |
| |
| .msg.bot .bubble { |
| background: #f1f1f1; |
| color: #000; |
| } |
| |
| .bubble.typing { |
| font-style: italic; |
| } |
| |
| .copy-btn { |
| background: none; |
| border: none; |
| cursor: pointer; |
| font-size: 1em; |
| margin-left: 6px; |
| color: #555; |
| float: right; |
| position: relative; |
| } |
| |
| .copy-btn::after { |
| content: "Copy"; |
| position: absolute; |
| bottom: 100%; |
| left: 50%; |
| transform: translateX(-50%); |
| background: #000; |
| color: #fff; |
| padding: 4px 8px; |
| font-size: 0.75em; |
| border-radius: 4px; |
| opacity: 0; |
| white-space: nowrap; |
| pointer-events: none; |
| transition: opacity 0.2s; |
| } |
| |
| .copy-btn:hover::after { |
| opacity: 1; |
| } |
| |
| .toast { |
| visibility: hidden; |
| min-width: 120px; |
| background-color: #333; |
| color: #fff; |
| text-align: center; |
| border-radius: 6px; |
| padding: 10px; |
| position: fixed; |
| z-index: 1; |
| left: 50%; |
| bottom: 30px; |
| transform: translateX(-50%); |
| font-size: 0.9em; |
| opacity: 0; |
| transition: opacity 0.5s, bottom 0.5s; |
| } |
| |
| .toast.show { |
| visibility: visible; |
| opacity: 1; |
| bottom: 50px; |
| } |
| |
| #typing { |
| display: flex; |
| padding: 5px; |
| font-style: italic; |
| color: #555; |
| align-items: center; |
| } |
| |
| #typing span.dot { |
| height: 10px; |
| width: 10px; |
| margin: 0 3px; |
| background-color: #999; |
| border-radius: 50%; |
| display: inline-block; |
| animation: blink 1.4s infinite; |
| } |
| |
| #typing span.dot:nth-child(2) { |
| animation-delay: 0.2s; |
| } |
| |
| #typing span.dot:nth-child(3) { |
| animation-delay: 0.4s; |
| } |
| |
| @keyframes blink { |
| 0%, 80%, 100% { |
| opacity: 0.2; |
| transform: scale(0.8); |
| } |
| 40% { |
| opacity: 1; |
| transform: scale(1); |
| } |
| } |
| |
| footer { |
| margin-top: 40px; |
| font-size: 0.9em; |
| } |
| |
| @media (max-width: 600px) { |
| .store-buttons { |
| flex-direction: column; |
| align-items: center; |
| } |
| } |
| </style> |
| </head> |
| <div id="toast" class="toast">Copied!</div> |
| <body> |
|
|
| <h1 id="welcomeTitle"></h1> |
| <p id="welcomeSub"></p> |
| <div class="store-buttons"> |
| <a href="#"><img |
| src="https://upload.wikimedia.org/wikipedia/commons/thumb/7/78/Google_Play_Store_badge_EN.svg/512px-Google_Play_Store_badge_EN.svg.png" |
| alt="Google Play"></a> |
| <a href="#"><img src="https://developer.apple.com/assets/elements/badges/download-on-the-app-store.svg" |
| alt="App Store"></a> |
| </div> |
|
|
| <button class="try-btn" onclick="openChat()">Try Now</button> |
|
|
| <footer> |
| <p><strong>Disclaimer:</strong> Tap Bot is powered by a fine-tuned language model trained using publicly available |
| data. </p> |
| <p> We do not claim ownership of the original data sources used for training. </p> |
| <p> Developed by <a href="https://sahildesai.dev" target="_blank" style="color:#fff;text-decoration:underline;">sahildesai.dev</a> |
| </p> |
| </footer> |
|
|
| <div class="chat-container" id="chatBox"> |
| <div class="chat-header"><span id="chatTitle"></span> |
| <div> |
| <button onclick="clearChat()" style="background:none; border:none; cursor:pointer;"> |
| <svg xmlns="http://www.w3.org/2000/svg" height="20" width="20" fill="white" viewBox="0 0 24 24"> |
| <path d="M3 6h18v2H3V6zm2 3h14l-1.5 12.5H6.5L5 9zm5.5-5h3v2h-3V4z"/> |
| </svg> |
| </button> |
| <button onclick="toggleChat()" |
| style="background:none;border:none;color:white;font-size:1.2em;cursor:pointer;">X |
| </button> |
| </div> |
| </div> |
| <div class="chat-body" id="chatMessages"> |
| <div class="msg bot" id="welcomeMessage"> |
| <div class="bubble">Hi there! I’m Tap Bot. How can I help you today?</div> |
| </div> |
| </div> |
| <div class="chat-input"> |
| <input type="text" id="userInput" placeholder="What's on your mind?" oninput="toggleSendButton()" |
| onkeydown="handleEnter(event)"/> |
| <button id="sendBtn" onclick="submitMessage()" disabled>Send</button> |
| </div> |
| </div> |
|
|
| <script> |
| const botName = "Tap Bot"; |
| |
| document.addEventListener("DOMContentLoaded", () => { |
| document.getElementById("welcomeTitle").textContent = `Welcome to ${botName}!`; |
| document.getElementById("welcomeSub").textContent = `Your AI-powered assistant is live.`; |
| document.getElementById("chatTitle").textContent = botName; |
| }); |
| |
| function copyToClipboard(button) { |
| const text = button.previousElementSibling.textContent; |
| navigator.clipboard.writeText(text).then(() => { |
| button.textContent = "✅"; |
| setTimeout(() => (button.textContent = "📋"), 1500); |
| }); |
| } |
| |
| function openChat() { |
| const box = document.getElementById("chatBox"); |
| box.classList.add("open"); |
| document.getElementById("chatMessages").innerHTML = ""; |
| const chatMessages = document.getElementById("chatMessages"); |
| box.style.display = "flex"; |
| chatMessages.innerHTML = ''; |
| |
| |
| const botDiv = document.createElement("div"); |
| botDiv.className = "msg bot"; |
| const typingDiv = document.createElement("div"); |
| typingDiv.className = "bubble typing"; |
| typingDiv.innerHTML = `<div id="typing"><span class="dot"></span><span class="dot"></span><span class="dot"></span></div>`; |
| botDiv.appendChild(typingDiv); |
| chatMessages.appendChild(botDiv); |
| |
| chatMessages.scrollTop = chatMessages.scrollHeight; |
| |
| |
| setTimeout(() => { |
| const welcomeText = "Hi there! I’m Tap Bot. How can I help you today?"; |
| let i = 0; |
| typingDiv.innerHTML = ""; |
| const textNode = document.createElement("div"); |
| typingDiv.appendChild(textNode); |
| |
| function typeChar() { |
| if (i < welcomeText.length) { |
| textNode.textContent += welcomeText[i++]; |
| chatMessages.scrollTop = chatMessages.scrollHeight; |
| setTimeout(typeChar, 30); |
| } |
| } |
| |
| typeChar(); |
| }, 500); |
| } |
| |
| function toggleChat() { |
| const box = document.getElementById("chatBox"); |
| box.classList.toggle("open"); |
| } |
| |
| function clearChat() { |
| const chatMessages = document.getElementById("chatMessages"); |
| chatMessages.innerHTML = ""; |
| const welcome = document.createElement("div"); |
| welcome.className = "msg bot"; |
| welcome.id = "welcomeMessage"; |
| welcome.innerHTML = `<div class="bubble">Hi there! I’m Tap Bot. How can I help you today?</div>`; |
| chatMessages.appendChild(welcome); |
| chatMessages.scrollTop = chatMessages.scrollHeight; |
| |
| setTimeout(() => { |
| const welcomeText = "Hi there! I’m Tap Bot. How can I help you today?"; |
| let i = 0; |
| typingDiv.innerHTML = ""; |
| |
| function typeChar() { |
| if (i < welcomeText.length) { |
| typingDiv.innerHTML += welcomeText[i++]; |
| chatMessages.scrollTop = chatMessages.scrollHeight; |
| setTimeout(typeChar, 30); |
| } |
| } |
| |
| typeChar(); |
| }, 500); |
| } |
| |
| function toggleSendButton() { |
| const btn = document.getElementById("sendBtn"); |
| const input = document.getElementById("userInput"); |
| btn.disabled = input.value.trim().length === 0; |
| } |
| |
| function handleEnter(e) { |
| if (e.key === "Enter") { |
| e.preventDefault(); |
| if (!document.getElementById("sendBtn").disabled) { |
| submitMessage(); |
| } |
| } |
| } |
| |
| function showToast(message = "Copied!") { |
| const toast = document.getElementById("toast"); |
| toast.textContent = message; |
| toast.classList.add("show"); |
| setTimeout(() => { |
| toast.classList.remove("show"); |
| }, 2000); |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| async function submitMessage() { |
| const input = document.getElementById("userInput"); |
| const msg = input.value.trim(); |
| if (!msg) return; |
| |
| const chatMessages = document.getElementById("chatMessages"); |
| |
| |
| const userDiv = document.createElement("div"); |
| userDiv.className = "msg user"; |
| userDiv.innerHTML = `<div class="bubble">${msg}</div>`; |
| chatMessages.appendChild(userDiv); |
| |
| |
| const botDiv = document.createElement("div"); |
| botDiv.className = "msg bot"; |
| const typingDiv = document.createElement("div"); |
| typingDiv.className = "bubble typing"; |
| |
| const typingIndicator = document.createElement("div"); |
| typingIndicator.id = "typing"; |
| typingIndicator.innerHTML = `<span class="dot"></span><span class="dot"></span><span class="dot"></span>`; |
| typingDiv.appendChild(typingIndicator); |
| botDiv.appendChild(typingDiv); |
| chatMessages.appendChild(botDiv); |
| chatMessages.scrollTop = chatMessages.scrollHeight; |
| |
| input.value = ""; |
| toggleSendButton(); |
| |
| try { |
| const res = await fetch("/chat", { |
| method: "POST", |
| headers: {"Content-Type": "application/json"}, |
| body: JSON.stringify({prompt: msg}) |
| }); |
| |
| const data = await res.json(); |
| const fullText = data.response; |
| |
| |
| const sentences = fullText.split(/(?<=\.)\s+/); |
| let selectedSentences = []; |
| |
| for (const sentence of sentences) { |
| selectedSentences.push(sentence.trim()); |
| } |
| |
| |
| |
| |
| const converter = new showdown.Converter(); |
| const markdownOutput = selectedSentences.join("\n"); |
| typingDiv.innerHTML = converter.makeHtml(markdownOutput); |
| typingDiv.classList.remove("typing"); |
| |
| |
| |
| const copyBtn = document.createElement("button"); |
| copyBtn.className = "copy-btn"; |
| copyBtn.innerHTML = ` |
| <svg xmlns="http://www.w3.org/2000/svg" height="18" width="18" viewBox="0 0 24 24" fill="white" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> |
| <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" /> |
| <rect x="8" y="2" width="8" height="4" rx="1" ry="1"/> |
| </svg>`; |
| copyBtn.onclick = function () { |
| navigator.clipboard.writeText(selectedSentences.join("\n")).then(() => { |
| showToast("Copied!"); |
| copyBtn.textContent = "✅"; |
| setTimeout(() => { |
| copyBtn.innerHTML = ` |
| <svg xmlns="http://www.w3.org/2000/svg" height="18" width="18" viewBox="0 0 24 24" fill="white" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> |
| <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" /> |
| <rect x="8" y="2" width="8" height="4" rx="1" ry="1"/> |
| </svg>`; |
| }, 1500); |
| }); |
| }; |
| typingDiv.appendChild(copyBtn); |
| |
| } catch (e) { |
| typingDiv.textContent = "Something went wrong. " + e.message; |
| typingDiv.classList.remove("typing"); |
| } |
| } |
| |
| </script> |
| </body> |
| </html> |