1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Gemini Notebookでチャットボットを作る

1
Posted at

はじめに

HTMLで作成したチャット画面にGemini Notebookをつなげてチャットボット(RAG)を作りました。

次のようなイメージです。

ユーザ <-> HTML <-> GAS <-> Sheets <-> Workspace Studio <-> Gemini Notebook

  1. ユーザがHTMLのチャット画面に質問を書き込む
  2. GASが質問をスプレッドシートに転記する(転記後、回答をポーリングする)
  3. スプレッドシートの更新をスターターとして、Workspace Studioがスプレッドシートの質問をノートブックに渡し、回答をスプレッドシートに転記する
  4. ポーリングしていたGASがスプレッドシートの回答をHTMLに渡す

スプレッドシートを作成する

質問と回答をやり取りするためのスプレッドシートを作成します。
注意点:スプレッドシートはマイドライブに作成し、共有しません。(共有すると、Workspace Studioが書き込めません。)
注意点:スプレッドシートのヘッダに日本語を使用しません。(日本語を使用するとWorkspace StudioがGemini Notebookに質問を渡せなくなります。)

チャット画面を作成する(GAS、HTML)

スプレッドシートからApp Script編集画面を開き、HTMLとGASを追加します。
image.png

コード.gs
function doGet() {
  return HtmlService.createHtmlOutputFromFile('index')
    .setTitle('チャットボット');
}
index.html
<!DOCTYPE html>
<html lang="ja">
	<head>
	    <meta charset="UTF-8">
	    <meta name="viewport" content="width=device-width, initial-scale=1.0">
	    <title>シンプルチャット</title>
	    <style>
	        body {
	            font-family: sans-serif;
	            background-color: #f4f4f9;
	            margin: 0;
	            display: flex;
	            justify-content: center;
	            align-items: center;
	            height: 100vh;
	        }
	        .chat-container {
	            width: 100%;
	            max-width: 400px;
	            height: 600px;
	            background: #fff;
	            border-radius: 8px;
	            box-shadow: 0 4px 10px rgba(0,0,0,0.1);
	            display: flex;
	            flex-direction: column;
	            overflow: hidden;
	        }
	        .chat-header {
	            background: #007bff;
	            color: #fff;
	            padding: 15px;
	            text-align: center;
	            font-weight: bold;
	        }
	        .chat-messages {
	            flex: 1;
	            padding: 15px;
	            overflow-y: auto;
	            background: #f9f9f9;
	            display: flex;
	            flex-direction: column;
	            gap: 10px;
	        }
	        .message {
	            max-width: 75%;
	            padding: 10px 14px;
	            border-radius: 16px;
	            font-size: 14px;
	            line-height: 1.4;
	            word-break: break-all;
	        }
	        .message.user {
	            background: #007bff;
	            color: #fff;
	            align-self: flex-end;
	            border-bottom-right-radius: 2px;
	        }
	        .message.bot {
	            background: #e2e8f0;
	            color: #333;
	            align-self: flex-start;
	            border-bottom-left-radius: 2px;
	        }
	        .chat-input-area {
	            display: flex;
	            padding: 10px;
	            background: #fff;
	            border-top: 1px solid #ddd;
	        }
	        .chat-input-area input {
	            flex: 1;
	            padding: 10px;
	            border: 1px solid #ddd;
	            border-radius: 4px;
	            outline: none;
	        }
	        .chat-input-area button {
	            background: #007bff;
	            color: #fff;
	            border: none;
	            padding: 0 16px;
	            margin-left: 8px;
	            border-radius: 4px;
	            cursor: pointer;
	        }
	        .chat-input-area button:hover {
	            background: #0056b3;
	        }
	    </style>
	</head>
	<body>

		<div class="chat-container">
		    <div class="chat-header">チャットサポート</div>
		    <div class="chat-messages" id="chat-messages">
		        <div class="message bot">こんにちは!何かお手伝いできますか?</div>
		    </div>
		    <div class="chat-input-area">
		        <input type="text" id="user-input" placeholder="メッセージを入力...">
		        <button id="send-btn">送信</button>
		    </div>
		</div>

		<script>
		    const messagesContainer = document.getElementById('chat-messages');
		    const userInput = document.getElementById('user-input');
		    const sendBtn = document.getElementById('send-btn');

		    function addMessage(text, sender) {
		        const messageDiv = document.createElement('div');
		        messageDiv.classList.add('message', sender);
		        messageDiv.textContent = text;
		        messagesContainer.appendChild(messageDiv);
		        messagesContainer.scrollTop = messagesContainer.scrollHeight;
		    }

		    function handleSend() {
		        const text = userInput.value.trim();
		        if (!text) return;

		        addMessage(text, 'user');
		        userInput.value = '';

		        setTimeout(() => {
		            addMessage('「' + text + '」ですね。承知いたしました!', 'bot');
		        }, 1000);
		    }

		    sendBtn.addEventListener('click', handleSend);
		    userInput.addEventListener('keypress', (e) => {
		        if (e.key === 'Enter') handleSend();
		    });
		</script>

	</body>
