ポートフォリオ:Nextjs × Supabase AIチャットアプリ
イントロダクション
こんにちは!今回はポートフォリオの一つとして制作したWebアプリケーション「MOOWAY!」(モーウェイ!)を紹介します。
自己紹介
新卒で広告代理店に入り、営業や広告運用などに4年間携わっていました。
今年、エンジニアの友人と起業したのですが、色々あって彼の力を借りられなくなってしまったので、4ヶ月ほど前からWeb開発を学び、再出発を図っています。
ポートフォリオの紹介
▼サービスメイン画面
サービス概要
「MOOWAY!」は”「我慢する人」と「我慢できる人」が損をしない働き方を支える”ことを目的とした、AIチャットアプリです。
友だち感覚でAIのキャラクターに仕事の愚痴をもらすだけで、その愚痴を職場の課題として言語化します。
そして課題への解決策も併せて提示することで、愚痴を愚痴で終わらせない建設的な愚痴体験を提供します。
▼サービスURL ※原則PCでの利用を想定しています。
▼GitHubリポジトリ
開発背景
私は、職場で「無意味な仕事」に苦しむ人たちを一定数見てきました。例えば、
- 締め切りやルールを守らない人のフォローで疲弊している人
- 上司を偉く見せるための取り巻きを演じさせられている人
本来必要のない仕事を"我慢"し続け、全く意味のない無価値なものに労力をかける日々。
そんな毎日に、中には精神的な病を患ってしまった人もいました。
"我慢"してくれる優しい人は、このような状況にも文句を言わず、ひたむきに頑張ってくれています。
でも"我慢"できるからこそ、結局何が無意味なことだったのかを忘れてしまい、
根本的な改善に踏み切れないこともあります。
そこで、愚痴を「課題」として言語化することで
これは解決すべき「課題なんだと認知する」きっかけと、その解決策を提供するべきだと考えました。
エンジニアの友人と起業をした際に、この「MOOWAY!」を企画し、彼にアプリを制作してもらっていました。
しかし記述の通り、彼が立つ鳥となり、Web開発を学ぶ素晴らしい機会を跡に残してくれたので、0から自力で「MOOWAY!」を作ってみることにしました。
また、ロゴやUIのデザインは私が作成したラフやレイアウトなどの草案をもとに、主にデザイナーの友人に作成していただきました。
サービスの使い方
チャットで仕事の愚痴をもらす
一般的なチャットアプリと同じ感覚で、チャットの入力フォームから愚痴を送信します。
仕事の愚痴から言語化された課題を認知する
愚痴を入力すると、ChatGPT APIにメッセージが投げられます。
AIが愚痴だと判定した場合は、その愚痴を課題として言語化し、画面に表示します。
課題の解決策を確認する
解決したい課題をクリックして、AIに解決策を考えてもらうことができます。
使用技術
フロントエンド
- Next.js(14.0.3)
- React(18.0.0)
- TypeScript(5.0.0)
- Tailwind CSS(3.3.0)
- Zod(3.22.4)
- ESLint
バックエンド
- Next.js(14.0.3)
- Route Handlers:APIエンドポイントの作成と管理
データベース・認証
- Supabase(2.39.0)
その他
- OpenAI(4.20.1)
- Date-fns
- Vercel
ER図
データベースには、ユーザーからのメッセージとAIからの返答を格納するだけなので、非常にシンプルな構成になっています。
主な技術選定の理由
フロントエンド:React/Next.js
フロントエンドについては、Next.jsで作成するということを決めていました。
理由としては、以下の通りです。
- ベンチマークした転職先候補の会社様の多くがNext.jsを採用していた
- トレンドとしても、React/Next.jsは人気が高まっているテクノロジーだと感じた
バックエンド:React/Next.js
Railsも検討しました。
しかし、今回のアプリケーションは、データベースやChtaGPTとのやりとりは非常にシンプルなものなので、Next.jsのRoute Handlersで十分対応可能だと考えました。
データベース・認証:Supabase
FirebaseとSupabaseで検討しました。
データベースについてはRDBの方が少しは前提知識があるのと、Next.jsでの活用に関する公式のチュートリアルも充実していたので、Supabaseを選択しました。
認証やリアルタイムリスナーも比較的簡単に実装できたので、要件を十分満たすことができたと思います。
工夫した点
Vercelのサーバーレス関数のリクエスト時間制限の対応
Vercelのサーバーレス関数は、Vercelの利用プランに応じて最大継続時間に制限があります。 (無料プランで最大5秒)
Next.jsのRoute Handlersで作成したAPIを使って、データベースの更新やChatGPTとの通信を行うため、この制限時間に注意する必要がありました。
まず、メイン機能であるチャットにおいては、ユーザーがメッセージを送信すると以下のような処理が発生します。
- ユーザーのメッセージをテーブルAに挿入
- 1.のユーザーのメッセージをChatGPTに送信し、回答をもらう
- 2.の応答をテーブルBに挿入する
- 1.のユーザーのメッセージをChatGPTに送信し、「仕事の愚痴」かどうか判定してもらう
- 4.の結果がtrueの場合は、再度1.のメッセージをChatGPTに送信し、課題のタイトルをもらう
- 5.の返答を1.で作った同じレコードの別カラムに更新する
- 再度1.のメッセージをChatGPTに送信し、課題の説明をもらう
- 7.の返答を1.で作った同じレコードの別カラムに更新する
まずこれら全ての通信を一つのAPIリクエストにまとめ、実現させようとしました。
const res = await fetch(`/api/response`, {
method: "POST",
//以下API
export async function POST(request: Request) {
const { prompt } = await request.json();
//1. ユーザーのメッセージをテーブルAに挿入
await supabase
.from('TABLE_A')
.insert([
{ content: prompt },
])
.select()
//2. 1.のユーザーのメッセージをChatGPTに送信し、回答をもらう
const gptResponseMessage = await sendPromptToGpt(prompt)
//3~8を記述
しかし当然、これら全ての処理を5秒以内に終わらせることは困難で、特にChatGPTとの通信にはそれなりに時間がかかります。
「Hi!」などの短いメッセージでもタイムアウトになることが多く、対応が必要でした。
1.ChatGPTに送るシステムプロンプトの削減
このアプリケーションではキャラクターとのやりとりを擬似的に実現しているので、システムに知らせるべきキャラクターの情報もあり、プロンプトの量が多くなりがちでした。
理想的な返答が生成されるようプロンプトを作り、その後プロンプトの量を減らしても理想に近い返答が生成されるようplaygroundでひたすら調整を行いました。
多少マシにはなりましたが、メッセージの長さが20文字を超えるとほぼタイムアウトになるため、まだまだ改善が必要でした。
2.詰め込んでいた処理を分割する
1つのサーバーレス関数に全ての処理を詰め込んでいたので、全ての処理をそれぞれ個別のAPIとして独立させ、それぞれの処理が完了すると次のAPIをリクエストするように修正しました。
この修正で、100文字ほどの文章であれば、ほとんどタイムアウトに引っかからずに処理を完遂することができるようになりました。
あくまでチャット感覚なので、短文ベースなやり取りを想定はしていますが、100文字を超えてくるとエラーの確率もかなり高くなってくるので、まだ改善が必要です。
3.フォームの入力文字数を制限する
一番直接的かつ容易(安易)な方法として、フォームの入力文字数を100文字に制限しました。
ユーザーに制限をかけるのはベストな方法だとは言えませんが、100文字以内でも十分愚痴を書けるのと、エラーが発生しないという最大のメリットを優先しました。
4.Vercelの有料プランを使う
100文字に規制しても、まだ完全にエラーが出ないというわけではなかったので、Vercelの有料プランを使い、
リクエストの最大時間を300秒にまで拡大しました。
技術のみで解決することができず悔しい気持ちもありますが、詰め込んでいた処理を分割しない状態で300秒荷拡大しても、5割ほどはエラーになっていたので、意味のある改善ができたのではないかと考えています。
その他工夫したところ
- ChatGPTがメッセージを生成するまでの間、吹き出しで「...」を表示して、相手が考えているような印象にする
- 「既読」や「送信時間」をメッセージ横に表示することで、ユーザーの見慣れたチャットUIに近づける
- 愚痴に対してChatGPTがかなり諭そうとしてくる(あまり愚痴の対象となる相手のことを批判してくれない)ので、なるべく諭してこないようプロンプトを調整。
でも「頑張ろう!」みたいな、頑張ってる人に向かってあまり言ってほしくないセリフがどうしても出てきやすいので、さらなる調整は必要。
結論と次のステップ
今回は、要件を満たしたサービスをポートフォリオとしてまず一つ作ってみる、ということを達成できたと思います。
起業した際に作り切ることのできなかったサービスを、こういう形であっても、自分で完成させられたのは、個人的にとても意味のあるアクションでした。
次は、Next.jsとRailsでの開発や、使いこなせていないDockerやテストなどにも挑戦して、もっと本格的なWebアプリケーションの開発を行ってみたいと思います。
また、綺麗で無駄のないコーディングや的確な変数名の指定などは、もっと質を高める必要があると感じています。
今回は要件を満たした機能の実装で精一杯になってしまったところもあったので、次回以降の改善点だと感じています。
Discussion