Документация по интеграции
PharmSearch встраивается двумя тегами и не имеет внешних зависимостей. Виджет работает с любым стэком: vanilla, React, Vue, Angular, Bitrix, Tilda — мы подключаемся как обычный <script>.
Быстрый старт
Подключите бандл, добавьте контейнер и инициализируйте виджет:
<script src="https://cdn.pharmsearch.ru/pharmsearch-widget.js"></script> <div id="pharmsearch"></div> <script> PharmSearch.init({ container: '#pharmsearch', apiBaseUrl: 'https://api.pharmsearch.ru/v1', apiKey: 'sk_yourtenant_xxx', }); </script>
Готово. Виджет рисует строку поиска и автоматически дотягивает результаты с бэкенда.
API-ключ
Каждому клиенту выдаётся ключ вида sk_yourtenant_.... Ключ привязан к домену вашего сайта (CORS-allowlist) — кража из браузера не даёт доступ с чужого origin'а. Полный набор ключей и список разрешённых доменов — в Личном кабинете.
Режимы встройки
Виджет умеет работать в двух режимах. Выбор зависит от того, какое место поиск занимает на вашем сайте.
Inline по умолчанию
Виджет рисуется внутри указанного контейнера и занимает столько места, сколько отдадите. Подходит для отдельных страниц поиска или landing-блоков.
Overlay для шапки сайта
В шапке сайта вы кладёте слим-инпут (триггер). По клику открывается полноэкранное окно с виджетом, ESC закрывает. Подходит для интернет-магазинов с навигацией.
Inline
Дефолтный режим. Контейнер должен иметь явную ширину; высоту виджет подбирает сам. Минимальная конфигурация — см. Быстрый старт.
min-height: 320px на контейнере — пока виджет не отрисовался, место уже зарезервировано.
Overlay
В шапку положите узкий контейнер для триггера. Виджет сам отрисует pill-input с иконкой; по клику или фокусу откроется полноэкранное окно с поиском. Закрытие — ESC, клик по фону, или крестик.
<header> <div class="my-logo">ACME</div> <div id="pharmsearch-trigger"></div> <div>Кабинет</div> </header> <script> PharmSearch.init({ container: '#pharmsearch-trigger', apiBaseUrl: 'https://api.pharmsearch.ru/v1', apiKey: 'sk_yourtenant_xxx', mode: 'overlay', placeholder: 'Поиск лекарств', }); </script>
Триггер наследует шрифт хост-сайта и подстраивается под ширину контейнера (до 320px). При открытии оверлея блокируется скролл страницы; фокус автоматически переходит в строку поиска и возвращается обратно при закрытии.
Посмотреть живой пример overlay-режима →
Опции init()
Все опции — необязательные, кроме container и apiBaseUrl:
| Опция | Тип | Описание |
|---|---|---|
container | string | Element | CSS-селектор или DOM-элемент. Обязательно. |
apiBaseUrl | string | URL API. Для прода — https://api.pharmsearch.ru/v1. Обязательно. |
apiKey | string | API-ключ tenant'а (если ваш бэкенд не проксирует запросы). |
mode | 'inline' \| 'overlay' | Режим встройки. По умолчанию 'inline'. |
placeholder | string | Текст в пустом инпуте. Для overlay — текст в триггере. |
locale | 'ru' \| 'en' | Язык интерфейса. По умолчанию 'ru'. |
theme | object | Цвета, шрифты, радиусы. См. Темизация. |
fontUrl | string | URL CSS-файла со шрифтом. См. Шрифты. |
callbacks | object | Объект с колбэками. См. Колбэки. |
Темизация
Можно передать тему как объект в init(), либо попросить менеджера прописать её в конфиге тенанта (применится ко всем встройкам автоматически). Темизацию через UI кабинета пока не поддерживаем.
PharmSearch.init({ container: '#pharmsearch', apiBaseUrl: 'https://api.pharmsearch.ru/v1', theme: { primaryColor: '#0ea5e9', primaryHoverColor: '#0284c7', backgroundColor: '#f8fafc', surfaceColor: '#ffffff', textColor: '#0f172a', textSecondaryColor: '#64748b', fontFamily: "'Inter', system-ui, sans-serif", borderRadiusLg: '12px', }, });
Полный список ключей темы:
| Категория | Ключи |
|---|---|
| Цвета | primaryColor, primaryHoverColor, backgroundColor, surfaceColor, textColor, textSecondaryColor, textMutedColor, borderColor, borderLightColor, errorColor, focusColor |
| Типографика | fontFamily, fontSizeXs … fontSize4xl |
| Радиусы | borderRadiusSm, borderRadiusMd, borderRadiusLg, borderRadiusPill, borderRadiusChip |
| Размеры | cardWidth, cardImageSize, searchBarHeight, buttonHeight, containerMaxWidth |
Шрифты
По умолчанию виджет использует Gilroy, поставляемый вместе с бандлом. Чтобы подгрузить свой шрифт, передайте fontUrl и обновите fontFamily в theme:
PharmSearch.init({ /* … */ fontUrl: 'https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap', theme: { fontFamily: "'Inter', system-ui, sans-serif" }, });
Локализация
Поддерживаются 'ru' (по умолчанию) и 'en'. Регистрировать свои локали можно через PharmSearch.registerLocale(code, dict) — обращайтесь, если нужен другой язык.
Колбэки
Колбэки позволяют связать виджет с вашим сайтом — добавлять товары в корзину, отправлять аналитику, реагировать на ошибки. Все колбэки опциональны.
| Колбэк | Аргументы | Когда вызывается |
|---|---|---|
onAddToCart | (product, qty) | Пользователь нажал «+» на карточке товара. |
onProductClick | (sku) | Клик по карточке (для перехода на страницу товара). |
onSearch | (query, filters) | Запущен новый поиск. |
onSearchResults | (phase, total) | Получены результаты (phase = 'instant' | 'final'). |
onAutocomplete | (prefix, suggestions) | Получены подсказки автокомплита. |
onSelectFuture | (filter) | Применён фильтр-«пузырь». |
onShelfSeeAll | (title, queries) | Клик «Все →» по полке. |
onError | ({type, message}) | Ошибка при запросе/рендере. |
onAnalyticsEvent | ({action, params}) | Универсальное аналитическое событие. См. ниже. |
Аналитика
Самый простой способ собрать статистику по виджету — подписаться на единый onAnalyticsEvent. Виджет сам генерирует события всех ключевых действий и передаёт их в формате, удобном для GA4 / Yandex.Метрика / dataLayer:
PharmSearch.init({ /* … */ callbacks: { onAnalyticsEvent: (ev) => { // Google Analytics 4 gtag('event', ev.action, ev.params); // Яндекс.Метрика — цели ym(XXXXXXXX, 'reachGoal', ev.action, ev.params); // GTM dataLayer dataLayer.push({ event: ev.action, ...ev.params }); }, }, });
Список событий (ev.action):
pharmsearch_search— пользователь запустил поискpharmsearch_results— пришли результаты (phase:'instant'/'final')pharmsearch_autocomplete— автокомплитpharmsearch_filter_apply,pharmsearch_filter_remove— клики по «пузырям»-фильтрамpharmsearch_product_click— клик по карточкеpharmsearch_add_to_cart,pharmsearch_remove_from_cart— изменение корзиныpharmsearch_shelf_view_open,pharmsearch_shelf_view_page— взаимодействие с «полкой»pharmsearch_reset— сброс поискаpharmsearch_error— ошибка
Корзина
Виджет не управляет корзиной напрямую — он сообщает вам о действиях пользователя через onAddToCart, а вы решаете, что делать. Это позволяет использовать вашу существующую логику (Bitrix, OpenCart, кастомный бэкенд):
callbacks: {
onAddToCart: async (product, qty) => {
await fetch('/cart/add', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ sku: product.sku, qty }),
});
updateMiniCart();
},
}
API без виджета
Если вы интегрируете PharmSearch в собственный сайт, iOS/Android-приложение или backend UI без готового виджета, используйте тот же session-based API. Бэкенд сам обрабатывает streaming Gemini, парсинг частичного JSON, запросы в поиск и дедупликацию товаров. Клиент получает уже готовые snapshots состояния.
data: payload заменяйте локальные shelves, futures и флаги состояния целиком.
Последовательность запросов
- Запустите поиск через
POST /v1/search. - Сохраните
session_idиз ответа. - Откройте
GET /v1/search/{session_id}/eventsсAccept: text/event-stream. - Парсьте каждое
data: {...}какSearchStateResponse. - Закройте соединение на
event: done.
POST /v1/search
X-API-Key: sk_yourtenant_xxx
Content-Type: application/json
{
"query": "болит горло",
"active_futures": [],
"previous_shelves": [],
"source": "search"
}
GET /v1/search/sess_abc123def456/events X-API-Key: sk_yourtenant_xxx Accept: text/event-stream
Формат SSE
В потоке приходят обычные Server-Sent Events:
data: {"phase":"fast","query":"болит горло","shelves":[],"futures":[],"is_processing":true,"error":""}
data: {"phase":"fast","query":"болит горло","shelves":[...],"futures":[...],"is_processing":true,"error":""}
event: done
data: {}
data: {...}— полный snapshot состояния поиска.event: done— enrichment завершён, поток можно закрыть.: keepalive— служебный ping, его нужно игнорировать.errorвнутри snapshot содержит безопасный текст ошибки, если обогащение не удалось.
Нативные приложения
Для iOS и Android подключение проще, чем в браузере: нативный HTTP-клиент может отправить X-API-Key прямо в streaming request. Используйте URLSession, OkHttp или другой клиент, который умеет читать response body построчно до закрытия соединения.
- Читайте поток до пустой строки — это граница одного SSE-события.
- Собирайте строки
data:и парсьте JSON после завершения события. - Останавливайте чтение на событии
done. - При обрыве можно переподключиться к тем же
/eventsили запросить/state.
Браузер без виджета
Нативный EventSource в браузере не умеет отправлять custom headers, включая X-API-Key. Для кастомной web-интеграции используйте backend proxy, short-lived stream token или fetch() streaming, где можно передать заголовки.
Fallback на polling
Если SSE недоступен, опрашивайте GET /v1/search/{session_id}/state каждые 300-500 мс и остановитесь, когда is_processing станет false.
CSP / CORS
Если сайт использует Content-Security-Policy, разрешите следующее:
script-src 'self' https://cdn.pharmsearch.ruconnect-src 'self' https://api.pharmsearch.rustyle-src 'self' 'unsafe-inline'— виджет инлайнит стили в IIFE-бандлimg-src 'self' data: https://*.pharmsearch.ru— для изображений товаров
Со стороны API мы добавляем ваш домен в CORS-allowlist при выдаче ключа. Если нужен дополнительный домен — добавьте его в Личном кабинете → Настройки → Allowed origins, или напишите нам.
Решение проблем
«PharmSearch is not defined»
Скрипт ещё не загружен в момент вызова init(). Либо подключайте бандл с атрибутом defer и оборачивайте инициализацию в DOMContentLoaded, либо ставьте <script src=…> до <script>PharmSearch.init…</script> и не используйте async.
«CORS error»
Ваш origin не в allowlist tenant'а. Откройте Личный кабинет → Настройки и добавьте домен (например, https://shop.example.ru). Изменения применяются мгновенно.
«401 Unauthorized»
API-ключ невалиден или отозван. Проверьте, что apiKey совпадает с тем, что показан в Личном кабинете, и что вы не используете dev-ключ на проде.
Поиск медленный
В overlay-режиме первый поиск может быть медленнее на 100-200мс, потому что виджет монтируется лениво. После первого открытия рендерер кэшируется. Если хотите прогревать его сразу — пишите, добавим preload: true.
Не работает на iOS Safari
Проверьте, что не блокируется fetch-запросы из приватного режима. Если используете кастомный шрифт, убедитесь что он отдается с правильным Access-Control-Allow-Origin.