公開範囲を社内に限定したGoogleサイトに、AIチャットを1時間で組み込む：Groq × Google Apps Script で実現する簡単実装

### はじめに：AIチャットの仕組みと全体像
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/181708/e1b69242-b4dd-4e28-9b2a-848b1a48ad4b.png)

今回実装するチャットは、主に以下の3つの要素で構成され、連携して動作します。
```mermaid
sequenceDiagram
    participant U as ユーザー
    participant GS as Googleサイト (埋め込みチャット)
    participant GAS as Google Apps Script
    participant GA as Groq API

    U->>GS: 1. メッセージ入力 (例: "XXXについて教えて")
    GS->>GAS: 2. メッセージ送信 (google.script.run)
    activate GAS
    GAS->>GA: 3. APIリクエスト (POST /chat/completions)
    activate GA
    GA-->>GAS: 4. AIからの回答 (Markdown形式)
    deactivate GA
    GAS-->>GS: 5. 回答表示 (withSuccessHandler)
    deactivate GAS
    GS->>U: 6. チャット画面に表示 (Markdownレンダリング)
```

1.  **Groq API**: AIの頭脳部分です。ユーザーからの質問を受け取り、適切な回答を生成します。
2.  **Google Apps Script (GAS)**: AIとウェブサイトを連携させる役割を担います。チャットのインターフェース（HTML）を表示し、ユーザーの質問をGroqに送信したり、Groqからの回答をウェブサイトに表示したりする司令塔としての機能を有します。
3.  **Googleサイト**: お客様のウェブサイトです。GASで作成したチャット画面を埋め込むプラットフォームとなります。

大まかな流れとしては、Googleサイトに埋め込まれたチャット画面から質問が送信されると、GASがそれを受け取りGroqに転送し、Groqからの回答をGASが受信し、再びチャット画面に表示するという仕組みで動作します。

-----

### ステップ1: GroqでAPIキーを取得する

まず、GroqというAIサービスを利用するためのAPIキー（アクセス権限の鍵）を取得します。このAPIキーは、お客様のプログラムがGroqサービスに対して「このサービスを利用しても良いですか？」と認証するための重要な情報ですので、**第三者に公開しないよう厳重に管理してください。**

