<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" type="image/x-icon" href="/favicon.ico">
<title>AI Chatbot</title>
<!-- Linking Google fonts for icons -->
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@48,400,0,0&family=Material+Symbols+Rounded:opsz,wght,FILL,GRAD@48,400,1,0"
/>
<!-- Including CSS directly -->
<style>
/* Importing Google Fonts - Inter */
@import url('https://fonts.googleapis.com/css2?family=Inter:opsz,wght@14..32,100..900&display=swap');
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: "Inter", sans-serif;
}
body {
width: 100%;
min-height: 100vh;
background: linear-gradient(#EEEEFF, #C8C7FF);
}
#chatbot-toggler {
position: fixed;
bottom: 30px;
right: 35px;
border: none;
height: 50px;
width: 50px;
display: flex;
cursor: pointer;
align-items: center;
justify-content: center;
border-radius: 50%;
background: #5350C4;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.1);
transition: all 0.2s ease;
}
body.show-chatbot #chatbot-toggler {
transform: rotate(90deg);
}
#chatbot-toggler span {
color: #fff;
position: absolute;
}
#chatbot-toggler span:last-child,
body.show-chatbot #chatbot-toggler span:first-child {
opacity: 0;
}
body.show-chatbot #chatbot-toggler span:last-child {
opacity: 1;
}
.chatbot-popup {
position: fixed;
right: 35px;
bottom: 90px;
width: 420px;
overflow: hidden;
background: #fff;
border-radius: 15px;
opacity: 0;
pointer-events: none;
transform: scale(0.2);
transform-origin: bottom right;
box-shadow: 0 0 128px 0 rgba(0, 0, 0, 0.1),
0 32px 64px -48px rgba(0, 0, 0, 0.5);
transition: all 0.1s ease;
}
body.show-chatbot .chatbot-popup {
opacity: 1;
pointer-events: auto;
transform: scale(1);
}
.chat-header {
display: flex;
align-items: center;
padding: 15px 22px;
background: #5350C4;
justify-content: space-between;
}
.chat-header .header-info {
display: flex;
gap: 10px;
align-items: center;
}
.header-info .chatbot-logo {
width: 35px;
height: 35px;
padding: 6px;
fill: #5350C4;
flex-shrink: 0;
background: #fff;
border-radius: 50%;
}
.header-info .logo-text {
color: #fff;
font-weight: 600;
font-size: 1.31rem;
letter-spacing: 0.02rem;
}
.chat-header #close-chatbot {
border: none;
color: #fff;
height: 40px;
width: 40px;
font-size: 1.9rem;
margin-right: -10px;
padding-top: 2px;
cursor: pointer;
border-radius: 50%;
background: none;
transition: 0.2s ease;
}
.chat-header #close-chatbot:hover {
background: #3d39ac;
}
.chat-body {
padding: 25px 22px;
gap: 20px;
display: flex;
height: 460px;
overflow-y: auto;
margin-bottom: 82px;
flex-direction: column;
scrollbar-width: thin;
scrollbar-color: #ccccf5 transparent;
}
.chat-body,
.chat-form .message-input:hover {
scrollbar-color: #ccccf5 transparent;
}
.chat-body .message {
display: flex;
gap: 11px;
align-items: center;
}
.chat-body .message .bot-avatar {
width: 35px;
height: 35px;
padding: 6px;
fill: #fff;
flex-shrink: 0;
margin-bottom: 2px;
align-self: flex-end;
border-radius: 50%;
background: #5350C4;
}
.chat-body .message .message-text {
padding: 12px 16px;
max-width: 75%;
font-size: 0.95rem;
}
.chat-body .bot-message.thinking .message-text {
padding: 2px 16px;
}
.chat-body .bot-message .message-text {
background: #F2F2FF;
border-radius: 13px 13px 13px 3px;
}
.chat-body .user-message {
flex-direction: column;
align-items: flex-end;
}
.chat-body .user-message .message-text {
color: #fff;
background: #5350C4;
border-radius: 13px 13px 3px 13px;
}
.chat-body .user-message .attachment {
width: 50%;
margin-top: -7px;
border-radius: 13px 3px 13px 13px;
}
.chat-body .bot-message .thinking-indicator {
display: flex;
gap: 4px;
padding-block: 15px;
}
.chat-body .bot-message .thinking-indicator .dot {
height: 7px;
width: 7px;
opacity: 0.7;
border-radius: 50%;
background: #6F6BC2;
animation: dotPulse 1.8s ease-in-out infinite;
}
.chat-body .bot-message .thinking-indicator .dot:nth-child(1) {
animation-delay: 0.2s;
}
.chat-body .bot-message .thinking-indicator .dot:nth-child(2) {
animation-delay: 0.3s;
}
.chat-body .bot-message .thinking-indicator .dot:nth-child(3) {
animation-delay: 0.4s;
}
@keyframes dotPulse {
0%,
44% {
transform: translateY(0);
}
28% {
opacity: 0.4;
transform: translateY(-4px);
}
44% {
opacity: 0.2;
}
}
.chat-footer {
position: absolute;
bottom: 0;
width: 100%;
background: #fff;
padding: 15px 22px 20px;
}
.chat-footer .chat-form {
display: flex;
align-items: center;
position: relative;
background: #fff;
border-radius: 32px;
outline: 1px solid #CCCCE5;
box-shadow: 0 0 8px rgba(0, 0, 0, 0.06);
transition: 0s ease, border-radius 0s;
}
.chat-form:focus-within {
outline: 2px solid #5350C4;
}
.chat-form .message-input {
width: 100%;
height: 47px;
outline: none;
resize: none;
border: none;
max-height: 180px;
scrollbar-width: thin;
border-radius: inherit;
font-size: 0.95rem;
padding: 14px 0 12px 18px;
scrollbar-color: transparent transparent;
}
.chat-form .chat-controls {
gap: 3px;
height: 47px;
display: flex;
padding-right: 6px;
align-items: center;
align-self: flex-end;
}
.chat-form .chat-controls button {
height: 35px;
width: 35px;
border: none;
cursor: pointer;
color: #706DB0;
border-radius: 50%;
font-size: 1.15rem;
background: none;
transition: 0.2s ease;
}
.chat-form .chat-controls button:hover,
body.show-emoji-picker .chat-controls #emoji-picker {
color: #3d39ac;
background: #f1f1ff;
}
.chat-form .chat-controls #send-message {
color: #fff;
display: none;
background: #5350C4;
}
.chat-form .chat-controls #send-message:hover {
background: #3d39ac;
}
.chat-form .message-input:valid ~ .chat-controls #send-message {
display: block;
}
.chat-form .file-upload-wrapper {
position: relative;
height: 35px;
width: 35px;
}
.chat-form .file-upload-wrapper :where(button, img) {
position: absolute;
}
.chat-form .file-upload-wrapper img {
height: 100%;
width: 100%;
object-fit: cover;
border-radius: 50%;
}
.chat-form .file-upload-wrapper #file-cancel {
color: #ff0000;
background: #fff;
}
.chat-form .file-upload-wrapper :where(img, #file-cancel),
.chat-form .file-upload-wrapper.file-uploaded #file-upload {
display: none;
}
.chat-form .file-upload-wrapper.file-uploaded img,
.chat-form .file-upload-wrapper.file-uploaded:hover #file-cancel {
display: block;
}
em-emoji-picker {
position: absolute;
left: 50%;
top: -337px;
width: 100%;
max-width: 350px;
visibility: hidden;
max-height: 330px;
transform: translateX(-50%);
}
body.show-emoji-picker em-emoji-picker {
visibility: visible;
}
/* Responsive media query for mobile screens */
@media (max-width: 520px) {
#chatbot-toggler {
right: 20px;
bottom: 20px;
}
.chatbot-popup {
right: 0;
bottom: 0;
height: 100%;
border-radius: 0;
width: 100%;
}
.chatbot-popup .chat-header {
padding: 12px 15px;
}
.chat-body {
height: calc(90% - 55px);
padding: 25px 15px;
}
.chat-footer {
padding: 10px 15px 15px;
}
.chat-form .file-upload-wrapper.file-uploaded #file-cancel {
opacity: 0;
}
}
</style>
</head>
<body>
<!-- Chatbot Toggler -->
<button id="chatbot-toggler">
<span class="material-symbols-rounded">mode_comment</span>
<span class="material-symbols-rounded">close</span>
</button>
<div class="chatbot-popup">
<!-- Chatbot Header -->
<div class="chat-header">
<div class="header-info">
<svg
class="chatbot-logo"
xmlns="http://www.w3.org/2000/svg"
width="50"
height="50"
viewBox="0 0 1024 1024"
>
<path
d="M738.3 287.6H285.7c-59 0-106.8 47.8-106.8 106.8v303.1c0 59 47.8 106.8 106.8 106.8h81.5v111.1c0 .7.8 1.1 1.4.7l166.9-110.6 41.8-.8h117.4l43.6-.4c59 0 106.8-47.8 106.8-106.8V394.5c0-59-47.8-106.9-106.8-106.9zM351.7 448.2c0-29.5 23.9-53.5 53.5-53.5s53.5 23.9 53.5 53.5-23.9 53.5-53.5 53.5-53.5-23.9-53.5-53.5zm157.9 267.1c-67.8 0-123.8-47.5-132.3-109h264.6c-8.6 61.5-64.5 109-132.3 109zm110-213.7c-29.5 0-53.5-23.9-53.5-53.5s23.9-53.5 53.5-53.5 53.5 23.9 53.5 53.5-23.9 53.5-53.5 53.5zM867.2 644.5V453.1h26.5c19.4 0 35.1 15.7 35.1 35.1v121.1c0 19.4-15.7 35.1-35.1 35.1h-26.5zM95.2 609.4V488.2c0-19.4 15.7-35.1 35.1-35.1h26.5v191.3h-26.5c-19.4 0-35.1-15.7-35.1-35.1zM561.5 149.6c0 23.4-15.6 43.3-36.9 49.7v44.9h-30v-44.9c-21.4-6.5-36.9-26.3-36.9-49.7 0-28.6 23.3-51.9 51.9-51.9s51.9 23.3 51.9 51.9z"
/>
</svg>
<h2 class="logo-text">Chatbot</h2>
</div>
<button id="close-chatbot" class="material-symbols-rounded">
keyboard_arrow_down
</button>
</div>
<!-- Chatbot Body -->
<div class="chat-body">
<div class="message bot-message">
<svg
class="bot-avatar"
xmlns="http://www.w3.org/2000/svg"
width="50"
height="50"
viewBox="0 0 1024 1024"
>
<path
d="M738.3 287.6H285.7c-59 0-106.8 47.8-106.8 106.8v303.1c0 59 47.8 106.8 106.8 106.8h81.5v111.1c0 .7.8 1.1 1.4.7l166.9-110.6 41.8-.8h117.4l43.6-.4c59 0 106.8-47.8 106.8-106.8V394.5c0-59-47.8-106.9-106.8-106.9zM351.7 448.2c0-29.5 23.9-53.5 53.5-53.5s53.5 23.9 53.5 53.5-23.9 53.5-53.5 53.5-53.5-23.9-53.5-53.5zm157.9 267.1c-67.8 0-123.8-47.5-132.3-109h264.6c-8.6 61.5-64.5 109-132.3 109zm110-213.7c-29.5 0-53.5-23.9-53.5-53.5s23.9-53.5 53.5-53.5 53.5 23.9 53.5 53.5-23.9 53.5-53.5 53.5zM867.2 644.5V453.1h26.5c19.4 0 35.1 15.7 35.1 35.1v121.1c0 19.4-15.7 35.1-35.1 35.1h-26.5zM95.2 609.4V488.2c0-19.4 15.7-35.1 35.1-35.1h26.5v191.3h-26.5c-19.4 0-35.1-15.7-35.1-35.1zM561.5 149.6c0 23.4-15.6 43.3-36.9 49.7v44.9h-30v-44.9c-21.4-6.5-36.9-26.3-36.9-49.7 0-28.6 23.3-51.9 51.9-51.9s51.9 23.3 51.9 51.9z"
/>
</svg>
<!-- prettier-ignore -->
<div class="message-text"> Hey there <br /> How can I help you today? </div>
</div>
</div>
<!-- Chatbot Footer -->
<div class="chat-footer">
<form action="#" class="chat-form">
<textarea
placeholder="Message..."
class="message-input"
required
></textarea>
<div class="chat-controls">
<button
type="button"
id="emoji-picker"
class="material-symbols-outlined"
>
sentiment_satisfied
</button>
<div class="file-upload-wrapper">
<input type="file" accept="image/*" id="file-input" hidden />
<img src="#" />
<button
type="button"
id="file-upload"
class="material-symbols-rounded"
>
attach_file
</button>
<button
type="button"
id="file-cancel"
class="material-symbols-rounded"
>
close
</button>
</div>
<button
type="submit"
id="send-message"
class="material-symbols-rounded"
>
arrow_upward
</button>
</div>
</form>
</div>
</div>
<!-- Linking Emoji Mart script for emoji picker -->
<script src="https://cdn.jsdelivr.net/npm/emoji-mart@latest/dist/browser.js"></script>
<!-- Including JavaScript directly -->
<script>
document.addEventListener("DOMContentLoaded", function() {
const chatBody = document.querySelector(".chat-body");
const messageInput = document.querySelector(".message-input");
const sendMessage = document.querySelector("#send-message");
const fileInput = document.querySelector("#file-input");
const fileUploadWrapper = document.querySelector(".file-upload-wrapper");
const fileCancelButton = fileUploadWrapper.querySelector("#file-cancel");
const chatbotToggler = document.querySelector("#chatbot-toggler");
const closeChatbot = document.querySelector("#close-chatbot");
// API setup
const API_KEY = "AIzaSyDKP_tbIPwsz5ehj5UZeQ-tD7n4owVgwrE";
const API_URL = `https://generativelanguage.googleapis.com/v1/models/gemini-1.5-pro:generateContent?key=${API_KEY}`;
// Initialize user message and file data
const userData = {
message: null,
file: {
data: null,
mime_type: null,
},
};
// Store chat history
const chatHistory = [];
const initialInputHeight = messageInput.scrollHeight;
// Create message element with dynamic classes and return it
const createMessageElement = (content, ...classes) => {
const div = document.createElement("div");
div.classList.add("message", ...classes);
div.innerHTML = content;
return div;
};
// Generate bot response using API
const generateBotResponse = async (incomingMessageDiv) => {
const messageElement = incomingMessageDiv.querySelector(".message-text");
// Add user message to chat history
chatHistory.push({
role: "user",
parts: [{ text: userData.message }, ...(userData.file.data ? [{ inline_data: userData.file }] : [])],
});
// API request options
const requestOptions = {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
contents: chatHistory,
}),
};
try {
// Fetch bot response from API
const response = await fetch(API_URL, requestOptions);
const data = await response.json();
if (!response.ok) throw new Error(data.error.message);
// Extract and display bot's response text
const apiResponseText = data.candidates[0].content.parts[0].text.replace(/\*\*(.*?)\*\*/g, "$1").trim();
messageElement.innerText = apiResponseText;
// Add bot response to chat history
chatHistory.push({
role: "model",
parts: [{ text: apiResponseText }],
});
} catch (error) {
// Handle error in API response
console.log(error);
messageElement.innerText = error.message;
messageElement.style.color = "#ff0000";
} finally {
// Reset user's file data, removing thinking indicator and scroll chat to bottom
userData.file = {};
incomingMessageDiv.classList.remove("thinking");
chatBody.scrollTo({ top: chatBody.scrollHeight, behavior: "smooth" });
}
};
// Handle outgoing user messages
const handleOutgoingMessage = (e) => {
e.preventDefault();
userData.message = messageInput.value.trim();
messageInput.value = "";
messageInput.dispatchEvent(new Event("input"));
fileUploadWrapper.classList.remove("file-uploaded");
// Create and display user message
const messageContent = '<div class="message-text"></div>' +
(userData.file.data ? '<img src="data:' + userData.file.mime_type + ';base64,' + userData.file.data + '" class="attachment" />' : '');
const outgoingMessageDiv = createMessageElement(messageContent, "user-message");
outgoingMessageDiv.querySelector(".message-text").innerText = userData.message;
chatBody.appendChild(outgoingMessageDiv);
chatBody.scrollTo({ top: chatBody.scrollHeight, behavior: "smooth" });
// Simulate bot response with thinking indicator after a delay
setTimeout(() => {
const messageContent = '<svg class="bot-avatar" xmlns="http://www.w3.org/2000/svg" width="50" height="50" viewBox="0 0 1024 1024">' +
'<path d="M738.3 287.6H285.7c-59 0-106.8..."></path>' +
'</svg>' +
'<div class="message-text">' +
'<div class="thinking-indicator">' +
'<div class="dot"></div>' +
'<div class="dot"></div>' +
'<div class="dot"></div>' +
'</div>' +
'</div>';
const incomingMessageDiv = createMessageElement(messageContent, "bot-message", "thinking");
chatBody.appendChild(incomingMessageDiv);
chatBody.scrollTo({ top: chatBody.scrollHeight, behavior: "smooth" });
generateBotResponse(incomingMessageDiv);
}, 600);
};
// Adjust input field height dynamically
messageInput.addEventListener("input", () => {
messageInput.style.height = `${initialInputHeight}px`;
messageInput.style.height = `${messageInput.scrollHeight}px`;
document.querySelector(".chat-form").style.borderRadius = messageInput.scrollHeight > initialInputHeight ? "15px" : "32px";
});
// Handle Enter key press for sending messages
messageInput.addEventListener("keydown", (e) => {
const userMessage = e.target.value.trim();
if (e.key === "Enter" && !e.shiftKey && userMessage && window.innerWidth > 768) {
handleOutgoingMessage(e);
}
});
// Handle file input change and preview the selected file
fileInput.addEventListener("change", () => {
const file = fileInput.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = (e) => {
fileInput.value = "";
fileUploadWrapper.querySelector("img").src = e.target.result;
fileUploadWrapper.classList.add("file-uploaded");
const base64String = e.target.result.split(",")[1];
// Store file data in userData
userData.file = {
data: base64String,
mime_type: file.type,
};
};
reader.readAsDataURL(file);
});
// Cancel file upload
fileCancelButton.addEventListener("click", () => {
userData.file = {};
fileUploadWrapper.classList.remove("file-uploaded");
});
// Initialize emoji picker and handle emoji selection
const picker = new EmojiMart.Picker({
theme: "light",
skinTonePosition: "none",
previewPosition: "none",
onEmojiSelect: (emoji) => {
const { selectionStart: start, selectionEnd: end } = messageInput;
messageInput.setRangeText(emoji.native, start, end, "end");
messageInput.focus();
},
onClickOutside: (e) => {
if (e.target.id === "emoji-picker") {
document.body.classList.toggle("show-emoji-picker");
} else {
document.body.classList.remove("show-emoji-picker");
}
},
});
document.querySelector(".chat-form").appendChild(picker);
sendMessage.addEventListener("click", (e) => handleOutgoingMessage(e));
document.querySelector("#file-upload").addEventListener("click", () => fileInput.click());
closeChatbot.addEventListener("click", () => document.body.classList.remove("show-chatbot"));
chatbotToggler.addEventListener("click", () => document.body.classList.toggle("show-chatbot"));
});
</script>
</body>
</html>