Build Google Gemini AI Chatbot with HTML CSS & JavaScript

0

Build Google Gemini AI Chatbot with HTML CSS & JavaScript

In today’s world, AI-powered chatbots are becoming increasingly popular for their ability to provide dynamic, context-aware responses. Imagine creating your own Gemini chatbot similar to ChatGPT that allows users to chat with AI, upload files or images for context-based queries, and even switch between light and dark themes, all using the free Gemini API. Sounds exciting, right?

In this blog post, I’ll guide you through building a Google Gemini AI chatbot using HTML, CSS, and JavaScript. By the end of this project, you’ll not only have a functional chatbot but also gain hands-on experience in API integration, data handling, DOM manipulation, and creating a sleek, modern design inspired by Gemini’s aesthetics.

Whether you’re a beginner or looking to expand your front-end development skills, this project is a great way to explore how AI chatbots are designed and implemented. Let’s get started!

Video Tutorial of Gemini AI Chatbot in HTML & JavaScript

If you prefer learning through video, check out the YouTube tutorial linked above. It provides a detailed walkthrough of every line of code, with clear explanations and helpful comments. For those who prefer written instructions, keep reading as we break down the steps to build the Gemini AI chatbot from scratch.

Steps to Create Gemini AI Chatbot HTML CSS & JavaScript

Follow these simple steps to build your Gemini chatbot using HTML, CSS, and JavaScript:

  • Create a new folder and name it, e.g., gemini-chatbot.
  • Inside this folder, create the following files: index.htmlstyle.css, and script.js.
  • Download the Gemini logo and place it in your project directory.

In the index.html file, set up the basic structure for the chatbot. Use semantic elements like <header>, <ul><li>, and <a> for a clean and accessible layout. Include external Google Fonts CDN and other links to display icons and load external CSS and JavaScript files.

<!DOCTYPE html>
<!-- Coding By CodingNepal - youtube.com/@codingnepal -->
<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@32,400,0,0" />
    <link rel="stylesheet" href="style.css" />
  </head>
  <body>
    <div class="container">
      <!-- App Header -->
      <header class="app-header">
        <h1 class="heading">Hello, there</h1>
        <h4 class="sub-heading">How can I help you today?</h4>
      </header>

      <!-- Suggestions List -->
      <ul class="suggestions">
        <li class="suggestions-item">
          <p class="text">Design a home office setup for remote work under $500.</p>
          <span class="icon material-symbols-rounded">draw</span>
        </li>
        <li class="suggestions-item">
          <p class="text">How can I level up my web development expertise in 2025?</p>
          <span class="icon material-symbols-rounded">lightbulb</span>
        </li>
        <li class="suggestions-item">
          <p class="text">Suggest some useful tools for debugging JavaScript code.</p>
          <span class="icon material-symbols-rounded">explore</span>
        </li>
        <li class="suggestions-item">
          <p class="text">Create a React JS component for the simple todo list app.</p>
          <span class="icon material-symbols-rounded">code_blocks</span>
        </li>
      </ul>

      <!-- Chats -->
      <div class="chats-container"></div>

      <!-- Prompt Input -->
      <div class="prompt-container">
        <div class="prompt-wrapper">
          <form action="#" class="prompt-form">
            <input type="text" placeholder="Ask Gemini" class="prompt-input" required />
            <div class="prompt-actions">
              <!-- File Upload Wrapper -->
              <div class="file-upload-wrapper">
                <img src="#" class="file-preview" />
                <input id="file-input" type="file" accept="image/*, .pdf, .txt, .csv" hidden />
                <button type="button" class="file-icon material-symbols-rounded">description</button>
                <button id="cancel-file-btn" type="button" class="material-symbols-rounded">close</button>
                <button id="add-file-btn" type="button" class="material-symbols-rounded">attach_file</button>
              </div>

              <!-- Send Prompt and Stop Response Buttons -->
              <button id="stop-response-btn" type="button" class="material-symbols-rounded">stop_circle</button>
              <button id="send-prompt-btn" class="material-symbols-rounded">arrow_upward</button>
            </div>
          </form>

          <!-- Theme and Delete Chats Buttons -->
          <button id="theme-toggle-btn" class="material-symbols-rounded">light_mode</button>
          <button id="delete-chats-btn" class="material-symbols-rounded">delete</button>
        </div>

        <p class="disclaimer-text">Gemini can make mistakes, so double-check it.</p>
      </div>
    </div>

    <script src="script.js"></script>
  </body>
