BotUIを使ってチャットボットを開発してみた

## はじめに

はじめまして！ t_powerです。
本記事では、社内FAQを[BotUI](https://docs.botui.org/)でチャットボット化した話を扱います。

プログラミング知識があまりない・・・
チャットボットを作ってみたいけど難しそう・・・
と思っている人にはぴったりのツールだと思うので、是非ご覧ください！

今回作成する上で下記のサイトを参考にさせていただきました。
この記事を見て実際に作ろうと思っている方はこちらも参考にしてください。
1. [JavaScriptだけで本格的なチャットボットを開発できるライブラリ「BotUI」を使ってみた！](https://paiza.hatenablog.com/entry/2017/09/21/%EF%BB%BFJavaScript%E3%81%A0%E3%81%91%E3%81%A7%E6%9C%AC%E6%A0%BC%E7%9A%84%E3%81%AA%E3%83%81%E3%83%A3%E3%83%83%E3%83%88%E3%83%9C%E3%83%83%E3%83%88%E3%82%92%E9%96%8B%E7%99%BA%E3%81%A7%E3%81%8D%E3%82%8B)
2. [チャットボットを作ろう：2. JekyllサイトでBotUI](https://haltaro.github.io/2017/12/30/chatbot)

## 今回作成したもの

https://www.chuo-system.co.jp/wwwlisabot/

「はじめる」と書かれているバナーからチャットボットを使うことができます。

## 作ろうと思った経緯

### before

![chatbot2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1998725/146af454-43a3-c6d4-0e1c-1c72fa8eca91.png)

従来は、問い合わせをメールなどで受け付けて、担当者が直接回答をしていました。
類似の問い合わせも多く、担当者の負担になっていました。

### after

![chatbot3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1998725/013f593f-2747-af4d-9b4c-249cabbfedf3.png)

**それなら・・・**

AIが流行っているし、問い合わせ対応をチャットボット化すればいいじゃないか！
という流れになり、チャットボットを開発することになりました。

## BotUIとは

>チャットボットのUIを構築するためのJavaScriptフレームワーク。直観的なJavaScript APIを使用して、メッセージを追加し、ユーザーが実行できるアクションを表示できる。
(出典: [BotUI公式ドキュメント](https://docs.botui.org/))

要するに、これを使えば、簡単にチャットボットが作れるよ～ってわけです！

チャットボットは大きく分けると**シナリオ型**と**AI型（人工知能型）**の2種類存在していて、BotUIは、**シナリオ型を簡単に作ることが出来るフレームワーク**になっています。
※シナリオ型とAI型の違いについてはこちらをご覧ください
[シナリオ型・AI型のチャットボットを比較～導入メリットとシナリオ作成のコツ](https://qualva.com/qualvatics/archive/688/)

チャットボット用のフレームワークやライブラリはたくさんあるようですが、今回は**BotUI**が適していそうだったので、こちらを採用しました。

## 事前準備

### 作業環境

OS: Windows10
テキストエディタ: Visual Studio Code
ブラウザ: Microsoft Edge

### フォルダ構成

![tree2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1998725/4899a9b9-aeda-da70-37d4-bfe5a872e5ee.png)

- `css/botui-theme-default.css`
独自のテーマを作成することができるファイル（デザインの変更はこちらのファイルがメイン）

- `css/botui.min.css`
基本レイアウトを設定しているファイル（あまり書き換える必要ない）

※CSSファイルはCDN経由で読み込んでもよいのですが、今回はデザインを変更したかったため、[GitHub](https://github.com/botui/botui)からダウンロードしました
※とにかく動くチャットボットをはやく作りたい！デザインなんてどうでもいい！という方はダウンロードする必要はございません

- `images/sample.png`
BOT側のアイコン
※もしアイコンを表示させたい場合は、こちらの**任意の画像**と、**botui.min.css**をダウンロードして、書き換える必要があります（無くてもチャットボットは動きます）

- `bot/index.html`
チャットボットを表示させるファイル

- `bot/answer.js`
質問に対する回答をまとめたファイル

- `bot/bot.js`
BotUIを使ってシナリオを構成するファイル

## 基本的な使い方

### index.html

```html:index.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8">
    <title>チャットボット</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="../css/botui-theme-default.css" />
    <link rel="stylesheet" href="../css/botui.min.css" />
    <!-- link rel="stylesheet" href="https://unpkg.com/botui/build/botui.min.css" / -->
    <!-- link rel="stylesheet" href="https://unpkg.com/botui/build/botui-theme-default.css" / -->
  </head>
  <body>
    <div id="botui-app" style="white-space:pre-wrap; word-wrap:break-word;">
      <bot-ui></bot-ui>
    </div>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.5/vue.min.js"></script>
    <script src="https://unpkg.com/botui/build/botui.min.js"></script>
    <script src="./answer.js"></script>
    <script src="./bot.js"></script>
  </body>
</html>
```

index.htmlは上記のコードをそのままコピペでOKです。
（デザインにこだわらない人は`<!--link rel="stylesheet" href="https://unpkg.com/botui/build/botui.min.css" />`と`<!-- link rel="stylesheet" href="https://unpkg.com/botui/build/botui-theme-default.css" / -->`　のコメントを外して、お使いください）

また、Bootstrapを導入して、スマホ、PC対応のレスポンシブな画面にしてもよいかもしれません。

- `style="white-space:pre-wrap; word-wrap:break-word;"`について
- CDN経由で読み込んでいるVue.jsのバージョンがなぜ最新バージョンではないのか

上記2点は[苦戦した点](#苦戦した点)にて記載しています。

---

### bot.js

```javascript:bot.js
var botui = new BotUI('botui-app');
var Photo = '../images/sample.png'

//初期メッセージ
botui.message.add({
  photo: Photo,
  content: 'チャットボットです',
  delay: 1000
}).then(showQuestions);

//質問の選択肢を表示する関数
function showQuestions(){
  botui.message.add({
    photo: Photo,
    content: '質問をお選びください',
    delay: 1000
  }).then(function(){

  return botui.action.button({
    autoHide: false,
    delay: 1000,
    action: [
      {icon: 'sticky-note-o', text: '採用', value: 'recruitment'},
      {icon: 'user', text: '新入社員', value: 'newEmployee'},
      {icon: 'ellipsis-h', text: 'その他', value: 'other'}]
  });
  }).then(function(res){
    botui.action.hide();
      switch(res.value){
        case 'recruitment': showRecruitment(); break;
        case 'newEmployee': showNewEmployee(); break;
        case 'other': showOther(); break;
        default: end();
      }
  });
}

//採用カテゴリの質問の選択肢を表示する関数
function showRecruitment(){
  botui.message.add({
    photo: Photo,
    delay: 1000,
    content: '当てはまるものをお選びください'
  }).then(function(){

  //ボタンを表示
  return botui.action.button({
    autoHide: false,
    delay: 1000,
    action: [
      {icon: 'circle', text: '一次面接と最終面接の違い', value: 'interviewDifference'},
      {icon: 'circle', text: '筆記試験の合格基準を教えていただけないでしょうか', value: 'successCriteria'},
      {icon: 'long-arrow-left', text: '1つ戻る', value: 'return'}]
  });
  }).then(function(res){
    botui.action.hide();
      switch(res.value){
        case 'interviewDifference': showInterviewDifference(); break;
        case 'successCriteria': showSuccessCriteria(); break;
        case 'return': showQuestions(); break;
        default: end(); 
      }  
  });
}

function showInterviewDifference(){
  botui.message.add({
    photo: Photo,
    delay: 1000,
    content: ansInterviewDifference
  }).then(askEnd);
}

/*
省略
*/

//プログラムを終了するか聞く関数
function askEnd(){
  botui.message.add({
    photo: Photo,
    delay: 2000,
    content: '他に質問はありますか'
  }).then(function(){
  return botui.action.button({
    delay: 1500,
    action: [
      {icon: 'circle-o', text: 'はい', value: true},
      {icon: 'close', text: 'いいえ', value: false}]
  });
  }).then(function(res){
    res.value ? showQuestions() : end();
  });
}

//プログラムを終了する関数
function end(){
  botui.message.add({
    photo: Photo,
    delay: 1500,
    content: ansEnd
  })
}
```

![スクリーンショット (176).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1998725/f7b56543-3d6c-4e5c-f06d-6fa65b883476.png)
今回作成したシナリオはこのような構成です。
（上記のコードには記載していませんが、「新入社員」、「その他」についても、「採用」と同様に作りました）

bot.jsもphotoのパス指定だけ気を付ければ、コピペで問題ないです。

今回使用したメソッドやオブジェクトについて説明します。

**method**

| 名前 | 説明 |
|----|----|
| botui.message.add | 新規メッセージを追加する |
| botui.action.button | ボタンを表示する |
| botui.action.hide | アクションを非表示にする |

**action object**

| 名前 | 説明 |
|----|----|
| autoHide | 自動的に非表示になるのを防ぐ <br> botui.action.hide()と一緒に使い、これが呼び出されたときにボタンを消す |
| action | 今回はbutton objectのicon、text、valueでそれぞれ、ボタンのアイコン、表示させる文、値を設定できる <br> また、iconは[FontAwesome](https://fontawesome.com/)を利用している <br> botui.action.button()などと一緒に使う|

**message object**

| 名前 | 説明 |
|----|----|
| photo | 画像のパスを指定して、アイコンを指定 |
| content | 表示されるメッセージを指定 |
| delay | 表示されるまでの秒数を設定できる <br> ※ミリ秒単位なので、1000は1秒にあたる |

他のメソッドやオブジェクトに関しては[こちら](https://docs.botui.org/reference.html#button-object)をご覧ください。


```javascript:bot.js
}).then(function(res){
    botui.action.hide();
      switch(res.value){
        case 'recruitment': showRecruitment(); break;
        case 'newEmployee': showNewEmployee(); break;
        case 'other': showOther(); break;
        default: end();
      }
  });
```

選択した項目からチャットを進めていくために、switch文で条件分岐させ、選択したボタンの`value`を`res`で取得し、それに合った関数を呼び出しています。

```javascript:bot.js
//プログラムを終了するか聞く関数
function askEnd(){
  botui.message.add({
    photo: Photo,
    delay: 2000,
    content: '他に質問はありますか'
  }).then(function(){
  return botui.action.button({
    delay: 1500,
    action: [
      {icon: 'circle-o', text: 'はい', value: true},
      {icon: 'close', text: 'いいえ', value: false}]
  });
  }).then(function(res){
    res.value ? showQuestions() : end();
  });
}
```

プログラムを終了するか聞く関数のaskEnd()では、「はい」か「いいえ」の2択しかないため、3項演算子を使用して分岐させています。
?の左側`res.value`が**true**なら`showQuestions()`、**false**なら`end()`を呼び出します。

![スクリーンショット (173).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1998725/071a6180-89e4-3e4e-eb5a-280540f5beac.png)

---

### answer.js

回答メッセージの変数をまとめたファイルです。
後で、回答メッセージをDBから取得するように改修する予定なので、一旦別ファイルに分けて宣言をしています。

```javascript:answer.js
var ansInterviewDifference = `一次面接は採用担当が実施します。
最終面接は社長が実施します。`

/*
省略
*/

var ansEnd = `ご利用いただきありがとうございました。
[TOPページへ戻る](http://xxx.xxx.xx.xxx)`
```

![スクリーンショット (174).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1998725/9ed7e529-8575-fc1a-a91c-ef613d5e1618.png)
![スクリーンショット (175).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1998725/2c4c29c2-bd4e-270a-2cb6-fd315d0f4631.png)

このように、質問に対する回答をまとめています。

また、表示させるメッセージについては、\`（バッククォート）で囲むことで、実際にファイルに記載したようにメッセージボックスにも表示されるようになります。

`[TOPページへ戻る](http://xxx.xxx.xx.xxx)`については、[苦戦した点](#苦戦した点)にて記載します。

```html:index.html
    <script src="./answer.js"></script>
    <script src="./bot.js"></script>
```

今回のように別のjsファイルに質問に対する回答をまとめる場合、`index.html`で**先に回答ファイル`answer.js`を読み込む必要がある**ので注意！

## 苦戦した点

### 指定した場所で改行できない

```html:index.html
<div class="container" id="botui-app" style="white-space:pre-wrap; word-wrap:break-word;">
```

```javascript:answer.js
var ansEnd = `ご利用いただきありがとうございました。
[TOPページへ戻る](http://xxx.xxx.xx.xxx)`
```

メッセージ内で`\n`や`\r\n`などの改行コードを試してもうまくいかず・・・

色々調べてみると・・・

Vue.jsで文字列を改行する場合
`style="white-space:pre-wrap; word-wrap:break-word;"`
bot-uiタグが囲まれているdivタグの中にこれを記載し、先ほどanswer.jsで紹介したメッセージの内容を\`（バッククォート）で囲む必要があるみたいです。

参考記事
[Vue.jsでdataの文字列を改行したい](https://teratail.com/questions/321479)
[HTMLクイックリファレンス white-space](http://www.htmq.com/style/white-space.shtml)
[HTMLクイックリファレンス word-wrap](http://www.htmq.com/style/word-wrap.shtml)

### リンク化できない

```html:index.html
<!-- これだとリンク化できない -->
<script src="https://cdn.jsdelivr.net/vue/latest/vue.min.js"></script>
<!-- 2.0.5にすることでリンク化できる -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.5/vue.min.js"></script>
```

[公式ガイド](https://docs.botui.org/install.html)に記載されている通り、Vue.jsの最新バージョンをCDN経由で読み込んでいました。しかし、その場合URLの後に別のメッセージが表示されると、リンク化されなくなってしまうみたいです。

この問題を解決するためには、Vue.jsのバージョンを2.0.5に落とすことで、どのような状況でもリンク化されるようになります。

参考記事
[URL loses its markup in javascript framework (BotUI)](https://stackoverflow.com/questions/48459341/url-loses-its-markup-in-javascript-framework-botui)

### URLを新規タブで展開できない

```javascript:answer.js
var ansEnd = `ご利用いただきありがとうございました。
[TOPページへ戻る](http://xxx.xxx.xx.xxx)`
```

\[表示文字](URL)で指定すると、同じタブで画面が切り替わってしまって、チャットボットが閉じてしまいます。

```javascript:answer.js
var newline = `[BotUI 公式マニュアル](https://docs.botui.org/index.html)^`
```

新規タブで展開するには、カッコの後ろに^（キャレット）を付けることで解決できました。

## デザイン

今回デザインを変更するために、GitHubからダウンロードして読み込んだ、
**botui.theme-default.css**と**botui.min.css**で重要だと思った点を紹介します。

### botui.theme-default.css

#### .botui-cotainer

```css:botui-theme-default.css
.botui-container{
  font-size:14px;
  background-color:#fff;
  font-family:"Open Sans",sans-serif
}
```

- font-size
メッセージの文字やボタンの文字の大きさを指定

- background-color
チャット背景色を指定

- font-family
  フォントを指定（フォントファミリーと総称フォントの2つがある）

#### .botui-messages-container

```css:botui-theme-default.css
.botui-messages-container{
  padding:10px 20px
}
```

- padding <br>
メッセージのパディングを指定

#### .botui-actions-container

```css:botui-theme-default.css
.botui-actions-container{
  padding:0px 20px
}
```

- padding <br>
アクション（今回はボタン）のパディングを指定

#### .botui-message

```css:botui-theme-default.css
.botui-message{
  min-height:30px
}
```

- min-height <br>
メッセージ間の最小幅を指定

#### .botui-message-content

```css:botui-theme-default.css
.botui-message-content{
  padding:7px 13px;
  border-radius:15px;
  color:#000000;
  background-color:#8bf8f8
}
```

- padding
メッセージ領域内のパディングを指定

- color
BOT側（左側）に表示されるメッセージの色を指定

- background-color
BOT側（左側）に表示されるメッセージ領域内の背景色を指定

※border-radiusは外枠の丸み具合を変更するプロパティだが、ここで値を変更しても変わりませんでした（メッセージの要素内を変更するプロパティだから効かない？）

#### .botui-message-content.human

```css:botui-theme-default.css
.botui-message-content.human{
  color:#000000;
  background-color:#f8ddaa
}
```

- color
人間側（右側）に表示されるメッセージの色を指定

- background-color
人間側（右側）に表示されるメッセージ領域内の背景色を指定

#### .botui-message-content.text

```css:botui-theme-default.css
.botui-message-content.text{
  line-height:150%
}
```

- line-height <br>
メッセージ領域内の行間の高さを指定

#### .botui-message-content.embed

```css:botui-theme-default.css
.botui-message-content.embed{
  padding:5px;
  border-radius:5px
}
```

- padding
動画のパディングを指定

- border-radius
外枠の丸みを指定

#### .botui-message-content-link

```css:botui-theme-default.css
.botui-message-content-link{
  color:#1A0DAB
}
```

- color <br>
リンクの色を指定

#### .botui-actions-buttons-button

```css:botui-theme-default.css
.botui-actions-buttons-button{
  border:1;
  color:#fff;
  line-height:1;
  cursor:pointer;
  font-size:14px;
  font-weight:normal;
  padding:10px 20px;
  border-radius:4px;
  font-family:"Open Sans",sans-serif;
  background:#777979;
  box-shadow:2px 3px 4px 0 rgba(0,0,0,.25)
}
```

- cursor
ボタンにカーソルを合わせたときの種類を指定

- font-weight
ボタンの文字の太さを指定

### botui.min.css

#### .botui-message-content

```css:botui.min.css
.botui-message-content{
  width:auto;
  max-width:80%;
  display:inline-block
}
```

- max-width <br>
メッセージボックスの幅を指定

#### .profil

```css:botui.min.css
.profil{
  position:relative;
  top: -10px;
  border-radius:50%
}
```

- top <br>
基準の位置からどれくらい上に動かすかを指定

※元々topプロパティは無かったが、アイコンを大きくしたらズレてしまったので、追記して丁度いい位置にくるように指定しました

#### .profil>img

```css:botui.min.css
.profil>img{
  width:50px;
  height:50px;
  border:2px solid #e8e8e8
}
```

- width
アイコン画像の幅を指定

- height
アイコン画像の高さを指定

#### .profil>img.agent

```css:botui.min.css
.profil>img.agent{
  /*初期設定だと変な画像になっているため、削除推奨
  content:url(http://decodemoji.com/img/logos/blue_moji_hat.svg);
  */
  border-radius:50%
}
```

- content <br>
ここで画像指定していると、photoで表示したい画像を選んでもBOT側のアイコンは強制的にここで設定している画像になるため、**削除推奨**

## おまけ

### YouTube動画の埋め込み方

今回作成したチャットボットでは使用していませんが、試したので記載します。

```javascript:bot.js
botui.message.add({
  type: 'embed',
  content: 'https://www.youtube.com/embed/1oSeywNsjak',
  delay: 1000
}).then(showQuestions);
```

YouTubeの動画を埋め込む際に注意する必要があります。
embedは外部要求を許可しているが、watchは許可していないため、URLを書き換える必要があります。
× <https://www.youtube.com/watch?v=1oSeywNsjak>
○ <https://www.youtube.com/embed/1oSeywNsjak>

![youtube.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1998725/f3e774d7-d46f-180c-c3de-3277b7f64a11.png)

参考記事
[youtube html 埋め込み](https://teratail.com/questions/203746)

## おわりに

JavaScript初心者でも、BotUIを使うことで、簡単にシナリオ型のチャットボットを作ることが出来ました。
直観的に操作することが出来るので、プログラミングがよくわからなくても、それっぽいものを作れてしまうのが、BotUIのいいところだと思いました。

![スクリーンショット (178).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1998725/843b14d3-4a95-018c-3778-0ccdd5eee7de.png)
しかし、数が増えるとボタンも増える（分岐が増えてしまう）ため、見栄えがとても悪くなってしまいます。そのため、今後はAI型にシフトさせるなどの対策が必要だと思いました。

最後まで読んでいただき、ありがとうございました:bow_tone1: