如何使用Next.js和OpenAI API创建ChatGPT应用。

使用Next.js、TypeScript和TailwindCSS构建ChatGPT应用程序。

先决条件

  • 您的机器上已安装Node.js和npm
  • React和TypeScript的基本理解
  • 一个OpenAI API密钥 - 您可以在OpenAI网站上注册帐户并生成API密钥。

你可以访问TeamSmart.AI来查看我的完整项目。这是一个Chrome扩展,可以让你选择不同的AI助手来帮助你完成日常任务。

我们将要建造什么

按照本教程,我们将使用OpenAI API创建一个类似于ChatGPT的简单聊天应用程序。

ChatGPT中文站

步骤1:设置项目。

我们将使用Apideck的Next.js入门套件来设置我们的项目。它预装了TypeScript、TailwindCSS和Apideck组件库。

  1. 使用命令行创建新项目:
yarn create-next-app --example https://github.com/apideck-io/next-starter-kit

2. 选择您的项目名称并导航到您的新文件夹。在其中,创建一个.env.local文件并将其放置在项目的根目录下,并添加以下一行代码(将YOUR_OPENAI_API_KEY替换为您的实际密钥):

OPENAI_API_KEY=YOUR_OPENAI_API_KEY

步骤2:编写API客户端

为了不暴露您的OpenAI API密钥,我们将创建一个API端点,而不是直接从浏览器向API发出请求。按照以下步骤设置您的端点,使用Next.js API路由:

  1. 定位您项目的页面文件夹,并创建一个名为api的新子文件夹。
  2. 在apifolder中,创建一个名为createMessage.ts的新TypeScript文件。
  3. 在 createMessage.ts 文件中,我们可以使用 OpenAI SDK 或向 OpenAI API 发出 HTTP 请求来生成与 AI 的“对话”的新消息。在本教程中,我们将使用直接的 API 调用。

以下是我们的API路由代码。

import { NextApiRequest, NextApiResponse } from 'next'

