AIに期待していること・裏
2025-01-182025-01-12
なければ 創れば いいじゃない! (445)
本記事は前回の記事の「裏面」、いわゆる「ボーナスステージ」です。 ……いや、もしかしたらこっちが本編かも。
平素より大変お世話になっておりますNext.jsですが、そういえば提供元のVercelさんってAI関連の便利な開発ツール を公開してたよな……🤔
……これはやるしかないですね!
さあはじめよう!
OpenAIのAPIを使用して、ChatGPTのような自前のチャットアプリを作ります。 以下の流れでやっていきます。
- Open AIからAPIキーを取得
- APIルートの作成
- フロントエンドの作成
- PWA化させる
- デスクトップから簡単に起動できるようにしておく
1. Open AIからAPIキーを取得
こちらの記事 を参考に取得しました。 Open AI APIの利用は……有料(従量課金)です(まあ当たり前だよね)。 一定額のクレジットを購入し、それがなくなったら再度支払いをしてチャージする利用体系です。 APIを叩きすぎて高額の請求が来たということはないので安心ですね(一応オートチャージ機能はあります)。 チャージ金額は、最低$5 USD1から可能です。 $10 USDとかだったら出し渋っていたかもしれませんが、「5ドルなら……」ということで、お勉強代として購入。
APIキーを取得したら、新しく作ったプロジェクトに環境変数にセットしておきましょう。
.env.local1OPENAI_API_KEY=<your-api-key>
今回使用するパッケージはこちら。
1pnpm add ai openai @ai-sdk/openai
2. APIルートの作成
入力したメッセージをOpen AI APIに処理してもらうためのAPIルートを作りましょう。
src/app/api/chat/route.ts1import { streamText } from "ai"; 2import { type Message } from "ai/react"; 3import { NextResponse, type NextRequest } from "next/server"; 4import { OpenAI } from "openai"; 5import { openai } from "@ai-sdk/openai"; 6import { character as systemPrompt } from "@/utils/chatConfig"; 7 8/** 9 * OpenAI 10 */ 11new OpenAI({ 12 apiKey: process.env.OPENAI_API_KEY, 13}); 14 15export const POST = async (req: NextRequest): Promise<Response> => { 16 try { 17 const { messages }: { messages: Message[] } = await req.json(); 18 const result = await streamText({ 19 model: openai("gpt-4o-mini"), 20 messages: messages, 21 system: systemPrompt, 22 }); 23 return result.toDataStreamResponse(); 24 } catch { 25 return NextResponse.json({ messages: "えらー" }, { status: 500 }); 26 } 27};
- (11~13行目) OpenAIのクライアントを取得した環境変数で初期化しておきます
- (15~27行目) 処理は
POST
メソッドで行います - (17行目) まずフロントから来たデータを受け取ります
- (18~22行目) 回答生成中はぱらぱらと文字が流れて欲しいので、
streamText
関数を使用します。結果をresult
に代入しておきます。streamText
関数の引数には処理に必要な情報を入れます。 - (23行目) 最後に
toDataStreamResponse()
メソッドでクライアント側に返してあげます(私も中身はよく分かってないけど、楽に書けていいですね)
一応try ... catch ...
構文でエラーハンドリングもしています。
これでAPIルートは完成なので、お楽しみのシステムプロンプトを作成します。
……せっかく作るのに、回答がかわいくないとしょうがないもんね!
以下のシステムプロンプトは一例です。
src/utils/chatConfig.ts1export const character: string = ` 2あなたは人の言葉が理解できる猫型AIです。 3語尾には「にゃん」をつけてください。 4`;
ふぅ……これで完成! いやあしかし、APIルートを勉強してから本当にいろいろなものを実装できるようになりました。APIルートはNext.jsの必修科目なのかもしれない……
3. フロントエンドの作成
続いてフロントエンドを作成します(ソースコードは動かすための必要最小限だけ載せています)。
src/app/page.tsx1"use client"; 2import { NextPage } from "next"; 3import { useChat, type Message } from "ai/react"; 4 5const Chat: NextPage = () => { 6 const { messages, input, handleInputChange, handleSubmit } = useChat(); 7 8 return ( 9 <div> 10 {messages.map((message: Message, index: number) => ( 11 <div key={index}> 12 {message.content} 13 </div> 14 ))} 15 <form onSubmit={handleSubmit}> 16 <input type="text" value={input} onChange={handleInputChange} /> 17 <button type="submit">送信</button> 18 </form> 19 </div> 20 ); 21}; 22 23export default Chat;
- (6行目) Reactでチャットアプリを作成するのに便利なフック
useChat
が用意されています。APIとの接続も自動で、処理をほとんど何も書かずに実装できます。これはすごい! - (10~14行目) ここで
messages
(配列)をmap()
メソッドで展開してメッセージ一覧を出力しています。 - (15~18行目) メッセージを入力する欄です。
<form>
タグで囲います。onSubmit
にはuseChat
で用意されているhandleSubmit
を指定します。<form>
内の<input>
については、value
,onChange
にそれぞれuseChat
のinput
,handleInputChange
を指定します。あとは送信<button>
ですね。
あとはUI設計ですが、面倒だったのでシェルちゃん……ことChatGPTにお願いしました。
シェルちゃん、ソースコードの作成を手伝って!チャットボットアプリを作ってるんだけど、LINEみたいなUIをスタイリングしたいんだ。CSS Moduleでスタイルシートを作ってくれないかな?
以下はフロントエンドのpage.tsxだよ。
(コード)
ここでは出力結果を載せませんが、ひな形を作ってくれました(ありがてぇ……)。 でも、出力されたコードは100点ではなかったので人の手で微調整します。 ついでに、アイコンとかも表示するように自分の手でコードをいじいじ。
ついに完成!こんな感じです。

