Build A Google Gemini Chatbot with HTML CSS and JavaScript
Artificial Intelligence (AI) has completely changed how we communicate with gadgets. Chatbots, such as Google Gemini and ChatGPT, are among the most interesting advancements in this sector since they can accurately replicate human-like discussions.
You're in luck if you're a web developer keen to explore the world of artificial intelligence chatbots.
With the help of HTML, CSS, and JavaScript, we'll show you how to build your very own chatbot that looks and feels like a Google Gemini.
Introduction
Google Gemini is a sophisticated AI chatbot model that has drawn notice for its remarkable conversational skills.
Gemini offers meaningful and captivating interactions with its powerful natural language processing and user-friendly design.
Even while it might take sophisticated knowledge and resources to create a chatbot as complicated as Gemini, you can use fundamental web technologies to create a working, interactive chatbot prototype.
You will be guided step by step through the process of creating a chatbot that is similar to Gemini by this post.
We'll go over setting up the project's structure, including the necessary HTML syntax, styling using CSS, and using JavaScript to enable the chatbot.
To provide a flawless user experience, we'll also incorporate functions like theme switching and conversation history management.
Steps to Build Google Gemini Chatbot
To begin, set up a new project folder and create the necessary files. Follow these steps:
Create a Project Folder and Name it something relevant, such as gemini-chatbot.
Add Essential Files: Create the following files in you code editor
index.html: This file will contain the HTML structure of your chatbot.
style.css: This file will be used for styling the chatbot and making it visually appealing.
script.js: This file will contain JavaScript code to make the chatbot interactive.
Images Folder: Download and add an Images folder to your project directory. This folder should include any logos or images you'll need for the chatbot.
Creating the HTML Structure
Open your index.html file and add the following HTML markup to set up the basic structure of your chatbot.
This includes a header, chat section, suggestions list, and a typing form.
<!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>
Styling with CSS
To give your chatbot a polished look, you’ll need to add CSS styles.
Open your style.css file and include the following CSS code:
/* Import Google Font - Poppins */
@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 {
/* Dark mode colors */
--text-color: #E3E3E3;
--subheading-color: #828282;
--placeholder-color: #A6A6A6;
--primary-color: #242424;
--secondary-color: #383838;
--secondary-hover-color: #444;
}
.light_mode {
/* Light mode colors */
--text-color: #222;
--subheading-color: #A0A0A0;
--placeholder-color: #6C6C6C;
--primary-color: #FFF;
--secondary-color: #E9EEF6;
--secondary-hover-color: #DBE1EA;
}
body {
background: var(--primary-color);
}
.header, .chat-list .message, .typing-form {
margin: 0 auto;
max-width: 980px;
}
.header {
margin-top: 6vh;
padding: 1rem;
overflow-x: hidden;
}
body.hide-header .header {
margin: 0;
display: none;
}
.header :where(.title, .subtitle) {
color: var(--text-color);
font-weight: 500;
line-height: 4rem;
}
.header .title {
width: fit-content;
font-size: 3rem;
background-clip: text;
background: linear-gradient(to right, #4285f4, #d96570);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.header .subtitle {
font-size: 2.6rem;
color: var(--subheading-color);
}
.suggestion-list {
width: 100%;
list-style: none;
display: flex;
gap: 1.25rem;
margin-top: 9.5vh;
overflow: hidden;
overflow-x: auto;
scroll-snap-type: x mandatory;
scrollbar-width: none;
}
.suggestion-list .suggestion {
cursor: pointer;
padding: 1.25rem;
width: 222px;
flex-shrink: 0;
display: flex;
flex-direction: column;
align-items: flex-end;
border-radius: 0.75rem;
justify-content: space-between;
background: var(--secondary-color);
transition: 0.2s ease;
}
.suggestion-list .suggestion:hover {
background: var(--secondary-hover-color);
}
.suggestion-list .suggestion :where(.text, .icon) {
font-weight: 400;
color: var(--text-color);
}
.suggestion-list .suggestion .icon {
width: 42px;
height: 42px;
display: flex;
font-size: 1.3rem;
margin-top: 2.5rem;
align-self: flex-end;
align-items: center;
border-radius: 50%;
justify-content: center;
color: var(--text-color);
background: var(--primary-color);
}
.chat-list {
padding: 2rem 1rem 12rem;
max-height: 100vh;
overflow-y: auto;
scrollbar-color: #999 transparent;
}
.chat-list .message.incoming {
margin-top: 1.5rem;
}
.chat-list .message .message-content {
display: flex;
gap: 1.5rem;
width: 100%;
align-items: center;
}
.chat-list .message .text {
color: var(--text-color);
white-space: pre-wrap;
}
.chat-list .message.error .text {
color: #e55865;
}
.chat-list .message.loading .text {
display: none;
}
.chat-list .message .avatar {
width: 40px;
height: 40px;
object-fit: cover;
border-radius: 50%;
align-self: flex-start;
}
.chat-list .message.loading .avatar {
animation: rotate 3s linear infinite;
}
@keyframes rotate {
100% {
transform: rotate(360deg);
}
}
.chat-list .message .icon {
color: var(--text-color);
cursor: pointer;
height: 35px;
width: 35px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
background: none;
font-size: 1.25rem;
margin-left: 3.5rem;
visibility: hidden;
}
.chat-list .message .icon.hide {
visibility: hidden;
}
.chat-list .message:not(.loading, .error):hover .icon:not(.hide){
visibility: visible;
}
.chat-list .message .icon:hover {
background: var(--secondary-hover-color);
}
.chat-list .message .loading-indicator {
display: none;
gap: 0.8rem;
width: 100%;
flex-direction: column;
}
.chat-list .message.loading .loading-indicator {
display: flex;
}
.chat-list .message .loading-indicator .loading-bar {
height: 11px;
width: 100%;
border-radius: 0.135rem;
background-position: -800px 0;
background: linear-gradient(to right, #4285f4, var(--primary-color), #4285f4);
animation: loading 3s linear infinite;
}
.chat-list .message .loading-indicator .loading-bar:last-child {
width: 70%;
}
@keyframes loading {
0% {
background-position: -800px 0;
}
100% {
background-position: 800px 0;
}
}
.typing-area {
position: fixed;
width: 100%;
left: 0;
bottom: 0;
padding: 1rem;
background: var(--primary-color);
}
.typing-area :where(.typing-form, .action-buttons) {
display: flex;
gap: 0.75rem;
}
.typing-form .input-wrapper {
width: 100%;
height: 56px;
display: flex;
position: relative;
}
.typing-form .typing-input {
height: 100%;
width: 100%;
border: none;
outline: none;
resize: none;
font-size: 1rem;
color: var(--text-color);
padding: 1.1rem 4rem 1.1rem 1.5rem;
border-radius: 100px;
background: var(--secondary-color);
}
.typing-form .typing-input:focus {
background: var(--secondary-hover-color);
}
.typing-form .typing-input::placeholder {
color: var(--placeholder-color);
}
.typing-area .icon {
width: 56px;
height: 56px;
flex-shrink: 0;
cursor: pointer;
border-radius: 50%;
display: flex;
font-size: 1.4rem;
color: var(--text-color);
align-items: center;
justify-content: center;
background: var(--secondary-color);
transition: 0.2s ease;
}
.typing-area .icon:hover {
background: var(--secondary-hover-color);
}
.typing-form #send-message-button {
position: absolute;
right: 0;
outline: none;
border: none;
transform: scale(0);
background: transparent;
transition: transform 0.2s ease;
}
.typing-form .typing-input:valid ~ #send-message-button {
transform: scale(1);
}
.typing-area .disclaimer-text {
text-align: center;
font-size: 0.85rem;
margin-top: 1rem;
color: var(--placeholder-color);
}
/* Responsive media query code for small screen */
@media (max-width: 768px) {
.header :is(.title, .subtitle) {
font-size: 2rem;
line-height: 2.6rem;
}
.header .subtitle {
font-size: 1.7rem;
}
.typing-area :where(.typing-form, .action-buttons) {
gap: 0.4rem;
}
.typing-form .input-wrapper {
height: 50px;
}
.typing-form .typing-input {
padding: 1.1rem 3.5rem 1.1rem 1.2rem;
}
.typing-area .icon {
height: 50px;
width: 50px;
}
.typing-area .disclaimer-text {
font-size: 0.75rem;
margin-top: 0.5rem;
}
}
Adding JavaScript Functionality
With the HTML structure and CSS styling in place, it’s time to add functionality using JavaScript.
Open your script.js file and include the following code:
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/v1/models/gemini-pro: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();
Conclusion
Congratulations! You've successfully built a functional and interactive Google Gemini like chatbot using HTML, CSS and JavaScript.
This project not only enhances your web development skills but also provides hands-on experience with API integration and application state management.
Next Steps:
Now that you have a working chatbot, consider adding additional features or improving its functionality. Here are a few ideas:
- Enhanced Suggestions: Provide users with relevant suggestions based on their input.
- Error Handling: Improve error handling and user feedback.
- Advanced Features: Implement more advanced AI functionalities, such as context-aware responses or user profiling.
By continuing to explore and experiment with different features, you can further refine your chatbot and make it more engaging and useful.
If you encounter any issues or need further assistance, feel free to refer to the project’s source code or reach out to the developer community for support. Happy coding!
Note: Remember to replace the placeholder API key and endpoint with your actual Gemini API key and endpoint before deploying your chatbot.
With these guidelines, you're now equipped to build and customize your very own AI chatbot.
Enjoy the process and the exciting possibilities that come with creating intelligent conversational agents!