はじめに
HTMLで作成したチャット画面にGemini Notebookをつなげてチャットボット(RAG)を作りました。
次のようなイメージです。
ユーザ <-> HTML <-> GAS <-> Sheets <-> Workspace Studio <-> Gemini Notebook
- ユーザがHTMLのチャット画面に質問を書き込む
- GASが質問をスプレッドシートに転記する(転記後、回答をポーリングする)
- スプレッドシートの更新をスターターとして、Workspace Studioがスプレッドシートの質問をノートブックに渡し、回答をスプレッドシートに転記する
- ポーリングしていたGASがスプレッドシートの回答をHTMLに渡す
スプレッドシートを作成する
質問と回答をやり取りするためのスプレッドシートを作成します。
注意点:スプレッドシートはマイドライブに作成し、共有しません。(共有すると、Workspace Studioが書き込めません。)
注意点:スプレッドシートのヘッダに日本語を使用しません。(日本語を使用するとWorkspace StudioがGemini Notebookに質問を渡せなくなります。)
チャット画面を作成する(GAS、HTML)
スプレッドシートからApp Script編集画面を開き、HTMLとGASを追加します。

function doGet() {
return HtmlService.createHtmlOutputFromFile('index')
.setTitle('チャットボット');
}
<!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に関数を追加
/*
* テキストを受け取りスプレッドシートへ書き込み後、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を他のツールに置き換えることもできると思います。
活用してみてください。