Amazonで商品を見るセール会場へ

GAS│Google Apps Scriptで作る!ChatGPT風チャットボットの作り方入門

ChatGPTにアクセスできない環境や、アカウントを持っていない人にChatGPTを使ってもらうにはどうしたらよいでしょうか。

ChatGPTのAPIを活用し、GAS(Google Apps Script)でWebアプリケーションを構築します。

これにより、ブラウザ上で動作する簡易的なChatGPTチャットボットを作成できます。

ブラウザ上で動作するチャットボットの完成イメージ。

Googleアカウントがあれば、ChatGPTのアカウントがなくても利用可能です。

対話ログはGoogleスプレッドシートに自動で記録されます。

なお、ChatGPTのAPIは従量課金制のため、利用頻度やトークン消費量に応じた使いすぎに注意してください。

目次

GASで動くChatGPTの作り方

UIと処理ロジックはGASで構築し、ログ保存にはGoogleスプレッドシートを使用します。

また、ChatGPTを利用するためにOpenAIのAPIキーを発行します。

これらの連携により、簡易的なチャットボット環境を構築できます。

ChatGPTのAPIキーの発行

まずはOpenAIの管理画面からChatGPTのAPIキーを発行します。

管理画面で発行されたAPIキーの確認画面。

発行されたシークレットキーは再表示できない場合があるため、必ず控えておきます。

スクリプトプロパティに登録

控えたAPIのシークレットキーを、GASの「スクリプトプロパティ」に登録します。

APIキーをコード内に直接記述すると流出の危険があるため、環境変数としてスクリプトプロパティから安全に参照します。

スクリプトプロパティの編集画面を開いた様子。

今回はプロパティ名を「ACCESS_TOKEN」として設定しました。

「値」の入力欄に先ほどのシークレットキーを設定して保存します。

スクリプトプロパティにプロパティ名とシークレットキーの値を設定して保存する画面。

ログ用のスプレッドシートを作成

対話履歴を記録するためのGoogleスプレッドシートを新規作成します。

作成後、スプレッドシートのID(URL内の該当部分)とシート名を控えておきます。

※控えたIDとシート名は、後述するmain.gs内の設定箇所に記述します。

対話履歴を記録するために新規作成したスプレッドシートの画面。

Webアプリの作成

GASエディタ上に必要な各ファイルを構成し、コードを記述していきます。

エディタ上に構成したWebアプリ用ファイルの一覧。
ファイル備考
app.gsHTMLファイルを読み込み、Webアプリケーションとして表示可能な形式に変換します。
index.html画面のUI(HTML構造)を記述します。
style.html画面デザインを定義するCSSを記述します。
script.html画面の挙動や非同期通信を制御するフロントエンドJavaScriptを記述します。
main.gsChatGPT APIの呼び出しやスプレッドシート連携を行うサーバー側処理を記述します。

app.gs

Webアプリとしてアクセスされた際に、HTMLを出力するための初期化処理を記述するファイルです。

function doGet() {
  return HtmlService.createTemplateFromFile("index").evaluate();
}

index.html

チャット画面の基本構造を定義するファイルです。

画面左側に履歴などを表示するサイドバーを配置し、中央に対話メッセージ領域を設けています。

また、外部ファイル化したCSSとJavaScriptを読み込む記述を含めています。

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
    <?!= HtmlService.createHtmlOutputFromFile("style").getContent(); ?>
  </head>
  <body>
    <div class="sidebar">
      <h2 onclick="startNewConversation()">新規会話</h2>
      <div id="historyList"></div>
    </div>

    <div class="main">
      <div class="header">
        <label for="modelSelect">モデル選択:</label>
        <select id="modelSelect">
          <option value="gpt-4.1">gpt-4.1</option>
          <option value="gpt-4.1-mini" selected>gpt-4.1-mini</option>
          <option value="gpt-4.1-nano">gpt-4.1-nano</option>
          <option value="o4-mini">o4-mini</option>
          <option value="gpt-4o">gpt-4o</option>
          <option value="gpt-4o-mini">gpt-4o-mini</option>
          <option value="gpt-4o-realtime-preview">gpt-4o-realtime-preview</option>
          <option value="gpt-4o-mini-tts">gpt-4o-mini-tts</option>
        </select>
      </div>
      <div class="chat-box" id="chatLog"></div>
      <div class="input-area">
        <textarea id="inputText" rows="5" placeholder="メッセージを入力..."></textarea>
        <button onclick="sendMessage()">送信</button>
      </div>
    </div>
    <?!= HtmlService.createHtmlOutputFromFile("script").getContent(); ?>
  </body>
