はじめに
GPT-4oのリリースで大きく世界が変わった。時代についていくにはAIを使いこなす必要がある
GPT-4oの登場は過去のGPTの中でも特に印象に残っています。とにかく返答が早く、スマホアプリでの音声チャットはほぼ人と話しているのと感覚が変わりませんでした。
今後AI技術を活かしながら新しいサービスを作ることはどんどん広がり、それがビジネスにつながっていくとはずです。AIを使ってどのような価値が作れるのかという視点をもてることが重要です
AIを使って何ができるのかを思いつくには、AIを試してみて何を実現することができるのかを理解していないといけません。
今回はChatGPTの音声入力がとても印象的だったので、それを真似てキャラクターとお話ができるリアルタイム音声チャットアプリをReactを用いてハンズオンで作成していきます。
動画での解説
細かいところなどは動画でより詳しく解説していますので、合わせてご利用ください!
1.開発環境の用意
まずはReactとTailwindCSSの環境を用意します。
ここではNode環境があることを前提にして行います。
$ npm create vite@latest
✔ Project name: … voice-chat-qiita
✔ Select a framework: › React
✔ Select a variant: › TypeScript
$ cd voice-chat-qiita
$ npm i
$ npm run dev
localhost:5173を開きます
Reactの環境を作成することができました。続いてTailwindCSSを導入します。
$ npm install -D tailwindcss postcss autoprefixer
$ npx tailwindcss init -p
VSCodeで作成したプロジェクトを開きます。
tailwind.config.js
を以下にします
/** @type {import('tailwindcss').Config} */
export default {
content: ["./src/**/*.{js,jsx,ts,tsx}"],
theme: {
extend: {},
},
plugins: [],
};
src/index.css
を以下にします
@tailwind base;
@tailwind components;
@tailwind utilities;
tailwindが正しく反映されているかを確認します。
src/app.tsx
を以下に変更します
import "./App.css";
function App() {
return (
<>
<h1 className="text-3xl font-bold underline">Hello world!</h1>
</>
);
}
export default App;
サーバーを再起動して確認するとスタイルがあたっているので導入がうまくいきました
2. 音声入力の実装
音声入力をして入力したテキストを表示する実装をします
今回はreact-speech-recognition
というライブラリを使います。
$ npm i react-speech-recognition
$ npm i --save-dev @types/react-speech-recognition
$ npm i regenerator-runtime
App.tsxを以下に変更します。
import "regenerator-runtime";
import SpeechRecognition, {
useSpeechRecognition,
} from "react-speech-recognition";
import { useState } from "react";
function App() {
const {
listening,
transcript,
resetTranscript,
browserSupportsSpeechRecognition,
} = useSpeechRecognition();
const [message, setMessage] = useState("");
const handleClickStart = () => {
SpeechRecognition.startListening();
};
const handleClickStop = () => {
SpeechRecognition.stopListening;
setMessage(transcript);
resetTranscript();
};
if (!browserSupportsSpeechRecognition) {
return <span>Browser doesn't support speech recognition.</span>;
}
return (
<>
<p>{message}</p>
<button onClick={handleClickStart}>Start</button>
<button onClick={handleClickStop}>Stop</button>
</>
);
}
export default App;
ここまで実装するとstart
を押して、こんにちはと言い、stop
を押すとこんにちは
と表示されます
ここですこし音声入力の説明をしていきます。
const {
listening,
transcript,
resetTranscript,
browserSupportsSpeechRecognition,
} = useSpeechRecognition();
ここでhookを使っています
- litening : 音声入力中かを表すフラグ (trueなら入力中)
- transcript : 音声入力が終わったら、入力した文章が入る
- resetTranscript : transcriptを初期化する
- browserSupportsSpeechRecognition : ブラウザで音声入力が使えるかを表すフラグ(falseなら利用できない)
const handleClickStart = () => {
SpeechRecognition.startListening();
};
const handleClickStop = () => {
SpeechRecognition.stopListening;
setMessage(transcript);
resetTranscript();
};
if (!browserSupportsSpeechRecognition) {
return <span>Browser doesn't support speech recognition.</span>;
}
Startを押したら、SpeechRecognitionのスタートを実行して音声を入力するようにして、stopを押したら入力を停止してから入力した文章をMessageにセットして初期化を行います
3. キャラクターが話せるようにする
今回は「ずんだもん」を利用して入力した音声を読んでもらうように実装していきます。
ずんだもんはAPIを利用することで簡単に使うことができます
まずはAPIキーを生成します
やり方に沿ってキーを取得してください
.env
を作成してキーの情報を環境変数として読み込めるようにします
$ touch .env
VITE_VOICEVOX_API_KEY=あなたのAPIキー
GithPushしないように.gitignore
に.envを追加しておきます
.env //最後の行に追加
VOICEVOXを使うにはGETリクエストをして、音声ファイルを再生することで簡単に利用が可能です。
const handleClickStop = () => {
SpeechRecognition.stopListening;
setMessage(transcript);
resetTranscript();
playVoice(transcript);
};
const playVoice = async (message: string) => {
const response = await fetch(
`https://deprecatedapis.tts.quest/v2/voicevox/audio/?key=${
import.meta.env.VITE_VOICEVOX_API_KEY
}&speaker=0&pitch=0&intonationScale=1&speed=1&text=${message}`
);
const blob = await response.blob();
const audio = new Audio(URL.createObjectURL(blob));
audio.play();
};
stop
を押したら音声入力の内容をplayVoice
に渡しています。
palyVoiceではAPIを叩いて、音声ファイルを再生します
ここまで実装した上でアプリに音声入力をすると入力した内容をずんだもんが復唱してくれるアプリが完成します
4. ChatGPTの実装
ここからはChatGPTを活用して会話を行えるようにし、返答をすべてずんだもんが読み上げるように実装をします。
まずは、ChatGPTのAPIキーを取得します
左メニューのAPI Keys
をクリック
create new Secret Key
を押してキーを取得します
.env
に環境変数として設定します
VITE_VOICEVOX_API_KEY=あなたのキー
VITE_OPENAI_API_KEY=あなたのキー
ChatGPTの実装をしていきます
import "regenerator-runtime";
import SpeechRecognition, {
useSpeechRecognition,
} from "react-speech-recognition";
import { useState } from "react";
type Message = {
role: "user" | "assistant";
content: string;
};
function App() {
const {
listening,
transcript,
resetTranscript,
browserSupportsSpeechRecognition,
} = useSpeechRecognition();
const [message, setMessage] = useState("");
const [history, setHistory] = useState<Message[]>([]);
const handleClickStart = () => {
SpeechRecognition.startListening();
};
const handleClickStop = () => {
SpeechRecognition.stopListening;
setMessage(transcript);
resetTranscript();
sendGPT(transcript); // ChatGPTに送る
};
const sendGPT = async (message: string) => {
const body = JSON.stringify({
messages: [...history, { role: "user", content: message }],
model: "gpt-4o", // GPT-4oを使用
});
const response = await fetch(`https://api.openai.com/v1/chat/completions`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${import.meta.env.VITE_OPENAI_API_KEY}`,
},
body,
});
const data = await response.json();
const choice = data.choices[0].message.content;
setHistory([...history, { role: "assistant", content: choice }]);
playVoice(choice);
};
const playVoice = async (message: string) => {
const response = await fetch(
`https://deprecatedapis.tts.quest/v2/voicevox/audio/?key=${
import.meta.env.VITE_VOICEVOX_API_KEY
}&speaker=0&pitch=0&intonationScale=1&speed=1&text=${message}`
);
const blob = await response.blob();
const audio = new Audio(URL.createObjectURL(blob));
audio.play();
};
if (!browserSupportsSpeechRecognition) {
return <span>Browser doesn't support speech recognition.</span>;
}
return (
<>
<p>{message}</p>
<button onClick={handleClickStart}>Start</button>
<button onClick={handleClickStop}>Stop</button>
</>
);
}
export default App;
ポイントを解説していきます
const sendGPT = async (message: string) => {
const body = JSON.stringify({
messages: [...history, { role: "user", content: message }],
model: "gpt-4o", // GPT-4oを使用
});
const response = await fetch(`https://api.openai.com/v1/chat/completions`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${import.meta.env.VITE_OPENAI_API_KEY}`,
},
body,
});
const data = await response.json();
const choice = data.choices[0].message.content;
setHistory([...history, { role: "assistant", content: choice }]);
playVoice(choice);
};
ここではChatGPTに入力した内容を投げています。
ポイントはbody
のmessages
です。
messagesは過去のメッセージと今回送りたいメッセージを配列にして送ることで、過去の知識をもったまま今回の質問に答えてくれるようになります。
1つの質問はMessage型
で送る必要があります
type Message = {
role: "user" | "assistant";
content: string;
};
ここでroleは誰がその発言をしたのかを表します。userは私たちで、assistantはChatGPTの発言ということを表します。
過去の質問は保存しておく必要があるので、historyを利用しています
setHistory([...history, { role: "assistant", content: choice }]);
ここまで行うと、質問に対して返答を音声で返してくれるようになりました。
5. スタイルを整える
最後にスマートフォンで利用することを前提にスタイルを整えていきます。
ここは本質ではないので以下のスタイルをコピーして利用してみてください。
まずはアイコンを利用するためにreact-icons
をインストールします
$ npm i react-icons
App.tsxを以下にします
import "regenerator-runtime";
import SpeechRecognition, {
useSpeechRecognition,
} from "react-speech-recognition";
import { useState } from "react";
import { FaSquare } from "react-icons/fa";
import { AiOutlineAudio } from "react-icons/ai";
type Message = {
role: "user" | "assistant";
content: string;
};
function App() {
const {
listening,
transcript,
resetTranscript,
browserSupportsSpeechRecognition,
} = useSpeechRecognition();
const [history, setHistory] = useState<Message[]>([]);
const handleClickStart = () => {
SpeechRecognition.startListening();
};
const handleClickStop = () => {
SpeechRecognition.stopListening;
resetTranscript();
sendGPT(transcript); // ChatGPTに送る
};
const sendGPT = async (message: string) => {
const body = JSON.stringify({
messages: [...history, { role: "user", content: message }],
model: "gpt-4o", // GPT-4oを使用
});
const response = await fetch(`https://api.openai.com/v1/chat/completions`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${import.meta.env.VITE_OPENAI_API_KEY}`,
},
body,
});
const playVoice = async (message: string) => {
const response = await fetch(
`https://deprecatedapis.tts.quest/v2/voicevox/audio/?key=${
import.meta.env.VITE_VOICEVOX_API_KEY
}&speaker=0&pitch=0&intonationScale=1&speed=1&text=${message}`
);
const blob = await response.blob();
const audio = new Audio(URL.createObjectURL(blob));
audio.play();
};
if (!browserSupportsSpeechRecognition) {
return <span>Browser doesn't support speech recognition.</span>;
}
const data = await response.json();
const choice = data.choices[0].message.content;
setHistory([...history, { role: "assistant", content: choice }]);
playVoice(choice);
};
const buttonClasses = `mt-4 w-[60px] h-[60px] flex items-center justify-center text-2xl ${
listening ? "bg-red-500" : "bg-blue-500"
} rounded-full text-white`;
const iconClass = listening ? <FaSquare /> : <AiOutlineAudio />;
return (
<>
<div className="flex flex-col items-center w-full min-h-screen">
<img
src="https://ucarecdn.com/b6b3f827-c521-4835-a7d4-5db0c87c9818/-/format/auto/"
alt="キャラクターの画像"
className="w-full h-auto sm:w-full object-cover"
/>
<div className="w-full max-w-[400px] sm:max-w-full sm:px-4 mt-4">
<FramedImage
characterName="ずんだもん"
dialogueText={
history[history.length - 1]
? history[history.length - 1].content
: ""
}
/>
</div>
{listening ? (
<button onClick={handleClickStop} className={buttonClasses}>
<i>{iconClass}</i>
</button>
) : (
<button onClick={handleClickStart} className={buttonClasses}>
<i>{iconClass}</i>
</button>
)}
</div>
</>
);
}
export default App;
返答の文章を表示するためのフレームをコンポーネントで用意します
$ touch src/FrameImage.tsx
import React from "react";
interface FramedImageProps {
characterName: string;
dialogueText: string;
}
const FramedImage: React.FC<FramedImageProps> = ({
characterName,
dialogueText,
}) => {
return (
<div className="relative w-full max-w-[360px] h-[190px] border-4 border-pink-500 mx-2 sm:px-2">
<div className="absolute top-[-12px] left-[-4px] py-1 px-2 bg-pink-700 text-white text-sm font-bold font-roboto">
{characterName}
</div>
<div className="absolute top-6 left-2 right-2 bottom-2 bg-white bg-opacity-80 text-black text-lg px-2 font-roboto overflow-y-auto">
{dialogueText}
</div>
</div>
);
};
export default FramedImage;
コンポーネントを作成したらApp.tsxでインポートしておきましょう
import { AiOutlineAudio } from "react-icons/ai";
ここまでできたらデベロッパーツールをひらいてiphone SE
のレイアウトにします
音声入力するとずんだもんが答えてくれます!
ChatGPTが素早いので会話も比較的スムーズです!
おわりに
今回はChatGPTを利用してお手軽な音声チャットを実装しました
キャラクター変更などの機能を加えたり、ChatGPTに性格情報などを与えることでよりリアルな会話を楽しむことも可能です
ここまで読んでいただけた方はいいねとストックよろしくお願いします。
@Sicut_study をフォローいただけるととてもうれしく思います。
また明日の記事でお会いしましょう!
JISOUのメンバー募集中!
プログラミングコーチングJISOUでは、新たなメンバーを募集しています。
実践的なカリキュラムで、あなたのエンジニアとしてのキャリアを最短で飛躍させましょう!
実践重視:即戦力を育てるアウトプット中心のプログラム。
モダンなスキル : Reactを中心としたモダンな技術を学べる。
キャリアアップ:スキルアップだけでなく、キャリアパスのサポートも充実。
興味のある方は、ぜひホームページからお気軽にカウンセリングをお申し込みください!
▼▼▼