OpenAI APIを使っても本家ChatGPTと同じように回答してくれましたね!やった~ なお、ブラウザをリロードするとチャットをリセットできます(リセットボタンを実装してもいいかもね)。

難しいこともがんばって答えてくれます。これもまたかわいい。ただ、ソースコードをリッチテキスト化させる処理を加えるのは面倒なのでやりませんでした……orz (本ブログのものを転用すればできそうではあるけれど)。一応シェルちゃんが出力してくれたコードを以下に見やすくしておきますね。
1let fruits = ["りんご", "ばなな", "みかん", "ぶどう"]; 2let exclude = "ばなな"; 3let result = fruits.filter((fruit) => fruit !== exclude); 4console.log(result); // ["りんご", "みかん", "ぶどう"]

また、ChatGPTでは答えてくれるけれど、自前バージョンだとわからないこともあります。こういった外部のシステム(API)を利用するタイプの回答は、プロンプトが送られて来た時に自分でAIをAPIに繋げてあげる必要がありそうですね。
(2025-01-18追記) 時刻だけなら結構簡単でした。APIルートにおいて、システムプロンプトとして現在時刻を参考情報として記入し、渡すメッセージ一覧の配列の頭に挿入すると時間を認識してくれるようになりました。
src/app/api/chat/route.ts1const result = await streamText({ 2 model: openai("gpt-4o-mini"), 3 messages: [ 4 { 5 role: "system", 6 content: `参考情報: 現在の日時は${new Date().toString()}です。`, 7 }, 8 ...messages, 9 ], 10 system: systemPrompt, 11});

