DEV Community

Cover image for Creating a Scholarship Chatbot: A Beginner's Step-by-Step Guide with HTML, CSS, JavaScript, and Gemini API
INDAH-780
INDAH-780

Posted on

Creating a Scholarship Chatbot: A Beginner's Step-by-Step Guide with HTML, CSS, JavaScript, and Gemini API

## Table Of Contents
* Introduction
* Setting Up Your environment
* Building the chat Interface
* Implementing the chatbot functionality
* Storing and retrieving chat History
* Integrating with API
* Testing and debugging
* Important Considerations
* conclusion

1. Introduction

In an era where technology is reshaping every aspect of our lives, generative AI has become a popular topic in the field of technology over the past years. Chatbots have emerged as powerful tools for automating interactions and providing instant assistance.

1.1 What is a chatbot?

A chatbot is an intelligent conversational system that leverages artificial intelligence, particularly natural language processing (NLP) and machine learning, to understand and engage with users in a more human-like and adaptive way. It goes beyond simple scripted responses, allowing it to handle a broad range of questions and tasks with flexibility and precision. This makes AI chatbots ideal for diverse applications such as customer support, virtual assistants, and interactive learning, where dynamic and personalized interactions are essential.
In this article, we will look into the process of creating a chatbot specifically designed to assist students in their search for scholarships. The chatbot will be capable of understanding and responding to user queries, providing information on scholarship opportunities, and offering helpful advice.

We’ll walk you through each step using HTML, CSS, JavaScript, and the Gemini API, ensuring even those new to coding can follow along with ease. From setting up your development environment to coding the chatbot’s features and integrating the Gemini API, this guide will cover all the essential aspects of chatbot development. By the end, you'll have a robust tool ready to assist users in finding scholarships and streamlining their applications.

2. Setting Up Your environment

2.1 Prerequisites

  • A computer With a modern web browser.
  • A text editor or an IDE such as vscode installed locally.
  • Nodejs and npm Installed To test that you have Node.js and npm correctly installed, you can type node --version and npm --version in the command line.

2.2 Folder structure

This section outlines the specific structure used to organize the project files, including directories for the front-end components like HTML, CSS, and JavaScript, as well as assets such as images and external libraries.
The organization of the back-end files, which handle server-side logic and integration with the Gemini API, is also covered.

Find below the folder structure for this

Image description

Lets walk you through these files and folders.

  • node_modules/
    This directory contains all the installed npm packages that the project depends on. It is automatically generated when running npm install and should not be modified manually.

  • .env
    This file holds environment variables for the application, such as API keys and sensitive configuration settings. It should not be shared publicly as it contains confidential information.

  • .env.example
    A template for the .env file, this example provides a reference for the required environment variables without exposing sensitive data. Developers can copy this file to create their own .env with appropriate values.

  • uploads/
    This folder is designated for storing uploaded files, such as images. It allows users to upload content which can be accessed and displayed within the chatbot interface.

  • .gitignore
    This file specifies intentionally untracked files that Git should ignore. It typically includes files and directories like node_modules/ and .env to prevent them from being included in version control.

  • index.html
    The main HTML file for the chatbot, which serves as the entry point for the application. It typically includes references to CSS and JavaScript files, along with the basic structure of the page.

  • server.js
    The main server file that sets up the Express.js server, handles API routes, and manages incoming requests and responses. This file is crucial for connecting the front-end to the back-end functionality.

  • styles/
    This folder holds the CSS files for styling the chatbot. It allows for organized management of styles, making it easier to maintain and update the design

  • script/
    Contains JavaScript files that define the functionality of the chatbot. This includes scripts for handling user input, API interactions, and dynamic updates to the UI.

  • package.json
    This file defines the project's dependencies, scripts, and metadata. It is essential for managing the project's npm packages and provides information about the project.

2.3 Developing the Folder Structure: Key Steps

a. Create a project Directory

  • Create a folder and give it a name scholarship-chatbot.
  • Open the folder in your editor, VsCode.
  • In your editor, with your terminal open, navigate into your new directory using cd scholarship-chatbot.

b. Create Project Files

i. Files to be created for client-side

  • index.html for your HTML structure.
  • styles.css for your CSS styling, to be created in a styles folder.
  • script.js for your JavaScript functionality, to be created in a script folder.

ii. Backend setup

  • Create a file named server.js
  • Creating a Node.js Project To begin our project, we need to set up a Node.js environment. Let's create a new Node.js project by running the following command in the terminal