export default async function createMessage(
req: NextApiRequest,
res: NextApiResponse
) {
const { messages } = req.body
const apiKey = process.env.OPENAI_API_KEY
const url = 'https://api.openai.com/v1/chat/completions'

const body = JSON.stringify({
messages,
model: 'gpt-3.5-turbo',
stream: false,
})

try {
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${apiKey}`,
},
body,
})
const data = await response.json()
res.status(200).json({ data })
} catch (error) {
res.status(500).json({ error: error.message })
}
}

针对此示例,我们使用了 gpt-3.5-turbo 模型,因为在撰写本文时该模型目前可用。如果您可以访问 GPT-4,则可以根据需要更改该值。

信息值是存储我们与人工智能聊天对话中的消息的数组。每条消息都包含角色和内容。角色可以是以下任一:

  • 系统这是发送给人工智能的初始提示,指导它的行为。例如,您可以使用“您是由OpenAI训练的语言模型ChatGPT。”或“您是一名软件工程师,使用各种编程语言和开发工具开发软件程序、Web应用程序和移动应用程序。”尝试不同的初始系统消息可以帮助您微调人工智能的行为。
  • 用户-这代表用户的输入。例如,用户可能会问,“你能提供一个JavaScript函数来获取当前天气吗?”
  • 助手 这是AI的回应,API端点将返回它。

步骤三:创建消息功能

现在,端点已经准备好与人工智能进行接口交互,我们可以开始设计用户界面以促进交互。首先,我们将创建“sendMessage”函数。以下是具体步骤:

  1. 在 utils 文件夹中创建一个名为 sendMessage.ts 的新文件。
  2. 将以下代码添加到sendMessage.ts文件中:
import { ChatCompletionRequestMessage } from 'openai'

export const sendMessage = async (messages: ChatCompletionRequestMessage[]) => {
try {
const response = await fetch('/api/createMessage', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ messages }),
})
return await response.json()
} catch (error) {
console.log(error)
}
}

有了这个功能,您可以通过 API 端点在用户界面和 AI 之间建立通信。

现在让我们设置逻辑,在useMessages挂钩内创建新消息。在utils文件夹内,创建名为useMessages.ts的文件并添加以下代码:

import { useToast } from '@apideck/components'
import { ChatCompletionRequestMessage } from 'openai'
import {
ReactNode,
createContext,
useContext,
useEffect,
useState,
} from 'react'
import { sendMessage } from './sendMessage'
interface ContextProps {
messages: ChatCompletionRequestMessage[]
addMessage: (content: string) => Promise<void>
isLoadingAnswer: boolean
}
const ChatsContext = createContext<Partial<ContextProps>>({})
export function MessagesProvider({ children }: { children: ReactNode }) {
const { addToast } = useToast()
const [messages, setMessages] = useState<ChatCompletionRequestMessage[]>([])
const [isLoadingAnswer, setIsLoadingAnswer] = useState(false)
useEffect(() => {
const initializeChat = () => {
const systemMessage: ChatCompletionRequestMessage = {
role: 'system',
content: 'You are ChatGPT, a large language model trained by OpenAI.',
}
const welcomeMessage: ChatCompletionRequestMessage = {
role: 'assistant',
content: 'Hi, How can I help you today?',
}
setMessages([systemMessage, welcomeMessage])
}
// When no messages are present, we initialize the chat the system message and the welcome message
// We hide the system message from the user in the UI
if (!messages?.length) {
initializeChat()
}
}, [messages?.length, setMessages])
const addMessage = async (content: string) => {
setIsLoadingAnswer(true)
try {
const newMessage: ChatCompletionRequestMessage = {
role: 'user',
content,
}
const newMessages = [...messages, newMessage]
// Add the user message to the state so we can see it immediately
setMessages(newMessages)
const { data } = await sendMessage(newMessages)
const reply = data.choices[0].message
// Add the assistant message to the state
setMessages([...newMessages, reply])
} catch (error) {
// Show error when something goes wrong
addToast({ title: 'An error occurred', type: 'error' })
} finally {
setIsLoadingAnswer(false)
}
}
return (
<ChatsContext.Provider value={{ messages, addMessage, isLoadingAnswer }}>
{children}
</ChatsContext.Provider>
)
}
export const useMessages = () => {
return useContext(ChatsContext) as ContextProps
}

步骤4:实现消息用户界面组件

在设置好我们的函数之后,我们现在可以设计 UI 组件,使用这些函数来创建交互式聊天界面。按照以下步骤:

  1. 在您项目的组件文件夹中创建名为MessageForm.tsx的新文件,并添加以下代码:
import { Button, TextArea } from '@apideck/components'
import { useState } from 'react'
import { useMessages } from 'utils/useMessages'

const MessageForm = () => {
const [content, setContent] = useState('')
const { addMessage } = useMessages()
const handleSubmit = async (e: any) => {
e?.preventDefault()
addMessage(content)
setContent('')
}
return (
<form
className="relative mx-auto max-w-3xl rounded-t-xl"
onSubmit={handleSubmit}
>
<div className=" supports-backdrop-blur:bg-white/95 h-[130px] rounded-t-xl border-t border-l border-r border-gray-200 border-gray-500/10 bg-white p-5 backdrop-blur dark:border-gray-50/[0.06]">
<label htmlFor="content" className="sr-only">
Your message
</label>
<TextArea
name="content"
placeholder="Enter your message here..."
rows={3}
value={content}
autoFocus
className="border-0 !p-3 text-gray-900 shadow-none ring-1 ring-gray-300/40 backdrop-blur focus:outline-none focus:ring-gray-300/80 dark:bg-gray-800/80 dark:text-white dark:placeholder-gray-400 dark:ring-0"
onChange={(e: any) => setContent(e.target.value)}
/>
<div className="absolute right-8 bottom-10">
<div className="flex space-x-3">
<Button className="" type="submit" size="small">
Send
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
className="ml-1 h-4 w-4"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M6 12L3.269 3.126A59.768 59.768 0 0121.485 12 59.77 59.77 0 013.27 20.876L5.999 12zm0 0h7.5"
/>
</svg>
</Button>
</div>
</div>
</div>
</form>
)
}
export default MessageForm

现在我们已经设置了消息UI组件,我们需要创建呈现消息列表的组件。

  1. 在组件文件夹中创建一个名为MessagesList.tsx的新文件,并添加以下代码:
import { useMessages } from 'utils/useMessages'
typescr
const MessagesList = () => {
const { messages, isLoadingAnswer } = useMessages()
return (
<div className="mx-auto max-w-3xl pt-8">
{messages?.map((message, i) => {
const isUser = message.role === 'user'
if (message.role === 'system') return null
return (
<div
id={`message-${i}`}
className={`fade-up mb-4 flex ${
isUser ? 'justify-end' : 'justify-start'
} ${i === 1 ? 'max-w-md' : ''}`}
key={message.content}
>
{!isUser && (
<img
src="https://www.teamsmart.ai/next-assets/team/ai.jpg"
className="h-9 w-9 rounded-full"
alt="avatar"
/>
)}
<div
style={{ maxWidth: 'calc(100% - 45px)' }}
className={`group relative rounded-lg px-3 py-2 ${
isUser
? 'from-primary-700 to-primary-600 mr-2 bg-gradient-to-br text-white'
: 'ml-2 bg-gray-200 text-gray-700 dark:bg-gray-800 dark:text-gray-200'
}`}
>
{message.content.trim()}
</div>
{isUser && (
<img
src="https://www.teamsmart.ai/next-assets/profile-image.png"
className="h-9 w-9 cursor-pointer rounded-full"
alt="avatar"
/>
)}
</div>
)
})}
{isLoadingAnswer && (
<div className="mb-4 flex justify-start">
<img
src="https://www.teamsmart.ai/next-assets/team/ai.jpg"
className="h-9 w-9 rounded-full"
alt="avatar"
/>
<div className="loader relative ml-2 flex items-center justify-between space-x-1.5 rounded-full bg-gray-200 p-2.5 px-4 dark:bg-gray-800">
<span className="block h-3 w-3 rounded-full"></span>
<span className="block h-3 w-3 rounded-full"></span>
<span className="block h-3 w-3 rounded-full"></span>
</div>
</div>
)}
</div>
)
}
export default MessagesList

我们不想显示初始系统消息,因此如果角色是系统,则返回null。接下来,根据角色是助手还是用户,我们调整消息的样式。

在等待响应时,我们会显示一个加载元素。为了使此加载器元素动画,我们需要添加一些自定义CSS。在样式文件夹中创建一个globals.css文件,并添加以下内容:

.loader span {
animation-name: bounce;
animation-duration: 1.5s;
animation-iteration-count: infinite;
animation-timing-function: ease-in-out;
}
.loader span:nth-child(2) {
animation-delay: 50ms;
}
.loader span:nth-child(3) {
animation-delay: 150ms;
}

确保将CSS文件导入您的_app.tsx文件中:

import 'styles/globals.css'
import 'styles/tailwind.css'
import { ToastProvider } from '@apideck/components'
import { AppProps } from 'next/app'

export default function App({ Component, pageProps }: AppProps): JSX.Element {
return (
<ToastProvider>
<Component {...pageProps} />
</ToastProvider>
)
}
  1. 现在我们可以在应用程序中使用我们的消息 UI 组件,因为我们已经构建了它们。找到页面目录并打开 index.tsx。删除该文件中的样板代码。
import Layout from 'components/Layout'
import MessageForm from 'components/MessageForm'
import MessagesList from 'components/MessageList'
import { NextPage } from 'next'
import { MessagesProvider } from 'utils/useMessages'

const IndexPage: NextPage = () => {
return (
<MessagesProvider>
<Layout>
<MessagesList />
<div className="fixed bottom-0 right-0 left-0">
<MessageForm />
</div>
</Layout>
</MessagesProvider>
)
}
export default IndexPage

我们已经使用 MessageProvider 将我们的组件包装起来,以便我们可以在组件之间共享状态。我们还在 MessageForm 组件中添加了一个容器 div,使其在页面底部具有固定位置。

步骤5:运行聊天应用程序

干得好!我们现在已经到了可以看到我们的聊天应用程序运行的时候了。以下是如何测试您的ChatGPT应用程序:

  1. 确保您的开发服务器正在运行。(yarn dev)
  2. 在浏览器中导航到您的应用程序的根URL。 (localhost:3000)
  3. 您应该在屏幕上看到UI渲染。在底部的文本字段中键入一条消息,然后点击发送。 AI聊天机器人将回复您的消息。

你现在可以和你的人工智能聊天机器人进行对话了!随意尝试不同类型的信息,看看AI的响应。

最后的话

感谢阅读!完整的源代码可以在这里找到。

如果您觉得这篇教程有用,请通过 Twitter 或者访问我的个人网站 jakeprins.com 与我联系。

另外,请务必查看TeamSmart AI,以获取我的完整ChatGPT应用程序。这是一个Chrome扩展,允许不同的人工智能团队成员帮助您完成日常任务。

2023-10-20 16:52:48 AI中文站翻译自原文