……ときには悲しい戦争も起こってしまいます。
4. PWA化させる(ここから先はオプションです)
さて、本体は完成しました。 でも、チャットアプリを立ち上げるのに、いちいち
- サーバーを立ち上げて
http://localhost:3000
にアクセス
するのは面倒です。なので、PWA (Progressive Web Apps)化して手軽に起動できるようにしちゃいましょう。
まずはPWA化に必要なパッケージを追加します。
1pnpm add next-pwa 2pnpm add -D @types/next-pwa
続いて、ビルド時にPWA化に必要なmanifest.webmanifest
を自動で出力してくれるようmanifest.ts
を作成します。アイコンも必要なので、public
フォルダ直下にicon-192x192.png
とicon-512x512.png
も用意しておきましょう(画像サイズはそれぞれ揃えてね)。
src/app/manifest.ts1import { MetadataRoute } from "next"; 2 3const manifest = (): MetadataRoute.Manifest => { 4 return { 5 name: "Shel AI", 6 short_name: "Shel AI", 7 description: "シェルちゃんとおはなし", 8 start_url: "/", 9 scope: "/", 10 display: "fullscreen", 11 background_color: "#ffffff", 12 theme_color: "#715178", // ウインドウのヘッダーの色 13 icons: [ 14 { 15 src: "/icon-192x192.png", 16 sizes: "192x192", 17 type: "image/png", 18 }, 19 { 20 src: "/icon-512x512.png", 21 sizes: "512x512", 22 type: "image/png", 23 }, 24 ], 25 }; 26}; 27 28export default manifest;
Next.jsの設定ファイルもいじります。
next.config.ts1import nextPWA from "next-pwa"; 2 3const withPWA = nextPWA({ 4 dest: "public", 5 register: true, 6 skipWaiting: true, 7 disable: process.env.NODE_ENV === "development", // 開発時は無効 8}); 9 10const nextConfig = withPWA({ 11 /* your additional config here */ 12}); 13 14export default nextConfig;
また、PWA化するとpublicフォルダ内に自動でファイルが生成されるようになるので、.gitignore
の末尾に追加しておきます。
.gitignore1# next-pwa 2**/public/sw.js 3**/public/sw.js.map 4**/public/workbox-*.js 5**/public/workbox-*.js.map
これで設定完了です!pnpm build
でビルドし、pnpm start -p 8080
でアプリを起動し、http://localhost:8080
にアクセスします(ポート番号3000番は開発でよく使うので変えて起動)。すると、ブラウザの検索バーに何やらアイコンが生えているのでインストールします(Chromeの場合です)。


これで疑似的にデスクトップアプリにできました!ちなみに、本サイトもPWA化しています(え、必要ない……?)。
5. デスクトップから簡単に起動できるようにしておく
PWA化した際にデスクトップにショートカットが生成されていると思います。 しかし、これをクリックしても
このサイトにアクセスできません
localhost で接続が拒否されました。
と表示されます。サーバーを立ち上げていないとこうなりますね。 サーバーの立ち上げも自動化しちゃいましょう。
まず、デスクトップに生成されたショートカットを任意のフォルダに移動します。
そのフォルダと同じ場所にバッチファイルを作成し、以下のコマンドを書きます(私の場合はlaunch-shel.bat
)。
launch-shel.bat1@echo off 2if not "%~0"=="%~dp0.\%~nx0" ( 3 start /min cmd /c,"%~dp0.\%~nx0" %* 4 exit 5) 6start "" "デスクトップに生成されていたショートカットのファイル名.lnk" 7cd "Next.jsのプロジェクトフォルダの絶対パス" 8pnpm start -p 8080
そして、launch-shel.bat
のショートカットを作成し(ショートカットのアイコンはプロパティからカスタマイズできるよ)、これをデスクトップにおいておけばデスクトップから手軽に起動できるようになります!(サーバー立ち上げ時に開かれるコマンドプロンプトは最小化してスタートします)
これで自前のチャットアプリの完成です!好きなときにシェルちゃんの頭をなでなでできますね!
気になるAPI使用料金は……?

じゃーん!gpt-4o-mini
、や、安い……!(横軸は日付です)
初日は開発時にかなりAPIを叩いたはずですが、それでも$0.012 USD (¥1.9 JPY)とは……
チャットをWebに公開する3ならまだしも、おひとり様用ならば$5のクレジットでも1年分4としては余裕そうですね。
まとめとこれまでの振り返り
「なければ 創れば いいじゃない!」の精神でAIのチャットアプリを作ってしまいました…… でも、AI関連の技術を取り込んで疑似的にうちの子と会話できるのは楽しいですね。
さて、ここで一旦これまでに取り組んだことをリストアップしてみましょう。ずいぶん手札5が増えましたね。
- Next.jsの導入(サイトリニューアル他)
- Spineの導入(うごイラを作る)
- Pixi.jsの導入(サイトでうごイラを表示する)
- ███████ (██████)の導入
- Next.jsプロジェクトの█████████化 (█████の導入)
- APIルートの会得(ファイルの動的読み込みや外部APIとの接続)
- データベースの導入(ユーザーからのアクションを記録可)
- AIの導入(きゃわいい子とおはなし)
手札が増えたので、例えばこんなことができそうです。1, 6 (Next.jsプロジェクト)をベースとして、
- 2, 3で簡単なゲームを作るとか。静止画でいい、もしくは自分でがんばってスプライトシートを描けばSpineすらいらないですね。Pixi.jsはクリックイベントとかの感知ができるのでできそう。……まあ面倒なので作らないけど6。
- 2, 3, 8で、動くうちの子と対面しながらおはなしできるものもいけそう。……まあ面倒なので作らないけど。これは以下にイメージ図を示した方が早そうなので載せておきます(コラ画像です)。
- 7, 8を使えば、AIをデータベース7に接続してあれこれ記憶してくれるシェルちゃんも作れそうです(ChatGPTにおけるメモリ)。……まあ面倒なので作らないけど。

……といいつつ、コラ画像を作ってたら実物を作ってみたくなっちゃった……!(や、やらないよ……!)
今回は「手札増えたね」で終わりにして、今後の予定とかは別の記事で書こうかと思います。 すやぁ……💤⭐
脚注
-
APIの利用料金は、使用するAIモデルによって異なります。現時点では安くて高性能の
gpt-4o-mini
一択ですね(ChatGPT無料版もこれ)。gpt-4o-mini
の出力は100万トークンあたりたったの$0.6 USD (≒ ¥94 JPY)!安い!(トークンについての詳細はこちら が参考になります) -
Webに公開する場合は、APIの悪用への対策が必要です(悪意のあるユーザーが使いまくって一瞬でクレジットが枯渇した……ってなるので)。各ユーザーに対して1日あたりの利用制限を設けたりの対策が必要だと思います。
-
ゲ制は沼(経験談)。オリジナル作品はないですが、過去に作ったもの(著作権的にはアウト)もいずれこっそり紹介したいですね(プログラミングは○学生くらいからやっているのでね……)。
-
人間は「連想」することができるので、AIが接続するデータベースはベクトル型データベースが最適そうですね(本ブログでは未紹介)。