npm init -y
Enter fullscreen mode Exit fullscreen mode

This command will initialize a new Node.js project.

2.4 Preparing your work for staging

  1. Setting Up Git
  2. Install Git If you haven’t already installed Git, download and install it from the official Git website. Follow the installation instructions for your operating system.
  • Initialize a Git Repository Navigate to the root folder of your chatbot project using the terminal or command prompt. Run the following command to initialize a new Git repository:
git init

Enter fullscreen mode Exit fullscreen mode
  • Create a .gitignore File To prevent unnecessary files from being tracked by Git, create a .gitignore file in the root of your project. Include entries for files and directories you want to ignore, such as
node_modules/
.env

Enter fullscreen mode Exit fullscreen mode
  • Add Files to the Repository and commit changes Regularly committing your changes keeps your project organized and allows you to track your progress effectively.

3. Building the chat Interface

In this section, we’ll focus on creating the user interface (UI) of your scholarship chatbot. The chat interface includes a user input form, a message display area, and a layout for both user and bot messages.

3.1 HTML Structure
The HTML structure serves as the foundation of your chatbot interface. We'll use basic HTML elements to create a simple and functional layout for the chat window and message input.
Our index.html have this code snippet;

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="stylesheet" href="styles/styles.css" />
    <title>Skollar Aid</title>
    <link rel="preconnect" href="https://fonts.googleapis.com" />
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
    <link
      href="https://fonts.googleapis.com/css2?family=Poppins:wght@100;400;500&display=swap"
      rel="stylesheet"
    />
  </head>
  <body>
    <div id="chat-container">
      <header id="header">
        <h1>Skollar Aid</h1>
      </header>
      <main>
        <!-- Layout for both user message and bot message -->
        <div id="chat-history"></div>

        <!-- Form to accept user input -->
        <form id="chat-form">
          <div class="formH">
            <div class="content">
              <div class="context">
                <div class="text">
                <textarea
                  id="textInput"
                  type="text"
                  placeholder="enter prompt here"
                ></textarea>
              </div>

              <!-- Uploads container which will be hidden and will only be visible when the upload icon is clicked-->
                <div class="image-wrapper">
                  <img id="imagePreview" alt="Image Preview" />
                  <span class="close-icon">&times;</span>
                </div></div>



              <!-- Upload button and file input -->
              <div class="imgg">
                <label for="imageInput">Upload</label>
                <input
                  id="imageInput"
                  accept="image/*"
                  type="file"
                  name="image"
                  hidden
                />
              </div>
            </div>
            <div class="fomb">
              <button id="sendButton" type="submit">Send</button>
            </div>
          </div>
        </form>
      </main>
    </div>

    <!-- Modal for Enlarging Image -->
    <div id="imageModal" class="modal">
      <!-- <div class="modal-content-container"> -->
      <span class="close">&times;</span>
      <img id="modalImage" class="modal-content" />
      <div id="caption"></div>
      <!-- </div> -->
    </div>
    <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>

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


Enter fullscreen mode Exit fullscreen mode

Key Elements

  • .chat-container: The main container that holds both the chat history and the form for user input.
  • .chat-history: The area where chat messages will be displayed.
  • #user-input: The input field where the user will type their message. button: A submit button for sending the message.

3.2 CSS Styling
With the HTML structure in place, the next step is to apply CSS styling to enhance the visual appeal of the chat interface. Basic styles will be added to ensure the layout is clean, responsive, and user-friendly. To implement these styles, you can visit the project's GitHub repository for the complete styling details and customization options.

4. Implementing the chatbot functionality

In the JavaScript file, the code is organized into various modules to ensure clarity and maintainability. One of the key modules is responsible for accessing and manipulating elements from the HTML file. This module utilizes methods such as document.getElementById(), document.querySelector(), and document.querySelectorAll() to target specific elements like forms, buttons, containers, or input fields. By modularizing this process, the code becomes easier to manage and update. Each element can be individually referenced, making it simpler to attach event listeners, alter styles, or manipulate content dynamically. This approach contributes to a more structured and scalable codebase, enabling smooth interaction between the JavaScript logic and the HTML structure.

  const textChatForm = document.getElementById("chat-form");
  const chatHistory = document.getElementById("chat-history");
  const textInput = document.getElementById("textInput");
  const imageInput = document.getElementById("imageInput");
  const imagePreview = document.getElementById("imagePreview");
  const closeIcon = document.querySelector(".close-icon");
  const imageWrapper = document.querySelector(".image-wrapper");
  const uploadIcon = document.querySelector(".imgg label");
  const modal = document.getElementById("imageModal");
  const modalImage = document.getElementById("modalImage");
  const closeModal = document.querySelector(".close");
