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 基于聊天的对话中的消息。每个消息都包含一个 rolecontentrole 可以是以下几种:

  • 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

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

当我们等待响应时,我们需要展示一个加载元素。为了让 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

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