Ассистент в Android и iOS SDK

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

Вам нужно сделать три части интерфейса: кнопку входа, товарную карусель и обработчик переходов. Экран чата, отправку сообщений, хранение текущего диалога и отправку событий ассистента берёт на себя Gravity SDK.

Шаг 1. Подготовьте SDK и раздел магазина

До написания кода ассистента проверьте следующие условия. Настройки кабинета может подготовить ваша команда маркетинга или менеджер Gravity Field; разработчику нужны их значения и опубликованные кампании.

Что должно быть готово Что получить или проверить Инструкция
Базовый SDK SDK установлен и инициализирован один раз; известны API-ключ и section магазина. Android SDK, iOS SDK, API-ключи
Идентификация Передаются просмотры экранов и события входа пользователя; SDK получает user.uid и сессию. Идентификация пользователей и раздел идентификации в руководстве вашей платформы
Shopping Assistant Подключён тот же раздел, чей ID указан в section SDK; сервис допускает обращения мобильного приложения. Что требуется для Shopping Assistant
Товарный каталог Фид синхронизирован, SKU и данные карточек корректны. Требования к товарному фиду
Ответы ассистента Тестовый вопрос в кабинете возвращает ожидаемые ответы и товары. Превью ассистента

Отдельно задавать пользователя для чата не нужно. Используйте обычную идентификацию Gravity SDK. Правила обмена section, uid и threadId описаны ниже.

Шаг 2. Настройте две API-кампании

Кампания возвращает настройки интерфейса, которые SDK получает по selector — строковому имени кампании. В этой интеграции используются два разных selector:

Кампания Selector в примерах Что управляется из кабинета Кто читает JSON
Кнопка входа assistant-entry Показывать ли кнопку на экране и как её подписать. Приложение через getContentBySelector.
Ассистент shopping-assistant Включён ли чат, его заголовок, приветствие и стартовые подсказки. SDK внутри prepareAssistant.

Имена в таблице — пример. Задайте свои имена в кабинете и передайте те же строки в приложение, без изменения регистра или пробелов.

Создайте кампании в кабинете

Для каждой строки таблицы:

  1. Откройте Campaigns → API Campaigns и создайте кампанию типа Custom JSON.
  2. Укажите её API-селектор.
  3. Добавьте Experience — сценарий с условиями показа. Для первого теста выберите условия, которым соответствует ваш тестовый пользователь и экран.
  4. Создайте вариацию и вставьте подходящий JSON из примеров ниже в редактор её содержимого.
  5. Активируйте и сценарий, и кампанию, затем нажмите Опубликовать.

Пошаговые экраны кабинета: Создание API-кампании. Если терминология незнакома: кампания → сценарий → вариация. Условия выбора настраиваются в таргетинге и расписании; редактируемые тексты — через переменные вариаций.

JSON кнопки входа

Для assistant-entry:

{ "enabled": true, "title": "Спросите помощника" }
  • enabled: true разрешает приложению подготовить ассистента на этом экране.
  • title — подпись кнопки приложения. Её положение, иконку и внешний вид задаёте вы.
  • При enabled: false, отсутствии данных, некорректном JSON или ошибке запроса кнопка остаётся скрытой.

JSON ассистента

Для shopping-assistant:

{
  "enabled": true,
  "title": "Помощник магазина",
  "subtitle": "Помогу выбрать подходящий товар.",
  "suggests": ["Помогите выбрать подарок"]
}
Поле Назначение Если не задано
enabled Включает ассистента; тип Boolean. Обязательное поле.
title Заголовок внутри чата; заданная строка должна быть непустой. "Помощник"
subtitle Приветствие на стартовом экране. Пустая строка.
suggests Массив непустых строк. Нажатие отправляет выбранную строку как сообщение пользователя. Пустой массив.

При enabled: false остальные поля не используются, результат подготовки — unavailable(disabled). Неизвестные поля игнорируются. Несколько разных конфигураций для одного selector — invalid_configuration.

В редактор вставляется JSON-объект из примера, без внешней обёртки custom. В ответе SDK этот JSON находится строкой в CampaignContent.custom.json. Стартовые подсказки приходят из этой кампании; подсказки после ответа — из response.ui.suggests. Подготовка не отправляет скрытое сообщение ассистенту.

Шаг 3. Подключите товарный блок и переходы