Enter fullscreen mode Exit fullscreen mode

In the JavaScript file, functions are defined to handle specific tasks, enhancing the modularity and reusability of the code.
Each function is crafted to perform a distinct action, such as validating input, processing data, or updating the UI. By encapsulating tasks into functions, the code follows the principle of separation of concerns, making it easier to maintain and debug.

Defining functions in this way promotes code readability, as each function serves a clear purpose and can be reused throughout the application when needed.
This modular approach streamlines the development process, ensures consistency across the codebase, and allows developers to focus on the specific logic of each function while maintaining overall cohesion.

We have functions such as;

The addMessageToChatHistory Function

The addMessageToChatHistory function is responsible for appending new messages to the chat interface. It dynamically creates and formats the message element, including text and optional images, before adding it to the chat history.
This function ensures that both the message content and its associated metadata, like timestamps, are consistently displayed in the chat window.

Below is a code snippet for this.

function addMessageToChatHistory(imageSrc, text, className) {
    const messageElement = document.createElement("div");
    messageElement.classList.add("message", className);

    if (imageSrc) {
      const imgElement = document.createElement("img");
      imgElement.src = imageSrc;
      imgElement.style.maxWidth = "100px";
      imgElement.style.cursor = "pointer";

      imgElement.addEventListener("click", function () {
        openModal(imageSrc);
      });
      messageElement.appendChild(imgElement);
    }

    const markdownText = marked.parse(text);
    const textElement = document.createElement("div");
    textElement.classList.add("text");
    textElement.innerHTML = markdownText;
    messageElement.appendChild(textElement);

    chatHistory.appendChild(messageElement);
    chatHistory.scrollTop = chatHistory.scrollHeight; // Scroll to the bottom after message
  }
Enter fullscreen mode Exit fullscreen mode

Event listeners for the chat form are added to capture user interactions, such as message submission or file uploads. These listeners monitor form events like the "submit" action, ensuring that when the user sends a message or attaches a file, the appropriate functions are triggered to process and display the content in the chat. This interaction streamlines communication within the chat interface.

textChatForm.addEventListener("submit", async function (event) {
    event.preventDefault(); // Prevent the default form submission

    const message = textInput.value.trim();
    const imageFile = imageInput.files[0];

    if (!message && !imageFile) {
      // No message or image, so don't proceed
      return;
    }

    if (imageFile) {
      const formData = new FormData();
      formData.append("image", imageFile);
      formData.append("message", message);

      try {
        const response = await fetch(`${BASE_URL}/upload`, {
          method: "POST",
          body: formData,
        });

        if (!response.ok) {
          throw new Error("Network response was not ok");
        }

        const data = await response.json();
        if (data.imageUrl) {
          const imageUrl = `${BASE_URL}${data.imageUrl}`;

          // Clear the input fields immediately
          textInput.value = "";
          imageInput.value = ""; 
          imagePreview.src = "";
          imageWrapper.style.display = "none";

          // Add to chat history
          addMessageToChatHistory(imageUrl, message, "user-message");
          saveMessageToLocalStorage(imageUrl, message, "user-message");

          // Send message and image info to chatbot API
          const chatResponse = await fetch(`${BASE_URL}/api/message`, {
            method: "POST",
            headers: {
              "Content-Type": "application/json",
            },
            body: JSON.stringify({ message: message, filePath: data.filePath }),
          });

          if (!chatResponse.ok) {
            throw new Error("Network response was not ok");
          }

          const chatData = await chatResponse.json();
          const botReply = chatData.reply || "Sorry, no reply from the bot.";
          setTimeout(() => {
            addMessageToChatHistory(null, botReply, "bot-message");
            saveMessageToLocalStorage(null, botReply, "bot-message");
          }, 1000);
        }
      } catch (error) {
        console.error("Error uploading image:", error);
      }
    } else {
      // Handle text-only submission
      textInput.value = "";
      addMessageToChatHistory(null, message, "user-message");
      saveMessageToLocalStorage(null, message, "user-message");

      try {
        const response = await fetch(`${BASE_URL}/api/message`, {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
          },
          body: JSON.stringify({ message: message }),
        });

        if (!response.ok) {
          throw new Error("Network response was not ok");
        }

        const data = await response.json();
        const botReply = data.reply || "Sorry, no reply from the bot.";
        setTimeout(() => {
          addMessageToChatHistory(null, botReply, "bot-message");
          saveMessageToLocalStorage(null, botReply, "bot-message");
        }, 1000);
      } catch (error) {
        console.error("Error fetching bot reply:", error);
      }
    }
  });

