Next.js + OpenAI API 快速创建 ChatGPT 聊天应用教程

使用 Next.js, TypeScript, TailwindCSS 构建 ChatGPT 应用 先决条件 本机已安装 Node.js 和 npm 对 React 和 TypeScript 基本了解

使用 Next.js, TypeScript, TailwindCSS 构建 ChatGPT 应用

先决条件

  • 本机已安装 Node.js 和 npm
  • 对 React 和 TypeScript 基本了解
  • 一个 OpenAI API key —— 你可以从 OpenAI 官网上注册账号并生成 API key

最终效果

跟着本教程,我们将使用 OpenAI API 来创建一个简单的像 ChatGPT 一样的聊天应用。

Next.js + OpenAI API 快速创建 ChatGPT 聊天应用教程

第一步:设置项目

我们将使用来自 Apideck 的 Next.js Starter Kit 来设置我们的项目。它已经预安装了 TypeScirpt, TailwindCSS 和 Apideck Components 库。

  1. 使用命令行创建一个新项目
lua
复制代码
yarn create-next-app --example https://github.com/apideck-io/next-starter-kit
  1. 设置你的项目名并选择新的目录。在项目根目录中,创建一个 .env.local 文件,并添加以下内容(使用实际的key来替换 YOUR_OPENAI_API_KEY):
ini
复制代码
OPENAI_API_KEY=YOUR_OPENAI_API_KEY

第二步:编写 API 客户端

为了不暴露你的 OpenAI key,我们需要要创建一个 API 端点来替代从浏览器直接请求 API。按照以下步骤使用 Next.js API 路由来设置你的端点:

  1. 在项目中的 pages 文件夹中创建一个名为 api 的新文件夹。
  2. 在 api 文件夹内,创建一个名为 createMessage.ts 的新的 TypeScript 文件。
  3. 在 createMessage.ts 文件中,我们可以使用 OpenAI SDK 或向 OpenAI API 发送 HTTP 请求,为我们与 AI 的“会话”生成新消息。在本教程中我们将直接调用 API。

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

typescript
复制代码
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,你可以在必要的时候修改这个值。

messages 的值是一个数组,它存储了我们与 AI 基于聊天的对话中的消息。每个消息都包含一个 role 和 content。role 可以是以下几种:

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

第三步:创建消息函数

现在端点已经准备好连接 AI 了,我们可以开始设计我们的用户界面来促进交互。首先,我们来创建 sendMessage 函数。就是这样:

  1. 在 utils 文件夹中创建一个新文件,名为 sendMessage.ts。
  2. 在 sendMessage.ts 中添加以下代码:
typescript
复制代码
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) } }

有了这个函数,你就可以在用户界面和 AI 之间通过 API 端点建立沟通了。

现在让我设置在 useMessages hook 中创建新消息的逻辑。在 utils 文件夹里,创建一个名为 useMessages.tsx 的文件,并添加以下代码:

typescript
复制代码
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 }

第四步:实现消息 UI 组件

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

  1. 在你项目的 components 文件夹中创建一个名叫 MessageForm.tsx 的新文件并添加以下代码:
typescript
复制代码
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. 在 components 文件夹中创建一个名为 MessageList.tsx 的新文件并添加以下代码:
typescript
复制代码
import { useMessages } from 'utils/useMessages' 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

我们不希望展示初始系统消息,因此如果 role 是 system 的话我们返回 null。接着,我们基于 role 是 assitant 或 user 来调整一下消息的样式。

当我们等待响应时,我们需要展示一个加载元素。为了让 loader 元素动起来,我们需要添加一些自定义的 CSS。在样式文件夹里,创建一个 globals.css 文件并添加以下样式:

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; }

确保在 _app.tsx 文件中导入这个 CSS 文件:

typescript
复制代码
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组件,现在可以在应用程序中使用它们了。打开 pages 目录并打开 index.tsx。在此文件中移除样板代码。
typescript
复制代码
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 容器,因为它被固定在了页面底部。

第五步:运行这个聊天应用程序

现在我们可以运行这个聊天程序了。你可以这样测试你的 ChatGPT 应用:

  1. 确保你的开发服务已运行。(yarn dev)
  2. 在浏览器中打开你的应用程序的根 URL。(localhost:3000)
  3. 你应该看到 UI 已经渲染出来。在底部的文本框中输入消息并点击 Send。AI 机器人将响应你的消息。

完成代码可以在这里查看。

原文连接:www.jakeprins.com/blog/how-to…

全文完。

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。

给TA打赏
共{{data.count}}人
人已打赏
人工智能

感谢ChatGPT,救了我狗的命!

2024-4-23 11:45:43

人工智能

基于阿里云免费算力自建LLM(类GPT)大模型

2024-4-24 7:31:31

❯
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索