До первой подготовки зарегистрируйте два объекта приложения:

Что реализовать Минимальная обязанность Контракт
AssistantProductBlockBuilder Нарисовать заголовок и горизонтальную карусель на переданную ширину; сообщать SDK о видимости и нажатии товара. Товарный блок
AssistantActionHandler Проверить поддержку действия и открыть карточку товара, поддержку или URL средствами приложения. Действия и навигация
GravitySDK.instance.setAssistantProductBlockBuilder(productBlockBuilder)
GravitySDK.instance.setAssistantActionHandler(actionHandler)
GravitySDK.instance.setAssistantProductBlockBuilder(productBlockBuilder)
GravitySDK.instance.setAssistantActionHandler(actionHandler)

Здесь productBlockBuilder и actionHandler — ваши реализации интерфейсов, приведённых ниже. В builder можно использовать существующий компонент карточки магазина. Он получает товары от SDK: отдельный запрос каталога для заполнения карусели не требуется.

При нажатии карточка сообщает tap SDK. Затем SDK учитывает клик, закрывает чат и вызывает ваш handler. В handler выполните переход по SKU или URL. Не делайте второй переход прямо из карточки. Для поддержки обработайте OpenSupportChat, для ссылки — FollowUrl; неподдержанное действие возвращает false из canHandle.

Шаг 4. Покажите кнопку после подготовки

На каждом экране, где нужен ассистент:

  1. Сразу скройте кнопку предыдущего экрана и передайте trackView(pageContext).
  2. Вызовите getContentBySelector("assistant-entry", pageContext).
  3. Прочитайте JSON точки входа. Если она включена, вызовите prepareAssistant("shopping-assistant", pageContext).
  4. Только после ready для текущего экрана покажите кнопку с entry.title.
  5. При нажатии вызовите openAssistant с видимой Activity или UIViewController.

PageContext должен описывать экран, с которого открывается чат: например, PRODUCT и SKU для карточки товара или CATEGORY и данные категории для каталога. Поля и примеры: PageContext Android, PageContext iOS, правила контекста API V2. Не меняйте регистр SKU, категорий и региона относительно фида.

prepareAssistant не заменяет trackView и не отправляет его повторно. Получение данных кампании само по себе не показывает кнопку: отрисовка точки входа остаётся в приложении.

Как прочитать настройку точки входа

Ответ getContentBySelector содержит выбранный контент по пути:

ContentResponse.data[]
  → campaign.payload[]
    → variation.contents[]
      → content.custom.json  // строка с JSON, которую нужно разобрать

Для точки входа используйте один выбранный контент своей кампании. Не объединяйте вариации разных кампаний. parseEntry в примере ниже — функция приложения: она разбирает эту строку и возвращает enabled и title; отсутствие или ошибка разбора означает, что кнопка не показывается. JSON второго selector приложение не разбирает — это делает prepareAssistant.

Полный порядок с защитой от смены экрана

Это псевдокод управления экраном. generation, задача экрана и видимость кнопки принадлежат приложению. В Kotlin используйте lifecycleScope/Job, в Swift — Task на MainActor. Поколение меняется до запроса точки входа, а не только перед подготовкой.

entrySelector = "assistant-entry"
assistantSelector = "shopping-assistant"

onScreenShown(pageContext):
    generation += 1
    cancel(previousScreenTask)
    myGeneration = generation
    hideEntryButton()
    sdk.trackView(pageContext)

    previousScreenTask = launch:
        entryResponse = await sdk.getContentBySelector(entrySelector, pageContext)
        if taskCancelled or myGeneration != generation: return
        entry = parseEntry(entryResponse) // custom.json; ошибка/нет данных -> disabled
        if not entry.enabled: return

        result = await sdk.prepareAssistant(assistantSelector, pageContext, timeout = 10s)
        if taskCancelled or myGeneration != generation: return
        if result == ready: showEntryButton(entry.title)

onScreenHiddenOrIdentityChanged:
    generation += 1
    cancel(previousScreenTask)
    hideEntryButton()

onEntryTap:
    result = sdk.openAssistant(currentPresenter)
    if result == not_ready or result == invalid_presenter:
        hideEntryButton()

Любая ошибка обоих запросов оставляет кнопку скрытой. Отмена coroutine сохраняет стандартную семантику Kotlin CancellationException; не перехватывайте её как сетевую ошибку. Swift Task дополнительно проверяет Task.isCancelled. currentPresenter — видимая ComponentActivity либо UIViewController, готовый к презентации.

