├── .eslintrc.json ├── .gitignore ├── README.md ├── next.config.mjs ├── package-lock.json ├── package.json ├── postcss.config.js ├── public ├── next.svg ├── run-demo-cta.png ├── screenshot.png └── vercel.svg ├── src ├── app │ ├── api │ │ └── copilotkit │ │ │ └── route.ts │ ├── favicon.ico │ ├── globals.css │ ├── layout.tsx │ └── page.tsx ├── components │ └── TodoItem.tsx └── types │ └── todo.ts ├── tailwind.config.ts └── tsconfig.json /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is a demo that showcases using CopilotKit to build a simple Todo app. 2 | 3 | ## Run the live demo 4 | 5 | Want to see CopilotKit in action? Click the button below to try the live demo. 6 | 7 | 8 | Todo Demo Screenshot 9 | 10 | 11 | 12 | Run the live demo 13 | 14 | 15 | ## Deploy with Vercel 16 | 17 | To deploy with Vercel, click the button below: 18 | 19 | [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FCopilotKit%2Fdemo-todo&env=NEXT_PUBLIC_COPILOT_CLOUD_API_KEY&project-name=copilotkit-demo-todo&repository-name=copilotkit-demo-todo) 20 | 21 | ## How to Build: a To-Do list app with an embedded AI copilot 22 | 23 | Learn how to create a To-Do list app with an embedded AI copilot. This tutorial will guide you through the process step-by-step. 24 | 25 | Tutorial: [How to Build: a To-Do list app with an embedded AI copilot](https://dev.to/copilotkit/how-to-build-an-ai-powered-to-do-list-nextjs-gpt4-copilotkit-20i4) 26 | 27 | ## Add your OpenAI API key 28 | 29 | Add your environment variables to `.env.local` in the root of the project. 30 | 31 | ``` 32 | OPENAI_API_KEY=your-api-key 33 | ``` 34 | 35 | ## Install dependencies 36 | 37 | ```bash 38 | npm install 39 | ``` 40 | 41 | ## Run the development server 42 | 43 | ```bash 44 | npm run dev 45 | ``` 46 | 47 | ## Open the demo 48 | 49 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 50 | 51 | ## The Copilot-Specific parts of the code: 52 | 53 | 1. Notice `` and `` in `page.tsx` 54 | 55 | 2. Notice `useCopilotReadable` in `page.tsx` 56 | 57 | 3. Notice the 2 `useCopilotAction` in `page.tsx` 58 | -------------------------------------------------------------------------------- /next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = {}; 3 | 4 | export default nextConfig; 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "todo", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@copilotkit/runtime": "1.0.0-beta.2", 13 | "@copilotkit/react-core": "1.0.0-beta.2", 14 | "@copilotkit/react-textarea": "1.0.0-beta.2", 15 | "@copilotkit/react-ui": "1.0.0-beta.2", 16 | "@copilotkit/shared": "1.0.0-beta.2", 17 | "nanoid": "^5.0.6", 18 | "next": "14.1.3", 19 | "react": "^18", 20 | "react-dom": "^18" 21 | }, 22 | "devDependencies": { 23 | "@types/node": "^20", 24 | "@types/react": "^18", 25 | "@types/react-dom": "^18", 26 | "autoprefixer": "^10.0.1", 27 | "eslint": "^8", 28 | "eslint-config-next": "14.1.3", 29 | "postcss": "^8", 30 | "tailwindcss": "^3.3.0", 31 | "typescript": "^5" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/run-demo-cta.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CopilotKit/demo-todo/da61e571c8c569e2c585ac548eb5b8129e3f17a6/public/run-demo-cta.png -------------------------------------------------------------------------------- /public/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CopilotKit/demo-todo/da61e571c8c569e2c585ac548eb5b8129e3f17a6/public/screenshot.png -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/api/copilotkit/route.ts: -------------------------------------------------------------------------------- 1 | import { NextRequest } from "next/server"; 2 | import { 3 | CopilotRuntime, 4 | OpenAIAdapter, 5 | copilotRuntimeNextJSAppRouterEndpoint, 6 | } from "@copilotkit/runtime"; 7 | 8 | export const POST = async (req: NextRequest) => { 9 | const { handleRequest } = copilotRuntimeNextJSAppRouterEndpoint({ 10 | runtime: new CopilotRuntime(), 11 | serviceAdapter: new OpenAIAdapter(), 12 | endpoint: req.nextUrl.pathname, 13 | }); 14 | 15 | return handleRequest(req); 16 | }; 17 | -------------------------------------------------------------------------------- /src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CopilotKit/demo-todo/da61e571c8c569e2c585ac548eb5b8129e3f17a6/src/app/favicon.ico -------------------------------------------------------------------------------- /src/app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import { Inter } from "next/font/google"; 3 | import "./globals.css"; 4 | 5 | const inter = Inter({ subsets: ["latin"] }); 6 | 7 | export const metadata: Metadata = { 8 | title: "Create Next App", 9 | description: "Generated by create next app", 10 | }; 11 | 12 | export default function RootLayout({ 13 | children, 14 | }: Readonly<{ 15 | children: React.ReactNode; 16 | }>) { 17 | return ( 18 | 19 | {children} 20 | 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /src/app/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { TodoItem } from "@/components/TodoItem"; 4 | import { nanoid } from "nanoid"; 5 | import { useState } from "react"; 6 | import { Todo } from "../types/todo"; 7 | 8 | /** 9 | * 10 | * 1) CopilotKit Integration 11 | * 12 | **/ 13 | 14 | import { 15 | CopilotKit, 16 | useCopilotAction, 17 | useCopilotReadable, 18 | } from "@copilotkit/react-core"; 19 | import { CopilotPopup } from "@copilotkit/react-ui"; 20 | import "@copilotkit/react-ui/styles.css"; 21 | 22 | export default function Home() { 23 | return ( 24 |
25 |

