Google Spread Sheetを用いた音声対話型チャットボットを作ってみた

#できること
音声対話型チャットボットを簡単に作ることができます。
音声対話する内容はGoogle Spread Sheet内で管理します。

#実装方法
##仕組み
PCのブラウザ上で動作させることを想定としています。
実行用(音声対話画面)と編集用(音声対話一覧表)があります。
![構成.png](https://qiita-image-store.s3.amazonaws.com/0/159447/ea533bc4-e373-9702-9515-6a61567bd634.png)

##音声対話一覧表側の用意(Google Spread Sheet)
下記の手順に従って、音声対話表(Google Spread Sheet)を用意して下さい。

①Googleドライブにアクセスし、『新規』> 『Googleスプレッドシート』で新たにGoogle Spread Sheetを作成してください。
![名称未設定-5.png](https://qiita-image-store.s3.amazonaws.com/0/159447/78309df1-1bb1-f82d-adad-66781b9c7aa8.png)

②無題になっているので、分かりやすい名前(音声対話一覧表など)付けてください。

③下記のように編集して下さい。1行目はカテゴリ名、２行目以降は対話する言葉を記入します。
![音声対話表.png](https://qiita-image-store.s3.amazonaws.com/0/159447/0c056289-b223-8b1f-8bd2-39d0a99af294.png)

④下部にあるタブのシート名を『test』としてください。(音声対話画面に合わせるため)

##音声対話画面側の実装
①Googleドライブにアクセスし、『新規』> 『その他』 > 『Google Apps Script』で新たにGoogle Apps Scriptを作成してください。
②Google Apps Scriptメニューの『ファイル』 > 『新規作成』 > 『HTMLファイル』でindex.htmlファイルを作成してください。
③『コード.gs』と『index.html』に下記にあるコードをそれぞれ書き込んでください。
④ Google Apps Scriptメニューの『公開』 > 『ウェブ アプリケーションとして導入』を選び、下記のように選び導入してください。
　下記の図の『更新』ボタンは、初回時は『導入』となっています。
![ウェブアプリケーションとして導入.png](https://qiita-image-store.s3.amazonaws.com/0/159447/0b409e82-ecbb-3008-b2c9-332c8099884d.png)
⑤ウェブアプリケーション用リンクが作成されるので、それをコピーしてください(YOUR_WEB_APP_IDの部分が異なります)。
例：https://script.google.com/macros/s/YOUR_WEB_APP_ID/exec

#コード.gs
urlの**YOUR_SPREADSHEET_ID**は作成したスプレッドシートのIDに置き変えてください。

```javascript:コード.gs
var url = "https://docs.google.com/spreadsheets/d/YOUR_SPREADSHEET_ID/edit#gid=0";
var spreadsheet = SpreadsheetApp.openByUrl(url);

function doGet(e) {
  return HtmlService.createTemplateFromFile('index')
      .evaluate()
      .setSandboxMode(HtmlService.SandboxMode.IFRAME);
}


function findRow(_sheet_name,_text){
  var sheet = spreadsheet.getSheetByName(_sheet_name);
  var dat = sheet.getDataRange().getValues(); //受け取ったシートのデータを二次元配列に取得
  
  //完全一致
  for(var i=1;i<dat.length;i++){
    if(dat[i][0] == _text){
      return dat[i][1];
    }
  }
  //部分一致
  for(var i=1;i<dat.length;i++){
    if(-1 < dat[i][0].indexOf(_text)){
      return dat[i][1];
    }
  }
  
  
  return 0;
}
```
#index.html
Web Speech APIの途切れない音声認識方法は下記のサイトを参考にさせて頂きました。
- [Web Speech APIで途切れない音声認識](http://jellyware.jp/kurage/iot/webspeechapi.html)

```html:index.html
<!DOCTYPE html>
<html>

<body onLoad="vr_function()">
    <p>認識されている音声は、確定前は『グレー色』、確定後は『黒色』で表示されます。<br>
    このページを起動すると音声認識が自動で開始されます。</p>
    
    <p>現在の状態<br>
    <textarea id="status" cols="100" rows="1">
    </textarea>
    </p>
    <p>音声認識されている言葉<br>
    <textarea id="result_text" cols="100" rows="4">
    </textarea>
    <p>返答された言葉<br>
    <textarea id="speak_text" cols="100" rows="4">
    </textarea>
    </p>
    <p>シートの名前<br>
    <input name="sheetnameBox" id="sheet" type="text" value="test" />
    </p>
    <div>
    <p>発話速度<br>
    <input id="rateRange" type="range" min="0" max="2.0" step="0.1" value="1.0" onchange="showValue()"/>
    <span id="rateArea">1.0</span>
    <p>音程<br>
    <input id="pitchRange" type="range" min="0" max="2.0" step="0.1" value="1.0" onchange="showValue()"/>
    <span id="pitchArea">1.0</span>
    </div>
    <!-- <input type="button" onClick="vr_function(true);" value="音声認識開始" input id="start_button"> -->
</body>

<head>
    <meta charset="UTF-8">
    <title>Web Speech API</title>
    <script type="text/javascript">
        var form = document.forms[ document.forms.length - 1 ];
        //var progress = form.getElementsByTagName( 'ol' )[ 0 ];
        
        var flag_speech = 0;
        function vr_function() {

            window.SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition
            if( ! window.SpeechRecognition ) {
                window.alert("お使いのブラウザでは、Speech APIはサポートされていません。");              
            }
            else{
              var recognition = new webkitSpeechRecognition();
              recognition.lang = 'ja';
              recognition.interimResults = true;
              recognition.continuous = true;
   
              recognition.onsoundstart = function() {
                  document.getElementById('status').innerHTML = "音声認識中";
              };
              recognition.onnomatch = function() {
                  document.getElementById('status').innerHTML = "もう一度試してください";
              };
              recognition.onerror = function() {
                  document.getElementById('status').innerHTML = "エラー";
                  if(flag_speech == 0)
                    vr_function();
              };
              recognition.onsoundend = function() {
                  document.getElementById('status').innerHTML = "停止中";
                    vr_function();
              };
   
              recognition.onresult = function(event) {
                  var results = event.results;
                  var confidence_value;
                  for (var i = event.resultIndex; i < results.length; i++) {
                      confidence_value = results[ i ][ 0 ].confidence;
                      
                      //認識確定
                      if (results[i].isFinal)
                      {
                          google.script.run.withSuccessHandler(result).findRow(document.getElementById('sheet').value, results[i][0].transcript);
                          function result(data){
                            if(data != 0){
                              speak(data);
                              document.getElementById('speak_text').innerHTML = data;
                            }
                            else{
                              document.getElementById('speak_text').innerHTML = "該当する返答する言葉が設定されていないため音声合成されません";
                            }
                          }
                          document.getElementById('result_text').innerHTML = results[i][0].transcript;
                          document.getElementById('result_text').style.color = 'black';
                          vr_function();
                         
                      }
                      //認識途中
                      else
                      {
                          document.getElementById('result_text').innerHTML = results[i][0].transcript;
                          document.getElementById('result_text').style.color = 'gray';
                          flag_speech = 1;
                      }
                  }
              }
              flag_speech = 0;
              document.getElementById('status').innerHTML = "スタンバイ中";
              recognition.start();
            }
        }
        
        function speak(_text){
          
          var speech = new SpeechSynthesisUtterance();
          speech.text = _text;
          
          //speech.volume = 1.0;       // 音量: 0.0～1.0
          speech.rate = parseFloat(document.getElementById("rateRange").value);         // 速度: 0.1～10.0
          speech.pitch = parseFloat(document.getElementById("pitchRange").value);        // 音程: 0.0～2.0
          speech.lang = 'ja-JP';     // 言語
          
          speechSynthesis.speak(speech);
        }
        
        function showValue () {
           document.getElementById("rateArea").innerHTML = document.getElementById("rateRange").value;
           document.getElementById("pitchArea").innerHTML = document.getElementById("pitchRange").value;
        }
        
    </script> 
</head>

</html>
```
#使い方
導入して作成した『[https://script.google.com/macros/s/YOUR_WEB_APP_ID/exec](https://script.google.com/macros/s/YOUR_WEB_APP_ID/exec)』にchromeでアクセスしてください。初回起動時は、おそらくパーミッションの許可を求められるので、例外で許可をしてください。また、マイクの許可も合わせて行ってください。

下記のような画面が開かれたら、マイクに向かって喋ってください。音声対話表に応じて返答が音声合成でされます。また、スプレッドシートのシートを複数作成した場合、対応したシート名を『シートの名前』に入力すれば音声対話表を切り替えることも可能です。
![音声認識ページ.png](https://qiita-image-store.s3.amazonaws.com/0/159447/98134c13-4abc-a8d2-7503-27be6fca362e.png)

#感想
Googleのサービスを使えば簡単に音声対話チャットボットが作成できたので、Googleは凄いなと改めて思いました。ちょっとした音声対話プロトタイプはこれで簡単に検証できますが、合成される音声の話者は日本語だと選べないのがちょっと残念でした。また、試してはないですがAndroidでもchromeで動かせば、動く？と思います。