Amazon недавно выпустила язык представления Alexa (APL). APL обеспечивает более богатое отображение мультимодальных навыков. Он основан на современных фреймворках, которые отделяют элементы отображения от источников данных. Это дает вам возможность включать множество визуальных элементов, таких как графика, изображения и слайд-шоу, и позволяет адаптировать отображение для различных устройств.

В этой статье я расскажу, как я обновил один из своих навыков для использования APL. Вы также можете использовать эти советы и приемы, если создаете новый навык.

Большинство моих навыков связаны с мультимодальной поддержкой с использованием Display Interface. Я решил изучить APL, обновив один из имеющихся у меня навыков. Я сосредоточился на своем навыке Видеопокер, потому что меня не устраивал существующий опыт работы с клиентами.

Видео покер представляет пользователям покерную комбинацию из 5 карт с возможностью удерживать и сбрасывать карты перед розыгрышем для завершения руки. Пользователи могут сделать это с помощью голосовой команды («оставить первую карту» или «оставить себе пару валетов») или касаясь карт на визуальном дисплее. ListTemplate2 был лучшим способом сделать это с помощью интерфейса дисплея. Однако это было связано с ограничением одновременного отображения на экране только трех карточек и номера под каждой карточкой в ​​списке.

Используя APL, я изменил макет ListTemplate2, чтобы уменьшить размер элементов списка, уменьшить расстояние между ними и разместить на экране целую руку с пятью карточками. Мне удалось удалить числа из элементов в списке и поместить текст, указывающий, какие карточки хранятся, жирным красным шрифтом. Мне удалось оптимизировать макет для экранов разных размеров, например для дисплеев меньшего размера, таких как Echo Spot.

Инструмент разработки APL

Я сделал это с помощью APL Authoring Tool. Этот удобный инструмент предоставляет список различных визуальных дизайнов, которые вы можете использовать в качестве основы для создания убедительных визуальных эффектов для ваших навыков. Он также позволяет вам сохранять и выгружать макеты, чтобы вы могли извлекать их в свой код навыков или загружать их, если вы делаете офлайн-обновления. В этом случае я начал с образца списка переадресации изображений, который основан на ListTemplate2.

После выбора этого визуального оформления в инструменте вы увидите общий пример списка с образцами сыра. Вы увидите документ APL внизу экрана, разделенный на две вкладки:

  • «Образец списка переадресации изображений», в котором представлен макет
  • Вкладка «Data JSON», которая обеспечивает просмотр данных документа.

Найдите минутку, чтобы просмотреть JSON на вкладке Image Forward List Sample (код макета) и на вкладке Data JSON (код содержимого). Вы заметите, что в макете есть несколько ссылок на значения, заключенные в ${}, например ${payload.listTemplate2ListData.listPage.listItems.length}. Если вы посмотрите в файл содержимого, вы увидите, что это путь к значению в содержимом. Таким образом APL привязывает данные к уровню представления и позволяет вам вносить изменения.

Обновление источников данных

В качестве первого шага я хотел обновить данные, чтобы они отображали изображения и текст карточек, соответствующие моим навыкам. Таким образом, когда я начну обновлять сам макет, я смогу увидеть, как он будет выглядеть с моими фактическими изображениями. Инструмент Authoring Tool показывает визуализированные изображения на экране в режиме реального времени, что делает его удобным в использовании, когда вы пытаетесь улучшить свой макет. Чтобы обновить данные, я предпринял следующие шаги:

  • Перейдите на вкладку Data JSON.
  • В listTemplate2Metadata измените элементы title и logo на что-нибудь, относящееся к видеопокеру.
  • В этом же элементе измените url в полях backgroundImages.sources, чтобы он указывал на фоновое изображение, которое я хотел использовать.
  • В listTemplate2ListData.listPage я обновил каждый из элементов массива listItems. В частности, я обновил этот массив, чтобы он содержал 5 элементов (мои карты), с listItemIdentifier и token, установленными на «card.x» (где x находился в диапазоне от 0 до 4). Я удалил secondaryText, так как мне нужна была только одна строка текста (которая либо была бы пустой, либо говорила «HELD»). Я обновил image.sources, чтобы он указывал на URL-адреса, содержащие изображения моих карточек. На данный момент я просто случайным образом выбрал несколько изображений карточек - код моего навыка будет обновлять данные во время игры с фактической рукой пользователя.
  • Обновите текст подсказки, чтобы использовать преобразование. Вместо того, чтобы жестко кодировать строку подсказки ключевым словом Alexa, вы можете использовать преобразование, чтобы изменить подсказку в подсказку, которая использует слово пробуждения, связанное с устройством, в случае, если пользователь изменил его. Для этого нужно удалить hintText из listTemplate2ListData и добавить следующее в свой lastTemplate2Metadata:
"properties": {
    "hintText": "select number 1"
},
"transformers": [
    {
        "inputPath": "hintText",
        "transformer": "textToHint"
    }
],

После того, как я внес их в источники данных, мое изображение выглядело так - очень похоже на навык, существующий с отображением ListTemplate2, что имеет смысл, поскольку на данный момент я все еще использую макет, основанный на ListTemplate2, и создал только контент изменения.

Обновление макета

Теперь самое интересное - обновление макета для одновременного вывода пяти изображений на экран. В этой части я обновил макет на вкладке «Образец списка переадресации изображений». Чтобы внести эти изменения, я щелкнул ползунок, который позволяет переключаться между визуальным представлением вложенных компонентов APL и необработанным представлением JSON. Я считаю, что просмотр полного документа JSON проще для использования, чем щелчок по каждому компоненту и редактирование JSON внутри компонента. Но вы можете поиграть с этим и следовать тому подходу, который вам кажется наиболее естественным.

Прежде чем говорить о внесенных мною изменениях, я хотел бы указать на некоторые элементы этого шаблона макета:

  • В JSON есть узел ListTemplate2, который предоставляет два контейнера в узле items: один применяется к круглым экранам (например, Echo Spot), а другой - к другим типам экранов. В этом блоге я собираюсь сосредоточиться на дисплеях, отличных от спотовых, но вы должны понимать, что вы также можете вносить изменения, относящиеся к разным макетам экрана.
  • Глядя на второй контейнер, который мы будем изменять, вы увидите набор items, включая изображение (фоновое изображение), AlexaHeader (заголовок на экране), Последовательность (список карточек) и AlexaFooter (подсказка внизу экрана).
  • Вы увидите, что последовательность указывает на HorizontalListItem, который является еще одним контейнером в этом документе JSON. Он содержит элементы, состоящие из изображения и двух текстовых элементов (основной текст и дополнительный текст).

Имея в виду этот контекст, я внес в этот документ следующие изменения:

  • В HorizontalListItem я изменил размеры элемента изображения - в частности, я установил height на 40vh и width на 17vw. Это устанавливает высоту каждой карточки на 40% от высоты области просмотра и ширину на 17% от ширины области просмотра.
  • Затем я обновил midWidth до 100. Это уменьшает ширину каждого элемента списка и позволяет отображать пять изображений карточек на экране.
  • Я изменил paddingLeft и paddingRight на 6, чтобы уменьшить расстояние между элементами.
  • Я добавил paddingTop и установил его на 100, чтобы добавить некоторое разделение между заголовком и изображениями карточек.
  • Я избавился от второстепенного элемента Text, так как на моем дисплее нет двух строк текста
  • Я изменил основной текстовый элемент, чтобы он не отображал порядковый номер. Итак, text в этом элементе изменилось с <b>${ordinal}.</b>${data.textContent.priamryText.text} на <b>${data.textContent.primaryText.text}</b>.
  • В этом же элементе я хотел, чтобы текст был красным и по центру. Я добился этого, добавив поле textAlign со значением «центр» и поле color со значением «красный».
  • Чтобы получить текст подсказки из подходящего места (теперь часть метаданных, а не данных списка), мне нужно было обновить элемент AlexaFooter, чтобы получить подсказку от ${payload.listTemplate2Metadata.properties.hintText}

Наконец, мне нужно было сделать карточки в моем списке доступными для выбора, чтобы я мог реагировать, когда пользователь касается одной из них на экране. Для этого мне нужно было изменить элементы, связанные с элементом Sequence, с FullHorizontalListItem на TouchWrapper, который, в свою очередь, содержал FullHorizontalListItem. В коде это означает, что я изменил это:

"item": [
  {
    "type": "FullHorizontalListItem",
    "listLength": "${payload.listTemplate2ListData.listPage.listItems.length}"
  }
]

к этому:

"item": [
  {
    "type": "TouchWrapper",
    "onPress": {
      "type": "SendEvent",
      "arguments": [
        "${data.token}"
      ]
    },
    "item": {
      "type": "FullHorizontalListItem",
      "listLength": "${payload.listTemplate2ListData.listPage.listItems.length}"
    }
  }
]

