Amazon Alexaask-sdkJavaScriptNode.js

Alexaにフロントエンドマークアップ言語がやってきた – Alexa Presentation Language (APL)ことはじめ

Alexaスキルを使っていると、たまに「ん?このディスプレイ表示どうやってるんだ?」となるものや、「デフォルトの天気予報みたいな表示やりたいんだけどなぁ」ということはありませんでしたか? いままでは決められたフォーマット […]

広告ここから
広告ここまで

Alexaスキルを使っていると、たまに「ん?このディスプレイ表示どうやってるんだ?」となるものや、「デフォルトの天気予報みたいな表示やりたいんだけどなぁ」ということはありませんでしたか?

いままでは決められたフォーマットでのみディスプレイ表示を定義することができましたが、今日からはついに自由にフロントエンドをマークアップすることができるようになります。

Now Available: Alexa Presentation Language (Public Beta) for Multimodal Experiences

「Alexaの見た目を定義する言語」ということで「Alexa Presentational Language(略称APL)」とよばれているこの機能を、さっそく触ってみましょう。

作ったSkillにAPLを導入する

APLをスキルに組み込むには、スキルの設定を更新する必要があります。

スキル開発画面の[インターフェース]から、[APL]をオンにしましょう。

ASK CLIで管理されている場合は、skill.jsonに以下を追加します。

