Русский
Русский
English
Статистика
Реклама

Навыки для виртуальных ассистентов на веб-технологиях

Недавно Cбер запустил Салют семейство виртуальных ассистентов, которые работают на разных платформах. Мы в SberDevices, кроме самого ассистента, занимаемся разработкой инструментов, позволяющих любому разработчику удобно создавать навыки, которые называются смартапы. Кроме общеизвестных диалоговых сценариев в формате чата ChatApp, можно создавать смартапы в формате веб-приложения на любых известных веб-технологиях Canvas App. О том, как создать простейший смартап такого типа, и пойдет сегодня речь.

Canvas App стандартное веб-приложение в привычном понимании, которое запускается и работает внутри WebView, но есть свои особенности.

Как это работает по шагам:

  1. Пользователь произносит ключевую фразу, например Салют, какие у меня задачи на сегодня.

  2. Голосовой запрос приходит в NLP-платформу, где разбирается на фонемы, там же определяется его эмоциональный окрас и т.д..

  3. В БД зарегистрированных смартапов находится тот, которому соответствует активационная фраза. Регистрация происходит через SmartApp Studio и доступна всем разработчикам без исключения.

  4. Во время регистрации смартапа в SmartApp Studio разработчик указывает два эндпоинта: один для веб-приложения, второй для сценарного бэкенда. Именно их достанет из БД NLP-платформа, когда найдет соответствующий смартап.

  5. В эндпоинт сценарного бэкенда будет отправлено сообщение с распознанной активационной фразой. Формат сообщений подробно описан в документации SmartApp API.

  6. Эндпоинт веб-приложения будет указан для загрузки в WebView.

  7. Ответ от сценарного бэкенда придёт в веб-приложение в качестве JS-события, подписавшись на которое, можно управлять веб-приложением.

Упрощенная схема для наглядностиУпрощенная схема для наглядности

Предмет нашего разговора веб-приложение. Делать будем смартап для ведения тудушек. Поскольку SmartApp Studio предоставляет онлайн-среду разработки сценариев, не будем подробно на этом останавливаться, а воспользуемся форком готового сценария, который в качестве примера доступен на GitHub. В одной из следующих статей расскажем, как написать такой сценарий на NodeJS.

В SmartApp Graph/IDE, той самой онлайн-среде, в качестве источника можно указать git-репозиторий, чем мы и воспользуемся, чтобы получить эндпоинт до сценарного бэкенда. Далее его надо указать при регистрации нашего смартапа в SmartApp Studio. В качестве эндпоинта веб-приложения укажем любой известный веб-ресурс, например, sberdevices.ru. Позже поменяем на URL нашего веб-приложения.

Шаблон проекта

Для примера будем делать веб-приложение на React. К React нет никакой привязки и пример ниже может быть написан на чём угодно. Для нетерпеливых выложили конечный результат на GitHub.

Итак, что мы хотим от приложения:

  • добавлять задачи;

  • выполнять задачи;

  • удалять задачи;

  • и все это голосом, но не сразу.

Для создания базового проекта воспользуемся CRA.

> npx create-react-app todo-canvas-app

Для реализации UI нам понадобится как минимум пара компонентов и форма.

Код формы
export const App: FC = memo(() => {  const [note, setNote] = useState("");  return (    <main className="container">      <form        onSubmit={(event) => {          event.preventDefault();          setNote("");        }}      >        <input          className="add-note"          type="text"          value={note}          onChange={({ target: { value } }) => setNote(value)}        />      </form>      <ul className="notes">        {appState.notes.map((note, index) => (          <li className="note" key={note.id}>            <span>              <span style={{ fontWeight: "bold" }}>{index + 1}. </span>              <span                style={{                  textDecorationLine: note.completed ? "line-through" : "none",                }}              >                {note.title}              </span>            </span>            <input              className="done-note"              type="checkbox"              checked={note.completed}            />          </li>        ))}      </ul>    </main>  );});

Дальше нам надо сделать базовую логику нашего приложения. Пользоваться будем стандартными средствами React, используя useReducer.

Код редьюсера
const reducer = (state, action) => {  switch (action.type) {    case "add_note":      return {        ...state,        notes: [          ...state.notes,          {            id: Math.random().toString(36).substring(7),            title: action.note,            completed: false,          },        ],      };    case "done_note":      return {        ...state,        notes: state.notes.map((note) =>          note.id === action.id ? { ...note, completed: !note.completed } : note        ),      };    case "delete_note":      return {        ...state,        notes: state.notes.filter(({ id }) => id !== action.id),      };    default:      throw new Error();  }};

