使用 Next.js 和 shadcn/ui 创建一个 ChatGPT 对话程序

901 阅读4分钟

一、初始化Nextjs项目

1.按照shadcn默认设置初始化Nextjs项目

npx create-next-app@latest my-app --typescript --tailwind --eslint

48938a1c57804f35b0eebf6c703a3931~tplv-73owjymdk6-watermark_看图王.png

image.png

2.按照文档Next.js - shadcn/ui初始化shadcnui

image.png

3.处理项目全局样式布局

app/global.css中添加如下基础样式

  html,
  body,
  :root {
    height: 100%;
  }

二、处理请求openAI API

1.安装openai依赖

npm install openai

2.根目录下新建.env.local文件

将APIkey放入其中

image.png

3.在app/api/目录下编写接口

image.png

该目录下的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文件进行编辑,用于消息发送和显示页面,这里主要使用了shadcnuiFormTextArea组件处理表单逻辑。

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

最后,测试一下效果:

image.png


总结:

本文介绍了如何通过Next.js搭配shadcn/ui进行页面样式处理并调用OpenAI API,实现一个简单的ChatGPT 示例。

一、初始化Next.js项目

  1. 初始化Next.js项目:使用shadcn默认设置,运行以下命令来创建一个包含TypeScript、Tailwind CSS和ESLint的Next.js项目:

    npx create-next-app@latest my-app --typescript --tailwind --eslint
    
  2. 初始化shadcn/ui:根据Next.js - shadcn/ui的文档,完成shadcn/ui的初始化。

  3. 处理全局样式布局:在app/global.css中添加基础样式,确保整个页面的高度为100%。

二、处理OpenAI API请求

  1. 安装OpenAI依赖
  2. 配置API Key:在项目根目录下创建.env.local文件,并将OpenAI的API Key放入其中。
  3. 编写API接口:在app/api/目录下编写接口,使其能够处理发送到/api/send的请求,并返回OpenAI的回复。

三、编写消息发送页面

编辑app/page.tsx文件,用于消息发送和显示页面。主要使用了shadcn/uiFormTextArea组件处理表单逻辑。通过使用React Hook Form管理表单状态,并使用Axios发送请求与后端通信。

四、编写消息显示组件

components目录下新增message-list.tsx文件,定义消息列表组件MessageList,用于处理和展示用户与OpenAI之间的对话消息。根据消息的角色(用户或OpenAI)来决定消息的显示样式和对齐方式。