"apis": {
      "custom": {
        "endpoint": {
          "uri": "arn:aws:lambda:us-east-1:999~"
        },
        "interfaces": [
          {  
              "type": "ALEXA_PRESENTATION_APL"
          } 
        ],

ALEXA_PRESENTATION_APLを含めた状態でask deployまたはask deploy -t skillすることでAPL Readyとなります。

APLでフロントエンドを構築する

ではこのAPL、どこで実装すればよいでしょうか?プレビューしながら実装するためのコンソールがスキル開発画面に用意されています。

[画面表示]をクリックすることで、以下のようにベースとなるテンプレートを選択できます。

よく見ると、サンプルはこれまであったテンプレートが表示されている様子ですね。

サンプルをクリックすると、以下のようにプレビュー付きの開発画面が表示されます。

デフォルトではブロック単位で編集するビジュアルモードが表示されます。真ん中にある青いトグルをクリックすると、APLの実態であるJSONがでてきます。

{
    "type": "APL",
    "version": "1.0",
    "theme": "dark",
    "import": [
        {
            "name": "alexa-layouts",
            "version": "1.0.0"
        }
    ],
    "resources": [
        {
            "description": "Stock color for the light theme",
            "colors": {
                "colorTextPrimary": "#151920"
            }
        },
        {
            "description": "Stock color for the dark theme",
            "when": "${viewport.theme == 'dark'}",
            "colors": {
                "colorTextPrimary": "#f0f1ef"
            }
        },
        {
            "description": "Standard font sizes",
            "dimensions": {
                "textSizeBody": 48,
                "textSizePrimary": 27,
                "textSizeSecondary": 23,
                "textSizeSecondaryHint": 25
            }
        },
        {
            "description": "Common spacing values",
            "dimensions": {
                "spacingThin": 6,
                "spacingSmall": 12,
                "spacingMedium": 24,
                "spacingLarge": 48,
                "spacingExtraLarge": 72
            }
        },
        {
            "description": "Common margins and padding",
            "dimensions": {
                "marginTop": 40,
                "marginLeft": 60,
                "marginRight": 60,
                "marginBottom": 40
            }
        }
    ],
    "styles": {
        "textStyleBase": {
            "description": "Base font description; set color and core font family",
            "values": [
                {
                    "color": "@colorTextPrimary",
                    "fontFamily": "Amazon Ember"
                }
            ]
        },
        "textStyleBase0": {
            "description": "Thin version of basic font",
            "extend": "textStyleBase",
            "values": {
                "fontWeight": "100"
            }
        },
        "textStyleBase1": {
            "description": "Light version of basic font",
            "extend": "textStyleBase",
            "values": {
                "fontWeight": "300"
            }
        },
        "mixinBody": {
            "values": {
                "fontSize": "@textSizeBody"
            }
        },
        "mixinPrimary": {
            "values": {
                "fontSize": "@textSizePrimary"
            }
        },
        "mixinSecondary": {
            "values": {
                "fontSize": "@textSizeSecondary"
            }
        },
        "textStylePrimary": {
            "extend": [
                "textStyleBase1",
                "mixinPrimary"
            ]
        },
        "textStyleSecondary": {
            "extend": [
                "textStyleBase0",
                "mixinSecondary"
            ]
        },
        "textStyleBody": {
            "extend": [
                "textStyleBase1",
                "mixinBody"
            ]
        },
        "textStyleSecondaryHint": {
            "values": {
                "fontFamily": "Bookerly",
                "fontStyle": "italic",
                "fontSize": "@textSizeSecondaryHint",
                "color": "@colorTextPrimary"
            }
        }
    },
    "layouts": {},
    "mainTemplate": {
        "description": "********* Full-screen background image **********",
        "parameters": [
            "payload"
        ],
        "items": [
            {
                "when": "${viewport.shape == 'round'}",
                "type": "Container",
                "direction": "column",
                "items": [
                    {
                        "type": "Image",
                        "source": "${payload.bodyTemplate1Data.backgroundImage.sources[0].url}",
                        "position": "absolute",
                        "width": "100vw",
                        "height": "100vh",
                        "scale": "best-fill"
                    },
                    {
                        "type": "AlexaHeader",
                        "headerTitle": "${payload.bodyTemplate1Data.title}",
                        "headerAttributionImage": "${payload.bodyTemplate1Data.logoUrl}"
                    },
                    {
                        "type": "Container",
                        "grow": 1,
                        "paddingLeft": "@marginLeft",
                        "paddingRight": "@marginRight",
                        "paddingBottom": "@marginBottom",
                        "items": [
                            {
                                "type": "Text",
                                "text": "${payload.bodyTemplate1Data.textContent.primaryText.text}",
                                "size": "@textSizeBody",
                                "spacing": "@spacingSmall",
                                "style": "textStyleBody"
                            }
                        ]
                    }
                ]
            },
            {
                "type": "Container",
                "height": "100vh",
                "items": [
                    {
                        "type": "Image",
                        "source": "${payload.bodyTemplate1Data.backgroundImage.sources[0].url}",
                        "position": "absolute",
                        "width": "100vw",
                        "height": "100vh",
                        "scale": "best-fill"
                    },
                    {
                        "type": "AlexaHeader",
                        "headerTitle": "${payload.bodyTemplate1Data.title}",
                        "headerAttributionImage": "${payload.bodyTemplate1Data.logoUrl}"
                    },
                    {
                        "type": "Container",
                        "paddingLeft": "@marginLeft",
                        "paddingRight": "@marginRight",
                        "paddingBottom": "@marginBottom",
                        "items": [
                            {
                                "type": "Text",
                                "text": "${payload.bodyTemplate1Data.textContent.primaryText.text}",
                                "size": "@textSizeBody",
                                "spacing": "@spacingSmall",
                                "style": "textStyleBody"
                            }
                        ]
                    }
                ]
            }
        ]
    }
}

たのしいですね。

APLを触ってみる

あえてJSONをダイレクトにさわるマゾい道を選んでみます。

themedarklightに変更すると、表示がこのように変わりました。

ここの文字色は以下のように定義されています。

    "resources": [
        {
            "description": "Stock color for the light theme",
            "colors": {
                "colorTextPrimary": "#151920"
            }
        },
        {
            "description": "Stock color for the dark theme",
            "when": "${viewport.theme == 'dark'}",
            "colors": {
                "colorTextPrimary": "#f0f1ef"
            }
        },

themedarkの場合は#f0f1ef、そうでない場合は#151920があたるということですね。

文字が読めなくなっているので、色を変えてみましょう。

    "resources": [
        {
            "description": "Stock color for the light theme",
            "colors": {
                "colorTextPrimary": "lightblue"
            }
        },
        {
            "description": "Stock color for the dark theme",
            "when": "${viewport.theme == 'dark'}",
            "colors": {
                "colorTextPrimary": "#f0f1ef"
            }
        },

ちなみに色コードにはRGBやRGBAも指定可能です。

ASK SDKのコードで読み込ませる

作成したViewはJSON形式でASK SDKから読み込ませます。エディタ右上の書き出しボタンでJSONを出力しましょう。

またASK SDK(Node.js)は2.2以上にする必要があります。

$ npm i -S ask-sdk-core@latest
or 
$ npm i - S ask-sdk@latest

そして読み込みのコードは以下のようになります。

      .addDirective({
          type: 'Alexa.Presentation.APL.RenderDocument',
          version: '1.0',
          datasources: {
                    // テンプレートに流し込むデータ
          },
          document: require('PATH_TO_TEMPLATE.JSON')
      })

先ほどのサンプルですと、こうなります。

handlerInput.responseBuilder
        .speak(messages)
        .addDirective({
          type: 'Alexa.Presentation.APL.RenderDocument',
          version: '1.0',
          datasources: {
            'bodyTemplate1Data': {
              'type': 'object',
              'objectId': 'bt1Sample',
              'backgroundImage': {
                'contentDescription': null,
                'smallSourceUrl': null,
                'largeSourceUrl': null,
                'sources': [
                  {
                    'url': 'https://d2o906d8ln7ui1.cloudfront.net/images/BT1_Background.png',
                    'size': 'small',
                    'widthPixels': 0,
                    'heightPixels': 0
                  },
                  {
                    'url': 'https://d2o906d8ln7ui1.cloudfront.net/images/BT1_Background.png',
                    'size': 'large',
                    'widthPixels': 0,
                    'heightPixels': 0
                  }
                ]
              },
              'title': 'Did You Know?',
              'textContent': {
                'primaryText': {
                  'type': 'PlainText',
                  'text': 'But in reality, mice prefer grains, fruits, and manmade foods that are high in sugar, and tend to turn up their noses at very smelly foods, like cheese. In fact, a 2006 study found that mice actively avoid cheese and dairy in general.'
                }
              },
              'logoUrl': 'https://d2o906d8ln7ui1.cloudfront.net/images/cheeseskillicon.png'
            }
          },
          document: require('../apmls/result.json')
        })

datasourcesの値は、先ほどのエディタ画面から[JSONデータ]タブを開くことでみることができます。

ここでJSONの値を入れ替えてプレビューすることもできますので、いろいろデバッグしてみましょう。

APLを使うときの注意点

Displayインターフェースと同じですが、Echo Dotsやスマートフォンアプリに対するレスポンスに含めることはNGです。リクエストの値を見てaddDirectiveをレスポンスに含めるかを確認するように実装しましょう。

ask-utilsというライブラリを公開・メンテナンスしていますが、こちらを使えば以下のような形で分岐させることが可能です。

const {
  supportsDisplay
} = require('ask-utils')

// 中略
handle(handlerInput) {
  const response = handlerInput.responseBuilder
                        .speak('こんにちは元気?')
                        .reprompt('元気?')
  if (supportsDisplay(handlerInput)) {
    response.addDirective({
          type: 'Alexa.Presentation.APL.RenderDocument',
          version: '1.0',
          datasources: {
                    // テンプレートに流し込むデータ
          },
          document: require('PATH_TO_TEMPLATE.JSON')
      })
  }
  return response.getResponse()
}

まとめ

今回は触りの部分を中心にAPLを紹介しました。デバイスサイズやモードによって動的にコンテンツを変更することなどもできますので、まとめ次第こちらも随時紹介していきます。

ブックマークや限定記事(予定)など

WP Kyotoサポーター募集中

WordPressやフロントエンドアプリのホスティング、Algolia・AWSなどのサービス利用料を支援する「WP Kyotoサポーター」を募集しています。
月額または年額の有料プランを契約すると、ブックマーク機能などのサポーター限定機能がご利用いただけます。

14日間のトライアルも用意しておりますので、「このサイトよく見るな」という方はぜひご検討ください。

広告ここから
広告ここまで

Related Category posts