🪄

Next.js ChatGPTに質問できるコンポーネントを作成

2024/06/28に公開

はじめに

Next.jsで下記のようなコンポーネントを作成してみます。

openai version 4.52.1を使用しています。
https://platform.openai.com/docs/quickstart?context=node

APIの使用料金は課金方式を従量制から前払い制に変更になっています。
https://help.openai.com/en/articles/9100826-how-to-switch-to-prepaid-billing-from-arrears-billing
この変更により、ユーザーはAPIの利用前にクレジットを購入し、そのクレジットを消費する形でサービスを利用することになります。

この新しい課金方式では、クレジットがなくなるとサービスの利用が停止されますが、オートチャージ機能を設定することでクレジットが一定以下になった場合に自動でチャージされる設定も可能になっています。

APIキーの取得

https://platform.openai.com/docs/overview にアクセスし、ログインしてください。

Dashboard

API keys > + Create new secret key

名前と権限を設定して、Create secret keyで、APIキーが発行されます。

APIキーが表示されますが、この画面を閉じてしまうと確認することができません。
閉じる前にコピーして保存しておいてください。

環境変数の設定

OPENAI_API_KEY=

ライブラリのインストール

npm install openai

https://platform.openai.com/docs/libraries

APIルートの作成

Next.jsのAPIルートを使って、OpenAIのAPIとやり取りするサーバーサイドのエンドポイントを作成します。

mkdir -p src/app/api/chat && touch src/app/api/chat/route.ts

今回はv4を使用するので、ConfigurationOpenAIApicreateChatCompletionを使用した書き方は使用していません。
https://stackoverflow.com/questions/76917525/openai-api-error-module-openai-has-no-exported-member-configuration

公式のモデル一覧
https://platform.openai.com/docs/models

api/chat/route.ts
import { NextRequest, NextResponse } from 'next/server';
import OpenAI from 'openai';

export async function POST(req: NextRequest) {
  const { messages } = await req.json();

  console.log('messages', messages);

  const openai = new OpenAI({
    apiKey: process.env.OPENAI_API_KEY || '',
  });

  try {
    const res = await openai.chat.completions.create({
      model: 'gpt-3.5-turbo',
      // stream: true,
      // max_tokens: 150,
      // messages: [{ role: 'user', content: messages }],
      messages,
    });

    console.log('res', res);

    // stream: true;がない時
    console.log(
      'res.choices[0].message.content',
      res.choices[0].message.content
    );
    return NextResponse.json(res.choices[0].message.content, { status: 200 });

    // stream: true;
    // const stream = OpenAIStream(res);
    // return new StreamingTextResponse(stream);
  } catch (err) {
    throw err;
  }
}

コンポーネントの作成

src/appと同じディレクトリにコンポーネントフォルダ作成(src/components
コンポーネントごとのフォルダを作成

mkdir -p src/components/chat && touch src/components/chat/chat.tsx

まずはレスポンスをストリーミング形式ではなく、回答が全て終わってから表示するようにします。

レスポンスをストリーミング形式(生成されたら順番に少しずつ文字を受け取る)で受け取る場合は、後述のVercel AI SDKを使用した方が便利です。

src/components/chat/chat.tsx
import axios from 'axios';
import { useState } from 'react';

interface ChatMessage {
  role: 'user' | 'chatGPT';
  text: string;
}

export function Chat() {
  const [messages, setMessages] = useState<string>('');
  const [chatHistory, setChatHistory] = useState<ChatMessage[]>([]);

  console.log('chatHistory', chatHistory);

  //  stream: true;
  // const { messages, input, handleInputChange, handleSubmit } = useChat({
  //   api: '/api/chat',
  // });

  // stream: true;ではない時;
  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();

    try {
      const res = await axios.post('/api/chat', {
        messages: [{ role: 'user', content: messages }],
      });

      setChatHistory((prev) => [
        ...prev,
        { role: 'user', text: messages },
        { role: 'chatGPT', text: res.data },
      ]);
      setMessages(''); // 入力フィールドをクリア
    } catch (err) {
      console.error('エラー:', err);
      setChatHistory((prev) => [
        ...prev,
        { role: 'user', text: messages },
        { role: 'chatGPT', text: 'エラーが発生しました。' },
      ]);
    }
  };

  return (
    <div className='flex flex-col w-full max-w-md py-24 mx-auto stretch'>
      {/* stream: true; */}
      {/* {messages.map((m) => (
        <div key={m.id} className='whitespace-pre-wrap'>
          {m.role === 'user' ? 'ユーザー名: ' : 'chatGPT: '}
          {m.content}
        </div>
      ))} */}
      {/* <form onSubmit={handleSubmit}>
        <input
          className='fixed bottom-0 w-full max-w-md p-2 mb-8 border border-gray-300 rounded shadow-xl'
          value={input}
          placeholder='Chat GPTにメッセージを送信する'
          onChange={handleInputChange}
        />
      </form> */}

      {/* stream: true;ではない時 */}
      {chatHistory.map((msg, index) => (
        <div key={index} className={`message ${msg.role}`}>
          <span>
            {msg.role === 'user' ? 'あなた' : 'Chat GPT'}: {msg.text}
          </span>
        </div>
      ))}
      <form onSubmit={handleSubmit}>
        <input
          className='fixed bottom-0 w-full max-w-md p-2 mb-8 border border-gray-300 rounded shadow-xl'
          value={messages}
          onChange={(e) => setMessages(e.target.value)}
          placeholder='メッセージを入力してください'
        />
        {/* <button type='submit'>送信</button> */}
      </form>
    </div>
  );
}