Шаг 5. Проверьте первый запуск

Проверка Ожидаемый результат
Открыли тестовый экран, обе кампании опубликованы и включены. После успешной подготовки появилась кнопка с подписью из кампании.
Нажали кнопку. Открылся полноэкранный чат с заголовком, приветствием и подсказками.
Отправили вопрос о товаре. SDK показал ответ и передал подборку вашему builder.
Нажали на товар. Чат закрылся, handler открыл карточку в приложении один раз.
Закрыли и снова открыли чат. Переписка сохранилась в памяти приложения.
Выключили точку входа и опубликовали изменение. При следующем запросе точки входа кнопка не появилась.

Если кнопка или чат не появились

Симптом Что проверить
getContentBySelector не вернул контент Значения section и selector, публикацию кампании и Experience, таргетинг для текущего PageContext.
Контент есть, кнопки нет Разбор custom.json, enabled: true, результат prepareAssistant; не успел ли пользователь уйти с экрана.
unavailable Нет подходящей настройки ассистента либо она выключена. Проверьте вторую кампанию, а не только кнопку.
invalid_configuration Зарегистрированы ли builder/handler, корректен ли JSON, непустой ли selector.
unauthorized API-ключ/раздел базового SDK и доступ раздела к Shopping API из мобильной сети.
not_ready при открытии Кнопку показали раньше ready или подготовка уже устарела после смены экрана/пользователя.
Товары есть, перехода нет canHandle(OpenProduct) возвращает true, карточка передаёт tap с product.ref, обработчик действительно выполняет навигацию.

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

Дополнительные сценарии перед выпуском

  • При отключённой или отсутствующей кампании кнопки нет; при медленной подготовке не появляется неработающая кнопка.
  • Быстрые переходы между страницами не показывают кнопку или подсказки предыдущей страницы.
  • Ошибка повторной подготовки не удаляет активный диалог; «Новый чат» не показывает старый стартовый экран.
  • Поворот, большие шрифты и пустые подборки не создают нулевую или бесконечную высоту блока.
  • Карточки за пределами горизонтального viewport не дают impression; перерисовки не создают дубли.
  • SKU без URL открывается; товар без обоих остаётся видимым без нажатия.
  • Товар и кнопка поддержки закрывают чат до перехода; быстрый двойной tap не открывает два экрана.
  • Чужая блокировка UI-кампаний сохраняется после закрытия ассистента.
  • После смены пользователя не видны старые сообщения, товары и поздние ответы.

Методы SDK

Сигнатуры для регистрации компонентов, подготовки и управления экраном ассистента.

Методы вызываются у GravitySDK.instance. Асинхронная подготовка — suspend-метод; действия с UI выполняются в главном потоке.

suspend fun prepareAssistant(
    assistantSelector: String,
    pageContext: PageContext,
    timeoutMillis: Long = 10_000,
): AssistantPrepareResult

fun openAssistant(presenter: ComponentActivity): AssistantOpenResult
fun closeAssistant()
fun setAssistantProductBlockBuilder(builder: AssistantProductBlockBuilder)
fun setAssistantActionHandler(handler: AssistantActionHandler)
fun performAssistantProductAction(
    product: AssistantProductRef,
    action: AssistantProductAction,
): AssistantInteractionResult

Методы доступны у GravitySDK.instance. Подготовка использует Swift concurrency; UI API и обработчики работают на MainActor.

@MainActor
func prepareAssistant(
    assistantSelector: String,
    pageContext: PageContext,
    timeout: TimeInterval = 10
) async -> AssistantPrepareResult

@MainActor func openAssistant(presenter: UIViewController) -> AssistantOpenResult
@MainActor func closeAssistant()
@MainActor func setAssistantProductBlockBuilder(_ builder: any AssistantProductBlockBuilder)
@MainActor func setAssistantActionHandler(_ handler: any AssistantActionHandler)
@MainActor func performAssistantProductAction(
    product: AssistantProductRef,
    action: AssistantProductAction
) -> AssistantInteractionResult

Товарный блок приложения

Builder получает целую подборку. SDK управляет вертикальной прокруткой чата; приложение рисует заголовок, карточки и горизонтальную прокрутку.

