flowhelpDokumentacja

Osadzenie widgetu flowhelp — instrukcja dla klienta (kontrakt v1)

Wersja 1.2 · 2026-09-17 · Źródło prawdy: packages/config/src/widget-embed.ts i DECYZJE.md ADR-026 (osadzenie), ADR-027 (markdown), ADR-028 (identify), ADR-029 (oceny, formularz kontaktowy, podgląd w panelu). Wszystko w tej instrukcji jest zamrożone — snippet wklejony dziś będzie działał z każdą przyszłą wersją widgetu.

1. Snippet

Jeden tag, bez kodu inline, przed </body>:

<script async src="https://cdn.flowhelp.ai/loader.js" data-key="pk_live_…"></script>
AtrybutWymaganyZnaczenie
data-keytakklucz publiczny agenta (pk_live_ + 32 znaki hex) — z panelu
data-localeniewymuszony język UI (pl/en); brak = konfiguracja agenta / przeglądarka
data-apinietylko dev/staging; produkcja używa https://app.flowhelp.ai

Klucz publiczny z założenia jest widoczny w HTML-u. Nie chroni go tajność, tylko allowlista domen (poniżej), limity i dobowy limit kredytów agenta.

2. Allowlista domen (Origin)

Widget działa wyłącznie na domenach wpisanych w konfiguracji agenta. Semantyka:

Strona spoza allowlisty: przeglądarka blokuje wywołanie (serwer odpowiada 403 bez nagłówków CORS), widget nie pojawia się i wypisuje błąd w konsoli. Nic nie kosztuje.

3. Content-Security-Policy gospodarza

Jeśli strona ma CSP, potrzebne są dokładnie dwa źródła:

script-src  … https://cdn.flowhelp.ai;
connect-src … https://app.flowhelp.ai;

Widget nie używa inline JS, eval, ani zewnętrznych zasobów poza tymi dwoma hostami. Style żyją w Shadow DOM (adoptedStyleSheets), więc style-src nie musi być zmieniany. Loader ma stałą ścieżkę (cache 5 min); core jest adresowany hashem z integrity (SRI) i crossorigin.

4. Publiczne API

Wywołania można kolejkować przed załadowaniem widgetu — zostaną wykonane w kolejności:

window.flowhelp = window.flowhelp || [];
window.flowhelp.push(['open']);
window.flowhelp.push(['on', 'ready', () => console.log('widget gotowy')]);
MetodaDziałanie
open / close / toggle / isOpenokno czatu
sendMessage(text)wysyła wiadomość jak użytkownik
setLocale('pl'|'en')język UI
setContext(obj)kontekst strony (v0: zapamiętywany, jeszcze nieużywany)
on(event, cb) / off(event, cb)zdarzenia: ready, open, close, message:sent, message:received, error
grantConsent() / revokeConsent()zgoda (patrz §6)
reset()kasuje identyfikator odwiedzającego, bieżącą rozmowę i tożsamość z identify
identify({ userId, hash })tożsamość zalogowanego użytkownika z podpisem HMAC (§5); identify(null) = wylogowanie
initbez efektu (widget startuje sam)

Nieznana metoda = ostrzeżenie w konsoli, bez wyjątku.

5. Zalogowani użytkownicy — identify() z podpisem HMAC

Jeśli Twoja strona wie, kto jest zalogowany, możesz to przekazać widgetowi. Rozmowy takiego użytkownika będą w panelu podpisane jego identyfikatorem, a przy włączonej opcji „wymagaj zweryfikowanej tożsamości" anonimowi odwiedzający nie będą mogli pisać.

Widget nie liczy podpisu i nie zna sekretu. Podpis liczy Twój backend, z sekretu HMAC agenta (panel → Ustawienia → Widget; do Etapu 4 przekazywany przez nas):

hash = hex( HMAC-SHA256( key = sekret_agenta, message = userId ) )

Node.js:

const { createHmac } = require('node:crypto');
const hash = createHmac('sha256', process.env.FLOWHELP_HMAC_SECRET).update(user.id).digest('hex');

PHP:

$hash = hash_hmac('sha256', $user->id, getenv('FLOWHELP_HMAC_SECRET'));

Python:

import hmac, hashlib
hash_ = hmac.new(SECRET.encode(), user_id.encode(), hashlib.sha256).hexdigest()

Na stronie (może być przed załadowaniem widgetu — trafi do kolejki):

window.flowhelp = window.flowhelp || [];
window.flowhelp.push(['identify', { userId: 'u_123', hash: '<64 znaki hex>' }]);
// po wylogowaniu:
window.flowhelp.push(['identify', null]);

Zasady:

6. Zgoda (GDPR) i disclaimer AI

consentMode z konfiguracji agenta:

Zgoda nie jest utrwalana między wizytami (v0). Identyfikator odwiedzającego to UUID w localStorage (flowhelp:visitor:<klucz>), bez cookies. Pierwsza wiadomość zawsze zawiera disclaimer AI (EU AI Act art. 50) — edytowalny w panelu, nie wyłączalny.

7. Limity i błędy

SytuacjaZachowanie
20 wiadomości / 5 min od jednego odwiedzającego (domyślnie; konfigurowalne per agent)429, widget blokuje pole na Retry-After sekund i pokazuje komunikat
druga wiadomość, zanim skończy się poprzednia odpowiedź429 (Retry-After: 5)
dobowy limit kredytów agenta wyczerpany429 do północy UTC
agent wstrzymany / nie w statusie ready403, widget się nie pojawia
agent wymaga tożsamości, a strona nie wywołała identify()wiadomość nie wychodzi; komunikat „Zaloguj się na tej stronie, aby korzystać z czatu."
niezgodny podpis identify()403 przed odpowiedzią, zero kosztu
pytanie spoza bazy wiedzyodpowiedź „Nie mam tej informacji w swojej bazie wiedzy." — bez zmyślania

8. Formatowanie odpowiedzi (markdown)

Odpowiedzi asystenta są renderowane z markdownu: akapity, pogrubienie, kursywa, ~~przekreślenie~~, kod i bloki kodu, listy (także zagnieżdżone), cytaty, linia pozioma, tabele, linki. Nagłówki # są renderowane jako pogrubione akapity, nie jako <h1> — nie wpływają na strukturę Twojej strony.

Czego widget nie renderuje, bo tekst modelu nigdy nie staje się HTML-em (ADR-027): surowego HTML-a (widoczny jako tekst), obrazków (zostaje opis), linków z protokołem innym niż http/https/mailto (zostają tekstem). Każdy link i cytat [n] otwiera się w nowej karcie z rel="noopener noreferrer".

9. Wygląd, motyw, RTL, mobile

10. Zgodność z frameworkami i CMS-ami (zweryfikowane 2026-09-14)

Test wycieku stylów w obie strony (docs/qa/etap3-zamkniecie-2026-09-14/): dwa przebiegi tej samej strony (bez widgetu i z widgetem), porównanie sond gospodarza i elementów widgetu, z-index, mobile 390 px, zero błędów konsoli.

ŚrodowiskoWynik
Bootstrap 5.3 (navbar fixed-top, stopka z-1050, agresywny arkusz globalny)0 różnic w sondach gospodarza, style widgetu nietknięte
Tailwind (Play CDN, preflight)jak wyżej
WordPress 7.1 + Elementor 4.2.4 + Hello Elementor 3.5.1widget ładuje się przez snippet w wp_footer, style Elementora i gospodarza bez zmian, zero błędów konsoli
Strona dir="rtl"nagłówek i kompozytor lustrzane, róg ekranu bez zmian

Jeśli Twój motyw ma reguły globalne w rodzaju * { letter-spacing: 2px } — nie mają wpływu (widget ma pełny reset z !important na hoście). Jedyne, czego widget potrzebuje od strony: dwa hosty w CSP (§3); X-Frame-Options Twojej strony nie ma znaczenia, bo widget nie jest iframe'em.

11. Test osadzenia

Strona QA: https://flowhelp.ai/qa/widget-embed-test.html (kod: docs/qa/). Sprawdza, że loader ładuje core, okno otwiera się z kolejki, odpowiedź ma cytat, a style gospodarza pozostają nietknięte. Ten sam plik serwowany z innego origin nie uruchamia widgetu.

12. Ocena odpowiedzi i formularz kontaktowy (ADR-029)

Obie rzeczy włącza się w panelu; w snippecie nic się nie zmienia i nie ma tu żadnego API do wołania ze strony.

Kciuki pod odpowiedzią

Gdy agent ma włączone zbieranie ocen, pod każdą odpowiedzią asystenta są dwa przyciski (👍/👎). Kliknięcie tego samego drugi raz cofa ocenę. Oceny widać w panelu w Rozmowach (filtr „kciuk w dół") i we Wnioskach — to jedyny sygnał jakości pochodzący od Twoich odwiedzających.

Wyłączenie ocen w panelu wyłącza je naprawdę: przyciski znikają, a żądanie wysłane mimo to dostaje 403. Kciuk nie wymaga zgody na cookies ani logowania — przypisujemy go do tej samej rozmowy, w której powstała odpowiedź, i tylko ta przeglądarka może ją ocenić.

Formularz kontaktowy (lead) i „porozmawiaj z człowiekiem"

Formularz pokazuje się według wyzwalacza ustawionego w panelu:

WyzwalaczKiedy widget pokazuje formularz
after_messagespo N odpowiedziach asystenta (N z panelu)
on_fallbackzaraz po odpowiedzi „nie mam tej informacji" — czyli tam, gdzie asystent nie pomógł
manualnigdy sam; w nagłówku okna jest przycisk z tytułem formularza

Pola (etykiety, typy, wymagalność — maks. 8) pochodzą z panelu. Zamknięcie formularza krzyżykiem wyłącza wyzwalacz automatyczny do końca tej rozmowy — nie wraca po każdej wiadomości. Po wysłaniu odwiedzający widzi Twój tekst podziękowania.

Eskalacja to ten sam formularz z dodatkowym polem „Wiadomość" i własnym przyciskiem w nagłówku (jego treść ustawiasz w panelu). Rozmowa dostaje wtedy znacznik „eskalowana", który zasila wskaźnik deflekcji we Wnioskach.

Każdy wysłany formularz trafia do panelu i na e-mail: adres z ustawień akcji, a gdy go nie ma — adres powiadomień workspace'u, a gdy i tego nie ma — e-mail właściciela. Wiadomość zawiera pola formularza i ostatnie 10 wiadomości rozmowy, żeby dało się odpowiedzieć bez wchodzenia do panelu.

Limit: 5 formularzy na odwiedzającego na dobę (doba UTC). To bezpiecznik Twojej skrzynki i naszej reputacji nadawcy — nie da się go podnieść w panelu. Odbity formularz dostaje 429 z komunikatem, treść zostaje w polach.

13. Podgląd w panelu (widget-preview.js)

Sekcja „Wygląd" w panelu pokazuje ten sam widget, który chodzi na Twojej stronie — to nie jest osobna makieta. Panel ładuje https://cdn.flowhelp.ai/widget-preview.js, czyli drugie wejście tego samego kodu, i montuje widget w ramce formularza zamiast w rogu ekranu.

Co się różni od widgetu na stronie:

Podgląd nie jest częścią kontraktu osadzenia: to narzędzie panelu i nie wklejaj go na swoją stronę — nie rozmawia z Twoim agentem. Gdy skrypt podglądu nie załaduje się w 5 s (CSP panelu, CDN), panel pokazuje statyczną kartę z kolorem i powitaniem, a formularz działa dalej.