</html>

さらに、プルダウンから利用するChatGPTのモデルを選択できる仕様にしています。

利用可能なモデルの種類や指定名については、公式ドキュメント等から確認できます。

style.html

チャットUI全体のレイアウトや配色を調整するCSSファイルです。

<style>
  body {
    font-family: sans-serif;
    background-color: #f5f5f5;
    margin: 0;
    padding: 0;
    display: flex;
  }
  .sidebar {
    width: 250px;
    background-color: #e6f0fa;
    border-right: 1px solid #ccc;
    padding: 1em;
    height: 100vh;
    overflow-y: auto;
  }
  .sidebar h2 {
    font-size: 1em;
    margin-top: 0;
    cursor: pointer;
    color: #1a73e8;
  }
  .sidebar .history-item {
    margin: 0.5em 0;
    color: #1a73e8;
    cursor: pointer;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;    
  }
  .main {
    flex-grow: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    padding: 1em;
  }
  .header {
    font-size: 1.2em;
    font-weight: bold;
    margin-bottom: 1em;
  }
  .chat-box {
    width: 100%;
    max-width: 800px;
    background-color: #fff;
    padding: 1em;
    box-shadow: 0 2px 5px rgba(0,0,0,0.1);
    min-height: 300px;
    margin-bottom: 1em;
    overflow-y: auto;
  }
  .input-area {
    display: flex;
    width: 100%;
    max-width: 800px;
  }
  textarea#inputText {
    flex: 1;
    padding: 0.5em;
    font-size: 1em;
    resize: vertical;
  }
  button {
    padding: 0.5em 1em;
    margin-left: 0.5em;
    font-size: 1em;
  }
  .message {
    margin: 0.5em 0;
    padding: 0.5em;
  }
  .user {
    color: #1a73e8;
  }
  .bot {
    color: #0b8043;
    white-space: pre-wrap;
  }
</style>

続いて、通信処理を担うmain.gsとフロントエンド処理のscript.htmlを実装します。

すべてのコードを実装したあと、Webアプリとしてデプロイすることでブラウザからアクセス可能になります。

通信処理やフロントエンド処理の実装コードとデプロイ前の状態。

ここから先の詳細な実装コードは有料コンテンツとなります。

Googleスプレッドシートの他の自動化操作については、まとめ記事をご確認ください。

script.html

フロントエンド側のJavaScriptを記述するファイルです。

送信ボタンの押下時にGAS側を呼び出す処理や、サイドバーへの対話履歴の描画を担当します。

<script>
  let conversation = [];
  let conversationId = Date.now().toString();

  const chatLog = document.getElementById("chatLog");
  const input = document.getElementById("inputText");
  const historyList = document.getElementById("historyList");

  function startNewConversation() {
    conversation = [];
    conversationId = Date.now().toString();
    chatLog.innerHTML = "";
    input.value = "";
    loadSidebar();
  }

  function appendMessage(role, content) {
    const div = document.createElement("div");
    div.className = `message ${role}`;
    div.innerHTML = `${role === 'user' ? 'あなた' : 'AI'}: ${content}`;
    chatLog.appendChild(div);
  }

  function groupByThread(data) {
    const grouped = {};
    data.forEach(item => {
      if (!grouped[item.thread]) grouped[item.thread] = [];
      grouped[item.thread].push(item);
    });
    return grouped;
  }

  function loadSidebar() {
    google.script.run
      .withSuccessHandler(function(data) {
        historyList.innerHTML = "";
        if (!Array.isArray(data) || data.length === 0) {
          historyList.textContent = "履歴がありません";
          return;
        }

        const grouped = groupByThread(data);
        Object.entries(grouped).reverse().forEach(([threadId, messages]) => {
          const div = document.createElement("div");
          div.className = "history-item";
          div.textContent = messages[0].user;
          div.onclick = () => loadHistoryFromThread(messages);
          historyList.appendChild(div);
        });
      })
      .withFailureHandler(err => {
        console.error("履歴取得に失敗:", err);
        alert("履歴の読み込みに失敗しました。");
      })
      .getHistoryFromSheetWithThreads();
  }

  function loadHistoryFromThread(messages) {
    conversation = [];
    conversationId = messages[0].thread;
    chatLog.innerHTML = "";

    messages.forEach(msg => {
      conversation.push({ role: 'user', content: msg.user });
      appendMessage('user', msg.user);
      conversation.push({ role: 'assistant', content: msg.bot });
      appendMessage('bot', msg.bot);
    });
  }

  function sendMessage() {
    const userMsg = input.value.trim();
    if (!userMsg) return;

    appendMessage('user', userMsg);
    input.value = "";

    conversation.push({ role: 'user', content: userMsg });

    const modelSelect = document.getElementById("modelSelect");
    const selectedModel = modelSelect.value;

    google.script.run
      .withSuccessHandler(function(response) {
        appendMessage('bot', response);
        conversation.push({ role: 'assistant', content: response });
        loadSidebar();
      })
      .askChatGPTWithModel(userMsg, conversationId, selectedModel);
  }

  window.onload = loadSidebar;
