Документация • 10 мин четене

WF Chat Widget — Техническа документация

Управляван от JSON конфигурация разговорен уиджет с фиксирано позициониране. Потребителят преминава през структурирани стъпки — избор на опции, въвеждане на контакти, разглеждане на имоти и статии — без сървърен код.

Инсталация

Включете chat.css в <head>. Поставете HTML структурата на уиджета в края на <body>, след което заредете chat.js и конфигурационния JSON скрипт. Уиджетът не изпраща данни автоматично — обработката на събраните отговори е отговорност на интегриращия код.

<link rel="stylesheet" href="./assets/css/chat.css" />

<div class="wf_chat_widget" data-show-after-scroll="true" ...>
  <button class="wf_chat_widget__launcher" aria-label="Отвори чат">
    <span class="wf_chat_widget__launcher-face">
      <img class="wf_chat_widget__launcher-img" src="..." alt="" />
    </span>
    <span class="wf_chat_widget__launcher-dot"></span>
  </button>
  <div class="wf_chat_widget__window" role="dialog" aria-hidden="true">
    <div class="wf_chat_widget__header-btns">
      <button class="wf_chat_widget__btn-reset" ...>...</button>
      <button class="wf_chat_widget__btn-close" ...>...</button>
    </div>
    <div class="wf_chat_widget__messages"></div>
    <div class="wf_chat_widget__input-area"></div>
  </div>
</div>

<script src="./assets/js/chat.js"></script>
<script type="application/json" id="wf-chat-config">
  { ... }
</script>

Атрибути на контейнера

JSON конфигурация — основни полета

Секция ui

Всички видими надписи се задават тук, за да работи уиджетът на всеки език:

Типове стъпки

Полето script е масив от обекти, всеки задължително с поле type. Изпълнението е последователно — всяка стъпка стартира следващата след действие на потребителя или след изтичане на автоматичното забавяне.

bot

Показва съобщение от бота с анимирани точки. Забавянето се изчислява пропорционално на дължината на текста (минимум 600 мс, максимум 3200 мс). Последователни bot стъпки се показват наредени, без повтарящ се аватар.

{ "type": "bot", "message": "Трудно Ви е да намерите точния имот?" }

user-choice

Хоризонтален ред с бутони-хапки, рендериран директно под последното съобщение на бота. При натискане надписът се показва като потребителско съобщение и разговорът продължава. Опциите могат да са низове или обекти с label — всички допълнителни полета (напр. min, max) се записват в сесията под <collect>Meta и са достъпни за стъпките render-*.

{
  "type": "user-choice",
  "collect": "budget",
  "choices": [
    { "label": "До 100 000 €", "max": 100000 },
    { "label": "100–200 000 €", "min": 100000, "max": 200000 },
    { "label": "Над 200 000 €", "min": 200000 }
  ]
}

user-multi

Множествен избор с хапки и бутон „Потвърди". Бутонът е деактивиран докато не е избрана поне една стойност. Избраните стойности се записват в сесията като масив от низове.

{
  "type": "user-multi",
  "collect": "features",
  "choices": ["Паркинг", "Асансьор", "Двор"],
  "confirmLabel": "Потвърди"
}

user-radio

Мрежа от карти (2 колони) с незадължителна SVG икона. При избор картата се маркира визуално, отговорът се записва и разговорът продължава.

{
  "type": "user-radio",
  "collect": "deal",
  "options": [
    { "label": "Покупка", "icon": "<svg viewBox='0 0 24 24' ...>...</svg>" },
    { "label": "Наем",    "icon": "<svg viewBox='0 0 24 24' ...>...</svg>" }
  ]
}

user-searchselect

Падащо меню с вградена търсачка и мулти-избор чрез чипове. Подходящо за дълги списъци като градски райони. При отваряне фокусът пада върху търсачката. Избраните стойности са чипове с бутон за индивидуално премахване.

{
  "type": "user-searchselect",
  "collect": "districts",
  "placeholder": "Изберете район(и)...",
  "searchPlaceholder": "Търси район...",
  "choices": ["Лозенец", "Младост", "Витоша", "Център"]
}

user-input

Текстово поле с валидация. inputType може да е "text", "email" или "tel". При "tel" се зарежда lazy intl-tel-input с търсачка за държавен код; initialCountry задава предизбраната държава (по подразбиране "bg"). Грешката се показва с анимация при неуспешен опит за изпращане. При skippable: true в списъка се появява бутон за пропускане — стойността в сесията се записва като null.

{
  "type": "user-input",
  "inputType": "tel",
  "placeholder": "0888 123 456",
  "validate": "phone",
  "collect": "phone",
  "initialCountry": "bg",
  "skippable": true,
  "skipLabel": "Пропуснете",
  "skip_text": "Предпочитам да не споделям телефон",
  "errorMessage": "Моля, въведете валиден телефонен номер."
}

