构建一个带有 AI 助手的电子表格应用程序(Next.js、gpt4、LangChain 和 CopilotKit)
CopilotKit:用于构建应用内AI副驾驶的框架
太长不看
在本文中,您将学习如何构建一个人工智能驱动的电子表格应用程序,该应用程序允许您执行各种会计功能,并使用简单的英语命令轻松地与数据进行交互。
我们将介绍如何做到以下几点:
- 使用 Next.js 构建 Web 应用程序
- 使用 React Spreadsheet 创建电子表格应用程序,
- 使用 CopilotKit 将 AI 集成到软件应用程序中。
- 让电子表格更易于使用,更有趣
CopilotKit:用于构建应用内AI副驾驶的框架
CopilotKit 是一个 开源的 AI 辅助驾驶平台。我们让您轻松地将强大的 AI 集成到您的 React 应用中。
建造:
- 聊天机器人:能够感知上下文并执行应用内操作的应用内聊天机器人💬
- CopilotTextArea:AI驱动的文本框,具备上下文感知自动完成和插入功能📝
- 协同代理:应用内人工智能代理,可以与您的应用和用户进行互动🤖
(请原谅人工智能的拼写错误,并给 CopilotKit 点个赞 :)
现在回到文章正文!
先决条件
要完全理解本教程,您需要对 React 或 Next.js 有一定的了解。
以下是构建人工智能驱动的电子表格应用程序所需的工具:
- React Spreadsheet—— 一个简单的软件包,使我们能够在 React 应用程序中添加电子表格。
- OpenAI API - 提供 API 密钥,使我们能够使用 ChatGPT 模型执行各种任务。
- Tavily AI—— 一款搜索引擎,使人工智能代理能够在应用程序内进行研究并访问实时知识。
- CopilotKit - 一个开源的辅助驾驶框架,用于构建自定义 AI 聊天机器人、应用内 AI 代理和文本区域。
项目设置和软件包安装
首先,在终端中运行以下代码片段,创建一个 Next.js 应用程序:
npx create-next-app spreadsheet-app
选择您偏好的配置设置。在本教程中,我们将使用 TypeScript 和 Next.js 应用路由。
接下来,安装 OpenAI 库、Heroicons和React Spreadsheet 包及其依赖项:
npm install openai react-spreadsheet scheduler @heroicons/react
最后,安装 CopilotKit 包。这些包使我们能够从 React 状态中检索数据,并将 AI 辅助功能添加到应用程序中。
npm install @copilotkit/react-ui @copilotkit/react-textarea @copilotkit/react-core @copilotkit/backend
恭喜!您现在可以开始构建应用程序了。
构建电子表格应用程序
在本节中,我将引导您使用 React Spreadsheet 构建电子表格应用程序。
该应用程序分为两个部分:Sidebar和SingleSpreadsheet。
要设置这些组件,请导航到 Next.js 应用文件夹并创建一个components包含以下文件的文件夹:
cd app
mkdir components && cd components
touch Sidebar.tsx SingleSpreadsheet.tsx
将新创建的组件导入到app/page.tsx文件中。
"use client";
import React, { useState } from "react";
//👇🏻 import the components
import { SpreadsheetData } from "./types";
import Sidebar from "./components/Sidebar";
import SingleSpreadsheet from "./components/SingleSpreadsheet";
const Main = () => {
return (
<div className='flex'>
<p>Hello world</p>
</div>
);
};
export default Main;
接下来,创建用于存储电子表格数据的 React 状态,并将它们作为 props 传递给组件。
const Main = () => {
//👇🏻 holds the title and data within a spreadsheet
const [spreadsheets, setSpreadsheets] = React.useState<SpreadsheetData[]>([
{
title: "Spreadsheet 1",
data: [
[{ value: "" }, { value: "" }, { value: "" }],
[{ value: "" }, { value: "" }, { value: "" }],
[{ value: "" }, { value: "" }, { value: "" }],
],
},
]);
//👇🏻 represents the index of a spreadsheet
const [selectedSpreadsheetIndex, setSelectedSpreadsheetIndex] = useState(0);
return (
<div className='flex'>
<Sidebar
spreadsheets={spreadsheets}
selectedSpreadsheetIndex={selectedSpreadsheetIndex}
setSelectedSpreadsheetIndex={setSelectedSpreadsheetIndex}
/>
<SingleSpreadsheet
spreadsheet={spreadsheets[selectedSpreadsheetIndex]}
setSpreadsheet={(spreadsheet) => {
setSpreadsheets((prev) => {
console.log("setSpreadsheet", spreadsheet);
const newSpreadsheets = [...prev];
newSpreadsheets[selectedSpreadsheetIndex] = spreadsheet;
return newSpreadsheets;
});
}}
/>
</div>
);
};
该代码片段创建 React 状态,用于保存电子表格数据及其索引,并将它们作为 props 传递给组件。
该Sidebar组件接受所有可用的电子表格,并且该SingleSpreadsheet组件接收所有电子表格,包括setSpreadsheet更新电子表格数据的函数。
将以下代码片段复制到Sidebar.tsx文件中。它会显示应用程序中的所有电子表格,并允许用户在它们之间切换。
import React from "react";
import { SpreadsheetData } from "../types";
interface SidebarProps {
spreadsheets: SpreadsheetData[];
selectedSpreadsheetIndex: number;
setSelectedSpreadsheetIndex: (index: number) => void;
}
const Sidebar = ({
spreadsheets,
selectedSpreadsheetIndex,
setSelectedSpreadsheetIndex,
}: SidebarProps) => {
return (
<div className='w-64 h-screen bg-gray-800 text-white overflow-auto p-5'>
<ul>
{spreadsheets.map((spreadsheet, index) => (
<li
key={index}
className={`mb-4 cursor-pointer ${
index === selectedSpreadsheetIndex
? "ring-2 ring-blue-500 ring-inset p-3 rounded-lg"
: "p-3"
}`}
onClick={() => setSelectedSpreadsheetIndex(index)}
>
{spreadsheet.title}
</li>
))}
</ul>
</div>
);
};
export default Sidebar;
SingleSpreadsheet.tsx请按如下方式更新文件:
import React from "react";
import Spreadsheet from "react-spreadsheet";
import { SpreadsheetData, SpreadsheetRow } from "../types";
interface MainAreaProps {
spreadsheet: SpreadsheetData;
setSpreadsheet: (spreadsheet: SpreadsheetData) => void;
}
//👇🏻 adds a new row to the spreadsheet
const addRow = () => {
const numberOfColumns = spreadsheet.rows[0].length;
const newRow: SpreadsheetRow = [];
for (let i = 0; i < numberOfColumns; i++) {
newRow.push({ value: "" });
}
setSpreadsheet({
...spreadsheet,
rows: [...spreadsheet.rows, newRow],
});
};
//👇🏻 adds a new column to the spreadsheet
const addColumn = () => {
const spreadsheetData = [...spreadsheet.data];
for (let i = 0; i < spreadsheet.data.length; i++) {
spreadsheet.data[i].push({ value: "" });
}
setSpreadsheet({
...spreadsheet,
data: spreadsheetData,
});
};
const SingleSpreadsheet = ({ spreadsheet, setSpreadsheet }: MainAreaProps) => {
return (
<div className='flex-1 overflow-auto p-5'>
{/** -- Spreadsheet title ---*/}
<div className='flex items-start'>
{/** -- Spreadsheet rows and columns---*/}
{/** -- Add column button ---*/}
</div>
{/** -- Add row button ---*/}
</div>
);
};
export default SingleSpreadsheet;
- 从上面的代码片段可以看出:
- 该
SingleSpreadsheet.tsx文件包含addRow和addColumn函数。 - 该
addRow函数计算当前行数,添加新行,并相应地更新电子表格。 - 同样,该
addColumn函数会在电子表格中添加一列。 - 该
SingleSpreadsheet组件用于渲染用户界面元素的占位符。
- 该
更新SingleSpreadsheet组件,使其能够渲染电子表格标题、数据以及添加行和列按钮。
return (
<div className='flex-1 overflow-auto p-5'>
{/** -- Spreadsheet title ---*/}
<input
type='text'
value={spreadsheet.title}
className='w-full p-2 mb-5 text-center text-2xl font-bold outline-none bg-transparent'
onChange={(e) =>
setSpreadsheet({ ...spreadsheet, title: e.target.value })
}
/>
{/** -- Spreadsheet rows and columns---*/}
<div className='flex items-start'>
<Spreadsheet
data={spreadsheet.data}
onChange={(data) => {
console.log("data", data);
setSpreadsheet({ ...spreadsheet, data: data as any });
}}
/>
{/** -- Add column button ---*/}
<button
className='bg-blue-500 text-white rounded-lg ml-6 w-8 h-8 mt-0.5'
onClick={addColumn}
>
+
</button>
</div>
{/** -- Add row button ---*/}
<button
className='bg-blue-500 text-white rounded-lg w-8 h-8 mt-5 '
onClick={addRow}
>
+
</button>
</div>
);
为确保一切按预期运行,请types.ts在文件夹中创建一个文件,app其中包含应用程序中声明的所有静态类型。
export interface Cell {
value: string;
}
export type SpreadsheetRow = Cell[];
export interface SpreadsheetData {
title: string;
rows: SpreadsheetRow[];
}
恭喜!🎉 您的电子表格应用程序应该运行正常。在接下来的章节中,您将学习如何使用 CopilotKit 添加一个 AI 助手来自动执行各种任务。
使用 CopilotKit 改进应用程序功能
在这里,您将学习如何使用 CopilotKit 向电子表格应用程序添加 AI 助手,以自动执行复杂操作。
CopilotKit 提供前端和 后端 软件包。它们使您能够接入 React 状态,并使用 AI 代理在后端处理应用程序数据。
首先,让我们将 CopilotKit React 组件添加到应用程序前端。
将 CopilotKit 添加到前端
在app/page.tsx组件顶部添加以下代码片段Main。
import "@copilotkit/react-ui/styles.css";
import { CopilotKit } from "@copilotkit/react-core";
import { CopilotSidebar } from "@copilotkit/react-ui";
import { INSTRUCTIONS } from "./instructions";
const HomePage = () => {
return (
<CopilotKit url='/api/copilotkit'>
<CopilotSidebar
instructions={INSTRUCTIONS}
labels={{
initial: "Welcome to the spreadsheet app! How can I help you?",
}}
defaultOpen={true}
clickOutsideToClose={false}
>
<Main />
</CopilotSidebar>
</CopilotKit>
);
};
const Main = () => {
//--- Main component //
};
export default HomePage;
- 从上面的代码片段可以看出:
- 我导入了 CopilotKit、它的侧边栏组件和 CSS 文件,以便在应用程序中使用它的前端组件。
- CopilotKit 组件 接受一个
urlprop,该 prop 表示将要配置 CopilotKit 的 API 服务器路由。 - Copilot 组件还会渲染 CopilotSidebar 组件 ,允许用户在应用程序内向 AI 副驾驶提供自定义指令。
- 最后,您可以导出
HomePage包含该CopilotSidebar组件的组件Main。
从上面的代码片段中可以看出,该CopilotSidebar组件有一个instructions属性。这个属性允许你向 CopilotKit 提供额外的上下文或指导信息。
因此,请instructions.ts在该app文件夹内创建一个文件,并将这些 命令复制 到该文件中。
接下来,你需要将 CopilotKit 接入应用程序的状态以访问应用程序的数据。为此,CopilotKit 提供了两个钩子: useCopilotAction 和 useMakeCopilotReadable。
useCopilotAction钩子 允许您定义 CopilotKit 要执行的操作。它接受一个包含以下参数的对象:
name- 该动作的名称。description- 该动作的描述。parameters- 包含所需参数列表的数组。render- 默认的自定义函数或字符串。handler- 由该操作触发的可执行函数。
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}!`);
},
});
useMakeCopilotReadable hook将 应用程序状态提供给 CopilotKit。
import { useMakeCopilotReadable } from "@copilotkit/react-core";
const appState = ...;
useMakeCopilotReadable(JSON.stringify(appState));
现在,让我们回到电子表格应用程序。在SingleSpreadsheet组件中,将应用程序状态传递给 CopilotKit,如下所示。
import {
useCopilotAction,
useMakeCopilotReadable,
} from "@copilotkit/react-core";
const SingleSpreadsheet = ({ spreadsheet, setSpreadsheet }: MainAreaProps) => {
//👇🏻 hook for providing the application state
useMakeCopilotReadable(
"This is the current spreadsheet: " + JSON.stringify(spreadsheet)
);
// --- other lines of code
};
接下来,您需要向SingleSpreadsheet组件添加两个操作,以便在用户更新电子表格数据时运行,并使用 CopilotKit 添加新行数据。
在继续之前,请创建一个utils文件夹,并canonicalSpreadsheetData.ts在该app文件夹内创建一个文件。
cd app
mkdir utils && cd utils
touch canonicalSpreadsheetData.ts
将以下代码片段复制到文件中。它会接收对电子表格所做的更新,并将其转换为电子表格中数据行所需的格式。
import { SpreadsheetRow } from "../types"
export interface RowLike {
cells: CellLike[] | undefined;
}
export interface CellLike {
value: string;
}
export function canonicalSpreadsheetData(
rows: RowLike[] | undefined
): SpreadsheetRow[] {
const canonicalRows: SpreadsheetRow[] = [];
for (const row of rows || []) {
const canonicalRow: SpreadsheetRow = [];
for (const cell of row.cells || []) {
canonicalRow.push({value: cell.value});
}
canonicalRows.push(canonicalRow);
}
return canonicalRows;
}
useCopilotAction现在,让我们使用组件内的钩子来创建操作SingleSpreadsheet。复制下面的第一个操作:
import { canonicalSpreadsheetData } from "../utils/canonicalSpreadsheetData";
import { PreviewSpreadsheetChanges } from "./PreviewSpreadsheetChanges";
import { SpreadsheetData, SpreadsheetRow } from "../types";
import { useCopilotAction } from "@copilotkit/react-core";
useCopilotAction({
name: "suggestSpreadsheetOverride",
description: "Suggest an override of the current spreadsheet",
parameters: [
{
name: "rows",
type: "object[]",
description: "The rows of the spreadsheet",
attributes: [
{
name: "cells",
type: "object[]",
description: "The cells of the row",
attributes: [
{
name: "value",
type: "string",
description: "The value of the cell",
},
],
},
],
},
{
name: "title",
type: "string",
description: "The title of the spreadsheet",
required: false,
},
],
render: (props) => {
const { rows } = props.args
const newRows = canonicalSpreadsheetData(rows);
return (
<PreviewSpreadsheetChanges
preCommitTitle="Replace contents"
postCommitTitle="Changes committed"
newRows={newRows}
commit={(rows) => {
const updatedSpreadsheet: SpreadsheetData = {
title: spreadsheet.title,
rows: rows,
};
setSpreadsheet(updatedSpreadsheet);
}}
/>
)
},
handler: ({ rows, title }) => {
// Do nothing.
// The preview component will optionally handle committing the changes.
},
});
上面的代码片段执行用户的任务,并使用 CopilotKit 的生成式 UI 功能显示结果预览。该suggestSpreadsheetOverride操作返回一个自定义组件(PreviewSpreadsheetChanges),该组件接受以下 props:
- 要添加到电子表格中的新数据行,
- 一些文本——
preCommitTitle和postCommitTitle,和 commit用于更新电子表格的函数。
你很快就会学会如何使用它们。
在 components 文件夹中创建PreviewSpreadsheetChanges组件,并将以下代码片段复制到该文件中:
import { CheckCircleIcon } from '@heroicons/react/20/solid'
import { SpreadsheetRow } from '../types';
import { useState } from 'react';
import Spreadsheet from 'react-spreadsheet';
export interface PreviewSpreadsheetChanges {
preCommitTitle: string;
postCommitTitle: string;
newRows: SpreadsheetRow[];
commit: (rows: SpreadsheetRow[]) => void;
}
export function PreviewSpreadsheetChanges(props: PreviewSpreadsheetChanges) {
const [changesCommitted, setChangesCommitted] = useState(false);
const commitChangesButton = () => {
return (
<button
className="inline-flex items-center gap-x-2 rounded-md bg-indigo-600 px-3.5 py-2.5 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
onClick={() => {
props.commit(props.newRows);
setChangesCommitted(true);
}}
>
{props.preCommitTitle}
</button>
);
}
const changesCommittedButtonPlaceholder = () => {
return (
<button
className=" inline-flex items-center gap-x-2 rounded-md bg-gray-100 px-3.5 py-2.5 text-sm font-semibold text-green-600 shadow-sm cursor-not-allowed"
disabled
>
{props.postCommitTitle}
<CheckCircleIcon className="-mr-0.5 h-5 w-5" aria-hidden="true" />
</button>
);
}
return (
<div className="flex flex-col">
<Spreadsheet
data={props.newRows}
/>
<div className="mt-5">
{changesCommitted ? changesCommittedButtonPlaceholder() : commitChangesButton() }
</div>
</div>
);
}
该PreviewSpreadsheetChanges组件返回一个包含请求生成的数据的电子表格,以及一个按钮(带有preCommitTitle文本),您可以通过点击该按钮(触发相应函数)将这些更改提交到主电子表格中commit。这样可以确保您在将结果添加到电子表格之前确认无误。
将下面的第二个操作添加到SingleSpreadsheet组件中。
useCopilotAction({
name: "appendToSpreadsheet",
description: "Append rows to the current spreadsheet",
parameters: [
{
name: "rows",
type: "object[]",
description: "The new rows of the spreadsheet",
attributes: [
{
name: "cells",
type: "object[]",
description: "The cells of the row",
attributes: [
{
name: "value",
type: "string",
description: "The value of the cell",
},
],
},
],
},
],
render: (props) => {
const status = props.status;
const { rows } = props.args
const newRows = canonicalSpreadsheetData(rows);
return (
<div>
<p>Status: {status}</p>
<Spreadsheet
data={newRows}
/>
</div>
)
},
handler: ({ rows }) => {
const canonicalRows = canonicalSpreadsheetData(rows);
const updatedSpreadsheet: SpreadsheetData = {
title: spreadsheet.title,
rows: [...spreadsheet.rows, ...canonicalRows],
};
setSpreadsheet(updatedSpreadsheet);
},
});
该appendToSpreadsheet操作会将新的数据行添加到电子表格中,从而更新电子表格。
以下是操作的简短演示:
[ https://www.youtube.com/watch?v=kGQ9xl5mSoQ ]
最后,在组件中添加一个操作Main,当用户提供指令时,该操作将创建一个新的电子表格。
useCopilotAction({
name: "createSpreadsheet",
description: "Create a new spreadsheet",
parameters: [
{
name: "rows",
type: "object[]",
description: "The rows of the spreadsheet",
attributes: [
{
name: "cells",
type: "object[]",
description: "The cells of the row",
attributes: [
{
name: "value",
type: "string",
description: "The value of the cell",
},
],
},
],
},
{
name: "title",
type: "string",
description: "The title of the spreadsheet",
},
],
render: (props) => {
const { rows, title } = props.args;
const newRows = canonicalSpreadsheetData(rows);
return (
<PreviewSpreadsheetChanges
preCommitTitle="Create spreadsheet"
postCommitTitle="Spreadsheet created"
newRows={newRows}
commit={ (rows) => {
const newSpreadsheet: SpreadsheetData = {
title: title || "Untitled Spreadsheet",
rows: rows,
};
setSpreadsheets((prev) => [...prev, newSpreadsheet]);
setSelectedSpreadsheetIndex(spreadsheets.length);
}}
/>
);
},
handler: ({ rows, title }) => {
// Do nothing.
// The preview component will optionally handle committing the changes.
},
});
恭喜!您已成功创建此应用程序所需的操作。现在,让我们将应用程序连接到 Copilotkit 后端。
将 Tavily AI 和 OpenAI 添加到 CopilotKit
在本教程的开头,我向大家介绍了 Tavily AI (一个为 AI 代理提供知识的搜索引擎)和 OpenAI(一个使我们能够访问 GPT-4 AI 模型的库) 。
在本节中,您将学习如何获取 Tavily 和 OpenAI API 密钥,并将它们集成到 CopilotKit 中,以构建高级智能应用程序。
访问 Tavily AI 网站,创建一个帐户,并将您的 API 密钥复制到.env.local您项目的文件中。
接下来,访问 OpenAI 开发者平台,创建一个 API 密钥,并将其复制到.env.local文件中。
以下是文件预览.env.local,其中包含 API 密钥并指定要使用的 OpenAI 模型。请注意,访问 GPT-4 模型需要 订阅 ChatGPT Plus 服务。
TAVILY_API_KEY=<your_API_key>
OPENAI_MODEL=gpt-4-1106-preview
OPENAI_API_KEY=<your_API_key>
回到我们的应用程序,您需要为 Copilot 创建一个 API 路由。因此,创建一个api/copilotkit包含 `<path>` 的文件夹route.ts,并在其中添加一个tavily.ts文件。
cd app
mkdir api && cd api
mkdir copilotkit && cd copilotkit
touch route.ts tavily.ts
在文件中创建一个函数,该函数接受用户的查询,使用 Tavily Search API 对查询进行搜索,并使用OpenAI GPT-4 模型tavily.ts总结结果 。
import OpenAI from "openai";
export async function research(query: string) {
//👇🏻 sends the request to the Tavily Search API
const response = await fetch("https://api.tavily.com/search", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
api_key: process.env.TAVILY_API_KEY,
query,
search_depth: "basic",
include_answer: true,
include_images: false,
include_raw_content: false,
max_results: 20,
}),
});
//👇🏻 the response
const responseJson = await response.json();
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY! });
//👇🏻 passes the response into the OpenAI GPT-4 model
const completion = await openai.chat.completions.create({
messages: [
{
role: "system",
content: `Summarize the following JSON to answer the research query \`"${query}"\`: ${JSON.stringify(
responseJson
)} in plain English.`,
},
],
model: process.env.OPENAI_MODEL,
});
//👇🏻 returns the result
return completion.choices[0].message.content;
}
最后,您可以通过将用户的查询传递给函数并将其响应提供给 CopilotKit 来执行文件research中的函数。route.ts
import { CopilotBackend, OpenAIAdapter } from "@copilotkit/backend";
import { Action } from "@copilotkit/shared";
import { research } from "./tavily";
//👇🏻 carries out a research on the user's query
const researchAction: Action<any> = {
name: "research",
description: "Call this function to conduct research on a certain query.",
parameters: [
{
name: "query",
type: "string",
description:
"The query for doing research. 5 characters or longer. Might be multiple words",
},
],
handler: async ({ query }) => {
console.log("Research query: ", query);
const result = await research(query);
console.log("Research result: ", result);
return result;
},
};
export async function POST(req: Request): Promise<Response> {
const actions: Action<any>[] = [];
if (process.env.TAVILY_API_KEY!) {
actions.push(researchAction);
}
const copilotKit = new CopilotBackend({
actions: actions,
});
const openaiModel = process.env.OPENAI_MODEL;
return copilotKit.response(req, new OpenAIAdapter({ model: openaiModel }));
}
恭喜!您已完成本教程的项目。
结论
CopilotKit 是一款功能强大的工具,可让您在几分钟内为产品添加 AI 助手。无论您是想开发 AI 聊天机器人和助手,还是想自动化复杂任务,CopilotKit 都能轻松实现。
如果您需要构建人工智能产品或将人工智能工具集成到您的软件应用程序中,则应该考虑使用 CopilotKit。
您可以在 GitHub 上找到本教程的源代码:
https://github.com/CopilotKit/spreadsheet-demo
感谢阅读!
文章来源:https://dev.to/copilotkit/build-an-ai-powered-spreadsheet-app-nextjs-langchain-copilotkit-109d






