Pakiet zawiera gotowy skrypt embed.js, przykład strony HTML i backend Node.js 22+ bez dodatkowych zależności. Potrzebujesz aktywnego konta API, klucza API oraz adresu API Zapytaj Kodeks. Płatności i dostęp są ustalane indywidualnie.
1. Umieść klucz na serwerze firmy
Skopiuj .env.example do .env i uzupełnij ZK_API_KEY, ZK_API_URL, SITE_ORIGIN. Produkcyjny SITE_ORIGIN to dokładna domena HTTPS, np. https://firma.pl, bez ścieżki i końcowego /. Klucz API nie może trafić do HTML, JavaScriptu przeglądarki, repozytorium ani adresu URL.
Przykład lokalny: node --env-file=.env server.mjs, następnie otwórz http://localhost:8080. Do lokalnego testu ustaw w index.html adres skryptu http://localhost:3100/embed.js. W produkcji użyj https://api.zapytajkodeks.pl/embed.js. Node 22 lub nowszy, bez npm install.
2. Dodaj skrypt i przycisk do HTML
<button data-zk-widget-open>Zapytaj o prawo</button>
<script defer
src="https://api.zapytajkodeks.pl/embed.js"
data-session-endpoint="/api/zk-widget-session"
data-launcher="true"></script>data-zk-widget-open możesz dodać do istniejącego przycisku lub linku. data-launcher="false" ukrywa pływającą ikonę. Endpoint sesji MUSI znajdować się na tej samej domenie co strona klienta. Gotowy session-handler.mjs obsługuje go po stronie backendu.
Skrypt https://api.zapytajkodeks.pl/embed.js otwiera iframe https://api.zapytajkodeks.pl/widget/chat/. Widżet i API działają na tej samej domenie. Nie trzeba wdrażać osobnego serwera widżetu. Nie przenoś samego embed.js na swoją domenę — jego kopia w pakiecie służy audytowi.
3. Wybierz sposób dostępu
Publiczna strona: użyj przykładu server.mjs. Każdy odwiedzający może korzystać z budżetu analiz firmy. Handler sprawdza origin i ogranicza tworzenie sesji do 20/min na adres połączenia. W produkcji ustaw dodatkowo limity i ochronę przed botami w swoim proxy/WAF. Mapa w przykładzie jest lokalna dla procesu; przy wielu instancjach użyj współdzielonego limitera. Nagłówek Origin sam nie jest zabezpieczeniem przed botem.
Aplikacja po zalogowaniu: zamontuj ten sam handler we własnym backendzie i przekaż funkcję odczytującą sprawdzoną sesję użytkownika:
const handler = createWidgetSessionHandler({
apiKey: process.env.ZK_API_KEY,
apiUrl: process.env.ZK_API_URL,
siteOrigin: "https://app.firma.pl",
mode: "authenticated",
getSubject: async (request) => {
const session = await yourExistingSessionService(request);
// Weryfikacja dostępu do widżetu odbywa się w Twoim systemie.
return session?.canUseLegalChat ? String(session.user.id) : null;
},
});yourExistingSessionService zastąp prawdziwą obsługą sesji Twojej aplikacji. Nigdy nie pobieraj ID użytkownika z treści żądania ani niezaufanego nagłówka. Jeśli używasz Express, podłącz handler po swoim middleware sesji. Za reverse proxy ustaw getRateLimitKey zgodnie z zaufaną konfiguracją proxy; nie ufaj dowolnemu X-Forwarded-For.
Przy wylogowaniu lub zmianie konta wywołaj window.ZapytajKodeks?.reset(). Widżet dodatkowo prosi backend firmy o nowy token przed każdym pytaniem i czyści rozmowę po wykryciu innej tożsamości. Już wydany token wygasa po 15 minutach; wylogowanie partnera nie unieważnia go natychmiast na serwerze Zapytaj Kodeks. Unieważnienie klucza API blokuje kolejne żądania również istniejących tokenów. Trwająca analiza wymaga osobnego przerwania (reset).
Zachowanie i koszty
- Jedno ukończone pytanie = jedna analiza AI z pakietu firmy. Wyszukiwania w ramach analizy nie pomniejszają puli zwykłych requestów.
- Wydanie tokena i otwarcie okna nie zużywają analiz. Globalne limity API, równoległość i RPM obowiązują wszystkie klucze firmy.
- Widżet przechowuje rozmowę wyłącznie w pamięci otwartej strony. Zwinięcie zachowuje wiadomości; odświeżenie, zamknięcie strony lub ponowne otwarcie przeglądarki je usuwa. Nie używa localStorage, sessionStorage ani ciasteczek do historii.
- Odpowiedź uwzględnia maksymalnie 8 poprzednich wiadomości, do 10 000 znaków każda. Pytanie: do 10 000 znaków. Długie rozmowy mogą wymagać ponownego podania kontekstu.
- Widżet pokazuje przebieg researchu, odpowiedź z cytowaniami inline i blokowymi oraz zweryfikowane cytaty, tak jak główna aplikacja. Kliknięcie cytowania otwiera podgląd źródła w oknie czatu. Dla orzeczeń i interpretacji pobierana jest pełna oryginalna treść, bez streszczeń AI. Pierwsze pobranie dokumentu zużywa 1 zwykły request; kolejne otwarcia w tej samej rozmowie korzystają z pamięci lokalnej.
- Brak załączników, tworzenia dokumentów i archiwum rozmów. Źródła otwiera użytkownik kliknięciem.
- To nie jest obietnica zerowej retencji u dostawcy AI ani przetwarzania wyłącznie w EOG. Te opcje wymagają osobnego wdrożenia i uzgodnienia.
API skryptu i polityka CSP
window.ZapytajKodeks.open(), .close(), .reset(), .destroy() są dostępne po załadowaniu skryptu. destroy usuwa widżet i kończy rozmowę. Po ponownym montażu SPA trzeba ponownie załadować skrypt. reset rozpoczyna nową pustą rozmowę.
Jeśli strona ma CSP, dodaj domenę widżetu do script-src i frame-src, a własny endpoint do connect-src (zwykle 'self'). Skrypt tworzy style w Shadow DOM; rygorystyczna polityka style-src wymaga dopuszczenia tych stylów, np. uzgodnionym hashem dokładnej treści bloku style. Nie wyłączaj całej polityki CSP. Połączenia z API wykonuje iframe na domenie widżetu. Nie wymaga cookies stron trzecich.
Przed uruchomieniem sprawdź otwarcie, odpowiedź ze źródłami, telefon, wylogowanie, wyczerpanie limitu i odświeżenie strony. Błąd 401 oznacza brak ważnej sesji/klucza, 403 brak aktywnego dostępu, 429 limit, 503 konfigurację lub czasową niedostępność.
Dyktowanie pytania
Mikrofon w polu pytania uruchamia nagrywanie po zgodzie przeglądarki. Zatwierdzenie zamienia mowę na tekst, który można poprawić przed wysłaniem. Limit nagrania w interfejsie: 2 minuty; maksymalny rozmiar: 5 MB. Zamknięcie widżetu, nowa rozmowa i opuszczenie strony kończą nagrywanie oraz anulują oczekującą transkrypcję. Wiadomości pozostają w bieżącej sesji.
Udana transkrypcja zużywa 1 zwykły request, a wysłanie pytania 1 analizę AI. Nieudana transkrypcja zwalnia rezerwację. Audio i wynik nie są zapisywane przez endpoint; nagranie jest przesyłane do dostawcy transkrypcji na standardowych warunkach przetwarzania.
Dyktowanie wymaga HTTPS (lokalnie localhost) i mikrofonu w przeglądarce. Skrypt nadaje iframe uprawnienie allow="microphone; clipboard-write". Jeżeli witryna firmy ustawia Permissions-Policy, musi pozwalać na mikrofon z domeny widżetu, np. microphone=(self "https://api.zapytajkodeks.pl"); nadrzędnego microphone=() iframe nie może obejść. Nowy embed.js należy wdrożyć razem z aktualizacją aplikacji.
Własny przycisk i przykładowe pytanie
ZapytajKodeks.open({ question: "Na co zwrócić uwagę przy najmie lokalu użytkowego?" });
ZapytajKodeks.setLauncherVisible(false); // opcjonalnie: tylko własny przycisk aplikacjiPytanie jest tylko wpisywane do pola. Użytkownik może je zmienić i samodzielnie wysłać. Treść dokumentów ani dane sprawy nie są automatycznie przekazywane do widżetu.
Kolor, nagłówek i powitanie
Ustaw atrybuty na skrypcie osadzającym:
<script defer src="https://api.zapytajkodeks.pl/embed.js"
data-session-endpoint="/api/zk-widget-session"
data-title="Asystent kancelarii"
data-subtitle="Pomoc w pytaniach prawnych"
data-launcher-color="#194d75"
data-welcome-text="Dzień dobry! W jakiej sprawie możemy pomóc?"></script>Możesz też zmienić wygląd po załadowaniu skryptu, również gdy czat jest otwarty:
ZapytajKodeks.configure({
title: "Asystent kancelarii",
subtitle: "Pomoc w pytaniach prawnych",
launcherColor: "#b6202a",
welcomeText: "Dzień dobry!\n\nOpisz swoją sprawę."
});
ZapytajKodeks.open();Kolor: #RGB lub #RRGGBB. Ten sam kolor otrzymują przycisk AI, dymki użytkownika, przycisk wysyłania i akcenty interfejsu. Tekst na kolorowym tle automatycznie dobiera biały lub czarny kolor dla czytelności. Powitanie: zwykły tekst, do 1000 znaków; nowe linie rozdzielają akapity. HTML nie jest wykonywany. Puste powitanie przywraca domyślny tekst, a niepoprawny kolor pozostawia poprzedni. Powitanie zmienia tylko interfejs — nie instrukcje modelu AI. Konfiguracja pozostaje po rozpoczęciu nowej rozmowy.
Tytuł (title / data-title) domyślnie brzmi „Zapytaj Kodeks”, a opis (subtitle / data-subtitle) „Asystent AI dla prawników”. Limit to odpowiednio 80 i 160 znaków. Pominięte pola zachowują obecną wartość, pusty tekst przywraca domyślną. Nagłówek renderuje zwykły tekst, bez HTML; długie teksty zajmują maksymalnie dwa wiersze. Zmiana nagłówka nie zmienia podpisu „Odpowiedzi od Zapytaj Kodeks” ani instrukcji modelu.
Blokowanie pytań wybranemu użytkownikowi
W backendzie firmy dodaj getChatAccess. Funkcja działa przed każdym nowym pytaniem i transkrypcją. Otrzymuje subject z Twojej sprawdzonej sesji, a nie ID przesłane przez przeglądarkę.
const handler = createWidgetSessionHandler({
apiKey: process.env.ZK_API_KEY,
apiUrl: process.env.ZK_API_URL,
siteOrigin: "https://app.firma.pl",
mode: "authenticated",
getSubject: async (request) => {
const session = await yourExistingSessionService(request);
return session?.canUseLegalChat ? String(session.user.id) : null;
},
getChatAccess: async (_request, { subject, purpose }) => {
const user = await yourDatabase.users.findById(subject);
if (user.legalChatBlocked) return {
allowed: false,
title: "Limit pytań został wykorzystany",
message: "Skontaktuj się z administratorem, aby kontynuować. Możesz nadal przeczytać tę rozmowę.",
};
return { allowed: true };
},
});yourDatabase i yourExistingSessionService zastąp własną bazą i obsługą logowania. allowed: false blokuje nowe pytania i dyktowanie, ale nie kasuje rozmowy ani dostępu do jej źródeł. Użytkownik widzi modal z rozmytym tłem i przyciskiem „Wróć do rozmowy”. Po zamknięciu może czytać; następna próba pytania ponownie sprawdza uprawnienia i otwiera modal, jeśli blokada nadal trwa. Zdjęcie blokady na backendzie pozwala wysłać następne pytanie. Tytuł ma do 120 znaków, opis do 800; to zwykły tekst, bez HTML.
Przy własnym limicie liczbowym rezerwuj miejsce atomowo w swojej bazie w getChatAccess dla purpose === "chat". Zwróć { allowed: true, grantId, remainingQuestions, title, message }: grantId oznacza jedno przyznane pytanie (1–100 liter, cyfr lub ._:-), a remainingQuestions to pozostała liczba po przyznaniu tego pytania. Wartość 0 pokazuje modal po zakończeniu ostatniej odpowiedzi. Dla purpose === "voice" sprawdź blokadę bez rezerwowania pytania. Nie ustalaj limitu na podstawie danych od przeglądarki.
Aktualny handler tworzy tokeny oddzielne dla chat, source i voice. Token źródła nie pozwala wysłać pytania ani audio. Token chat jest jednorazowy: API odrzuca ponowne wykonanie tego samego grantId, niezależnie od odświeżenia tokena i nagłówka idempotencji. Błąd po przyjęciu pytania również zużywa przyznane miejsce demo; to osobny limit od rozliczenia udanej analizy AI. Nie ponawiaj płatnego żądania automatycznie.
Już wydanego, niewykorzystanego tokena nie cofa sama zmiana stanu w bazie firmy: wygasa po 15 minutach i pozwala na najwyżej jedno pytanie. Blokada jest sprawdzana przed wydaniem kolejnego. Trwająca analiza może się zakończyć. Własny backend sesji powinien zachować ten sam protokół: przyjąć { purpose: "chat" | "source" | "voice" }, wydać token o tym zakresie albo zwrócić 403 z { error_code: "WIDGET_CHAT_BLOCKED", block: { title, message } }. Nie blokuj sesji source, jeśli użytkownik ma zachować możliwość czytania źródeł.