TL;DR
By the end of this article, you'll learn how to:
Integrate an AI copilot into your Next.js application by building a scheduling app with Cal.com.
Develop and manage a custom scheduler that enhances user experience across your application.
Plus, you'll have a cool project to showcase in your portfolio!
What is an AI Copilot?
An AI Copilot, is an in-app AI assistant that helps users answer questions and take actions inside an application. It brings LLM intelligence right into your application.
The following are some of the potential use cases for AI copilots:
- ChatBot: A contextual in-app messaging facility for the application that can help the users with queries and perform some specific actions in the application.
- AI Autocomplete: Smart input controls that offer suggestions related to the context as the user is typing in a message.
- CoAgents: Artificially intelligent helpers who can work hands-on with your app and your users, capable of performing complex tasks on their own.
CopilotKit is the leading, most robust, and easiest to use open-source framework for building in-app AI copilots. You can have a fully custom AI copilot running in your app within minutes.
Prerequisites
To follow along with this tutorial, you need to have the following.
- Knowledge of Next.js and Typescript.
- NPM and Node.js are installed on your computer.
- CopilotKit account, an open-source copilot framework for building custom AI chatbots, in-app AI agents, and text areas.
- Cal.com account, follow this guide to create one if you have not.
Here is a demo of what weβll be building throughout this tutorial:
Creating a New Next.js Project
Next.js is one of the most widely used frameworks for creating scalable and high-performance web applications. It builds upon React's core features and offers server-side rendering, static site generation, and simpler routing interfaces which help to create fast and production-ready websites with good SEO.
To allow us to focus on learning how to integrate Cal.com and Copilot into your room booking Next.js app, I have created the components and UI interface for this app. Run the command below to clone and run the app:
git clone https://github.com/icode247/copilot-booking-app
&& cd copilot-booking-app && npm install && npm run dev
Integrating Cal.com API for Booking
Now define those routes. In those routes, weβll make API requests to the Cal.com APU to create new bookings or cancel bookings. In your API folder, create a new bookings/route.ts
file and add the code below:
import { NextRequest, NextResponse } from 'next/server';
import axios from 'axios';
export async function POST(request: NextRequest) {
const { room, start, end, time, email } = await request.json();
try {
const [startDate] = start.split("T");
const startDateTime = new Date(`${startDate}T${time}:00`);
const formattedStart = startDateTime.toISOString();
const endDateTime = new Date(startDateTime);
endDateTime.setHours(endDateTime.getHours() + 1);
const formattedEnd = endDateTime.toISOString();
// Step 1: Create event-type
const eventTypePayload = {
length: 60,
slug: `booking-${Date.now()}-${room.toLowerCase().replace(/\s+/g, "-")}`,
title: `Booking for ${room}`,
description: `Booking for ${room} from ${formattedStart} to ${formattedEnd}`,
locations: [{ type: "inPerson", address: room }],
disableGuests: false,
slotInterval: 0,
minimumBookingNotice: 0,
beforeEventBuffer: 0,
afterEventBuffer: 0,
};
const eventTypeResponse = await axios.post(
`${CALCOM_API_BASE_URL}/event-types`,
eventTypePayload,
{
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.NEXT_PUBLIC_CALCOM_API_KEY}`,
},
}
);
const eventTypeId = eventTypeResponse.data.data.id;
// Step 2: Create booking
const bookingPayload = {
end: formattedEnd,
start: formattedStart,
eventTypeId,
eventTypeSlug: eventTypePayload.slug,
timeZone: "Africa/Lagos",
user: [email],
language: "en",
bookingUid: `booking-${Date.now()}`,
metadata: {},
responses: {
name: email.split("@")[0],
email,
guests: [],
notes: `Booking for ${room} from ${formattedStart} to ${formattedEnd}`,
},
};
const bookingResponse = await axios.post(
`${process.env.CALCOM_API_BASE_URL}/bookings`,
bookingPayload,
{
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.NEXT_PUBLIC_CALCOM_API_KEY}`,
},
}
);
return NextResponse.json({ booking: bookingResponse.data }, { status: 201 });
} catch (error) {
console.error("Error response status:", error.response?.status);
return NextResponse.json(
{
error: "Failed to create booking",
details: error.response?.data || error.message,
},
{ status: 500 }
);
}
}
The above code will create an event type in Cal.com with the booking details, including the room and time information. Then, using the ID of the newly created event type, it proceeds to create an actual booking on Cal.com. The code handles date formatting constructs the necessary payloads for both API calls, and uses axios to send requests to the Cal.com API.
Create a new cancel/route.ts
file and add the code below:
import { NextRequest, NextResponse } from "next/server";
import axios from "axios";
export async function POST(
request: NextRequest,
{ params }: { params: { id: string } }
) {
try {
const body = await request.json();
const bookingId = params.id;
const cancelledBookingResponse = await axios.post(
`${process.env.CALCOM_API_BASE_URL}/bookings/${bookingId}/cancel`,
body,
{
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.NEXT_PUBLIC_CALCOM_API_KEY}`,
},
}
);
return NextResponse.json(
{ booking: cancelledBookingResponse.data },
{ status: 201 }
);
} catch (error) {
console.error("Error cancelling booking:", error);
return NextResponse.json(
{ error: "Failed to cancel booking" },
{ status: 500 }
);
}
}
The above code implements an API route handler for canceling bookings using the Cal.com API.
Now update your app/page.tsx
file to use the RoomBookingProvider
we created:
// app/page.tsx
'use client';
import Layout from "@/components/Layout";
import RoomBookingCard from "@/components/RoomBookingCard";
import { RoomBookingProvider } from "@/lib/hooks/use-room-booking";
export default function Home() {
return (
<RoomBookingProvider>
<Layout>
<RoomBookingCard />
</Layout>
</RoomBookingProvider>
);
}
Then, update your RoomBookingCard.tsx file to use the useRoomBooking hook to allow users to book rooms.
// components/RoomBookingCard.tsx
"use client";
import { FC, useEffect, useState } from "react";
import RoomList from "./RoomList";
import Notification from "./Notification";
import BookingForm from "./BookingForm";
import { useRoomBooking } from "@/lib/hooks/use-room-booking";
const RoomBookingCard: FC = () => {
const [selectedRoom, setSelectedRoom] = useState<string | null>(null);
const [notification, setNotification] = useState<string | null>(null);
const [rooms, setRooms] = useState([]);
const { addBooking } = useRoomBooking();
useEffect(() => {
async function fetchRooms() {
const response = await fetch("/api/rooms");
const data = await response.json();
setRooms(data);
}
fetchRooms();
}, []);
const handleRoomSelect = (room: string) => {
setSelectedRoom(room);
};
const handleBookingConfirm = async (
sdate: string,
time: string,
edate: string,
email: string
) => {
try {
if (selectedRoom) {
await addBooking(selectedRoom, sdate, time, edate, email);
setNotification("Booking confirmed!");
setSelectedRoom(null);
}
} catch (error) {
setNotification(error.message);
}
};
return (
<div>
{notification && (
<Notification
message={notification}
onClose={() => setNotification(null)}
/>
)}
{selectedRoom ? (
<BookingForm room={selectedRoom} onConfirm={handleBookingConfirm} />
) : (
<RoomList rooms={rooms} onSelectRoom={handleRoomSelect} />
)}
</div>
);
};
export default RoomBookingCard;
Now you can select any room, enter your details, and book it.
Now letβs make the application more interesting by adding AI copilot using CopilotKit. First, visit theΒ OpenAI Developers' PlatformΒ and create a new secret key.
Integrating Copilotkit for AI-Driven Interaction
Setting Up Copilotkit
Copilotkit offers two options for integration:
- Copilot Cloud: Which easiest way to get started with CopilotKit and
- Self Hosting: This sets up an instance of Copilot Runtime on your own infrastructure.
For this tutorial, weβll use the Copilot Cloud. Click here to get your Copilot Cloud API key for free. Then, replaceΒ <your-public-api-key>
Β with your actual Open AI key to generate a Copilot public key.
Add your API key securely to your .env.local
file:
PUBLIC_API_KEY=<your-public-api-key>
Update your app/page.tsx
file to wrap theΒ <CopilotKit>
Β provider in your app:
// app/page.tsx
"use client";
import Layout from "@/components/Layout";
import RoomBookingCard from "@/components/RoomBookingCard";
import { RoomBookingProvider } from "@/lib/hooks/use-room-booking";
import { CopilotKit } from "@copilotkit/react-core";
import { CopilotPopup } from "@copilotkit/react-ui";
import "@copilotkit/react-ui/styles.css";
export default function Home() {
return (
<CopilotKit publicApiKey={process.env.PUBLIC_API_KEY}>
<RoomBookingProvider>
<Layout>
<RoomBookingCard />
</Layout>
</RoomBookingProvider>
<CopilotPopup />
</CopilotKit>
);
}
In the above code, we imported the <CopilotPopup />
component and wrapped the page with the <CopilotKit>
provider, both from @copilotkit/react-ui
. We also imported optional built-in styles from the same package to enhance the UI.
Now youβll see the chat popup in the bottom right corner of the page.
For Copilot to provide us with the right answers and perform tasks, we need to make it aware of our app state using theΒ useCopilotReadable
Β hook. Update the code in your lib/hooks/use-room-booking.tsx
file to provide copilot with the state of our bookings and rooms:
// ...
import { useCopilotReadable } from "@copilotkit/react-core";
// ...
export function RoomBookingProvider({ children }: { children: ReactNode }) {
// ...
const [rooms, setRooms] = useState([]);
// ...
useCopilotReadable({
description: "The state of the booking list",
value: JSON.stringify(bookings),
});
useCopilotReadable({
description: "The state of the rooms list",
value: JSON.stringify(rooms),
});
//...
}
Now you can test the Copilot by asking how many bookings you have.
Handling Booking and Cancellations
Letβs enable the Copilot to perform more tasks like creating bookings, canceling bookings, and having information about available rooms. To achieve that, weβll use the Copilot useCopilotAction
Β hook. This hook allows you to make actions available to the copilot.
Update your lib/hooks/use-room-booking.tsx
file and add the following actions:
// ...
export function RoomBookingProvider({ children }: { children: ReactNode }) {
// ...
useCopilotAction({
name: "addBooking",
description: "Adds a booking to the list",
parameters: [
{
name: "room",
type: "string",
description: "The room to be booked",
required: true,
},
{
name: "date",
type: "string",
description: "The date of the booking",
required: true,
},
{
name: "time",
type: "string",
description: "The time of the booking",
required: true,
},
{
name: "end",
type: "string",
description: "The checkout time for booking",
required: true,
},
{
name: "email",
type: "string",
description: "Email address of the user booking the room",
required: true,
},
],
handler: async ({ room, date, time, end, email }) => {
await addBooking(room, date, time, end, email);
},
});
useCopilotAction({
name: "cancelBooking",
description: "Cancels a booking from the list",
parameters: [
{ name: "room", type: "string", description: "The room of the booking to be cancelled", required: true },
{ name: "date", type: "string", description: "The date of the booking to be cancelled", required: true },
{ name: "reason", type: "string", description: "The reason for cancellation", required: true },
],
handler: async ({ room, date, reason }) => {
await cancelBooking(room, date, reason);
},
});
useCopilotAction({
name: "fetchAvailableRooms",
description: "Fetches available rooms for a given date",
parameters: [
{
name: "date",
type: "string",
description: "The date to check room availability",
required: true,
},
],
handler: async ({ date }) => {
const availableRooms = await fetchAvailableRooms(date);
setRooms(availableRooms);
},
});
useCopilotAction({
name: "setBookingStatus",
description: "Sets the status of a booking",
parameters: [
{
name: "id",
type: "number",
description: "The ID of the booking",
required: true,
},
{
name: "status",
type: "string",
description: "The status of the booking",
enum: Object.values(BookingStatus),
required: true,
},
],
handler: ({ id, status }) => {
setBookingStatus(id, status);
},
});
//...
}
In the above code snippet, we used the useCopilotAction
Β hook to create actions to create bookings, addBooking
, cancelBooking
, fetchAvailableRooms
, and setBookingStatus
. Each of these actions takes an object with the following properties.
name
stands for the name of the action.
description
refers to characterizing the action. This is very important because it would allow our copilot to pick the right action.
parameters
is the set of parameters that the action takes which is an array. It conforms to the JSON Schema format.
handler
is simply a function that will be executed when the action is executed.
Now you can use the copilot to perform advanced actions like seeing a list of available rooms, booking, and canceling bookings. Feel free to chat with the copilot to perform other actions.
Excellent, you now have an AI-powered scheduling app to add to your portfolio.
Wrapping It Up
CopilotKit is an innovative and convenient tool for adding AI capabilities to your products. Whether it is creating a voice interactive system or deploying automation for challenging procedures, its adaptability will satisfy the needs of any software developer who wants to incorporate AI into his work.
If you are developing products with a strong emphasis on AI or are looking for ways to add it to the applications you have made, then you should definitely consider using CopilotKit. The interface is simple and can be configured quickly which translates into reduced time as well as improved focus globally.
Your curiosity about tools such as CopilotKit helps you keep up with the rapidly changing landscape of the software industry.
Join CopilotKit's amazing community of developers and come build with us!
Don't forget to check out our GitHub and show your love by giving us a star βοΈ
Top comments (52)
Dad-bod Pikachu is π₯
π
Hahaπ₯π₯
Awesome article!
Thanks, Nevo!
I use Cal.com for my scheduling and this is a great use case to boost it with AI.
Nicely done @arindam_1729!
Glad you liked it, Nathan!
With CopilotKit, We can make so many cool projects that will help us save our time!
I need to look further into Cal.com, but as far as building an AI scheduling app, are time zones automatically handled?
I'm curious because I'm building a SaaS, and I heard about CopilotKit a few days ago and was intrigued. Just now looking into them.
Hey @johncook1122, thanks for checking out CopilotKit. Getting started is extremely easy and depending on which kind of AI interface you want to add, we have a suite of options.
docs.copilotkit.ai/
Hey John,
Yes, They do handle that.
They have a prop
autoUpdateTimezone
in the Cal Provider that automatically updates the userβs timezone.You can check this for reference: Cal.com Docs
Damn! Dope content!
Glad you liked it!
Amazing article π
Glad you liked it!
Nice!
I didn't know you could hook into Cal.com's API. This is really cool!
Agreed, they have pretty good docs to get you started - cal.com/docs/api-reference/v2/intr...
Glad you found it interesting.
Cal.com's API is pretty interesting, and combining it with Copilotkit takes it to another level.
Thanks dude for such great article! Simple and easy to follow through. My team and I are currently working on a scheduling app for institutions in the academic world. We are integrating GOOGLE Calendar API for scheduling. How can we leverage your approach to that?
You can use Cal.com API, It will give you many features out of the box, like automatic time changes based on Timezones and many others.
You can check them here: cal.com/docs/api-reference/v2/intr...
Thanks @arindam_1729 for your prompt response! What's the pricing range for cal.com API calls?
Very impressive article!
Thanks for checking out Amitesh!
Nicely written! π₯
Great tutorial
Glad you liked it David!