1.  **Groq Cloudへのアクセス**:
    Groqの公式ウェブサイトにアクセスし、アカウントの作成またはログインをお願いします。Googleアカウントでの登録も可能です。
    [https://console.groq.com/](https://console.groq.com/)

2.  **APIキーの場所を確認**:
    ログイン後、ダッシュボードの左側メニューにある「**API Keys**」の項目をクリックしてください。

3.  **新しいAPIキーの生成**:
    「**Create API Key**」ボタンをクリックすると、英数字が混在した長い文字列が表示されます。これがお客様のAPIキーです。**このAPIキーをコピーし、後で使用するため一時的にメモ帳などに保存しておくことをお勧めします。**

-----

### ステップ2: Google Apps Script (GAS) でチャットアプリケーションを構築する

次に、Google Apps Script（GAS）環境でチャットのプログラム本体を構築します。ここでは、AIとの対話を行うコードと、チャット画面のインターフェースを生成するコードを記述します。

1.  **GASのウェブサイトにアクセス**:
    新しいタブで Google Apps Script にアクセスしてください。
    [https://script.google.com/](https://script.google.com/)

2.  **新しいプロジェクトの作成**:
    左上にある「**新しいプロジェクト**」をクリックしてください。コード記述画面が表示されます。

3.  **APIキーの安全な設定（重要）**:
    APIキーをプログラムコード内に直接記述することは、セキュリティ上のリスクを伴います。GASには「スクリプトプロパティ」という、安全に情報を保存できる機能があるので、ここにAPIキーを格納します。

      * GASの編集画面で、左メニューの「**プロジェクトの設定**」（歯車アイコン⚙️）をクリックしてください。
      * 画面を下へスクロールし、「**スクリプトのプロパティ**」セクションにある「**行を追加**」をクリックしてください。
      * 「プロパティ」の欄に `GROQ_API_KEY` と入力してください。
      * 「値」の欄に、**ステップ1でコピーしたGroq APIキー**を貼り付けてください。
      ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/181708/87aa36a2-70ce-46b3-843c-70a8405daa58.png)

      * 最後に「**スクリプトのプロパティを保存**」（フロッピーディスクのアイコン💾）をクリックし、設定を保存してください。

4.  **チャットのバックエンドプログラムの記述（`Code.gs`）**:
    GASの編集画面に戻り、`Code.gs` というファイルが既に開いているはずです。既存のコードを**すべて削除し**、以下のコードをコピーして貼り付けてください。

    ```javascript
    // Code.gs

    // ★Groq APIキーを安全に読み込む設定
    // 「プロジェクトの設定」（歯車アイコン）->「スクリプトのプロパティ」でGROQ_API_KEYが設定されていることをご確認ください。
    const GROQ_API_KEY = PropertiesService.getScriptProperties().getProperty('GROQ_API_KEY'); 
    const GROQ_MODEL = "llama3-8b-8192"; // 使用するAIモデルの指定。速度と性能のバランスが良好な「llama3-8b-8192」を推奨します。

    // Googleサイトからアクセスがあった際に実行される関数
    function doGet() {
      // 「Index.html」というチャット画面のファイルを表示します
      return HtmlService.createTemplateFromFile('Index').evaluate()
          .setTitle('AIチャット（Groq）') // チャットのタイトルを設定
          .setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL); // Googleサイトへの埋め込みを許可する設定
    }

    // ユーザーメッセージをGroq AIに送信し、応答を取得する関数
    function sendMessageToGroq(message) {
      // Groq APIのエンドポイント（アクセス先アドレス）
      const url = "https://api.groq.com/openai/v1/chat/completions";

      // Groqに送信するデータの内容（ユーザーの質問など）
      const payload = {
        model: GROQ_MODEL, // 使用するAIモデル
        messages: [
          { role: "user", content: message } // ユーザーからの質問メッセージ
          // 会話履歴をAIに保持させる場合は、ここに過去のメッセージも追加します（今回は省略）
        ],
        temperature: 0.7, // AIの応答の「自由度」（0.0は厳密、1.0以上は創造的）
        max_tokens: 1024, // AIが生成する応答の最大トークン数（おおよそ1024文字程度）
      };

      // Groqへのデータ送信に関する設定
      const options = {
        method: "post", // データ送信方式
        contentType: "application/json", // 送信するデータの形式
        headers: {
          "Authorization": `Bearer ${GROQ_API_KEY}` // APIキーをHTTPヘッダーに設定
        },
        payload: JSON.stringify(payload), // 送信するデータをJSON形式に変換
        muteHttpExceptions: true // HTTPエラーが発生しても例外をスローせず、応答を返せるようにする設定
      };

      try {
        // 実際にGroqにデータを送信し、応答を取得
        const response = UrlFetchApp.fetch(url, options);
        const responseCode = response.getResponseCode(); // HTTPステータスコード（200は成功）
        const responseText = response.getContentText(); // サーバーからの応答内容（テキスト）

        if (responseCode === 200) { // リクエストが成功した場合
          const jsonResponse = JSON.parse(responseText); // 応答内容をJSON形式で解析

          // AIからのメッセージを抽出
          if (jsonResponse.choices && jsonResponse.choices[0] && jsonResponse.choices[0].message && jsonResponse.choices[0].message.content) {
            return jsonResponse.choices[0].message.content; // AIの応答をそのまま返します（Markdown形式のテキスト）
          } else {
            // AIからの応答が予期せぬ形式だった場合、コンソールにエラーを出力
            console.error("Groqからの応答が予期せぬ形式です:", jsonResponse);
            return "恐れ入ります、Groqからの応答が取得できませんでした。";
          }
        } else { // HTTPエラーが発生した場合
          console.error(`API呼び出しエラー: ステータスコード ${responseCode}, 応答: ${responseText}`);
          return `エラーが発生しました（コード: ${responseCode}）：${responseText}`;
        }
      } catch (e) { // その他のエラー（ネットワーク接続不良など）
        console.error("ネットワークまたは解析エラー:", e);
        return "チャットサービスで予期せぬエラーが発生しました: " + e.toString();
      }
    }
    ```

5.  **チャット画面のインターフェースの記述（`Index.html`）**:
    次に、チャットのユーザーインターフェースをHTMLで作成します。GASの左側メニューにある「**ファイル**」アイコン（ファイル追加）をクリックし、「**HTML**」を選択後、「`Index`」という名前で新しいファイルを作成してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/181708/652bea44-82bf-4fde-91f6-35002578a0ad.png)

    新しく作成された `Index.html` ファイルに、以下のコードを**すべてコピーして貼り付けてください。**
    このコードには、Markdown形式で表示するためのJavaScriptライブラリ「`marked.js`」も組み込まれています。

    ```html
    <!DOCTYPE html>
    <html>
    <head>
      <base target="_top">
      <style>
        /* チャット画面のスタイルを定義するCSSです */
        body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; margin: 20px; background-color: #f4f7f6; color: #333; }
        h1 { color: #2c3e50; text-align: center; margin-bottom: 25px; }
        #chat-container {
          border: 1px solid #dfe6e9;
          padding: 15px;
          height: 400px; /* チャット表示エリアの高さ */
          overflow-y: scroll; /* メッセージ増加時にスクロールを有効にする */
          margin-bottom: 15px;
          background-color: #ffffff;
          border-radius: 10px;
          box-shadow: 0 4px 8px rgba(0,0,0,0.1); /* 軽微な影を設定 */
        }
        .message {
          margin-bottom: 15px;
          padding: 10px 15px;
          border-radius: 8px;
          max-width: 80%; /* メッセージの最大幅 */
          word-wrap: break-word; /* 長い単語の自動改行 */
        }
        .user-message {
          text-align: right;
          background-color: #007bff; /* ユーザーメッセージの背景色 */
          color: white;
          margin-left: auto; /* 右寄せ */
          border-bottom-right-radius: 0; /* 吹き出し形状の調整 */
        }
        .groq-message {
          text-align: left;
          background-color: #e2e6ea; /* AIメッセージの背景色 */
          color: #333;
          margin-right: auto; /* 左寄せ */
          border-bottom-left-radius: 0; /* 吹き出し形状の調整 */
        }
        .system-message {
          text-align: center;
          color: #e74c3c; /* エラーメッセージの色 */
          font-weight: bold;
          font-size: 0.9em;
        }
        input[type="text"] {
          width: calc(100% - 110px); /* ボタンの幅を考慮 */
          padding: 12px;
          border: 1px solid #bdc3c7;
          border-radius: 5px;
          font-size: 16px;
          box-sizing: border-box; /* paddingとborderを幅に含める */
        }
        button {
          padding: 12px 20px;
          background-color: #28a745; /* ボタンの背景色 */
          color: white;
          border: none;
          border-radius: 5px;
          cursor: pointer; /* マウスカーソルをポインターにする */
          margin-left: 10px;
          font-size: 16px;
          transition: background-color 0.3s ease; /* ホバー時のトランジション効果 */
        }
        button:hover {
          background-color: #218838; /* ホバー時の背景色 */
        }

        /* Markdown表示用のスタイル（コードブロックなど） */
        /* コードブロックのスタイル */
        .message pre {
            background-color: #2d2d2d; /* 暗めの背景 */
            color: #f8f8f2; /* 明るめの文字色 */
            padding: 10px;
            border-radius: 5px;
            overflow-x: auto; /* コードが長い場合に横スクロールを有効にする */
            font-family: 'Fira Code', 'Courier New', Courier, monospace; /* プログラミングに適したフォント */
            font-size: 0.9em;
            line-height: 1.4;
        }
        /* インラインコードのスタイル */
        .message code {
            background-color: #e0e0e0;
            padding: 2px 4px;
            border-radius: 3px;
            font-family: 'Fira Code', 'Courier New', Courier, monospace;
            font-size: 0.9em;
            color: #c0392b; /* 赤系の文字色 */
        }
        .groq-message code { /* AIメッセージ内のインラインコードの色 */
            background-color: #c9d0d6;
            color: #2c3e50;
        }
        /* Markdownのリスト（箇条書き）スタイル */
        .message ul, .message ol {
            margin-left: 20px;
            padding-left: 0;
            list-style-position: inside; /* リスト記号を内側に配置 */
        }
        /* 見出しスタイル */
        .message h1, .message h2, .message h3, .message h4 {
            margin-top: 15px;
            margin-bottom: 10px;
            border-bottom: 1px solid #eee; /* 下線 */
            padding-bottom: 5px;
            color: #2c3e50;
        }
        /* 段落スタイル */
        .message p {
            margin-top: 0;
            margin-bottom: 10px;
            line-height: 1.6;
        }
      </style>
      <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
    </head>
    <body>
      <h1>AIチャットをお試しください</h1>
      <div id="chat-container"></div>
      <input type="text" id="user-input" placeholder="こちらにメッセージを入力し、送信ボタンを押してください">
      <button onclick="sendMessage()">送信</button>

      <script>
        // marked.js の設定（Markdownの変換ルールを詳細に指定できます）
        marked.setOptions({
          gfm: true, // GitHub Flavored Markdown を有効にする（テーブルやチェックリストに対応）
          breaks: true, // 改行を <br> タグに変換する
          // highlight: function(code, lang) {
          //   // 構文ハイライト機能を追加する場合はここにライブラリを組み込みます（今回は省略）
          //   return code;
          // }
        });

        // メッセージを送信する関数
        function sendMessage() {
          const userInput = document.getElementById('user-input'); // 入力欄の情報を取得
          const message = userInput.value.trim(); // 入力されたメッセージ（前後の空白を削除）
          if (message === '') return; // メッセージが空の場合は何もしない

          appendMessage('user', message); // ユーザーメッセージを画面に追加（Markdown変換は行いません）
          userInput.value = ''; // 入力欄をクリア

          // Google Apps Scriptのサーバーサイド関数（sendMessageToGroq）を呼び出す
          google.script.run
              .withSuccessHandler(function(response) {
                // AIからの応答が成功した場合、Marked.jsでMarkdownをHTMLに変換して表示します
                appendMessage('groq', marked.parse(response)); 
              })
              .withFailureHandler(function(error) {
                // エラーが発生した場合、画面にエラーメッセージを表示
                appendMessage('system', 'エラーが発生いたしました: ' + error);
              })
              .sendMessageToGroq(message); // AIにメッセージを送信
        }

        // 画面にメッセージを追加する関数
        function appendMessage(sender, content) { // contentはテキストまたはHTMLコンテンツとなります
          const chatContainer = document.getElementById('chat-container'); // チャット表示エリア
          const messageDiv = document.createElement('div'); // 新しいメッセージ表示用のdiv要素を作成
          messageDiv.classList.add('message'); // 共通のCSSスタイルを適用
          messageDiv.classList.add(sender + '-message'); // 送信者に応じたCSSスタイルを適用（user-message, groq-messageなど）

          // ユーザーおよびシステムメッセージはテキストとして、AIメッセージはHTMLとして挿入します
          if (sender === 'user' || sender === 'system') {
            messageDiv.textContent = content; // テキストコンテンツとして安全に挿入
          } else {
            messageDiv.innerHTML = content; // AIからのメッセージはHTMLコンテンツとして挿入（Markdown変換済み）
          }

          chatContainer.appendChild(messageDiv); // チャット表示エリアにメッセージを追加
          chatContainer.scrollTop = chatContainer.scrollHeight; // スクロール位置を一番下へ（最新メッセージが見えるように）
        }
      </script>
    </body>
    </html>
    ```

