Homework 4: ChatJS

Due October 8th at 11:59 PM

Topics: HTTP Requests, API Calls, Asynchronous JavaScript, LLM Chatbots

In this homework, we will use HTML, CSS, and JavaScript to build a chatbot using the Gemini API!

This homework will involve working with asynchronous JavaScript, making HTTP requests to APIs, and handling responses from a large language model (LLM) to create a functional chatbot. You will also implement a chat persistence feature that allows users to save and retrieve their chat history.

This project will use a combination of Gemini API calls for generating chat responses, and a provided Chat API for saving and retrieving chat history. You will need to implement the necessary methods to interact with these APIs and handle the data appropriately.

Assignment Goals

  • Build a fully-functional LLM chatbot that uses Google Gemini
  • Test your ability to work with asynchronous syntax in JavaScript
  • Gain experience with working with backend systems by sending data to a provided storage API to save chat history

Introduction & Installation

Starter Code

Files

Upon retrieving the starter files, make sure you have the following files:

  • script.ts
  • chat.ts
  • chat-api.ts
  • types.ts
  • An empty index.html
  • A filled sample style.css
  • package.json, the ts/prettier/eslint configs, RUBRIC.md, and an README.md file

Installation & Running

This homework uses TypeScript, so you will need to build your ts files into js files before running the project.

You will be using the serve package to run the project, as it is a simple static file server that will serve your files on a local port. The start script is provided in the package.json file, which will build your ts files and then run the server on port 3000. You can run npm start in the terminal to run the project. You can then open your browser and navigate to http://localhost:3000 to see your project running, or use VSCode's built-in browser to view it in your IDE.

Google Gemini

In this homework, you will use the Gemini API to generate responses.

🤑 Gemini offers a free API, no credit card required!

First, you will need to create a new project in the Google Cloud Console. (This may require you to have a separate Google account from your school account. Let us know ASAP if you have issues with this step)

Once you've created a project, head over to Google AI Studio and create a new API key.

It should ask for your project name, and then you can create a new API key. Select the project you created in the previous step.

Once you have created the API key, you can copy it and paste it into the chat.ts file in the first lines of the script:

// Insert your Gemini API key here
const GEMINI_API_KEY = "YOUR_GEMINI_API_KEY";
const GEMINI_API_URL = "https://generativelanguage.googleapis.com/...";

Make sure to replace the placeholder string with your actual Gemini API key. Keep in mind that you should never share your API key publicly or commit it to a public repository, as it can be used by others to access your quota and potentially incur costs.

The starter code has provided a template Gemini API URL that calls the Gemini 3.8 Flash model. There are many applicable models that you could use for this assignment, so feel free to search them up and experiment with different ones if you are interested! You can find the documentation for the Gemini API here. Note that are using fetch API to call the Gemini API instead of the official client library in the docs since we are working in a primarily frontend environment only.

Chat API

In order to use the Chat API for chat persistence, you will need another API key that we will provide for you. You should receive a Canvas message or email from the instructors soon after the homework is released with your API key. Make sure to never share this API key, just as you wouldn't share your Gemini API key! Once you have the API key, paste it into the chat-api.ts file towards the top where the placeholder string is. This API key is specific to the Chat API and is different from your Gemini API key, so make sure not to mix them up!:

// Insert your Chat API key here
const BASE_URL = "https://cis-1962-fa26-hw4.onrender.com/api";
const API_KEY = "YOUR_CHAT_API_KEY";

Important: Cold-Starting the API

When you make the first request to the API after it has been idle for a while, it may take a long time to respond (up to a minute or two) since the server needs to "wake up" from idleness. This is called cold-starting. To avoid this, you can make a simple GET request to the /chat endpoint right after you initialize the app to wake up the server. You can type the following command into the terminal to make a simple API request to wake up the server and start working:

curl "https://cis-1962-fa26-hw4.onrender.com/api/version"

Instructions

Part 1: ChatAPI & Persistence

Files: chat-api.ts

We will begin by implementing the chat API to save the chat history. Implement the following in the chat-api.ts file:

  • ChatAPI's constructor
  • GET /chat - fetchChats()
    • Response: id: "default", messages: { role: string, content: string }[]
  • POST /chat - createChat()
    • Response: id: string, messages: { role: string, content: string }[]
  • GET /chat/:id - getChat(id: string)
    • Response: id: "default", messages: { role: string, content: string }[]
  • PUT /chat/:id - updateChat(chat: Chat)
    • Request: id: "default", messages: { role: string, content: string }[]
    • Response: id: "default", messages: { role: string, content: string }[]
  • DELETE /chat - clearChats()
  • (no request or response)

For these methods, you will need to use the fetch API to make HTTP requests to the provided endpoints. For instance, all of these requests use the /chat endpoint, so you would use the URL https://cis-1962-fa26-hw4.onrender.com/api/chat, but the PUT request would also include the chat ID as a parameter in the URL.

