雑食エンジニアの気まぐれレシピ

日ごろ身に着けた技術や見知った知識などの備忘録的なまとめ.主にRaspberry Piやマイコンを使った電子工作について綴っていく予定.機械学習についても書けるといいな.

ただひたすらChatGPTと英会話できるWebアプリを作ってみた

最近仕事で本格的に英語会議が増えてきたので、英会話の機会を増やしたい、、、 ということで、勢いでそんなアプリを作ってみました。 shikky-lab.github.io

使い方は簡単。OpenAIのAPIトークンを入力して、会話を開始するだけです。
話し終わったら録音を終了ボタンを押せば、文字起こし→GPTへの問い合わせ→帰ってきた文字を読み上げ、といったフローになります。

APIトークンはクライアント側-OpenAIだけでやり取りするため、内容がどこか他に送られることはありません。

今回は、この裏側について書きたいと思います。

はじめに

といっても、特に書くことはなかったりします(笑)

実は今回のコレ、寝付けない夜にノリで「こういうの欲しいな」と思い立ち、それをChatGPTにいったら大体のひな形を出してくれたというものになります。

この際にWeb Speech APIというものを知りました。

Web Speech API

JavaScriptベースで音声認識(文字起こし)および、文字読み上げを行ってくれるAPI群、とのことです。
developer.mozilla.org

主要なブラウザ(除FireFox)で対応しているようです。
・・・・・・・。
(私の愛用ブラウザは火狐なので、テスト用にChromeをインストールしました。)

さておき、このChatGPTはこのAPIを使うサンプルコードを出してきて、大体それが動きました。
その時のログ。

Q.

 次の仕様のWebアプリケーションを作りたいです。言語はtypescriptでお願いします。基本的にサーバは初回アクセス時にこのスクリプトをクライアントに返すだけで、以降の処理はこのクライアントのスクリプト内で完結する想定です。
- WebSpeechAPIを使い、ユーザの発言を文字起こしして、画面上に「あなた:(WebSpeechAPIで読み込んだ内容を表示)」と表示する
- 入力された内容を外部のAPIに渡し、応答を得る(この部分は私が書くので、ダミーでよいです)
- 画面上に出ている先ほどの文字の下に、「AI:(上記で取得した応答)」を表示する
- (上記で取得した応答)の部分を、WebSpeechAPIにより読み上げる
- 読み上げが終わったら、次の発言を待機する。
- 以下、この動作を繰り返す。

A.

以下は、指定された仕様に基づいて作成された簡単なWebアプリケーションのコードです。このコードはHTMLとTypeScriptを一緒に記述しています。

html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Speech To Text and Text To Speech App</title>
</head>
<body>

<div>
    <button id="startButton">発言を開始</button>
    <div id="chatLog"></div>
</div>

<script>
    window.onload = function() {
        const startButton = document.getElementById("startButton");
        const chatLog = document.getElementById("chatLog");

        const recognition = new (window.SpeechRecognition || window.webkitSpeechRecognition || window.mozSpeechRecognition || window.msSpeechRecognition)();
        recognition.lang = 'ja-JP';
        recognition.interimResults = false;
        recognition.maxAlternatives = 1;

        const speechSynthesis = window.speechSynthesis;

        recognition.onresult = function(event) {
            const last = event.results.length - 1;
            const text = event.results[last][0].transcript;

            chatLog.innerHTML += `<div>あなた:${text}</div>`;

            // ここで外部APIにリクエストする代わりにダミー応答を返します。
            const dummyResponse = "こんにちは、どのようにお手伝いできますか?";
            chatLog.innerHTML += `<div>AI:${dummyResponse}</div>`;

            const utterance = new SpeechSynthesisUtterance(dummyResponse);
            speechSynthesis.speak(utterance);
        };

        recognition.onend = function() {
            // 読み上げが終わった後、再び音声認識を開始します。
            recognition.start();
        };

        startButton.addEventListener('click', function() {
            recognition.start();
        });
    };
</script>
</body>
</html>

    このコードには、ユーザが話すときの開始ボタンと、ユーザとAIの会話を表示するためのdivが含まれています。
    WebSpeechAPIを使用して音声認識と音声合成を行います。
    recognition.onresultで、ユーザの音声をテキストとして取得し、それを表示します。
    その後、ダミーのAI応答を表示して、それを読み上げます。
    読み上げが終わったら、recognition.onendが呼び出され、再び音声認識を開始します。

このサンプルは、外部APIとの統合部分をダミーで代替していますので、必要に応じて適切なAPI呼び出しを追加してください。

・・・大体これで動きました。(驚愕

基本はこれをベースにしつつ、機能を詰め込んでいます。
ちなみにUIについても、

Q.

transcriptsのdivには、チャットのようにユーザの発言と、それに対するAIからの応答が表示されていきます。
You : XXX
AI : YYY
といった形ですね。これを見栄え良く表示したいと考えています。あなたならどのような表示を考えますか?

A.

チャットの表示に関しては、多くの方法やスタイルが考えられますが、以下は一般的な方法をBootstrapを使用してスタイルを適用する例です。

    左右にテキストを寄せる: ユーザの発言は左側、AIの応答は右側に表示することで、二者間の会話としての視覚的な区別を強化します。

    バックグラウンドカラーの違い: ユーザとAIのバルーンの背景色を変えることで、さらに区別しやすくします。

    角丸: 通常、チャットのバルーンは角丸でデザインされることが多いです。

以下はBootstrapを用いて上記のスタイルを実装した例です:

html

<div id="transcripts">
    <div class="chat-message user-message">
        <p><strong>You:</strong> XXX</p>
    </div>
    <div class="chat-message ai-message">
        <p><strong>AI:</strong> YYY</p>
    </div>
</div>

そして、対応するCSSは以下のようになります:

css

.chat-message {
    border-radius: 10px;
    padding: 10px;
    margin-bottom: 10px;
    width: 60%;
}

.user-message {
    background-color: #e6e6e6; /* ユーザのメッセージの背景色 */
    margin-left: 5%;
}

.ai-message {
    background-color: #007bff; /* Bootstrapの主要な色(primary color) */
    color: white; /* テキストの色を白に */
    margin-right: 5%;
    margin-left: 35%; /* 右側に寄せる */
}

この例では、ユーザのメッセージは灰色のバックグラウンドを持ち、左側に配置され、AIのメッセージはBootstrapの主要な色(青色)のバックグラウンドを持ち、右側に配置されます。

さらに、特定の効果やアニメーション、ホバーエフェクトなどを追加することで、さらにユーザーエクスペリエンスを向上させることができます。

こんな感じで回答してもらったものを(大体そのまま)使っています。

Whisper API

音声認識の部分は最終的にWeb Speech APIからOpenAIのWhisperに切り替えました。

Web Speech APIも認識率は悪くないのですが、言い直しなどがそのまま反映されるのと、一定時間以上の間があると会話が終了してしまう(詰まったり、二文以上の会話ができない)という問題がありました。

というわけで、録音データを送り付けたら文字起こしして返してくれるという、OpenAIのWhisper APIを採用しました。

それに伴い、録音終了をボタン操作にしました。これにより若干インタラクティブ性は低下したのですが、ちょっと詰まったり言い直しても正しく認識されるので、トータルの満足度は高くなったかなと思います。

(なおこれによりWebSpeechAPIのディクテーション部分を使わなくなったので、FireFoxでも無事動くようになりました(笑))

GitHub Pages

Webページ自体は作れたとして、デプロイはどうしようかと悩んでいたところ、GitHub Pagesという存在を知りました。

これはGitHubにPushしたソースコードからそのままデプロイできるという神機能で、ほとんど操作不要でそのままデプロイ出来て、大変UXが高かったです。

静的なデプロイを行う場合には、非常に有用なサービスだと思います。これからも使っていきたいと思います。

(ちなみにTypeScriptから直接アップするには一工夫必要なので、今回はローカルでJSにトランスパイルしたものを上げてます。)

終わりに

というわけで、勢いから始まったプロジェクトでしたが、まぁまぁな形に着地できたと思います。
(もともと3日くらいの予定だったのに、結果一週間くらいかけたあたり、なんだかなぁですが。)

ChatGPT様々ですね。

ちなみに、今回ついでにGitHub Copilotの1か月トライアルも始めました。これも非常に便利でしたね。

AIのおかげで、こういう雑アプリを作るのは本当に簡単になったと思います。

これからもどんどん「思い付きを即、形にする」というのを続けていきたいですね。

それでは。