interface AssistantProductBlockBuilder {
    @Composable
    fun Build(block: AssistantProductBlock, layout: AssistantProductLayout)
}

data class AssistantProductLayout(
    val availableWidth: Dp,
    val isInteractionEnabled: Boolean,
)
@MainActor
protocol AssistantProductBlockBuilder {
    func build(block: AssistantProductBlock, layout: AssistantProductLayout) -> AnyView
}

struct AssistantProductLayout {
    let availableWidth: CGFloat
    let isInteractionEnabled: Bool
}

availableWidth — полезная ширина внутри чата после safe area и внутренних отступов, в dp на Android и points на iOS. Блок занимает ровно эту ширину и возвращает конечную измеряемую высоту по содержимому. Не используйте бесконечную высоту, fillMaxHeight, вертикальный ScrollView или GeometryReader как единственный источник высоты.

На Android корневому контейнеру задают Modifier.width(layout.availableWidth); Compose измеряет высоту содержимого. На iOS применяют .frame(width: layout.availableWidth) и .fixedSize(horizontal: false, vertical: true); SwiftUI сообщает высоту контейнеру SDK. При повороте, изменении размера окна, масштаба текста или загрузке изображения layout пересчитывается. Используйте соотношение сторон/placeholder для изображения, чтобы высота была определена ещё до загрузки. Для UIKit/Android View обёртка должна участвовать в нативном измерении высоты.

Модель данных

AssistantProductBlock Тип Значение
blockId String Уникальный ключ блока; не используйте только shelf_id как глобальный ключ.
title String? Заголовок подборки.
products [AssistantProduct] / List<AssistantProduct> Товары в порядке ответа. Пустой список допустим.
AssistantProduct Тип Значение
itemId String Уникальное появление товара в ответе. Одинаковый SKU в двух местах — разные itemId.
ref AssistantProductRef Непрозрачная ссылка SDK для событий. Не создавать самостоятельно и не сохранять между открытиями чата.
name String Название.
sku, url, imageUrl String? SKU, URL товара и изображения. url не является URL аналитики.
price Double? Цена без форматирования. Валюту определяет приложение.
brand, features String? Дополнительные сведения.
categoryId String? Значение category_id из Shopping API; не название и не путь категории.
isStm Bool? / Boolean? Признак СТМ.
canOpen Bool / Boolean Есть SKU или допустимый URL, зарегистрирован handler и его canHandle(OpenProduct(product)) возвращает true.

Поля товара, кроме itemId, ref, name и canOpen, могут отсутствовать. Не удаляйте товар из-за отсутствующей картинки, цены или категории. Показывайте placeholder и форматируйте цену по правилам магазина. Пустой блок показывает пустое состояние. События аналитики хранятся внутри SDK и не требуются builder.

Видимость и нажатия

Приложение вызывает публичный метод SDK; это не callback, который SDK вызывает в приложении:

GravitySDK.instance.performAssistantProductAction(
    product = product.ref,
    action = AssistantProductAction.VisibleImpression,
)
GravitySDK.instance.performAssistantProductAction(
    product = product.ref,
    action = AssistantProductAction.Tap,
)
GravitySDK.instance.performAssistantProductAction(product: product.ref, action: .visibleImpression)
GravitySDK.instance.performAssistantProductAction(product: product.ref, action: .tap)

AssistantProductAction имеет только visibleImpression и tap. AssistantInteractionResult: accepted, duplicate, stale_reference, not_actionable. На Kotlin это VisibleImpression, Tap, Accepted, Duplicate, StaleReference, NotActionable; в Swift — одноимённые case в lowerCamelCase.

  • Отправляйте visibleImpression, когда не менее 50% площади карточки видно непрерывно одну секунду одновременно внутри карусели, видимой области чата и окна приложения. Фоновое приложение и закрытый чат не считаются видимостью. При потере видимости таймер сбрасывают.
  • Отправляйте tap один раз на физическое нажатие, только при layout.isInteractionEnabled && product.canOpen.
  • Builder обязан применить это условие к кликам и accessibility actions. SDK не может изменить обработчики произвольной host view. SDK дополнительно отклоняет недопустимое действие на своей границе.
  • Повторное создание view не создаёт новые показы: SDK принимает один visibleImpression для одного появления товара за одно открытие чата. При новом открытии выдаются новые ref.
  • Старые ссылки после закрытия, нового чата или смены идентичности возвращают stale_reference без tracking и навигации. Повторный tap в уже начатом переходе возвращает duplicate.