</html>

In the style.css file, design your chatbot to achieve a modern Gemini-inspired look. Experiment with colors, layout, and typography to make it visually appealing and unique. Add subtle animations or effects to enhance the user experience and give it your personal touch!

/* 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 theme colors */
  --text-color: #edf3ff;
  --subheading-color: #97a7ca;
  --placeholder-color: #c3cdde;
  --primary-color: #101623;
  --secondary-color: #283045;
  --secondary-hover-color: #333e58;
  --scrollbar-color: #626a7f;
}

body.light-theme {
  /* Light theme colors */
  --text-color: #090c13;
  --subheading-color: #7b8cae;
  --placeholder-color: #606982;
  --primary-color: #f3f7ff;
  --secondary-color: #dce6f9;
  --secondary-hover-color: #d2ddf2;
  --scrollbar-color: #a2aac2;
}

body {
  color: var(--text-color);
  background: var(--primary-color);
}

.container {
  overflow-y: auto;
  padding: 32px 0 60px;
  height: calc(100vh - 127px);
  scrollbar-color: var(--scrollbar-color) transparent;
}

.container :where(.app-header, .suggestions, .message, .prompt-wrapper) {
  position: relative;
  margin: 0 auto;
  width: 100%;
  padding: 0 20px;
  max-width: 990px;
}

.container .app-header {
  margin-top: 3vh;
}

.app-header .heading {
  width: fit-content;
  font-size: 3rem;
  background: linear-gradient(to right, #1d7efd, #8f6fff);
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
}

.app-header .sub-heading {
  font-size: 2.6rem;
  margin-top: -5px;
  color: var(--subheading-color);
}

.container .suggestions {
  width: 100%;
  list-style: none;
  display: flex;
  gap: 15px;
  margin-top: 9.5vh;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
}

body.chats-active .container :where(.app-header, .suggestions) {
  display: none;
}

.suggestions .suggestions-item {
  cursor: pointer;
  padding: 18px;
  width: 228px;
  flex-shrink: 0;
  display: flex;
  scroll-snap-align: center;
  flex-direction: column;
  align-items: flex-end;
  border-radius: 12px;
  justify-content: space-between;
  background: var(--secondary-color);
  transition: 0.3s ease;
}

.suggestions .suggestions-item:hover {
  background: var(--secondary-hover-color);
}

.suggestions .suggestions-item .text {
  font-size: 1.1rem;
}

.suggestions .suggestions-item .icon {
  width: 45px;
  height: 45px;
  display: flex;
  font-size: 1.4rem;
  margin-top: 35px;
  align-self: flex-end;
  align-items: center;
  border-radius: 50%;
  justify-content: center;
  color: #1d7efd;
  background: var(--primary-color);
}

.suggestions .suggestions-item:nth-child(2) .icon {
  color: #28a745;
}

.suggestions .suggestions-item:nth-child(3) .icon {
  color: #ffc107;
}

.suggestions .suggestions-item:nth-child(4) .icon {
  color: #6f42c1;
}

.container .chats-container {
  display: flex;
  gap: 20px;
  flex-direction: column;
}

.chats-container .message {
  display: flex;
  gap: 11px;
  align-items: center;
}

.chats-container .message .avatar {
  width: 43px;
  height: 43px;
  flex-shrink: 0;
  align-self: flex-start;
  border-radius: 50%;
  padding: 6px;
  margin-right: -7px;
  background: var(--secondary-color);
  border: 1px solid var(--secondary-hover-color);
}

.chats-container .message.loading .avatar {
  animation: rotate 3s linear infinite;
}

@keyframes rotate {
  100% {
    transform: rotate(360deg);
  }
}

.chats-container .message .message-text {
  padding: 3px 16px;
  word-wrap: break-word;
  white-space: pre-line;
}

.chats-container .bot-message {
  margin: 9px auto;
}

.chats-container .user-message {
  flex-direction: column;
  align-items: flex-end;
}

.chats-container .user-message .message-text {
  padding: 12px 16px;
  max-width: 75%;
  background: var(--secondary-color);
  border-radius: 13px 13px 3px 13px;
}

.chats-container .user-message .img-attachment {
  margin-top: -7px;
  width: 50%;
  border-radius: 13px 3px 13px 13px;
}

.chats-container .user-message .file-attachment {
  display: flex;
  gap: 6px;
  align-items: center;
  padding: 10px;
  margin-top: -7px;
  border-radius: 13px 3px 13px 13px;
  background: var(--secondary-color);
}

.chats-container .user-message .file-attachment span {
  color: #1d7efd;
}

.container .prompt-container {
  position: fixed;
  width: 100%;
  left: 0;
  bottom: 0;
  padding: 16px 0;
  background: var(--primary-color);
}

.prompt-container :where(.prompt-wrapper, .prompt-form, .prompt-actions) {
  display: flex;
  gap: 12px;
  height: 56px;
  align-items: center;
}

.prompt-container .prompt-form {
  height: 100%;
  width: 100%;
  border-radius: 130px;
  background: var(--secondary-color);
}

.prompt-form .prompt-input {
  width: 100%;
  height: 100%;
  background: none;
  outline: none;
  border: none;
  font-size: 1rem;
  color: var(--text-color);
  padding-left: 24px;
}


.prompt-form .prompt-input::placeholder {
  color: var(--placeholder-color);
}

.prompt-wrapper button {
  width: 56px;
  height: 100%;
  flex-shrink: 0;
  cursor: pointer;
  border-radius: 50%;
  font-size: 1.4rem;
  border: none;
  color: var(--text-color);
  background: var(--secondary-color);
  transition: 0.3s ease;
}

.prompt-wrapper :is(button:hover, #cancel-file-btn, .file-icon) {
  background: var(--secondary-hover-color);
}

.prompt-form .prompt-actions {
  gap: 5px;
  margin-right: 7px;
}

.prompt-wrapper .prompt-form :where(.file-upload-wrapper, button, img) {
  position: relative;
  height: 45px;
  width: 45px;
}

.prompt-form .prompt-actions #send-prompt-btn {
  color: #fff;
  display: none;
  background: #1d7efd;
}

