How to have AI onboard your users (Next.js, OpenAI, CopilotKit)
Tags: webdev, tutorial, programming, javascript
Published: 2024-04-25 09:38:35

## TL;DR

In this article, you'll learn how to build an AI-powered campaign manager application that allows you to create and analyze advertisement campaigns, enabling you to make the right decisions for your business.

We'll cover how to:

- build web applications with Next.js,
- integrate AI assistants into software applications with CopilotKit, and
- create action-specific AI copilots that handle various tasks within the application.
- Build a campaign manager


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9xqpz356qm79t90f1l87.gif)

---

## CopilotKit: The framework for building in-app AI copilots

CopilotKit is an [open-source AI copilot platform](https://github.com/CopilotKit/CopilotKit). 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 🤖

![https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx3us3vc140aun0dvrdof.gif](https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx3us3vc140aun0dvrdof.gif)

{% cta https://git.new/devtoarticle1 %} Star CopilotKit ⭐️ {% endcta %}

---

## Prerequisites

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

We'll also make use of the following:

- [Radix UI](https://www.radix-ui.com/) - for creating accessible UI components for the application.
- [OpenAI API Key](https://platform.openai.com/api-keys) - to enable us to perform various tasks using the GPT models.
- [CopilotKit](https://github.com/CopilotKit/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:

```bash
npx create-next-app campaign-manager
```

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


![Next.js App Installation](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xboujt60i6lpoaqgjyap.png)

Next, install [Heroicons](https://www.npmjs.com/package/@heroicons/react), [Radix UI](https://www.radix-ui.com/), and its primitive components into the project.

```bash
npm install @heroicons/react @radix-ui/react-avatar @radix-ui/react-dialog @radix-ui/react-dropdown-menu @radix-ui/react-icons @radix-ui/react-label @radix-ui/react-popover @radix-ui/react-select @radix-ui/react-slot @radix-ui/react-tabs
```

Also, install the [Recharts library](https://recharts.org/en-US) - a React library for creating interactive charts - and the following utility packages:

```bash
npm install recharts class-variance-authority clsx cmdk date-fns lodash react-day-picker tailwind-merge tailwindcss-animate
```

Finally, install the [CopilotKit packages](https://docs.copilotkit.ai/getting-started/quickstart-chatbot). These packages enable the AI copilot to retrieve data from the React state and make decisions within the application.

```bash
npm install @copilotkit/react-ui @copilotkit/react-textarea @copilotkit/react-core @copilotkit/backend
```

Congratulations! You're now ready to build the application.

---

## Building the Campaign Manager App with Next.js

In this section, I'll walk you through building the user interface for the campaign manager application.

First, let’s do some initial set up.

Create a `components` and `lib` folder within the `src` folder.

```bash
cd src
mkdir components lib
```

Within the **`lib`** folder, we'll declare the static types and default campaigns for the application. Therefore, create **`data.ts`** and **`types.ts`** files within the **`lib`** folder.

```bash
cd lib
touch data.ts type.ts
```

Copy the code snippet below into the `type.ts` file. It declares the campaign attributes and their data types.

```tsx
export interface Campaign {
	id: string;
	objective?:
		| "brand-awareness"
		| "lead-generation"
		| "sales-conversion"
		| "website-traffic"
		| "engagement";
	title: string;
	keywords: string;
	url: string;
	headline: string;
	description: string;
	budget: number;
	bidStrategy?: "manual-cpc" | "cpa" | "cpm";
	bidAmount?: number;
	segment?: string;
}
```

Create the default campaign lists for the application and copy them into the `data.ts` file.

```tsx
import { Campaign } from "./types";

export let DEFAULT_CAMPAIGNS: Campaign[] = [
	{
		id: "1",
		title: "CopilotKit",
		url: "https://www.copilotkit.ai",
		headline: "Copilot Kit - The Open-Source Copilot Framework",
		description:
			"Build, deploy, and operate fully custom AI Copilots. In-app AI chatbots, AI agents, AI Textareas and more.",
		budget: 10000,
		keywords: "AI, chatbot, open-source, copilot, framework",
	},
	{
		id: "2",
		title: "EcoHome Essentials",
		url: "https://www.ecohomeessentials.com",
		headline: "Sustainable Living Made Easy",
		description:
			"Discover our eco-friendly products that make sustainable living effortless. Shop now for green alternatives!",
		budget: 7500,
		keywords: "eco-friendly, sustainable, green products, home essentials",
	},
	{
		id: "3",
		title: "TechGear Solutions",
		url: "https://www.techgearsolutions.com",
		headline: "Innovative Tech for the Modern World",
		description:
			"Find the latest gadgets and tech solutions. Upgrade your life with smart technology today!",
		budget: 12000,
		keywords: "tech, gadgets, innovative, modern, electronics",
	},
	{
		id: "4",
		title: "Global Travels",
		url: "https://www.globaltravels.com",
		headline: "Travel the World with Confidence",
		description:
			"Experience bespoke travel packages tailored to your dreams. Luxury, adventure, relaxation—your journey starts here.",
		budget: 20000,
		keywords: "travel, luxury, adventure, tours, global",
	},
	{
		id: "5",
		title: "FreshFit Meals",
		url: "https://www.freshfitmeals.com",
		headline: "Healthy Eating, Simplified",
		description:
			"Nutritious, delicious meals delivered to your door. Eating well has never been easier or tastier.",
		budget: 5000,
		keywords: "healthy, meals, nutrition, delivery, fit",
	},
];
```

Since we are using Radix UI to create base UI components that can be easily customized with TailwindCSS, create a **`utils.ts`** file within the **`lib`** folder and copy the following code snippet into the file.

```tsx
//👉🏻 The lib folder now contains 3 files - data.ts, type.ts, util.ts
//👇🏻 Copy the code below into the "lib/util.ts" file.
import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";

export function cn(...inputs: ClassValue[]) {
	return twMerge(clsx(inputs));
}

export function randomId() {
	return Math.random().toString(36).substring(2, 15);
}
```

Navigate into the `components` folder and create three other folders within it.

```bash
cd components
mkdir app dashboard ui
```

The `components/app` folder will contain various components to be used within the application, while the dashboard folder contains the UI components for some elements.

The `ui` folder holds multiple UI elements created using Radix UI. Copy [these elements within the project repository](https://github.com/CopilotKit/campaign-manager-demo/tree/main/src/components/ui) into the folder.

Congratulations! The `ui` folder should contain the necessary UI elements. Now, we can use them to create the various components needed within the application.

### Creating the application UI components

Here, I'll walk you through creating the user interface for the application.

![Application User Interface](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9l4lvv4394gg033oatwq.png)

First, navigate to the **`app/page.tsx`** file and paste the following code snippet into it. This file renders an App component declared within the **`components/app`** folder.

```tsx
"use client";
import { App } from "@/components/app/App";

export default function DashboardPage() {
	return <App />;
}
```

Create the `App.tsx`, `CampaignForm.tsx`, `MainNav.tsx`, and `UserNav.tsx` files within the `components/app` folder.

```bash
cd components/app
touch App.tsx CampaignForm.tsx MainNav.tsx UserNav.tsx
```

Copy the code snippet below into the `App.tsx` file.

```tsx
"use client";
import { DEFAULT_CAMPAIGNS } from "@/lib/data";
import { Campaign } from "@/lib/types";
import { randomId } from "@/lib/utils";
import { Dashboard } from "../dashboard/Dashboard";
import { CampaignForm } from "./CampaignForm";
import { useState } from "react";
import _ from "lodash";

export function App() {
	//👇🏻 default segments
	const [segments, setSegments] = useState<string[]>([
		"Millennials/Female/Urban",
		"Parents/30s/Suburbs",
		"Seniors/Female/Rural",
		"Professionals/40s/Midwest",
		"Gamers/Male",
	]);

	const [campaigns, setCampaigns] = useState<Campaign[]>(
		_.cloneDeep(DEFAULT_CAMPAIGNS)
	);

	//👇🏻 updates campaign list
	function saveCampaign(campaign: Campaign) {
		//👇🏻 newly created campaign
		if (campaign.id === "") {
			campaign.id = randomId();
			setCampaigns([campaign, ...campaigns]);
		} else {
			//👇🏻 existing campaign - search for the campaign and updates the campaign list
			const index = campaigns.findIndex((c) => c.id === campaign.id);
			if (index === -1) {
				setCampaigns([...campaigns, campaign]);
			} else {
				campaigns[index] = campaign;
				setCampaigns([...campaigns]);
			}
		}
	}

	const [currentCampaign, setCurrentCampaign] = useState<Campaign | undefined>(
		undefined
	);

	return (
		<div className='relative'>
			<CampaignForm
				segments={segments}
				currentCampaign={currentCampaign}
				setCurrentCampaign={setCurrentCampaign}
				saveCampaign={(campaign) => {
					if (campaign) {
						saveCampaign(campaign);
					}
					setCurrentCampaign(undefined);
				}}
			/>
			<Dashboard
				campaigns={campaigns}
				setCurrentCampaign={setCurrentCampaign}
				segments={segments}
				setSegments={setSegments}
			/>
		</div>
	);
}
```

- From the code snippet above,
  - I created a list of default segments for the campaign and made a deep copy of the already defined campaign list.
  - The `saveCampaign` function accepts a campaign as a parameter. If the campaign does not have an ID, it means it is newly created, so it adds it to the campaign list. Otherwise, it finds the campaign and updates its attributes.
  - The `Dashboard` and the `CampaignForm` components accept the segments and the campaigns as props.

The [Dashboard component](https://github.com/CopilotKit/campaign-manager-demo/blob/main/src/components/dashboard/Dashboard.tsx) displays various UI elements on the dashboard, while the [CampaignForm component](https://github.com/CopilotKit/campaign-manager-demo/blob/main/src/components/app/CampaignForm.tsx) enables users to create and save a new campaign in the application.

You can also update the dashboard and app components using the code snippets from the [GitHub repository](https://github.com/CopilotKit/campaign-manager-demo/tree/main/src/components).

Congratulations! You should have a working web application that allows users to view and create new campaigns.

In the upcoming section, you'll learn how to add CopilotKit to the application to analyze and make decisions based on the goals and budget of each campaign.


![Application Overview](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4phyaucli8pdcbe625u4.gif)

---

## Analyzing ad campaigns with AI using CopilotKit

Here, you'll learn how to add AI to the application to help you analyze your campaigns and make the best decisions.

Before we proceed, visit the [OpenAI Developers' Platform](https://platform.openai.com/api-keys) and create a new secret key.

![Fetch OpenAI API key](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/02972pt2aj3kf9l5suqq.png)

Create a `.env.local` file and copy the your newly created secret key into the file.

```
OPENAI_API_KEY=<YOUR_OPENAI_SECRET_KEY>
OPENAI_MODEL=gpt-4-1106-preview
```

Next, you need to create an API endpoint for CopilotKit. Within the Next.js app folder, create an `api/copilotkit` folder containing a `route.ts` file.

```bash
cd app
mkdir api && cd api
mkdir copilotkit && cd copilotkit
touch route.ts
```

Copy the code snippet below into the `route.ts` file. The [CopilotKit backend](https://docs.copilotkit.ai/reference/CopilotBackend) accept users’ requests and make decisions using the OpenAI model.

```tsx
import { CopilotBackend, OpenAIAdapter } from "@copilotkit/backend";

export const runtime = "edge";

export async function POST(req: Request): Promise<Response> {
	const copilotKit = new CopilotBackend({});
	const openaiModel = process.env["OPENAI_MODEL"];

	return copilotKit.response(req, new OpenAIAdapter({ model: openaiModel }));
}
```

To connect your application to this API endpoint, update the `app/page.tsx` file as shown below:

```tsx
"use client";
import { App } from "@/components/app/App";
import { CopilotKit } from "@copilotkit/react-core";
import { CopilotSidebar } from "@copilotkit/react-ui";

export default function DashboardPage() {
	return (
		<CopilotKit url='/api/copilotkit/'>
			<CopilotSidebar
				instructions='Help the user create and manage ad campaigns.'
				defaultOpen={true}
				labels={{
					title: "Campaign Manager Copilot",
					initial:
						"Hello there! I can help you manage your ad campaigns. What campaign would you like to work on?",
				}}
				clickOutsideToClose={false}
			>
				<App />
			</CopilotSidebar>
		</CopilotKit>
	);
}
```

The `CopilotKit` component wraps the entire application and accepts a `url` prop that contains a link to the API endpoint. The `CopilotSidebar` component adds a chatbot sidebar panel to the application, enabling us to provide various instructions to CopilotKit.


![Adding CopilotKit to Next.js](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wzh8ui253ftzmgtrcksd.gif)

### How to enable the AI copilot to perform various actions

CopilotKit provides two hooks that enable us to handle user's request and plug into the application state: [useCopilotAction](https://docs.copilotkit.ai/reference/useCopilotAction) and [useMakeCopilotReadable](https://docs.copilotkit.ai/reference/useMakeCopilotReadable).

The `useCopilotAction` hook allows you to define actions to be carried out by CopilotKit. It accepts an object containing the following parameters:

- name - the action's name.
- description - the action's description.
- parameters - an array containing the list of the required parameters.
- render - the default custom function or string.
- handler - the executable function that is triggered by the action.

```tsx
useCopilotAction({
	name: "sayHello",
	description: "Say hello to someone.",
	parameters: [
		{
			name: "name",
			type: "string",
			description: "name of the person to say greet",
		},
	],
	render: "Process greeting message...",
	handler: async ({ name }) => {
		alert(`Hello, ${name}!`);
	},
});
```

The `useMakeCopilotReadable` hook provides the application state to CopilotKit.

```tsx
import { useMakeCopilotReadable } from "@copilotkit/react-core";

const appState = ...;
useMakeCopilotReadable(JSON.stringify(appState));
```

CopilotKit also allows you to provide context to the user's prompt, enabling it to make adequate and precise decisions.

Add a `guidance.ts` and a `script.ts` to the `lib` folder within your project, and copy this [guidance](https://github.com/CopilotKit/campaign-manager-demo/blob/main/src/lib/guideline.ts) and [script suggestions](https://github.com/CopilotKit/campaign-manager-demo/blob/main/src/lib/script.ts) into the file to enable CopilotKit make decisions.

Within the App component, pass the current date, script suggestions, and guidance into CopilotKit.

```tsx
import { GUIDELINE } from "@/lib/guideline";
import { SCRIPT_SUGGESTION } from "@/lib/script";
import {
	useCopilotAction,
	useMakeCopilotReadable,
} from "@copilotkit/react-core";

export function App() {
	//-- 👉🏻 ...other component functions

	//👇🏻 Ground the Copilot with domain-specific knowledge for this use-case: marketing campaigns.
	useMakeCopilotReadable(GUIDELINE);
	useMakeCopilotReadable(SCRIPT_SUGGESTION);

	//👇🏻 Provide the Copilot with the current date.
	useMakeCopilotReadable("Today's date is " + new Date().toDateString());

	return (
		<div className='relative'>
			<CampaignForm
				segments={segments}
				currentCampaign={currentCampaign}
				setCurrentCampaign={setCurrentCampaign}
				saveCampaign={(campaign) => {
					if (campaign) {
						saveCampaign(campaign);
					}
					setCurrentCampaign(undefined);
				}}
			/>
			<Dashboard
				campaigns={campaigns}
				setCurrentCampaign={setCurrentCampaign}
				segments={segments}
				setSegments={setSegments}
			/>
		</div>
	);
}
```

Create a CopilotKit action within the `App` component that creates a new campaign or edits an existing one when the user provides such instruction.

```tsx
useCopilotAction({
	name: "updateCurrentCampaign",
	description:
		"Edit an existing campaign or create a new one. To update only a part of a campaign, provide the id of the campaign to edit and the new values only.",
	parameters: [
		{
			name: "id",
			description:
				"The id of the campaign to edit. If empty, a new campaign will be created",
			type: "string",
		},
		{
			name: "title",
			description: "The title of the campaign",
			type: "string",
			required: false,
		},
		{
			name: "keywords",
			description: "Search keywords for the campaign",
			type: "string",
			required: false,
		},
		{
			name: "url",
			description:
				"The URL to link the ad to. Most of the time, the user will provide this value, leave it empty unless asked by the user.",
			type: "string",
			required: false,
		},
		{
			name: "headline",
			description:
				"The headline displayed in the ad. This should be a 5-10 words",
			type: "string",
			required: false,
		},
		{
			name: "description",
			description:
				"The description displayed in the ad. This should be a short text",
			type: "string",
			required: false,
		},

		{
			name: "budget",
			description: "The budget of the campaign",
			type: "number",
			required: false,
		},
		{
			name: "objective",
			description: "The objective of the campaign",
			type: "string",
			enum: [
				"brand-awareness",
				"lead-generation",
				"sales-conversion",
				"website-traffic",
				"engagement",
			],
		},

		{
			name: "bidStrategy",
			description: "The bid strategy of the campaign",
			type: "string",
			enum: ["manual-cpc", "cpa", "cpm"],
			required: false,
		},
		{
			name: "bidAmount",
			description: "The bid amount of the campaign",
			type: "number",
			required: false,
		},
		{
			name: "segment",
			description: "The segment of the campaign",
			type: "string",
			required: false,
			enum: segments,
		},
	],
	handler: (campaign) => {
		const newValue = _.assign(
			_.cloneDeep(currentCampaign),
			_.omitBy(campaign, _.isUndefined)
		) as Campaign;

		setCurrentCampaign(newValue);
	},
	render: (props) => {
		if (props.status === "complete") {
			return "Campaign updated successfully";
		} else {
			return "Updating campaign";
		}
	},
});
```

{% embed https://www.youtube.com/watch?v=gCJpH6Tnj5g %}

Add another action that simulates an API call to allow CopilotKit to retrieve historical data from previously created campaigns.

```tsx
// Provide this component's Copilot with the ability to retrieve historical cost data for certain keywords.
// Will be called automatically when needed by the Copilot.
useCopilotAction({
	name: "retrieveHistoricalData",
	description: "Retrieve historical data for certain keywords",
	parameters: [
		{
			name: "keywords",
			description: "The keywords to retrieve data for",
			type: "string",
		},
		{
			name: "type",
			description: "The type of data to retrieve for the keywords.",
			type: "string",
			enum: ["CPM", "CPA", "CPC"],
		},
	],
	handler: async ({ type }) => {
		// fake an API call that retrieves historical data for cost for certain keywords based on campaign type (CPM, CPA, CPC)
		await new Promise((resolve) => setTimeout(resolve, 2000));

		function getRandomValue(min: number, max: number) {
			return (Math.random() * (max - min) + min).toFixed(2);
		}

		if (type == "CPM") {
			return getRandomValue(0.5, 10);
		} else if (type == "CPA") {
			return getRandomValue(5, 100);
		} else if (type == "CPC") {
			return getRandomValue(0.2, 2);
		}
	},
	render: (props) => {
		// Custom in-chat component rendering. Different components can be rendered based on the status of the action.
		let label = "Retrieving historical data ...";
		if (props.args.type) {
			label = `Retrieving ${props.args.type} for keywords ...`;
		}
		if (props.status === "complete") {
			label = `Done retrieving ${props.args.type} for keywords.`;
		}

		const done = props.status === "complete";
		return (
			<div className=''>
				<div className=' w-full relative max-w-xs'>
					<div className='absolute inset-0 h-full w-full bg-gradient-to-r from-blue-500 to-teal-500 transform scale-[0.80] bg-red-500 rounded-full blur-3xl' />
					<div className='relative shadow-xl bg-gray-900 border border-gray-800  px-4 py-8 h-full overflow-hidden rounded-2xl flex flex-col justify-end items-start'>
						<h1 className='font-bold text-sm text-white mb-4 relative z-50'>
							{label}
						</h1>
						<p className='font-normal text-base text-teal-200 mb-2 relative z-50 whitespace-pre'>
							{props.args.type &&
								`Historical ${props.args.type}: ${props.result || "..."}`}
						</p>
					</div>
				</div>
			</div>
		);
	},
});
```

![Application Preview](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kz3xm63ciq5q3kyooz9s.gif)

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

## Conclusion

[CopilotKit](https://copilotkit.ai/) 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/CopilotKit/campaign-manager-demo](https://github.com/CopilotKit/campaign-manager-demo)

Thank you for reading!