このテンプレートは、Next.jsで構築したフロントエンドから、OpenAIのRealtime API(WebRTC)を使って音声チャットを実現する最小構成のプロジェクトです。
npx create-next-app --example with-docker {your project name}cd {your project name}
git remote add origin https://github.com/{your git account id}/{your git repository url}
git branch -M main
git push -u origin main- Tailwind CSSをインストールする 以下コマンドを実行します。
npm install tailwindcss @tailwindcss/postcss postcss- PostCSS設定にTailwindを追加する Projectのrootディレクトリに、postcss.config.mjsを作成し、以下設定値を記述します。
export default {
plugins: {
"@tailwindcss/postcss": {},
}
}- Tailwind CSSをインポートする globals.cssに、以下設定を行います。
@import "tailwindcss";
OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxOpenAIにエフェメラルキーをリクエストするAPI。
import type { NextApiRequest, NextApiResponse } from 'next';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method !== 'GET') {
return res.status(405).json({ error: 'Method Not Allowed' });
}
const apiKey = process.env.OPENAI_API_KEY;
if (!apiKey) {
return res.status(500).json({ error: 'API Key not found' });
}
const response = await fetch("https://api.openai.com/v1/realtime/sessions", {
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "gpt-4o-realtime-preview-2024-12-17",
voice: "verse",
}),
});
if (!response.ok) {
const errorText = await response.text();
console.error('Failed to create session:', errorText);
return res.status(response.status).json({ error: 'Failed to create session', details: errorText });
}
const data = await response.json();
res.status(200).json({ ephemeralKey: data.client_secret.value });
}音声チャットのUIとWebRTC接続処理。
import { useState, useRef } from 'react';
export default function Chat() {
const [isChatting, setIsChatting] = useState(false);
const peerConnection = useRef<RTCPeerConnection | null>(null);
const dataChannelRef = useRef<RTCDataChannel | null>(null);
const startChat = async () => {
const res = await fetch('/api/openai-realtime/init');
const { ephemeralKey } = await res.json();
const pc = new RTCPeerConnection();
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
stream.getTracks().forEach(track => pc.addTrack(track, stream));
const audioEl = document.createElement('audio');
audioEl.autoplay = true;
pc.ontrack = (e) => audioEl.srcObject = e.streams[0];
const dc = pc.createDataChannel('oai-events');
dataChannelRef.current = dc;
dc.onmessage = () => {};
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
const sdpResponse = await fetch(`https://api.openai.com/v1/realtime?model=gpt-4o-realtime-preview-2024-12-17`, {
method: 'POST',
headers: {
Authorization: `Bearer ${ephemeralKey}`,
'Content-Type': 'application/sdp',
},
body: offer.sdp,
});
const sdpText = await sdpResponse.text();
const answer: RTCSessionDescriptionInit = {
type: 'answer',
sdp: sdpText,
};
await pc.setRemoteDescription(answer);
peerConnection.current = pc;
setIsChatting(true);
};
const stopChat = () => {
peerConnection.current?.close();
dataChannelRef.current?.close();
setIsChatting(false);
};
return (
<div className="p-4">
<h1 className="text-xl font-bold">リアルタイム音声チャット</h1>
<div className="mt-4">
{isChatting ? (
<button onClick={stopChat} className="px-4 py-2 bg-red-500 text-white rounded">
⏹チャット終了
</button>
) : (
<button onClick={startChat} className="px-4 py-2 bg-blue-500 text-white rounded">
チャット開始
</button>
)}
</div>
</div>
);
}npm run devhttp://localhost:3000/openai-realtime