.prompt-form .prompt-input:valid~.prompt-actions #send-prompt-btn {
  display: block;
}

.prompt-form #send-prompt-btn:hover {
  background: #0264e3;
}

.prompt-form .file-upload-wrapper :where(button, img) {
  display: none;
  border-radius: 50%;
  object-fit: cover;
  position: absolute;
}

.prompt-form .file-upload-wrapper.active #add-file-btn {
  display: none;
}

.prompt-form .file-upload-wrapper #add-file-btn,
.prompt-form .file-upload-wrapper.active.img-attached img,
.prompt-form .file-upload-wrapper.active.file-attached .file-icon,
.prompt-form .file-upload-wrapper.active:hover #cancel-file-btn {
  display: block;
}

.prompt-form :is(#stop-response-btn:hover, #cancel-file-btn) {
  color: #d62939;
}

.prompt-wrapper .prompt-form .file-icon {
  color: #1d7efd;
}

.prompt-form #stop-response-btn,
body.bot-responding .prompt-form .file-upload-wrapper {
  display: none;
}

body.bot-responding .prompt-form #stop-response-btn {
  display: block;
}

.prompt-container .disclaimer-text {
  font-size: 0.9rem;
  text-align: center;
  padding: 16px 20px 0;
  color: var(--placeholder-color);
}

/* Responsive media query code for small screens */
@media (max-width: 768px) {
  .container {
    padding: 20px 0 100px;
  }

  .app-header :is(.heading, .sub-heading) {
    font-size: 2rem;
    line-height: 1.4;
  }

  .app-header .sub-heading {
    font-size: 1.7rem;
  }

  .container .chats-container {
    gap: 15px;
  }

  .chats-container .bot-message {
    margin: 4px auto;
  }

  .prompt-container :where(.prompt-wrapper, .prompt-form, .prompt-actions) {
    gap: 8px;
    height: 53px;
  }

  .prompt-container button {
    width: 53px;
  }

  .prompt-form :is(.file-upload-wrapper, button, img) {
    height: 42px;
    width: 42px;
  }

  .prompt-form .prompt-input {
    padding-left: 20px;
  }

  .prompt-form .file-upload-wrapper.active #cancel-file-btn {
    opacity: 0;
  }

  .prompt-wrapper.hide-controls :where(#theme-toggle-btn, #delete-chats-btn) {
    display: none;
  }
}