Обратите внимание на элемент onPress в этом элементе. В частности, список аргументов. Вы можете указать массив различных аргументов для отправки вашему умению при выборе элемента. Поскольку мой существующий код обрабатывал токен выбранной карты, я решил продолжить делать то же самое, чтобы минимизировать количество кода, который мне нужно было изменить. Но вы также можете передать ${ordinal}, который сообщит вам индекс выбранного элемента без необходимости обрабатывать токен.

Обновления кода навыков

После внесения изменений в инструмент разработки вы можете нажать кнопку «Экспорт кода», которая упакует ваш макет и файлы данных в один файл JSON. Я решил использовать в своем коде два разных файла JSON, один из которых называется main, который я использовал для макета, а другой - источниками данных, которые я использовал для данных. Мне нравится разделять макет и контент в моем исходном коде в качестве общей передовой практики. Было удивительно, что Amazon Authoring Tool не поддержала и этого.

Теперь, когда мы загрузили документ и контент, нам нужно внести изменения в код, чтобы включить его и обновлять данные по мере того, как пользователь играет с нашими навыками. Мы можем сделать это, манипулируя элементами данных, а затем передавая их обратно в навык. Я использовал функцию перехватчика ответов Alexa (о которой я рассказываю в отдельном сообщении в блоге). Я загружаю источник данных из файла JSON, затем обновляю карточки и текст в структуре перед отправкой в ​​Alexa. Я делаю это с помощью следующего кода:

const main = require('./main.json');
const datasource = require('./datasource.json');
function drawTable(handlerInput) {
  const event = handlerInput.requestEnvelope;
  const attributes = handlerInput.attributesManager.getSessionAttributes();
  const game = attributes[attributes.currentGame];

  let i;
  let cardText;
  let url;
  // Update the images
  for (i = 0; i < game.cards.length; i++) {
    const card = game.cards[i];
    url = GetCardURL(card);
    cardText = (card.hold) ? 'HELD' : '';
    datasource.listTemplate2ListData.listPage.listItems[i]
      .textContent.primaryText.text = cardText;
    datasource.listTemplate2ListData.listPage.listItems[i]
      .image.sources[0].url = url;
    datasource.listTemplate2ListData.listPage.listItems[i]
      .image.sources[1].url = url;
  }

  // Give an appropriate hint
  if (game.state === 'FIRSTDEAL') {
      if (game.cards[0].hold) {
        datasource.listTemplate2ListData.hintText = 'discard the first card';
      } else {
        datasource.listTemplate2ListData.hintText = 'hold the first card';
      }
      datasource.listTemplate2Metadata.title = 'Select cards to hold or say Deal';
    } else {
      datasource.listTemplate2ListData.hintText = 'deal';      datasource.listTemplate2Metadata.title = 'Your last hand';
    }
  }

  return handlerInput.responseBuilder
    .addDirective({
      type: 'Alexa.Presentation.APL.RenderDocument',
      version: '1.0',
      document: main,
      datasources: datasource,
    });
}

Второе место, где мне пришлось изменить код, - это обработать пользователя, касающегося одного из элементов моего списка. В моем старом коде я анализировал токены элементов вида «card.x», где x - порядковый номер карты в списке. В интерфейсе дисплея это означало поиск ElementSelected request. В APL ваш код получит APL.Presentation.APL.UserEvent, и вы можете обработать запрос следующим образом, чтобы определить, какая карта была выбрана:

module.exports = {
  canHandle(handlerInput) {
    const request = handlerInput.requestEnvelope.request;
   
    // Was this a touch item selected?
    if (request.type === 'Alexa.Presentation.APL.UserEvent') {
      return ((request.source.type === 'TouchWrapper')
        && (request.source.handler === 'Press'));
    }
    return false;
  },
  handle(handlerInput) {
    let index;
    const event = handlerInput.requestEnvelope;

    // Was this a touch item selected?
    if (event.request.type === 'Alexa.Presentation.APL.UserEvent') {
      const cards = event.request.arguments[0].split('.');
      if (cards.length === 2) {
        index = cards[1];
      }
      
      // Do something with the selected card...
    }
  },
};

С этими изменениями я приобрел гораздо более чистый вид навыков видеопокера, который наверняка понравится моим клиентам больше, чем старый формат, который я использовал. Я только начал поверхностно разбираться в возможностях APL. Но я могу сказать, что это откроет новый мир возможностей для голосовых мультимодальных навыков! Дайте мне знать в комментариях о вашем собственном опыте работы с этой новой структурой.