チャットbotをchromeの拡張機能として作成してみた


## はじめに
#### この記事の目的や背景
今回自社botをLangchainを使用して作成している際にインターフェースをコミュニケーションツールに置くのか、webアプリを作成するのか使い勝手を検証するためのChromeの拡張機能を使用してチャット画面を常に表示させるとどうなるのかなと思い試してみました。
#### 対象読者
・チャットアプリを作成してみてたい方
・Chromeの拡張機能の作り方を知りたい方
## 目次
1. Chromeの拡張機能とは
2. Chromeの拡張機能でできること
3. Javascriptコードの実装
4. Manifest.jsonの作成
5. ChromeのデベロッパーモードでChromeにアップロード
6. 実際に触ってみる


## 1. Chromeの拡張機能とは
見出しの通りになりますがChromeの機能を拡張することができます。
こちらのwebストアからhttps://chromewebstore.google.com/?utm_source=ext_sidebar&hl=ja色々な拡張機能をインストールすることができます。
また自分で作成した拡張機能をGoogle ウェブストアにリリースすることも可能です。
https://chrome.google.com/webstore/devconsole/register
今回はチャットアプリを検証してみたかっただけなので自身のChromeに中だけ動くものを作成していきます。

##  2. Chromeの拡張機能でできること
基本的にJavascriptでできることはできます。
その他API連携のインターフェースとして扱うことでchatGPTとの連携も行うことができます。
この記事ではあくまでもインターフェース側の内容のみ対象で裏側のチャット処理については割愛させていただきます。
##  3. Javascriptコードの実装
チャット画面の構築
```
function initChatApp() {
   const app = document.createElement('div');
   document.body.appendChild(app);


   app.id = 'chat-app';
   app.style.position = 'fixed';
   app.style.bottom = '20px';
   app.style.right = '20px';
   app.style.width = '300px';
   app.style.height = '400px';
   app.style.backgroundColor = '#fff';
   app.style.boxShadow = '0 2px 10px rgba(0,0,0,0.3)';
   app.style.borderRadius = '10px';
   app.style.overflow = 'hidden';
   app.style.display = 'flex';
   app.style.flexDirection = 'column';


   // チャットウィンドウヘッダーの追加
   const header = document.createElement('div');
   header.style.backgroundColor = '#B2EBF2';
   header.style.color = '#0277BD';
   header.style.padding = '10px';
   header.style.display = 'flex';
   header.style.alignItems = 'center';


   // ロゴ（またはアイコン）
   const logo = document.createElement('img');
   logo.src = 'ロゴ画像パス'
   logo.style.width = '30px';
   logo.style.height = '30px';
   logo.style.borderRadius = '50%';
   logo.style.marginRight = '10px';
   header.appendChild(logo);


   // ボットの名前
   const botName = document.createElement('div');
   botName.textContent = 'Smart Support AI';
   header.appendChild(botName);


   app.appendChild(header);


   // 閉じるボタンの追加
   const closeButton = document.createElement('button');
   closeButton.textContent = '✖'; // 閉じるアイコン
   closeButton.style.marginLeft = 'auto';
   closeButton.style.border = 'none';
   closeButton.style.background = 'none';
   closeButton.style.color = '#0277BD';
   closeButton.style.cursor = 'pointer';
   closeButton.style.fontSize = '16px';
   header.appendChild(closeButton);




   // チャットエリアのスタイル
   const chat = document.createElement('div');
   chat.id = 'chat';
   chat.style.flex = '1';
   chat.style.overflowY = 'scroll';
   chat.style.padding = '10px';
   chat.style.backgroundColor = '#f0f0f0';
   app.appendChild(chat);


   // メッセージ入力エリアのスタイル
   const inputArea = document.createElement('div');
   inputArea.style.display = 'flex';
   inputArea.style.padding = '10px';
   inputArea.style.backgroundColor = '#E0F7FA';
   app.appendChild(inputArea);


   // メッセージ入力フィールドのスタイル
   const messageInput = document.createElement('textarea');
   messageInput.id = 'message';
   messageInput.placeholder = 'メッセージを入力';
   messageInput.style.flex = '1';
   messageInput.style.height = '40px';
   messageInput.style.marginRight = '10px';
   messageInput.style.padding = '10px';
   messageInput.style.border =  '1px solid #B2EBF2';
   messageInput.style.borderRadius = '20px';
   messageInput.style.resize = 'none'; // リサイズ不可に設定
   inputArea.appendChild(messageInput);




   // 送信ボタンのスタイル
   const sendButton = document.createElement('button');
   sendButton.id = 'send';
   sendButton.textContent = '送信';
   sendButton.style.padding = '10px 15px';
   sendButton.style.border = 'none';
   sendButton.style.borderRadius = '20px';
   sendButton.style.backgroundColor = '#4DD0E1';
   sendButton.style.color =  '#0277BD';
   sendButton.style.cursor = 'pointer';
   inputArea.appendChild(sendButton);


   // 送信ボタンのクリックイベント
   sendButton.addEventListener('click', function() {
       const message = messageInput.value;
       displayMessage(message, 'human');
       sendMessageToBot(message); // チャットボットにメッセージを送信
       messageInput.value = '';
   });
   // フローティングアクションボタンの生成
   const fab = document.createElement('button');
   fab.textContent = '💬';
   fab.style.position = 'fixed';
   fab.style.bottom = '80px'; // チャットウィンドウの下に配置
   fab.style.right = '20px';
   fab.style.width = '60px';
   fab.style.height = '60px';
   fab.style.borderRadius = '50%';
   fab.style.backgroundColor = '#4DD0E1';
   fab.style.color = 'white';
   fab.style.border = 'none';
   fab.style.fontSize = '24px';
   fab.style.cursor = 'pointer';
   fab.style.boxShadow = '0 2px 10px rgba(0,0,0,0.3)';
   fab.style.display = 'flex';
   fab.style.alignItems = 'center';
   fab.style.justifyContent = 'center';
   document.body.appendChild(fab);


   // チャットウィンドウの表示・非表示を切り替える
   fab.addEventListener('click', function() {
       if (app.style.display === 'none' || app.style.display === '') {
           app.style.display = 'flex';
           fab.style.display = 'none'; // チャット表示時にFABを非表示にする
       } else {
           app.style.display = 'none';
           fab.style.display = 'flex'; // チャット非表示時にFABを表示する
       }
   });


   // 閉じるボタンのクリックイベント
   closeButton.addEventListener('click', function() {
       app.style.display = 'none';
       // FABを再表示する
       fab.style.display = 'flex'; // 初期状態ではFABを表示
   });


   // チャットウィンドウを非表示に初期化
   app.style.display = 'none';
   fab.style.display = 'flex'; // 初期状態ではFABを表示


   document.body.appendChild(fab);
}
```

