Responsive Google Gemini Clone Using HTML CSS JavaScript With Free Source Code
Introduction
Hello coders, Today in this article we’ve made a Responsive Google Gemini Clone Using HTML CSS JavaScript. This responsive chat bot is a clone of google Gemini. In this we’ll learn that how to create this type of project.
In this era of artificial intelligence everyone aware about AI chat bot. Basically AI chat bot’s are the tool which provides us information about any topic or any particular thing. We’ve build this responsive google Gemini clone Using HTML CSS and JavaScript. In this project you’ll get to know that how API works.
To create this project, we’ve used HTML CSS and JS. Like all other projects HTML is used to create the basic structure of the clone and then CSS is applied to give styling to the project. Lastly, JavaScript is added to provide functionality to our project. If you also want to create advance type of project then you should definitely check out our website. We’ve much more stuff for you guys.
Let’s see the code.
index.html
This is our HTML Code. Using this code we’ve developed the basic structure of our website. The code starts with a tag doctype which define that it is a HTML document. After that html tag specifies the language as English.
The <head> contains UTF-8 character set and a viewport meta tag to make the page adapt to all screen sizes. “Gemini Chatbot” appears as the page title, and the design includes custom icons from Google Fonts.
The <body> starts with a header that welcomes users and shows a list of questions the chatbot can help with. These cover topics such as game night planning public speaking improvement, web development news updates, and JavaScript coding. Each topic has a matching icon next to it.
Below the header, you’ll find a chat area and a section to type in your own questions. The design has buttons to send messages, change themes, and erase chats. A notice warns users that the chatbot might give wrong information. The page also connects to separate CSS and JavaScript files to control its look and how it works.
Gemini Chatbot | CodingNepal
Hello, there
How can I help you today?
-
Help me plan a game night with my 5 best friends for under $100.
draw
-
What are the best tips to improve my public speaking skills?
lightbulb
-
Can you help me find the latest news on web development?
explore
-
Write JavaScript code to sum all elements in an array.
code
Gemini may display inaccurate info, including about people, so double-check its responses.
styles.css
This is our CSS code. It starts by importing google font Poppins. Then it resets the margin, padding and some other properties using universal selector. The :root tag is used to define some variable in the code so that they can be used later in code. For light mode some variables are created.
A background color is given to body using variable. Margin and width is given to header, chat-list, message and typing form. After that we styled all the other tags of the HTML like header, chat-list and more. Inside the .header, a gradient is applied to the title text, creating a colorful, eye-catching effect, while the subtitle uses a more subdued color.
After that we styled suggestion list. Suggestion list is styled horizontally scrollable. A hover effect is also added on the cards of suggestion list to make it more interactive. Some icons are added into the suggestion list.
Messages in the chat list have different styles for incoming messages and errors. All the elements of chat list are styled according to their importance and you can see that in the code below. Loading animations are defined by the keyframes like rotate and loading and a feedback option is also styled when messages are being processed.
In the bottom typing area is fixed, makes sure its visible to user type message. Input fields and icons within the form adjust in size and appearance based on user interaction. At the end media queries are used to make our project responsive for smaller screens.
This is all about our 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;
}
}
script.js
This is JavaScript code for the chatbot: message processing, change of theme, and interaction with an API. First, it selects some key HTML elements using document.querySelector and document.querySelectorAll. There are typed forms, the container for the chat, suggestions, and buttons to change the theme and remove chats.
To store the user’s message, I have used state variables, together with one flag that tells whether the chatbot is generating at some point. The required API key is stored in API_KEY along with setting API_URL to use the language model API.
loadDataFromLocalstorage Loads saved chats and the selected theme when page loads from local storage. It applies the theme, restoring saved chats that scroll the chat container to the bottom.
The function createMessageElement dynamically creates message elements in a chat. It accepts a piece of message content and optional CSS classes and appends the element at the bottom of the chat container.
We implement the function called showTypingEffect. The function implements a real-time typing effect in which words are typed one by one. The function uses an interval so that after each word, it displays the word and updates the chat when all words have been displayed.
In reply to the user message shared above, it fetches the response of an API. Then, by sending a POST request to the API, its response blinks to create a typing effect. The errors are displayed as the error message in the chat.
The showLoadingAnimation function appends a loading message with blink bars for creating a loading animation until when the response is ready to be shown.
There are also several functions in the class: copyMessage for copying chat text to the clipboard, handleOutgoingChat for sending user messages, listeners of toggling the theme, deleting chats, and suggestion click event.
And at last, on page load, there is loadDataFromLocalstorage function running.
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 = `
content_copy`;
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 = `
`;
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();