Hello CopilotKit 🪁

26 |

Todo List Example

27 | 28 | {/** 29 | * 30 | * 2) Wrap the TodoList component with CopilotKit 31 | * 32 | **/} 33 | 34 | 39 | 40 | 41 | {/** 42 | * 43 | * 3) Add the CopilotPopup component to get the chat 44 | * 45 | */} 46 | 47 | 59 | 60 |
61 | ); 62 | } 63 | 64 | const TodoList: React.FC = () => { 65 | const [todos, setTodos] = useState([]); 66 | const [input, setInput] = useState(""); 67 | 68 | /** 69 | * 70 | * 4) make the users todo list available with useCopilotReadable 71 | * 72 | **/ 73 | useCopilotReadable({ 74 | description: "The user's todo list.", 75 | value: todos, 76 | }); 77 | 78 | /** 79 | * 80 | * 5) Add the useCopilotAction to enable the copilot to interact with the todo list 81 | * 82 | **/ 83 | 84 | useCopilotAction({ 85 | name: "updateTodoList", 86 | description: "Update the users todo list", 87 | parameters: [ 88 | { 89 | name: "items", 90 | type: "object[]", 91 | description: "The new and updated todo list items.", 92 | attributes: [ 93 | { 94 | name: "id", 95 | type: "string", 96 | description: 97 | "The id of the todo item. When creating a new todo item, just make up a new id.", 98 | }, 99 | { 100 | name: "text", 101 | type: "string", 102 | description: "The text of the todo item.", 103 | }, 104 | { 105 | name: "isCompleted", 106 | type: "boolean", 107 | description: "The completion status of the todo item.", 108 | }, 109 | { 110 | name: "assignedTo", 111 | type: "string", 112 | description: 113 | "The person assigned to the todo item. If you don't know, assign it to 'YOU'.", 114 | required: true, 115 | }, 116 | ], 117 | }, 118 | ], 119 | handler: ({ items }) => { 120 | console.log(items); 121 | const newTodos = [...todos]; 122 | for (const item of items) { 123 | const existingItemIndex = newTodos.findIndex( 124 | (todo) => todo.id === item.id 125 | ); 126 | if (existingItemIndex !== -1) { 127 | newTodos[existingItemIndex] = item; 128 | } else { 129 | newTodos.push(item); 130 | } 131 | } 132 | setTodos(newTodos); 133 | }, 134 | render: "Updating the todo list...", 135 | }); 136 | 137 | /** 138 | * 139 | * 5) Add another useCopilotAction to enable the copilot to delete a todo item 140 | * 141 | **/ 142 | useCopilotAction({ 143 | name: "deleteTodo", 144 | description: "Delete a todo item", 145 | parameters: [ 146 | { 147 | name: "id", 148 | type: "string", 149 | description: "The id of the todo item to delete.", 150 | }, 151 | ], 152 | handler: ({ id }) => { 153 | setTodos(todos.filter((todo) => todo.id !== id)); 154 | }, 155 | render: "Deleting a todo item...", 156 | }); 157 | 158 | const addTodo = () => { 159 | if (input.trim() !== "") { 160 | // Check if input is not just whitespace 161 | const newTodo: Todo = { 162 | id: nanoid(), 163 | text: input.trim(), // Trim whitespace 164 | isCompleted: false, 165 | }; 166 | setTodos([...todos, newTodo]); 167 | setInput(""); // Reset input field 168 | } 169 | }; 170 | 171 | const handleKeyPress = (e: React.KeyboardEvent) => { 172 | if (e.key === "Enter") { 173 | addTodo(); 174 | } 175 | }; 176 | 177 | const toggleComplete = (id: string) => { 178 | setTodos( 179 | todos.map((todo) => 180 | todo.id === id ? { ...todo, isCompleted: !todo.isCompleted } : todo 181 | ) 182 | ); 183 | }; 184 | 185 | const deleteTodo = (id: string) => { 186 | setTodos(todos.filter((todo) => todo.id !== id)); 187 | }; 188 | 189 | const assignPerson = (id: string, person: string | null) => { 190 | setTodos( 191 | todos.map((todo) => 192 | todo.id === id 193 | ? { ...todo, assignedTo: person ? person : undefined } 194 | : todo 195 | ) 196 | ); 197 | }; 198 | 199 | return ( 200 |
201 |
202 | setInput(e.target.value)} 206 | onKeyDown={handleKeyPress} // Add this to handle the Enter key press 207 | /> 208 | 214 |
215 | {todos.length > 0 && ( 216 |
217 | {todos.map((todo, index) => ( 218 | 226 | ))} 227 |
228 | )} 229 |
230 | ); 231 | }; 232 | -------------------------------------------------------------------------------- /src/components/TodoItem.tsx: -------------------------------------------------------------------------------- 1 | import { Todo } from "@/types/todo"; 2 | 3 | interface TodoItemProps { 4 | todo: Todo; 5 | toggleComplete: (id: string) => void; 6 | deleteTodo: (id: string) => void; 7 | assignPerson: (id: string, person: string | null) => void; 8 | hasBorder?: boolean; 9 | } 10 | 11 | export const TodoItem: React.FC = ({ 12 | todo, 13 | toggleComplete, 14 | deleteTodo, 15 | assignPerson, 16 | hasBorder, 17 | }) => { 18 | return ( 19 |
25 |
26 | toggleComplete(todo.id)} 31 | /> 32 | 37 | {todo.assignedTo && ( 38 | 39 | {todo.assignedTo} 40 | 41 | )} 42 | {todo.text} 43 | 44 |
45 |
46 | 65 | 87 |
88 |
89 | ); 90 | }; 91 | -------------------------------------------------------------------------------- /src/types/todo.ts: -------------------------------------------------------------------------------- 1 | export interface Todo { 2 | id: string; 3 | text: string; 4 | isCompleted: boolean; 5 | assignedTo?: string; 6 | } 7 | -------------------------------------------------------------------------------- /tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from "tailwindcss"; 2 | 3 | const config: Config = { 4 | content: [ 5 | "./src/pages/**/*.{js,ts,jsx,tsx,mdx}", 6 | "./src/components/**/*.{js,ts,jsx,tsx,mdx}", 7 | "./src/app/**/*.{js,ts,jsx,tsx,mdx}", 8 | ], 9 | theme: { 10 | extend: { 11 | backgroundImage: { 12 | "gradient-radial": "radial-gradient(var(--tw-gradient-stops))", 13 | "gradient-conic": 14 | "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))", 15 | }, 16 | }, 17 | }, 18 | plugins: [], 19 | }; 20 | export default config; 21 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["dom", "dom.iterable", "esnext"], 4 | "allowJs": true, 5 | "skipLibCheck": true, 6 | "strict": true, 7 | "noEmit": true, 8 | "esModuleInterop": true, 9 | "module": "esnext", 10 | "moduleResolution": "bundler", 11 | "resolveJsonModule": true, 12 | "isolatedModules": true, 13 | "jsx": "preserve", 14 | "incremental": true, 15 | "plugins": [ 16 | { 17 | "name": "next" 18 | } 19 | ], 20 | "paths": { 21 | "@/*": ["./src/*"] 22 | } 23 | }, 24 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 25 | "exclude": ["node_modules"] 26 | } 27 | --------------------------------------------------------------------------------