一、初始化Nextjs项目
1.按照shadcn
默认设置初始化Nextjs
项目
npx create-next-app@latest my-app --typescript --tailwind --eslint
2.按照文档Next.js - shadcn/ui初始化shadcnui
3.处理项目全局样式布局
在app/global.css
中添加如下基础样式
html,
body,
:root {
height: 100%;
}
二、处理请求openAI API
1.安装openai依赖
npm install openai
2.根目录下新建.env.local
文件
将APIkey放入其中
3.在app/api/
目录下编写接口
该目录下的route.ts
可以响应url为/api/send
的请求,其中具体代码如下:
import { NextResponse } from "next/server";
import OpenAI from "openai";
// 从环境变量中读取 OpenAI API 密钥
const apiKey = process.env.OPENAI_API_KEY
// 使用提供的 API 密钥和代理的URL 初始化 OpenAI 客户端
const openai = new OpenAI({
apiKey,
baseURL: "https://api.chatanywhere.tech/v1"
})
// 定义一个异步的 POST 请求处理函数
export async function POST(req: Request) {
try {
// 从请求体中解析出 messages 字段
const { messages } = await req.json();
// 如果没有配置 API 密钥,返回 500 状态码和错误信息
if (!apiKey) {
return new NextResponse("请配置openAI apiKey!", { status: 500 });
}
// 如果请求体中没有 messages 字段,返回 400 状态码和错误信息
if (!messages) {
return new NextResponse("提示信息不能为空!", { status: 400 });
}
// 调用 OpenAI 的 chat.completions.create 方法,生成对话回复
const response = await openai.chat.completions.create({
messages,
model: "gpt-3.5-turbo",
});
// 将生成的回复消息返回给客户端
return NextResponse.json(response.choices[0].message);
} catch (error) {
// 捕获并处理可能发生的错误,记录错误日志并返回 500 状态码和错误信息
console.log(error);
return new NextResponse("Internal Error", { status: 500 });
}
}
三、编写消息发送页面
对app/page.tsx
文件进行编辑,用于消息发送和显示页面,这里主要使用了shadcnui
的Form
和TextArea
组件处理表单逻辑。
"use client";
import * as z from "zod";
import OpenAI from "openai";
import axios from "axios";
import MessageList from "@/components/message-list";
import { useForm, SubmitHandler } from "react-hook-form";
import { Form, FormControl, FormField, FormItem } from "@/components/ui/form";
import { Textarea } from "@/components/ui/textarea";
import { Button } from "@/components/ui/button";
import { Send, Loader2 } from "lucide-react";
import { useState } from "react";
// 定义表单值的类型
type FormValues = {
prompt: string;
}
export default function Home() {
// 使用状态钩子来存储消息列表
const [messages, setMessages] = useState<OpenAI.Chat.ChatCompletionUserMessageParam[]>([]);
// 使用 react-hook-form 初始化表单
const form = useForm<FormValues>({
defaultValues: {
prompt: "",
},
});
// 定义表单提交处理函数
const onSubmit: SubmitHandler<FormValues> = async (values) => {
try {
// 构造用户消息对象
const userMessage: OpenAI.Chat.ChatCompletionUserMessageParam = {
content: values.prompt,
role: "user",
};
// 合并当前消息列表和新用户消息
const submitMessages = [...messages, userMessage];
// 向后端发送请求
const res = await axios.post("/api/send", {
messages: submitMessages,
});
// 重置表单
form.reset();
// 更新消息列表
setMessages((message) => [...message, userMessage, res.data]);
} catch (error) {
// 捕获并处理错误
console.log(error)
}
};
// 检查表单是否正在提交
const isLoading = form.formState.isSubmitting;
return (
<div className="flex flex-col h-full w-full p-4">
{/* 消息列表展示区域 */}
<div className="flex-1 bg-gray-50 my-2 rounded-lg overflow-y-auto">
<MessageList messages={messages} />
</div>
{/* 表单输入区域 */}
<div className="relative h-[80px]">
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
<FormField
name="prompt"
render={({ field }) => (
<FormItem>
<FormControl>
<Textarea {...field} placeholder="请输入消息" />
</FormControl>
</FormItem>
)}
/>
<div className="absolute right-6 top-4">
<Button
disabled={!form.getValues('prompt')}
>
发送
{isLoading ? (
<Loader2 className="h-4 w-4 ml-2 animate-spin" />
) : (
<Send className="w-4 h-4 ml-2" />
)}
</Button>
</div>
</form>
</Form>
</div>
</div>
);
}
四、编写消息显示组件
在components
下新增message-list.tsx
文件用于处理消息的显示。
import { cn } from "@/lib/utils";
import { Avatar, AvatarImage } from "@/components/ui/avatar";
import OpenAI from 'openai';
// 定义 MessageList 组件的属性接口
interface MessageListProps {
messages: OpenAI.Chat.ChatCompletionUserMessageParam[];
}
// 定义 MessageList 组件
const MessageList: React.FC<MessageListProps> = ({ messages }) => {
return (
<div className="p-4">
{messages.map((message, index) => {
// 判断消息是否来自用户
const ifUser = message.role === "user";
return (
<div
key={`message${index}`}
// 根据消息角色决定消息的对齐方式
className={cn("flex", ifUser ? "justify-end" : "justify-start")}
>
{!ifUser && (
// 如果消息不是来自用户,则显示openai的图片
<Avatar className="w-8 h-8 mt-4 mr-2">
<AvatarImage src="/openai.png" />
</Avatar>
)}
<div
className={cn(
"p-4 rounded-lg m-2 max-w-[80%]",
// 根据消息角色设置不同的背景色和文字颜色
ifUser ? "bg-blue-50 text-blue-500" : "bg-white"
)}
>
<p className="break-words">{message.content.toString()}</p>
</div>
</div>
);
})}
</div>
);
};
export default MessageList;
最后,测试一下效果:
总结:
本文介绍了如何通过Next.js
搭配shadcn/ui
进行页面样式处理并调用OpenAI API
,实现一个简单的ChatGPT
示例。
一、初始化Next.js项目
-
初始化Next.js项目:使用
shadcn
默认设置,运行以下命令来创建一个包含TypeScript、Tailwind CSS和ESLint的Next.js项目:npx create-next-app@latest my-app --typescript --tailwind --eslint
-
初始化shadcn/ui:根据Next.js - shadcn/ui的文档,完成shadcn/ui的初始化。
-
处理全局样式布局:在
app/global.css
中添加基础样式,确保整个页面的高度为100%。
二、处理OpenAI API请求
- 安装OpenAI依赖
- 配置API Key:在项目根目录下创建
.env.local
文件,并将OpenAI的API Key放入其中。 - 编写API接口:在
app/api/
目录下编写接口,使其能够处理发送到/api/send
的请求,并返回OpenAI的回复。
三、编写消息发送页面
编辑app/page.tsx
文件,用于消息发送和显示页面。主要使用了shadcn/ui
的Form
和TextArea
组件处理表单逻辑。通过使用React Hook Form管理表单状态,并使用Axios发送请求与后端通信。
四、编写消息显示组件
在components
目录下新增message-list.tsx
文件,定义消息列表组件MessageList
,用于处理和展示用户与OpenAI之间的对话消息。根据消息的角色(用户或OpenAI)来决定消息的显示样式和对齐方式。