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
CopilotKit: The framework for building in-app AI copilots
CopilotKit is an open-source AI copilot platform. 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 🤖
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 - for creating accessible UI components for the application.
- OpenAI API Key - to enable us to perform various tasks using the GPT 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 campaign-manager
Select your preferred configuration settings. For this tutorial, we'll be using TypeScript and Next.js App Router.
Next, install Heroicons, Radix UI, and its primitive components into the project.
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 - a React library for creating interactive charts - and the following utility packages:
npm install recharts class-variance-authority clsx cmdk date-fns lodash react-day-picker tailwind-merge tailwindcss-animate
Finally, install the CopilotKit packages. These packages enable the AI copilot to retrieve data from the React state and make decisions within the application.
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.
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.
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.
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.
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.
//👉🏻 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.
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 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.
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.
"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.
cd components/app
touch App.tsx CampaignForm.tsx MainNav.tsx UserNav.tsx
Copy the code snippet below into the App.tsx
file.
"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 theCampaignForm
components accept the segments and the campaigns as props.
The Dashboard component displays various UI elements on the dashboard, while the CampaignForm component 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.
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.
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 and create a new secret key.
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.
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 accept users’ requests and make decisions using the OpenAI model.
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:
"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.
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 and 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.
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.
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 and script suggestions into the file to enable CopilotKit make decisions.
Within the App component, pass the current date, script suggestions, and guidance into CopilotKit.
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.
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";
}
},
});
Add another action that simulates an API call to allow CopilotKit to retrieve historical data from previously created campaigns.
// 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>
);
},
});
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/CopilotKit/campaign-manager-demo
Thank you for reading!
Top comments (28)
This is awesome!
Thank you so much!
Thanks for checking out our tutorial Nevo!
Amazing! Great work David.
Thank you! @envitab
You're welcome
This is a long one, but really awesome!
I can see how this can be used for smooth user-onboarding into more complicated apps, all powered by AI.
Yes, exactly.
Thank you @uliyahoo
Very long article, but could be awesome! Will try it out this weekend.
Great! You should definitely try it out, Oliver.
These projects are always perfect weekend builds. We always see the traffic to the demo repos skyrocket in the weekend :)
Congratulations! Amazing project
Thank you! @matteosant_dev
The concept is exciting, the structure is awesome and the UI is top-notch. WOW!
One heck of a tutorial, I would say :)
Thank you so much! @anmolbaranwal
Agreed!
Cool project. Congrats to you David!
Thank you, Harry!
This one is too complicated, but it looks interesting.
This is great, thank you!