In the script.js file, you’ll bring your chatbot to life by adding interactivity and functionality. This script will manage user messages, handle image uploads, connect to the Google Gemini API for dynamic responses, and more. Be sure to check the comments in the code to understand the purpose of each section.

const container = document.querySelector(".container");
const chatsContainer = document.querySelector(".chats-container");
const promptForm = document.querySelector(".prompt-form");
const promptInput = promptForm.querySelector(".prompt-input");
const fileInput = promptForm.querySelector("#file-input");
const fileUploadWrapper = promptForm.querySelector(".file-upload-wrapper");
const themeToggleBtn = document.querySelector("#theme-toggle-btn");

// API Setup
const API_KEY = "PASTE-YOUR-API-KEY";
const API_URL = `https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key=${API_KEY}`;

let controller, typingInterval;
const chatHistory = [];
const userData = { message: "", file: {} };

// Set initial theme from local storage
const isLightTheme = localStorage.getItem("themeColor") === "light_mode";
document.body.classList.toggle("light-theme", isLightTheme);
themeToggleBtn.textContent = isLightTheme ? "dark_mode" : "light_mode";

// Function to create message elements
const createMessageElement = (content, ...classes) => {
  const div = document.createElement("div");
  div.classList.add("message", ...classes);
  div.innerHTML = content;
  return div;
};

// Scroll to the bottom of the container
const scrollToBottom = () => container.scrollTo({ top: container.scrollHeight, behavior: "smooth" });

// Simulate typing effect for bot responses
const typingEffect = (text, textElement, botMsgDiv) => {
  textElement.textContent = "";
  const words = text.split(" ");
  let wordIndex = 0;

  // Set an interval to type each word
  typingInterval = setInterval(() => {
    if (wordIndex < words.length) {
      textElement.textContent += (wordIndex === 0 ? "" : " ") + words[wordIndex++];
      scrollToBottom();
    } else {
      clearInterval(typingInterval);
      botMsgDiv.classList.remove("loading");
      document.body.classList.remove("bot-responding");
    }
  }, 40); // 40 ms delay
};

// Make the API call and generate the bot's response
const generateResponse = async (botMsgDiv) => {
  const textElement = botMsgDiv.querySelector(".message-text");
  controller = new AbortController();

  // Add user message and file data to the chat history
  chatHistory.push({
    role: "user",
    parts: [{ text: userData.message }, ...(userData.file.data ? [{ inline_data: (({ fileName, isImage, ...rest }) => rest)(userData.file) }] : [])],
  });

  try {
    // Send the chat history to the API to get a response
    const response = await fetch(API_URL, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ contents: chatHistory }),
      signal: controller.signal,
    });

    const data = await response.json();
    if (!response.ok) throw new Error(data.error.message);

    // Process the response text and display with typing effect
    const responseText = data.candidates[0].content.parts[0].text.replace(/\*\*([^*]+)\*\*/g, "$1").trim();
    typingEffect(responseText, textElement, botMsgDiv);

    chatHistory.push({ role: "model", parts: [{ text: responseText }] });
  } catch (error) {
    textElement.textContent = error.name === "AbortError" ? "Response generation stopped." : error.message;
    textElement.style.color = "#d62939";
    botMsgDiv.classList.remove("loading");
    document.body.classList.remove("bot-responding");
    scrollToBottom();
  } finally {
    userData.file = {};
  }
};

// Handle the form submission
const handleFormSubmit = (e) => {
  e.preventDefault();
  const userMessage = promptInput.value.trim();
  if (!userMessage || document.body.classList.contains("bot-responding")) return;

  userData.message = userMessage;
  promptInput.value = "";
  document.body.classList.add("chats-active", "bot-responding");
  fileUploadWrapper.classList.remove("file-attached", "img-attached", "active");

  // Generate user message HTML with optional file attachment
  const userMsgHTML = `
    <p class="message-text"></p>
    ${userData.file.data ? (userData.file.isImage ? `<img src="data:${userData.file.mime_type};base64,${userData.file.data}" class="img-attachment" />` : `<p class="file-attachment"><span class="material-symbols-rounded">description</span>${userData.file.fileName}</p>`) : ""}
  `;

  const userMsgDiv = createMessageElement(userMsgHTML, "user-message");
  userMsgDiv.querySelector(".message-text").textContent = userData.message;
  chatsContainer.appendChild(userMsgDiv);
  scrollToBottom();

  setTimeout(() => {
    // Generate bot message HTML and add in the chat container
    const botMsgHTML = `<img class="avatar" src="gemini.svg" /> <p class="message-text">Just a sec...</p>`;
    const botMsgDiv = createMessageElement(botMsgHTML, "bot-message", "loading");
    chatsContainer.appendChild(botMsgDiv);
    scrollToBottom();
    generateResponse(botMsgDiv);
  }, 600); // 600 ms delay
};

