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をダイレクトにさわるマゾい道を選んでみます。
theme
のdark
をlight
に変更すると、表示がこのように変わりました。
ここの文字色は以下のように定義されています。
"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"
}
},
theme
がdark
の場合は#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を紹介しました。デバイスサイズやモードによって動的にコンテンツを変更することなどもできますので、まとめ次第こちらも随時紹介していきます。