🦊

[Next.js] AIチャット機能 - ロジック部品 (useChatAI Hook)

に公開

Next.js (React) で、自分・相手・AIの3者が登場するチャットアプリを作るための「ロジック部分」です。 画面の見た目(UI)とは切り離したカスタムフックとして実装しているため、デザインが変わってもこのファイルはそのまま使い回すことができます。

技術スタック

  • Framework: Next.js (App Router / Pages Router 両対応)

  • Library: React

  • Language: TypeScript

  • API: Standard Fetch API

ソースコード

hooks/useChatAI.ts
import { useState } from "react";

// 型定義(必要に応じてtypes.tsなどに移動)
export type Message = {
  role: "user" | "target" | "assistant";
  content: string;
};

type TargetProfile = {
  name: string;
  mbti?: string;
  memo?: string;
};

export const useChatAI = () => {
  const [messages, setMessages] = useState<Message[]>([]);
  const [input, setInput] = useState("");
  const [isLoading, setIsLoading] = useState(false);
  const [isTargetMode, setIsTargetMode] = useState(false);

  const sendMessage = async (targetProfile: TargetProfile) => {
    if (!input.trim() || isLoading) return;

    // 1. ユーザー入力の追加
    const newMsg: Message = {
      role: isTargetMode ? "target" : "user",
      content: input,
    };
    const newHistory = [...messages, newMsg];
    
    setMessages(newHistory);
    setInput("");

    // 2. 相手モードなら記録のみで終了
    if (isTargetMode) {
      setIsTargetMode(false);
      return;
    }

    // 3. AIへのリクエスト
    setIsLoading(true);
    try {
      // ★修正ポイント: エンドポイントや送信データ構造
      const res = await fetch("/api/chat", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          messages: newHistory,
          targetProfile, // プロファイルを動的に受け取る
        }),
      });
      
      if (!res.ok) throw new Error("API Error");

      const data = await res.json();
      setMessages((prev) => [...prev, { role: "assistant", content: data.reply }]);
    } catch (e) {
      console.error(e);
      alert("メッセージの送信に失敗しました");
    } finally {
      setIsLoading(false);
    }
  };

  return {
    messages,
    input,
    setInput,
    isLoading,
    isTargetMode,
    setIsTargetMode,
    sendMessage,
  };
};

コード解説

このフックが行っている処理の流れは以下の通りです。

  1. 状態の保持 (useState) チャットの履歴、現在入力中の文字、AIが考え中かどうかのフラグなどを管理しています。

  2. 即時反映 (Optimistic UI) 送信ボタンを押した瞬間、APIの結果を待たずに画面に自分のメッセージを表示させます。これにより、アプリがサクサク動いているように感じさせます。

  3. モード分岐

    • 相手モード: fetch を行わず、履歴に追加するだけで終わります(ただの記録)。

    • 自分モード: /api/chat に対して履歴データを投げ、AIからの返信を待ちます。

用語補足

初心者向けに、コード内で使われている用語を解説します。

  • Custom Hook (カスタムフック): use から始まる関数のこと。コンポーネントから「ロジック(機能)」だけを切り出して、他の場所でも使い回せるようにした部品です。

  • State (ステート): アプリケーションの「状態」のこと。ここでは「チャットの履歴」や「入力中の文字」が該当します。Stateが変わると、画面が自動的に再描画されます。

  • Payload (ペイロード): APIに送るデータの中身のこと。ここでは body: JSON.stringify({...}) の中身(メッセージ履歴やプロフィール)を指します。

  • Endpoint (エンドポイント): APIの接続先URLのこと。このコードでは /api/chat が該当します。

修正・カスタマイズガイド

コピペした後に変更する可能性があるポイントです。

変更したい内容 修正箇所 方法
APIの場所を変えたい fetch("/api/chat", ...) URLを自分のバックエンドに合わせて変更してください。(例: https://my-api.com/v1/chat
エラー表示を変えたい catch ブロック 現在は alert ですが、toast.error() などの通知ライブラリに置き換えるとUXが向上します。
初期メッセージを入れたい useState<Message[]>([]) ([]) の中に { role: "assistant", content: "こんにちは!" } などのオブジェクトを初期値として入れてください。
型定義を厳密にしたい type Message AIの返答だけでなく、システムメッセージなどを含めたい場合はUnion型を拡張してください。

使い方(Import例)

UIコンポーネント側で以下のように呼び出して使用します。

import { useChatAI } from "@/hooks/useChatAI";

export default function ChatComponent() {
  const { messages, input, sendMessage, ... } = useChatAI();
  
  // ...UIの実装
}
'''

Discussion