Web-интеграция Shopping Assistant
Встройте готовый чат в сайт через Web-кампанию Gravity Field. Покупатель открывает чат, отправляет вопрос и видит товары в шаблоне вашего магазина. Виджет отвечает за переписку, код кампании — за точку входа и вставку товарных карточек.
1. Подготовьте секцию и шаблон
До начала нужны:
- установленный Web-скрипт и актуальный контекст страницы;
- подключённый Shopping Assistant в той же секции
GF.sectionс разрешёнными запросами из браузера покупателя; - каталог и успешная проверка подбора в «Превью»;
- ID товарного шаблона, который принимает набор SKU и отображает карточки со ссылками и трекингом.
На странице должны быть доступны GF.userData.uid и GF.pageContext. uid — идентификатор пользователя Gravity Field; он передаётся как resourceId. Не оставляйте значение виджета по умолчанию "api request": иначе разные покупатели будут использовать один идентификатор.
2. Создайте Web-кампанию
- Создайте Web-кампанию типа «Пользовательский код».
- Добавьте сценарий и выберите страницы, аудитории и условия показа. Для первого теста используйте тестовую аудиторию.
- Создайте вариацию с ассистентом. Вставьте JavaScript из следующего шага и замените ID товарного шаблона.
- Если используете контрольную группу, оставьте её без кода, создающего кнопку или чат.
- Активируйте нужный сценарий и кампанию, затем опубликуйте изменения.
Публикация и таргетинг определяют, где запускается код. Один запуск вне целевой страницы не подтверждает, что кампания будет показана всем покупателям.
3. Подключите виджет и товары
Пример использует JS/CSS версии 1.41, собственную кнопку и один экземпляр чата на вкладку. Это рабочий каркас для вариации; ID шаблона нужно заменить на значение вашей секции. Оформление кнопки и чата настраивается CSS вариации.
(async () => {
const state = window.gfShoppingAssistantState ??= {};
const run = state.run = (state.run ?? 0) + 1;
state.unmount?.();
state.spaToken ??= GF.PubSub.on('spa', () => {
state.run += 1;
state.unmount?.();
});
function loadAssets() {
return state.assets ??= new Promise((resolve, reject) => {
const css = document.createElement('link');
css.rel = 'stylesheet';
css.href = 'https://cdn-01.gravityfield.ai/shopping-assistant/shopping-assistant.1.41.min.css';
const js = document.createElement('script');
js.src = 'https://cdn-01.gravityfield.ai/shopping-assistant/shopping-assistant.1.41.min.js';
let ready = 0;
const loaded = () => { if (++ready === 2) resolve(); };
css.onload = js.onload = loaded;
css.onerror = js.onerror = () => {
css.remove(); js.remove();
state.assets = null;
reject(new Error('Не удалось загрузить виджет'));
};
document.head.append(css, js);
});
}
try {
await Promise.all([loadAssets(), GF.waitForVariable('GF.userData.uid')]);
if (state.run !== run) return;
if (!GF.section || !GF.pageContext || !GF.userData.uid) {
throw new Error('Нет секции, контекста или идентификатора пользователя');
}
const assistant = state.assistant ??= new ShoppingAssistant({
autoInsertTrigger: false,
resourceId: GF.userData.uid,
startScreenTitle: 'Помощник магазина',
startScreenDescription: 'Помогу выбрать подходящий товар.',
placeholder: 'Задайте вопрос',
quickQuestions: [{ title: 'Помогите выбрать подарок' }],
focusInputOnOpen: false
});
assistant.attachTo(document.body);
const button = document.createElement('button');
button.type = 'button';
button.className = 'gf-assistant-entry';
button.textContent = 'Помощник магазина';
const open = () => {
// После смены пользователя начинаем отдельный разговор.
if (assistant.config.resourceId !== GF.userData.uid) {
assistant.startNewChat();
assistant.config.resourceId = GF.userData.uid;
}
assistant.open();
};
button.addEventListener('click', open);
const onProducts = ({ detail }) => {
const { skus, uniqueId } = detail;
if (!skus.length) return;
// Событие приходит до вставки контейнера: ждём завершения текущего вызова.
queueMicrotask(() => {
if (state.run !== run || !detail.element.isConnected) return;
const order = new Map(skus.map((sku, index) => [String(sku), index]));
GF.Templates.insertRecs('YOUR_PRODUCT_TEMPLATE_ID', {
selector: '#' + uniqueId,
identifier: 'gf-chat-' + uniqueId,
insertMethod: 3,
maxProducts: Math.min(50, skus.length),
title: detail.title ?? '',
realtimeRules: [{
id: 1, type: 'include', slots: [],
query: { conditions: [{
field: 'sku',
arguments: skus.map(sku => ({ action: 'equal', value: sku }))
}] }
}],
prerenderSlotsHandler: slots => {
const seen = new Set();
return slots.filter(slot => {
const sku = String(slot.item.sku);
if (!order.has(sku) || seen.has(sku)) return false;
seen.add(sku);
return true;
}).sort((a, b) => order.get(String(a.item.sku)) - order.get(String(b.item.sku)));
},
campaignObj: {
var: '${slVariationId}', ver: '${slVersionId}',
exp: '${slExperienceId}', cam: '${slTagId}'
}
});
});
};
document.addEventListener('shopping-assistant-recs', onProducts);
document.body.append(button);
state.unmount = () => {
button.removeEventListener('click', open);
button.remove();
document.removeEventListener('shopping-assistant-recs', onProducts);
assistant.close();
state.unmount = null;
};
} catch (error) {
console.error('Shopping Assistant:', error.message);
}
})();
YOUR_PRODUCT_TEMPLATE_ID — ID товарного шаблона, а не ID кампании или стратегии. Имена ${slVariationId}, ${slVersionId}, ${slExperienceId} и ${slTagId} — служебные подстановки Web-кампании; сохраняйте их без переименования. Обращения к JS SDK выполняются через GF.
Проверьте, что шаблон не добавляет fallback-товары и дубли. При отсутствии части SKU показывайте только доступные позиции из ответа. maxProducts ограничивает число отображаемых товаров; пример рассчитан на подборки до 50 SKU. Сопоставление SKU должно учитывать правила регистра вашего фида.
4. Настройте интерфейс
На mobile можно прикрепить чат к document.body, а на desktop — к контейнеру вашего окна. Если используется оверлей, создайте его через GF.Overlay, передайте его контейнер в attachTo, а при нажатии откройте сначала оверлей, затем ассистента.
Для встроенной кнопки задайте autoInsertTrigger: true, вызовите attachTo и уберите собственную кнопку. Параметры и методы: Web-виджет.
Тексты, логотип и стартовые вопросы можно связать с переменными вариации. Стартовые вопросы — объекты { title, sub? }; они отличаются от строк response.ui.suggests, которые приходят после ответа.
5. Проверьте SPA и события
Пример повторно использует чат, а на событии spa снимает внешние обработчики и кнопку. При следующем запуске целевой вариации они создаются заново. Если пользователь попал в контрольную группу или ушёл с целевой страницы, прежняя кнопка не остаётся видимой.
Не создавайте новый экземпляр на каждом SPA-переходе: destroy() версии 1.41 удаляет контейнер, но не снимает все внутренние обработчики. При смене пользователя пример очищает переписку через startNewChat() и обновляет resourceId. Если приложение меняет пользователя при открытом чате, выполните эту смену до следующей отправки сообщения.
Виджет отправляет события переписки через GF.API. Товарные показы и клики должен учитывать товарный шаблон; не дублируйте их ручной отправкой. Разделение ответственности описано в трекинге.
Пройдите чеклист запуска, включая контрольную группу, мобильный экран, повторный переход и ошибку загрузки ресурсов.