mcpField
A
ai-ui-patterns

ai-ui-patterns

github.com/patternsdev開発2572.0k 回インストール28 週間前に更新
リポジトリ

ai-ui-patterns について

ai-ui-patternsは、ViteとNext.jsの実装例を含む、AIインターフェース構築のためのReact開発者向けガイドです。会話状態やストリーミング応答、エラー処理のパターンに加え、Vercel AI SDKと再利用可能なコンポーネントを扱います。

主な機能

  1. 01プロンプトと会話状態の管理
  2. 02AI応答のストリーミング表示
  3. 03入力のデバウンス処理
  4. 04エラー処理と再試行
  5. 05再利用可能なUIコンポーネントの作成
  6. 06ViteとNext.jsの構成比較

活用シーン

  1. 01Reactアプリのチャット画面構築
  2. 02リアルタイムAIオートコンプリートの実装
  3. 03サーバーからのAI APIの安全な呼び出し

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/patternsdev/skills --skill ai-ui-patterns

インストール後、Claude Code で/ai-ui-patternsを入力して読み込まれたか確認します。

詳細

AI UI パターン

目次

AI を活用したインターフェースを構築するには、チャットボットからインテリジェントなアシスタントまで、バックエンドの AI サービスとリアクティブな UI コンポーネントを慎重に統合する必要があります。この章では、このようなインターフェースの React におけるデザインパターンを、2 つの実装に焦点を当てて解説します。Vite を使う通常の React アプリと、Next.js アプリです。AI エンジンには Vercel AI SDK 経由で OpenAI の API を使用し、スタイリングには TailwindCSS を使用します。主なトピックとして、プロンプト管理、ストリーミングレスポンス、入力のデバウンス、エラー処理、そしてこれらのパターンが Vite と Next.js でどのように異なるかを取り上げます。また、洗練されたチャット UI を構築するための再利用可能なコンポーネントパターンと、**Vercel の AI UI コンポーネント(AI Elements)**についても紹介します。

使用する場面

  • LLM からのレスポンスをストリーミングする会話型 AI インターフェースを構築する場合に使用します
  • OpenAI、Anthropic、その他の AI プロバイダーを React アプリケーションに統合する際に役立ちます
  • プロンプト管理、ストリーミング、エラー処理、AI 固有の UI のパターンが必要な場合に使用します

手順

  • Vercel AI SDK の useChat フックを使用して、会話の状態とストリーミングレスポンスを管理します
  • API キーはサーバー上に保持します。AI 呼び出しには、Next.js API ルートまたは別のバックエンドを使用します
  • チャットインターフェースでリアルタイムのレスポンスを実現するため、ストリーミング(stream: true)を有効にします
  • オートコンプリート機能では入力をデバウンスし、チャットではレスポンスのストリーミング中に入力を無効にします
  • データ取得ロジックから切り離した、再利用可能なコンポーネント(ChatMessage、InputBox)を構築します

詳細

**注:**この記事では例として OpenAI を使用していますが、Vercel AI SDK は Gemini、OpenAI、Anthropic など、複数のモデルプロバイダーをサポートしています。SDK の統一されたインターフェースを通じて、プロバイダーを簡単に切り替えられます。ここではデモのために、選択肢の 1 つとして OpenAI を使用しています。

はじめに:React の AI インターフェース

ChatGPT のような LLM の普及に伴い、AI 主導のユーザーインターフェース(UI)が広く使われるようになりました。従来の UI とは異なり、AI インターフェースでは会話型のインタラクション、動的なコンテンツのストリーミング、非同期のバックエンド呼び出しがよく発生します。これにより、React 開発者には固有の課題やパターンが生まれます。一般的な AI チャットインターフェースは、ユーザー入力とレスポンスの表示を担うフロントエンドと、AI モデルを呼び出すバックエンドで構成されます。セキュリティとパフォーマンスの観点から、API キーや負荷の高い処理をクライアントから切り離すには、バックエンドが不可欠です。Vercel の AI SDK などのツールを使うと、プロバイダー(OpenAI、HuggingFace など)への接続や、レスポンスのリアルタイムストリーミングが容易になります。これらの課題に対処するための Next.js アプリと Vite(React)アプリのセットアップ方法を解説し、どちらにも適用できるベストプラクティスについて説明します。

