从”人工智障”到”AI全能助手”:CopilotKit让我见识了什么叫真正的人机协作

从”人工智障”到”AI全能助手”:CopilotKit让我见识了什么叫真正的人机协作

从”人工智障”到”AI全能助手”:CopilotKit让我见识了什么叫真正的人机协作

一行代码让现有产品秒变智能,这个刚爆火的AI开发框架终于被我们扒透了


一、为什么这个项目值得你花时间?

1.1 传统AI集成的痛点

想象一下这个场景:你在做一个SaaS产品,产品经理跑过来跟你说:”咱们能不能加个AI助手?让用户问问题的时候能自动回答。”

作为开发者,你的内心可能是崩溃的。因为这意味着你可能要:

# 传统的AI集成方式 —— 每一个都是坑

# 坑1: 需要自己处理流式响应
def chat_with_ai(message):
    response = openai.ChatCompletion.create(
        model="gpt-4",
        messages=[{"role": "user", "content": message}]
    )
    return response.choices[0].message.content
    # 问题:这是同步的,用户要等好几秒才能看到结果
    # 问题:没有流式输出,用户体验很差

# 坑2: 需要自己实现上下文管理
# 你得手动维护一个messages数组,处理token限制
# 处理历史消息的截断
# 处理不同角色的消息格式

# 坑3: 需要自己构建UI组件
# 打字机效果?消息气泡?输入框?加载状态?
# 这些看似简单的东西,做起来全是细节

# 坑4: 需要自己处理多轮对话
# 记忆管理?对话状态持久化?
# 如果用户刷新页面怎么办?

# 坑5: 需要自己实现agent能力
# 工具调用?插件系统?函数执行?
# 这部分代码量巨大,而且很容易出bug

1.2 CopilotKit带来了什么?