Additionally, when sending requests to the API, you may need to specify the method, body, and headers of the HTTP request. Any HTTP method (POST, PUT, DELETE) that is NOT GET should have the requisite method specified (since fetch by default sends a GET request). Any requests that have a specified "request" body in the API documentation will require you to include that JSON body in the request, and ALL requests will require you to include your API key in the headers (for authentication purposes). Use the following format for the headers within your fetch requests:

headers: {
    "Authorization": `Bearer ${this.apiKey}`,
    "Content-Type": "application/json" // only needed for requests with a body
}

Part 2: Chat Messages

Files: script.ts, chat.ts

Now we will implement the Chat class to send and store messages. We've provided you the implementation of generateGeminiResponse() method in chat.ts, which sends a request to the Gemini API and retrieves a response. You'll be using this method within the file to input a chat log and output a response from the Gemini API.

Implement the following in the chat.ts file:

  • The Chat class's constructor
  • getMessages(): Get an array of the message log
  • sendMessages(message): Send a new message to the message log, generate a response from the Gemini API, and update the message log
  • save(): saves the chat to the ChatAPI, by calling the updateChat() method.

Part 3: Chat UI

Files: index.html, style.css

We need one more step before we make the chat functional: we need the chat UI! You will need to make changes to the index.html and style.css files to create a chat interface that allows users to send messages and view responses from the chatbot.

You will need to implement the following UI elements in index.html and style them in style.css:

  • A chat container that displays the chat messages
  • A message input field for the user to type their message
  • A send button to send the message
  • A chat list to display available chats
  • A new chat button to create a new chat
  • A delete all chats button to clear all chats

Feel free to style the chat messages, input, and form within style.css. We've provided a template stylesheet, but you can modify it as needed. You can use modern chat clients like ChatGPT or apps like WhatsApp as a model. Just be sure that it is easily readable and accessible- you will lose points if your app is unusable, hard to read, or confusing to use. Feel free to be creative! 😃


Part 4: Chat Functionality

Files: script.ts, index.html, style.css

Finally, let's make the chat functional! You will need to implement the following in the script.ts file:

  • A default Chat class for the first load of the app
  • initializeApp(): Runs on the first load of the app, fetching the first chat from the chatAPI and switching to it, or default if there are no chats yet. If default is chosen, a new chat is created.
  • switchToChat(): Switches to a different chat. This is called upon first load, or when a user clicks a button for a different chat on the sidebar.
  • renderChatList(): Renders the list of available chats (gotten form the ChatAPI) on the sidebar.
  • renderMessages(): Renders the messages of the currently selected chat. This will display the actual back and forth between a user and the LLM, which you will need to style appropriately.
  • hideTypingIndicator()/showTypingIndicator(): Hides and shows a typing indicator respectively.
  • An event listener to handle submitting messages.
  • An event listener to create a new chat when the "New Chat" button is clicked"
  • An event listener to clear all chats when the "Clear Chats" button is clicked"

Documentation and details for each of the methods above can be found in the JSDocs and comments within the starter code. Make sure to read through them carefully and understand what each method is supposed to do before implementing it.

After you have implemented the `script.js` file, you should be able to:

  • See an initial default chat upon first load (with no previous chats)
  • See the chat list in the chat list container
  • Click on the "Create New Chat" button to create a new chat
  • Click on a chat item to switch to that chat
  • See the chat messages in the chat messages container
  • See the chat input and form in the chat form container
  • Send messages to the chat and get responses from the LLM

Do not worry too much about the sorted order of the chat list. You only need to show us the ID of the chat in the chat list, and just make sure that the chat list stores the correct chatIds and messages.

Make sure to test out your chatbot thoroughly and ensure that it is working as expected. Make sure it can handle errors as well, such as when the API keys are invalid or when the API is down. You can simulate these errors by changing the API keys to invalid values or by temporarily disabling your internet connection. Make sure to handle these errors gracefully and provide appropriate feedback to the user.

Submission

README

Answer the provided reflection questions within the starter code README file. In this reflection, you will also indicate whether or not you used AI, and also document your usage of AI as well. Please don't forget this step, as it is important feedback for the homework and the content of the course!

Submission

Submit your code through Gradescope as a .zip file that contains your project. Make sure your project includes all files you worked on during this homework and your README.md file, all config files for TS, eslint, and prettier, your test script, and the every JSON file used in the tests. You should NOT include the node_modules folder in the .zip file (feel free to delete it before submission), as it is quite heavy and we will reinstall the dependencies for grading anyways. Make sure the submitted file structure within your submission is exactly or similar to the file structure you used to run and develop the project. Points will be taken off for malformed project structures in the final submission!

Before you submit, make sure you lint your code for style errors using the command npm run lint. More details on style can be found in the style guide. We will take -1 points for every style error remaining in the submission for the submitted files. Since this project requires you to make your own ESLint, we will use your linting rules instead of the standard rules we would apply, so make sure you pass your own set of style rules!

For this homework, we've provided a rubric file named RUBRIC.md in the starter code. Make sure to read through it carefully and ensure that your submission meets all the requirements outlined in the rubric. This will help you maximize your score and ensure that you've covered all necessary aspects of the assignment.