DEV Community

Cover image for How to have AI onboard your users (Next.js, OpenAI, CopilotKit)
David Asaolu for CopilotKit

Posted on

How to have AI onboard your users (Next.js, OpenAI, CopilotKit)

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


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 🤖

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

Star CopilotKit ⭐️


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
Enter fullscreen mode Exit fullscreen mode

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

Next.js App Installation

Next, install HeroiconsRadix 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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",
    },
];
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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

cd components
mkdir app dashboard ui
Enter fullscreen mode Exit fullscreen mode

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.

Application User Interface

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 />;
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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>
    );
}
Enter fullscreen mode Exit fullscreen mode
  • 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 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.

Application Overview


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.

Fetch OpenAI API 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 }));
}
Enter fullscreen mode Exit fullscreen mode

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>
    );
}
Enter fullscreen mode Exit fullscreen mode

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

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}!`);
    },
});
Enter fullscreen mode Exit fullscreen mode

The useMakeCopilotReadable hook provides the application state to CopilotKit.

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

const appState = ...;
useMakeCopilotReadable(JSON.stringify(appState));
Enter fullscreen mode Exit fullscreen mode

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>
    );
}
Enter fullscreen mode Exit fullscreen mode

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";
        }
    },
});
Enter fullscreen mode Exit fullscreen mode

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>
        );
    },
});
Enter fullscreen mode Exit fullscreen mode

Application Preview

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 (26)

Collapse
 
nevodavid profile image
Nevo David

This is awesome!

Collapse
 
arshadayvid profile image
David Asaolu

Thank you so much!

Collapse
 
uliyahoo profile image
uliyahoo

Thanks for checking out our tutorial Nevo!

Collapse
 
uliyahoo profile image
uliyahoo

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.

Collapse
 
arshadayvid profile image
David Asaolu

Yes, exactly.
Thank you @uliyahoo

Collapse
 
envitab profile image
Ekemini Samuel

Amazing! Great work David.

Collapse
 
arshadayvid profile image
David Asaolu

Thank you! @envitab

Collapse
 
envitab profile image
Ekemini Samuel

You're welcome

Collapse
 
anmolbaranwal profile image
Anmol Baranwal

The concept is exciting, the structure is awesome and the UI is top-notch. WOW!
One heck of a tutorial, I would say :)

Collapse
 
arshadayvid profile image
David Asaolu

Thank you so much! @anmolbaranwal

Collapse
 
oliver0012 profile image
Oliver

Very long article, but could be awesome! Will try it out this weekend.

Collapse
 
arshadayvid profile image
David Asaolu

Great! You should definitely try it out, Oliver.

Collapse
 
uliyahoo profile image
uliyahoo

These projects are always perfect weekend builds. We always see the traffic to the demo repos skyrocket in the weekend :)

Collapse
 
matteosant_dev profile image
Matteo Santoro Dev

Congratulations! Amazing project

Collapse
 
arshadayvid profile image
David Asaolu

Thank you! @matteosant_dev

Collapse
 
time121212 profile image
tim brandom

This one is too complicated, but it looks interesting.

Collapse
 
henryjohn21 profile image
henryjohn21

nice

Collapse
 
arshadayvid profile image
David Asaolu

Glad, you enjoyed it! ⚡😎

Collapse
 
uliyahoo profile image
uliyahoo

Agreed!

Collapse
 
harry-123 profile image
Harry

Cool project. Congrats to you David!

Collapse
 
arshadayvid profile image
David Asaolu

Thank you, Harry!

Collapse
 
jeremiah_the_dev_man profile image
Jeremiah

Oh wow, didn't see this one until now. It's awesome! One of the best tutorials you guys have written.

Collapse
 
arshadayvid profile image
David Asaolu

Thank you, Jeremiah!

Collapse
 
justinwells21 profile image
justinwells21

Wow it can handle pretty complex stuff at this point

Collapse
 
arshadayvid profile image
David Asaolu

Yes, it can.😎🔥

Collapse
 
esareynor profile image
Rachman Esa

This is amazing!