取り上げる主なパターン:

  • AI プロンプトデータの構成と会話状態の管理
  • リアルタイムのフィードバックを実現する、AI レスポンスの UI へのストリーミング
  • API への過剰なリクエストを避けるためのユーザー入力のデバウンス
  • UX におけるエラー処理とフォールバック
  • メッセージや入力欄などの再利用可能な UI コンポーネント(TailwindCSS を使用)
  • アーキテクチャの違い:Next.js のルートハンドラーと、Node バックエンドを使う Vite

この章を読み終える頃には、Next.js と Vite のどちらを選んでも、レスポンスが速く堅牢な AI 搭載型 React UI を構築できるようになります。

プロジェクトのセットアップとツール

コードに取りかかる前に、必要なパッケージと設定が揃っていることを確認してください。

  • React & Vite: Vite + React プロジェクトを初期化します(例:npm create vite@latest my-ai-app -- --template react)。Next.js の場合は、npx create-next-app または Next 13 App Router テンプレートを使用できます。どちらも利用できます。違いについては、順を追って説明します。

  • TailwindCSS: 手早くスタイルを設定できるよう、プロジェクトに Tailwind をセットアップします。

  • OpenAI API & Vercel AI SDK: OpenAI のライブラリまたは Vercel AI SDK をインストールします。ここでは、便利な React フック(useChat、useCompletion)とサーバーユーティリティを提供する Vercel の AI SDK(npm i ai)を使用します。この SDK はフレームワークに依存せず、Next.js、vanilla React、Svelte などで動作します。ストリーミングと状態管理を簡素化でき、無料で利用できるオープンソースです。

  • API キー: OpenAI ダッシュボードから OpenAI API キーを取得し、安全に保管します。Next.js では、.env.local に記述し(例:OPENAI_API_KEY=sk-...)、絶対にコミットしないでください。Vite アプリでは、クライアントコードにキーを公開しないでください。代わりに、バックエンドのプロキシまたはサーバー上の環境変数を使用します。

AI エンドポイントのセットアップ(Next.js と Vite)

Next.js の実装: Next.js では、サーバーレス関数としてルートハンドラーを作成できます。React フロントエンドが AI の応答を取得するために呼び出す API ルートを定義できます。

// app/api/chat/route.ts (Next.js)
import { Configuration, OpenAIApi } from 'openai-edge';
import { OpenAIStream, StreamingTextResponse } from 'ai';

export const runtime = 'edge';

const config = new Configuration({ apiKey: process.env.OPENAI_API_KEY });
const openai = new OpenAIApi(config);

export async function POST(req: Request) {
  const { messages } = await req.json();
  const response = await openai.createChatCompletion({
    model: 'gpt-3.5-turbo',
    stream: true,
    messages: messages.map((m: any) => ({ role: m.role, content: m.content }))
  });
  const stream = OpenAIStream(response);
  return new StreamingTextResponse(stream);
}

このハンドラーでは、メッセージの配列(チャット履歴)を含む JSON ボディを受け取ります。stream: true を指定して OpenAI のチャット補完を呼び出し、ストリーミング応答を取得します。次に、AI SDK が提供する StreamingTextResponse で応答をラップし、チャンク単位でクライアントに返します。Next.js の API ルートを使うことで、API キーをサーバー上に保持し、効率よくデータをストリーミングできます。

Vite(React)の実装: Vite アプリにはサーバーが組み込まれていないため、OpenAI の呼び出し用に独自のバックエンドを作成する必要があります。シンプルな Node/Express サーバーを利用できます。

// backend/server.js (Node/Express for Vite app)
import express from 'express';
import { Configuration, OpenAIApi } from 'openai';

const app = express();
app.use(express.json());

const config = new Configuration({ apiKey: process.env.OPENAI_API_KEY });
const openai = new OpenAIApi(config);

