Перейти к содержанию

Интерфейс приложения

Что стоит реализовать в мобильном клиенте, в порядке приоритета, и что произойдёт, если пункт пропустить. Механика кадров — в Протоколе v1.

Обязательный минимум

Живое соединение по WebSocket, лента сообщений и ввод текста.

  • Делать: подключение с hello, лента ответов (message), поле ввода → text с постоянным id, индикатор связи, переподключение с паузой.
  • Если не делать: приложение не работает как чат. Без переподключения любой сбой сети (мобильный интернет, сон устройства) навсегда «зависает» — лента перестаёт отвечать, а пользователь думает, что агент сломался.

Кнопки карточек

Кадр prompt несёт text и options[{id,label,style}]. Для exec_approval сервер дополнительно шлёт поля command (точная команда) и description — покажите команду рядом с кнопками, чтобы человек видел, что одобряет. Нажатие → один кадр prompt с prompt_id и option_id; сервер подтверждает prompt_done (или prompt_done{expired:true}). Карточку может закрыть только то устройство, которому она показана: ответ с другого соединения получает forbidden.

Типы карточек:

  • exec_approval — одобрение опасной команды. options — once / session / always / deny. Рядом с кнопками показывайте command и description.
  • slash_confirm — подтверждение слэш-команды: once / always / cancel.
  • clarify — уточняющий вопрос: c0, c1, … плюс other («свой ответ», вместе с text).

Оформление: style — primary (основная кнопка), danger (опасная), пусто — обычная.

  • Если не делать: агент вынужден спрашивать текстом, а пользователю — печатать /approve и подобное вручную. Опасные действия либо не проходят, либо проходят менее осознанно; уточнения приходится вводить вручную. Ответ на карточку не дублируйте обычным text — иначе он уйдёт агенту как лишнее сообщение.

Валидация на сервере

Не подставляйте option_id наугад: сервер сам заменит недопустимое значение на безопасное (deny / cancel) и вернёт фактический выбор в prompt_done.option_id.

Медиа в обе стороны

Отправка (вверх). sendImage / sendDocument / sendVoice / sendMedia. Кадры media, kind ∈ image/video/document/audio. Файл ≤ 8 МиБ — одним кадром; крупнее — кусками по ~3 МиБ с постоянным id (SDK делает это сам).

Приём (вниз). Кадры media от агента (image/voice/video/document/animation): собирайте data по id, показывайте/сохраняйте при final: true; если есть url — качайте по ссылке.

  • Если не делать: пользователь не может прислать фото/файл/PDF агенту, а сгенерированные агентом картинки, отчёты и голосовые просто исчезают. Не собирая куски по id, вы получите битые файлы для всего крупнее 3 МиБ.

Потоковая выдача (правка по id)

Если на стороне агента включён стриминг, ответ приходит целиком, а затем правится кадрами message с тем же id и полем edit: true. Приложение должно хранить сообщения по id и при edit: true перерисовывать существующее, а не добавлять новое.

{"t":"message","id":"r1","text":"Считаю","edit":true,"chunk":{"index":0,"total":1},"final":false}
{"t":"message","id":"r1","text":"Считаю ответ…","edit":true,"chunk":{"index":0,"total":1},"final":true}
  • Если не делать: каждый кадр правки покажется как новое сообщение — в ленте появятся дубли («Считаю», «Считаю ответ…» отдельными пузырями). Текст при этом не теряется: если edit_message не удался, шлюз дошлёт хвост обычным message.

Аватар

Аватар — HTML-страница в WebView, реализующая контракт window.hermesAvatar:

window.hermesAvatar = {
  setState:  (state) => {},  // 'idle' | 'thinking' | 'speaking'
  speak:     (text)  => {},  // просьба озвучить/показать реплику агента
  setVolume: (level) => {},  // уровень микрофона при записи, 0..1
};

URL страницы приходит в кадре ready (avatar_url; задаётся MOBILE_AVATAR_URL). Если URL пуст — используйте свою страницу (в examples/react-native/avatarHtml.ts есть минимальная с тем же контрактом). Состояния берите из кадров avatar (idle/thinking/speaking).

  • Если не делать: «живости» нет — пользователь не видит, что агент думает/говорит. Это необязательный пункт: без аватара протокол работает полностью, но пропадает обратная связь во время долгого ответа (частично её закрывает индикатор typing).

Голос

Запись WAV (16 кГц, моно, PCM 16 бит) → кадр audio с format: "wav" и data в base64. Если в приложении есть своё распознавание — заполните text, тогда агент получит и текст, и аудио; иначе Hermes распознает речь сам (при настроенном STT на машине агента). Голосовое от агента приходит кадром media с kind: "voice".

  • Если не делать: нет голосового ввода и озвучки ответов. Не используйте неупакованные форматы и частоту выше 16 кГц: распознавание ожидает WAV 16 кГц — иначе качество распознавания падает, а запись «съедает» лимит 8 МиБ быстрее.

Локальная история

Кэшируйте ленту на устройстве, чтобы при запуске и при обрыве сети пользователь видел последние сообщения, а id уже отправленных сообщений сохранялись.

  • Если не делать: пустой экран при каждом запуске, а после повторной отправки после обрыва — дубли. Источник истины по истории всё равно Hermes (диалог не теряется на сервере), но локальный кэш нужен для приятного UX и для идемпотентности (повтор с тем же id сервер игнорирует).

Сводка: что будет, если пропустить

Пункт Без него
Переподключение связь не восстанавливается после сбоя сети
Кнопки карточек одобрение/clarify/подтверждение приходится печатать текстом
Медиа в обе стороны файлы и картинки «теряются»; крупные — битые
Правка по id дубли в ленте при стриминге
Аватар нет обратной связи во время долгого ответа (некритично)
Голос нет голосового ввода и озвучки
Локальная история пустой экран при запуске, дубли после обрыва