展示如何通过 Next.js、OpenAI 和 CopilotKit 快速构建自动生成 PowerPoint 的 AI 应用,附完整实现代码。
在本文中,你将学习如何使用 Nextjs、CopilotKit 和 OpenAI 构建一款由 AI 驱动的 PowerPoint 应用。我们将介绍:
利用 ChatGPT 创建 PowerPoint 演示文稿📊
与 PowerPoint 演示文稿进行对话💬
向 PowerPoint 演示文稿添加音频和图片🔉
CopilotKit:构建深度集成到应用内的 AI 聊天机器人 💬
CopilotKit 是一个开源的 AI 副驾平台。我们可以帮助你轻松地将强大的 AI 聊天机器人集成到 React 应用中,并且支持完全自定义和深度集成。
现在回到本文。
要开始学习本教程,你需要在计算机上安装以下软件:
文本编辑器(例如 Visual Studio Code)
步骤 1:使用以下命令克隆 PowerPoint 应用的样板代码。
git clone https://github.com/TheGreatBonnie/aipoweredpowerpointpresentation
步骤 2:使用文本编辑器打开 PowerPoint 应用的样板代码,并使用以下命令安装项目的所有依赖项。
npm install
步骤 3:进入根目录并创建一个名为 .env.local 的文件。在该文件中添加以下环境变量,用于保存你的 ChatGPT API 密钥。
OPENAI_API_KEY="Your ChatGPT API Key”
步骤 4:在命令行中运行 npm run dev 命令。访问 http://localhost:3000/,你应该会看到 PowerPoint 应用的前端界面。
步骤 1:进入 /[root]/src/app/components,创建一个名为 present.tsx 的文件。然后在文件顶部导入以下依赖项。
"use client";
import { useCopilotContext } from "@copilotkit/react-core";
import { CopilotTask } from "@copilotkit/react-core";
import {
useMakeCopilotActionable,
useMakeCopilotReadable,
} from "@copilotkit/react-core";
import { useEffect, useState } from "react";
import "./../presentation/styles.css";
import Markdown from "react-markdown";
步骤 2:定义一个名为 Slide 的 TypeScript 接口,其中包含 PowerPoint 演示文稿幻灯片的 title 和 content 属性。
// Define slide interface
interface Slide {
title: string;
content: string;
}
步骤 3:创建一个名为 Presentation 的函数,并使用 useState 初始化名为 allSlides 和 currentSlideIndex 的状态变量。变量 allSlides 用于保存幻灯片数组,而 currentSlideIndex 用于保存当前幻灯片的索引。
export function Presentation (){
const [allSlides, setAllSlides] = useState<Slide[]>([]);
const [currentSlideIndex, setCurrentSlideIndex] = useState<number>(0);
}
步骤 4:在 Presentation 函数中,使用 useMakeCopilotReadable Hook,将 allSlides 幻灯片数组作为应用内聊天机器人的上下文。
useMakeCopilotReadable("Powerpoint presentation slides: " + JSON.stringify(allSlides));
步骤 5:使用 useMakeCopilotActionable Hook 设置一个名为 createNewPowerPointSlide 的操作,为其添加描述和实现函数。该实现函数会更新 allSlides 和 currentSlideIndex 状态变量,如下所示。
useMakeCopilotActionable(
{
name: "createNewPowerPointSlide",
description: "create slides for a powerpoint presentation. Call this function multiple times to present multiple slides.",
argumentAnnotations: [
{
name: "slideTitle",
type: "string",
description: "The topic to display in the presentation slide. Use simple markdown to outline your speech, like a headline.",
required: true,
},
{
name: "content",
type: "string",
description: "The content to display in the presentation slide. Use simple markdown to outline your speech, like lists, paragraphs, etc.",
required: true
},
],
implementation: async (newSlideTitle, newSlideContent) => {
const newSlide: Slide = { title: newSlideTitle, content: newSlideContent};
const updatedSlides = [...allSlides, newSlide];
setAllSlides(updatedSlides);
setCurrentSlideIndex(updatedSlides.length - 1);
},
},
[],
);
步骤 6:定义一个名为 displayCurrentSlide 的函数,用于在前端显示当前索引对应的幻灯片。
// Display current slide
const displayCurrentSlide = () => {
const slide = allSlides[currentSlideIndex];
return slide ? (
<div
className="h-screen flex flex-col justify-center items-center text-5xl text-white bg-cover bg-center bg-no-repeat p-10 text-center"
style={{
textShadow: "1px 1px 0 #000, -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000",
}}
>
<Markdown className="markdown">{slide.title}</Markdown>
<Markdown className="markdown">{slide.content}</Markdown>
</div>
) : (
<div className="h-screen flex flex-col justify-center items-center text-5xl text-white bg-cover bg-center bg-no-repeat p-10 text-center">
No Slide To Display
</div>
);
};
步骤 7:定义一个名为 addSlide 的函数,其中包含一个名为 CopilotTask 的类。CopilotTask 类定义了添加新幻灯片的功能。
// Add new slide function
const addSlide = new CopilotTask({
instructions: "create a new slide",
actions: [
{
name: "newSlide",
description: "Make a new slide related to the current topic.",
argumentAnnotations: [
{
name: "title",
type: "string",
description: "The title to display in the presentation slide.",
required: true,
},
{
name: "content",
type: "string",
description: "The title to display in the presentation slide.",
required: true,
},
],
implementation: async (newSlideTitle,newSlideContent,) => {
const newSlide: Slide = {title: newSlideTitle,content: newSlideContent,};
const updatedSlides = [...allSlides, newSlide];
setAllSlides(updatedSlides);
setCurrentSlideIndex(updatedSlides.length - 1);
},
},
],
});
const context = useCopilotContext();
const [randomSlideTaskRunning, setRandomSlideTaskRunning] = useState(false);
步骤 8:定义两个名为 goBack 和 goForward 的函数。goBack 函数定义了导航到上一张幻灯片的功能,而 goForward 函数定义了导航到下一张幻灯片的功能。
// Button click handlers for navigation
const goBack = () => setCurrentSlideIndex((prev) => Math.max(0, prev - 1));
const goForward = () => setCurrentSlideIndex((prev) => Math.min(allSlides.length - 1, prev + 1));
步骤 9:返回一个组件,该组件调用 displayCurrentSlide 函数,并包含用于调用 addSlide、goBack 和 goForward 函数的按钮。
return (
<div>
{displayCurrentSlide()}
<button
disabled={randomSlideTaskRunning}
className={`absolute bottom-12 left-0 mb-4 ml-4 bg-blue-500 text-white font-bold py-2 px-4 rounded
${randomSlideTaskRunning ? "opacity-50 cursor-not-allowed" : "hover:bg-blue-700"}`}
onClick={async () => {
try {
setRandomSlideTaskRunning(true);
await addSlide.run(context);
} finally {
setRandomSlideTaskRunning(false);
}
}}
>
{randomSlideTaskRunning ? "Loading slide..." : "Add Slide +"}
</button>
<button
className={`absolute bottom-0 left-0 mb-4 ml-4 bg-blue-500 text-white font-bold py-2 px-4 rounded
${randomSlideTaskRunning ? "opacity-50 cursor-not-allowed" : "hover:bg-blue-700"}`}
onClick={goBack}>Prev</button>
<button
className={`absolute bottom-0 left-20 mb-4 ml-4 bg-blue-500 text-white font-bold py-2 px-4 rounded
${randomSlideTaskRunning ? "opacity-50 cursor-not-allowed" : "hover:bg-blue-700"}`}
onClick={goForward}>Next</button>
</div>
);
步骤 10:在 Presentation 文件夹中,将以下代码添加到 page.tsx 文件。
"use client";
import {
CopilotKit
} from "@copilotkit/react-core";
import { CopilotSidebar } from "@copilotkit/react-ui";
import {Presentation} from "../components/present";
import "./styles.css";
let globalAudio: any = undefined;
let globalAudioEnabled = false;
const Demo = () => {
return (
<CopilotKit url="/api/copilotkit/openai">
<CopilotSidebar
defaultOpen={true}
labels={{
title: "Presentation Copilot",
initial: "Hi you! 👋 I can give you a presentation on any topic.",
}}
clickOutsideToClose={false}
onSubmitMessage={async (message) => {
if (!globalAudioEnabled) {
globalAudio.play();
globalAudio.pause();
}
globalAudioEnabled = true;
}}
>
<Presentation/>
</CopilotSidebar>
</CopilotKit>
);
};
export default Demo;
步骤 11:访问 http://localhost:3000/,单击「Get Started」按钮,你将被重定向到集成了聊天机器人的演示文稿页面,如下所示。
步骤 12:在右侧的聊天机器人中输入类似“Create a PowerPoint presentation on TypeScript”的提示词。聊天机器人将开始生成回复,完成后会在页面左侧显示生成的 PowerPoint 幻灯片,如下所示。
步骤 13:关闭聊天机器人窗口,然后单击「Add Slide +」按钮,为 PowerPoint 演示文稿添加一张新幻灯片,如下所示。
步骤 14:单击「Prev」按钮将跳转到上一张幻灯片。如果单击「Next」按钮,则会跳转到下一张幻灯片。
步骤 1:在 present.tsx 文件中声明一个名为 globalAudio 的变量,如下所示。
let globalAudio: any = undefined;
步骤 2:在 Presentation 组件内部声明一个 useEffect Hook,使用新的 Audio 对象初始化 globalAudio,如下所示。
useEffect(() => {
if (!globalAudio) {
globalAudio = new Audio();
}
}, []);
步骤 3:更新 useMakeCopilotActionable Hook,通过 API 将 PowerPoint 幻灯片文本转换为语音,如下所示。
useMakeCopilotActionable(
{
name: "createNewPowerPointSlide",
description: "create slides for a powerpoint presentation. Call this function multiple times to present multiple slides.",
argumentAnnotations: [
{
name: "slideTitle",
type: "string",
description: "The topic to display in the presentation slide. Use simple markdown to outline your speech, like a headline.",
required: true,
},
{
name: "content",
type: "string",
description: "The content to display in the presentation slide. Use simple markdown to outline your speech, like lists, paragraphs, etc.",
required: true
},
{
name: "speech",
type: "string",
description: "An informative speech about the current slide.",
required: true,
},
],
implementation: async (newSlideTitle, newSlideContent, speech) => {
const newSlide: Slide = { title: newSlideTitle, content: newSlideContent };
const updatedSlides = [...allSlides, newSlide];
setAllSlides(updatedSlides);
setCurrentSlideIndex(updatedSlides.length - 1);
const encodedText = encodeURIComponent(speech);
const url = `/api/tts?text=${encodedText}`;
globalAudio.src = url;
await globalAudio.play();
await new Promise<void>((resolve) => {
globalAudio.onended = function () {
resolve();
};
});
await new Promise((resolve) => setTimeout(resolve, 500));
},
},
[],
);
步骤 4:更新 addSlide 函数,通过 API 将新 PowerPoint 幻灯片的文本转换为语音,如下所示。
// Add new slide function
const addSlide = new CopilotTask({
instructions: "create a new slide",
actions: [
{
name: "newSlide",
description: "Make a new slide related to the current topic.",
argumentAnnotations: [
{
name: "title",
type: "string",
description:"The title to display in the presentation slide.",
required: true,
},
{
name: "content",
type: "string",
description:"The title to display in the presentation slide.",
required: true,
},
{
name: "speech",
type: "string",
description: "An informative speech about the current slide.",
required: true,
},
],
implementation: async (newSlideTitle, newSlideContent, speech) => {
const newSlide: Slide = { title: newSlideTitle, content: newSlideContent };
const updatedSlides = [...allSlides, newSlide];
setAllSlides(updatedSlides);
setCurrentSlideIndex(updatedSlides.length - 1);
const encodedText = encodeURIComponent(speech);
const url = `/api/tts?text=${encodedText}`;
globalAudio.src = url;
await globalAudio.play();
await new Promise<void>((resolve) => {
globalAudio.onended = function () {
resolve();
};
});
await new Promise((resolve) => setTimeout(resolve, 500));
},
}
]
});
步骤 5:再次向聊天机器人输入“Create a PowerPoint presentation on TypeScript”提示词,你应该会听到讲解幻灯片的语音。
步骤 1:在 present.tsx 文件中,为 Slide 接口类型添加一个名为 backgroundImage 的新属性,如下所示。
// Define slide interface
interface Slide {
title: string;
content: string;
backgroundImage: string;
}
步骤 2:更新 useMakeCopilotActionable Hook,为 PowerPoint 演示文稿幻灯片生成图像。
useMakeCopilotActionable(
{
name: "createPowerPointSlides",
description: "create slides for a powerpoint presentation. Call this function multiple times to present multiple slides.",
argumentAnnotations: [
{
name: "slideTitle",
type: "string",
description: "The topic to display in the presentation slide. Use simple markdown to outline your speech, like a headline.",
required: true,
},
{
name: "content",
type: "string",
description: "The content to display in the presentation slide. Use simple markdown to outline your speech, like lists, paragraphs, etc.",
required: true
},
{
name: "backgroundImage",
type: "string",
description: "What to display in the background of the slide (i.e. 'dog' or 'house').",
required: true,
},
{
name: "speech",
type: "string",
description: "An informative speech about the current slide.",
required: true,
},
],
implementation: async (newSlideTitle, newSlideContent, newSlideBackgroundImage, speech) => {
const newSlide: Slide = { title: newSlideTitle, content: newSlideContent, backgroundImage: newSlideBackgroundImage };
const updatedSlides = [...allSlides, newSlide];
setAllSlides(updatedSlides);
setCurrentSlideIndex(updatedSlides.length - 1);
const encodedText = encodeURIComponent(speech);
const url = `/api/tts?text=${encodedText}`;
globalAudio.src = url;
await globalAudio.play();
await new Promise<void>((resolve) => {
globalAudio.onended = function () {
resolve();
};
});
await new Promise((resolve) => setTimeout(resolve, 500));
},
},
[],
);
步骤 2:更新 addSlide 函数,为新的 PowerPoint 演示文稿幻灯片生成图像。
步骤 3:更新 slide.tsx 文件中的 Slide 组件,通过 unsplash.com 生成图像。
// Add new slide function
const addSlide = new CopilotTask({
instructions: "create a new slide",
functions: [
{
name: "newSlide",
description: "Make a new slide related to the current topic.",
argumentAnnotations: [
{
name: "title",
type: "string",
description:"The title to display in the presentation slide.",
required: true,
},
{
name: "content",
type: "string",
description:"The title to display in the presentation slide.",
required: true,
},
{
name: "backgroundImage",
type: "string",
description: "What to display in the background of the slide (i.e. 'dog' or 'house').",
required: true,
},
{
name: "speech",
type: "string",
description: "An informative speech about the current slide.",
required: true,
},
],
implementation: async (newSlideTitle, newSlideContent, newSlideBackgroundImage, speech) => {
const newSlide: Slide = { title: newSlideTitle, content: newSlideContent, backgroundImage: newSlideBackgroundImage };
const updatedSlides = [...allSlides, newSlide];
setAllSlides(updatedSlides);
setCurrentSlideIndex(updatedSlides.length - 1);
const encodedText = encodeURIComponent(speech);
const url = `/api/tts?text=${encodedText}`;
globalAudio.src = url;
await globalAudio.play();
await new Promise<void>((resolve) => {
globalAudio.onended = function () {
resolve();
};
});
await new Promise((resolve) => setTimeout(resolve, 500));
},
}
]
});
步骤 3:更新 displayCurrentSlide 函数,为 PowerPoint 演示文稿的幻灯片添加背景图片。
// Display current slide
const displayCurrentSlide = () => {
const slide = allSlides[currentSlideIndex];
return slide ? (
<div
className="h-screen flex flex-col justify-center items-center text-5xl text-white bg-cover bg-center bg-no-repeat p-10 text-center"
style={{
backgroundImage: 'url("https://source.unsplash.com/featured/?' + encodeURIComponent(allSlides[currentSlideIndex].backgroundImage) + '")',
textShadow: "1px 1px 0 #000, -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000",
}}
>
<Markdown className="markdown">{slide.title}</Markdown>
<Markdown className="markdown">{slide.content}</Markdown>
</div>
) : (
<div className="h-screen flex flex-col justify-center items-center text-5xl text-white bg-cover bg-center bg-no-repeat p-10 text-center">
No Slide To Display
</div>
);
};
步骤 4:进入 Web 应用,你应该能看到 PowerPoint 幻灯片中已经添加了背景图片。
总而言之,你可以使用 CopilotKit 构建应用内 AI 聊天机器人,使其能够查看应用的当前状态并在应用内执行操作。AI 聊天机器人可以与你的应用前端、后端以及第三方服务进行通信。
总而言之,你可以使用 CopilotKit 构建应用内 AI 聊天机器人,使其能够查看应用的当前状态并在应用内执行操作。AI 聊天机器人可以与你的应用前端、后端以及第三方服务进行通信。
完整源代码:https://github.com/TheGreatBonnie/aipoweredpresentation
感谢 @theGreatBonnie 为本文所做的出色工作。
部分评论可能仅对已登录的访客可见。登录后即可查看所有评论。
如需采取进一步措施,你可以考虑屏蔽此人和/或举报其滥用行为。