app.post('/api/chat', async (req, res) => {
  try {
    const { messages = [] } = req.body;
    const systemMsg = { role: 'system', content: 'You are a helpful assistant.' };
    const inputMessages = [systemMsg, ...messages];
    const response = await openai.createChatCompletion({
      model: 'gpt-3.5-turbo',
      stream: false,
      messages: inputMessages
    });
    const content = response.data.choices[0].message?.content;
    res.json({ content });
  } catch (err) {
    console.error(err);
    res.status(500).json({ error: 'Internal Server Error' });
  }
});

app.listen(6000, () => console.log('API server listening on http://localhost:6000'));

開発中は、Vite の開発サーバーで /api の呼び出しをこのバックエンドにプロキシするよう設定できます(例:vite.config.js で server.proxy['/api'] = 'http://localhost:6000' を設定します)。重要なのは、React アプリが相対パスの /api/chat エンドポイントを呼び出し、プロキシまたはホスティング環境がそのリクエストをサーバーコードに振り分けることです。これにより、OpenAI のキーを非公開に保てます。

Node でのストリーミングの有効化: 上記の Express の例では、簡潔さのため stream: false とし、完了後に応答全体を返しています。Node でストリーミングするには、OpenAI の HTTP ストリームを使用します。stream: true を設定し、応答をデータストリームとして処理します。そのためには、response.data ストリームを読み取り、res.write() でチャンクをクライアントに送信します。ストリーミングなしで応答全体を返す方法を選んでも、UI のパターンはおおむね同じように適用できますが、ストリーミングによって UX は大幅に向上します。

プロンプトの処理と会話状態

あらゆる AI インターフェースの中核となるのは、プロンプト管理です。ユーザー入力(およびコンテキスト)を組み立てて、AI モデルに渡すプロンプトまたはメッセージのシーケンスを作成します。チャットのシナリオでは、ロールとコンテンツを持つメッセージのリストを保持します。OpenAI の Chat API では、メッセージを { role: 'user' | 'assistant' | 'system', content: string } という形式で指定します。通常、最初にシステムメッセージ(アシスタントの動作やコンテキストを設定するもの)を置き、会話が進むにつれてユーザーメッセージとアシスタントメッセージを交互に追加します。

React での状態管理: コンポーネントの状態に会話を保存できます。Vercel SDK の React フックを使うと、次のようになります。

import { useChat } from 'ai/react';

function ChatInterface() {
  const { messages, input, handleInputChange, handleSubmit } = useChat();
  // ...
}

useChat フックは多くの処理を担います。メッセージオブジェクトの配列である messages の状態と、現在のテキスト入力を保持する input の状態を管理し、handleInputChange と handleSubmit というヘルパーを提供します。デフォルトでは、useChat() は送信時に /api/chat へ POST リクエストを送ります。

手動での状態管理: useChat を使用しない場合は、useState やコンテキストで状態を管理できます。フォームの送信時に API を呼び出し、ユーザーのクエリとアシスタントの応答を追加してメッセージ配列を更新します。

システムプロンプトとコンテキスト: よく使われるパターンは、アシスタントの役割やナレッジベースを説明する初期システムメッセージを含めることです。たとえば、ドキュメントのヘルパーを構築する場合、システムメッセージの内容は「あなたはドキュメントアシスタントです。ドキュメントの例を使って回答してください。」のようになります。

単一ターンと複数ターン: 会話の記憶を必要としない単一の質問応答インターフェースであれば、代わりに Vercel SDK の useCompletion フックを使用できます。チャットボットや複数ターンの対話には、各リクエストでメッセージ履歴を保持して送信する useChat が一般的なパターンです。

AI の応答を UI にストリーミングする

最新の AI UI を特徴づけるものの一つが、ストリーミング出力です。AI がトークンを生成すると、ユーザーには返信がリアルタイムで表示されます。モデルが生成する回答は長かったり、時間がかかったりするため、UX の向上に欠かせません。何秒も無言で待つ代わりに、ストリーミングを使えば部分的な結果をすぐに表示できます。