Действия и навигация

Единый AssistantActionHandler обслуживает и товары, и действия ответа: например, open_support_chat. Он должен быть зарегистрирован до подготовки. Отсутствие handler или builder — invalid_configuration, поэтому интеграция не доходит до чата с неработающими товарами.

interface AssistantActionHandler {
    fun canHandle(action: AssistantAction): Boolean
    fun handle(action: AssistantAction)
}

sealed interface AssistantAction {
    data class OpenProduct(val product: AssistantProduct) : AssistantAction
    data class OpenSupportChat(val payload: Map<String, JsonValue>) : AssistantAction
    data class FollowUrl(val url: String) : AssistantAction
    data class Custom(val type: String, val payload: Map<String, JsonValue>) : AssistantAction
}
@MainActor
protocol AssistantActionHandler {
    func canHandle(_ action: AssistantAction) -> Bool
    func handle(_ action: AssistantAction)
}

enum AssistantAction {
    case openProduct(AssistantProduct)
    case openSupportChat(payload: [String: JSONValue])
    case followUrl(URL)
    case custom(type: String, payload: [String: JSONValue])
}

JsonValue/JSONValue — JSON-значение: null, boolean, number, string, array или object. payload содержит исходный объект серверной кнопки (type, value, action и прочие полученные поля) без переименования ключей; отдельного серверного поля payload не требуется. Серверное action: "follow_url" с корректным HTTP(S) URL преобразуется в FollowUrl/.followUrl; open_support_chat — в OpenSupportChat/.openSupportChat. canHandle синхронен, не выполняет сеть и не запускает навигацию. Для неизвестного типа возвращайте false; SDK оставляет такое действие недоступным и фиксирует диагностику, не ломая весь ответ.

Для товара SDK проверяет действие, ставит click tracking на отправку, закрывает чат и после завершения закрытия вызывает handle. Для open_support_chat, follow_url и поддержанных custom-действий порядок закрытия и вызова такой же. Сам переход — ответственность приложения. При обработке tap не выполняйте второй переход напрямую из builder и не отправляйте отдельный sendProductEngagement.

В handle откройте карточку по SKU либо URL, чат поддержки либо другой экран приложения. Обработчик выполняется один раз для принятого нажатия. Асинхронная аналитика не задерживает навигацию. Если приложение не может завершить переход после передачи управления, оно показывает собственную ошибку навигации.

Подготовка, контекст и диалог

Подготовка и диалог — независимые состояния. Подготовка бывает idle, preparing, ready, unavailable или failed. Диалог бывает empty, active или sending. Подготовка никогда не отправляет скрытое сообщение от имени пользователя.

Каждая новая подготовка сразу делает предыдущий стартовый экран недействительным. Даже при равных аргументах действует последний вызов. Поздний ответ, отмена или timeout старого запроса не меняют результат нового.

Событие Поведение
Первое открытие после ready Приветствие и подсказки актуальной подготовки.
Успешная повторная подготовка того же ассистента Обновляет данные для следующего нового чата. Уже начатый диалог сохраняется.
Подготовка не завершена или закончилась ошибкой Старый стартовый экран не используется. Уже открытый активный диалог сохраняется; новый чат недоступен.
Новый экран приложения trackView инвалидирует стартовую подготовку при изменении контекста. Приложение скрывает кнопку и повторяет выбор точки входа и подготовку.
Закрытие чата Закрывает только UI; сообщения и черновик сохраняются в памяти.
Повторное открытие Показывает активный диалог; если диалога нет, требует актуальный ready.
«Новый чат» при ready Атомарно прекращает ожидание старого ответа в UI, создаёт новый ID диалога, очищает сообщения и показывает актуальный стартовый экран.
«Новый чат» во время подготовки или после её ошибки Кнопка недоступна. SDK показывает статус подготовки/ошибку с повтором; старый диалог не удаляется.
Смена пользователя, сессии или раздела SDK Закрывает чат, очищает диалог и подготовку, отменяет запросы и инвалидирует ссылки на товары.
Другой assistantSelector или tenant Предыдущий диалог не переносится в другого ассистента. Для него нужна новая успешная подготовка.
Перезапуск приложения Новый диалог. Автоматического восстановления предыдущего чата нет.

