如何让 AI 融入你的用户(Next.js、OpenAI、CopilotKit)

2025-05-24

如何让 AI 融入你的用户(Next.js、OpenAI、CopilotKit)

TL;DR

在本文中,您将学习如何构建一个由人工智能驱动的广告活动管理器应用程序,该应用程序允许您创建和分析广告活动,从而使您能够为您的业务做出正确的决策。

我们将介绍如何:

  • 使用 Next.js 构建 Web 应用程序,
  • 使用 CopilotKit 将 AI 助手集成到软件应用程序中,以及
  • 创建针对特定行动的 AI 副驾驶员来处理应用程序内的各种任务。
  • 建立一个活动管理器

图片描述


CopilotKit:构建应用内 AI 副驾驶的框架

CopilotKit 是一个 开源的 AI 副驾驶平台。我们可以轻松地将强大的 AI 集成到你的 React 应用中。

建造:

  • ChatBot:具有上下文感知能力的应用内聊天机器人,可以在应用内采取行动💬
  • CopilotTextArea:具有上下文感知自动完成和插入功能的 AI 驱动文本字段📝
  • 合作代理:可以与您的应用和用户交互的应用内 AI 代理🤖

https://media2.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

明星 CopilotKit ⭐️


先决条件

要完全理解本教程,您需要对 React 或 Next.js 有基本的了解。

我们还将利用以下内容:

  • Radix UI—— 用于为应用程序创建可访问的 UI 组件。
  • OpenAI API 密钥 - 使我们能够使用 GPT 模型执行各种任务。
  • CopilotKit  - 一个开源副驾驶框架,用于构建自定义 AI 聊天机器人、应用内 AI 代理和文本区域。

项目设置和包安装

首先,通过在终端中运行以下代码片段来创建 Next.js 应用程序:

npx create-next-app campaign-manager
Enter fullscreen mode Exit fullscreen mode

选择您喜欢的配置设置。在本教程中,我们将使用 TypeScript 和 Next.js App Router。

Next.js 应用安装

接下来,将 Heroicons、  Radix UI及其原始组件安装到项目中。

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

另外,安装Recharts 库(用于创建交互式图表的 React 库)以及以下实用程序包:

npm install recharts class-variance-authority clsx cmdk date-fns lodash react-day-picker tailwind-merge tailwindcss-animate
Enter fullscreen mode Exit fullscreen mode

最后,安装 CopilotKit 软件包。这些软件包使 AI 副驾驶能够从 React 状态中检索数据并在应用程序内做出决策。

npm install @copilotkit/react-ui @copilotkit/react-textarea @copilotkit/react-core @copilotkit/backend
Enter fullscreen mode Exit fullscreen mode

恭喜!您现在可以构建应用程序了。


使用 Next.js 构建 Campaign Manager 应用

在本节中,我将引导您构建活动管理器应用程序的用户界面。

首先,让我们做一些初始设置。

在文件夹中创建一个components和文件夹libsrc

cd src
mkdir components lib
Enter fullscreen mode Exit fullscreen mode

在该lib文件夹中,我们将声明应用程序的静态类型和默认活动。因此,请在该文件夹中创建data.tstypes.ts文件lib

cd lib
touch data.ts type.ts
Enter fullscreen mode Exit fullscreen mode

将以下代码片段复制到type.ts文件中。该代码片段声明了广告系列属性及其数据类型。

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

为应用程序创建默认活动列表并将其复制到data.ts文件中。

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

由于我们使用 Radix UI 创建可以使用 TailwindCSS 轻松定制的基本 UI 组件,因此请在文件夹utils.ts中创建一个文件lib并将以下代码片段复制到该文件中。

//👉🏻 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

导航到该components文件夹​​并在其中创建另外三个文件夹。

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

components/app文件夹将包含应用程序内要使用的各种组件,而仪表板文件夹包含一些元素的 UI 组件。

ui文件夹包含使用 Radix UI 创建的多个 UI 元素。请将 项目存储库中的这些元素复制 到该文件夹​​中。

恭喜!该ui文件夹应该包含必要的 UI 元素。现在,我们可以用它们来创建应用程序所需的各种组件了。

创建应用程序 UI 组件

在这里,我将引导您创建应用程序的用户界面。

应用程序用户界面

首先,找到该app/page.tsx文件并将以下代码片段粘贴到其中。该文件渲染components/app文件夹中声明的 App 组件。

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

export default function DashboardPage() {
    return <App />;
}
Enter fullscreen mode Exit fullscreen mode

在文件夹中创建App.tsxCampaignForm.tsxMainNav.tsx和文件UserNav.tsxcomponents/app

cd components/app
touch App.tsx CampaignForm.tsx MainNav.tsx UserNav.tsx
Enter fullscreen mode Exit fullscreen mode