6.  **プログラムの公開（デプロイ）**:
    コードの修正が完了しましたら、GASのプログラムを「ウェブアプリ」として公開します。これにより、Googleサイトからアクセス可能となります。

      * GASの編集画面の右上にある「**デプロイ**」ボタンをクリックしてください。
      * 表示されるメニューから「**新しいデプロイ**」を選択してください。
      * 「**種類の選択**」（歯車アイコン⚙️）をクリックし、「**ウェブアプリ**」を選択してください。
      * 「**実行者**」は「**自分**」のままで結構です。
      * 「**アクセスできるユーザー**」は「**全員**」を選択してください（この設定がないと、Googleサイトに埋め込んでも正しく動作いたしません）。
      * 「**デプロイ**」をクリックしてください。
      * 初回デプロイ時には、「**アクセスを承認**」のダイアログが表示されますので、ご自身のGoogleアカウントを選択し、「許可」をしてください。
      * デプロイが成功すると、「**ウェブアプリのURL**」が表示されますので、**これをコピーしてください。** このURLをGoogleサイトに埋め込みます。

-----

### ステップ3: Googleサイトへの埋め込み

いよいよ最後のステップです。作成したAIチャットをGoogleサイトに組み込みます。

1.  **Googleサイトへのアクセス**:
    ご自身のGoogleサイトを開き、チャットを埋め込みたいページに移動してください。
    [https://sites.google.com/](https://sites.google.com/)

2.  **「埋め込む」機能の利用**:
    Googleサイトの編集画面で、右側のメニューにある「**挿入**」タブをクリックし、「**埋め込む**」ボタンを探してクリックしてください。

3.  **URLの貼り付け**:
    「URLで埋め込む」タブを選択し、**ステップ2の最後にコピーしたウェブアプリのURL**を貼り付けてください。
    「**挿入**」をクリックすると、お客様のGoogleサイトにチャットが表示されるかとおもいます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/181708/c8640484-c131-49c0-896b-113e25dc04b1.png)

