Project using CopilotKit with Next.js implementing chatbot and AutoSuggestion.
Saurav Kumar Mahato
Posted on May 6, 2024
WingPilot: Effortless Data Management With Suggestion
- Simplify your data management experience with WingPilot, a versatile tool designed to streamline interaction with employee datasets. WingPilot offers intuitive features for discussing insights, adding new employees, and removing outdated information, all through a user-friendly chat interface powered by Copilot Kit.
Features of this project
1. Efficient Employee Data Management: Simplify Your Data Handling
WingPilot streamlines the management of employee data, offering a user-friendly interface for effortless interaction. Whether it's adding, deleting, or discussing insights, WingPilot ensures a smooth experience, reducing complexity and enhancing productivity.
2. Smart Record Creation with Suggestions: Streamline Data Entry
WingPilot integrates intelligent suggestions into the record creation process, providing users with helpful prompts to populate employee records accurately. By leveraging smart suggestions, WingPilot minimizes manual effort and ensures data completeness and accuracy.
TechStack
- Next JS
- TypeScript
- Material UI (Tailwind)
- CopilotKit
Package manager used
yarn ( install if you don't have it )
Install yarn using npm (global)
npm install --global yarn
Different steps you have to follow are given below to make the project:
1. Setup next js project:
I have created a folder as WingPilot inside which I want my project. Navigate inside and type below:
yarn create next-app
Follow the step and check the default options given like below:
Once run the project using below:
yarn run dev
2. Setup Material-Tailwind UI from its docs for Next js or follow below step:
yarn add @material-tailwind/react
then, change tailwind.config.ts to below:
import withMT from "@material-tailwind/react/utils/withMT";
module.exports = withMT({
content: [
"./pages/**/*.{js,ts,jsx,tsx,mdx}",
"./components/**/*.{js,ts,jsx,tsx,mdx}",
"./app/**/*.{js,ts,jsx,tsx,mdx}",
],
theme: {
extend: {
backgroundImage: {
"gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
"gradient-conic":
"conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
},
},
},
plugins: [],
});
3. Now to setup project simply lets understand the hierarcy of the project and then copy the code provided for each component:
app directory might have been created. Now we are going to change and make it like below:
app
├── Home
│ └── page.tsx
├── SideBar.tsx
├── Table
│ ├── Form.tsx
│ └── page.tsx
├── api
│ └── copilotkit
│ └── route.ts
├── components
│ ├── Footer.tsx
│ └── Layout.tsx
├── favicon.ico
├── globals.css
├── layout.tsx
└── page.tsx
A. Home/page.tsx
"use client";
import React from "react";
import Layout from "../components/Layout";
const Home = () => {
return (
<Layout>
<div className="">
<h2 className="text-2xl text-center my-16">WingPilot</h2>
<p className="px-28 font-sans text-lg font-normal leading-relaxed text-inherit">
Welcome to WingPilot, where seamless interaction with your employee
dataset is made possible through our innovative Copilot Kit library.
Managing and understanding your employee data has never been easier –
whether it <i></i>s discussing insights, adding new employees, or removing
outdated information, our intuitive chat interface powered by Copilot
Kit streamlines the entire process. With the ability to engage in
natural language conversations, you can effortlessly navigate through
your dataset, gaining valuable insights and making informed decisions.
Experience the convenience of data management like never before with
WingPilot.
</p>
</div>
</Layout>
);
};
export default Home;
B. SideBar.tsx
import {
Card,
Typography,
List,
ListItem,
ListItemPrefix,
} from "@material-tailwind/react";
import { PresentationChartBarIcon } from "@heroicons/react/24/solid";
import Link from "next/link";
let Options: string[] = ["Home", "Table", "About", "Contact"];
export function DefaultSidebar() {
return (
<Card className="h-[calc(80vh-2rem)] w-half max-w-[20rem] p-4 shadow-xl shadow-blue-gray-900/5">
<div className="mb-2 p-4">
<Typography variant="h5" color="blue-gray">
WingPilot
</Typography>
</div>
<List>
{Options.map((option) => (
<Link href={option} key={option}>
<ListItem>
<ListItemPrefix>
<PresentationChartBarIcon className="h-5 w-5" />
</ListItemPrefix>
{option}
</ListItem>
</Link>
))}
</List>
</Card>
);
}
C. Table/Form.tsx
"use client";
import { CopilotTextarea } from "@copilotkit/react-textarea";
import { CopilotKit } from "@copilotkit/react-core";
import React, { useState, ChangeEvent } from "react";
import {
Button,
Dialog,
Card,
CardBody,
Typography,
Input,
CardFooter,
} from "@material-tailwind/react";
interface FormData {
id: string; // Added 'id' field
name: string;
job: string;
date: string;
qualities: string; // Added 'qualities' field
}
interface Props {
addEntry: (formData: FormData) => void;
}
const Form: React.FC<Props> = ({ addEntry }) => {
const [open, setOpen] = useState(false);
const [formData, setFormData] = useState<FormData>({
id: "", // Initialized 'id' field
name: "",
job: "",
date: "",
qualities: "", // Initialized 'qualities' field
});
const handleOpen = () => setOpen((cur) => !cur);
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target;
setFormData({ ...formData, [name]: value });
};
const handleSubmit = () => {
addEntry(formData);
setFormData({
id: "", // Reset 'id' field
name: "",
job: "",
date: "",
qualities: "", // Reset 'qualities' field
});
handleOpen();
};
return (
<>
<CopilotKit url="/api/copilotkit">
{/* Global state & copilot logic. Put this around the entire app */}
<Button onClick={handleOpen}>Add Entry</Button>
<Dialog
size="xs"
open={open}
handler={handleOpen}
className="bg-transparent shadow-none"
>
<Card className="mx-auto w-full max-w-[24rem]">
<CardBody className="flex flex-col gap-4">
<Typography variant="h4" color="blue-gray">
Add your entry
</Typography>
<Typography
className="mb-3 font-normal"
variant="paragraph"
color="gray"
>
fill the information below
</Typography>
<Typography className="-mb-2" variant="h6">
Name
</Typography>
<Input
label="Name"
size="lg"
name="name"
value={formData.name}
onChange={handleChange}
/>
<Typography className="-mb-2" variant="h6">
Job
</Typography>
<Input
label="Job"
size="lg"
name="job"
value={formData.job}
onChange={handleChange}
/>
<Typography className="-mb-2" variant="h6">
Date Joined
</Typography>
<Input
label="Date"
size="lg"
name="date"
value={formData.date}
onChange={handleChange}
/>
<Typography className="-mb-2" variant="h6">
Qualities
</Typography>
<CopilotTextarea
className="peer w-[42vh] h-[20vh] bg-transparent text-blue-gray-700 font-sans font-normal outline outline-0 focus:outline-0 disabled:bg-blue-gray-50 disabled:border-0 disabled:cursor-not-allowed transition-all placeholder-shown:border placeholder-shown:border-blue-gray-200 placeholder-shown:border-t-blue-gray-200 border focus:border-2 border-t-transparent focus:border-t-transparent placeholder:opacity-0 focus:placeholder:opacity-100 text-sm px-3 py-3 rounded-md border-blue-gray-200 focus:border-gray-900"
label="Qualities"
placeholder="Briefly describe qualities of the employee..."
value={formData.qualities}
onValueChange={(value: string) =>
setFormData({ ...formData, qualities: value })
}
autosuggestionsConfig={
{
textareaPurpose:
"Suggest something similar and best about the employee based on the information provided.",
chatApiConfigs: {
suggestionsApiConfig: {
forwardedParams: {
max_tokens: 20,
stop: [".", "?", "!"],
},
},
},
}
}
/>
</CardBody>
<CardFooter className="pt-0">
<Button variant="gradient" onClick={handleSubmit} fullWidth>
Add
</Button>
</CardFooter>
</Card>
</Dialog>
</CopilotKit>
</>
);
};
export default Form;
D. Table/page.tsx
'use client';
import React, { useState } from "react";
import { Card, Typography } from "@material-tailwind/react";
import Layout from "../components/Layout";
import Form from "./Form";
import {
useCopilotAction,
useCopilotReadable,
} from "@copilotkit/react-core";
interface TableRow {
id: string;
name: string;
job: string;
date: string;
qualities: string; // Added 'qualities' field
}
const TABLE_HEAD: string[] = ["Name", "Job", "Date Joined", "Qualities", ""];
const Table: React.FC = () => {
const [tableRows, setTableRows] = useState<TableRow[]>([
{
id: "1",
name: "John Michael",
job: "Manager",
date: "23/04/18",
qualities: "honesty, Disciplied, Punctual", // Example qualities
},
// Other initial data...
]);
// Implementing the useCopilotReadable to describe the user's employee list
useCopilotReadable({
description: "The user's employee list.",
value: tableRows,
});
const deleteEmployee = (id: string) => {
setTableRows((prevRows) => prevRows.filter((row) => row.id !== id));
};
// Implementing the useCopilotAction for deleting an employee from the list
useCopilotAction({
name: "deleteEmployee",
description: "Delete an employee",
parameters: [
{
name: "id",
type: "string",
description: "The id of the employee to delete.",
},
],
handler: ({ id }: { id: string }) => {
deleteEmployee(id);
},
render: "Deleting an employee...",
});
const addEntry = (entry: TableRow) => {
setTableRows([...tableRows, entry]);
};
// Implementing the useCopilotAction for updating the employee list
useCopilotAction({
name: "updateEmployeeList",
description: "Update the employee list",
parameters: [
{
name: "items",
type: "object[]",
description: "The new and updated employee list items.",
attributes: [
{
name: "id",
type: "string",
description: "The id of the employee.",
},
{
name: "name",
type: "string",
description: "The name of the employee.",
},
{
name: "job",
type: "string",
description: "The job title of the employee.",
},
{
name: "date",
type: "string",
description: "The date joined of the employee.",
},
{
name: "qualities",
type: "string",
description: "The qualities of the employee.",
},
],
},
],
handler: ({ items }: { items: TableRow[] }) => {
setTableRows(items);
},
render: "Updating the employee list...",
});
return (
<Layout>
<Card className="h-full w-full overflow-scroll">
<table className="w-full min-w-max table-auto text-left">
<thead>
<tr>
{TABLE_HEAD.map((head) => (
<th
key={head}
className="border-b border-blue-gray-100 bg-blue-gray-50 p-4"
>
<Typography
variant="small"
color="blue-gray"
className="font-normal leading-none opacity-70"
>
{head}
</Typography>
</th>
))}
</tr>
</thead>
<tbody>
{tableRows.map(({ id, name, job, date, qualities }, index) => {
const isLast = index === tableRows.length - 1;
const classes = isLast
? "p-4"
: "p-4 border-b border-blue-gray-50";
return (
<tr key={id}>
<td className={classes}>
<Typography
variant="small"
color="blue-gray"
className="font-normal"
>
{name}
</Typography>
</td>
<td className={`${classes} bg-blue-gray-50/50`}>
<Typography
variant="small"
color="blue-gray"
className="font-normal"
>
{job}
</Typography>
</td>
<td className={classes}>
<Typography
variant="small"
color="blue-gray"
className="font-normal"
>
{date}
</Typography>
</td>
<td className={`${classes} bg-blue-gray-50/50`}>
<Typography
variant="small"
color="blue-gray"
className="font-normal"
>
{qualities}
</Typography>
</td>
<td className={`${classes} bg-blue-gray-50/50`}>
<Typography
as="a"
href="#"
variant="small"
color="blue-gray"
className="font-medium"
onClick={() => deleteEmployee(id)}
>
Delete
</Typography>
</td>
</tr>
);
})}
</tbody>
</table>
<div className="mt-10">
<Form addEntry={addEntry} />
</div>
</Card>
</Layout>
);
}
export default Table;
E. (api/copilotkit/route.ts) Setup copilot backend
import { CopilotRuntime, OpenAIAdapter } from "@copilotkit/backend";
export async function POST(req: Request): Promise<Response> {
const copilotKit = new CopilotRuntime({});
return copilotKit.response(req, new OpenAIAdapter({ model: 'gpt-3.5-turbo' }));
}
F. make a component directory inside app and then make components/Footer.tsx
import { Typography } from "@material-tailwind/react";
const LINKS = [
{
title: "Product",
items: ["Table", "Home", "Solutions", "Tutorials"],
},
{
title: "Company",
items: ["About", "Contact", "Press", "News"],
},
{
title: "Resource",
items: ["Blog", "Newsletter", "Events", "Help center"],
},
];
const currentYear = new Date().getFullYear();
export function Footer() {
return (
<footer className="relative w-full">
<div className="mx-auto w-full max-w-7xl px-8">
<div className="grid grid-cols-1 justify-between gap-4 md:grid-cols-2">
<Typography variant="h5" className="mb-6">
WingPilot
</Typography>
<div className="grid grid-cols-3 justify-between gap-4">
{LINKS.map(({ title, items }) => (
<ul key={title}>
<Typography
variant="small"
color="blue-gray"
className="mb-3 font-medium opacity-40"
>
{title}
</Typography>
{items.map((link) => (
<li key={link}>
<Typography
as="a"
href="#"
color="gray"
className="py-1.5 font-normal transition-colors hover:text-blue-gray-900"
>
{link}
</Typography>
</li>
))}
</ul>
))}
</div>
</div>
<div className="mt-12 flex w-full flex-col items-center justify-center border-t border-blue-gray-50 py-4 md:flex-row md:justify-between">
<Typography
variant="small"
className="mb-4 text-center font-normal text-blue-gray-900 md:mb-0"
>
© {currentYear} <a href="#">WingPilot</a>. All
Rights Reserved.
</Typography>
<div className="flex gap-4 text-blue-gray-900 sm:justify-center">
<Typography as="a" href="#" className="opacity-80 transition-opacity hover:opacity-100">
<svg className="h-5 w-5" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path
fill-rule="evenodd"
d="M22 12c0-5.523-4.477-10-10-10S2 6.477 2 12c0 4.991 3.657 9.128 8.438 9.878v-6.987h-2.54V12h2.54V9.797c0-2.506 1.492-3.89 3.777-3.89 1.094 0 2.238.195 2.238.195v2.46h-1.26c-1.243 0-1.63.771-1.63 1.562V12h2.773l-.443 2.89h-2.33v6.988C18.343 21.128 22 16.991 22 12z"
clip-rule="evenodd"
/>
</svg>
</Typography>
<Typography as="a" href="#" className="opacity-80 transition-opacity hover:opacity-100">
<svg className="h-5 w-5" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path
fill-rule="evenodd"
d="M12.315 2c2.43 0 2.784.013 3.808.06 1.064.049 1.791.218 2.427.465a4.902 4.902 0 011.772 1.153 4.902 4.902 0 011.153 1.772c.247.636.416 1.363.465 2.427.048 1.067.06 1.407.06 4.123v.08c0 2.643-.012 2.987-.06 4.043-.049 1.064-.218 1.791-.465 2.427a4.902 4.902 0 01-1.153 1.772 4.902 4.902 0 01-1.772 1.153c-.636.247-1.363.416-2.427.465-1.067.048-1.407.06-4.123.06h-.08c-2.643 0-2.987-.012-4.043-.06-1.064-.049-1.791-.218-2.427-.465a4.902 4.902 0 01-1.772-1.153 4.902 4.902 0 01-1.153-1.772c-.247-.636-.416-1.363-.465-2.427-.047-1.024-.06-1.379-.06-3.808v-.63c0-2.43.013-2.784.06-3.808.049-1.064.218-1.791.465-2.427a4.902 4.902 0 011.153-1.772A4.902 4.902 0 015.45 2.525c.636-.247 1.363-.416 2.427-.465C8.901 2.013 9.256 2 11.685 2h.63zm-.081 1.802h-.468c-2.456 0-2.784.011-3.807.058-.975.045-1.504.207-1.857.344-.467.182-.8.398-1.15.748-.35.35-.566.683-.748 1.15-.137.353-.3.882-.344 1.857-.047 1.023-.058 1.351-.058 3.807v.468c0 2.456.011 2.784.058 3.807.045.975.207 1.504.344 1.857.182.466.399.8.748 1.15.35.35.683.566 1.15.748.353.137.882.3 1.857.344 1.054.048 1.37.058 4.041.058h.08c2.597 0 2.917-.01 3.96-.058.976-.045 1.505-.207 1.858-.344.466-.182.8-.398 1.15-.748.35-.35.566-.683.748-1.15.137-.353.3-.882.344-1.857.048-1.055.058-1.37.058-4.041v-.08c0-2.597-.01-2.917-.058-3.96-.045-.976-.207-1.505-.344-1.858a3.097 3.097 0 00-.748-1.15 3.098 3.098 0 00-1.15-.748c-.353-.137-.882-.3-1.857-.344-1.023-.047-1.351-.058-3.807-.058zM12 6.865a5.135 5.135 0 110 10.27 5.135 5.135 0 010-10.27zm0 1.802a3.333 3.333 0 100 6.666 3.333 3.333 0 000-6.666zm5.338-3.205a1.2 1.2 0 110 2.4 1.2 1.2 0 010-2.4z"
clip-rule="evenodd"
/>
</svg>
</Typography>
<Typography as="a" href="#" className="opacity-80 transition-opacity hover:opacity-100">
<svg className="h-5 w-5" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path d="M8.29 20.251c7.547 0 11.675-6.253 11.675-11.675 0-.178 0-.355-.012-.53A8.348 8.348 0 0022 5.92a8.19 8.19 0 01-2.357.646 4.118 4.118 0 001.804-2.27 8.224 8.224 0 01-2.605.996 4.107 4.107 0 00-6.993 3.743 11.65 11.65 0 01-8.457-4.287 4.106 4.106 0 001.27 5.477A4.072 4.072 0 012.8 9.713v.052a4.105 4.105 0 003.292 4.022 4.095 4.095 0 01-1.853.07 4.108 4.108 0 003.834 2.85A8.233 8.233 0 012 18.407a11.616 11.616 0 006.29 1.84" />
</svg>
</Typography>
<Typography as="a" href="#" className="opacity-80 transition-opacity hover:opacity-100">
<svg className="h-5 w-5" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path
fill-rule="evenodd"
d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z"
clip-rule="evenodd"
/>
</svg>
</Typography>
<Typography as="a" href="#" className="opacity-80 transition-opacity hover:opacity-100">
<svg className="h-5 w-5" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path
fill-rule="evenodd"
d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10c5.51 0 10-4.48 10-10S17.51 2 12 2zm6.605 4.61a8.502 8.502 0 011.93 5.314c-.281-.054-3.101-.629-5.943-.271-.065-.141-.12-.293-.184-.445a25.416 25.416 0 00-.564-1.236c3.145-1.28 4.577-3.124 4.761-3.362zM12 3.475c2.17 0 4.154.813 5.662 2.148-.152.216-1.443 1.941-4.48 3.08-1.399-2.57-2.95-4.675-3.189-5A8.687 8.687 0 0112 3.475zm-3.633.803a53.896 53.896 0 013.167 4.935c-3.992 1.063-7.517 1.04-7.896 1.04a8.581 8.581 0 014.729-5.975zM3.453 12.01v-.26c.37.01 4.512.065 8.775-1.215.25.477.477.965.694 1.453-.109.033-.228.065-.336.098-4.404 1.42-6.747 5.303-6.942 5.629a8.522 8.522 0 01-2.19-5.705zM12 20.547a8.482 8.482 0 01-5.239-1.8c.152-.315 1.888-3.656 6.703-5.337.022-.01.033-.01.054-.022a35.318 35.318 0 011.823 6.475 8.4 8.4 0 01-3.341.684zm4.761-1.465c-.086-.52-.542-3.015-1.659-6.084 2.679-.423 5.022.271 5.314.369a8.468 8.468 0 01-3.655 5.715z"
clip-rule="evenodd"
/>
</svg>
</Typography>
</div>
</div>
</div>
</footer>
);
}
G. components/Layout.tsx
import React, { PropsWithChildren } from "react";
import { DefaultSidebar } from "../SideBar";
import { Footer } from "./Footer";
const Layout = ({ children }: PropsWithChildren) => {
return (
<>
<div className="flex gap-4 my-4">
<DefaultSidebar />
{children}
</div>
<div className="m-20">
<Footer />
</div>
</>
);
};
export default Layout;
H. Update global.css to below:
@tailwind base;
@tailwind components;
@tailwind utilities;
I. layout.tsx
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import { CopilotKit } from "@copilotkit/react-core";
import { CopilotSidebar } from "@copilotkit/react-ui";
import "@copilotkit/react-ui/styles.css";
import { CopilotPopup } from "@copilotkit/react-ui";
import "./globals.css";
const inter = Inter({ subsets: ["latin"] });
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body className={inter.className}>
<CopilotKit url="/api/copilotkit">
<CopilotSidebar instructions={
"Help the user manage a employee list. If the user provides a high level info then " +
"break it down into different fields and add them to the employeelist"
}
defaultOpen={true}
labels={ {
title: "You Assistant WingPilot",
initial: "Hi you! 👋 How can I help you ?",
} }
clickOutsideToClose={false}>
{children}
</CopilotSidebar>
</CopilotKit>
</body>
</html>
);
}
J. page.tsx
"use client";
import { DefaultSidebar } from "./SideBar";
export default function Home() {
return (
<div className="my-4">
<DefaultSidebar />
</div>
);
}
K. Add your OpenAI API key in environment variable
Add your environment variables to .env.local
inside this directory WingPilot/wingpilot/.env.local
OPENAI_API_KEY=your-api-key
Finally run the project in local host and navigate to
yarn run dev
http://localhost:3000/
Demo Video
Github of Project :
Detail of the project can be seen through below link in Github. Clone the project and follow the steps given in README.md in github to run the project.
https://github.com/SauravKumarMahato/WingPilot
Conclusion
By following these steps, you can integrate Copilot Kit into your Next.js project, providing an intuitive chat interface for managing employee data. With Copilot Kit's powerful capabilities, navigating and interacting with your dataset becomes effortless, empowering you to make informed decisions effectively.
Posted on May 6, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
May 6, 2024
May 6, 2024