CopilotKit(https://github.com/CopilotKit/CopilotKit)是一个专门为应用内AI助手设计的开源框架。它的核心理念是:让开发者专注于业务逻辑,而不是AI基础设施

# 使用CopilotKit后的代码 —— 简洁到不敢相信

# 只需要几行代码,就能创建一个完整的AI助手
from copilotkit import CopilotKitSDK

sdk = CopilotKitSDK(
    model="gpt-4",
    system_prompt="你是一个专业的客服助手"
)

# 然后在你的应用里直接使用
# <CopilotKit id="customer-service" />
# 就是这么简单!

1.3 CopilotKit的核心优势

特性 传统方式 CopilotKit
接入速度 3-5天 10分钟
代码量 1000+行 50行
流式输出 需自己实现 内置支持
上下文管理 手动处理 自动管理
工具调用 从零开发 装饰器即可
UI组件 从零开发 丰富组件库

二、环境搭建:手把手从零开始

2.1 前置要求

在开始之前,请确保你的开发环境满足以下要求:

# 检查Node.js版本(需要18.0或更高)
node --version
# v20.10.0

# 检查npm或yarn
npm --version
# 10.2.0

# 如果使用Python后端(可选)
python --version
# Python 3.10+

2.2 创建你的第一个CopilotKit项目

方式一:使用官方CLI(推荐新手)

# 全局安装CopilotKit CLI
npm install -g @copilotkit/cli

# 创建一个新项目
copilotkit create my-first-ai-app

# 进入项目目录
cd my-first-ai-app

# 安装依赖
npm install

# 启动开发服务器
npm run dev

方式二:手动集成到现有项目

# 如果你已经有了一个React/Next.js项目,可以直接添加CopilotKit

# 安装核心包
npm install @copilotkit/react-core @copilotkit/react-ui

# 如果需要后端支持
npm install @copilotkit/langchain

# 如果使用Next.js
npm install @copilotkit/nextjs

2.3 项目结构一览

创建一个标准项目后,你会看到这样的目录结构:

my-first-ai-app/
├── src/
│   ├── app/
│      ├── layout.tsx          # 根布局      ├── page.tsx            # 首页      └── api/
│          └── copilotkit/
│              └── route.ts    # CopilotKit API路由   ├── components/
│      ├── copilot/
│         ├── ChatWindow.tsx  # 聊天窗口组件         └── MessageBubble.tsx # 消息气泡组件      └── business/
│          └── Dashboard.tsx   # 你的业务组件   ├── lib/
│      ├── copilotkit.ts       # CopilotKit配置      └── tools.ts            # 自定义工具   └── styles/
│       └── globals.css
├── public/
├── package.json
├── next.config.js
├── tailwind.config.js
└── tsconfig.json

2.4 环境变量配置

在项目根目录创建.env.local文件:

# OpenAI配置
OPENAI_API_KEY=sk-your-api-key-here

# 如果你想使用其他模型
# ANTHROPIC_API_KEY=sk-ant-your-api-key

# CopilotKit配置(可选)
COPILOT_CLOUD_API_KEY=cpk-your-cloud-key

# 应用配置
NEXT_PUBLIC_APP_URL=http://localhost:3000

三、核心功能详解

3.1 CopilotKit的三大核心模块

CopilotKit主要由三个模块组成:

┌─────────────────────────────────────────────────────────┐
│                    CopilotKit 架构                       │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────────┐  │
│  │   React     │  │   Runtime   │  │    Agents       │  │
│  │   UI        │  │   Engine    │  │    Framework    │  │
│  ├─────────────┤  ├─────────────┤  ├─────────────────┤  │
│  │ • 聊天窗口  │  │ • 上下文管理 │  │ • 工具调用      │  │
│  │ • 消息气泡  │  │ • 流式处理  │  │ • 任务分解      │  │
│  │ • 输入框    │  │ • 状态管理  │  │ • 多步骤执行    │  │
│  │ • 侧边栏    │  │ • 记忆存储  │  │ • 循环控制      │  │
│  └─────────────┘  └─────────────┘  └─────────────────┘  │
│                                                         │
│  ┌─────────────────────────────────────────────────┐    │
│  │              Provider Integration               │    │
│  │  OpenAI  │  Anthropic  │  Azure  │  自定义      │    │
│  └─────────────────────────────────────────────────┘    │
│                                                         │
└─────────────────────────────────────────────────────────┘

3.2 模块一:React UI 组件库

CopilotKit提供了一套完整的UI组件,让你可以快速构建专业的AI聊天界面。

基础聊天窗口

// src/components/copilot/BasicChat.tsx
"use client";

import { CopilotKit } from "@copilotkit/react-core";
import { CopilotPopup } from "@copilotkit/react-ui";
import "@copilotkit/react-ui/styles.css";

export function BasicChat() {
  return (
    // CopilotKit 是所有AI功能的容器
    // 它需要包裹所有使用AI功能的组件
    <CopilotKit
      // API端点指向你的后端
      url="/api/copilotkit"
      // 可选默认的系统提示词
      defaultSystemMessage="你是一个友好、专业的AI助手。"
    >
      {/* CopilotPopup 是可弹出的聊天窗口 */}
      <CopilotPopup
        // 是否默认打开
        defaultOpen={false}
        // 自定义触发按钮的标签
        triggerLabel="💬 咨询AI助手"
        // 自定义样式可选
        className="my-custom-chat"
      />
    </CopilotKit>
  );
}

内联AI助手(嵌入在页面中)

// src/components/copilot/InlineAssistant.tsx
"use client";

import { CopilotKit, useCopilotAction } from "@copilotkit/react-core";
import { CopilotSidebar } from "@copilotkit/react-ui";

export function InlineAssistant() {
  return (
    <CopilotKit url="/api/copilotkit">
      <div className="flex">
        {/* 侧边栏模式 */}
        <CopilotSidebar
          // 侧边栏位置
          side="right"
          // 侧边栏宽度
          width="350px"
        >
          {/* 这里放你的主应用内容 */}
          <YourMainApp />
        </CopilotSidebar>
      </div>
    </CopilotKit>
  );
}

消息气泡组件

// src/components/copilot/MessageBubble.tsx
"use client";

import { useCopilotChat } from "@copilotkit/react-core";
import { cn } from "@/lib/utils";

export function MessageList() {
  // useCopilotChat 是一个hook,用于获取聊天相关的状态和方法
  const { messages, isLoading } = useCopilotChat();

  return (
    <div className="flex flex-col gap-4 p-4">
      {messages.map((message, index) => (
        <div
          key={index}
          className={cn(
            "flex",
            message.role === "user" ? "justify-end" : "justify-start"
          )}
        >
          <div
            className={cn(
              "max-w-[80%] rounded-2xl px-4 py-2",
              message.role === "user"
                ? "bg-blue-500 text-white"
                : "bg-gray-100 text-gray-900"
            )}
          >
            {/* 支持Markdown渲染 */}
            <div className="prose prose-sm">
              {message.content}
            </div>

            {/* 显示时间戳 */}
            <div className="text-xs opacity-70 mt-1">
              {new Date(message.createdAt).toLocaleTimeString()}
            </div>
          </div>
        </div>
      ))}

      {/* 加载指示器 */}
      {isLoading && (
        <div className="flex justify-start">
          <div className="bg-gray-100 rounded-2xl px-4 py-2">
            <div className="flex gap-1">
              <span className="w-2 h-2 bg-gray-400 rounded-full animate-bounce" />
              <span className="w-2 h-2 bg-gray-400 rounded-full animate-bounce delay-75" />
              <span className="w-2 h-2 bg-gray-400 rounded-full animate-bounce delay-150" />
            </div>
          </div>
        </div>
      )}
    </div>
  );
}

3.3 模块二:Runtime Engine(运行时引擎)

Runtime Engine是CopilotKit的大脑,负责处理所有AI相关的逻辑。

上下文管理

// src/components/copilot/ContextAwareAssistant.tsx
"use client";

import { 
  CopilotKit, 
  useCopilotContext 
} from "@copilotkit/react-core";

export function ContextAwareAssistant() {
  // useCopilotContext 提供对上下文的访问
  const context = useCopilotContext();

  // 获取当前选中的文本
  const selectedText = context.selectedText;

  // 获取当前页面的URL
  const currentUrl = context.url;

  // 获取应用自定义的上下文数据
  const customContext = context.get("user_preferences");
  const documentContent = context.get("document_content");

  return (
    <CopilotKit url="/api/copilotkit">
      <div className="p-4">
        <h2>当前上下文信息</h2>
        <ul>
          <li>选中文本: {selectedText || "无"}</li>
          <li>当前页面: {currentUrl}</li>
          <li>用户偏好: {JSON.stringify(customContext)}</li>
          <li>文档内容: {documentContent?.substring(0, 100)}...</li>
        </ul>
      </div>
    </CopilotKit>
  );
}

状态持久化

// src/components/copilot/PersistentChat.tsx
"use client";

import { 
  CopilotKit, 
  useCopilotChat 
} from "@copilotkit/react-core";
import { useEffect } from "react";
import { useLocalStorage } from "@/hooks/useLocalStorage";

export function PersistentChat() {
  // 自定义的本地存储hook
  const [savedMessages, setSavedMessages] = useLocalStorage<
    Array<{role: string; content: string}>
  >("chat-history", []);

  const { messages, appendMessage, clearMessages } = useCopilotChat({
    // 启用消息持久化
    enablePersistence: true,
    // 持久化存储位置
    storageKey: "chat-history",
    // 加载历史消息
    initialMessages: savedMessages,
  });

  // 当消息变化时,自动保存
  useEffect(() => {
    setSavedMessages(messages);
  }, [messages, setSavedMessages]);

  return (
    <div className="flex flex-col h-[500px]">
      <div className="flex-1 overflow-y-auto">
        {messages.map((msg, i) => (
          <MessageItem key={i} message={msg} />
        ))}
      </div>

      <div className="flex gap-2 p-4 border-t">
        <button
          onClick={() => clearMessages()}
          className="px-4 py-2 text-sm text-red-600 hover:bg-red-50 rounded"
        >
          清空对话
        </button>
      </div>
    </div>
  );
}

3.4 模块三:Agents Framework(智能体框架)

这是CopilotKit最强大的部分 —— 它让你可以创建能够执行复杂任务的AI智能体。

基础工具定义

// src/lib/tools.ts
"use client";

import { 
  Tool, 
  tool 
} from "@copilotkit/langchain";

// 定义一个简单的工具:获取天气
export const getWeatherTool = tool({
  // 工具名称,AI会看到这个名称
  name: "get_weather",

  // 工具描述,这非常重要!
  // AI会根据这个描述决定是否调用这个工具
  description: "获取指定城市的天气信息。当用户询问某个城市的天气时使用。",

  // 参数模式定义(使用Zod schema)
  parameters: {
    type: "object",
    properties: {
      city: {
        type: "string",
        description: "城市名称,例如:北京、上海、东京",
      },
      unit: {
        type: "string",
        enum: ["celsius", "fahrenheit"],
        description: "温度单位,默认是摄氏度",
        default: "celsius",
      },
    },
    required: ["city"],
  },

  // 工具的实际执行逻辑
  execute: async ({ city, unit }) => {
    // 这里是你的实际业务逻辑
    const response = await fetch(
      `https://api.weather.example.com?city=${city}&unit=${unit}`
    );
    const data = await response.json();

    return `当前${city}的天气是${data.condition},温度为${data.temperature}°${
      unit === "celsius" ? "C" : "F"
    }。`;
  },
});

复杂工具:数据库查询

// src/lib/database-tools.ts
"use client";

import { tool } from "@copilotkit/langchain";
import { z } from "zod";

// 数据库查询工具
export const queryDatabaseTool = tool({
  name: "query_database",

  description: `在数据库中执行SQL查询。这个工具只能用于:
  - 查询用户请求的数据
  - 不能执行UPDATE、DELETE等危险操作
  - 每次查询最多返回100条记录

  当用户想要查看、分析数据报告时使用。`,

  parameters: z.object({
    sql: z.string().describe("要执行的SQL查询语句"),
    params: z.record(z.any()).optional().describe("查询参数"),
  }),

  execute: async ({ sql, params }) => {
    // 安全性检查
    const dangerousKeywords = ["DROP", "DELETE", "UPDATE", "INSERT", "TRUNCATE"];
    const upperSql = sql.toUpperCase();

    for (const keyword of dangerousKeywords) {
      if (upperSql.includes(keyword)) {
        return `错误:不允许执行包含 "${keyword}" 的操作。`;
      }
    }

    try {
      // 执行查询
      const results = await db.query(sql, params);

      // 格式化返回结果
      return {
        rowCount: results.rows.length,
        columns: results.fields.map(f => f.name),
        rows: results.rows,
      };
    } catch (error) {
      return `查询出错:${error.message}`;
    }
  },
});

文件系统操作工具

// src/lib/file-tools.ts
"use client";

import { tool } from "@copilotkit/langchain";
import { z } from "zod";
import { writeFile, readFile, glob } from "fs/promises";
import path from "path";

export const fileSystemTools = {
  // 读取文件
  readFileTool: tool({
    name: "read_file",
    description: "读取指定路径的文件内容。用于查看代码、配置文件或文档。",
    parameters: z.object({
      filePath: z.string().describe("文件的完整路径"),
    }),
    execute: async ({ filePath }) => {
      try {
        const absolutePath = path.resolve(filePath);
        const content = await readFile(absolutePath, "utf-8");
        return content;
      } catch (error) {
        return `读取文件失败:${error.message}`;
      }
    },
  }),

  // 写入文件
  writeFileTool: tool({
    name: "write_file",
    description: "创建或更新文件内容。谨慎使用,仅用于用户明确要求的文件修改。",
    parameters: z.object({
      filePath: z.string().describe("文件的完整路径"),
      content: z.string().describe("要写入的内容"),
      createDirectories: z.boolean().optional().describe("是否自动创建目录"),
    }),
    execute: async ({ filePath, content, createDirectories = false }) => {
      try {
        const absolutePath = path.resolve(filePath);

        if (createDirectories) {
          const dir = path.dirname(absolutePath);
          await mkdir(dir, { recursive: true });
        }

        await writeFile(absolutePath, content, "utf-8");
        return `文件已成功写入:${absolutePath}`;
      } catch (error) {
        return `写入文件失败:${error.message}`;
      }
    },
  }),

  // 搜索文件
  searchFilesTool: tool({
    name: "search_files",
    description: "根据模式搜索文件。例如:搜索所有.tsx文件、查找包含特定内容的文件等。",
    parameters: z.object({
      pattern: z.string().describe("glob模式,例如:*.tsx、src/**/*.js"),
      basePath: z.string().optional().describe("搜索的起始路径"),
    }),
    execute: async ({ pattern, basePath = "." }) => {
      try {
        const files = await glob(pattern, { cwd: basePath });
        return {
          count: files.length,
          files: files.slice(0, 50), // 限制返回数量
        };
      } catch (error) {
        return `搜索失败:${error.message}`;
      }
    },
  }),
};

四、实战教程:从需求到实现

4.1 场景一:构建一个智能客服助手

需求分析

  • 用户可以随时点击按钮打开客服窗口
  • 客服能够回答常见问题
  • 支持转人工(当AI无法解决问题时)
  • 聊天记录需要保存

Step 1: 创建API路由

// src/app/api/copilotkit/route.ts
import { CopilotRuntime, OpenAIAdapter } from "@copilotkit/runtime";
import { NextRequest } from "next/server";

// 你的知识库数据(实际项目中可以从数据库获取)
const knowledgeBase = [
  {
    question: "如何重置密码?",
    answer: "请访问 /forgot-password 页面,输入您的注册邮箱,我们会发送重置链接。",
  },
  {
    question: "如何联系人工客服?",
    answer: "您可以通过以下方式联系我们:\n- 电话:400-123-4567\n- 邮箱:support@example.com\n- 工作时间:周一至周五 9:00-18:00",
  },
  {
    question: "如何开通会员?",
    answer: "请访问 /pricing 页面选择适合您的会员套餐,完成支付后即可开通。",
  },
];

const copilotKit = new CopilotRuntime({
  // 公开可访问的工具列表
  tools: [
    {
      name: "searchKnowledgeBase",
      description: "在知识库中搜索相关内容。当用户询问产品使用、常见问题时使用。",
      parameters: {
        type: "object",
        properties: {
          query: {
            type: "string",
            description: "用户的查询内容",
          },
        },
        required: ["query"],
      },
      handler: async ({ query }) => {
        // 简单的关键词匹配(实际项目可以用更复杂的算法)
        const results = knowledgeBase.filter(
          (item) =>
            item.question.includes(query) || item.answer.includes(query)
        );

        if (results.length === 0) {
          return "抱歉,我在知识库中没有找到相关内容。建议您联系人工客服获取帮助。";
        }

        return results.map((r) => `${r.question}\n${r.answer}`).join("\n\n");
      },
    },
    {
      name: "transferToHuman",
      description: "将对话转接给人工客服。当用户明确要求人工服务、AI无法回答或用户表示不满时使用。",
      parameters: {
        type: "object",
        properties: {
          reason: {
            type: "string",
            description: "转接原因",
          },
        },
      },
      handler: async ({ reason }) => {
        // 这里可以调用你的工单系统
        console.log("转接人工:", reason);
        return "好的,我将为您转接人工客服。请稍候...";
      },
    },
  ],
});

export async function POST(req: NextRequest) {
  const { headers } = req;

  const response = await copilotKit.generateText({
    messages: req.messages,
    systemPrompt: `你是公司的智能客服助手。你的职责是:
    1. 友好、专业地回答用户的问题
    2. 优先使用知识库回答问题
    3. 如果知识库无法回答,可以基于你的知识给出一般性建议
    4. 如果问题无法解决,主动建议转人工
    5. 永远保持耐心和礼貌
    6. 不要编造不存在的产品功能或政策`,
  });

  return response;
}

Step 2: 创建客服组件

// src/components/customer-service/CustomerServiceChat.tsx
"use client";

import { CopilotKit, useCopilotChat } from "@copilotkit/react-core";
import { CopilotPopup } from "@copilotkit/react-ui";
import { useState, useRef, useEffect } from "react";
import "@copilotkit/react-ui/styles.css";

export function CustomerServiceChat() {
  return (
    <CopilotKit url="/api/copilotkit">
      <CopilotPopup
        defaultOpen={false}
        triggerLabel="在线咨询"
        // 自定义弹窗样式
        className="customer-service-popup"
        // 弹窗位置
        position="bottom-right"
      />
    </CopilotKit>
  );
}

// 更高级的客服组件 - 带有满意度评价
export function AdvancedCustomerService() {
  const { 
    messages, 
    isLoading, 
    sendMessage, 
    setMessages 
  } = useCopilotChat();
  const [inputValue, setInputValue] = useState("");
  const [showRating, setShowRating] = useState(false);
  const [rating, setRating] = useState(0);
  const messagesEndRef = useRef<HTMLDivElement>(null);

  // 自动滚动到底部
  useEffect(() => {
    messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
  }, [messages]);

  // 当对话结束时显示评价
  useEffect(() => {
    if (!isLoading && messages.length > 0) {
      const lastMessage = messages[messages.length - 1];
      if (lastMessage.role === "assistant") {
        setShowRating(true);
      }
    }
  }, [isLoading, messages]);

  const handleSend = async () => {
    if (!inputValue.trim() || isLoading) return;

    const userMessage = inputValue;
    setInputValue("");

    await sendMessage(userMessage);
  };

  const handleRating = async (score: number) => {
    setRating(score);
    // 发送评价到服务器
    console.log("用户评价:", score, "对话历史:", messages);

    setTimeout(() => {
      setShowRating(false);
      setRating(0);
    }, 2000);
  };

  return (
    <div className="flex flex-col h-[600px] w-[400px] bg-white rounded-lg shadow-xl overflow-hidden">
      {/* 头部 */}
      <div className="bg-blue-600 text-white p-4 flex items-center gap-3">
        <div className="w-10 h-10 bg-white rounded-full flex items-center justify-center text-blue-600 font-bold">
          AI
        </div>
        <div>
          <h3 className="font-semibold">智能客服助手</h3>
          <p className="text-xs text-blue-100">在线 · 随时为您服务</p>
        </div>
      </div>

      {/* 消息区域 */}
      <div className="flex-1 overflow-y-auto p-4 space-y-4">
        {messages.map((msg, index) => (
          <div
            key={index}
            className={`flex ${msg.role === "user" ? "justify-end" : "justify-start"}`}
          >
            <div
              className={`max-w-[80%] rounded-2xl px-4 py-3 ${
                msg.role === "user"
                  ? "bg-blue-500 text-white rounded-br-md"
                  : "bg-gray-100 text-gray-800 rounded-bl-md"
              }`}
            >
              <div className="text-sm whitespace-pre-wrap">
                {msg.content}
              </div>
              <div
                className={`text-xs mt-1 ${
                  msg.role === "user" ? "text-blue-100" : "text-gray-400"
                }`}
              >
                {new Date(msg.createdAt || Date.now()).toLocaleTimeString()}
              </div>
            </div>
          </div>
        ))}

        {isLoading && (
          <div className="flex justify-start">
            <div className="bg-gray-100 rounded-2xl rounded-bl-md px-4 py-3">
              <div className="flex gap-1">
                <span className="w-2 h-2 bg-gray-400 rounded-full animate-bounce" />
                <span className="w-2 h-2 bg-gray-400 rounded-full animate-bounce delay-75" />
                <span className="w-2 h-2 bg-gray-400 rounded-full animate-bounce delay-150" />
              </div>
            </div>
          </div>
        )}

        {/* 评价组件 */}
        {showRating && !isLoading && rating === 0 && (
          <div className="bg-gray-50 rounded-lg p-4 text-center">
            <p className="text-sm text-gray-600 mb-2">本次服务满意吗</p>
            <div className="flex justify-center gap-2">
              {[1, 2, 3, 4, 5].map((star) => (
                <button
                  key={star}
                  onClick={() => handleRating(star)}
                  className="text-2xl hover:scale-110 transition-transform"
                >
                  
                </button>
              ))}
            </div>
          </div>
        )}

        {rating > 0 && (
          <div className="bg-green-50 rounded-lg p-4 text-center">
            <p className="text-green-600 font-medium">感谢您的评价</p>
          </div>
        )}

        <div ref={messagesEndRef} />
      </div>

      {/* 输入区域 */}
      <div className="border-t p-4">
        <div className="flex gap-2">
          <input
            type="text"
            value={inputValue}
            onChange={(e) => setInputValue(e.target.value)}
            onKeyPress={(e) => e.key === "Enter" && handleSend()}
            placeholder="请输入您的问题..."
            className="flex-1 px-4 py-2 border rounded-full focus:outline-none focus:ring-2 focus:ring-blue-500"
          />
          <button
            onClick={handleSend}
            disabled={!inputValue.trim() || isLoading}
            className="px-6 py-2 bg-blue-500 text-white rounded-full hover:bg-blue-600 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
          >
            发送
          </button>
        </div>
      </div>
    </div>
  );
}

4.2 场景二:构建AI数据分析助手

需求分析

  • 用户可以上传数据文件
  • 用自然语言询问数据问题
  • AI自动生成SQL查询
  • 可视化展示结果

Step 1: 创建数据分析工具

// src/lib/analytics-tools.ts
"use client";

import { tool } from "@copilotkit/langchain";
import { z } from "zod";
import { analyzeCSV, generateChart } from "@/lib/data-processing";

// 数据分析工具
export const analyzeDataTool = tool({
  name: "analyze_data",
  description: `对上传的数据进行分析。这个工具可以:
  - 计算各种统计指标(平均值、中位数、总和等)
  - 按维度分组统计
  - 找出数据中的规律和异常
  - 生成数据洞察

  当用户询问数据分析、统计、趋势等问题时使用。`,

  parameters: z.object({
    action: z.enum([
      "summary",      // 总体统计
      "group_by",     // 分组统计
      "trend",        // 趋势分析
      "outlier",      // 异常检测
      "correlation",  // 相关性分析
    ]).describe("要执行的分析动作"),

    columns: z.array(z.string()).optional().describe("要分析的列"),

    groupBy: z.string().optional().describe("分组字段"),

    options: z.record(z.any()).optional().describe("其他选项"),
  }),

  execute: async ({ action, columns, groupBy, options }) => {
    // 获取当前加载的数据
    const data = getCurrentData();

    if (!data) {
      return "请先上传数据文件。";
    }

    switch (action) {
      case "summary":
        return calculateSummary(data, columns);

      case "group_by":
        return calculateGroupBy(data, groupBy, columns);

      case "trend":
        return analyzeTrend(data, columns, options);

      case "outlier":
        return detectOutliers(data, columns);

      case "correlation":
        return calculateCorrelation(data, columns);

      default:
        return "不支持的分析类型";
    }
  },
});

// 数据可视化工具
export const visualizeDataTool = tool({
  name: "visualize_data",
  description: `创建数据可视化图表。支持多种图表类型:
  - 折线图:展示趋势变化
  - 柱状图:比较不同类别
  - 饼图:展示占比
  - 散点图:展示相关性

  当用户需要图表来理解数据时使用。`,

  parameters: z.object({
    chartType: z.enum(["line", "bar", "pie", "scatter", "histogram"]),
    xAxis: z.string().describe("X轴字段"),
    yAxis: z.string().or(z.array(z.string())).describe("Y轴字段"),
    title: z.string().optional().describe("图表标题"),
    options: z.record(z.any()).optional().describe("其他配置"),
  }),

  execute: async ({ chartType, xAxis, yAxis, title, options }) => {
    const data = getCurrentData();

    if (!data) {
      return "请先上传数据文件。";
    }

    const chartConfig = {
      type: chartType,
      xAxis,
      yAxis,
      title: title || "数据可视化",
      data,
      ...options,
    };

    const chartHtml = generateChart(chartConfig);

    return {
      type: "chart",
      html: chartHtml,
      message: `已生成${getChartTypeName(chartType)}${title || "数据可视化"}`,
    };
  },
});

Step 2: 创建数据助手组件

// src/components/analytics/DataAssistant.tsx
"use client";

import { CopilotKit, useCopilotChat, useCopilotAction } from "@copilotkit/react-core";
import { useState, useRef } from "react";
import { Upload, FileSpreadsheet, MessageSquare, BarChart3 } from "lucide-react";
import "@copilotkit/react-ui/styles.css";

export function DataAssistant() {
  const [uploadedFile, setUploadedFile] = useState<File | null>(null);
  const [dataPreview, setDataPreview] = useState<any[]>([]);
  const [chartHtml, setChartHtml] = useState<string | null>(null);
  const fileInputRef = useRef<HTMLInputElement>(null);

  const { 
    messages, 
    isLoading, 
    sendMessage, 
    appendMessage 
  } = useCopilotChat();

  // 处理文件上传
  const handleFileUpload = async (event: React.ChangeEvent<HTMLInputElement>) => {
    const file = event.target.files?.[0];
    if (!file) return;

    setUploadedFile(file);

    // 解析CSV文件
    const text = await file.text();
    const data = parseCSV(text);
    setDataPreview(data.slice(0, 10)); // 只显示前10行预览

    // 将数据注入到AI上下文中
    await appendMessage({
      role: "system",
      content: `用户上传了数据文件:${file.name}
      数据包含 ${data.length} 行。
      列名:${Object.keys(data[0] || {}).join(", ")}
      数据预览:
      ${JSON.stringify(data.slice(0, 5), null, 2)}`,
    });
  };

  // 处理自然语言查询
  const handleQuery = async (query: string) => {
    await sendMessage(query);
  };

  // 监听AI返回的图表
  useCopilotAction({
    name: "visualize_data",
    description: "创建数据可视化图表",
    handler: async (result) => {
      if (result.type === "chart" && result.html) {
        setChartHtml(result.html);
      }
      return result.message;
    },
  });

  return (
    <CopilotKit url="/api/copilotkit">
      <div className="flex h-screen">
        {/* 左侧:数据面板 */}
        <div className="w-1/3 border-r bg-gray-50 p-6 overflow-y-auto">
          <h2 className="text-lg font-semibold mb-4 flex items-center gap-2">
            <FileSpreadsheet className="w-5 h-5" />
            数据面板
          </h2>

          {/* 文件上传 */}
          <div
            className="border-2 border-dashed border-gray-300 rounded-lg p-8 text-center hover:border-blue-500 cursor-pointer transition-colors"
            onClick={() => fileInputRef.current?.click()}
          >
            <Upload className="w-12 h-12 mx-auto text-gray-400 mb-2" />
            <p className="text-gray-600">
              {uploadedFile ? uploadedFile.name : "点击上传数据文件"}
            </p>
            <p className="text-sm text-gray-400 mt-1">
              支持 CSVExcel 格式
            </p>
            <input
              ref={fileInputRef}
              type="file"
              accept=".csv,.xlsx,.xls"
              onChange={handleFileUpload}
              className="hidden"
            />
          </div>

          {/* 数据预览 */}
          {dataPreview.length > 0 && (
            <div className="mt-6">
              <h3 className="font-medium mb-2">数据预览前10行</h3>
              <div className="overflow-x-auto">
                <table className="w-full text-sm">
                  <thead>
                    <tr>
                      {Object.keys(dataPreview[0]).map((key) => (
                        <th key={key} className="px-2 py-1 bg-gray-200 text-left">
                          {key}
                        </th>
                      ))}
                    </tr>
                  </thead>
                  <tbody>
                    {dataPreview.map((row, i) => (
                      <tr key={i} className="border-b">
                        {Object.values(row).map((val, j) => (
                          <td key={j} className="px-2 py-1">
                            {String(val)}
                          </td>
                        ))}
                      </tr>
                    ))}
                  </tbody>
                </table>
              </div>
            </div>
          )}

          {/* 图表展示 */}
          {chartHtml && (
            <div className="mt-6">
              <h3 className="font-medium mb-2 flex items-center gap-2">
                <BarChart3 className="w-4 h-4" />
                可视化结果
              </h3>
              <div
                className="bg-white rounded-lg p-4"
                dangerouslySetInnerHTML={{ __html: chartHtml }}
              />
            </div>
          )}
        </div>

        {/* 右侧:聊天界面 */}
        <div className="flex-1 flex flex-col bg-white">
          <div className="flex-1 overflow-y-auto p-6 space-y-4">
            {messages.length === 0 && (
              <div className="text-center text-gray-400 mt-20">
                <MessageSquare className="w-16 h-16 mx-auto mb-4 opacity-50" />
                <p>上传数据后用自然语言询问数据问题</p>
                <p className="text-sm mt-2">
                  例如"分析销售数据的趋势"
                </p>
              </div>
            )}

            {messages.map((msg, index) => (
              <div
                key={index}
                className={`flex ${msg.role === "user" ? "justify-end" : "justify-start"}`}
              >
                <div
                  className={`max-w-[70%] rounded-2xl px-4 py-3 ${
                    msg.role === "user"
                      ? "bg-blue-500 text-white"
                      : "bg-gray-100 text-gray-800"
                  }`}
                >
                  <div className="prose prose-sm max-w-none">
                    {msg.content}
                  </div>
                </div>
              </div>
            ))}

            {isLoading && (
              <div className="flex justify-start">
                <div className="bg-gray-100 rounded-2xl px-4 py-3">
                  <div className="flex gap-1">
                    <span className="w-2 h-2 bg-gray-400 rounded-full animate-bounce" />
                    <span className="w-2 h-2 bg-gray-400 rounded-full animate-bounce delay-75" />
                    <span className="w-2 h-2 bg-gray-400 rounded-full animate-bounce delay-150" />
                  </div>
                </div>
              </div>
            )}
          </div>

          {/* 输入框 */}
          <div className="border-t p-4">
            <div className="flex gap-2 max-w-3xl mx-auto">
              <input
                type="text"
                placeholder="用自然语言询问数据..."
                className="flex-1 px-4 py-3 border rounded-full focus:outline-none focus:ring-2 focus:ring-blue-500"
                onKeyPress={(e) => {
                  if (e.key === "Enter" && !isLoading) {
                    handleQuery(e.currentTarget.value);
                    e.currentTarget.value = "";
                  }
                }}
              />
            </div>
          </div>
        </div>
      </div>
    </CopilotKit>
  );
}

// CSV解析辅助函数
function parseCSV(text: string): any[] {
  const lines = text.split("\n");
  const headers = lines[0].split(",").map(h => h.trim());

  return lines.slice(1).filter(line => line.trim()).map(line => {
    const values = line.split(",").map(v => v.trim());
    const row: any = {};
    headers.forEach((header, i) => {
      row[header] = values[i] || "";
    });
    return row;
  });
}

4.3 场景三:构建AI代码审查助手

需求分析

  • 自动分析代码问题
  • 提供修复建议
  • 生成代码改进报告
  • 支持多种编程语言

Step 1: 创建代码审查工具

// src/lib/code-review-tools.ts
"use client";

import { tool } from "@copilotkit/langchain";
import { z } from "zod";
import { readFile, glob } from "fs/promises";
import path from "path";

// 代码审查工具
export const codeReviewTool = tool({
  name: "review_code",
  description: `对代码进行全面审查。检查内容包括:
  - 代码质量问题(复杂度、重复代码)
  - 安全漏洞(SQL注入、XSS、敏感信息泄露)
  - 性能问题(内存泄漏、无效循环)
  - 代码风格和最佳实践
  - 测试覆盖率

  当用户要求审查代码或指出代码问题时使用。`,

  parameters: z.object({
    filePath: z.string().describe("要审查的文件路径"),
    reviewTypes: z.array(
      z.enum(["security", "performance", "style", "best-practice"])
    ).optional().describe("要检查的类型"),
  }),

  execute: async ({ filePath, reviewTypes = ["security", "performance", "style"] }) => {
    try {
      const absolutePath = path.resolve(filePath);
      const content = await readFile(absolutePath, "utf-8");
      const extension = path.extname(filePath);

      const results: string[] = [];

      // 安全检查
      if (reviewTypes.includes("security")) {
        const securityIssues = checkSecurity(content, extension);
        results.push(...securityIssues);
      }

      // 性能检查
      if (reviewTypes.includes("performance")) {
        const performanceIssues = checkPerformance(content, extension);
        results.push(...performanceIssues);
      }

      // 代码风格
      if (reviewTypes.includes("style")) {
        const styleIssues = checkStyle(content, extension);
        results.push(...styleIssues);
      }

      if (results.length === 0) {
        return `✅ 代码审查通过!\n\n文件:${filePath}\n未发现问题。`;
      }

      return `🔍 代码审查报告\n\n文件:${filePath}\n\n发现问题 ${results.length} 个:\n\n${results.join("\n\n")}`;
    } catch (error) {
      return `无法读取文件:${error.message}`;
    }
  },
});

// 安全检查规则
function checkSecurity(content: string, extension: string): string[] {
  const issues: string[] = [];

  // 模板:SQL注入检测
  if (content.includes("SELECT") && content.includes("+") && extension === ".js") {
    issues.push(`⚠️ 安全风险:可能存在SQL注入\n行号:${findLineNumber(content, "SELECT")}\n建议:使用参数化查询`);
  }

  // 模板:硬编码密码
  if (content.match(/password\s*=\s*["'][^"']{1,50}["']/i)) {
    issues.push(`🔴 安全漏洞:发现硬编码密码\n建议:将敏感信息移至环境变量`);
  }

  // 模板:eval使用
  if (content.includes("eval(")) {
    issues.push(`⚠️ 安全风险:使用eval可能引发安全问题\n建议:考虑使用更安全的替代方案`);
  }

  return issues;
}

// 性能检查规则
function checkPerformance(content: string, extension: string): string[] {
  const issues: string[] = [];

  // 嵌套循环检测
  const nestedLoops = detectNestedLoops(content);
  if (nestedLoops > 2) {
    issues.push(`⚠️ 性能问题:发现${nestedLoops}层嵌套循环\n建议:考虑优化算法复杂度`);
  }

  // 大数组操作
  if (content.includes(".map(") && content.includes(".map(").length > 5) {
    issues.push(`💡 性能提示:连续使用多个map操作\n建议:考虑合并或使用流式处理`);
  }

  return issues;
}

// 代码风格检查
function checkStyle(content: string, extension: string): string[] {
  const issues: string[] = [];

  // 过长函数检测
  const functionLengths = detectFunctionLengths(content);
  for (const { name, length } of functionLengths) {
    if (length > 100) {
      issues.push(`📝 代码风格:函数 ${name} 过长 (${length}行)\n建议:考虑拆分为更小的函数`);
    }
  }

  // 缺少注释
  const linesWithoutComments = countUncommentedLines(content);
  if (linesWithoutComments > 100) {
    issues.push(`📝 代码风格:${linesWithoutComments}行代码缺少注释\n建议:添加必要的代码注释`);
  }

  return issues;
}

// 辅助函数
function findLineNumber(content: string, keyword: string): number {
  const lines = content.split("\n");
  for (let i = 0; i < lines.length; i++) {
    if (lines[i].includes(keyword)) {
      return i + 1;
    }
  }
  return 0;
}

function detectNestedLoops(content: string): number {
  let maxDepth = 0;
  let currentDepth = 0;
  const lines = content.split("\n");

  for (const line of lines) {
    const forMatches = (line.match(/\bfor\s*\(/g) || []).length;
    const whileMatches = (line.match(/\bwhile\s*\(/g) || []).length;
    const openBraces = (line.match(/\{/g) || []).length;
    const closeBraces = (line.match(/\}/g) || []).length;

    currentDepth += forMatches + whileMatches;
    maxDepth = Math.max(maxDepth, currentDepth);
    currentDepth -= closeBraces;
  }

  return maxDepth;
}

function detectFunctionLengths(content: string): Array<{ name: string; length: number }> {
  const results: Array<{ name: string; length: number }> = [];
  const lines = content.split("\n");

  let inFunction = false;
  let functionName = "";
  let functionStart = 0;
  let braceCount = 0;

  for (let i = 0; i < lines.length; i++) {
    const line = lines[i];

    // 检测函数开始
    const funcMatch = line.match(/(?:function|def|const|let|var)\s+(\w+)\s*[=\(]/);
    if (funcMatch && !inFunction) {
      inFunction = true;
      functionName = funcMatch[1];
      functionStart = i;
      braceCount = 0;
    }

    // 统计大括号或缩进
    if (inFunction) {
      braceCount += (line.match(/\{/g) || []).length;
      braceCount -= (line.match(/\}/g) || []).length;

      if (braceCount === 0 && line.includes("}")) {
        results.push({
          name: functionName,
          length: i - functionStart + 1,
        });
        inFunction = false;
      }
    }
  }

  return results;
}

function countUncommentedLines(content: string): number {
  const lines = content.split("\n");
  let count = 0;

  for (const line of lines) {
    const trimmed = line.trim();
    if (
      trimmed.length > 0 &&
      !trimmed.startsWith("//") &&
      !trimmed.startsWith("#") &&
      !trimmed.startsWith("/*") &&
      !trimmed.startsWith("*")
    ) {
      count++;
    }
  }

  return count;
}

Step 2: 创建代码审查界面

// src/components/code-review/CodeReviewPanel.tsx
"use client";

import { CopilotKit, useCopilotChat, useCopilotAction } from "@copilotkit/react-core";
import { useState } from "react";
import { FileCode, AlertTriangle, CheckCircle, Lightbulb } from "lucide-react";
import "@copilotkit/react-ui/styles.css";

interface ReviewResult {
  filePath: string;
  issues: Array<{
    type: "error" | "warning" | "info";
    message: string;
    line?: number;
    suggestion?: string;
  }>;
  summary: {
    total: number;
    errors: number;
    warnings: number;
    suggestions: number;
  };
}

export function CodeReviewPanel() {
  const [currentFile, setCurrentFile] = useState<string>("");
  const [reviewResults, setReviewResults] = useState<ReviewResult[]>([]);
  const [selectedResult, setSelectedResult] = useState<ReviewResult | null>(null);

  const { messages, isLoading, sendMessage } = useCopilotChat();

  // 监听AI的代码审查结果
  useCopilotAction({
    name: "review_code",
    description: "审查代码文件",
    handler: async (result) => {
      // 解析审查结果
      const parsed = parseReviewResult(result, currentFile);
      setReviewResults((prev) => [...prev, parsed]);
      setSelectedResult(parsed);
      return result;
    },
  });

  const handleReviewRequest = async () => {
    if (!currentFile) return;

    await sendMessage(`请审查这个文件:${currentFile}`);
  };

  return (
    <CopilotKit url="/api/copilotkit">
      <div className="flex h-screen">
        {/* 左侧:文件列表 */}
        <div className="w-64 border-r bg-gray-50 p-4">
          <h2 className="font-semibold mb-4 flex items-center gap-2">
            <FileCode className="w-5 h-5" />
            文件列表
          </h2>

          <input
            type="text"
            placeholder="输入文件路径..."
            value={currentFile}
            onChange={(e) => setCurrentFile(e.target.value)}
            className="w-full px-3 py-2 border rounded mb-4 text-sm"
          />

          <button
            onClick={handleReviewRequest}
            disabled={!currentFile || isLoading}
            className="w-full px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 disabled:opacity-50 mb-4"
          >
            {isLoading ? "审查中..." : "开始审查"}
          </button>

          {/* 审查历史 */}
          <div className="space-y-2">
            <h3 className="text-sm font-medium text-gray-500">审查历史</h3>
            {reviewResults.map((result, index) => (
              <button
                key={index}
                onClick={() => setSelectedResult(result)}
                className={`w-full text-left p-2 rounded text-sm ${
                  selectedResult === result
                    ? "bg-blue-100 border border-blue-300"
                    : "hover:bg-gray-100"
                }`}
              >
                <div className="truncate">{path.basename(result.filePath)}</div>
                <div className="flex gap-2 mt-1">
                  <span className="text-red-500">{result.summary.errors}</span>
                  <span className="text-yellow-500">{result.summary.warnings}</span>
                  <span className="text-blue-500">{result.summary.suggestions}</span>
                </div>
              </button>
            ))}
          </div>
        </div>

        {/* 中间:代码展示 */}
        <div className="flex-1 p-6 overflow-auto">
          {selectedResult ? (
            <div>
              <h2 className="text-xl font-semibold mb-4">
                审查报告{selectedResult.filePath}
              </h2>

              {/* 统计摘要 */}
              <div className="grid grid-cols-4 gap-4 mb-6">
                <div className="bg-gray-100 rounded-lg p-4 text-center">
                  <div className="text-2xl font-bold">{selectedResult.summary.total}</div>
                  <div className="text-sm text-gray-600">总问题数</div>
                </div>
                <div className="bg-red-50 rounded-lg p-4 text-center">
                  <div className="text-2xl font-bold text-red-600">
                    {selectedResult.summary.errors}
                  </div>
                  <div className="text-sm text-red-600">错误</div>
                </div>
                <div className="bg-yellow-50 rounded-lg p-4 text-center">
                  <div className="text-2xl font-bold text-yellow-600">
                    {selectedResult.summary.warnings}
                  </div>
                  <div className="text-sm text-yellow-600">警告</div>
                </div>
                <div className="bg-blue-50 rounded-lg p-4 text-center">
                  <div className="text-2xl font-bold text-blue-600">
                    {selectedResult.summary.suggestions}
                  </div>
                  <div className="text-sm text-blue-600">建议</div>
                </div>
              </div>

              {/* 问题列表 */}
              <div className="space-y-3">
                {selectedResult.issues.map((issue, index) => (
                  <div
                    key={index}
                    className={`border rounded-lg p-4 ${
                      issue.type === "error"
                        ? "border-red-200 bg-red-50"
                        : issue.type === "warning"
                        ? "border-yellow-200 bg-yellow-50"
                        : "border-blue-200 bg-blue-50"
                    }`}
                  >
                    <div className="flex items-start gap-3">
                      {issue.type === "error" ? (
                        <AlertTriangle className="w-5 h-5 text-red-500 flex-shrink-0" />
                      ) : issue.type === "warning" ? (
                        <AlertTriangle className="w-5 h-5 text-yellow-500 flex-shrink-0" />
                      ) : (
                        <Lightbulb className="w-5 h-5 text-blue-500 flex-shrink-0" />
                      )}
                      <div className="flex-1">
                        <p className="font-medium">{issue.message}</p>
                        {issue.line && (
                          <p className="text-sm text-gray-500 mt-1">
                            行号{issue.line}
                          </p>
                        )}
                        {issue.suggestion && (
                          <p className="text-sm text-green-600 mt-2">
                            💡 建议{issue.suggestion}
                          </p>
                        )}
                      </div>
                    </div>
                  </div>
                ))}
              </div>

              {selectedResult.summary.total === 0 && (
                <div className="text-center py-12 text-gray-400">
                  <CheckCircle className="w-16 h-16 mx-auto mb-4 text-green-500" />
                  <p className="text-lg">代码审查通过</p>
                  <p>未发现问题</p>
                </div>
              )}
            </div>
          ) : (
            <div className="flex items-center justify-center h-full text-gray-400">
              <div className="text-center">
                <FileCode className="w-16 h-16 mx-auto mb-4 opacity-50" />
                <p>选择一个文件开始审查</p>
              </div>
            </div>
          )}
        </div>

        {/* 右侧:AI对话 */}
        <div className="w-80 border-l bg-gray-50 flex flex-col">
          <div className="p-4 border-b bg-white">
            <h3 className="font-semibold">AI 助手</h3>
          </div>

          <div className="flex-1 overflow-y-auto p-4 space-y-3">
            {messages.map((msg, index) => (
              <div
                key={index}
                className={`text-sm ${
                  msg.role === "user" ? "text-right" : "text-left"
                }`}
              >
                <div
                  className={`inline-block px-3 py-2 rounded-lg ${
                    msg.role === "user"
                      ? "bg-blue-500 text-white"
                      : "bg-white border"
                  }`}
                >
                  {msg.content}
                </div>
              </div>
            ))}
          </div>
        </div>
      </div>
    </CopilotKit>
  );
}

// 解析审查结果
function parseReviewResult(text: string, filePath: string): ReviewResult {
  const issues: ReviewResult["issues"] = [];

  // 简单的文本解析(实际项目应该用更复杂的解析逻辑)
  const lines = text.split("\n");
  for (const line of lines) {
    if (line.includes("🔴")) {
      issues.push({
        type: "error",
        message: line.replace("🔴", "").trim(),
      });
    } else if (line.includes("⚠️")) {
      issues.push({
        type: "warning",
        message: line.replace("⚠️", "").trim(),
      });
    } else if (line.includes("📝") || line.includes("💡")) {
      issues.push({
        type: "info",
        message: line.replace("📝", "").replace("💡", "").trim(),
      });
    }
  }

  return {
    filePath,
    issues,
    summary: {
      total: issues.length,
      errors: issues.filter(i => i.type === "error").length,
      warnings: issues.filter(i => i.type === "warning").length,
      suggestions: issues.filter(i => i.type === "info").length,
    },
  };
}

// path.basename polyfill
const path = {
  basename: (p: string) => p.split(/[\\/]/).pop() || p,
};

五、高级使用技巧

5.1 自定义AI模型配置

CopilotKit支持多种AI模型,你可以根据需要选择:

// src/lib/copilotkit-config.ts
import { CopilotRuntime, OpenAIAdapter, AnthropicAdapter } from "@copilotkit/runtime";

// OpenAI配置
const openaiAdapter = new OpenAIAdapter({
  model: "gpt-4-turbo",
  temperature: 0.7,
  maxTokens: 2000,
  topP: 0.9,
});

// Anthropic配置
const anthropicAdapter = new AnthropicAdapter({
  model: "claude-3-sonnet-20240229",
  maxTokens: 2000,
});

// 本地模型配置(使用Ollama等)
const localAdapter = new OpenAIAdapter({
  model: "llama2",
  apiKey: "ollama", // 特殊标记
  baseUrl: "http://localhost:11434/v1",
});

// 根据环境选择适配器
export function getAdapter() {
  const modelProvider = process.env.MODEL_PROVIDER;

  switch (modelProvider) {
    case "anthropic":
      return anthropicAdapter;
    case "local":
      return localAdapter;
    case "openai":
    default:
      return openaiAdapter;
  }
}

5.2 多语言支持

// src/lib/i18n.ts
export const translations = {
  zh: {
    welcome: "你好!有什么可以帮助你的?",
    placeholder: "输入消息...",
    send: "发送",
    thinking: "思考中...",
    error: "出错了,请重试",
  },
  en: {
    welcome: "Hello! How can I help you?",
    placeholder: "Type a message...",
    send: "Send",
    thinking: "Thinking...",
    error: "Something went wrong. Please try again.",
  },
  ja: {
    welcome: "こんにちは!何かお手伝いできることはありますか?",
    placeholder: "メッセージを入力...",
    send: "送信",
    thinking: "考え中...",
    error: "エラーが発生しました。もう一度お試しください。",
  },
};

// 使用翻译
export function useTranslation(lang: keyof typeof translations = "zh") {
  return translations[lang];
}

5.3 性能优化

// src/components/copilot/OptimizedChat.tsx
"use client";

import { 
  CopilotKit, 
  useCopilotChat,
  useLazyCopilotChat 
} from "@copilotkit/react-core";
import { useMemo, useCallback } from "react";

// 大量消息时使用虚拟列表
import { FixedSizeList } from "react-window";

export function OptimizedChat() {
  // useLazyCopilotChat 是懒加载版本
  // 适用于不需要立即初始化的情况
  const chat = useLazyCopilotChat({
    // 限制保存的消息数量
    maxMessages: 100,
    // 消息截断配置
    truncateMessages: {
      maxTokens: 8000,
      strategy: "newest", // 保留最新的消息
    },
  });

  // 使用 useMemo 缓存消息组件
  const MessageComponents = useMemo(() => {
    return chat.messages.map((msg, i) => (
      <MessageItem key={msg.id || i} message={msg} />
    ));
  }, [chat.messages]);

  // 使用 useCallback 缓存回调
  const handleSend = useCallback(async (content: string) => {
    await chat.sendMessage(content);
  }, [chat]);

  return (
    <div>
      {/* 使用固定高度列表提高渲染性能 */}
      <FixedSizeList
        height={500}
        itemCount={chat.messages.length}
        itemSize={80}
        width="100%"
      >
        {({ index, style }) => (
          <div style={style}>
            <MessageItem message={chat.messages[index]} />
          </div>
        )}
      </FixedSizeList>
    </div>
  );
}

5.4 事件追踪与监控

// src/lib/analytics.ts
"use client";

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

// 事件追踪钩子
export function useChatAnalytics() {
  const chat = useCopilotChat();

  // 追踪消息发送
  const trackMessageSent = (content: string) => {
    // 发送到你的分析服务
    analytics.track("chat_message_sent", {
      content,
      contentLength: content.length,
      timestamp: Date.now(),
    });
  };

  // 追踪响应时间
  const trackResponseTime = (startTime: number, endTime: number) => {
    const duration = endTime - startTime;

    analytics.track("chat_response_time", {
      duration,
      durationBucket: getDurationBucket(duration),
    });
  };

  // 追踪工具调用
  const trackToolUsage = (toolName: string, success: boolean) => {
    analytics.track("chat_tool_used", {
      toolName,
      success,
    });
  };

  return {
    trackMessageSent,
    trackResponseTime,
    trackToolUsage,
  };
}

function getDurationBucket(duration: number): string {
  if (duration < 1000) return "fast";
  if (duration < 5000) return "normal";
  if (duration < 10000) return "slow";
  return "very_slow";
}

六、最佳实践与注意事项

6.1 安全最佳实践

// src/lib/security.ts
"use client";

import { tool } from "@copilotkit/langchain";
import { z } from "zod";

// 安全的文件操作工具(带权限检查)
export const secureFileTool = tool({
  name: "secure_read_file",
  description: "读取文件(仅限授权目录)",
  parameters: z.object({
    filePath: z.string(),
  }),

  execute: async ({ filePath }) => {
    // 1. 验证文件路径
    const allowedPaths = ["/app/data", "/app/config"];
    const isAllowed = allowedPaths.some(p => filePath.startsWith(p));

    if (!isAllowed) {
      throw new Error("权限不足:无法访问此文件");
    }

    // 2. 验证文件类型
    const allowedExtensions = [".json", ".txt", ".csv"];
    const ext = path.extname(filePath);

    if (!allowedExtensions.includes(ext)) {
      throw new Error("不支持的文件类型");
    }

    // 3. 记录访问日志
    console.log(`[安全日志] 用户读取文件: ${filePath}`);

    // 4. 执行实际操作
    return await readFile(filePath);
  },
});

// 输入验证和清理
export function sanitizeInput(input: string): string {
  return input
    .replace(/[<>]/g, "") // 移除HTML标签
    .trim()
    .slice(0, 10000); // 限制长度
}

6.2 错误处理

// src/components/copilot/ErrorHandledChat.tsx
"use client";

import { 
  CopilotKit, 
  useCopilotChat,
  CopilotKitError 
} from "@copilotkit/react-core";
import { useState } from "react";

export function ErrorHandledChat() {
  const [error, setError] = useState<string | null>(null);
  const [isRetrying, setIsRetrying] = useState(false);

  const chat = useCopilotChat({
    onError: (error) => {
      if (error instanceof CopilotKitError) {
        // 处理API错误
        switch (error.code) {
          case "RATE_LIMIT":
            setError("请求过于频繁,请稍后再试");
            break;
          case "INVALID_API_KEY":
            setError("API配置错误,请检查设置");
            break;
          case "MODEL_UNAVAILABLE":
            setError("当前模型不可用,已自动切换到备用方案");
            break;
          default:
            setError("发生未知错误");
        }
      } else {
        setError("网络连接失败");
      }
    },
  });

  const handleRetry = async () => {
    setIsRetrying(true);
    setError(null);

    try {
      // 重试最后一次消息
      const lastUserMessage = chat.messages
        .filter(m => m.role === "user")
        .pop();

      if (lastUserMessage) {
        await chat.sendMessage(lastUserMessage.content);
      }
    } finally {
      setIsRetrying(false);
    }
  };

  return (
    <div>
      {error && (
        <div className="bg-red-50 border border-red-200 rounded-lg p-4 mb-4">
          <p className="text-red-700">{error}</p>
          <button
            onClick={handleRetry}
            disabled={isRetrying}
            className="mt-2 px-4 py-2 bg-red-500 text-white rounded hover:bg-red-600 disabled:opacity-50"
          >
            {isRetrying ? "重试中..." : "重试"}
          </button>
        </div>
      )}

      {/* 聊天内容 */}
      <ChatMessages messages={chat.messages} />
    </div>
  );
}

6.3 测试策略

// src/__tests__/copilot-integration.test.tsx
"use client";

import { render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { CopilotKit } from "@copilotkit/react-core";
import { ChatComponent } from "@/components/ChatComponent";

// Mock API响应
const mockApiResponse = {
  choices: [
    {
      message: {
        content: "这是一个测试响应",
      },
    },
  ],
};

describe("CopilotKit 集成测试", () => {
  it("应该正确渲染聊天界面", async () => {
    render(
      <CopilotKit url="/api/copilotkit">
        <ChatComponent />
      </CopilotKit>
    );

    // 检查主要元素
    expect(screen.getByPlaceholderText("输入消息...")).toBeInTheDocument();
    expect(screen.getByRole("button", { name: "发送" })).toBeInTheDocument();
  });

  it("应该能够发送消息并接收响应", async () => {
    // Mock fetch
    global.fetch = jest.fn().mockResolvedValue({
      json: () => Promise.resolve(mockApiResponse),
    }) as jest.Mock;

    render(
      <CopilotKit url="/api/copilotkit">
        <ChatComponent />
      </CopilotKit>
    );

    const input = screen.getByPlaceholderText("输入消息...");
    const sendButton = screen.getByRole("button", { name: "发送" });

    // 输入并发送消息
    await userEvent.type(input, "你好");
    await userEvent.click(sendButton);

    // 等待响应
    await waitFor(() => {
      expect(screen.getByText("这是一个测试响应")).toBeInTheDocument();
    });
  });

  it("应该显示加载状态", async () => {
    // Mock一个延迟响应
    global.fetch = jest.fn().mockImplementation(() => {
      return new Promise((resolve) => {
        setTimeout(() => {
          resolve({
            json: () => Promise.resolve(mockApiResponse),
          });
        }, 1000);
      });
    }) as jest.Mock;

    render(
      <CopilotKit url="/api/copilotkit">
        <ChatComponent />
      </CopilotKit>
    );

    const sendButton = screen.getByRole("button", { name: "发送" });
    await userEvent.click(sendButton);

    // 检查加载指示器
    expect(screen.getByText("发送中...")).toBeInTheDocument();
  });
});

七、总结与资源推荐

7.1 CopilotKit的核心价值

通过本文的详细讲解,我们可以看到CopilotKit为AI应用开发带来了革命性的变化:

开发效率提升

  • 从数天的开发时间缩短到几小时
  • 代码量从数千行减少到几百行
  • 无需深入了解AI底层原理

用户体验优化

  • 内置流式输出,响应即时可见
  • 丰富的UI组件,开箱即用
  • 支持多种交互模式

功能强大

  • 完善的Agent框架
  • 灵活的工具系统
  • 支持多种AI模型

7.2 学习路径建议

# 学习路线图

第一阶段入门1-2
  ├── 安装和配置环境
  ├── 创建第一个简单的AI助手
  └── 了解基本概念和组件

第二阶段进阶3-5
  ├── 学习工具定义和使用
  ├── 掌握上下文管理
  └── 实现一个完整的客服系统

第三阶段精通1-2
  ├── 自定义AI模型配置
  ├── 性能优化技巧
  ├── 安全最佳实践
  └── 项目实战经验积累

7.3 相关资源推荐

官方资源

  • GitHub仓库:https://github.com/CopilotKit/CopilotKit
  • 官方文档:https://docs.copilotkit.ai
  • 示例代码:https://github.com/CopilotKit/CopilotKit/tree/main/examples

相关AI项目推荐

项目名称 简介 适用场景
LangChain 构建AI应用的通用框架 复杂的AI应用
AutoGPT 自主AI代理 实验性项目
Semantic Kernel 微软的AI编排框架 企业应用
Guidance 微软的结构化输出框架 需要精确控制输出

社区资源

  • Discord社区:加入与其他开发者交流
  • GitHub Discussions:提问和分享经验
  • Twitter:关注最新动态

7.4 展望未来

CopilotKit作为一个快速发展的开源项目,未来可能在以下方向持续进化:

# 未来可能的功能方向

1. 更多预置工具
   ├── 图像处理工具
   ├── 数据库操作工具
   └── API集成工具

2. 更智能的Agent
   ├── 多Agent协作
   ├── 自主学习能力
   └── 更强的推理能力

3. 更好的开发体验
   ├── 可视化调试工具
   ├── 更强大的调试能力
   └── 实时预览功能

4. 企业级特性
   ├── 多租户支持
   ├── 更细粒度的权限控制
   └── 合规性支持

写在最后

CopilotKit的出现标志着AI应用开发进入了一个新的阶段。它让开发者能够专注于创造价值,而不是被技术细节所困扰。无论你是想快速为现有产品添加AI能力,还是想从头构建一个AI原生应用,CopilotKit都值得一试。

希望这篇教程能够帮助你快速上手CopilotKit。如果有任何问题,欢迎在评论区留言交流!

祝编码愉快! 🚀

如果内容对您有帮助,欢迎打赏

您的支持是我继续创作的动力

前往打赏页面

评论区

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注