COTOHA vue.jsで事始め

#はじめに

COTOHAのイベントが行われていましたので、使ってみることにしました。
最近はvue.jsを使用したアプリケーションを作ることが多くなったためvue.jsを採用して検証してみました。

#環境
* node.jsがインストール済み
* COTOHAの[COTOHA API for Developers](https://api.ce-cotoha.com/contents/developers/index.html)に無料登録済みであること

#使っているプラグイン
| プラグイン                |説明             |参考             |
|:-------------------------|:----------------|:----------------|
|vue|vue.jsそのもの||
|vue-cli|vueコマンドライン||
|bootstrap-vue|お馴染みのbootstrap||
|axios|お馴染みのPromiseベースのHTTPクライアント|[axios の導入と簡単な使い方](https://qiita.com/ksh-fthr/items/2daaaf3a15c4c11956e9)|
|vue-axios|vue用のaxios||
|vue-json-tree-view|jsonデータを簡単に表示することができます|[vue-json-tree-view](https://www.npmjs.com/package/vue-json-tree-view)|

#インストール
* githubにソースコードをアップロードしていますのでクローンしてください。
  * [cotoha-vue](https://github.com/hiroakimurayama27/cotoha-vue) こちらにあります。
* クローン先のフォルダーに移動（コマンドプロンプト）
* npm install
* クローン先のフォルダー直下に.envファイルを作成してCOTOHAの情報を設定してください
* 
```
VUE_APP_CLIENT_ID=<COTOHA CLIENT ID>
VUE_APP_CLIENT_SECRET=<COTOHA CLIENT SECRET>
VUE_APP_ACCESSTOKEN_URL=<access token Publish URL>
```

* npm run serve
* http://localhost:8080/ にブラウザーアクセス

#画面はこんな感じ
![コメント 2020-03-03 104246.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/57930/b972b7fc-6c0a-ab53-5eeb-c827c739611c.png)

.envファイルを設定していれば、API実行に必要な状態に初めからなっています。
設定していないときは、画面内のclient_idとclient_secretを設定してから、実行ボタンをクリックして
アクセストークンを取得してください。

### 構文解析
![9e5a45fd-a5f8-4eaf-9d86-81b83ae91097.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/57930/6c9d7b64-93be-9ce5-5e20-2bad199a175d.png)

構文解析を行うときは、ナビメニューから選択して、解析したい文章を入力して実行ボタンをクリックしてください。
必要に応じて、オプションを設定してください。

![a27d344e-3b80-4c1e-9f56-1289c341dd55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/57930/5cac225a-4459-0bb1-0661-1d48db50ad92.png)

実行すると、画面下部にjson形式で結果が表示されます。

#使える機能
* 構文解析
* 固有表現抽出
* 照応解析
* キーワード抽出
* 類似度算出
* 文タイプ判定
* 感情分析

#使えない（対応していない）機能
無料プランでの利用のため、有料版機能は対応していません。
また、β版も今回は見送りました。

* 固有名詞(企業名)補正
* ユーザ属性推定(β)
* 言い淀み除去(β)
* 要約(β)
* 音声認識誤り検知(β)
* 音声認識
* 音声合成

#ソースコードについて
![コメント 2020-03-03 135857.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/57930/005f08e4-8b94-fd55-4f94-a83ed25ed6d4.png)

あんまり難しくはないと思います。以下の順にファイルを追っていけば問題ないかと。

1. main.js
2. App.vue
3. router/index.js
4. views/Main.vue
5. その他viewsフォルダーのファイル

```views/Main.vue
<template>
  <div>
    <nav class="navbar navbar-expand-lg navbar-light bg-light">
      <a class="navbar-brand" @click="show('Main')">Cotoha Vue</a>
      <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
        <span class="navbar-toggler-icon"></span>
      </button>
      <div class="collapse navbar-collapse" id="navbarNav">
        <ul class="navbar-nav">
          <li class="nav-item" :class="{'active': isShow('Parse')}">
            <a class="nav-link"  @click="show('Parse')">構文解析</a>
          </li>
          <li class="nav-item" :class="{'active': isShow('Ne') == true}">
            <a class="nav-link" @click="show('Ne')">固有表現抽出</a>
          </li>
          <li class="nav-item" :class="{'active': isShow('Coreference') == true}">
            <a class="nav-link" @click="show('Coreference')">照応解析</a>
          </li>
          <li class="nav-item" :class="{'active': isShow('Keyword') == true}">
            <a class="nav-link" @click="show('Keyword')">キーワード抽出</a>
          </li>
          <li class="nav-item" :class="{'active': isShow('Similarity') == true}">
            <a class="nav-link" @click="show('Similarity')">類似度算出</a>
          </li>
          <li class="nav-item" :class="{'active': isShow('SentenceType') == true}">
            <a class="nav-link" @click="show('SentenceType')">文タイプ判定</a>
          </li>
          <li class="nav-item" :class="{'active': isShow('Sentiment') == true}">
            <a class="nav-link" @click="show('Sentiment')">感情分析</a>
          </li>
          
        </ul>
      </div>
    </nav>

    <div class="col-sm-12 mt-5" v-show="isShow('Main')">
      <div class="form-group row">
        <span class="offset-3 col-sm-2">client_id</span>
        <input type="tel" class="form-control col-sm-3" v-model="client_id">
      </div>
      <div class="form-group row">
        <span class="offset-3 col-sm-2">client_secret</span>
        <input type="tel" class="form-control col-sm-3" v-model="client_secret">
      </div>
      <div class="form-group row">
        <button class="btn btn-primary offset-5 col-sm-1" @click="getToken">実行</button>
      </div>
      <div class="form-group row">
        <span class="offset-3 col-sm-2">access_token</span>
        <input type="tel" class="form-control col-sm-3" v-model="access_token">
      </div>
    </div>

    <Parse v-show="isShow('Parse')" :accessToken="access_token"></Parse>
    <Ne v-show="isShow('Ne')" :accessToken="access_token"></Ne>
    <Coreference v-show="isShow('Coreference')" :accessToken="access_token"></Coreference>
    <Keyword v-show="isShow('Keyword')" :accessToken="access_token"></Keyword>
    <Similarity v-show="isShow('Similarity')" :accessToken="access_token"></Similarity>
    <SentenceType v-show="isShow('SentenceType')" :accessToken="access_token"></SentenceType>
    <Sentiment v-show="isShow('Sentiment')" :accessToken="access_token"></Sentiment>

  </div>
</template>

<script>

import cotoha from '../mixins/cotoha'
import Parse from './Parse'
import Sentiment from './Sentiment'
import Ne from './Ne'
import Coreference from './Coreference'
import Keyword from './Keyword'
import Similarity from './Similarity'
import SentenceType from './SentenceType'

export default {
  name: 'Main',
  mixins: [cotoha],
  components: {
     Parse
    ,Sentiment
    ,Ne
    ,Coreference
    ,Keyword
    ,Similarity
    ,SentenceType
  },
  data() {
    return {
      actions: {
        'Main': true,
        'Parse': false,
        'Sentiment': false,
        'Ne': false,
        'Coreference': false,
        'Keyword': false,
        'Similarity': false,
      },
    }
  },
  mounted() {
    console.log(this.client_id);
    console.log(this.client_secret);
    console.log(this._access_token);
        
    this.getAccessToken();
    console.log(this._access_token);
  },
  methods: {
    show(action) {
      for (let key in this.actions) {
        this.actions[key] = false;
      }
      this.actions[action] = true;
    },
    isShow(action) {
      return this.actions[action];
    },
    getToken() {
      this.getAccessToken();
    }

  }
}
</script>
```
基本的には、navと表示する各コンポーネントの表示切替とアクセストークンを取得しています。
アクセストークンはmixins/cotoha.jsファイル内を参照してください。

```mixins/cotoha.js

import config from '../config/cotoha.config'

export default {
  data() {
    return {
      // クライアントID
      client_id: null,
      // サークレット
      client_secret: null,
      // アクセストークン
      access_token: null,
      // 解析する文章
      sentence: null,
      // 構文解析結果
      response: {
        result: {},
        status: null,
        message: null,
      },
    }
  },
  props: {
    accessToken: null,
  },
  created() {
    this.client_id = config.CLIENT_ID;
    this.client_secret = config.CLIENT_SECRET;
  },
  methods: {
    /**
     * アクセストークンを取得する
     */
    getAccessToken() {
      const data = {
        grantType: 'client_credentials',
        clientId: this.client_id,
        clientSecret: this.client_secret
      }

      // アクセストークンを取得する
      this.axios.post(config.ACCESSTOKENS_URL, data)
      .then((response) => {
        if (201 == response.status) {
          this.access_token = response.data.access_token;
        }
        else {
          console.log('アクセストークンを取得に失敗しました。' + response.statusText);
        }
      })
      .catch((e) => {
        console.log('アクセストークンを取得に失敗しました。例外エラー: ' + e);
      });
    },
    /**
     * APIの実行
     */
    post(url, data) {
      var AccessToken = this.access_token;
      if (null != this.accessToken) {
        AccessToken = this.accessToken;
      }
      
      return new Promise((resolve, reject) => {
        const headers = {
          'Content-Type': 'application/json;charset=UTF-8',
          Authorization: `Bearer ${AccessToken}`,
        }

        // APIを実行
        this.axios({
          method: 'POST',
          headers: headers,
          url: config.BASE_URL + url,
          data: data
        })
        .then((response) => {
          resolve(response);
        })
        .catch((e) => {
          reject(e);
        });
      });
    },
  }
}

```

**Promise**が登場しています。
[僕なりのPromise入門](https://qiita.com/valley/items/4cf782bd04170fddd5d5)
こちらが参考になりました。ありがとうございました。というよりよく読みますし、読み返しては忘れ、、、使い方良く忘れます、。。

COTOHA APIの実行については、公式のCOTOHA API Portalを参考にしてください。
[COTOHA API Portal](https://api.ce-cotoha.com/)

```views/Parse.vue
<template>
  <div>
    <div class="col-sm-12 mt-5">
      <div class="form-group row">
        <input type="text" class="form-control offset-3 col-sm-6" v-model="sentence">
      </div>
      <div class="form-group row">
        <span class="offset-3 col-sm-2">type</span>
        <select class="form-control col-sm-4" v-model="req_type">
          <option v-for="(value, index) in type" :key="index">{{value}}</option>
        </select>
      </div>
      <div class="form-group row">
        <span class="offset-3 col-sm-2">dic_type</span>
        <div class="col-sm-2 custom-control custom-checkbox text-left" :class="{'offset-5': index > 0 && index % 3 == 0}" v-for="(value, index) in dic_type" :key="index">
          <input
            :id="'req_dic_type' + index"
            type="checkbox"
            class="custom-control-input"
            :value="value"
            v-model="req_dic_type"
          >
          <label :for="'req_dic_type' + index" class="custom-control-label">{{value}}</label>
        </div>
      </div>
      <div class="form-group row">
        <button class="btn btn-primary offset-5 col-sm-1" @click="run">実行</button>
      </div>
      <div class="form-group row">
        <tree-view class="offset-3 col-sm-6" :data="response" :options="{maxDepth: 5}"></tree-view>
      </div>
    </div>
  </div>
</template>

<script>
import API from '../router/cotoha.api'
import cotoha from '../mixins/cotoha'

export default {
  name: 'Parse',
  mixins: [cotoha],
  data() {
    return {
      // リクエスト
      req_type: 'default',
      req_dic_type: [],

      type: [
        'default',      // 通常文
        'kuzure'        // SNSなどの崩れた文
      ],
      dic_type: [
        'IT',           // コンピュータ・情報・通信
        'automobile',   // 自動車
        'chemistry',    // 化学・石油工業
        'company',      // 企業
        'construction', // 土木建築
        'economy',      // 経済・法令
        'energy',       // 電力・エネルギー
        'institution',  // 機関・団体
        'machinery',    // 機械
        'medical',      // 医学
        'metal',        // 非鉄・金属
      ]
    }
  },
  methods: {
    /**
     * 構文分析を行う
     */
    run() {
      if (null != this.sentence && '' != this.sentence
       && null != this.req_type && '' != this.req_type
      ){

        const data = {
          sentence: this.sentence,
          type: this.req_type,
          dic_type: this.req_dic_type,
        }

        this.post(API.COTOHA_API_PARSE, data).then(res => {
            if (0 == res.data.status) {
              this.response = res.data;
            }
            else {
              console.log(res);  
            }
          },
          err => {
            console.log(err);
          });
      }
    },
  }
}
</script>

```

構文解析の例です。基本的にはほかのコンポーネントも同じつくりです。
その他は、ほぼコピペで作りました。
リクエストの内容が解析方法ごとに異なりますので、そのあたりはAPIの仕様書を参照してください。


#肝心のCOTOHA APIについて

1. アクセストークンを取得
    2. [COTOHA API Portal](https://api.ce-cotoha.com/)にログインするとアクセストークンを取得できるURLが掲載されていますので、そのURLにPOSTでアクセスすることで取得できます。
    3. 一般的なWeb APIなので詳細は割愛します。
2. アクセストークンを含めて、各APIを実行
3. json形式で結果を取得します

いろいろなデータで結果を確認して、”おっ！結構使えるじゃね？”と思っています。
※ wikipediaなどの文章をコピペで試してみました。
仕事に使えるかについては、もっと検証が必要ですが。

#ハマったこと
* アクセストークンを取得できない
  * 原因としては、post実行結果のレスポンスのステータスが成功時は201で返却されること
  * ※ status==200　になっていることを確認していたため取得に成功しているにもかかわらず、失敗扱いにしていました。。。。
* APIの実行結果をうまく表示できない
  * はじめは丁寧に１つ１つレスポンスを表示していましたが、動作検証が目的だったため、
  * 簡単に表示できないか探すのに時間がかかりました。
  * ※　vue-json-tree-viewが見つかってラッキーでした


#終わりに
APIの性能確認や動作確認などにご利用ください。