将下面的代码片段复制到App.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>
    );
}
Enter fullscreen mode Exit fullscreen mode
  • 从上面的代码片段来看,
    • 我为该活动创建了一个默认细分列表,并对已定义的活动列表进行了深度复制。
    • saveCampaign函数接受一个活动作为参数。如果该活动没有 ID,则表示它是新创建的,因此会将其添加到活动列表中。否则,它会找到该活动并更新其属性。
    • Dashboard组件CampaignForm接受片段和活动作为道具。

Dashboard组件在仪表板上显示各种 UI 元素,而CampaignForm 组件则使用户能够在应用程序中创建和保存新的活动。

您还可以使用GitHub 存储库中的代码片段更新仪表板和应用程序组件

恭喜!您应该已经拥有一个可以运行的 Web 应用程序,用户可以查看和创建新的营销活动。

在接下来的部分中,您将学习如何将 CopilotKit 添加到应用程序中,以根据每个活动的目标和预算进行分析和做出决策。

应用程序概述


使用 CopilotKit 通过 AI 分析广告活动

在这里,您将学习如何将 AI 添加到应用程序中,以帮助您分析您的活动并做出最佳决策。

在我们继续之前,请访问OpenAI 开发者平台并创建一个新的密钥。

获取 OpenAI API 密钥

创建一个.env.local文件并将您新创建的密钥复制到该文件中。

OPENAI_API_KEY=<YOUR_OPENAI_SECRET_KEY>
OPENAI_MODEL=gpt-4-1106-preview
Enter fullscreen mode Exit fullscreen mode

接下来,您需要为 CopilotKit 创建一个 API 端点。在 Next.js 应用文件夹中,创建一个api/copilotkit包含route.ts文件的文件夹。

cd app
mkdir api && cd api
mkdir copilotkit && cd copilotkit
touch route.ts
Enter fullscreen mode Exit fullscreen mode

将以下代码片段复制到route.ts文件中。CopilotKit后端接受用户请求并使用 OpenAI 模型做出决策。

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

要将您的应用程序连接到此 API 端点,app/page.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>
    );
}
Enter fullscreen mode Exit fullscreen mode

CopilotKit组件包装了整个应用程序,并接受一个url包含指向 API 端点链接的 prop。该CopilotSidebar组件向应用程序添加了一个聊天机器人侧边栏面板,使我们能够向 CopilotKit 提供各种指令。

将 CopilotKit 添加到 Next.js

如何让AI副驾驶执行各种操作

CopilotKit 提供了两个钩子,使我们能够处理用户的请求并插入应用程序状态:useCopilotActionuseMakeCopilotReadable

useCopilotAction钩子允许您定义由 CopilotKit 执行的操作。它接受包含以下参数的对象:

  • name——动作的名称。
  • 描述——动作的描述。
  • 参数——包含所需参数列表的数组。
  • render - 默认的自定义函数或字符串。
  • 处理程序——由操作触发的可执行函数。
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

useMakeCopilotReadable钩子向 CopilotKit 提供应用程序状态。

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

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

CopilotKit 还允许您为用户的提示提供上下文,使其能够做出充分而准确的决策。

guidance.ts和 添加script.tslib项目内的文件夹中,并将此指导脚本建议复制到文件中以使 CopilotKit 做出决策。

在 App 组件内,将当前日期、脚本建议和指导传递到 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

在组件内创建一个 CopilotKit 操作,App当用户提供此类指令时,该操作会创建一个新的活动或编辑现有的活动。

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

添加另一个模拟 API 调用的操作,以允许 CopilotKit 从以前创建的活动中检索历史数据。

// 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

应用程序预览

恭喜!您已完成本教程的项目。

结论

CopilotKit 是一款功能强大的工具,可让您在几分钟内将 AI Copilot 添加到您的产品中。无论您对 AI 聊天机器人和助手感兴趣,还是对复杂任务的自动化感兴趣,CopilotKit 都能让您轻松实现。

如果您需要构建 AI 产品或将 AI 工具集成到您的软件应用程序中,您应该考虑 CopilotKit。

您可以在 GitHub 上找到本教程的源代码:

https://github.com/CopilotKit/campaign-manager-demo

感谢您的阅读!

文章来源:https://dev.to/copilotkit/build-an-ai-powered-campaign-manager-nextjs-openai-copilotkit-59ii
PREV
构建 AI 驱动的简历和求职信生成器(CopilotKit、LangChain、Tavily 和 Next.js)CopilotKit:用于构建应用内 AI 副驾驶员的开源框架
NEXT
使用 Anthropic、Pinecone 和 CopilotKit 构建您自己的 RAG Cop​​ilot