完整教程展示用 GPT-4、LangChain、CopilotKit 实现 AI 辅助表格编辑。346 次社区热评验证强实用需求。
在本文中,你将学习如何构建一个由 AI 驱动的电子表格应用程序,它允许你使用纯英文命令轻松执行各种会计功能并与数据交互。
使用 Next.js 构建 Web 应用程序,
使用 React Spreadsheet 创建电子表格应用程序,且
使用 CopilotKit 将 AI 集成到软件应用程序中。
让电子表格更易使用且更令人愉悦
CopilotKit 是一个开源的 AI 助手平台。我们使将强大的 AI 集成到你的 React 应用程序中变得容易。
ChatBot:能够在应用内执行操作的上下文感知型应用内聊天机器人 💬
CopilotTextArea:具有上下文感知自动完成和插入功能的 AI 驱动文本字段 📝
Co-Agents:能够与你的应用和用户交互的应用内 AI 智能体 🤖
(原谅 AI 的拼写错误,并为 CopilotKit 点个星 :)
现在回到文章!
为了充分理解本教程,你需要对 React 或 Next.js 有基本的理解。
以下是构建 AI 驱动的电子表格应用程序所需的工具:
React Spreadsheet - 一个简单的包,使我们能够在 React 应用程序中添加电子表格。
OpenAI API - 提供一个 API 密钥,使我们能够使用 ChatGPT 模型执行各种任务。
Tavily AI - 一个搜索引擎,使 AI 智能体能够在应用程序内进行研究和访问实时知识。
CopilotKit - 一个开源的助手框架,用于构建自定义 AI 聊天机器人、应用内 AI 智能体和文本区域。
首先,通过在终端中运行以下代码片段来创建 Next.js 应用程序:
npx create-next-app spreadsheet-app
选择你首选的配置设置。在本教程中,我们将使用 TypeScript 和 Next.js App Router。
接下来,安装 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.tsx 文件包括 addRow 和 addColumn 函数。
addRow 函数计算当前行数,添加新行,并相应地更新电子表格。
类似地,addColumn 函数向电子表格添加新列。
更新 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>
);
为了确保一切按预期工作,请在 app 文件夹内创建一个 types.ts 文件,其中包含应用程序中声明的所有静态类型。
export interface Cell {
value: string;
}
export type SpreadsheetRow = Cell[];
export interface SpreadsheetData {
title: string;
rows: SpreadsheetRow[];
}
恭喜!🎉 你的电子表格应用程序现在应该可以完美运行了。在接下来的章节中,你将学习如何添加一个 AI 助手,使用 CopilotKit 自动执行各种任务。
在本节中,你将学习如何使用 CopilotKit 为电子表格应用程序添加一个 AI 助手,以自动执行各种复杂操作。
CopilotKit 同时提供前端和后端软件包。借助这些软件包,你可以接入 React 状态,并使用 AI 智能体在后端处理应用程序数据。
首先,让我们将 CopilotKit React 组件添加到应用程序前端。
在 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 组件接收一个 url 属性,用于表示配置 CopilotKit 的 API 服务器路由。Copilot 组件还会渲染 CopilotSidebar 组件,允许用户在应用程序中为 AI 助手提供自定义指令。最后,你可以导出包含 CopilotSidebar 和 Main 组件的 HomePage 组件。
我导入了 CopilotKit、它的侧边栏组件以及 CSS 文件,以便在应用程序中使用其前端组件。
CopilotKit 组件接收一个 url 属性,用于表示配置 CopilotKit 的 API 服务器路由。
Copilot 组件还会渲染 CopilotSidebar 组件,允许用户在应用程序中为 AI 助手提供自定义指令。
最后,你可以导出包含 CopilotSidebar 和 Main 组件的 HomePage 组件。
从上面的代码片段中,你会注意到 CopilotSidebar 组件有一个 instructions 属性。该属性允许你为 CopilotKit 提供额外的上下文或指导。
因此,请在 app 文件夹内创建一个 instructions.ts 文件,并将这些指令复制到该文件中。
接下来,你需要将 CopilotKit 接入应用程序状态,以访问应用程序的数据。为此,CopilotKit 提供了两个 Hook:useCopilotAction 和 useMakeCopilotReadable。
useCopilotAction Hook 允许你定义由 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 更新电子表格数据以及添加一行新数据时,分别运行这两个操作。
在继续之前,请在 app 文件夹内创建一个 utils 文件夹,并在其中创建 canonicalSpreadsheetData.ts 文件。
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;
}
现在,让我们在 SingleSpreadsheet 组件中使用 useCopilotAction Hook 创建这些操作。复制下面的第一个操作:
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——一个为 AI 智能体提供知识的搜索引擎,以及 OpenAI——一个让我们能够访问 GPT-4 AI 模型的库。
在本节中,你将学习如何获取 Tavily 和 OpenAI API 密钥,并将它们集成到 CopilotKit 中,以构建高级且智能的应用程序。
访问 Tavily AI 网站,创建账户,然后将你的 API 密钥复制到项目的 .env.local 文件中。
接下来,前往 OpenAI 开发者平台,创建一个