ストリーミングの仕組み: OpenAI API で stream: true を有効にすると、応答は単一の JSON オブジェクトではなく、一連のチャンク(データイベント)として送信されます。Vercel AI SDK を使うと、これらのチャンクを簡単に処理できます。サーバーでは、応答をテキストストリーム(StreamingTextResponse)に変換しました。クライアント側では、useChat フックがこのストリームの読み取りを処理し、新しいテキストが届くたびにメッセージの状態を逐次更新します。

SDK を使わずに React でストリーミングを手動で実装する場合は、次のようになります。

const res = await fetch('/api/chat', { method: 'POST', body: JSON.stringify({ messages }) });
const reader = res.body.getReader();
const decoder = new TextDecoder();
let partial = "";
while(true) {
  const { value, done } = await reader.read();
  if (done) break;
  partial += decoder.decode(value);
  setAssistantMessage(partial);
}

自動スクロール: ストリーミング時の UX で気をつけたい点の一つは、最新のメッセージが表示されるようにすることです。useEffect でメッセージ配列の長さを監視し、更新時にメッセージコンテナーを自動スクロールする方法があります。

部分的なレンダリングと完了: ストリーミング中は、点滅するカーソルや「AI が入力中…」のメッセージなど、視覚的なインジケーターを表示します。ストリームが終了したら、メッセージの表示を確定します。

入力処理とデバウンス

チャットでのやり取りでは、通常、ユーザーがフォームを送信したときにクエリを送信します。ただし、一部の AI アプリケーションでは、入力に継続的に反応させたい場合があります。たとえば、オートコンプリートの候補やAI によるリアルタイム検証などです。このような場合は、デバウンスが重要です。

デバウンスが必要な理由: キー入力のたびに OpenAI API を呼び出すのは、非常に非効率でコストもかかります。デバウンスを使うと、ユーザーがしばらく入力を止めてから API を呼び出すようにできます。

const [draft, setDraft] = useState("");

useEffect(() => {
  if (!draft) return;
  const timeout = setTimeout(() => {
    getSuggestion(draft);
  }, 500);
  return () => clearTimeout(timeout);
}, [draft]);

明示的な「送信」操作があるシンプルなチャットボットでは、通常、デバウンスは必要ありません。ユーザーが Enter キーを押したときに送信します。ただし、AI の応答中に入力を無効にする、または複数回の送信を防ぐと便利です。

エラー処理と耐障害性

AI アプリケーションでは、堅牢なエラー処理が不可欠です。

  • API 呼び出しを try/catch で囲む: サーバー上で OpenAI の呼び出しを try/catch で囲みます。問題が発生した場合は、適切なエラーレスポンスを返します。

  • クライアント側のエラー状態: レスポンスがエラーを示している場合に対処します。

try {
  await sendMessage({ text: input });
} catch (error) {
  console.error("Failed to send message:", error);
}
  • ユーザーへのフィードバック: 問題が発生した場合は、必ずユーザーに知らせます。チャット内にエラーをインラインで表示します。たとえば、「申し訳ありません。問題が発生しました。もう一度お試しください。」という特別な「system」メッセージを表示します。

  • 再試行の仕組み: 「もう一度試す」ボタンで、ユーザーが再試行できるようにすることを検討します。

  • 検証エラー: API を呼び出す前にクライアント側で検証します。入力が空の場合は送信を無効にするか、長さの上限を超える入力を切り詰めます。

UI の構築:コンポーネントとスタイル設定のパターン

チャットメッセージコンポーネント: 単一のメッセージバブルを描画する ChatMessage コンポーネントを作成します。ロールに応じてスタイルを変えます。

function ChatMessage({ role, content }) {
  const isUser = role === 'user';
  return (
    <div className={`flex ${isUser ? 'justify-end' : 'justify-start'} mb-2`}>
      <div className={`max-w-xl px-4 py-2 rounded-lg ${
        isUser ? 'bg-blue-500 text-white' : 'bg-gray-200 text-gray-900'
      }`}>
        {content}
      </div>
    </div>
  );
}

入力コンポーネント:

function InputBox({ value, onChange, onSubmit, disabled }) {
  return (
    <form onSubmit={onSubmit} className="flex gap-2">
      <input
        type="text"
        value={value}
        onChange={onChange}
        disabled={disabled}
        className="flex-1 border rounded px-3 py-2"
        placeholder="Type your message..."
      />
      <button type="submit" disabled={disabled} className="bg-blue-500 text-white px-4 py-2 rounded">
        Send
      </button>
    </form>
  );
}

コンポーネントの組み合わせ:

function ChatInterface() {
  const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat();
  
  return (
    <div className="flex flex-col h-screen max-w-2xl mx-auto p-4">
      <div className="flex-1 overflow-y-auto">
        {messages.map((msg, i) => (
          <ChatMessage key={i} role={msg.role} content={msg.content} />
        ))}
      </div>
      <InputBox 
        value={input} 
        onChange={handleInputChange} 
        onSubmit={handleSubmit}
        disabled={isLoading}
      />
    </div>
  );
}

関心事を分離することで、UI パーツのテストや交換が簡単になります。ロジック(useChat)は表示コンポーネントから切り離されています。

Vercel AI Elements(チャット UI の事前構築済みコンポーネント)

Vercel の AI Elements ライブラリでは、AI チャットインターフェース専用に設計された、すぐに使える React コンポーネントが提供されています。

  • Conversation:自動スクロール機能を備えた、メッセージ一覧を描画するコンテナーです。
  • Prompt:チャットプロンプト向けに最適化された入力コンポーネントです。
  • TypingIndicator:AI が「考え中」である場合や、レスポンスをストリーミング中である場合に表示されます。
  • ErrorBoundary/ErrorMessage:エラーを適切に処理して表示します。
import { Conversation, Prompt, TypingIndicator } from '@vercel/ai-elements';

function ChatApp() {
  const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat();
  
  return (
    <div className="h-screen flex flex-col">
      <Conversation messages={messages} />
      {isLoading && <TypingIndicator />}
      <Prompt 
        value={input} 
        onChange={handleInputChange} 
        onSubmit={handleSubmit}
      />
    </div>
  );
}
すべてを組み合わせる
  1. バックエンド API ルート: Next.js のルートハンドラーを使う場合も、別の Express サーバーを使う場合も、メッセージを受信して AI モデルを呼び出し、レスポンスをストリーミングで返すエンドポイントを作成します。

  2. 状態管理: Vercel AI SDK の useChat フック(または独自に useState を使用した実装)で会話の状態を管理します。

  3. ストリーミング: 応答性の高い UX を実現するため、サーバーとクライアントの両方でストリーミングを有効にします。

  4. デバウンスとレート制限: オートコンプリートなどの機能では、API 呼び出しにデバウンスを適用します。チャットでは、レスポンスのストリーミング中に入力を無効にします。

  5. エラー処理: API 呼び出しを try/catch で囲み、エラー発生時にユーザーへフィードバックを提供し、再試行の仕組みを検討します。

  6. 再利用可能なコンポーネント: データ取得ロジックから切り離された、表示用コンポーネント(ChatMessage、InputBox)を構築します。本番環境向けにすぐ使えるコンポーネントとして、AI Elements の利用も検討します。

  7. スタイル設定: TailwindCSS(またはお好みのスタイル設定手段)を使い、すっきりとしたレスポンシブなチャットインターフェースを作成します。

アーキテクチャの比較:Next.js と Vite
項目Next.jsVite + Node Backend
API ルート組み込み(pages/api/ または app/api/)別途 Express/Node サーバーが必要
ストリーミングEdge Runtime でネイティブサポートres.write() による手動実装
デプロイVercel(最適化済み)またはセルフホストフロントエンド(静的)とバックエンドを別々にデプロイ
複雑さ低い(オールインワン)高い(2 つのコードベース)
柔軟性フレームワークの規約完全な制御

多くの AI チャットアプリケーションでは、統合された API ルートとストリーミングサポートにより、Next.js を使うと開発者エクスペリエンスがシンプルになります。ただし、既存の Vite/React アプリがある場合や、より多くの制御を望む場合は、ここで説明したパターンを別のバックエンドと組み合わせてうまく活用できます。

出典

参考資料

コメント