支払い情報を登録する

https://ai-island-media.com/2024/01/12/chatgpt-api-acttivate-gpt-4/
https://www.goatman.co.jp/media/chatgpt/openai-payment/

Setting

Billing > Add payment details

個人で登録する場合はIndividual、企業で登録する場合はCompanyを選択

https://kimini.jp/ を参考に入力したらContinue

チャージ金額を入力します。$0では進めないので、最低額の$5で入力してContinue
チャージ設定はあとから変更できます。

内容を確認してComfirm payment

$5チャージされました。

デプロイ後は、急にサービスが停止することになるため、automatic rechargeの設定(自動チャージ)をした方が良いと思います。

なお、支払い情報を登録する前の作成されたAPIキーは削除して、新しいAPIキーを生成する必要があります。
支払い情報を登録した際は反映までに時間がかかる場合があるようです。

月間の予算制限とメール通知の設定

Limits > Usage limitsで下記の設定ができます。

  • 月の予算を設定し、予算を超えた場合にAPIリクエストは拒否する設定
  • 指定した金額を超えた場合はメール通知で通知する設定

Vercel AI SDK

AI機能を備えたアプリケーションを容易に構築できるようにするためのソフトウェア開発キットです。
https://vercel.com/blog/introducing-the-vercel-ai-sdk

インストール

npm install ai
chat.tsx
import { useChat } from 'ai/react';
import { useState } from 'react';

export function Chat() {
  const { messages, input, handleInputChange, handleSubmit } = useChat({
    api: '/api/chat',
  });

  return (
    <div className='flex flex-col w-full max-w-md py-24 mx-auto stretch'>
      {messages.map((m) => (
        <div key={m.id} className='whitespace-pre-wrap'>
          {m.role === 'user' ? 'ユーザー名: ' : 'chatGPT: '}
          {m.content}
        </div>
      ))}

      <form onSubmit={handleSubmit}>
        <input
          className='fixed bottom-0 w-full max-w-md p-2 mb-8 border border-gray-300 rounded shadow-xl'
          value={input}
          placeholder='Chat GPTにメッセージを送信する'
          onChange={handleInputChange}
        />
      </form>
    </div>
  );
}
src/app/api/chat/route.ts
import { OpenAIStream, StreamingTextResponse } from 'ai';
import { NextRequest } from 'next/server';
import OpenAI from 'openai';

export async function POST(req: NextRequest) {
  const { messages } = await req.json();

  const openai = new OpenAI({
    apiKey: process.env.OPENAI_API_KEY || '',
  });

  try {
    const res = await openai.chat.completions.create({
      model: 'gpt-3.5-turbo',
      stream: true,
      messages,
    });

    const stream = OpenAIStream(res);
    return new StreamingTextResponse(stream);
  } catch (error) {
    console.error('error', error);
    throw error;
  }
}

stream:true;

レスポンスをストリーミング形式(生成されたら順番に少しずつ文字を受け取る)で受け取ることができます。

終わりに

何かありましたらお気軽にコメント等いただけると助かります。
ここまでお読みいただきありがとうございます🎉

Discussion