4.  **サイズの調整**:
    埋め込まれたチャットは、クリックすると周囲に枠が表示されます。その枠をドラッグすることで、サイズや位置を自由に調整することが可能です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/181708/ab3d7e2a-4866-4751-b580-cac2635be455.png)


５．サイトの公開

公開のボタンで公開をしてください。

６．確認

「公開したサイトを表示」で確認してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/181708/d6669cb5-2226-4477-9570-301f79a3d46f.png)

以上で、お客様のGoogleサイトにGroqを活用したAIチャットが実装されました。実際にサイトを公開し、AIとの会話をお試しください。Markdownで記述された箇条書きやコードブロックなどが正しく表示されるか、ぜひご確認ください。


-----

### 注意事項とヒント

  * **APIキーの厳重な管理**: GroqのAPIキーは、お客様のサービス利用権限を示すものです。**絶対に第三者に開示しないでください。** もし漏洩した場合、不正利用によりGroqの利用料が発生する可能性があります。
  * **AIモデルの選択**: `Code.gs` 内の `const GROQ_MODEL = "llama3-8b-8192";` の部分を、Groqで提供されている別のモデル（例：より大規模な `llama3-70b-8192` など）に変更することも可能です。性能は向上する可能性がございますが、応答速度が変化する場合もあるので、いくつかのモデルを試してみるのも良いでしょう。
  * **会話履歴の保持**: 現在のチャットは、AIが以前の会話内容を記憶しておりません（単発のやり取りとなります）。継続的な対話を実現するためには、会話履歴を保持する機能を追加するためのプログラムの改造が必要となります。今回のチャットが動作することをご確認いただいた後、次のステップとして挑戦されることをお勧めします。
  * **GASの実行制限**: Google Apps Scriptには、1日の実行時間や呼び出し回数に制限がございます。個人での利用においては通常問題ございませんが、チャットが非常に活発に利用される場合、制限に達する可能性もあります。