AI chatbots like Google Gemini or ChatGPT have changed how we interact with technology, making conversations with machines almost human. As a beginner web developer, have you ever thought about building your own AI chatbot? The good news is that it is possible to build a Google Gemini-like chatbot using HTML, CSS, and JavaScript.
For those who aren’t familiar, Gemini is an advanced chatbot model developed by Google, similar to ChatGPT. It uses artificial intelligence to produce human-like responses and has become popular for its natural conversational abilities.
In this blog post, I’ll guide you through building a Google Gemini Chatbot in HTML, CSS, and JavaScript. With this chatbot, users will be able to chat, copy responses, and toggle between light and dark themes. Additionally, the themes and chat history will be saved in the browser’s local storage, ensuring they persist even after a page refresh.
Video Tutorial to Build Gemini Chatbot in HTML CSS & JavaScript
The YouTube video above is a great resource if you prefer video tutorials. It explains each line of code and provides comments, making it easy to follow along with your Gemini chatbot clone project. If you prefer reading or need a step-by-step guide, keep following this post.
Steps to Build Gemini Chatbot in HTML & JavaScript
To build an interactive and functional Gemini chatbot using HTML, CSS, and JavaScript, follow these simple step-by-step instructions:
Create a folder with any name you like, e.g., gemini-chatbot.
Inside it, create the necessary files: index.html, style.css, and script.js.
Download the Images folder and put it in your project directory. This folder contains logos you’ll need for this chatbot project.
In your index.html file, add the essential HTML markup to structure your Gemini chat layout. It features a greetings header, suggestion list, chat section, and typing form, all structured with semantic tags.
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<!DOCTYPE html>
<!-- Coding By CodingNepal - www.codingnepalweb.com -->
Gemini may display inaccurate info, including about people, so double-check its responses.
</p>
</div>
<scriptsrc="script.js"></script>
</body>
</html>
<!DOCTYPE html>
<!-- Coding By CodingNepal - www.codingnepalweb.com -->
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Gemini Chatbot | CodingNepal</title>
<!-- Linking Google Fonts For Icons -->
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Rounded:opsz,wght,FILL,GRAD@24,400,0,0" />
<link rel="stylesheet" href="style.css">
</head>
<body>
<header class="header">
<!-- Header Greetings -->
<h1 class="title">Hello, there</h1>
<p class="subtitle">How can I help you today?</p>
<!-- Suggestion list -->
<ul class="suggestion-list">
<li class="suggestion">
<h4 class="text">Help me plan a game night with my 5 best friends for under $100.</h4>
<span class="icon material-symbols-rounded">draw</span>
</li>
<li class="suggestion">
<h4 class="text">What are the best tips to improve my public speaking skills?</h4>
<span class="icon material-symbols-rounded">lightbulb</span>
</li>
<li class="suggestion">
<h4 class="text">Can you help me find the latest news on web development?</h4>
<span class="icon material-symbols-rounded">explore</span>
</li>
<li class="suggestion">
<h4 class="text">Write JavaScript code to sum all elements in an array.</h4>
<span class="icon material-symbols-rounded">code</span>
</li>
</ul>
</header>
<!-- Chat List / Container -->
<div class="chat-list"></div>
<!-- Typing Area -->
<div class="typing-area">
<form action="#" class="typing-form">
<div class="input-wrapper">
<input type="text" placeholder="Enter a prompt here" class="typing-input" required />
<button id="send-message-button" class="icon material-symbols-rounded">send</button>
</div>
<div class="action-buttons">
<span id="theme-toggle-button" class="icon material-symbols-rounded">light_mode</span>
<span id="delete-chat-button" class="icon material-symbols-rounded">delete</span>
</div>
</form>
<p class="disclaimer-text">
Gemini may display inaccurate info, including about people, so double-check its responses.
</p>
</div>
<script src="script.js"></script>
</body>
</html>
<!DOCTYPE html>
<!-- Coding By CodingNepal - www.codingnepalweb.com -->
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Gemini Chatbot | CodingNepal</title>
<!-- Linking Google Fonts For Icons -->
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Rounded:opsz,wght,FILL,GRAD@24,400,0,0" />
<link rel="stylesheet" href="style.css">
</head>
<body>
<header class="header">
<!-- Header Greetings -->
<h1 class="title">Hello, there</h1>
<p class="subtitle">How can I help you today?</p>
<!-- Suggestion list -->
<ul class="suggestion-list">
<li class="suggestion">
<h4 class="text">Help me plan a game night with my 5 best friends for under $100.</h4>
<span class="icon material-symbols-rounded">draw</span>
</li>
<li class="suggestion">
<h4 class="text">What are the best tips to improve my public speaking skills?</h4>
<span class="icon material-symbols-rounded">lightbulb</span>
</li>
<li class="suggestion">
<h4 class="text">Can you help me find the latest news on web development?</h4>
<span class="icon material-symbols-rounded">explore</span>
</li>
<li class="suggestion">
<h4 class="text">Write JavaScript code to sum all elements in an array.</h4>
<span class="icon material-symbols-rounded">code</span>
</li>
</ul>
</header>
<!-- Chat List / Container -->
<div class="chat-list"></div>
<!-- Typing Area -->
<div class="typing-area">
<form action="#" class="typing-form">
<div class="input-wrapper">
<input type="text" placeholder="Enter a prompt here" class="typing-input" required />
<button id="send-message-button" class="icon material-symbols-rounded">send</button>
</div>
<div class="action-buttons">
<span id="theme-toggle-button" class="icon material-symbols-rounded">light_mode</span>
<span id="delete-chat-button" class="icon material-symbols-rounded">delete</span>
</div>
</form>
<p class="disclaimer-text">
Gemini may display inaccurate info, including about people, so double-check its responses.
</p>
</div>
<script src="script.js"></script>
</body>
</html>
In your style.css file, add CSS code to style your chatbot, and give it a responsive and Gemini-like design. Experiment with different CSS properties such as colors, fonts, and backgrounds to make your clone more attractive.
In your script.js file, add JavaScript code to make your chatbot interactive and functional. This includes enabling features such as sending and receiving messages, toggling between light and dark themes, and managing chat history.
// Prevent default form submission and handle outgoing chat
typingForm.addEventListener("submit", (e)=>{
e.preventDefault();
handleOutgoingChat();
});
loadDataFromLocalstorage();
const typingForm = document.querySelector(".typing-form");
const chatContainer = document.querySelector(".chat-list");
const suggestions = document.querySelectorAll(".suggestion");
const toggleThemeButton = document.querySelector("#theme-toggle-button");
const deleteChatButton = document.querySelector("#delete-chat-button");
// State variables
let userMessage = null;
let isResponseGenerating = false;
// API configuration
const API_KEY = "PASTE-YOUR-API-KEY"; // Your API key here
const API_URL = `https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key=${API_KEY}`;
// Load theme and chat data from local storage on page load
const loadDataFromLocalstorage = () => {
const savedChats = localStorage.getItem("saved-chats");
const isLightMode = (localStorage.getItem("themeColor") === "light_mode");
// Apply the stored theme
document.body.classList.toggle("light_mode", isLightMode);
toggleThemeButton.innerText = isLightMode ? "dark_mode" : "light_mode";
// Restore saved chats or clear the chat container
chatContainer.innerHTML = savedChats || '';
document.body.classList.toggle("hide-header", savedChats);
chatContainer.scrollTo(0, chatContainer.scrollHeight); // Scroll to the bottom
}
// Create a new message element and return it
const createMessageElement = (content, ...classes) => {
const div = document.createElement("div");
div.classList.add("message", ...classes);
div.innerHTML = content;
return div;
}
// Show typing effect by displaying words one by one
const showTypingEffect = (text, textElement, incomingMessageDiv) => {
const words = text.split(' ');
let currentWordIndex = 0;
const typingInterval = setInterval(() => {
// Append each word to the text element with a space
textElement.innerText += (currentWordIndex === 0 ? '' : ' ') + words[currentWordIndex++];
incomingMessageDiv.querySelector(".icon").classList.add("hide");
// If all words are displayed
if (currentWordIndex === words.length) {
clearInterval(typingInterval);
isResponseGenerating = false;
incomingMessageDiv.querySelector(".icon").classList.remove("hide");
localStorage.setItem("saved-chats", chatContainer.innerHTML); // Save chats to local storage
}
chatContainer.scrollTo(0, chatContainer.scrollHeight); // Scroll to the bottom
}, 75);
}
// Fetch response from the API based on user message
const generateAPIResponse = async (incomingMessageDiv) => {
const textElement = incomingMessageDiv.querySelector(".text"); // Getting text element
try {
// Send a POST request to the API with the user's message
const response = await fetch(API_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
contents: [{
role: "user",
parts: [{ text: userMessage }]
}]
}),
});
const data = await response.json();
if (!response.ok) throw new Error(data.error.message);
// Get the API response text and remove asterisks from it
const apiResponse = data.candidates[0].content.parts[0].text.replace(/\*\*(.*?)\*\*/g, '$1');
showTypingEffect(apiResponse, textElement, incomingMessageDiv); // Show typing effect
} catch (error) { // Handle error
isResponseGenerating = false;
textElement.innerText = error.message;
textElement.parentElement.closest(".message").classList.add("error");
} finally {
incomingMessageDiv.classList.remove("loading");
}
}
// Show a loading animation while waiting for the API response
const showLoadingAnimation = () => {
const html = `<div class="message-content">
<img class="avatar" src="images/gemini.svg" alt="Gemini avatar">
<p class="text"></p>
<div class="loading-indicator">
<div class="loading-bar"></div>
<div class="loading-bar"></div>
<div class="loading-bar"></div>
</div>
</div>
<span onClick="copyMessage(this)" class="icon material-symbols-rounded">content_copy</span>`;
const incomingMessageDiv = createMessageElement(html, "incoming", "loading");
chatContainer.appendChild(incomingMessageDiv);
chatContainer.scrollTo(0, chatContainer.scrollHeight); // Scroll to the bottom
generateAPIResponse(incomingMessageDiv);
}
// Copy message text to the clipboard
const copyMessage = (copyButton) => {
const messageText = copyButton.parentElement.querySelector(".text").innerText;
navigator.clipboard.writeText(messageText);
copyButton.innerText = "done"; // Show confirmation icon
setTimeout(() => copyButton.innerText = "content_copy", 1000); // Revert icon after 1 second
}
// Handle sending outgoing chat messages
const handleOutgoingChat = () => {
userMessage = typingForm.querySelector(".typing-input").value.trim() || userMessage;
if(!userMessage || isResponseGenerating) return; // Exit if there is no message or response is generating
isResponseGenerating = true;
const html = `<div class="message-content">
<img class="avatar" src="images/user.jpg" alt="User avatar">
<p class="text"></p>
</div>`;
const outgoingMessageDiv = createMessageElement(html, "outgoing");
outgoingMessageDiv.querySelector(".text").innerText = userMessage;
chatContainer.appendChild(outgoingMessageDiv);
typingForm.reset(); // Clear input field
document.body.classList.add("hide-header");
chatContainer.scrollTo(0, chatContainer.scrollHeight); // Scroll to the bottom
setTimeout(showLoadingAnimation, 500); // Show loading animation after a delay
}
// Toggle between light and dark themes
toggleThemeButton.addEventListener("click", () => {
const isLightMode = document.body.classList.toggle("light_mode");
localStorage.setItem("themeColor", isLightMode ? "light_mode" : "dark_mode");
toggleThemeButton.innerText = isLightMode ? "dark_mode" : "light_mode";
});
// Delete all chats from local storage when button is clicked
deleteChatButton.addEventListener("click", () => {
if (confirm("Are you sure you want to delete all the chats?")) {
localStorage.removeItem("saved-chats");
loadDataFromLocalstorage();
}
});
// Set userMessage and handle outgoing chat when a suggestion is clicked
suggestions.forEach(suggestion => {
suggestion.addEventListener("click", () => {
userMessage = suggestion.querySelector(".text").innerText;
handleOutgoingChat();
});
});
// Prevent default form submission and handle outgoing chat
typingForm.addEventListener("submit", (e) => {
e.preventDefault();
handleOutgoingChat();
});
loadDataFromLocalstorage();
const typingForm = document.querySelector(".typing-form");
const chatContainer = document.querySelector(".chat-list");
const suggestions = document.querySelectorAll(".suggestion");
const toggleThemeButton = document.querySelector("#theme-toggle-button");
const deleteChatButton = document.querySelector("#delete-chat-button");
// State variables
let userMessage = null;
let isResponseGenerating = false;
// API configuration
const API_KEY = "PASTE-YOUR-API-KEY"; // Your API key here
const API_URL = `https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key=${API_KEY}`;
// Load theme and chat data from local storage on page load
const loadDataFromLocalstorage = () => {
const savedChats = localStorage.getItem("saved-chats");
const isLightMode = (localStorage.getItem("themeColor") === "light_mode");
// Apply the stored theme
document.body.classList.toggle("light_mode", isLightMode);
toggleThemeButton.innerText = isLightMode ? "dark_mode" : "light_mode";
// Restore saved chats or clear the chat container
chatContainer.innerHTML = savedChats || '';
document.body.classList.toggle("hide-header", savedChats);
chatContainer.scrollTo(0, chatContainer.scrollHeight); // Scroll to the bottom
}
// Create a new message element and return it
const createMessageElement = (content, ...classes) => {
const div = document.createElement("div");
div.classList.add("message", ...classes);
div.innerHTML = content;
return div;
}
// Show typing effect by displaying words one by one
const showTypingEffect = (text, textElement, incomingMessageDiv) => {
const words = text.split(' ');
let currentWordIndex = 0;
const typingInterval = setInterval(() => {
// Append each word to the text element with a space
textElement.innerText += (currentWordIndex === 0 ? '' : ' ') + words[currentWordIndex++];
incomingMessageDiv.querySelector(".icon").classList.add("hide");
// If all words are displayed
if (currentWordIndex === words.length) {
clearInterval(typingInterval);
isResponseGenerating = false;
incomingMessageDiv.querySelector(".icon").classList.remove("hide");
localStorage.setItem("saved-chats", chatContainer.innerHTML); // Save chats to local storage
}
chatContainer.scrollTo(0, chatContainer.scrollHeight); // Scroll to the bottom
}, 75);
}
// Fetch response from the API based on user message
const generateAPIResponse = async (incomingMessageDiv) => {
const textElement = incomingMessageDiv.querySelector(".text"); // Getting text element
try {
// Send a POST request to the API with the user's message
const response = await fetch(API_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
contents: [{
role: "user",
parts: [{ text: userMessage }]
}]
}),
});
const data = await response.json();
if (!response.ok) throw new Error(data.error.message);
// Get the API response text and remove asterisks from it
const apiResponse = data.candidates[0].content.parts[0].text.replace(/\*\*(.*?)\*\*/g, '$1');
showTypingEffect(apiResponse, textElement, incomingMessageDiv); // Show typing effect
} catch (error) { // Handle error
isResponseGenerating = false;
textElement.innerText = error.message;
textElement.parentElement.closest(".message").classList.add("error");
} finally {
incomingMessageDiv.classList.remove("loading");
}
}
// Show a loading animation while waiting for the API response
const showLoadingAnimation = () => {
const html = `<div class="message-content">
<img class="avatar" src="images/gemini.svg" alt="Gemini avatar">
<p class="text"></p>
<div class="loading-indicator">
<div class="loading-bar"></div>
<div class="loading-bar"></div>
<div class="loading-bar"></div>
</div>
</div>
<span onClick="copyMessage(this)" class="icon material-symbols-rounded">content_copy</span>`;
const incomingMessageDiv = createMessageElement(html, "incoming", "loading");
chatContainer.appendChild(incomingMessageDiv);
chatContainer.scrollTo(0, chatContainer.scrollHeight); // Scroll to the bottom
generateAPIResponse(incomingMessageDiv);
}
// Copy message text to the clipboard
const copyMessage = (copyButton) => {
const messageText = copyButton.parentElement.querySelector(".text").innerText;
navigator.clipboard.writeText(messageText);
copyButton.innerText = "done"; // Show confirmation icon
setTimeout(() => copyButton.innerText = "content_copy", 1000); // Revert icon after 1 second
}
// Handle sending outgoing chat messages
const handleOutgoingChat = () => {
userMessage = typingForm.querySelector(".typing-input").value.trim() || userMessage;
if(!userMessage || isResponseGenerating) return; // Exit if there is no message or response is generating
isResponseGenerating = true;
const html = `<div class="message-content">
<img class="avatar" src="images/user.jpg" alt="User avatar">
<p class="text"></p>
</div>`;
const outgoingMessageDiv = createMessageElement(html, "outgoing");
outgoingMessageDiv.querySelector(".text").innerText = userMessage;
chatContainer.appendChild(outgoingMessageDiv);
typingForm.reset(); // Clear input field
document.body.classList.add("hide-header");
chatContainer.scrollTo(0, chatContainer.scrollHeight); // Scroll to the bottom
setTimeout(showLoadingAnimation, 500); // Show loading animation after a delay
}
// Toggle between light and dark themes
toggleThemeButton.addEventListener("click", () => {
const isLightMode = document.body.classList.toggle("light_mode");
localStorage.setItem("themeColor", isLightMode ? "light_mode" : "dark_mode");
toggleThemeButton.innerText = isLightMode ? "dark_mode" : "light_mode";
});
// Delete all chats from local storage when button is clicked
deleteChatButton.addEventListener("click", () => {
if (confirm("Are you sure you want to delete all the chats?")) {
localStorage.removeItem("saved-chats");
loadDataFromLocalstorage();
}
});
// Set userMessage and handle outgoing chat when a suggestion is clicked
suggestions.forEach(suggestion => {
suggestion.addEventListener("click", () => {
userMessage = suggestion.querySelector(".text").innerText;
handleOutgoingChat();
});
});
// Prevent default form submission and handle outgoing chat
typingForm.addEventListener("submit", (e) => {
e.preventDefault();
handleOutgoingChat();
});
loadDataFromLocalstorage();
Important: Your chatbot is not ready to generate responses until you configure it with a Gemini API key. To do this, add your API key to the API_KEY variable in the script.js file. You can get your free API key from Google AI Studio. It will look something like this: AIzaSyAtpnKGX14bTgmx0l_gQeatYvdWvY_wOTQ.
Once you have added your API key to the code, you’ll be ready to start chatting with your Gemini chatbot. Simply open the index.html file in your browser to see it in action!
Conclusion and final words
You have successfully built your own Google Gemini chatbot using HTML, CSS, and JavaScript. Following these steps, you have developed a functional chatbot capable of interacting with users, changing themes, and saving chat history using local storage.
This project not only improves your web development skills but also gives you practical experience in integrating APIs and managing application states. With your chatbot operational, you can now explore adding extra features or improving its functionality to better meet your requirements.
If you run into any issues, you can download the source code for this Gemini chatbot project by clicking the “Download” button. Don’t forget to check the README.md file for setup and usage instructions. If you need help, you’ll also find support information there.
Salam Brother from Pakistan. I I really appreciate this site and the Programmers who gave me these all material. But please Gave me API key because its my beginning in this Profession.
This is amazing. I have developed a chatbot backend (python + fast api). I’ve been struggling with front end and I believe this will be a good starting point for me
I really appreciate the site, in fact I am passionate about programming, I work a lot to be a programmer, your support will do me good!
thanks for your tutorials
Salam Brother from Pakistan. I I really appreciate this site and the Programmers who gave me these all material. But please Gave me API key because its my beginning in this Profession.
if I create this search in website. … so we can use many time or not free many time?or only use 1000 searchs?
Can you make the chatbot answer questions about the code in a frame likes ChatGpt?
This is amazing. I have developed a chatbot backend (python + fast api). I’ve been struggling with front end and I believe this will be a good starting point for me
Thank you very much! For all these ideas that you share with us. I am Josias and I am following you from Benin!
Happy to hear that! Keep following.
cake, your ad prevent system sucks
Apologies for any inconvenience, you can visit this page for a solution: https://www.codingnepalweb.com/disable-adblocker-on-this-site
I really appreciate the site, in fact I am passionate about programming, I work a lot to be a programmer, your support will do me good!
thanks for your tutorials
Keep going! Best wishes.