Для кнопки приложения достаточно результата подготовки; отдельный hasAssistantActiveDialog не нужен. При каждой новой странице кнопку показывают после ready, даже если в памяти есть диалог. Уже открытый чат при этом не закрывается из-за обычной ошибки подготовки.

Контекст активного диалога фиксируется при его старте. Переход на другую страницу не переписывает контекст незавершённого запроса. Новый контекст применяется к следующему новому чату. Значения SKU, категорий и региона передаются без изменения регистра, согласно PageContext Android и PageContext iOS.

SDK не допускает параллельной отправки двух сообщений в одном диалоге и блокирует повторное нажатие отправки. Во время запроса виден индикатор. Таймаут запроса ответа — 120 секунд; он независим от десятисекундной подготовки. При сетевой ошибке текст сохраняется; SDK не отправляет его автоматически повторно. Если ответ сервера мог быть сохранён до обрыва связи, SDK сначала проверяет историю текущего диалога. Повтор использует тот же ID сообщения и исходный запрос, включая контекст; отредактированный текст является новым сообщением. Повтор после неопределённого сетевого исхода не даёт серверной гарантии exactly-once.

Для сообщений message_conflict соответствует HTTP 409 MESSAGE_ID_CONFLICT: SDK не создаёт новый ID автоматически и не перезаписывает ответ. Для истории history_unavailable соответствует HTTP 404 SHOPPING_HISTORY_NOT_FOUND: это не разрешение стереть локальные сообщения. Ошибки истории или генерации сохраняют текущий диалог. Остальные сетевые ошибки используют те же коды, что перечислены выше. Оценки ответа отправляет SDK по assistantMessageId из генерации либо id сообщения ассистента из истории; приложение не отправляет их повторно. Пока корректного ID ответа нет, отправка оценки недоступна.

Закрытие UI не отменяет уже отправленное сообщение: успешный ответ продолжает обрабатываться в текущем диалоге. «Новый чат», смена идентичности и смена ассистента инвалидируют старые ответы; они не попадают в новый диалог. Уже отправленный запрос может завершиться и сохраниться в старом серверном диалоге.

Результаты и ошибки

Имена результатов ниже записаны в общем формате. В Kotlin используются Ready, Unavailable(reason), Failure(error), Cancelled, Superseded; в Swift — .ready, .unavailable(reason), .failure(error), .cancelled, .superseded. Результаты подготовки и их вложенные типы сравнимы по значению (Equatable в Swift).

Результат подготовки Значение
ready Для последнего контекста загружена и проверена конфигурация стартового экрана.
unavailable(no_matching_configuration) Выбор кампании успешно завершён, подходящего контента нет. Это может быть отсутствие кампании, условия таргетинга или непустой selector, который ни с чем не совпал. SDK не угадывает конкретную причину.
unavailable(disabled) Выбранная конфигурация ассистента явно отключена.
failure(error) Не удалось получить или проверить конфигурацию; подробности в AssistantError.
cancelled Подготовка отменена экраном приложения или сменой идентичности.
superseded Подготовку заменил более поздний вызов; её ответ не меняет состояние.

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

code Точное условие Действие приложения
invalid_configuration Пустой selector, неположительный timeout, некорректный JSON выбранной конфигурации, отсутствие обязательных настроек подключения либо незарегистрированный builder/handler. Исправить интеграцию или кампанию.
unauthorized Сервис вернул HTTP 401 или 403. Проверить доступ; не повторять запрос циклически.
network Нет соединения, DNS/TLS/transport failure. Предложить повтор при восстановлении сети.
timeout Истёк общий срок подготовки, включая ожидание всех необходимых запросов. Оставить кнопку скрытой; повторить подготовку явно.
rate_limited HTTP 429. Повторить позже с учётом Retry-After, если он есть.
server HTTP 5xx. Повторить позже.
invalid_response Успешный HTTP-ответ не соответствует ожидаемому протоколу, либо иной непредусмотренный HTTP 4xx. Зафиксировать диагностику.

Опечатка в непустом selector, давшая пустой ответ, — unavailable, а не доказанная invalid_configuration. ready подтверждает готовность стартового экрана; доступность следующего запроса сообщения проверяется при его отправке.

AssistantOpenResult: opened, already_open, not_ready, invalid_presenter. Повторное открытие не создаёт второй экран. invalid_presenter означает, что Activity/UIViewController уже закрыт, не виден или занят несовместимой презентацией.