</html>

Webアプリとしてデプロイします。
入力した内容を「受け付けました」と返してくれればOKです。

チャット画面とスプレッドシートをつなぐ(GAS、HTML)

GASとHTMLに手を加えて、チャット画面に入力された質問をスプレッドシートに書き込み、スプレッドシートに書き込まれた回答をチャット画面に返す機能を追加します。

index.htmlの変更
-        setTimeout(() => {
-            addMessage('「' + text + '」ですね。承知いたしました!', 'bot');
-        }, 1000);
+        // GAS側の関数を呼び出す
+        google.script.run
+          .withSuccessHandler(function(response) {
+            addMessage(response, 'bot');
+          })
+          .withFailureHandler(function(error) {
+            addMessage('エラーが発生しました。', 'bot');
+          })
+          .getResponse(text);
コード.gs
//コード.gsに関数を追加
/*
 * テキストを受け取りスプレッドシートへ書き込み後、C列の書き込みを監視して応答を返す関数
 * @param {string} text - 書き込むテキスト
 * @return {string} C列に書き込まれたテキスト、または「タイムアウト」
 */
function getResponse(text) {
  // アクティブなシートを取得(シート名を指定する場合は sheet.getSheetByName('シート名') を使用)
  var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
  
  // 追記先の行番号と No.(行数 - 1)の計算
  var targetRow = sheet.getLastRow() + 1;
  var no = targetRow - 1;
  
  // A列にNo.、B列にテキストを書き込み
  sheet.getRange(targetRow, 1).setValue(no);
  sheet.getRange(targetRow, 2).setValue(text);
  
  // 変更を即時反映
  SpreadsheetApp.flush();
  
  // 書き込み完了後、10秒待機
  Utilities.sleep(10000);
  
  // C列の書き込み確認(1秒置きに最大35回)
  var maxAttempts = 35;
  for (var i = 0; i < maxAttempts; i++) {
    // 外部からの更新を確実に反映・取得するため最新化
    SpreadsheetApp.flush();
    
    var responseValue = sheet.getRange(targetRow, 3).getValue();
    
    // C列に値が入っているか判定(空文字・nullでない場合)
    if (responseValue !== "" && responseValue !== null && responseValue !== undefined) {
      return responseValue;
    }
    
    // 1秒待機して再確認
    Utilities.sleep(1000);
  }
  
  return "タイムアウト";
}

動作確認します。
チャット画面に入力した質問がスプレッドシートに書き込まれることを確認します。

スプレッドシートに質問が書き込まれたら、タイムアウトする前に手入力で回答を書き込み、チャット画面に表示されることを確認します。

ノートブックを作成する

Gemini Notebookでノートブックを作成します。
注意点:ノートブックのソースとしてファイルをアップロードします。(ソースとしてGoogle Drive上のファイルを指定すると、動作しなくなります。(時間経過で、ノートブックとDriveの接続が切れるようです。))

Workspace Studioのフローを作成する

Workspace Studioのフローで、スプレッドシートとノートブックを連携させます。

ステップ1(開始条件)シートの変更時
項目 内容
スプレッドシート 作成したスプレッドシートを指定
シート 作成したスプレッドシートのシートを指定
変更を監視する行を検索 列:No. 値:すべて
監視する新しい値を選択 列:Question 値:すべて
ステップ2 Gemini Notebookに質問
項目 内容
ノートブックを選択 作成したノートブックを指定
プロンプトを入力 ステップ 1: 詳細リスト - 最新の値 "Question"
ステップ3 行を更新
項目 内容
スプレッドシート 作成したスプレッドシートを指定
シート 作成したスプレッドシートのシートを指定
更新する行を検索 列:No. 値:ステップ 1: 詳細リスト - 最新の値 "No."
列ごとに新しい値を設定 列:Answer 値:ステップ 2: Gemini Notebook で作成されたコンテンツ

まとめ

Gemini Notebookを使ったチャットボットを動かすことができました。
この記事には記載していませんが、Gemini Notebookの回答をテキストで返すため、回答に含まれるソースの参照リンクを削除したり、外部リンクの表記を修正したりといった後処理を加えています。

うまく動かない場合のデバッグが難しいですが、Workspace Studioのテスト実行やアクティビティ表示を使って原因究明します。

HTMLを他のツールに置き換えることもできると思います。
活用してみてください。

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?