Enter fullscreen mode Exit fullscreen mode

The event listener for the ImageInput monitors when a user selects an image file. Once triggered, it processes the image, generates a preview, and updates the chat interface to allow users to add a description before sending. This enhances the user experience by enabling image attachments alongside messages.

 imageInput.addEventListener("change", function () {
    const file = imageInput.files[0];
    if (file) {
      const reader = new FileReader();
      reader.onload = function (e) {
        imagePreview.src = e.target.result;
        imageWrapper.style.display = "block";
      };
      reader.readAsDataURL(file);
    }
  });
Enter fullscreen mode Exit fullscreen mode

5. Storing and retrieving chat History

The function to store chat history saves messages locally, ensuring that the conversation persists even after a page refresh. It uses local storage to retain both text and images for future retrieval.

  function addMessageToChatHistory(imageSrc, text, className) {
    const messageElement = document.createElement("div");
    messageElement.classList.add("message", className);

    if (imageSrc) {
      const imgElement = document.createElement("img");
      imgElement.src = imageSrc;
      imgElement.style.maxWidth = "100px";
      imgElement.style.cursor = "pointer";

      imgElement.addEventListener("click", function () {
        openModal(imageSrc);
      });
      messageElement.appendChild(imgElement);
    }

    const markdownText = marked.parse(text);
    const textElement = document.createElement("div");
    textElement.classList.add("text");
    textElement.innerHTML = markdownText;
    messageElement.appendChild(textElement);

    chatHistory.appendChild(messageElement);
    chatHistory.scrollTop = chatHistory.scrollHeight; // Scroll to the bottom after message
  }
Enter fullscreen mode Exit fullscreen mode

6. Integrating with API


6.1 Creating a Gemini API key
What is Gemini?
Gemini AI is a set of large language models (LLMs) created by Google AI, known for its cutting-edge advancements in multimodal understanding and processing. It’s essentially a powerful AI tool that can handle various tasks involving different types of data, not just text.