Далее будем диспатчить экшены их обработчиков на форме.

Код подключения
export const App: FC = memo(() => {  const [appState, dispatch] = useReducer(reducer, { notes: [] });  //...  return (    <main className="container">      <form        onSubmit={(event) => {          event.preventDefault();          dispatch({ type: "add_note", note });          setNote("");        }}      >        <input          className="add-note"          type="text"          placeholder="Add Note"          value={note}          onChange={({ target: { value } }) => setNote(value)}          required          autoFocus        />      </form>      <ul className="notes">        {appState.notes.map((note, index) => (          <li className="note" key={note.id}>            <span>              <span style={{ fontWeight: "bold" }}>{index + 1}. </span>              <span                style={{                  textDecorationLine: note.completed ? "line-through" : "none",                }}              >                {note.title}              </span>            </span>            <input              className="done-note"              type="checkbox"              checked={note.completed}              onChange={() => dispatch({ type: "done_note", id: note.id })}            />          </li>        ))}      </ul>    </main>  );});

Запускаем и проверяем.

npm start

Работа с голосом

Когда наше приложение базово работает, можно добавить немного магии голосового управления. Для этого надо установить Assistant Client библиотеку для взаимодействия с виртуальным ассистентом.

npm i @sberdevices/assistant-client

В момент открытия WebView платформа инжектит JS API для взаимодействия с ассистентом. Это биндиги до нативных методов платформы. Assistant Client обёртка, которая в дев-режиме позволяет отлаживать взаимодействие с ассистентом в браузере, а в продакшене предоставляет удобный для веб-приложений API.

Идём в app.js и там же, где наш основной редюсер, создаем инстанс Assistant Client.

const initializeAssistant = () => {  if (process.env.NODE_ENV === "development") {    return createSmartappDebugger({      token: process.env.REACT_APP_TOKEN ?? "",      initPhrase: `Запусти ${process.env.REACT_APP_SMARTAPP}`,    });  }  return createAssistant();};

Судя по коду выше, нужен некий токен. Токен обеспечивает авторизацию сообщений в NLP-платформе. Токен автоматически приклеивается к сообщениям, когда смартап запускается на устройстве, но в нашем случае это браузер, поэтому токен надо передать вручную. Токен генерируется автоматически для каждого разработчика в SmartApp Studio.

После этого перезапустим наше приложение. Теперь мы видим панельку ассистента с лавашаром и текстовым полем. Лавашар это такое визуальное представление ассистента. По нажатию на лавашар включится микрофон и вы сможете отправить команду ассистенту так же, как вы бы это сделали, запуская смартап на устройстве. Относитесь к этому не как к эмулятору, а как к дев-тулзам, в продакшене всё это за нас будет делать платформа. Те же самые команды вы можете посылать не только голосом, но и текстом, используя текстовое поле рядом с лавашаром, чтобы не будить своих домашних по ночам.

Ассистент присылает структурированные команды в формате JSON. Полное описание формата можно найти в документации Assistant Client на GitHub.

interface AssistantSmartAppCommand {  // Тип команды  type: "smart_app_data";  // Любые данные, которые нужны смартапу  smart_app_data: Record<string, any>;  sdkMeta: {    requestId: string;  };}

Теперь подпишем наши экшены на команды от ассистента. Для этого в коде нашего сценария определены специальные интенты ключевые слова в фразах, которые может говорить пользователь. Разные интенты генерируют разные команды веб-приложению.

export const App: FC = memo(() => {  const [appState, dispatch] = useReducer(reducer, { notes: [] });  const [note, setNote] = useState("");  const assistantRef = useRef();  useEffect(() => {    assistantRef.current = initializeAssistant();    assistantRef.current.on("data", ({ action }) => {      if (action) {        dispatch(action);      }    });  }, []);    // ...

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

Дело в том, что ваш сценарий сам по себе только лишь по фразе пользователя не может узнать то, что у вас сейчас на экране. Чтобы эта магия работала, к каждому голосовому запросу необходимо клеить стейт веб-приложения. Тут мы приходим к осознанию, что сценарный бэкенд получает на вход не только разобранную фразу, но и данные с экрана стейт. Задача сценария провести пользователя к следующему шагу по этим двум параметрам, отправив команду веб-приложению на изменение стейта. Можно мыслить себе это как голосовой аналог клика. Разница лишь в том, что элемент управления для такого клика в интерфейсе может и не существовать физически. Например, если бы мы делали интернет-магазин, то кнопку добавления в корзину можно было бы и опустить в пользу голосовой команды Афина, добавь в корзину красные туфли.

Для того, чтобы это было удобно делать из веб-приложения, в Assistant Client есть API для передачи состояния getState. В нашем случае стейт это список тудушек и некоторая мета-информация.

Дополним код инициализации Asisstant Client.

const initializeAssistant = (getState) => {  if (process.env.NODE_ENV === "development") {    return createSmartappDebugger({      token: process.env.REACT_APP_TOKEN ?? "",      initPhrase: `Запусти ${process.env.REACT_APP_SMARTAPP}`,      getState,    });  }  return createAssistant({ getState });};

И передадим стейт в обработку ассистенту. Формат стейта также описан в документации Asisstant Client.

export const App: FC = memo(() => {  // ...  const assistantStateRef = useRef<AssistantAppState>();// ...  useEffect(() => {    assistantRef.current = initializeAssistant(() => assistantStateRef.current);    // ...  }, []);  useEffect(() => {    assistantStateRef.current = {      item_selector: {        items: appState.notes.map(({ id, title }, index) => ({          number: index + 1,          id,          title,        })),      },    };  }, [appState]);  // ...

Из кода выше видим появление мета-информации в виде нумерации. Зачем? Согласитесь, тудухи могут быть довольными длинными и иногда удобнее было бы говорить Джой, я сделал первую задачу вместо полного заголовка. Но погодите, как это работает? Где единичка превращается в первую? Эту магию кастования натуральных фраз, которые мы привыкли использовать в повседневной речи, в машинный формат делает за нас NLP-платформа. То же самое происходит, например, с командами навигации.

Тудух может скопиться достаточное количество, чтобы они не влезли в экран. Само собой, мы хотим уметь скроллить экран, чтобы иметь возможность прочитать всё, что скопилось. На устройствах, где нет тач-интерфейса, например, на SberBox, мы можем скроллить пультом ДУ или голосом. Нажатия кнопок на пульте превращаются в события нажатий на стрелки клавиатуры на window, но что делать с голосом?

Голосовые паттерны навигации встроены в NLP-платформу, и разработчику сценария ничего не надо делать самому. А для разработчика веб-приложения достаточно подписаться на специальный тип команд, приходящих от ассистента через Assistant Client. Все вариации навигационных фраз будут кастится в конечное число навигационных команд. Их всего пять: UP, DOWN, LEFT, RIGHT, BACK.

assistant.on('data', (command) => {    if (command.navigation) {        switch(command.navigation.command) {            case 'UP':                window.scrollTo(0, 0);                break;            case 'DOWN':                window.scrollTo(0, 1000);                break;        }    }});

Перезапускаем наше приложение и пробуем после нажатия на лавашар сказать: Напомни купить коту корм. И вуаля!

Если у вас есть устройство под рукой, то можно проверить работу смартапа на нём. Для этого не обязательно его публиковать или деплоить куда-либо. Достаточно создать тоннель с локального хоста, например, с помощью ngrok.

ngrok http 3000

Полученный URL с https указываем в SmartApp Studio, сохраняем черновик и говорим ассистенту: Сбер, какие у меня задачи на сегодня?. Это cработает, если вы залогинены под одним и тем же SberID на устройстве и в SmartApp Studio. Черновики по-умолчанию доступны к запуску на устройствах разработчика.

Вместо эпилога

Смысл статьи в наглядной демонстрации того, как голосовое управление прозрачным образом можно интегрировать не только в специально для этого созданные приложения, но и в уже существующие. Например, если у вас уже есть рабочий веб-сервис, то научить его работать на платформе Салют не составит большого труда.

Этот короткое интро скорее, обзор возможностей на искусственном примере. Как сделать смартап с компонентами, оплатой, автотестами, обязательно расскажем в следующих статьях. Спасибо за внимание, ещё увидимся!

По всем вопросам разработки смартапов можно обращаться в сообщество разработчиков SmartMarket в телеграмме.

Источник: habr.com
К списку статей
Опубликовано: 28.12.2020 14:06:07
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Блог компании сбер

Блог компании sberdevices

Open source

Javascript

Голосовые интерфейсы

Голосовые ассистенты

React

Веб-разработка

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru