Документация по интеграции

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

Дефолтный режим. Контейнер должен иметь явную ширину; высоту виджет подбирает сам. Минимальная конфигурация — см. Быстрый старт.

Совет. Для предотвращения CLS (cumulative layout shift) задайте 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:

ОпцияТипОписание
containerstring | ElementCSS-селектор или DOM-элемент. Обязательно.
apiBaseUrlstringURL API. Для прода — https://api.pharmsearch.ru/v1. Обязательно.
apiKeystringAPI-ключ tenant'а (если ваш бэкенд не проксирует запросы).
mode'inline' \| 'overlay'Режим встройки. По умолчанию 'inline'.
placeholderstringТекст в пустом инпуте. Для overlay — текст в триггере.
locale'ru' \| 'en'Язык интерфейса. По умолчанию 'ru'.
themeobjectЦвета, шрифты, радиусы. См. Темизация.
fontUrlstringURL CSS-файла со шрифтом. См. Шрифты.
callbacksobjectОбъект с колбэками. См. Колбэки.

Темизация

Можно передать тему как объект в 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, fontSizeXsfontSize4xl
Радиусы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):

Корзина

Виджет не управляет корзиной напрямую — он сообщает вам о действиях пользователя через 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 состояния.

Важно. Поток отдаёт не diff'ы, а полное накопленное состояние. На каждый data: payload заменяйте локальные shelves, futures и флаги состояния целиком.

Последовательность запросов

  1. Запустите поиск через POST /v1/search.
  2. Сохраните session_id из ответа.
  3. Откройте GET /v1/search/{session_id}/events с Accept: text/event-stream.
  4. Парсьте каждое data: {...} как SearchStateResponse.
  5. Закройте соединение на 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: {}

Нативные приложения

Для iOS и Android подключение проще, чем в браузере: нативный HTTP-клиент может отправить X-API-Key прямо в streaming request. Используйте URLSession, OkHttp или другой клиент, который умеет читать response body построчно до закрытия соединения.

Браузер без виджета

Нативный 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, разрешите следующее:

Со стороны 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.

Не нашли ответ? Напишите на hello@pharmsearch.ru — отвечаем в рабочее время в течение пары часов.