LoginSignup
17
21

Next.js(最新version)でChatGPTのCloneアプリを作成してみた。(Gitで確認できます)

Last updated at Posted at 2023-10-08

ChatGPTをゼロから作ってみた

作成したアプリの画像

スクリーンショット 2023-10-09 0.59.43.png
下記でアプリを実際に動かせるのでよかったら確認してみてください。
ChatGPT-Clone-AppのURL

プロジェクトはGitで管理してますので、クローンして使ってみてください。
chat-gpt-clone-appのGithubリポジトリのURL

自己紹介

お久しぶりの投稿です。本業と個人の仕事で忙しく1年振りの投稿になります。
株式会社DYMのエンジニアをしております、永松です。
普段はインフラはAWSを使い、Python(FastAPi), Golang(net/http), PHP(Laravel), TypeScript(Nuxt.js or Next.js)など気分に合わせて開発してます。

今回、10/7, 10/8の2日間でChatGPTを作成してみました。

Next.js(13)を触れてみたくて、今回話題のChatGPTを作成することにしました。

開発期間は2日程度なので、UI/UXはまだまだですが暇な時にアップデートしていきます。


1.ローカルで確認したい時に必要なこと

1.1 Google OAuth Loginの設定

今回ログインにGoogleログインを使用した。

下記記事を参考に、IDとSECRET_KEYを.envファイルにセットする。

参考: WEBページに「Googleアカウントでログイン」を実装する

.dotenv
GOOGLE_ID=
GOOGLE_SECRET=

1.2 firebaseの有効化

ChatGPTは対話形式のWebアプリケーションなので、チャットアプリと親和性の高いFireBaseが相性が良いのではと思います。
下記記事を参考に、firebase-admin.jsonを作成してください。

参考: [実装編]Firebaseをセットアップしよう

e16c7a3702ed060d3dd262ad.jpg

このままuploadすると危険なので、.envファイルに移動させる。

NEXT_PUBLIC_FIRE_BASE_API_KEY=
NEXT_PUBLIC_FIRE_BASE_AUTH_DOMAIN=
NEXT_PUBLIC_FIRE_BASE_PROJECT_ID=
NEXT_PUBLIC_FIRE_BASE_BUCKET=
NEXT_PUBLIC_FIRE_BASE_SENDER_ID=
NEXT_PUBLIC_FIRE_BASE_APP_ID=
NEXT_PUBLIC_FIRE_BASE_MEASUREMENT_ID=

1.3 OpenAIでAPIキーを取得

下記記事を参考に、firebase-admin.jsonを作成してください。

参考: [超初心者向け] ChatGPT(OpenAI)のAPI key取得手順

このままuploadすると危険なので、.envファイルに移動させる。

OPENAI_API_KEY=

2. Next.jsのバージョン13について

App Routerを使用すると、今までのpageディレクトリに書く構成とは異なり癖が強い

ただ慣れれば難しくないイメージです。

ただ癖が強いとは言え、Vue.jsoption apiの書き方からcomposition apiに変わったのに比べると優しい。

下記にそれぞれメリットデメリットを書いてく


2.1 Next.js version13のメリット

2.1.1 画面レイアウトを共通ファイルで管理

layout.tsxという重要なファイルが追加された。

これが最強で、画面のレイアウトが簡単になった。

例えば、ログイン画面(/login)と管理画面(/admin/user or /admin/chat)のグラフのページで、レイアウトを分けたいとする、
そう言った場合は、下記のような構成にできる

├── favicon.ico
├── admin
│   ├── layout.tsx // 管理画面用のレイアウト
│   ├── page.tsx
│   ├── user
│   │   └── page.tsx
│   └── chat
│       └── page.tsx
├── globals.css
├── page.tsx
└── login
    ├── layout.tsx // ログイン画面用のレイアウト
    └── page.tsx

このような構成にすることで共通な画面構成をlayout.tsxにまとめることができる。


2.1.2 API単位でのISR

今までNext.jsでISRを実装しようとすると、page単位だった。

今回のアップデートで、API単位でISRの実装が可能になった。

下記でご確認ください

公式ドキュメント


2.2 Next.js version13のデメリット

2.2.1 pagesディレクトリの仕様変更

従来はpagesディレクトリにルーティングを記述していたが、

今回のupdateからAPI を記述するようになったらしい。

説明が難しいのでプロジェクトをご確認ください。

APIを呼び出してる箇所(/api/askQuestion)

/* @/components/ChatInput.tsx */
"use client"

import { FormEvent, HTMLFormElement } from 'react'
import toast from 'react-hot-toast'

type Props = {
  chatId: string
}

const ChatInput = (props: Props) => {
  const sendMessage = async (e: FormEvent<HTMLFormElement>) => {
    // Toast notification
    const chatId = props.chatId
    await fetch("/api/askQuestion", {
      method: "POST",
      headers: {
        "Content-Type": "application/json"
      },
      body: JSON.stringify({
        prompt: input, chatId, model, session,
      }),
    }).then(() => {
      toast.success("ChatGPT has responded!", {
        id: notification
      })
      // aaa
      return ""
    })
  }
  return (
      <form onSubmit={(e) => sendMessage(e)} className={"p-5 space-x-5 flex"}>
        <input
            className={"bg-transparent focus:outline-none flex-1 disabled:cursor-not-allowed disabled:text-gray-300"}
            disabled={!session}
            type="text"
            onChange={(e) => setPrompt(e.target.value)}
            placeholder={"Type your message here..."}
        />

        <button type={"submit"} disabled={!prompt || !session} className={"bg-[#11A37F] hover:opacity-50 text-white font-bold px-4 py-2 rounded disabled:bg-gray-300 disabled:cursor-not-allowed"}>
          <PaperAirplaneIcon className={"h-4 w-4 -rotate-45"} />
        </button>
      </form>
  )
}

export default ChatInput

わかりやすくするために、コードの一部抜粋しています。

全文はここから確認お願いします。

APIを呼び出してる箇所(/api/askQuestion)

/* @/pages/api/askQuestion.ts */
import {NextApiRequest, NextApiResponse} from 'next'
import queryOpenAi from '@/lib/queryApi'

type Data = {
  answer: string
}

export default async function handler(
    req: NextApiRequest,
    res: NextApiResponse<Data>
) {
  const { prompt, chatId, model, session } = req.body

  if(!prompt){
    res.status(400).json({answer: "please provide a prompt!"})
  }

  if(!chatId){
    res.status(400).json({answer: "please provide a valid chat ID!"})
  }

  const response = await queryOpenAi(prompt, chatId, model)


  res.status(200).json({ answer: "成功した" })
}

わかりやすくするために、コードの一部抜粋しています。

全文はここから確認お願いします。

pagesディレクトリに書いた処理はApiとして呼び出すことができる。
今まではViewに特化してた分違和感がある。


17
21
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
21