Аналитика

События жизненного цикла SDK отправляет через обычный канал custom events Gravity Field. Приложение не дублирует их:

Событие Когда SDK отправляет
chat_dialog_open После завершения каждого открытия UI.
assistant_message_sent Один раз при принятии нового пользовательского сообщения; сетевой повтор с тем же ID не является новым сообщением.
assistant_response_received При первом отображении полученного ответа, включая отображение после повторного открытия.
assistant_product_selection_received Вместе с первым отображением ответа, если в нём есть хотя бы один непустой товарный блок.
chat_error При показе ошибки отправки, истории или подготовки в открытом чате. Отменённые и вытесненные запросы не считаются ошибками.
chat_dialog_close После закрытия UI, включая переход через handler.
chat_dialog_end При явном «Новом чате» или сбросе идентичности/ассистента. Простое закрытие не завершает диалог.

События содержат идентификаторы диалога, сообщения и кампании, где применимо, без текста переписки. SDK сохраняет привязку события к исходному пользователю; событие старого диалога не отправляется от имени нового аккаунта. Завершение процесса не гарантирует доставку chat_dialog_close или chat_dialog_end.

SDK использует события click и visible_impression из product.events ответа Shopping API. Для каждого принятого действия он отправляет все допустимые URL соответствующего типа; одинаковые URL внутри одного действия отправляются один раз. Отсутствие событий не блокирует отображение товара и навигацию, но аналитика этого действия через данный механизм отсутствует.

Приложение не вызывает tracking URL, не создаёт их из SKU и не отправляет одновременно обычный sendProductEngagement. События товаров ассистента и engagement обычных кампаний — разные механизмы. События уровня ответа не подменяют показы конкретных карточек. SDK обрабатывает поддержанные события ответа отдельно при соответствующем событии жизненного цикла ответа.

Очередь уже принятых кликов не отменяется закрытием чата; событие сохраняет исходные товар и идентичность. SDK не обещает доставку после завершения процесса приложения или exactly-once доставку по сети.

Кнопки open_support_chat и follow_url сами по себе не содержат товарных tracking URL. Вызов action handler не означает отправку product click и не подменяет аналитику приложения для этих действий.

Другие UI-кампании

На время презентации и показа ассистента SDK блокирует автоматические modal/in-app показы и переходы шагов таких кампаний. Блокировка устанавливается до открытия и снимается после полного закрытия, включая системный Back и swipe-dismiss.

Ассистент снимает только собственную блокировку. Если приложение уже вызвало lockPresentation, закрытие чата не отменяет блокировку приложения. Ошибка открытия и уничтожение экрана также освобождают только блокировку ассистента.

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

Подключение к Shopping API

SDK использует https://shopping-assistant-api.gravityfield.ai. runtimeContext.tenant_id равен section, указанному при инициализации Gravity SDK. Этот раздел должен быть подключён к Shopping Assistant; selector выбирает сценарий внутри раздела и не переключает tenant. resourceId и runtimeContext.uid — канонический user.uid Gravity Field, полученный базовым SDK. Собственный ID из setUser не подставляется вместо него напрямую.

Подготовка дожидается готовности базового SDK и разрешения user.uid в пределах своего общего timeout. До этого ready не возвращается; случайный локальный ID вместо user.uid не создаётся. threadId — отдельный UUID чата, а не user.ses.

Выбор кампании идёт через обычный Gravity API V2 с ключом базового SDK. Запросы к публичному Shopping API передают Content-Type: application/json; ключ API V2 и серверные ключи коннекторов в эти запросы и в custom.json не добавляют. Отдельный логин пользователя в ассистент не нужен. На стороне сервиса раздел должен допускать обращения из мобильной сети; HTTP 403, в том числе IP_NOT_ALLOWED, означает отказ в доступе и возвращается как unauthorized. Настройки доступа не меняются методами SDK.

Подробный транспортный контракт: интеграция Shopping Assistant по API. Приложение не вызывает эти запросы параллельно с SDK.

Связанные разделы

API кампании
/personalization/campaigns/api_campaigns/
Таргетинг
/personalization/campaigns/targeting_schedule/
Превью
/shopping_assistant/personal_account/preview/
Трекинг событий
/shopping_assistant/tracking/
Shopping Assistant API
/shopping_assistant/api_reference/