To access the Gemini API and begin working with its functionalities, you can acquire a free Google API Key by registering with MakerSuite at Google. MakerSuite, offered by Google, provides a user-friendly, visual-based interface for interacting with the Gemini API. Within MakerSuite, you can seamlessly engage with Generative Models through its intuitive UI, and if desired, generate an API Token for enhanced control and customization.
Follow the steps to generate a Gemini API key:

  1. To initiate the process, you can either click the link (https://makersuite.google.com) to be redirected to MakerSuite or perform a quick search on Google to locate it. Accept the terms of service and click on continue.
  2. Click on Get API key link from the sidebar and Create API key in new project button to generate the key. Copy the generated API key and configure and add it in your project, by creating a .env file and pasting the key in this format.
export API_KEY=<YOUR_API_KEY>
Enter fullscreen mode Exit fullscreen mode

Image description

6.2 Installing dependencies
Begin the exploration by installing the necessary dependencies listed below:

6.2.1 Install the Gemini API SDK
To use the Gemini API in your own application, you need to install the GoogleGenerativeAI package for Node.js:

npm install @google/generative-ai
Enter fullscreen mode Exit fullscreen mode

6.3 Server Configuration

In this part of the project, we set up our server using Node.js and Express. This server acts as the backbone of our chatbot, handling requests, processing them through the Gemini API, and sending back responses. Let’s break down the server setup and configuration step-by-step.

First, we initialize our server using Express and HTTP, which are essential for handling the networking aspects of our application. We use the Express framework because of its flexibility and minimalistic structure, which is great for a project like this.

Image description

We also set up necessary middleware to parse JSON bodies and serve static files, ensuring our server can understand incoming requests and serve our HTML, CSS, and JavaScript files correctly:

Image description

6.3 Storing Files in our backend.
To manage file storage in our backend, we use Multer, a middleware designed for handling file uploads in Node.js. Multer simplifies the process of receiving files, parsing the request, and saving the files to specified directories.
It integrates seamlessly with Express.js and offers various configuration options, such as setting file size limits and defining storage destinations. By utilizing Multer, our application can efficiently handle user-uploaded files, ensuring they are securely stored and easily accessible for subsequent processing or display.

Image description

6.4 Integration with the Gemini API
To interact with the Gemini API, we upload the file using a POST request. This involves sending the file data to the API endpoint and receiving a structured response. The response typically includes valuable information extracted from the uploaded file, such as metadata or specific content details. By integrating this API into our application, we can automate the process of extracting insights from user-uploaded files, enhancing the overall functionality and user experience

Image description

Our server’s main functionality is handling POST requests to our /message endpoint. This is where user inputs are received and processed. The request handling is set up as follows:

app.post("/api/message", async (req, res) => {
  const { message, filePath } = req.body;

  try {
    let history = [];

    if (filePath) {
      // Ensure the file is uploaded and available for the chat
      const file = await uploadToGemini(filePath, "image/jpeg");

      history.push({
        role: "user",
        parts: [
          {
            fileData: {
              mimeType: file.mimeType,
              fileUri: file.uri,
            },
          },
        ],
      });
    }

    if (message) {
      history.push({
        role: "user",
        parts: [
          {
            text: message,
          },
        ],
      });
    }

    if (history.length === 0) {
      throw new Error("No message or file content provided");
    }

    const chatSession = model.startChat({
      generationConfig: {
        temperature: 0.9,
        topP: 1,
        maxOutputTokens: 2048,
        responseMimeType: "text/plain",
      },
      history: history,
    });

    const result = await chatSession.sendMessage(message);
    console.log(result.response.text());

    res.json({ reply: result.response.text() });
  } catch (error) {
    console.error("Error generating response:", error);
    res.status(500).json({
      reply: "Sorry, something went wrong while generating the response.",
    });
  }
});
Enter fullscreen mode Exit fullscreen mode

Finally, we configure our server to listen on the specified port, making it ready to handle incoming connections. This step ensures that our application is accessible to users, enabling it to respond to requests and serve content as intended. By specifying the port, we define where the server will be available, ensuring a smooth deployment and reliable communication between the server and clients.

Image description

7. Testing and Debugging


After developing our chat application, it's essential to ensure that it is fully functional and stable. Testing involves checking that each feature, like message submission, image uploads, and chat history storage, works as expected across different devices and browsers. Debugging helps identify and fix issues such as broken features, layout problems, or performance bottlenecks, ensuring a smooth and reliable user experience. Effective testing and debugging enhance code quality and prevent future errors.

7.1 Testing for message submission

Image description

7.2 Testing for File uploads

Image description

Image description

8. Important Considerations

In the process of developing a chatbot, several challenges can arise, ranging from technical difficulties to infrastructure limitations. This chapter highlights key issues encountered during the creation of the scholarship chatbot.
This section serves as a valuable reference for future developers, offering insights into common problems and practical approaches to resolving them.

8.1 Electricity Faults and Internet Connection Issues:

a. Problem: Frequent power outages and unstable internet connections disrupted the development process, causing delays and data loss.
b. Solution: Regularly saved work and used offline tools when possible. Also, utilized a backup power source and internet connection to minimize disruptions.

8.2 Image Upload Functionality Not Working Properly:

a. Problem: The image upload feature wasn't functioning as expected; the file manager reloaded when an image was selected, and the preview didn't show up immediately.
b. Solution: Implemented proper event handling and adjusted the logic to ensure the image preview and upload process worked seamlessly without page reloads.

8.3 Handling User Inputs and Chat History:

a.Problem: Managing chat history and inputs from the user in a structured manner, ensuring that the chat session data persisted and loaded correctly.
b. Solution: Utilized local storage to save chat history and implemented checks for session expiry to manage data efficiently.

8.4 Integrating Gemini API with Asynchronous Requests:

a. Problem: Sending and receiving data from the Gemini API asynchronously while ensuring the chatbot responded in a timely manner.
b. Solution: Used async/await for handling API requests and provided user feedback during response delays.

8.5 Styling and UI Adjustments:

a. Problem: Ensuring a consistent and user-friendly interface, especially when switching between text and image input modes.
b.Solution: Refined the CSS styles and layout to create a responsive design, making sure all components aligned and behaved correctly across different input scenarios.

9. Conclusion


In conclusion, the app you’ve built integrates multimodal features, allowing for file storage, chat history preservation, and image previews and can now be pushed to github and deployed

Thank you for taking the time to read this article! If you found it insightful and helpful, please feel free to share it with others who might also appreciate learning about this technology. Don’t forget to give it a 👏 if you enjoyed it.

The full source code for this tutorial can be found here, github repository for the scholarship chatbot

Top comments (0)