こんにちは!この記事では、TypeScriptとReactを使って、OpenAI APIを活用したシンプルなAIチャットボットを実装する方法を解説します。実際のコード例と共に、ステップバイステップで進めていきます。
概要
今回作成するのは以下の機能を持つチャットボットです:
- ユーザーの入力に対してAIが応答する
- 会話履歴を保持し、文脈を理解した応答が可能
- TypeScriptの型安全性を活かした実装
- ReactとTailwind CSSによるモダンなUI
前提条件
- Node.js (v14以上)
- React (v18推奨)
- TypeScript
- OpenAI APIキー
1. プロジェクトのセットアップ
まずはプロジェクトをセットアップします。create-react-appを使用することも可能ですが、今回はViteを使用します。
npm create vite@latest my-ai-chatbot -- --template react-ts
cd my-ai-chatbot
npm install
次に必要なパッケージをインストールします:
npm install openai axios tailwindcss @tailwindcss/forms
Tailwind CSSの設定:
npx tailwindcss init -p
tailwind.config.js
を以下のように編集:
/** @type {import('tailwindcss').Config} */
export default {
content: [
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {},
},
plugins: [
require('@tailwindcss/forms'),
],
}
src/index.css
に以下を追加:
@tailwind base;
@tailwind components;
@tailwind utilities;
2. 型定義
TypeScriptの利点を活かすため、まずは型定義を行います。src/types/index.ts
を作成します:
export interface Message {
id: string;
content: string;
role: 'user' | 'assistant';
timestamp: Date;
}
export interface ChatState {
messages: Message[];
isLoading: boolean;
error: string | null;
}
3. OpenAI APIとの連携サービス
APIとの連携部分を作成します。src/services/openai.ts
を作成:
import axios from 'axios';
import { Message } from '../types';
const API_KEY = 'YOUR_OPENAI_API_KEY'; // 環境変数から取得する方が安全です
const API_URL = 'https://api.openai.com/v1/chat/completions';
export const generateResponse = async (messages: Message[]): Promise<string> => {
try {
const formattedMessages = messages.map(msg => ({
role: msg.role,
content: msg.content
}));
const response = await axios.post(
API_URL,
{
model: 'gpt-3.5-turbo',
messages: formattedMessages,
temperature: 0.7,
},
{
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${API_KEY}`
}
}
);
return response.data.choices[0].message.content;
} catch (error) {
console.error('Error generating AI response:', error);
throw new Error('AIレスポンスの生成に失敗しました');
}
};
4. チャットコンポーネントの実装
次に、チャットUIを実装します。src/components/ChatInterface.tsx
を作成:
import React, { useState, useRef, useEffect } from 'react';
import { v4 as uuidv4 } from 'uuid';
import { Message, ChatState } from '../types';
import { generateResponse } from '../services/openai';
const ChatInterface: React.FC = () => {
const [chatState, setChatState] = useState<ChatState>({
messages: [],
isLoading: false,
error: null
});
const [input, setInput] = useState<string>('');
const messagesEndRef = useRef<HTMLDivElement>(null);
// 新しいメッセージが追加されたら自動スクロール
useEffect(() => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
}, [chatState.messages]);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!input.trim()) return;
// ユーザーメッセージを追加
const userMessage: Message = {
id: uuidv4(),
content: input,
role: 'user',
timestamp: new Date()
};
setChatState(prev => ({
...prev,
messages: [...prev.messages, userMessage],
isLoading: true,
error: null
}));
setInput('');
try {
// AIの応答を取得
const aiResponse = await generateResponse([...chatState.messages, userMessage]);
// AIメッセージを追加
const assistantMessage: Message = {
id: uuidv4(),
content: aiResponse,
role: 'assistant',
timestamp: new Date()
};
setChatState(prev => ({
...prev,
messages: [...prev.messages, assistantMessage],
isLoading: false
}));
} catch (error) {
setChatState(prev => ({
...prev,
isLoading: false,
error: 'AIの応答取得に失敗しました'
}));
}
};
return (
<div className="flex flex-col h-screen max-w-2xl mx-auto p-4">
<div className="bg-white rounded-lg shadow-md overflow-hidden flex flex-col h-full">
<div className="bg-blue-600 text-white p-4">
<h1 className="text-xl font-bold">AI Chat Assistant</h1>
</div>
<div className="flex-1 overflow-y-auto p-4 space-y-4">
{chatState.messages.length === 0 && (
<div className="text-center text-gray-500 my-8">
AIアシスタントに質問してみましょう
</div>
)}
{chatState.messages.map((message) => (
<div
key={message.id}
className={`flex ${message.role === 'user' ? 'justify-end' : 'justify-start'}`}
>
<div
className={`max-w-[80%] rounded-lg p-3 ${
message.role === 'user'
? 'bg-blue-500 text-white rounded-br-none'
: 'bg-gray-200 text-gray-800 rounded-bl-none'
}`}
>
{message.content}
</div>
</div>
))}
{chatState.isLoading && (
<div className="flex justify-start">
<div className="bg-gray-200 text-gray-800 rounded-lg rounded-bl-none p-3 max-w-[80%]">
<div className="flex space-x-2">
<div className="w-2 h-2 bg-gray-500 rounded-full animate-bounce"></div>
<div className="w-2 h-2 bg-gray-500 rounded-full animate-bounce" style={{ animationDelay: '0.2s' }}></div>
<div className="w-2 h-2 bg-gray-500 rounded-full animate-bounce" style={{ animationDelay: '0.4s' }}></div>
</div>
</div>
</div>
)}
{chatState.error && (
<div className="bg-red-100 text-red-800 p-3 rounded-lg text-center">
{chatState.error}
</div>
)}
<div ref={messagesEndRef} />
</div>
<form onSubmit={handleSubmit} className="border-t p-4">
<div className="flex space-x-2">
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="メッセージを入力..."
className="flex-1 border-gray-300 rounded-full focus:ring-blue-500 focus:border-blue-500"
disabled={chatState.isLoading}
/>
<button
type="submit"
disabled={chatState.isLoading || !input.trim()}
className="bg-blue-600 text-white px-4 py-2 rounded-full font-medium hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50"
>
送信
</button>
</div>
</form>
</div>
</div>
);
};
export default ChatInterface;
5. App.tsxの編集
最後に、src/App.tsx
を編集してチャットインターフェースを表示します:
import ChatInterface from './components/ChatInterface';
function App() {
return (
<div className="min-h-screen bg-gray-100">
<ChatInterface />
</div>
);
}
export default App;
6. 実行と動作確認
以上の実装が完了したら、以下のコマンドでアプリケーションを起動します:
npm run dev
ブラウザでhttp://localhost:5173
にアクセスすると、AIチャットボットが表示されます。
7. 発展的な機能
基本的な機能の実装が完了したら、以下のような機能を追加することも検討できます:
- 会話履歴の永続化(LocalStorageやFirebase等を使用)
- ユーザー認証
- 複数の会話スレッド対応
- プロンプトのカスタマイズ
- 応答の生成途中でのストリーミング表示
まとめ
この記事では、TypeScriptとReactを使用して、OpenAI APIを活用したシンプルなAIチャットボットを実装する方法を紹介しました。TypeScriptの型安全性とReactのコンポーネント指向は、複雑なアプリケーション開発でも構造を整理しやすくする利点があります。
さらに知識を深めたい方は、OpenAIのドキュメントを参照するとともに、プロンプトエンジニアリングについても学習することをおすすめします。AIの可能性を最大限に引き出すには、適切な指示を与えることが重要です。
参考リンク
実装中に何か問題に遭遇した場合や、質問があればコメントでお知らせください!