Assistant Client — это инструмент для локального тестирования и отладки Сanvas App c виртуальным ассистентом. Он реализован в виде JavaScript протокола, который эмулирует среду Android и вызывает нативные методы. Такой подход не требует от разработчика наличия физических устройств и позволяет запустить виртуального ассистента через браузер.
Для работы с Assistant Client необходимо:
createSmartappDebugger
в параметре token
.Смартапы должны корректно отображаться на разных устройствах (SberBox, SberPortal и др). Для этого необходимо проверять смартап на следующих разрешениях: 559x568, 768x400, 959x400, 1920x1080. Настроить эти разрешения можно на вкладке Devices Chrome.
Для установки Assistant Client выполните следующую команду:
$ npm i @salutejs/client
// Функция createSmartappDebugger используется в development среде. В production среде необходимо использовать createAssistant.
import { createAssistant, createSmartappDebugger } from '@salutejs/client';
const initialize = (getState, getRecoveryState) => {
if (process.env.NODE_ENV === 'development') {
return createSmartappDebugger({
// Токен из Кабинета разработчика
token: 'token',
// Пример фразы для запуска смартапа
initPhrase: 'Хочу попкорн',
// Текущее состояние смартапа
getState,
// Состояние смартапа, с которым он будет восстановлен при следующем запуске
getRecoveryState,
// Необязательные параметры панели, имитирующей панель на реальном устройстве
nativePanel: {
// Стартовый текст в поле ввода пользовательского запроса
defaultText: 'Покажи что-нибудь',
// Позволяет включить вид панели, максимально приближенный к панели на реальном устройстве
screenshotMode: false,
// Атрибут `tabindex` поля ввода пользовательского запроса
tabIndex: -1,
},
});
}
// Только для среды production
return createAssistant({ getState, getRecoveryState });
}
...
const assistant = initialize(() => state, () => recoveryState);
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;
}
}
});
const handleOnClick = () => {
// Отправка сообщения ассистенту с фронтенд.
// Структура может меняться на усмотрение разработчика, в зависимости от бэкенд
assistant.sendData({ action: { action_id: 'some_action_name', parameters: { param: 'some' } } });
};
const handleOnRefreshClick = () => {
// Отправка сообщения бэкенду с возможностью подписки на ответ.
// В обработчик assistant.on('data') сообщение не передается
const unsubscribe = assistant.sendAction(
{ action_id: 'some_action_name', parameters: { param: 'some' } },
(data: { type: string; payload: Record<string, unknown> }) => {
// Обработка данных, переданных от бэкенд
unsubscribe();
},
(error: { code: number; description: string }) => {
// Обработка ошибки, переданной от бэкенд
});
}
Assitant Client доступен для подключения через <script>
.
Версию assistant сlient можно поменять в src. Доступ к API осуществляется через глобальную переменную assistant
.
Пример, для разработки и отладки в браузере:
<script src="https://unpkg.com/@salutejs/client@stable/umd/assistant.development.min.js"></script>
<script>
const client = assistant.createSmartappDebugger({
// Токен из Кабинета разработчика
token: 'token',
// Пример фразы для запуска смартапа
initPhrase: 'Хочу попкорн',
// Текущее состояние смартапа
getState: () => ({}),
// Состояние смартапа, с которым он будет восстановлен при следующем запуске
getRecoveryState: () => ({}),
});
</script>
Пример, для использования на устройствах:
<script src="https://unpkg.com/@salutejs/client@stable/umd/assistant.production.min.js"></script>
<script>
const client = assistant.createAssistant({ getState: () => ({}), getRecoveryState: () => ({}), });
</script>
createAssistant
Создает экземпляр AssistantClient
для запуска виртуального ассистента. Используется на устройствах в production среде.
Параметр | Обязательный | Описание |
---|---|---|
getState | Да | Функция, которая возвращает актуальное состояние смартапа |
getRecoveryState | Нет | Функция, которая сохраняет состояние смартапа на момент последнего закрытия |
createSmartappDebugger
Создает экземпляр AssistantClient
и добавляет на экран браузера панель с голосовым ассистентом (подобно устройствам). Панель ассистента находится в нижней части отрисованного экрана и позволяет отправлять виртуальному ассистенту следующие типы сообщений:
createSmartappDebugger
используется для локальной отладки и разработки в development среде.
Параметр | Обязательный | Описание |
---|---|---|
token | Да | Токен из SmartApp Studio |
initPhrase | Да | Фраза, которая запускает смартап |
getState | Да | Функция, которая возвращает актуальное состояние смартапа |
getRecoveryState | Нет | Функция, которая сохраняет состояние смартапа на момент последнего закрытия |
settings | Нет | Объект настроек ассистента |
nativePanel | Нет | Объект настроек панели ассистента |
surface | Нет | Строка, название поверхности. Возможные значения: SBERBOX (SberBox), STARGATE (SberPortal), SATELLITE (SberBox Top) SBOL (приложение СберБанк Онлайн), COMPANION (приложение Салют), TV (Салют ТВ), TV_HUAWEI (Huawei Vision), TIME (SberBox Time) |
Максимальная частота отправки сообщений при отладке приложения с помощью
createSmartappDebugger
— два запроса в секунду.При превышении ограничения ассистент ответит ошибкой:
{ "code": -1008, "description": "Виртуальные ассистенты тоже иногда ломаются. Предлагаю немного подождать, пока меня починят (код ошибки -1008)" }
Свойство | Значения | По умолчанию | Описание |
---|---|---|---|
dubbing | true / false | true | Озвучивание ответа ассистента |
nativePanel
Все свойства являются необязательными.
Свойство | Тип значения | По умолчанию | Описание |
---|---|---|---|
defaultText | string | Покажи что-нибудь | Стартовый текст в поле ввода пользовательского запроса |
screenshotMode | boolean | false | Позволяет включить вид панели, максимально приближенный к панели на реальном устройстве |
tabIndex | number | -1 | Атрибут tabindex поля ввода пользовательского запроса |
Опциональный метод. Вызывается со стороны смартапа для остановки озвучивания ответа (pronounceText). Имеет значение undefined, если устройство не поддерживает данную функцию.
Вызывается со стороны смартапа для завершения работы.
Возвращает данные, полученные при инициализации смартапа. Передает в Assistant Client текущего ассистента, а не ассистента, выставленного по умолчанию.
Если при запуске смартапа не вызвать команду getInitialData()
, то команды из appInitialData
будут отправляться в on('data')
.
Возвращает состояние, сохраненное при закрытии смартапа. Устройство запоминает последнее состояние, которое возвращает функция getRecoveryState
при инициализации Assistant Client.
Позволяет подписаться на любой тип команд, приходящих в смартап. Возвращает метод отписки.
Осуществляет подписку на событие готовности ассистента к работе.
Осуществляет подписку на событие получения данных с бэкенда. Если при запуске смартапа не была вызвана команда getInitialData()
, то получает команды из appInitialData
.
Выполняет подписку на события начала и окончания озвучки.
state
- состояние озвучки, начало или окончание.
owner
- флаг принадлежности озвучки текущему смартапу.
Передает ошибки и обработчики ответа от бэкенда.
sendAction
— отправляет server-action и типизирует сообщения data и error.
clear()
— делает отписку от сообщений бэкенда. Это означает, что сообщения не будут переданы в обработчик assistant.on('data')
Пример:
import { AssistantSmartAppCommand } from '@salutejs/client';
interface SomeBackendMessage extends AssistantSmartAppCommand['smart_app_data'] {
type: 'target_action',
payload: {
data: ['some_data'],
},
}
const unsubscribe = assistant.sendAction<SomeBackendMessage>({ action_id: 'some_action_name', parameters: { someParam: 'some_value' } },
({ payload }) => {
// обработка payload.data
unsubscribe();
}, (error) => {});
Отправляет события с фронтенда на бэкенд через ассистента.
Первый параметр (обязательный) принимает данные для отправки.
Второй параметр (опциональный) принимает обработчик ответа (на переданные первым параметром данные). В этом случае в on('data')
ответ не приходит.
Возвращает функцию, вызов которой отменяет обработчик ответа.
Пример с обработкой ответа:
...
const unsubscribe = assistant.sendData({ action: { action_id: 'some_action_name' } }, (data: command) => {
if (data.type === 'smart_app_data' && data.smart_app_data.type === 'target_action') {
unsubsribe();
... // обработка команды
}
});
Подменяет callback, который возвращает актуальное состояние смартапа.
Подменяет callback, который возвращает объект, доступный только при следующем запуске смартапа. Данные приходят при вызове getRecoveryState
.
AssistantAppState
Объект AssistantAppState
— текущее состояние смартапа, которое не хранится в платформе или сценарии. Каждый раз, когда пользователь начинает говорить, Assistant Client вызывает getState
, чтобы получить и передать в бэкенд состояние экрана пользователя.
То, что происходит на экране у пользователя и как он взаимодействует со смартапом в конкретный момент времени — ответственность смартапа. Assistant Client в данном случае — это буфер, который только передает состояние платформе или сценарию.
interface AssistantAppState {
// Любые данные, которые могут потребоваться в бэкенде для принятия решений
[key: string]: unknown;
item_selector?: {
ignored_words?: string[];
// Список соответствий между голосовыми командами и действиями в приложении
items: AssistantViewItem[];
};
}
interface AssistantViewItem {
// Уникальный (в рамках items) порядковый номер элемента, который назначается смартапом
number?: number;
// Уникальный id элемента
id?: string;
// Ключевая фраза, которая должна приводить к данному действию
title?: string;
// Фразы-синонимы, которые должны приводить к данному действию
aliases?: string[];
// Проксирование action обратно на бэкенд
server_action?: AssistantServerAction;
// Выполнение действия от имени пользователя
action?: Action | { type: string };
// Дополнительные данные для бэкенд
[key: string]: unknown;
}
Например, когда пользователь говорит «Покажи 1», бэкенд должен понимать, что скрывается за единицей (то есть, какой элемент у пользователя пронумерован этой цифрой). Ниже пример состояния, который позволяет понять бэкенду, что, называя «1», пользователь хочет чипсы.
{
item_selector: {
ignored_words: ["покажи"],
items: [
{ title: 'Сладкий попкорн' },
{ title: 'Соленый попкорн' },
{ title: 'Чипсы', number: 1 },
{ title: 'Начос', number: 2 },
{ title: 'Кола', number: 3 }
]
}
}
AssistantServerAction
Объект AssistantServerAction
— это любое сообщение, которое отправляется с фронтенда на бэкенд. Оно может быть привязано к ui-элементу и приходить с бэкенд, или формироваться самостоятельно фронтовой частью при обработке событий внутри WebView смартапа.
interface AssistantServerAction {
// Тип Server Action
action_id: string;
// Любые параметры
parameters?: Record<string, unknown>;
}
AssistantCharacterCommand
Объект AssistantCharacterCommand
— информирует смартап о текущем персонаже (Сбер, Афина или Джой). Персонаж может быть изменен в любой момент по инициативе пользователя. Поэтому разработчик может дополнительно добавить обработку таких изменений.
interface AssistantCharacterCommand {
type: "character";
character: {
id: "sber" | "eva" | "joy";
};
sdk_meta: {
requestId: string;
};
}
AssistantNavigationCommand
Объект AssistantNavigationCommand
— команда навигации пользователя по смартапу (вперед, назад, дальше и т. д.). В платформе виртуального ассистента есть стандартные фразы, которые приходят и обрабатываются одинаково для всех смартапов.
interface AssistantNavigationCommand {
// Тип команды
type: "navigation";
// Навигационная команда (направление навигации)
navigation: { command: "UP" | "DOWN" | "LEFT" | "RIGHT" | "FORWARD" };
sdk_meta: {
requestId: string;
};
}
AssistantInsetsCommand
Объект AssistantInsetsCommand
— команда, которая сообщает смартапу о том, что поверх него будет отображен нативный UI и его размеры (например, высота шторки с КПСС, саджесты, или клавиатура). Размеры нужно соблюдать, чтобы не было наложения нативных UI элементов и контента смартапа.
insets
— отступы от краев экрана;dynamic_insets
— размеры нативного UI;minimum_static_insets
— минимальные значения размеров нативного UI;maximum_static_insets
— максимальные значения размеров нативного UI.interface AssistantInsetsCommand {
type: 'insets' | 'dynamic_insets' | 'minimum_static_insets' | 'maximum_static_insets';
insets: {
left: number; // px
top: number; // px
right: number; // px
bottom: number; // px
};
}
AssistantThemeCommand
Объект AssistantThemeCommand
- команда, которая сообщает смартапу текущую тему платформы — тёмная или светлая. По умолчанию нужно использовать тёмную тему.
interface AssistantThemeCommand {
type: 'theme';
theme: {
name: 'dark' | 'light'
}
}
AssistantVisibilityCommand
Объект AssistantVisibilityCommand
— команда, которая сообщает о состоянии приложения: развернуто оно или нет. Параметр hidden
указывает, что разработчику навыка необходимо остановить звук и/или видео. Параметр visible
указывает, что воспроизведение звука и/или видео можно продолжить.
interface AssistantVisibilityCommand {
type: 'visibility';
visibility: 'visible' | 'hidden';
}
AssistantSmartAppError
Объект AssistantSmartAppError
— это уведомление об ошибке.
interface AssistantSmartAppError {
type: 'smart_app_error';
smart_app_error: {
code: number;
description: string;
};
}
AssistantSmartAppCommand
Объект AssistantSmartAppCommand
— это команда передачи смартапу любых данных с бэкенда.
interface AssistantSmartAppCommand {
// Тип команды
type: "smart_app_data";
smart_app_data: {
type: string;
// Любые данные, которые нужны смартапу
payload: Record<string, unknown>;
};
sdk_meta: {
requestId: string;
};
}
Для получения и обработки нажатия кнопок на пульте от SberBox необходимо подписаться на события нажатия клавиш клавиатуры. Пример ниже:
window.addEventListener('keydown', (event) => {
switch(event.code) {
case 'ArrowDown':
// вниз
break;
case 'ArrowUp':
// вверх
break;
case 'ArrowLeft':
// влево
break;
case 'ArrowRight':
// вправо
break;
case 'Enter':
// ок
break;
}
});
Для корректной обработки кнопки back
и навигации по страницам смартапа необходимо построить историю переходов, используя History API
. Например, подписываемся на window.onpopstate
и реализуем изменение страницы в обработчике этого события. Когда хотим выполнить изменение страницы, вызываем window.history.pushState
:
const [page, setPage] = useState<string>('previous');
const handleNext = () => {
window.history.pushState({ page: 'next' }, ''); // инициируем переход на следующую страницу
}
useEffect(() => {
window.history.replaceState({ page: 'previous' }, ''); // устанавливаем текущую страницу
window.onpopstate = ({ state }) => {
setPage(state.page); // выполняем переход на заданную страницу: next - по вызову handleNext или previous - по нажатию кнопки back
}
}, []);
Для имитации команд от ассистента используйте утилиту createAssistantHostMock
. Ниже приведен пример использования.
import { createAssistantHostMock } from '@salutejs/client';
const ITEMS = [
{
id: 1,
title: 'Купить молоко',
number: 1,
},
{
id: 2,
title: 'Купить хлеб',
number: 2,
},
];
describe('Мой список дел', () => {
it('По клику на чекбокс - ожидаем экшен "done" c заголовком выбранного элемента', (done) => {
cy.visit('/')
.window()
.then((window) => {
const mock = createAssistantHostMock({ context: window });
const selected = ITEMS[1];
mock.onReady(() => {
// эмулируем инициализационную команду от бэкенда со списком задач
mock.receiveCommand({
type: 'smart_app_data',
action: {
type: 'init',
notes: [...ITEMS],
},
})
.then(() =>
// ожидаем вызов assistantClient.sendData
mock.waitAction(() =>
// эмулируем отметку выполнения пользователем, который должен вызвать sendData({ action: { type: 'done } })
window.document.getElementById(`checkbox-note-${selected.id}`).click(),
),
)
.then(({ action, state }) => {
expect(action.type).to.equal('done'); // ожидаем экшен data_note
expect(action.payload?.title).to.equal(selected.title); // ожидаем в параметрах title экшена
expect(state?.item_selector.items).to.deep.equal(ITEMS); // ожидаем отправку списка в стейте
done();
});
});
});
});
});
createAssistantHostMock
можно вызывать только при использовании createAssistant
. Например, при использовании cypress
функция инициализации ассистента может выглядеть следующим образом:
import { createAssistant, createSmartappDebugger } from '@salutejs/client';
const initializeAssistant = (getState: AssistantAppState) => {
if (process.env.NODE_ENV === 'development' && window.Cypress == null) {
return createSmartappDebugger({
token: process.env.REACT_APP_TOKEN ?? '',
initPhrase: `Запусти ${process.env.REACT_APP_SMARTAPP}`,
getState,
});
}
if (window.cypress) {
window.appInitialData = [];
}
return createAssistant({ getState });
};
Подписка на экшены фронтенда с определенным type, который передается первым параметром.
Отмена подписки от экшенов фронтенда.
Эмуляция команды, полученной от бэкенда. Команда приходит подписчикам AssistantClient.onData
.
Получение promise
, который будет разрезолвлен при следующем вызове AssistantClient.sendData
Подписка на события готовности утилиты. Параметр cb
будет вызван по готовности к работе.
В режиме разработки есть возможность записать и скачать лог сообщений.
Управление записью осуществляется кнопками start
и stop
. Кнопка save
сохранит файл с логом в загрузки браузера. Пример активации панели управления записью лога:
import { createSmartappDebugger } from '@salutejs/client';
const assistant = createSmartappDebugger({
token: process.env.REACT_APP_TOKEN ?? '',
initPhrase: `Запусти ${process.env.REACT_APP_SMARTAPP}`,
getState,
enableRecord: true, // активируем функцию записи лога
recordParams: {
defaultActive: true, // включать запись при старте приложения (по-умолчанию = true)
}
});
Пример пошагового воспроизведения лога сообщений. Входящие сообщения от ассистента будут последовательно переданы подписчикам AssistantClient.on('data')
.
import { createRecordPlayer } from '@salutejs/client';
import assistantLog from './assistant-log.json';
const player = createRecordPlayer(assistantLog);
let end = false;
while(!end) {
end = !player.continue();
}
Передает следующее сообщение от ассистента в AssistantClient (может содержать несколько команд). Возвращает флаг наличия в логе следующих сообщений от ассистента.
Последовательно передает все сообщения лога от ассистента в AssistantClient.
Возвращает следующее сообщение от AssistantClient (вызов sendData
) в ассистент. Можно использовать для сравнения эталонного сообщения (из лога) и текущего в тесте.
Загружает указанную запись в плеер.
Нужно перейти в настройки сайта и разрешить доступ к звуку и микрофону.