Kanál

Vložení widgetu na váš web

Tento průvodce ukazuje, jak přidat chat Breezaro na jakýkoli web vložením krátkého úryvku do HTML vašich stránek.

  • Funguje na jakékoli platformě, kde můžete upravit HTML nebo přidat blok vlastního kódu.
  • Není potřeba programovat. Zkopírovat, vložit, zveřejnit.
  • Hotovo během několika minut.
Krok za krokem

Kroky instalace

Postupujte podle těchto kroků pro přidání widgetu Breezaro na váš web.

1. Otevřete editor HTML nebo vlastního kódu na svém webu

Nastavení vlastního kódu v tvůrci webu

2. Vložte úryvek Breezaro

Vložení úryvku do bloku vlastního kódu
<script data-app-id="YOUR_APP_ID" src="https://breezaro.com/breezaro-widget.js" defer></script>

3. Vložte jej na všechny stránky těsně před uzavírací značku body

Výběr, kde se úryvek spustí
Výběr, kde se úryvek spustí

4. Uložte a zveřejněte

Widget spuštěný na webu
Krok za krokem

Ovládání widgetu z vašeho webu

Widget vystavuje malé JavaScriptové API. Použijte ho, když chcete chat otevřít vlastním tlačítkem nebo asistentovi sdělit, kdo je přihlášený návštěvník.

identify
Přihlásí návštěvníka, aby asistent mohl pracovat s jeho ověřenou identitou. Kvůli zpětné kompatibilitě identify stále přijímá označení pouze hodnotou resetId, nové integrace ale mají vždy předávat resetId i resetAt.
open
Otevře okno chatu.
close
Zavře okno chatu.
reset
Ukončí konverzaci navázanou na aktuální identitu. Zavolejte ho při odhlášení nebo přepnutí účtu. Potvrzovací callback vrátí durable; použitelnou dvojici resetId a resetAt vrací jen při úspěšném zápisu resetovací značky. Po resetu je neoznačené identify odmítnuto až do znovunačtení stránky, takže při dalším volání identify na téže stránce předejte vrácenou dvojici.
setContext
Nahradí nebo vymaže dočasný, neověřený snímek košíku, který může asistent použít s dalšími zprávami návštěvníka. Označené setContext je tvrzením o celé generaci: resetId bez resetAt je odmítnuto. Po resetu předejte obě hodnoty jako čtvrtý argument při dalším nastavování kontextu na téže stránce.
setPosition
Dynamicky nastaví pozici nebo odsazení tlačítka a okna chatu (např. při zobrazení cookie lišty nebo plovoucího pruhu). Předejte x a y pro relativní posun nebo offsetX, offsetY, position pro přepsání. Pro návrat k výchozímu nastavení předejte nulový posun nebo null.

Volání po načtení widgetu

Jakmile načítací skript proběhne, je window.breezaro připravené a můžete volat kterýkoli příkaz přímo. Tohle je běžný případ u jednostránkových aplikací nebo když se návštěvník přihlásí bez znovunačtení stránky.

JavaScript
breezaro('open');

Volání před načtením widgetu

Pokud musí volání proběhnout dřív, třeba když se stránka vykresluje na serveru a už ví, kdo návštěvník je, vložte nad načítací skript frontu. Ta volání zaznamená a přehraje je, jakmile je widget připravený.

HTML
<script>
const BREEZARO_APP_ID = 'YOUR_APP_ID';

function breezaroResetGeneration() {
  try {
    const reset = JSON.parse(
      localStorage.getItem('breezaro-widget_reset_' + BREEZARO_APP_ID),
    );

    if (typeof reset?.id !== 'string' || !reset.id) {
      return { resetId: null, resetAt: null };
    }

    return {
      resetId: reset.id,
      resetAt: Number.isFinite(reset.at) ? reset.at : null,
    };
  } catch {
    return { resetId: null, resetAt: null };
  }
}

  window.breezaro =
    window.breezaro ||
    function () {
      (window.breezaro.q = window.breezaro.q || []).push(arguments);
    };
  breezaro('open');

  // Queue 'identify' only WITH the current reset generation. An untagged queued
  // identify is refused once this browser has ever signed out, because
  // nothing bounds when it was pushed.
  breezaro('identify', identity, undefined, breezaroResetGeneration());
</script>

Dynamická změna pozice (např. cookie lišta)

Pokud se na vašem webu zobrazí plovoucí cookie lišta, informační pruh nebo spodní lišta na mobilu, můžete dynamicky posunout widget, aby se nepřekrýval. Po zavření lišty jej snadno vrátíte zpět.

Předáním x a y přidáte relativní posun k nakonfigurovanému odsazení. Například y: 80 posune widget o 80 px výše. Předáním x: 0, y: 0 se widget plynule vrátí přesně na své nakonfigurované výchozí místo.

Můžete také zadat absolutní přepis pomocí offsetX, offsetY nebo změnit rohovou pozici pomocí position. Volání resetPosition nebo předání null zruší všechny dynamické přepisy.

JavaScript
// 1. Relativní posun nahoru při zobrazení cookie lišty (např. výška 80px):
breezaro('setPosition', { y: 80 });

// 2. Návrat na výchozí pozici po zavření cookie lišty:
breezaro('setPosition', { x: 0, y: 0 });

// 3. Alternativně můžete použít i přímé nastavení rohové pozice nebo absolutního odsazení:
breezaro('setPosition', {
  position: 'bottom-left', // 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'
  offsetX: 24,
  offsetY: 80,
});

// 4. Kompletní reset všech dynamických přepisů do původního stavu z administrace:
breezaro('resetPosition');

Přidání aktuálního košíku do kontextu

Použijte setContext verze 1, když chcete, aby asistent rozuměl aktuálnímu obsahu košíku návštěvníka. Předejte vždy jeden úplný snímek. Každé platné volání nahradí celý předchozí snímek; jednotlivá pole se nikdy neslučují. Hodnotou null nebo prázdným objektem snímek vymažete.

Payload je striktní: version musí mít hodnotu 1. cart přijímá pouze currency, subtotal, total a items; currency a items jsou povinné. Každá položka přijímá pouze id, sku, name, variant, quantity, unitPrice a url; name a quantity jsou povinné. Ceny jsou nezáporná konečná čísla v měnové jednotce určené hodnotou currency, nikoli v haléřích nebo centech. URL musí být absolutní a používat HTTP nebo HTTPS; parametry dotazu a fragment se odstraní dřív, než se URL dostane k asistentovi. Snímek může obsahovat nejvýše 20 položek a jeho úplný blok pro prompt po bezpečném převedení speciálních znaků se musí vejít do 8 KiB v UTF-8. Neznámá pole jsou odmítnuta.

Widget může zahájit novou konverzaci také vlastním ovládacím prvkem Nová konverzace. Poslouchejte událost breezaro:conversation-changed a aktuální košík nastavte znovu s hodnotami resetId i resetAt z event.detail. Obě hodnoty zachyťte před každým asynchronním načítáním košíku a předejte je jako čtvrtý argument, aby pozdější reset odmítl zastaralou odpověď, i když znovu použije stejné id.

JavaScript
const BREEZARO_APP_ID = 'YOUR_APP_ID';

function breezaroResetGeneration() {
  try {
    const reset = JSON.parse(
      localStorage.getItem('breezaro-widget_reset_' + BREEZARO_APP_ID),
    );

    if (typeof reset?.id !== 'string' || !reset.id) {
      return { resetId: null, resetAt: null };
    }

    return {
      resetId: reset.id,
      resetAt: Number.isFinite(reset.at) ? reset.at : null,
    };
  } catch {
    return { resetId: null, resetAt: null };
  }
}

async function onContextResult(snapshot, result, mayRetry) {
  if (result?.ok) return;

  if (
    !mayRetry ||
    result?.reason !== 'reset' ||
    typeof result.resetId !== 'string' ||
    (!Number.isFinite(result.resetAt) && result.resetAt !== null)
  ) {
    console.warn('Breezaro setContext failed:', result?.reason);
    return;
  }

  const generation = { resetId: result.resetId, resetAt: result.resetAt };

  try {
    const clearsContext =
      snapshot === null ||
      (typeof snapshot === 'object' &&
        snapshot !== null &&
        !Array.isArray(snapshot) &&
        Object.keys(snapshot).length === 0);

    // A cart snapshot may belong to the pre-reset account. Fetch it again;
    // either documented clear shape carries no account data.
    const currentSnapshot = clearsContext
      ? null
      : await fetch('/my-api/cart').then((response) => response.json());

    applyCartContext(currentSnapshot, generation, false);
  } catch {
    console.warn('Breezaro cart refresh failed');
  }
}

function applyCartContext(snapshot, generation, mayRetry = true) {
  window.breezaro(
    'setContext',
    snapshot,
    (result) => void onContextResult(snapshot, result, mayRetry),
    generation ?? breezaroResetGeneration(),
  );
}

function clearCartContext() {
  applyCartContext(null);
}

const cartSnapshot = {
  version: 1,
  cart: {
    currency: 'CZK',
    subtotal: 1490,
    total: 1390,
    items: [
      {
        id: 'product-123',
        sku: 'WREATH-NATURAL',
        name: 'Natural wreath',
        variant: '40 cm',
        quantity: 1,
        unitPrice: 1390,
        url: 'https://shop.example.com/products/natural-wreath',
      },
    ],
  },
};

applyCartContext(cartSnapshot);

window.addEventListener('breezaro:conversation-changed', async (event) => {
  const { appId, resetId, resetAt } = event.detail ?? {};

  if (
    appId !== BREEZARO_APP_ID ||
    typeof resetId !== 'string' ||
    !Number.isFinite(resetAt)
  ) return;

  // Capture BOTH values before this round trip. A later reset may reuse its id,
  // and resetAt still makes the outdated snapshot fail closed.
  const resetGeneration = { resetId, resetAt };
  const snapshot = await fetch('/my-api/cart').then((response) =>
    response.json(),
  );

  applyCartContext(snapshot, resetGeneration);
});

Snímek zůstává pouze v paměti aktuální karty prohlížeče. Neukládá se do úložiště prohlížeče ani se nesdílí s dalšími kartami, takže ho váš web musí po úplném načtení stránky nastavit znovu. Příkaz reset snímek vymaže; zavolejte ho při odhlášení nebo přepnutí účtu. Aktuální resetId i resetAt načtěte před každým asynchronním načítáním košíku a obě hodnoty předejte příkazu setContext jako čtvrtý argument. Pokud během načítání proběhne reset, widget opožděný snímek odmítne. Potvrzovací callback při odmítnutí vrátí aktuální dvojici, pokud ji lze bezpečně načíst; ukázka pod touto generací košík znovu načte a volání jednou zopakuje. Nikdy znovu nepoužije snímek z doby před resetem ani nevytvoří smyčku. Vymazání kontextu zopakuje přímo s hodnotou null a košík nenačítá. Po kterémkoli resetu musí každé volání setContext na téže stránce nést přesnou dvojici. Volání zařazené do fronty ji musí nést vždy, když má prohlížeč značku resetu. Samotné volání setContext neposílá žádný požadavek API ani nespouští LLM; nejnovější snímek se přikládá ke každé zprávě návštěvníka, dokud ho nenahradíte nebo nevymažete.

Obnovení podpisu na dlouho otevřené stránce

Platnost se kontroluje ve chvíli, kdy widget payload odešle, ne když zavoláte identify. Návštěvník, který stránku načte, nechá ji na pozadí a chat otevře až po hodinách, předloží několik hodin starý podpis a ten je odmítnutý. Asistent si místo toho řekne o kód z e-mailu.

Při běžném prohlížení se podpis obnovuje sám, protože každé načtení stránky razí nový. Jednostránková aplikace nebo stránka otevřená celé odpoledne ale ne. Stáhněte si v takovém případě ze svého backendu čerstvý podpis a zavolejte identify znovu, jakmile se karta vrátí do popředí.

Generaci resetu si přečtěte ještě před odesláním požadavku a předejte ji jako čtvrtý argument. Odhlášení, které proběhne během požadavku, tuto generaci posune a widget potom opožděnou identitu odmítne, místo aby zapsal identitu předchozího účtu do konverzace, kterou už používá další návštěvník.

JavaScript
const BREEZARO_APP_ID = 'YOUR_APP_ID';

function breezaroResetGeneration() {
  try {
    const reset = JSON.parse(
      localStorage.getItem('breezaro-widget_reset_' + BREEZARO_APP_ID),
    );

    if (typeof reset?.id !== 'string' || !reset.id) {
      return { resetId: null, resetAt: null };
    }

    return {
      resetId: reset.id,
      resetAt: Number.isFinite(reset.at) ? reset.at : null,
    };
  } catch {
    return { resetId: null, resetAt: null };
  }
}

document.addEventListener('visibilitychange', async () => {
  if (document.visibilityState !== 'visible') return;

  // Read the generation BEFORE the round trip, not after it.
  const resetGeneration = breezaroResetGeneration();

  // Your own endpoint: it signs a fresh payload server-side, where the
  // secret lives, and returns { email, name, externalId, issuedAt, nonce, hmac }.
  const identity = await fetch('/my-api/breezaro-identity').then((r) => r.json());

  breezaro('identify', identity, undefined, resetGeneration);
});

Při odhlášení návštěvníka widget resetujte

Zavolejte reset ve chvíli, kdy se někdo odhlašuje, a vlastní odhlášení pak dokončete bez ohledu na to, co widget odpoví: zablokovaný nebo nenačtený skript nesmí návštěvníka nikdy uvěznit v relaci, kterou ukončil. Úspěšný potvrzovací callback má ok nastavené na true a vrací durable; použitelnou dvojici resetId a resetAt vrací jen při úspěšném zápisu resetovací značky. Ukázka odhlášení přechází na jinou stránku, proto kontroluje pouze durable; kód, který na stránce zůstává, má použít vrácenou dvojici. Pokud se reset v krátkém časovém limitu nepotvrdí jako trvale uložený, zapište čerstvou značku resetu do resetovacího klíče widgetu sami, jak to dělá ukázka, aby další načtení stránky začalo s čistou konverzací. Resetujte i pokaždé, když se přihlášená osoba změní nebo vyprší platnost relace.

JavaScript
const BREEZARO_APP_ID = 'YOUR_APP_ID';

async function signOut() {
  // Ask the widget to reset, then sign out no matter what it answers: a blocked
  // or unmounted loader must never trap the user in a session they ended.
  const resetResult = await new Promise((resolve) => {
    const done = setTimeout(() => resolve(null), 500);
    const finish = (result) => {
      clearTimeout(done);
      resolve(result);
    };

    // Missing or broken loaders enter the durable fallback immediately; the
    // timeout covers a mounted widget that never answers.
    if (typeof window.breezaro !== 'function') {
      finish(null);
      return;
    }

    try {
      window.breezaro('reset', undefined, finish);
    } catch (_) {
      finish(null);
    }
  });

  // A successful reset also returns resetId and resetAt for calls that continue
  // on this page. Sign-out navigates away, so only durable matters here.
  // If the widget could not confirm a durable reset (ad blocker, storage error,
  // never mounted), write the marker the loader reads on the next load so the
  // next visitor starts a clean conversation instead of restoring this one.
  if (!resetResult?.ok || !resetResult.durable) {
    try {
      localStorage.setItem(
        'breezaro-widget_reset_' + BREEZARO_APP_ID,
        JSON.stringify({ id: crypto.randomUUID(), at: Date.now() }),
      );
    } catch (_) {}
  }

  await fetch('/my-api/sign-out', { method: 'POST' });
  window.location.assign('/');
}