メッセージ送信の処理
```
// チャットボットにメッセージを送信する関数
function sendMessageToBot(message) {
    // 点の表示を開始
    let dotCount = 0;
    const dotInterval = setInterval(() => {
        displayMessage('.'.repeat(dotCount % 3 + 1), 'dot');
        dotCount++;
    }, 500); // 500ミリ秒ごとに更新
   // APIエンドポイントのURL
   const apiUrl = 'Apiのulr';


   // POSTリクエストのオプション
   const requestOptions = {
       method: 'POST',
       headers: { 'Content-Type': 'application/json' },
       body: JSON.stringify({ message: message })
   };


   // APIへのリクエストを送信
   fetch(apiUrl, requestOptions)
       .then(response => response.json())
       .then(data => {
           clearInterval(dotInterval); // 点の表示を停止
           clearDots(); // 既存の点をクリア
           // レスポンスメッセージを表示
           displayMessage(data['message'], 'bot');


       })
       .catch(error => {
           clearInterval(dotInterval); // 点の表示を停止
           clearDots(); // 既存の点をクリア
           console.error('チャットボットとの通信に失敗しました:', error);
       });
}
// 点をクリアする関数
function clearDots() {
   const chat = document.getElementById('chat');
   const dots = chat.querySelectorAll('.dot-message');
   dots.forEach(dot => chat.removeChild(dot));
}




// メッセージをチャットエリアに表示する関数
function displayMessage(message, sender) {
   const chat = document.getElementById('chat');
   const msg = document.createElement('div');
   msg.textContent = message;
   msg.style.padding = '10px 15px';
   msg.style.margin = '10px 0';
   msg.style.borderRadius = '20px';
   msg.style.maxWidth = '70%';
   msg.style.wordWrap = 'break-word';


   if (sender === 'human') {
       msg.style.background = '#B2EBF2';
       msg.style.alignSelf = 'flex-end';
       msg.style.marginLeft = 'auto';
   } else if (sender === 'bot') {
       msg.style.background = '#fff';
       msg.style.alignSelf = 'flex-start';
       msg.style.marginRight = 'auto';
   } else if (sender === 'dot') {
       // 点のメッセージのスタイル
       msg.style.background = '#fff';
       msg.className = 'dot-message'; // クラスを追加して後で削除しやすくする
       msg.style.alignSelf = 'center';
       msg.style.marginRight = 'auto';
       msg.style.marginLeft = 'auto';
       // 既存の点をクリア
       clearDots();
   }


   chat.appendChild(msg);
   chat.scrollTop = chat.scrollHeight; // 新しいメッセージでスクロール
}
```

DOMがロードされている場合もされていない場合もチャット画面の構築を行う
```
if (document.readyState === 'loading') {
   // DOMがまだロードされていない場合、イベントリスナーを設定
   document.addEventListener('DOMContentLoaded', initChatApp);
} else {
   // DOMが既にロードされている場合、直接関数を実行
   initChatApp();
}
```

##  4. Manifest.jsonの作成
今回はこのチャットを拡張機能化するために最低限の構成になっています。
matchesは拡張機能が適応されるURLになります。
今回はどのページでもチャットができるように*を使用しています。
続いてjsで実際に実行されるjavascriptのファイルを指定しています。
```
{
   "manifest_version": 3,
   "name": "Chat App",
   "version": "1.0",
   "description": "A chat app for the browser.",
   "content_scripts": [
     {
       "matches": ["https://*/*"],
       "js": ["chat.js"]
     }
   ]


 }
 ```
## 5. ChromeのデベロッパーモードでChromeにアップロード
chrome://extensions/
にアクセスして画面右上のデベロッパーモードをオンにするとパッケージ化されていない拡張機能を読み込むボタンが表示されるのでそこを押下して作成したフォルダを選択します。
## 6. 実際に触ってみる

chromeのブラウザの右下にチャットのフローティングボタンが表示されます。
![スクリーンショット 2024-01-21 16.10.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3513156/2252baa5-b5e6-976b-f3c1-1214fca84bad.png)


ボタンを押下してみるとこんな感じのチャット画面が開きチャットすることができるようになります。

![スクリーンショット 2024-01-21 16.11.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3513156/22c23625-acc4-120d-ac88-8ced44eeefbe.png)