How to Build an AI-powered To-Do list (Next.js, GPT4, & CopilotKit)

TL;DR

A to-do list is a classic project for every dev. In today’s world it is great to learn how to build with AI and to have some AI projects in your portfolio.

Today, I will go through step by step of how to build a to-do list with an embedded AI copilot for some AI magic 🪄.

Image description

We’ll cover how to:

  • Build the to-do list generator web app using Next.js, TypeScript, and Tailwind CSS.
  • Use CopilotKit to integrate AI functionalities into the to-do list generator.
  • Use AI chatbot to add lists, assign lists to someone, mark lists as completed, and delete lists.

Image description


CopilotKit: The framework for building in-app AI copilots

CopilotKit is an open-source AI copilot framework. We make it easy to integrate powerful AI into your React apps.

Build:

  • ChatBot: Context-aware in-app chatbots that can take actions in-app 💬
  • CopilotTextArea: AI-powered textFields with context-aware autocomplete & insertions 📝
  • Co-Agents: In-app AI agents that can interact with your app & users 🤖

Image description

Star CopilotKit ⭐️


Prerequisites

To fully understand this tutorial, you need to have a basic understanding of React or Next.js.

Here are the tools required to build the AI-powered to-do list generator:

  • Nanoid – a tiny, secure, URL-friendly, unique string ID generator for JavaScript.
  • OpenAI API – provides an API key that enables you to carry out various tasks using ChatGPT models.
  • CopilotKit – an open-source copilot framework for building custom AI chatbots, in-app AI agents, and text areas.

Project Set up and Package Installation

First, create a Next.js application by running the code snippet below in your terminal:

npx create-next-app@latest todolistgenerator
Enter fullscreen mode
Exit fullscreen mode

Select your preferred configuration settings. For this tutorial, we’ll be using TypeScript and Next.js App Router.

Image description

Next, install Nanoid package and its dependancies.

npm i nanoid
Enter fullscreen mode
Exit fullscreen mode

Finally, install the CopilotKit packages. These packages enable us to retrieve data from the React state and add AI copilot to the application.

 npm install @copilotkit/react-ui @copilotkit/react-textarea @copilotkit/react-core @copilotkit/backend @copilotkit/shared
Enter fullscreen mode
Exit fullscreen mode

Congratulations! You’re now ready to build an AI-powered to-do list generator.

Building The To-Do List Generator Frontend

In this section, I will walk you through the process of creating the to-do list generator frontend with static content to define the generator’s user interface.

To get started, go to /[root]/src/app in your code editor and create a folder called types. Inside the types folder, create a file named todo.ts and add the following code that defines a TypeScript interface called Todo.

The Todo interface defines an object structure where every todo item must have an id, text, and isCompleted status, while it may optionally have an assignedTo property.

export interface Todo { id: string; text: string; isCompleted: boolean; assignedTo?: string;
} 
Enter fullscreen mode
Exit fullscreen mode

Then go to /[root]/src/app in your code editor and create a folder called components. Inside the components folder, create three files named Header.tsx, TodoList.tsx and TodoItem.tsx .

In the Header.tsx file, add the following code that defines a functional component named Header that will render the generator’s navbar.

import Link from "next/link"; export default function Header() { return ( <> <header className="flex flex-wrap sm:justify-start sm:flex-nowrap z-50 w-full bg-gray-800 border-b border-gray-200 text-sm py-3 sm:py-0 "> <nav className="relative max-w-7xl w-full mx-auto px-4 sm:flex sm:items-center sm:justify-between sm:px-6 lg:px-8" aria-label="Global"> <div className="flex items-center justify-between"> <Link className="w-full flex-none text-xl text-white font-semibold p-6" href="/" aria-label="Brand"> To-Do List Generator </Link> </div> </nav> </header> </> );
} 
Enter fullscreen mode
Exit fullscreen mode

In the TodoItem.tsx file, add the following code that defines a React functional component called TodoItem. It uses TypeScript to ensure type safety and to define the props that the component accepts.