</script>

main.gs

ChatGPTのAPIへのリクエスト送信やスプレッドシートへの記録処理を記述するファイルです。

冒頭の設定変数に、事前準備で控えたスプレッドシートのIDとシート名を記述します。

ChatGPTのAPIを呼び出すための処理を記述します。

const SHEET_ID = "xxxxxxxxxxxxxxxxxxxxxxxxxxx";
const SHEET_NAME = "シート1";

function askChatGPTWithModel(userInput, threadId, modelName) {
  const apiKey = PropertiesService.getScriptProperties().getProperty("ACCESS_TOKEN");

  const payload = {
    model: modelName || "gpt-4o",
    messages: [
      { role: "system", content: "あなたは有能なメールアシスタントです。" },
      { role: "user", content: userInput }
    ]
  };

  const options = {
    method: "post",
    contentType: "application/json",
    headers: {
      Authorization: "Bearer " + apiKey
    },
    payload: JSON.stringify(payload),
    muteHttpExceptions: true
  };

  try {
    const response = UrlFetchApp.fetch("https://api.openai.com/v1/chat/completions", options);
    const result = JSON.parse(response.getContentText());

    if (result.error) {
      return "エラー: " + result.error.message;
    }

    const reply = result.choices[0].message.content.trim();
    saveToSpreadsheet(threadId, userInput, reply);
    return reply;
  } catch (e) {
    return "通信エラー: " + e.message;
  }
}

function saveToSpreadsheet(threadId, userInput, botResponse) {
  const sheet = SpreadsheetApp.openById(SHEET_ID).getSheetByName(SHEET_NAME);
  sheet.appendRow([new Date(), threadId, userInput, botResponse]);
}

function getHistoryFromSheetWithThreads() {
  const sheet   = SpreadsheetApp.openById(SHEET_ID).getSheetByName(SHEET_NAME);
  const lastRow = sheet.getLastRow();
  if (lastRow < 2) return [];

  // 2行目(= データの先頭)から最終行まで一気に取得
  const data = sheet.getRange(2, 1, lastRow - 1, 4).getValues();
  const tz   = Session.getScriptTimeZone();

  return data.map(r => ({
    timestamp: Utilities.formatDate(r[0], tz, "yyyy/MM/dd HH:mm:ss"),
    thread   : r[1],
    user     : r[2],
    bot      : r[3],
  }));
}

デプロイして表示

すべてのコードの記述が完了したら、GASエディタ右上の「デプロイ」から「新しいデプロイ」を実行してWebアプリを公開します。

エディタ右上のデプロイメニューから新しいデプロイを実行する画面。

デプロイ完了後に発行されるウェブアプリのURLをクリックして開きます。

デプロイ完了後に発行されたウェブアプリのURL画面。

入力欄にメッセージを送信し、正常に応答が返ってくるかテストしてみましょう。

完成したチャットボットの画面でメッセージを送信し、応答をテストしている様子。

以上で、GASを使ったChatGPT風チャットボットの構築手順は完了です。

この記事で紹介しているコードは、GitHubのMOTOKI-LLC/cg-method-codeにもまとめています。

外部のサービスと連携する記事は、ほかにもあります。

次に学ぶ・作業環境を選ぶ

学習を続けたい方や、作業環境を整えたい方は、目的に合うガイドをご覧ください。

スプレッドシート・GASのおすすめ書籍

作業環境の作り方

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次