// Handle file input change (file upload)
fileInput.addEventListener("change", () => {
  const file = fileInput.files[0];
  if (!file) return;

  const isImage = file.type.startsWith("image/");
  const reader = new FileReader();
  reader.readAsDataURL(file);

  reader.onload = (e) => {
    fileInput.value = "";
    const base64String = e.target.result.split(",")[1];
    fileUploadWrapper.querySelector(".file-preview").src = e.target.result;
    fileUploadWrapper.classList.add("active", isImage ? "img-attached" : "file-attached");

    // Store file data in userData obj
    userData.file = { fileName: file.name, data: base64String, mime_type: file.type, isImage };
  };
});

// Cancel file upload
document.querySelector("#cancel-file-btn").addEventListener("click", () => {
  userData.file = {};
  fileUploadWrapper.classList.remove("file-attached", "img-attached", "active");
});

// Stop Bot Response
document.querySelector("#stop-response-btn").addEventListener("click", () => {
  controller?.abort();
  userData.file = {};
  clearInterval(typingInterval);
  chatsContainer.querySelector(".bot-message.loading").classList.remove("loading");
  document.body.classList.remove("bot-responding");
});

// Toggle dark/light theme
themeToggleBtn.addEventListener("click", () => {
  const isLightTheme = document.body.classList.toggle("light-theme");
  localStorage.setItem("themeColor", isLightTheme ? "light_mode" : "dark_mode");
  themeToggleBtn.textContent = isLightTheme ? "dark_mode" : "light_mode";
});

// Delete all chats
document.querySelector("#delete-chats-btn").addEventListener("click", () => {
  chatHistory.length = 0;
  chatsContainer.innerHTML = "";
  document.body.classList.remove("chats-active", "bot-responding");
});

// Handle suggestions click
document.querySelectorAll(".suggestions-item").forEach((suggestion) => {
  suggestion.addEventListener("click", () => {
    promptInput.value = suggestion.querySelector(".text").textContent;
    promptForm.dispatchEvent(new Event("submit"));
  });
});

// Show/hide controls for mobile on prompt input focus
document.addEventListener("click", ({ target }) => {
  const wrapper = document.querySelector(".prompt-wrapper");
  const shouldHide = target.classList.contains("prompt-input") || (wrapper.classList.contains("hide-controls") && (target.id === "add-file-btn" || target.id === "stop-response-btn"));
  wrapper.classList.toggle("hide-controls", shouldHide);
});

// Add event listeners for form submission and file input click
promptForm.addEventListener("submit", handleFormSubmit);
promptForm.querySelector("#add-file-btn").addEventListener("click", () => fileInput.click());

Important: Your chatbot won’t be able to generate responses until you configure it with a Gemini API key. To do this, simply add your API key to the API_KEY variable in the script.js file. You can get a free API key from Google AI Studio. The key will look something like this: AIzaSyAtbnKGX15bTgmx0l_gQeatYvdWvY_wOTQ.

Once your API key is added to the code, your Gemini chatbot will be ready for action! Just open the index.html file in your browser, and you can start chatting, uploading files, and explore other features.

Conclusion and final words

Congratulations! You’ve successfully built a Google Gemini AI chatbot using HTML, CSS, and JavaScript. This project not only helps you understand how to integrate AI into a chatbot but also boosts your skills in API handling, DOM manipulation, and UI design. With features like file uploads and theme switching, your chatbot is both interactive and user-friendly.

But don’t stop here! You can keep improving your chatbot by adding features like speech-to-text, chat history, copy responses, and many more. It’s a fantastic way to level up your skills and build something even more impressive for your portfolio.

If you run into any issues, you can download the source code for this Gemini AI 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.

 

Previous articleStreamlining Designer-Developer Collaboration: Tools and Best Practices

LEAVE A REPLY

Please enter your comment!
Please enter your name here