import { Todo } from "../types/todo"; // Importing the Todo type from a types file // Defining the interface for the props that the TodoItem component will receive
interface TodoItemProps { todo: Todo; // A single todo item toggleComplete: (id: string) => void; // Function to toggle the completion status of a todo deleteTodo: (id: string) => void; // Function to delete a todo assignPerson: (id: string, person: string | null) => void; // Function to assign a person to a todo hasBorder?: boolean; // Optional prop to determine if the item should have a border
} // Defining the TodoItem component as a functional component with the specified props
export const TodoItem: React.FC<TodoItemProps> = ({ todo, toggleComplete, deleteTodo, assignPerson, hasBorder,
}) => { return ( <div className={ "flex items-center justify-between px-4 py-2 group" + (hasBorder ? " border-b" : "") // Conditionally adding a border class if hasBorder is true }> <div className="flex items-center"> <input className="h-5 w-5 text-blue-500" type="checkbox" checked={todo.isCompleted} // Checkbox is checked if the todo is completed onChange={() => toggleComplete(todo.id)} // Toggle completion status on change /> <span className={`ml-2 text-sm text-white ${ todo.isCompleted ? "text-gray-500 line-through" : "text-gray-900" // Apply different styles if the todo is completed }`}> {todo.assignedTo && ( <span className="border rounded-md text-xs py-[2px] px-1 mr-2 border-purple-700 uppercase bg-purple-400 text-black font-medium"> {todo.assignedTo} {/* Display the assigned person's name if available */} </span> )} {todo.text} {/* Display the todo text */} </span> </div> <div> <button onClick={() => deleteTodo(todo.id)} // Delete the todo on button click className="text-red-500 opacity-0 group-hover:opacity-100 transition-opacity duration-200"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="w-5 h-5"> <path strokeLinecap="round" strokeLinejoin="round" d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0" /> </svg> </button> <button onClick={() => { const name = prompt("Assign person to this task:"); assignPerson(todo.id, name); }} className="ml-2 text-blue-500 opacity-0 group-hover:opacity-100 transition-opacity duration-200"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="w-5 h-5"> <path strokeLinecap="round" strokeLinejoin="round" d="M18 7.5v3m0 0v3m0-3h3m-3 0h-3m-2.25-4.125a3.375 3.375 0 1 1-6.75 0 3.375 3.375 0 0 1 6.75 0ZM3 19.235v-.11a6.375 6.375 0 0 1 12.75 0v.109A12.318 12.318 0 0 1 9.374 21c-2.331 0-4.512-.645-6.374-1.766Z" /> </svg> </button> </div> </div> );
}; 
Enter fullscreen mode
Exit fullscreen mode

In the TodoList.tsx file, add the following code that defines a React functional component named TodoList. This component is used to manage and display a list of to-do items.

"use client"; import { TodoItem } from "./TodoItem"; // Importing the TodoItem component
import { nanoid } from "nanoid"; // Importing the nanoid library for generating unique IDs
import { useState } from "react"; // Importing the useState hook from React
import { Todo } from "../types/todo"; // Importing the Todo type // Defining the TodoList component as a functional component
export const TodoList: React.FC = () => { // State to hold the list of todos const [todos, setTodos] = useState<Todo[]>([]); // State to hold the current input value const [input, setInput] = useState(""); // Function to add a new todo const addTodo = () => { if (input.trim() !== "") { // Check if the input is not empty const newTodo: Todo = { id: nanoid(), // Generate a unique ID for the new todo text: input.trim(), // Trim the input text isCompleted: false, // Set the initial completion status to false }; setTodos([...todos, newTodo]); // Add the new todo to the list setInput(""); // Clear the input field } }; // Function to handle key press events const handleKeyPress = (e: React.KeyboardEvent) => { if (e.key === "Enter") { // Check if the Enter key was pressed addTodo(); // Add the todo } }; // Function to toggle the completion status of a todo const toggleComplete = (id: string) => { setTodos( todos.map((todo) => todo.id === id ? { ...todo, isCompleted: !todo.isCompleted } : todo ) ); }; // Function to delete a todo const deleteTodo = (id: string) => { setTodos(todos.filter((todo) => todo.id !== id)); }; // Function to assign a person to a todo const assignPerson = (id: string, person: string | null) => { setTodos( todos.map((todo) => todo.id === id ? { ...todo, assignedTo: person ? person : undefined } : todo ) ); }; return ( <div> <div className="flex mb-4"> <input className="border rounded-md p-2 flex-1 mr-2" value={input} onChange={(e) => setInput(e.target.value)} onKeyDown={handleKeyPress} // Add this to handle the Enter key press /> <button className="bg-blue-500 rounded-md p-2 text-white" onClick={addTodo}> Add Todo </button> </div> {todos.length > 0 && ( // Check if there are any todos <div className="border rounded-lg"> {todos.map((todo, index) => ( <TodoItem key={todo.id} // Unique key for each todo item todo={todo} // Pass the todo object as a prop toggleComplete={toggleComplete} // Pass the toggleComplete function as a prop deleteTodo={deleteTodo} // Pass the deleteTodo function as a prop assignPerson={assignPerson} // Pass the assignPerson function as a prop hasBorder={index !== todos.length - 1} // Conditionally add a border to all but the last item /> ))} </div> )} </div> );
}; 
Enter fullscreen mode
Exit fullscreen mode

Next, go to /[root]/src/page.tsx file, and add the following code that imports TodoList and Header components and defines a functional component named Home.

import Header from "./components/Header";
import { TodoList } from "./components/TodoList"; export default function Home() { return ( <> <Header /> <div className="border rounded-md max-w-2xl mx-auto p-4 mt-4"> <h1 className="text-2xl text-white font-bold mb-4"> Create a to-do list </h1> <TodoList /> </div> </> );
} 
Enter fullscreen mode
Exit fullscreen mode

Next, remove the CSS code in the globals.css file and add the following CSS code.

@tailwind base;
@tailwind components;
@tailwind utilities; body { height: 100vh; background-color: rgb(16, 23, 42);
}
Enter fullscreen mode
Exit fullscreen mode

Finally, run the command npm run dev on the command line and then navigate to http://localhost:3000/.

Now you should view the To-Do List generator frontend on your browser, as shown below.

Image description

Integrating AI Functionalities To The Todo List Generator Using CopilotKit

In this section, you will learn how to add an AI copilot to the To-Do List generator to generate lists using CopilotKit.

CopilotKit offers both frontend and backend packages. They enable you to plug into the React states and process application data on the backend using AI agents.

First, let’s add the CopilotKit React components to the To-Do List generator frontend.

Adding CopilotKit to the To-Do List Generator Frontend

Here, I will walk you through the process of integrating the To-Do List generator with the CopilotKit frontend to facilitate lists generation.

To get started, use the code snippet below to import useCopilotReadable, and useCopilotAction, custom hooks at the top of the /[root]/src/app/components/TodoList.tsx file.

import { useCopilotAction, useCopilotReadable } from "@copilotkit/react-core";
Enter fullscreen mode
Exit fullscreen mode

Inside the TodoList function, below the state variables, add the following code that uses the useCopilotReadable hook to add the to-do lists that will be generated as context for the in-app chatbot. The hook makes the to-do lists readable to the copilot.

useCopilotReadable({ description: "The user's todo list.", value: todos, });
Enter fullscreen mode
Exit fullscreen mode

Below the code above, add the following code that uses the useCopilotAction hook to set up an action called updateTodoList which will enable the generation of to-do lists.

The action takes one parameter called items which enables the generation of todo lists and contains a handler function that generates todo lists based on a given prompt.

Inside the handler function, todos state is updated with the newly generated todo list, as shown below.

// Define the "updateTodoList" action using the useCopilotAction function useCopilotAction({ // Name of the action name: "updateTodoList", // Description of what the action does description: "Update the users todo list", // Define the parameters that the action accepts parameters: [ { // The name of the parameter name: "items", // The type of the parameter, an array of objects type: "object[]", // Description of the parameter description: "The new and updated todo list items.", // Define the attributes of each object in the items array attributes: [ { // The id of the todo item name: "id", type: "string", description: "The id of the todo item. When creating a new todo item, just make up a new id.", }, { // The text of the todo item name: "text", type: "string", description: "The text of the todo item.", }, { // The completion status of the todo item name: "isCompleted", type: "boolean", description: "The completion status of the todo item.", }, { // The person assigned to the todo item name: "assignedTo", type: "string", description: "The person assigned to the todo item. If you don't know, assign it to 'YOU'.", // This attribute is required required: true, }, ], }, ], // Define the handler function that executes when the action is invoked handler: ({ items }) => { // Log the items to the console for debugging purposes console.log(items); // Create a copy of the existing todos array const newTodos = [...todos]; // Iterate over each item in the items array for (const item of items) { // Find the index of the existing todo item with the same id const existingItemIndex = newTodos.findIndex( (todo) => todo.id === item.id ); // If an existing item is found, update it if (existingItemIndex !== -1) { newTodos[existingItemIndex] = item; } // If no existing item is found, add the new item to the newTodos array else { newTodos.push(item); } } // Update the state with the new todos array setTodos(newTodos); }, // Provide feedback or a message while the action is processing render: "Updating the todo list...", });
Enter fullscreen mode
Exit fullscreen mode

Below the code above, add the following code that uses the useCopilotAction hook to set up an action called deleteTodo which enables you to delete a to-do item.

The action takes a parameter called id which enables you to delete a todo item by id and contains a handler function that updates the todos state by filtering out the deleted todo item with the given id.

// Define the "deleteTodo" action using the useCopilotAction function useCopilotAction({ // Name of the action name: "deleteTodo", // Description of what the action does description: "Delete a todo item", // Define the parameters that the action accepts parameters: [ { // The name of the parameter name: "id", // The type of the parameter, a string type: "string", // Description of the parameter description: "The id of the todo item to delete.", }, ], // Define the handler function that executes when the action is invoked handler: ({ id }) => { // Update the state by filtering out the todo item with the given id setTodos(todos.filter((todo) => todo.id !== id)); }, // Provide feedback or a message while the action is processing render: "Deleting a todo item...", });
Enter fullscreen mode
Exit fullscreen mode

After that, go to /[root]/src/app/page.tsx file and import CopilotKit frontend packages and styles at the top using the code below.

import { CopilotKit } from "@copilotkit/react-core";
import { CopilotPopup } from "@copilotkit/react-ui";
import "@copilotkit/react-ui/styles.css";
Enter fullscreen mode
Exit fullscreen mode

Then use CopilotKit to wrap the CopilotPopup and TodoList components, as shown below. The CopilotKit component specifies the URL for CopilotKit’s backend endpoint (/api/copilotkit/) while the CopilotPopup renders the in-app chatbot that you can give prompts to generate todo lists.

export default function Home() { return ( <> <Header /> <div className="border rounded-md max-w-2xl mx-auto p-4 mt-4"> <h1 className="text-2xl text-white font-bold mb-4"> Create a to-do list </h1> <CopilotKit runtimeUrl="/api/copilotkit"> <TodoList /> <CopilotPopup instructions={ "Help the user manage a todo list. If the user provides a high level goal, " + "break it down into a few specific tasks and add them to the list" } defaultOpen={true} labels={{ title: "Todo List Copilot", initial: "Hi you! 👋 I can help you manage your todo list.", }} clickOutsideToClose={false} /> </CopilotKit> </div> </> );
}
Enter fullscreen mode
Exit fullscreen mode

After that, run the development server and navigate to http://localhost:3000. You should see that the in-app chatbot was integrated into the todo list generator.

Image description

Adding CopilotKit Backend to the Blog

Here, I will walk you through the process of integrating the todo lists generator with the CopilotKit backend that handles requests from frontend, and provides function calling and various LLM backends such as GPT.

To get started, create a file called .env.local in the root directory. Then add the environment variable below in the file that holds your ChatGPT  API keys.

OPENAI_API_KEY="Your ChatGPT API key”
Enter fullscreen mode
Exit fullscreen mode

To get the ChatGPT API key, navigate to https://platform.openai.com/api-keys.

Image description

After that, go to /[root]/src/app and create a folder called api. In the api folder, create a folder called copilotkit.

In the copilotkit folder, create a file called route.ts that contains code that sets up a backend functionality to process POST requests.

// Import the necessary modules from the "@copilotkit/backend" package
import { CopilotRuntime, OpenAIAdapter } from "@copilotkit/backend"; // Define an asynchronous function to handle POST requests
export async function POST(req: Request): Promise<Response> { // Create a new instance of CopilotRuntime const copilotKit = new CopilotRuntime({}); // Use the copilotKit to generate a response using the OpenAIAdapter // Pass the incoming request (req) and a new instance of OpenAIAdapter to the response method return copilotKit.response(req, new OpenAIAdapter());
} 
Enter fullscreen mode
Exit fullscreen mode

How To Generate Todo Lists

Now go to the in-app chatbot you integrated earlier and give it a prompt like, “I want to go to the gym to do a full body workout. add to the list workout routine I should follow”

Once it is done generating, you should see the list of full-body workout routine you should follow, as shown below.

Image description

You can assign the to-do list to someone by giving the chatbot a prompt like, “assign the to-do list to Doe.”

Image description

You can mark the to-do list as completed by giving the chatbot a prompt like, “mark the to-do list as completed.”

Image description

You can delete the to-do list by giving the chatbot a prompt like, “delete the todo list.”

Image description

Congratulations! You’ve completed the project for this tutorial.

Conclusion

CopilotKit is an incredible tool that allows you to add AI Copilots to your products within minutes. Whether you’re interested in AI chatbots and assistants or automating complex tasks, CopilotKit makes it easy.

If you need to build an AI product or integrate an AI tool into your software applications, you should consider CopilotKit.

You can find the source code for this tutorial on GitHub: https://github.com/TheGreatBonnie/AIpoweredToDoListGenerator


Discover more from Coursity

Subscribe to get the latest posts to your email.

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *