はじめに
前回、ChatGPTをSpringアプリケーションに組み込みました。しかし、ChatGPTのレスポンスを待機する間、ユーザーは待機中であることを認識しづらいという問題がありました。
そこで、JavaScriptを使って待機メッセージを表示/非表示させてみました。
やりたいこと
主な処理の流れは、次の通りです。
- ボタンをクリックすると、待機メッセージ「考え中…」が表示される(画像①)
※このとき、過去のレスポンスは消える。 - レスポンスを受信すると、待機メッセージは消え、レスポンスが表示される(画像②)
実装方針
- HTML上に、待機メッセージ要素とレスポンス要素を用意します。
- 要素が特定のクラスを持つ場合、非表示にします。非表示の処理はCSSで実装します。
- 要素へのクラスの追加・削除は、イベント発生時に行います。JSで実装します。
これらをまとめると、処理フローは次のようになります。
ボタンクリック | → | レスポンス受信 | → | |
---|---|---|---|---|
待機メッセージ要素 | 表示 | 非表示 | ||
レスポンス要素 | 非表示 | 表示 |
上の表に、適切なイベントとクラス名・ID名を指定します。
clickイベント | loadイベント | |||
---|---|---|---|---|
待機メッセージ要素(id="wait”, class="message”) | loadedクラス削除 | 表示 | loadedクラス追加 | #wait.loadedは非表示 |
レスポンス要素(id=”resp”, class="message”) | loadedクラス削除 | #resp:not(.loaded)は非表示 | loadedクラス追加 | 表示 |
-
:not(クラス名)
を使用することで、特定のクラス名を含まない場合を指定しています。詳細はこちらをご確認ください。
実装する
実装方針に従い、実装したコードを以下に示します。
1.HTML
2つの要素を記述します。(サーバからレスポンスを受信するためにThymeleafを使用していますが、本記事とは直接的な関係が無い為、説明は省略します。)
<!-- 待機メッセージ要素 -->
<p class="message" id="wait">考え中…</p>
<!-- レスポンス -->
<p class="message" id="resp" th:if="${apiMessage != null}" nl2br:text="${apiMessage}"></p>
2.JavaScript
2つのイベントに対する処理を記述します。2要素に対するクラスの追加・削除操作は、forEach文で行います。
// ボタンクリックイベント
var button = document.getElementById("button");
button.addEventListener('click', () => {
// 2要素を取得する
var message = document.querySelectorAll(".message");
message.forEach(element => {
// それぞれの要素に対してloadedクラスを削除
element.classList.remove('loaded');
});
});
// ロードイベント
window.addEventListener('load', () => {
// 2要素を取得する
var message = document.querySelectorAll(".message");
message.forEach(element => {
// それぞれの要素に対してloadedクラスを追加
element.classList.add('loaded');
});
});
3.CSS
要素を非表示にする処理を記述します。
上述の表をもとに、非表示の場合のクラス名・ID名をセレクタに指定します。
#wait.loaded,
#resp:not(.loaded) {
display: none; /* 非表示 */
}
以上が、JavaScriptを使った待機メッセージ表示処理のコードです。
コード全体
前回紹介したWebアプリケーションに、本記事の内容を追加したコードを示します。
form.html
<!DOCTYPE html>
<html lang="ja" xmlns:th="http://www.thymeleaf.org" xmlns:nl2br="https://github.com/bufferings/thymeleaf-extras-nl2br">
<head><!-- 省略 --></head>
<body>
<div class="container">
<h1 class="m-5">ChatGPT API Demo</h1>
<form th:action="@{/chat}" method="post" class="form-floating m-5">
<textarea class="form-control" id="userMessage" name="userMessage" style="height: 100px"></textarea>
<label for="userMessage">Enter your message</label>
<button type="submit" class="btn btn-outline-primary mt-3" id="button">Generate Response</button>
</form>
<div class="m-5 p-3 bg-light rounded">
<h2 class="m-3">ChatGPT Response</h2>
<!-- 待機中 -->
<p class="m-3 message" id="wait">考え中…</p>
<!-- レスポンス -->
<p class="m-3 message" id="resp" th:if="${apiMessage != null}" nl2br:text="${apiMessage}"></p>
</div>
</div>
<style>
#wait.loaded,
#resp:not(.loaded) {
display: none; /* 非表示 */
}
</style>
<script>
var button = document.getElementById("button");
button.addEventListener('click', () => {
var message = document.querySelectorAll(".message");
message.forEach(element => {
element.classList.remove('loaded');
});
});
window.addEventListener('load', () => {
var message = document.querySelectorAll(".message");
message.forEach(element => {
element.classList.add('loaded');
});
});
</script>
</body>
</html>
【追記】戻るボタンを押すとどうなる?
※コメントを頂戴しましたので、追記させていだたきます!
レスポンス後(画像③)に戻るボタンを押した場合、「Generate Response」クリック前の画面に戻ります。(画像④)
待機メッセージは非表示、レスポンスは(前回の内容を)表示となっています。
※ただし、前回のレスポンスが存在しない場合は、表示されません。
戻るボタンを押すと、loadイベントが発生する為、各要素の表示状態はレスポンス後と同じになるようです。
今回の目的である「JSを使ってレスポンス待機メッセージを表示/非表示する」という点において、戻るボタン押下後は待機中では無い為、待機メッセージは非表示であるべきです。
よって、上記の画面表示に問題はないと考えています。
(とはいえ、戻るボタンについてはすっかり失念しておりましたので、今後は注意を払いたいです。)
おわりに
JavaScriptを使って待機メッセージの表示・非表示を管理することができました。
これにより、「ユーザーは待機中であることを認識しづらい」という問題を解決することができたかと思います。
お読みいただきありがとうございました。この記事が誰かのお役に立てることを祈っております。
今後、Ajaxを使った実装も試してみる予定です。(2024/01/11追記)