|
<!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/[email protected]/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> |