ChatGPTをゼロから作ってみた
作成したアプリの画像
下記でアプリを実際に動かせるのでよかったら確認してみてください。
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アカウントでログイン」を実装する
GOOGLE_ID=
GOOGLE_SECRET=
1.2 firebaseの有効化
ChatGPTは対話形式のWebアプリケーションなので、チャットアプリと親和性の高いFireBaseが相性が良いのではと思います。
下記記事を参考に、firebase-admin.jsonを作成してください。
参考: [実装編]Firebaseをセットアップしよう
このまま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.jsのoption 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に特化してた分違和感がある。