user-nav

Мрежа от навигационни бутони (до 3 на ред с икони), показвана след приключване на основния поток. Всеки бутон стартира собствен под-скрипт чрез полето script. Блокът се запомня вътрешно, за да може repeat-nav да го покаже отново след всяко взаимодействие.

{
  "type": "user-nav",
  "items": [
    {
      "label": "Апартаменти",
      "icon": "<svg ...>...</svg>",
      "script": [
        { "type": "bot", "message": "Колко стаи търсите?" },
        { "type": "user-choice", "collect": "rooms", "choices": ["1 стая", "2 стаи", "3 стаи", "4+ стаи"] },
        { "type": "render-apartments" },
        { "type": "repeat-nav", "message": "Мога ли да помогна с нещо друго?" }
      ]
    }
  ]
}

render-apartments

Рендерира карти за апартаменти от data.apartments, филтрирани по текущата сесия. Активни филтри: deal (тип сделка), districts (масив с райони), budgetMeta.min / budgetMeta.max (ценови диапазон от предишен user-choice), rooms (брой стаи; суфикс + означава „и повече"). Всеки филтър се прилага само ако би дал резултати — при пълно изключване се пропуска. При крайна липса на резултати се показва ui.noApartments.

render-articles

Рендерира карти за статии от data.articles, филтрирани по session.topic. Статии с category равна на ui.generalCategory се показват при всяка тема. При избор на ui.allTopic се показват всички статии без филтриране. При крайна липса на резултати се показва ui.noArticles.

render-location

Рендерира карта (OpenStreetMap чрез Leaflet, зарежда се lazy при първо използване) с персонализиран SVG маркер и детайли от data.location: адрес, телефон (като tel: линк) и работно време.

contacts-outcome

Проверява дали и session.email, и session.phone са null (двата пропуснати). При пропуснати и двата контакта показва skippedMessage и рендерира карта с директни контакти (телефон, имейл, часове от data.location). При наличие на поне един контакт показва successMessage.

{
  "type": "contacts-outcome",
  "successMessage": "Благодарим! Ще се свържем с Вас до края на деня.",
  "skippedMessage": "Няма проблем! Свържете се с нас директно:"
}

repeat-nav

Повторно показва последния user-nav блок. При зададено message се показва бот съобщение преди навигацията. Преди рендерирането rooms, topic и timeline се изтриват от сесията, за да могат под-скриптовете да работят отначало.

{ "type": "repeat-nav", "message": "Мога ли да помогна с нещо друго?" }

Динамични избори

Когато наличните опции зависят от предишен отговор, задайте choices (или options) като обект вместо масив и добавете choicesBy (или optionsBy) с ключа на сесийната променлива. Ключовете са възможните стойности; "default" се използва при липса на съвпадение.

{
  "type": "user-choice",
  "collect": "timeline",
  "choicesBy": "deal",
  "choices": {
    "Покупка": ["До 6 месеца", "До 1 година", "Гъвкаво"],
    "Наем":    ["Веднага", "До 3 месеца", "Гъвкаво"],
    "default": ["Гъвкаво"]
  }
}

Секция data

data.apartments — масив от обекти с полета: title, district (низ, съвпадащ с елементите в user-searchselect), price (число), type (трябва да съвпада с ui.rentType за наемни имоти), rooms (число), sqm (число), badge (низ или null), img, url.

data.articles — масив с title, category, excerpt, img, url.

data.location — обект с lat, lng, address, phone, email, hours и незадължителен pin обект (color, dotColor, size) за персонализация на маркера.

Сесия и събиране на данни

Вътрешният обект session се попълва от стъпките с поле collect. Низовите стойности се записват директно; при обектни опции допълнителните метаданни (напр. min/max) се записват под <ключ>Meta. При пропускане стойността е null. Стъпките render-* и contacts-outcome четат от сесията при рендериране. Натискането на бутона за нулиране изчиства сесията и рестартира разговора от началото.

Изпращане на данни

При достигане на финална стъпка уиджетът извиква вътрешната функция submitSession(). Тя винаги записва събраните данни в конзолата и опционално ги изпраща към сървър.

console.log

При всяко завършване в конзолата на браузъра се появява запис с всички събрани стойности:

[wf-chat] session: {
  deal: "Покупка",
  budget: "100–200 000 €",
  budgetMeta: { min: 100000, max: 200000 },
  districts: ["Лозенец", "Младост"],
  rooms: "3 стаи",
  email: "ivan@example.com",
  phone: null
}

cfg.action

Добавете поле "action" в конфигурацията за автоматичен POST:

{
  "brand": "Имот Асистент",
  "action": "https://your-endpoint.com/leads",
  ...
}

Уиджетът изпраща POST заявка с Content-Type: application/json и тяло — копие на session в момента на завършване. Заявката е fire-and-forget: грешките не се показват на потребителя.

Кога се извиква