Homework 4: ChatJS
Due February 26th at 11:59 PM
This homework will be a continuation of Homework 3. You will build out the simple chatbot you create in Homework 3 into a fully fledged chatbot that uses Gemini API to generate responses. You will also implement chat persistence using a provided storage API that saves your chat history to a database.
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 CodeFiles
Upon retrieving the starter files, make sure you have the following files:
- script.ts
- chat.ts
- chat-api.ts
- index.html
- style.css
- package.json, the prettier/eslint configs, RUBRIC.md, and an README.md file
Installation & Importing HW3
You will import over the HTML and CSS files from HW3. You will need to copy over the contents of your index.html and style.css files from HW3 into the corresponding files in the HW4 starter code. You will then build on top of that existing structure and styling to implement the new features for HW4, so make sure to keep all of your previous code and not delete anything important!
You can use either the Live Server extension or the serve package to run your project locally for development. Refer to the previous homework's instructions if you need a refresher on how to use either of these tools. If you choose to use the serve package, make sure to run the following command in your terminal from the root directory of your project to start the server:
npx serve ./dist -p 3000Google 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.
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 (we've made your respositories private for this assignment), 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 2.5 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 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-201-sp26-hw4.onrender.com/api";
const API_KEY = "YOUR_API_KEY";IMPORTANT
For the simplicity of this homework, we hard-coded the Gemini API and Chat API keys. In a real-world application, you should never do this. Web applications are publicly accessible, so is your API key in your code. You should store your API key in a secure way, such as in an environment variable behind a backend server.
Instructions
Part 1: Modularize and Reformat
Files: chat.tsYou may notice that we didn't tell you to import the contents of your script.ts file from HW3 into the new script.ts file in the starter code. This is because you will need to restructure much of the code you wrote in HW3 to better fit the new modularized structure of the starter code. We'll begin with the format of messages themselves.
Begin by opening the chat.ts file and implement the following methods within the Chat class:
- constructor({ id, messages })
- getMessages()
- sendMessage(message)
For this assignment we've provided a types.ts file that contains useful TypeScript type definitions. Make sure to use these types where applicable in your code.
Additionally, pay attention to the format of messages, specifically the role fields of the message object. To properly pass chat history into Gemini API, you need to use "user" and "model" as the values for the role field for user messages and AI messages, respectively.
We've provided you the implementation of generateGeminiResponse() method, which sends a request to the Gemini API and retrieves a response. You can use this method to test if your Gemini API key is working and if you are sending requests in the correct format. You can call this method in the browser console after initializing the app to see if you get a response back from the Gemini API.
Part 2: Chat Persistence
Files: chat-api.tsNow, we will implement the chat API to save the chat history. Implement the following methods in the chat-api.ts file, associated with the provided endpoints in the API documentation:
- GET /chat - fetchChats()
- Response: id: "default", 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 }[]
For these methods, you will need to use the fetch API to make HTTP requests to the provided endpoints. For instance, both of these use the /chat endpoint, so they would each have the URL of https://cis-1962-201-sp26-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 (GET, POST, PUT, DELETE) that is NOT GET should have the requisite method specified. 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 a the header within your fetch requests:
headers: {
"Authorization": `Bearer ${this.apiKey}`,
"Content-Type": "application/json"
}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) 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:
curl "https://cis-1962-201-sp26-hw4.onrender.com/api/version"Part 3: Functional Chat
Files: script.ts, chat.ts
Now we will use the chat API to make the chat functional. You will need to make changes to the script.ts file, which initializes and manages various parts about the chatbot. We've provided important method headers that you will need to implement, some of which are different from HW3 slightly. You will need to:
- Implement the save() method in the Chat class using the chat API. Your implementation should just call updateChat() with the current chat object passed into the argument to that method.
- Import the Chat class from the chat.ts file. Then, create a new Chat object stored to a variable called currentChat with an id of "default" and an initially empty message array. Pay attention to the parameters of the Chat class when constructing it.
- Implement renderMessages(), which should be similar from the same method in HW3.
- Implement switchToChat(chatId: string). This gets the chat with the given ID in the API and switches to it, updating the currentChat variable. This could call the renderMessages() method here to update the UI. You will also call the renderChatList() method here too, but that will be implemented later.
- Implement initializeApp(), a method that is run at the start of the app to fetch a chat. This should call the respective API endpoint to retrieve the chat data, then switch to the first chat in the list. If none exist yet, load the "default" chat.
- Implement showTypingIndicator() and hideTypingIndicator(), which shows or hides the typing indicator in the UI respectively when the AI is typing.
- Add an event listener for submitting a new message to the chatbot. Your implementation here will incorporate sending a message to the Gemini, awaiting a response, and all the while rending the typing indicators. Follow the jsdoc comments for details.
At the end of this section, you should have a functional chatbot that has a single working chatroom. You should be able to send messages and receive responses from Gemini, and the chat history should be saved to the database through the chat API.
Part 4: Multiple Chats
Now, we will implement multiple chats in the chat API. This will involve implementing a new endpoint in the ChatAPI to create and delete chats, while adding new UI elements to the frontend to display the list of chats and allow the user to switch between them, create new ones, and delete all chats.
Files: chat-api.tsFirst, within chat-api.ts, implement the following endpoints:
- POST /chat - createChat()
- Response: id: string, messages: { role: string, content: string }[]
- DELETE /chat - clearChats()
- (no request or response)
Files: index.html and style.css
Next, implement some new UI elements for your new features. Implement the following in index.html and style them in style.css:
- A "container" for the chat list (i.e. a sidebar on the left)
- The styles for the chat items to be housed in the above container
- A "Create New Chat" button
- A "Delete All Chats" button
Files: script.tsFinally, we'll implement the functionality for switching between multiple chats. Implement the following in script.ts:
- renderChatList()
- Add renderChatList() to switchToChat(), so that the chat list is updated when switching chats.
- An event listener for the "Create New Chat" button
- An event listener for the "Delete All Chats" button
At the end of this section, you should have a fully functional chatbot that can handle multiple chats. You should be able to create new chats, switch between them, and delete all chats. Each chat should have its own separate chat history that is saved to the database through the chat API.
Submission
README
Answer the provided reflection questions within the starter code README file. In this reflection, you will also indicite 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. Make sure to include everything, including the contents of both src and dist directories, and all ts and compiled js files. 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.