@spilki/widget 1.0.33 → 1.0.34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- const F = `
1
+ const R = `
2
2
  <style>
3
3
  :host {
4
4
  all: initial;
@@ -71,13 +71,13 @@ const F = `
71
71
  <span class="badge" hidden aria-hidden="true">0</span>
72
72
  </button>
73
73
  `;
74
- function R(o) {
74
+ function K(r) {
75
75
  const e = document.createElement("div");
76
- e.setAttribute("part", "bubble-root"), e.setAttribute("data-position", o.position), e.style.setProperty("--spilki-accent", o.color);
76
+ e.setAttribute("part", "bubble-root"), e.setAttribute("data-position", r.position), e.style.setProperty("--spilki-accent", r.color);
77
77
  const t = e.attachShadow({ mode: "open" });
78
- t.innerHTML = F;
78
+ t.innerHTML = R;
79
79
  const s = t.querySelector("button"), i = t.querySelector(".badge");
80
- return s.addEventListener("click", () => o.onClick()), {
80
+ return s.addEventListener("click", () => r.onClick()), {
81
81
  element: e,
82
82
  mount() {
83
83
  document.body.appendChild(e);
@@ -85,20 +85,20 @@ function R(o) {
85
85
  destroy() {
86
86
  e.remove();
87
87
  },
88
- setOpen(a) {
89
- s.setAttribute("aria-expanded", String(a)), s.setAttribute("aria-label", a ? "Close chat" : "Open chat");
88
+ setOpen(l) {
89
+ s.setAttribute("aria-expanded", String(l)), s.setAttribute("aria-label", l ? "Close chat" : "Open chat");
90
90
  },
91
- setBadge(a) {
92
- const n = Math.max(0, a), l = s.getAttribute("aria-expanded") === "true";
91
+ setBadge(l) {
92
+ const n = Math.max(0, l), a = s.getAttribute("aria-expanded") === "true";
93
93
  if (n === 0) {
94
- i.toggleAttribute("hidden", !0), i.textContent = "0", l || s.setAttribute("aria-label", "Open chat");
94
+ i.toggleAttribute("hidden", !0), i.textContent = "0", a || s.setAttribute("aria-label", "Open chat");
95
95
  return;
96
96
  }
97
- i.toggleAttribute("hidden", !1), i.textContent = n > 99 ? "99+" : String(n), l || s.setAttribute("aria-label", `Open chat, ${n} unread`);
97
+ i.toggleAttribute("hidden", !1), i.textContent = n > 99 ? "99+" : String(n), a || s.setAttribute("aria-label", `Open chat, ${n} unread`);
98
98
  }
99
99
  };
100
100
  }
101
- const K = "https://api.spilki.app", C = {
101
+ const H = "https://api.spilki.app", I = {
102
102
  welcome: "Hi! I'm your assistant.",
103
103
  placeholder: "Type a message…",
104
104
  sendLabel: "Send",
@@ -109,57 +109,57 @@ const K = "https://api.spilki.app", C = {
109
109
  offline: "Unable to connect. Please try again later.",
110
110
  title: "Spilki Assistant"
111
111
  }, w = {
112
- apiBase: K,
112
+ apiBase: H,
113
113
  position: "bottom-right",
114
114
  theme: "auto",
115
115
  color: "#6366f1",
116
- welcome: C.welcome,
116
+ welcome: I.welcome,
117
117
  persist: !0,
118
118
  sound: !0,
119
- i18n: C
119
+ i18n: I
120
120
  };
121
- function H(o) {
122
- var t, s, i, r, a, n, l, g;
123
- const e = { ...C, ...(t = o.i18n) != null ? t : {} };
121
+ function z(r) {
122
+ var t, s, i, o, l, n, a, g;
123
+ const e = { ...I, ...(t = r.i18n) != null ? t : {} };
124
124
  return {
125
125
  ...w,
126
- ...o,
127
- apiBase: (s = o.apiBase) != null ? s : w.apiBase,
126
+ ...r,
127
+ apiBase: (s = r.apiBase) != null ? s : w.apiBase,
128
128
  i18n: e,
129
- welcome: (i = o.welcome) != null ? i : e.welcome,
130
- position: (r = o.position) != null ? r : w.position,
131
- theme: (a = o.theme) != null ? a : w.theme,
132
- color: (n = o.color) != null ? n : w.color,
133
- persist: (l = o.persist) != null ? l : w.persist,
134
- sound: (g = o.sound) != null ? g : w.sound
129
+ welcome: (i = r.welcome) != null ? i : e.welcome,
130
+ position: (o = r.position) != null ? o : w.position,
131
+ theme: (l = r.theme) != null ? l : w.theme,
132
+ color: (n = r.color) != null ? n : w.color,
133
+ persist: (a = r.persist) != null ? a : w.persist,
134
+ sound: (g = r.sound) != null ? g : w.sound
135
135
  };
136
136
  }
137
- function z(o = "msg") {
138
- return typeof crypto != "undefined" && crypto.randomUUID ? crypto.randomUUID() : `${o}-${Math.random().toString(16).slice(2)}`;
137
+ function _(r = "msg") {
138
+ return typeof crypto != "undefined" && crypto.randomUUID ? crypto.randomUUID() : `${r}-${Math.random().toString(16).slice(2)}`;
139
139
  }
140
- function _() {
141
- var o, e;
142
- return (e = (o = window.matchMedia) == null ? void 0 : o.call(window, "(prefers-color-scheme: dark)").matches) != null ? e : !1;
140
+ function G() {
141
+ var r, e;
142
+ return (e = (r = window.matchMedia) == null ? void 0 : r.call(window, "(prefers-color-scheme: dark)").matches) != null ? e : !1;
143
143
  }
144
- function P(o) {
145
- return o === "light" || o === "dark" ? o : _() ? "dark" : "light";
144
+ function P(r) {
145
+ return r === "light" || r === "dark" ? r : G() ? "dark" : "light";
146
146
  }
147
- function x(o, e = 30) {
148
- return o.slice(-e);
147
+ function S(r, e = 30) {
148
+ return r.slice(-e);
149
149
  }
150
- function G(o) {
151
- if (!o || o.length === 0) return;
150
+ function q(r) {
151
+ if (!r || r.length === 0) return;
152
152
  const e = window.location.origin;
153
- o.includes(e) || console.warn(
154
- `SpilkiWidget: current origin ${e} not in allowedOriginsHint: ${o.join(", ")}`
153
+ r.includes(e) || console.warn(
154
+ `SpilkiWidget: current origin ${e} not in allowedOriginsHint: ${r.join(", ")}`
155
155
  );
156
156
  }
157
- const q = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
158
- function Y(o) {
159
- const e = new Date(o), t = /* @__PURE__ */ new Date(), s = (l) => String(l).padStart(2, "0"), i = `${s(e.getHours())}:${s(e.getMinutes())}`, r = new Date(t.getFullYear(), t.getMonth(), t.getDate()).getTime(), a = r - 864e5, n = new Date(e.getFullYear(), e.getMonth(), e.getDate()).getTime();
160
- return n === r ? `Today at ${i}` : n === a ? `Yesterday at ${i}` : `${q[e.getMonth()]} ${e.getDate()}, ${i}`;
157
+ const Y = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
158
+ function V(r) {
159
+ const e = new Date(r), t = /* @__PURE__ */ new Date(), s = (a) => String(a).padStart(2, "0"), i = `${s(e.getHours())}:${s(e.getMinutes())}`, o = new Date(t.getFullYear(), t.getMonth(), t.getDate()).getTime(), l = o - 864e5, n = new Date(e.getFullYear(), e.getMonth(), e.getDate()).getTime();
160
+ return n === o ? `Today at ${i}` : n === l ? `Yesterday at ${i}` : `${Y[e.getMonth()]} ${e.getDate()}, ${i}`;
161
161
  }
162
- const V = ':host{--spilki-bg-light: #ffffff;--spilki-bg-dark: #0f172a;--spilki-text-light: #0f172a;--spilki-text-dark: #f8fafc;--spilki-border-light: rgba(15, 23, 42, .1);--spilki-border-dark: rgba(148, 163, 184, .25);--spilki-muted-light: #64748b;--spilki-muted-dark: #94a3b8;--spilki-shadow: 0 10px 40px rgba(15, 23, 42, .2);--spilki-radius: 16px;--spilki-font: "Inter", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;color:inherit;font-family:var(--spilki-font)}.wrapper{position:relative;width:100%;height:100%;display:flex;flex-direction:column;background:var(--spilki-surface);color:var(--spilki-text);border-radius:var(--spilki-radius);box-shadow:var(--spilki-shadow);border:1px solid var(--spilki-border);overflow:hidden}header{display:flex;align-items:center;justify-content:space-between;padding:.75rem 1rem;background:var(--spilki-surface);border-bottom:1px solid var(--spilki-border)}header h1{font-size:1rem;margin:0;display:flex;align-items:center;gap:.5rem}header button.close{border:none;background:transparent;color:inherit;font-size:1.25rem;cursor:pointer}header button.close:focus-visible{outline:2px solid var(--spilki-accent);outline-offset:2px;border-radius:4px}header .status-dot{width:10px;height:10px;border-radius:999px;background:var(--spilki-accent)}.messages{flex:1;overflow-y:auto;padding:1rem;display:flex;flex-direction:column;gap:.75rem;scrollbar-width:thin;scrollbar-color:rgba(148,163,184,.3) transparent;scroll-behavior:smooth}.message{display:flex;flex-direction:column;gap:.15rem;max-width:85%;width:fit-content;line-height:1.4;word-wrap:break-word;overflow-wrap:anywhere;white-space:pre-wrap}.message.user{align-self:flex-end;align-items:flex-end}.message .bubble{padding:.6rem .8rem;border-radius:1rem;background:#6366f126}.message.user .bubble{background:var(--spilki-accent);color:#fff}.message.bot .bubble{background:#94a3b826}.msg-time{font-size:.7rem;color:var(--spilki-muted);margin-top:2px}.date-separator{display:flex;align-items:center;gap:.5rem;padding:.4rem 0;font-size:.72rem;color:var(--spilki-muted);white-space:nowrap}.date-separator:before,.date-separator:after{content:"";flex:1;height:1px;background:var(--spilki-border)}.scroll-bottom{position:absolute;right:1rem;bottom:88px;width:38px;height:38px;border:none;border-radius:999px;background:var(--spilki-accent);color:#fff;box-shadow:0 8px 20px #0f172a40;cursor:pointer;opacity:0;pointer-events:none;transform:translateY(4px);transition:opacity .18s ease,transform .18s ease;z-index:3}.scroll-bottom.visible{opacity:1;pointer-events:auto;transform:translateY(0)}.scroll-arrow{font-size:.8rem;line-height:1}.scroll-count{position:absolute;top:-5px;right:-5px;min-width:16px;height:16px;border-radius:999px;background:#ef4444;color:#fff;font-size:10px;line-height:16px;text-align:center;padding:0 4px}.suggested-replies{display:flex;flex-wrap:wrap;gap:.4rem;padding:.5rem 1rem;border-top:1px solid var(--spilki-border)}.suggested-chip{border:1px solid var(--spilki-accent);background:transparent;color:var(--spilki-accent);border-radius:999px;padding:.35rem .75rem;font-size:.8rem;font-family:inherit;cursor:pointer;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;transition:background .15s,color .15s}.suggested-chip:hover{background:var(--spilki-accent);color:#fff}.suggested-chip:focus-visible{outline:2px solid var(--spilki-accent);outline-offset:2px}.input-area{padding:.5rem .75rem}.input-wrap{display:flex;align-items:center;border:1px solid var(--spilki-border);border-radius:1.5rem;background:transparent;padding-left:0}.input-area textarea{flex:1;resize:none;min-height:2.4rem;max-height:6rem;border:0;border-radius:0;outline:0;background:transparent;box-shadow:none;-webkit-box-shadow:none;-webkit-appearance:none;appearance:none;padding:.55rem 0 .55rem 1rem;font-family:inherit;font-size:.95rem;color:var(--spilki-text);scrollbar-width:thin;scrollbar-color:rgba(148,163,184,.3) transparent}.input-actions{display:flex;align-items:center;gap:.25rem;padding:.25rem .35rem;flex-shrink:0}.input-actions button{width:32px;height:32px;display:grid;place-items:center;border:none;border-radius:50%;cursor:pointer;padding:0;transition:opacity .15s}.emoji-btn{background:#6366f126;color:var(--spilki-accent)}.emoji-btn:hover{background:#6366f140}.emoji-btn:focus-visible{outline:2px solid var(--spilki-accent);outline-offset:1px}.send-btn{background:var(--spilki-accent);color:#fff}.send-btn:hover{opacity:.85}.send-btn:focus-visible{outline:2px solid var(--spilki-accent);outline-offset:2px}.emoji-picker{position:absolute;right:1rem;bottom:80px;width:240px;max-height:180px;overflow-y:auto;display:none;grid-template-columns:repeat(10,minmax(0,1fr));gap:.25rem;padding:.5rem;background:var(--spilki-surface);border:1px solid var(--spilki-border);border-radius:12px;box-shadow:0 14px 30px #0f172a40;z-index:4}.emoji-option{width:100%;aspect-ratio:1;border:none;border-radius:8px;background:transparent;font-size:1rem;cursor:pointer}.emoji-option:hover,.emoji-option:focus-visible{background:#94a3b833}.typing{font-size:.75rem;color:var(--spilki-muted);padding:0 1rem .75rem}.typing:after{content:"";animation:dots 1.2s steps(4,end) infinite}@keyframes dots{0%{content:""}25%{content:"."}50%{content:".."}75%{content:"..."}}.offline{font-size:.8rem;padding:0 1rem;color:#f97316}:host([data-theme="dark"]){--spilki-surface: var(--spilki-bg-dark);--spilki-text: var(--spilki-text-dark);--spilki-border: var(--spilki-border-dark);--spilki-muted: var(--spilki-muted-dark)}:host([data-theme="dark"]) .messages,:host([data-theme="dark"]) .input-area textarea,:host([data-theme="dark"]) .emoji-picker{scrollbar-color:rgba(255,255,255,.2) transparent}:host([data-theme="light"]){--spilki-surface: var(--spilki-bg-light);--spilki-text: var(--spilki-text-light);--spilki-border: var(--spilki-border-light);--spilki-muted: var(--spilki-muted-light)}:host([data-theme="dark"]) .message .bubble{background:#94a3b81f}:host([data-theme="dark"]) .message.bot .bubble{background:#6366f126}:host([data-theme="dark"]) .input-area textarea{background:transparent}.messages::-webkit-scrollbar,.input-area textarea::-webkit-scrollbar,.emoji-picker::-webkit-scrollbar{width:6px}.messages::-webkit-scrollbar-track,.input-area textarea::-webkit-scrollbar-track,.emoji-picker::-webkit-scrollbar-track{background:transparent}.messages::-webkit-scrollbar-thumb,.input-area textarea::-webkit-scrollbar-thumb,.emoji-picker::-webkit-scrollbar-thumb{background:#94a3b84d;border-radius:999px}.messages::-webkit-scrollbar-thumb:hover,.input-area textarea::-webkit-scrollbar-thumb:hover,.emoji-picker::-webkit-scrollbar-thumb:hover{background:#94a3b880}:host([data-theme="dark"]) .messages::-webkit-scrollbar-thumb,:host([data-theme="dark"]) .input-area textarea::-webkit-scrollbar-thumb,:host([data-theme="dark"]) .emoji-picker::-webkit-scrollbar-thumb{background:#fff3}:host([data-theme="dark"]) .messages::-webkit-scrollbar-thumb:hover,:host([data-theme="dark"]) .input-area textarea::-webkit-scrollbar-thumb:hover,:host([data-theme="dark"]) .emoji-picker::-webkit-scrollbar-thumb:hover{background:#ffffff59}.conversation-separator{display:flex;align-items:center;gap:.5rem;padding:.5rem 0;cursor:pointer;user-select:none;font-size:.7rem;color:var(--spilki-muted);white-space:nowrap}.conversation-separator:before,.conversation-separator:after{content:"";flex:1;height:1px;background:var(--spilki-border)}.conversation-separator:hover{color:var(--spilki-text)}.conversation-separator:focus-visible{outline:2px solid var(--spilki-accent);outline-offset:2px;border-radius:4px}.conversation-history{display:none;flex-direction:column;gap:.5rem}.conversation-history.expanded{display:flex}', j = 24 * 60 * 60 * 1e3, J = 2 * 60 * 1e3, X = 200, Z = [
162
+ const J = ':host{--spilki-bg-light: #ffffff;--spilki-bg-dark: #0f172a;--spilki-text-light: #0f172a;--spilki-text-dark: #f8fafc;--spilki-border-light: rgba(15, 23, 42, .1);--spilki-border-dark: rgba(148, 163, 184, .25);--spilki-muted-light: #64748b;--spilki-muted-dark: #94a3b8;--spilki-shadow: 0 10px 40px rgba(15, 23, 42, .2);--spilki-radius: 16px;--spilki-font: "Inter", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;color:inherit;font-family:var(--spilki-font)}.wrapper{position:relative;width:100%;height:100%;display:flex;flex-direction:column;background:var(--spilki-surface);color:var(--spilki-text);border-radius:var(--spilki-radius);box-shadow:var(--spilki-shadow);border:1px solid var(--spilki-border);overflow:hidden}header{display:flex;align-items:center;justify-content:space-between;padding:.75rem 1rem;background:var(--spilki-surface);border-bottom:1px solid var(--spilki-border)}header h1{font-size:1rem;margin:0;display:flex;align-items:center;gap:.5rem}header button.close{border:none;background:transparent;color:inherit;font-size:1.25rem;cursor:pointer}header button.close:focus-visible{outline:2px solid var(--spilki-accent);outline-offset:2px;border-radius:4px}header .status-dot{width:10px;height:10px;border-radius:999px;background:var(--spilki-accent)}.messages{flex:1;overflow-y:auto;padding:1rem;display:flex;flex-direction:column;gap:.75rem;scrollbar-width:thin;scrollbar-color:rgba(148,163,184,.3) transparent;scroll-behavior:smooth}.message{display:flex;flex-direction:column;gap:.15rem;max-width:85%;width:fit-content;line-height:1.4;word-wrap:break-word;overflow-wrap:anywhere;white-space:pre-wrap}.message.user{align-self:flex-end;align-items:flex-end}.message .bubble{padding:.6rem .8rem;border-radius:1rem;background:#6366f126}.message.user .bubble{background:var(--spilki-accent);color:#fff}.message.bot .bubble{background:#94a3b826}.msg-time{font-size:.7rem;color:var(--spilki-muted);margin-top:2px}.date-separator{display:flex;align-items:center;gap:.5rem;padding:.4rem 0;font-size:.72rem;color:var(--spilki-muted);white-space:nowrap}.date-separator:before,.date-separator:after{content:"";flex:1;height:1px;background:var(--spilki-border)}.scroll-bottom{position:absolute;right:1rem;bottom:88px;width:38px;height:38px;border:none;border-radius:999px;background:var(--spilki-accent);color:#fff;box-shadow:0 8px 20px #0f172a40;cursor:pointer;opacity:0;pointer-events:none;transform:translateY(4px);transition:opacity .18s ease,transform .18s ease;z-index:3}.scroll-bottom.visible{opacity:1;pointer-events:auto;transform:translateY(0)}.scroll-arrow{font-size:.8rem;line-height:1}.scroll-count{position:absolute;top:-5px;right:-5px;min-width:16px;height:16px;border-radius:999px;background:#ef4444;color:#fff;font-size:10px;line-height:16px;text-align:center;padding:0 4px}.suggested-replies{display:flex;flex-wrap:wrap;gap:.4rem;padding:.5rem 1rem;border-top:1px solid var(--spilki-border)}.suggested-chip{border:1px solid var(--spilki-accent);background:transparent;color:var(--spilki-accent);border-radius:999px;padding:.35rem .75rem;font-size:.8rem;font-family:inherit;cursor:pointer;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;transition:background .15s,color .15s}.suggested-chip:hover{background:var(--spilki-accent);color:#fff}.suggested-chip:focus-visible{outline:2px solid var(--spilki-accent);outline-offset:2px}.input-area{padding:.5rem .75rem}.input-wrap{display:flex;align-items:center;border:1px solid var(--spilki-border);border-radius:1.5rem;background:transparent;padding-left:0}.input-area textarea{flex:1;resize:none;min-height:2.4rem;max-height:6rem;border:0;border-radius:0;outline:0;background:transparent;box-shadow:none;-webkit-box-shadow:none;-webkit-appearance:none;appearance:none;padding:.55rem 0 .55rem 1rem;font-family:inherit;font-size:.95rem;color:var(--spilki-text);scrollbar-width:thin;scrollbar-color:rgba(148,163,184,.3) transparent}.input-actions{display:flex;align-items:center;gap:.25rem;padding:.25rem .35rem;flex-shrink:0}.input-actions button{width:32px;height:32px;display:grid;place-items:center;border:none;border-radius:50%;cursor:pointer;padding:0;transition:opacity .15s}.emoji-btn{background:#6366f126;color:var(--spilki-accent)}.emoji-btn:hover{background:#6366f140}.emoji-btn:focus-visible{outline:2px solid var(--spilki-accent);outline-offset:1px}.send-btn{background:var(--spilki-accent);color:#fff}.send-btn:hover{opacity:.85}.send-btn:focus-visible{outline:2px solid var(--spilki-accent);outline-offset:2px}.emoji-picker{position:absolute;right:1rem;bottom:80px;width:240px;max-height:180px;overflow-y:auto;display:none;grid-template-columns:repeat(10,minmax(0,1fr));gap:.25rem;padding:.5rem;background:var(--spilki-surface);border:1px solid var(--spilki-border);border-radius:12px;box-shadow:0 14px 30px #0f172a40;z-index:4}.emoji-option{width:100%;aspect-ratio:1;border:none;border-radius:8px;background:transparent;font-size:1rem;cursor:pointer}.emoji-option:hover,.emoji-option:focus-visible{background:#94a3b833}.typing{font-size:.75rem;color:var(--spilki-muted);padding:0 1rem .75rem}.typing:after{content:"";animation:dots 1.2s steps(4,end) infinite}@keyframes dots{0%{content:""}25%{content:"."}50%{content:".."}75%{content:"..."}}.offline{font-size:.8rem;padding:0 1rem;color:#f97316}:host([data-theme="dark"]){--spilki-surface: var(--spilki-bg-dark);--spilki-text: var(--spilki-text-dark);--spilki-border: var(--spilki-border-dark);--spilki-muted: var(--spilki-muted-dark)}:host([data-theme="dark"]) .messages,:host([data-theme="dark"]) .input-area textarea,:host([data-theme="dark"]) .emoji-picker{scrollbar-color:rgba(255,255,255,.2) transparent}:host([data-theme="light"]){--spilki-surface: var(--spilki-bg-light);--spilki-text: var(--spilki-text-light);--spilki-border: var(--spilki-border-light);--spilki-muted: var(--spilki-muted-light)}:host([data-theme="dark"]) .message .bubble{background:#94a3b81f}:host([data-theme="dark"]) .message.bot .bubble{background:#6366f126}:host([data-theme="dark"]) .input-area textarea{background:transparent}.messages::-webkit-scrollbar,.input-area textarea::-webkit-scrollbar,.emoji-picker::-webkit-scrollbar{width:6px}.messages::-webkit-scrollbar-track,.input-area textarea::-webkit-scrollbar-track,.emoji-picker::-webkit-scrollbar-track{background:transparent}.messages::-webkit-scrollbar-thumb,.input-area textarea::-webkit-scrollbar-thumb,.emoji-picker::-webkit-scrollbar-thumb{background:#94a3b84d;border-radius:999px}.messages::-webkit-scrollbar-thumb:hover,.input-area textarea::-webkit-scrollbar-thumb:hover,.emoji-picker::-webkit-scrollbar-thumb:hover{background:#94a3b880}:host([data-theme="dark"]) .messages::-webkit-scrollbar-thumb,:host([data-theme="dark"]) .input-area textarea::-webkit-scrollbar-thumb,:host([data-theme="dark"]) .emoji-picker::-webkit-scrollbar-thumb{background:#fff3}:host([data-theme="dark"]) .messages::-webkit-scrollbar-thumb:hover,:host([data-theme="dark"]) .input-area textarea::-webkit-scrollbar-thumb:hover,:host([data-theme="dark"]) .emoji-picker::-webkit-scrollbar-thumb:hover{background:#ffffff59}.conversation-separator{display:flex;align-items:center;gap:.5rem;padding:.5rem 0;cursor:pointer;user-select:none;font-size:.7rem;color:var(--spilki-muted);white-space:nowrap}.conversation-separator:before,.conversation-separator:after{content:"";flex:1;height:1px;background:var(--spilki-border)}.conversation-separator:hover{color:var(--spilki-text)}.conversation-separator:focus-visible{outline:2px solid var(--spilki-accent);outline-offset:2px;border-radius:4px}.conversation-history{display:none;flex-direction:column;gap:.5rem}.conversation-history.expanded{display:flex}', D = 24 * 60 * 60 * 1e3, X = 2 * 60 * 1e3, Z = 200, Q = [
163
163
  "😀",
164
164
  "😂",
165
165
  "🤣",
@@ -211,10 +211,10 @@ const V = ':host{--spilki-bg-light: #ffffff;--spilki-bg-dark: #0f172a;--spilki-t
211
211
  "☕",
212
212
  "🍕"
213
213
  ];
214
- function E(o) {
215
- return o.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
214
+ function x(r) {
215
+ return r.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
216
216
  }
217
- class Q {
217
+ class ee {
218
218
  constructor(e) {
219
219
  this.options = e, this.relativeTimeFormat = new Intl.RelativeTimeFormat(void 0, {
220
220
  numeric: "auto"
@@ -229,9 +229,9 @@ class Q {
229
229
  day: "numeric",
230
230
  year: "numeric"
231
231
  }), this.renderedMessages = [], this.focusable = [], this.seenIds = /* @__PURE__ */ new Set(), this.lastTimelineMessage = null, this.scrollUnreadCount = 0, this.emojiPickerOpen = !1, this.open = !1, this.host = document.createElement("div"), this.host.setAttribute("part", "panel-root"), this.host.style.position = "fixed", this.host.style.bottom = "96px", this.host.style[e.position === "bottom-right" ? "right" : "left"] = "24px", this.host.style.width = "360px", this.host.style.maxWidth = "calc(100vw - 32px)", this.host.style.height = "520px", this.host.style.display = "none", this.host.style.zIndex = "2147483001", this.shadow = this.host.attachShadow({ mode: "open" });
232
- const t = E(e.i18n.title), s = E(e.i18n.typing), i = E(e.i18n.offline), r = E(e.i18n.placeholder), a = E(e.i18n.sendLabel);
232
+ const t = x(e.i18n.title), s = x(e.i18n.typing), i = x(e.i18n.offline), o = x(e.i18n.placeholder), l = x(e.i18n.sendLabel);
233
233
  this.shadow.innerHTML = `
234
- <style>${V}</style>
234
+ <style>${J}</style>
235
235
  <div class="wrapper" role="dialog" aria-modal="true" aria-label="${t}">
236
236
  <header>
237
237
  <h1><span class="status-dot" aria-hidden="true"></span>${t}</h1>
@@ -248,7 +248,7 @@ class Q {
248
248
  <div class="emoji-picker" aria-label="Emoji picker"></div>
249
249
  <div class="input-area">
250
250
  <div class="input-wrap">
251
- <textarea rows="1" placeholder="${r}" aria-label="${r}"></textarea>
251
+ <textarea rows="1" placeholder="${o}" aria-label="${o}"></textarea>
252
252
  <div class="input-actions">
253
253
  <button class="emoji-btn" type="button" aria-label="Insert emoji">
254
254
  <svg width="18" height="18" viewBox="0 0 24 24" fill="none" aria-hidden="true">
@@ -258,7 +258,7 @@ class Q {
258
258
  <circle cx="15" cy="10" r="1" fill="currentColor"/>
259
259
  </svg>
260
260
  </button>
261
- <button type="button" class="send-btn" aria-label="${a}">
261
+ <button type="button" class="send-btn" aria-label="${l}">
262
262
  <svg width="18" height="18" viewBox="0 0 24 24" fill="none" aria-hidden="true">
263
263
  <path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z" fill="currentColor"/>
264
264
  </svg>
@@ -278,23 +278,23 @@ class Q {
278
278
  this.options.onClose();
279
279
  }
280
280
  }, this.handleShadowKeydown = (n) => {
281
- const l = n;
282
- if (l.key === "Escape") {
281
+ const a = n;
282
+ if (a.key === "Escape") {
283
283
  if (this.emojiPickerOpen) {
284
- l.preventDefault(), this.closeEmojiPicker();
284
+ a.preventDefault(), this.closeEmojiPicker();
285
285
  return;
286
286
  }
287
287
  this.options.onClose();
288
288
  }
289
- l.key === "Tab" && this.trapFocus(l);
289
+ a.key === "Tab" && this.trapFocus(a);
290
290
  }, this.handleFocusin = () => this.collectFocusable(), this.handleMessagesScroll = () => this.updateScrollBottomState(), this.handleScrollBottomClick = () => {
291
291
  this.scrollToBottom(), this.resetScrollUnreadCount();
292
292
  }, this.handleEmojiClick = () => {
293
293
  this.toggleEmojiPicker();
294
294
  }, this.handleDocumentPointerDown = (n) => {
295
295
  if (!this.emojiPickerOpen) return;
296
- const l = n.composedPath();
297
- l.includes(this.emojiPickerEl) || l.includes(this.emojiButton) || this.closeEmojiPicker();
296
+ const a = n.composedPath();
297
+ a.includes(this.emojiPickerEl) || a.includes(this.emojiButton) || this.closeEmojiPicker();
298
298
  }, this.closeButton.addEventListener("click", this.handleCloseClick), this.sendButton.addEventListener("click", this.handleSendClick), this.emojiButton.addEventListener("click", this.handleEmojiClick), this.scrollBottomButton.addEventListener("click", this.handleScrollBottomClick), this.input.addEventListener("keydown", this.handleInputKeydown), this.messagesEl.addEventListener("scroll", this.handleMessagesScroll), this.shadow.addEventListener("keydown", this.handleShadowKeydown), this.shadow.addEventListener("focusin", this.handleFocusin), document.addEventListener("pointerdown", this.handleDocumentPointerDown, !0), this.renderEmojiPicker(), this.updateScrollBottomState(), this.collectFocusable();
299
299
  }
300
300
  mount() {
@@ -329,8 +329,8 @@ class Q {
329
329
  this.messagesEl.innerHTML = "", this.seenIds.clear(), this.renderedMessages.length = 0, this.lastTimelineMessage = null;
330
330
  for (const s of e) {
331
331
  s.messages.forEach((n) => this.seenIds.add(n.id));
332
- const i = this.createHistoryContainer(s.messages), r = s.messages[s.messages.length - 1].ts, a = this.createSeparatorElement(r, i);
333
- this.messagesEl.appendChild(i), this.messagesEl.appendChild(a);
332
+ const i = this.createHistoryContainer(s.messages), o = s.messages[s.messages.length - 1].ts, l = this.createSeparatorElement(o, i);
333
+ this.messagesEl.appendChild(i), this.messagesEl.appendChild(l);
334
334
  }
335
335
  t.forEach((s) => {
336
336
  this.seenIds.add(s.id), this.appendTimelineMessage(s);
@@ -395,17 +395,17 @@ class Q {
395
395
  }
396
396
  createSeparatorElement(e, t) {
397
397
  const s = document.createElement("div");
398
- s.className = "conversation-separator", s.setAttribute("role", "button"), s.setAttribute("tabindex", "0"), s.setAttribute("aria-expanded", "false"), s.setAttribute("aria-label", "Show previous conversation"), s.textContent = Y(e);
398
+ s.className = "conversation-separator", s.setAttribute("role", "button"), s.setAttribute("tabindex", "0"), s.setAttribute("aria-expanded", "false"), s.setAttribute("aria-label", "Show previous conversation"), s.textContent = V(e);
399
399
  const i = () => {
400
- const r = t.classList.toggle("expanded");
401
- s.setAttribute("aria-expanded", String(r)), s.setAttribute(
400
+ const o = t.classList.toggle("expanded");
401
+ s.setAttribute("aria-expanded", String(o)), s.setAttribute(
402
402
  "aria-label",
403
- r ? "Hide previous conversation" : "Show previous conversation"
403
+ o ? "Hide previous conversation" : "Show previous conversation"
404
404
  );
405
405
  };
406
- return s.addEventListener("click", i), s.addEventListener("keydown", (r) => {
407
- const a = r;
408
- (a.key === "Enter" || a.key === " ") && (a.preventDefault(), i());
406
+ return s.addEventListener("click", i), s.addEventListener("keydown", (o) => {
407
+ const l = o;
408
+ (l.key === "Enter" || l.key === " ") && (l.preventDefault(), i());
409
409
  }), s;
410
410
  }
411
411
  createHistoryContainer(e) {
@@ -414,8 +414,8 @@ class Q {
414
414
  let s = null;
415
415
  return e.forEach((i) => {
416
416
  this.appendDateSeparatorIfNeeded(s, i, t);
417
- const { item: r } = this.createMessageElement(i);
418
- t.appendChild(r), s = i;
417
+ const { item: o } = this.createMessageElement(i);
418
+ t.appendChild(o), s = i;
419
419
  }), t;
420
420
  }
421
421
  appendTimelineMessage(e) {
@@ -431,24 +431,24 @@ class Q {
431
431
  }
432
432
  updateTimestampVisibility() {
433
433
  for (let e = 0; e < this.renderedMessages.length; e += 1) {
434
- const t = this.renderedMessages[e], s = this.renderedMessages[e + 1], i = !!s && s.message.author === t.message.author && s.message.ts - t.message.ts <= J && s.message.ts >= t.message.ts;
434
+ const t = this.renderedMessages[e], s = this.renderedMessages[e + 1], i = !!s && s.message.author === t.message.author && s.message.ts - t.message.ts <= X && s.message.ts >= t.message.ts;
435
435
  t.timeEl.toggleAttribute("hidden", i), t.timeEl.textContent = this.formatMessageTimestamp(t.message.ts);
436
436
  }
437
437
  }
438
438
  formatMessageTimestamp(e) {
439
439
  const s = Date.now() - e;
440
- if (s < j) {
440
+ if (s < D) {
441
441
  const i = Math.max(1, Math.round(s / 6e4));
442
442
  if (i < 60)
443
443
  return this.relativeTimeFormat.format(-i, "minute");
444
- const r = Math.max(1, Math.round(i / 60));
445
- return this.relativeTimeFormat.format(-r, "hour");
444
+ const o = Math.max(1, Math.round(i / 60));
445
+ return this.relativeTimeFormat.format(-o, "hour");
446
446
  }
447
447
  return this.absoluteTimeFormat.format(new Date(e));
448
448
  }
449
449
  formatDateSeparator(e) {
450
450
  const t = new Date(e), s = this.startOfDay(Date.now()), i = this.startOfDay(e);
451
- return i === s ? "Today" : i === s - j ? "Yesterday" : this.dateSeparatorFormat.format(t);
451
+ return i === s ? "Today" : i === s - D ? "Yesterday" : this.dateSeparatorFormat.format(t);
452
452
  }
453
453
  isSameDay(e, t) {
454
454
  return this.startOfDay(e) === this.startOfDay(t);
@@ -458,7 +458,7 @@ class Q {
458
458
  return new Date(t.getFullYear(), t.getMonth(), t.getDate()).getTime();
459
459
  }
460
460
  isScrolledUp() {
461
- return this.distanceFromBottom() > X;
461
+ return this.distanceFromBottom() > Z;
462
462
  }
463
463
  distanceFromBottom() {
464
464
  return this.messagesEl.scrollHeight - this.messagesEl.scrollTop - this.messagesEl.clientHeight;
@@ -505,7 +505,7 @@ class Q {
505
505
  this.emojiPickerOpen && (this.emojiPickerOpen = !1, this.emojiPickerEl.style.display = "none", this.emojiButton.setAttribute("aria-expanded", "false"), this.collectFocusable());
506
506
  }
507
507
  renderEmojiPicker() {
508
- this.emojiPickerEl.replaceChildren(), Z.forEach((e) => {
508
+ this.emojiPickerEl.replaceChildren(), Q.forEach((e) => {
509
509
  const t = document.createElement("button");
510
510
  t.type = "button", t.className = "emoji-option", t.textContent = e, t.setAttribute("aria-label", `Insert ${e}`), t.addEventListener("click", () => {
511
511
  this.insertEmojiAtCursor(e), this.closeEmojiPicker();
@@ -513,11 +513,11 @@ class Q {
513
513
  });
514
514
  }
515
515
  insertEmojiAtCursor(e) {
516
- var a, n;
517
- const t = this.input.value, s = (a = this.input.selectionStart) != null ? a : t.length, i = (n = this.input.selectionEnd) != null ? n : t.length;
516
+ var l, n;
517
+ const t = this.input.value, s = (l = this.input.selectionStart) != null ? l : t.length, i = (n = this.input.selectionEnd) != null ? n : t.length;
518
518
  this.input.value = `${t.slice(0, s)}${e}${t.slice(i)}`;
519
- const r = s + e.length;
520
- this.input.setSelectionRange(r, r), this.input.dispatchEvent(new Event("input", { bubbles: !0 })), this.input.focus();
519
+ const o = s + e.length;
520
+ this.input.setSelectionRange(o, o), this.input.dispatchEvent(new Event("input", { bubbles: !0 })), this.input.focus();
521
521
  }
522
522
  collectFocusable() {
523
523
  const e = this.shadow.querySelectorAll(
@@ -533,14 +533,40 @@ class Q {
533
533
  e.shiftKey && i === t ? (e.preventDefault(), s.focus()) : !e.shiftKey && i === s && (e.preventDefault(), t.focus());
534
534
  }
535
535
  }
536
- const v = 1500, D = 8e3;
537
- class ee {
536
+ const v = 1500, L = 8e3;
537
+ class te {
538
538
  constructor(e, t) {
539
539
  this.sessionId = null, this.currentKind = null, this.stopped = !1, this.backoff = v, this.connectPromise = null, this.options = e, this.handlers = t;
540
540
  }
541
541
  setAccessToken(e) {
542
542
  this.options.accessToken = e;
543
543
  }
544
+ setUser(e) {
545
+ const t = {};
546
+ for (const [s, i] of Object.entries(e))
547
+ if (typeof i == "string") {
548
+ const o = i.trim();
549
+ o && (t[s] = o);
550
+ }
551
+ this.user = t, this.sessionId && this.sendIdentify().catch(
552
+ (s) => this.handlers.onError(s)
553
+ );
554
+ }
555
+ async sendIdentify() {
556
+ const e = `${this.options.apiBase.replace(/\/$/, "")}/widget/identify`, t = await fetch(e, {
557
+ method: "POST",
558
+ headers: {
559
+ "Content-Type": "application/json",
560
+ ...this.options.accessToken ? { "X-Authorization": `Bearer ${this.options.accessToken}` } : {}
561
+ },
562
+ body: JSON.stringify({
563
+ sessionId: this.sessionId,
564
+ user: this.user
565
+ })
566
+ });
567
+ if (!t.ok)
568
+ throw new Error(`SpilkiWidget: identify failed (${t.status})`);
569
+ }
544
570
  get kind() {
545
571
  return this.currentKind;
546
572
  }
@@ -559,14 +585,15 @@ class ee {
559
585
  }
560
586
  }
561
587
  async doConnect() {
562
- var a, n;
588
+ var l, n;
563
589
  this.stopped = !1;
564
- const e = `${this.options.apiBase.replace(/\/$/, "")}/widget/session`, t = (n = (a = this.sessionId) != null ? a : this.options.sessionId) != null ? n : void 0, s = {
590
+ const e = `${this.options.apiBase.replace(/\/$/, "")}/widget/session`, t = (n = (l = this.sessionId) != null ? l : this.options.sessionId) != null ? n : void 0, s = {
565
591
  organisationId: this.options.org,
566
592
  sessionId: t,
567
593
  userAgent: typeof navigator != "undefined" ? navigator.userAgent : "",
568
594
  referrer: typeof document != "undefined" ? document.referrer : "",
569
- origin: typeof window != "undefined" ? window.location.origin : ""
595
+ origin: typeof window != "undefined" ? window.location.origin : "",
596
+ ...this.user ? { user: this.user } : {}
570
597
  }, i = await fetch(e, {
571
598
  method: "POST",
572
599
  headers: {
@@ -577,15 +604,16 @@ class ee {
577
604
  });
578
605
  if (!i.ok)
579
606
  throw new Error(`SpilkiWidget: connect failed (${i.status})`);
580
- const r = await i.json();
581
- return this.sessionId = r.sessionId, this.options.sessionId = r.sessionId, this.backoff = v, await this.startTransport(r), r;
607
+ const o = await i.json();
608
+ return this.sessionId = o.sessionId, this.options.sessionId = o.sessionId, this.backoff = v, await this.startTransport(o), o;
582
609
  }
583
610
  async send(e, t) {
584
- var a, n;
611
+ var l, n, a;
585
612
  const s = {
586
- sessionId: (n = (a = this.sessionId) != null ? a : this.options.sessionId) != null ? n : "",
613
+ sessionId: (n = (l = this.sessionId) != null ? l : this.options.sessionId) != null ? n : "",
587
614
  text: e,
588
- ...t ? { messageId: t } : {}
615
+ ...t ? { messageId: t } : {},
616
+ ...(a = this.user) != null && a.userId ? { userId: this.user.userId } : {}
589
617
  };
590
618
  if (!s.sessionId)
591
619
  throw new Error("SpilkiWidget: missing session id");
@@ -593,7 +621,7 @@ class ee {
593
621
  this.ws.send(JSON.stringify({ type: "message", payload: s }));
594
622
  return;
595
623
  }
596
- const i = `${this.options.apiBase.replace(/\/$/, "")}/widget/message`, r = await fetch(i, {
624
+ const i = `${this.options.apiBase.replace(/\/$/, "")}/widget/message`, o = await fetch(i, {
597
625
  method: "POST",
598
626
  headers: {
599
627
  "Content-Type": "application/json",
@@ -601,8 +629,8 @@ class ee {
601
629
  },
602
630
  body: JSON.stringify(s)
603
631
  });
604
- if (!r.ok)
605
- throw new Error(`SpilkiWidget: send failed (${r.status})`);
632
+ if (!o.ok)
633
+ throw new Error(`SpilkiWidget: send failed (${o.status})`);
606
634
  }
607
635
  // #1: accept resetBackoff param; #8 #9: clear retryTimer
608
636
  stop(e = !0) {
@@ -632,7 +660,7 @@ class ee {
632
660
  return;
633
661
  }
634
662
  this.currentKind = "ws", this.handlers.onOpen("ws"), this.backoff = v, t();
635
- }, this.wsOnMessage = (r) => this.handleIncoming(r.data), this.wsOnClose = () => {
663
+ }, this.wsOnMessage = (o) => this.handleIncoming(o.data), this.wsOnClose = () => {
636
664
  this.stopped || this.retryFallback("ws");
637
665
  }, this.wsOnError = () => {
638
666
  this.handlers.onError(new Error("SpilkiWidget: websocket error")), i.readyState !== WebSocket.OPEN && s(new Error("WebSocket failed"));
@@ -652,8 +680,8 @@ class ee {
652
680
  "X-Authorization": `Bearer ${this.options.accessToken}`
653
681
  },
654
682
  signal: i.signal
655
- }).then((r) => {
656
- if (!r.ok || !r.body) {
683
+ }).then((o) => {
684
+ if (!o.ok || !o.body) {
657
685
  s(new Error("SSE failed"));
658
686
  return;
659
687
  }
@@ -662,21 +690,21 @@ class ee {
662
690
  return;
663
691
  }
664
692
  this.currentKind = "sse", this.handlers.onOpen("sse"), this.backoff = v, t();
665
- const a = r.body.getReader(), n = new TextDecoder();
666
- let l = "";
693
+ const l = o.body.getReader(), n = new TextDecoder();
694
+ let a = "";
667
695
  const g = () => {
668
- a.read().then(({ done: u, value: h }) => {
696
+ l.read().then(({ done: u, value: d }) => {
669
697
  var m;
670
698
  if (u || this.stopped) {
671
699
  this.stopped || (this.handlers.onError(new Error("SpilkiWidget: SSE stream ended")), this.retryFallback("sse"));
672
700
  return;
673
701
  }
674
- l += n.decode(h, { stream: !0 });
675
- const d = l.split(`
702
+ a += n.decode(d, { stream: !0 });
703
+ const h = a.split(`
676
704
 
677
705
  `);
678
- l = (m = d.pop()) != null ? m : "";
679
- for (const f of d)
706
+ a = (m = h.pop()) != null ? m : "";
707
+ for (const f of h)
680
708
  for (const p of f.split(`
681
709
  `))
682
710
  p.startsWith("data:") && this.handleIncoming(p.slice(5).trim());
@@ -686,8 +714,8 @@ class ee {
686
714
  });
687
715
  };
688
716
  g();
689
- }).catch((r) => {
690
- i.signal.aborted || s(r);
717
+ }).catch((o) => {
718
+ i.signal.aborted || s(o);
691
719
  });
692
720
  });
693
721
  }
@@ -704,9 +732,9 @@ class ee {
704
732
  });
705
733
  if (!s.ok) throw new Error(`Poll failed ${s.status}`);
706
734
  const i = await s.json();
707
- x(i).forEach((r) => this.handlers.onMessage(r)), this.backoff = v;
735
+ S(i).forEach((o) => this.handlers.onMessage(o)), this.backoff = v;
708
736
  } catch (s) {
709
- this.handlers.onError(s), this.backoff = Math.min(this.backoff * 1.5, D);
737
+ this.handlers.onError(s), this.backoff = Math.min(this.backoff * 1.5, L);
710
738
  } finally {
711
739
  this.stopped || (this.pollTimer = window.setTimeout(t, this.backoff));
712
740
  }
@@ -715,7 +743,7 @@ class ee {
715
743
  }
716
744
  // #1: preserve backoff by calling stop(false); #8: check stopped before reconnect
717
745
  retryFallback(e) {
718
- this.stopped || (this.stop(!1), this.backoff = Math.min(this.backoff * 1.5, D), this.retryTimer = window.setTimeout(() => {
746
+ this.stopped || (this.stop(!1), this.backoff = Math.min(this.backoff * 1.5, L), this.retryTimer = window.setTimeout(() => {
719
747
  this.stopped || (this.handlers.onError(new Error(`SpilkiWidget: retrying after ${e}`)), this.connect().catch((t) => this.handlers.onError(t)));
720
748
  }, this.backoff));
721
749
  }
@@ -735,19 +763,19 @@ class ee {
735
763
  dispatchIncoming(e) {
736
764
  var t;
737
765
  if ("type" in e) {
738
- e.type === "message" ? this.handlers.onMessage(e.payload) : e.type === "typing" ? this.handlers.onTyping(!!((t = e.payload) != null && t.active)) : e.type === "AGENT_EVENT" && se(e.payload) && this.handlers.onAgentEvent(e.payload);
766
+ e.type === "message" ? this.handlers.onMessage(e.payload) : e.type === "typing" ? this.handlers.onTyping(!!((t = e.payload) != null && t.active)) : e.type === "AGENT_EVENT" && ie(e.payload) && this.handlers.onAgentEvent(e.payload);
739
767
  return;
740
768
  }
741
769
  this.handlers.onMessage(e);
742
770
  }
743
771
  }
744
- const te = /* @__PURE__ */ new Set(["TYPING", "THINKING", "SEARCHING", "EXECUTING_TOOL"]);
745
- function se(o) {
746
- if (typeof o != "object" || o === null) return !1;
747
- const e = o;
748
- return te.has(e.eventType) && typeof e.active == "boolean";
772
+ const se = /* @__PURE__ */ new Set(["TYPING", "THINKING", "SEARCHING", "EXECUTING_TOOL"]);
773
+ function ie(r) {
774
+ if (typeof r != "object" || r === null) return !1;
775
+ const e = r;
776
+ return se.has(e.eventType) && typeof e.active == "boolean";
749
777
  }
750
- const L = 30 * 60 * 1e3, ie = 3e4, T = 30;
778
+ const U = 30 * 60 * 1e3, re = 3e4, A = 30;
751
779
  class oe {
752
780
  constructor(e, t) {
753
781
  this.org = e, this.listeners = /* @__PURE__ */ new Set(), this.activityTimer = null, this.activeEvents = /* @__PURE__ */ new Set(), this.state = {
@@ -783,7 +811,7 @@ class oe {
783
811
  updateDisplayedActivity() {
784
812
  this.activityTimer && (clearTimeout(this.activityTimer), this.activityTimer = null), this.activeEvents.size > 0 && (this.activityTimer = setTimeout(() => {
785
813
  this.activeEvents.clear(), this.state.agentActivity = null, this.activityTimer = null, this.emit();
786
- }, ie));
814
+ }, re));
787
815
  const e = this.resolveTopActivity();
788
816
  this.state.agentActivity !== e && (this.state.agentActivity = e, this.emit());
789
817
  }
@@ -802,15 +830,15 @@ class oe {
802
830
  addMessage(e) {
803
831
  var s, i;
804
832
  const t = {
805
- id: (s = e.id) != null ? s : z("msg"),
833
+ id: (s = e.id) != null ? s : _("msg"),
806
834
  ts: (i = e.ts) != null ? i : Date.now(),
807
835
  author: e.author,
808
836
  text: e.text
809
837
  };
810
- return this.state.messages.some((r) => r.id === t.id) ? null : (this.state.messages = x([...this.state.messages, t], T), this.persistMessages(), this.touchActivity(), this.emit(), t);
838
+ return this.state.messages.some((o) => o.id === t.id) ? null : (this.state.messages = S([...this.state.messages, t], A), this.persistMessages(), this.touchActivity(), this.emit(), t);
811
839
  }
812
840
  setMessages(e) {
813
- this.state.messages = x(e, T), this.persistMessages(), this.emit();
841
+ this.state.messages = S(e, A), this.persistMessages(), this.emit();
814
842
  }
815
843
  clearMessages() {
816
844
  this.state.messages = [], this.persistMessages(), this.emit();
@@ -902,7 +930,7 @@ class oe {
902
930
  }
903
931
  isSessionExpired() {
904
932
  const e = this.lastActivityTs;
905
- return e === 0 ? !1 : Date.now() - e >= L;
933
+ return e === 0 ? !1 : Date.now() - e >= U;
906
934
  }
907
935
  getConversationGroups() {
908
936
  const e = this.state.messages;
@@ -910,7 +938,7 @@ class oe {
910
938
  const t = [];
911
939
  let s = { startTs: e[0].ts, messages: [e[0]] };
912
940
  for (let i = 1; i < e.length; i++)
913
- e[i].ts - e[i - 1].ts >= L ? (t.push(s), s = { startTs: e[i].ts, messages: [e[i]] }) : s.messages.push(e[i]);
941
+ e[i].ts - e[i - 1].ts >= U ? (t.push(s), s = { startTs: e[i].ts, messages: [e[i]] }) : s.messages.push(e[i]);
914
942
  return t.push(s), t;
915
943
  }
916
944
  emit() {
@@ -930,14 +958,14 @@ class oe {
930
958
  const e = localStorage.getItem(this.historyKey);
931
959
  if (!e) return [];
932
960
  const t = JSON.parse(e);
933
- return Array.isArray(t) ? x(t, T) : [];
961
+ return Array.isArray(t) ? S(t, A) : [];
934
962
  } catch (e) {
935
963
  return console.error("SpilkiWidget: unable to load messages", e), [];
936
964
  }
937
965
  }
938
966
  }
939
- function re(o) {
940
- const e = o.replace(/-/g, "+").replace(/_/g, "/"), t = e.padEnd(e.length + (4 - e.length % 4) % 4, "=");
967
+ function ne(r) {
968
+ const e = r.replace(/-/g, "+").replace(/_/g, "/"), t = e.padEnd(e.length + (4 - e.length % 4) % 4, "=");
941
969
  if (typeof atob == "function")
942
970
  return decodeURIComponent(
943
971
  Array.prototype.map.call(atob(t), (i) => `%${`00${i.charCodeAt(0).toString(16)}`.slice(-2)}`).join("")
@@ -947,24 +975,24 @@ function re(o) {
947
975
  return s.from(t, "base64").toString("utf8");
948
976
  throw new Error("SpilkiWidget: no base64 decoder available");
949
977
  }
950
- function ne(o) {
951
- if (!o) return null;
952
- const e = o.split(".");
978
+ function ae(r) {
979
+ if (!r) return null;
980
+ const e = r.split(".");
953
981
  if (e.length < 2) return null;
954
982
  try {
955
- const t = re(e[1]);
983
+ const t = ne(e[1]);
956
984
  return JSON.parse(t);
957
985
  } catch (t) {
958
986
  return console.error("SpilkiWidget: unable to parse JWT", t), null;
959
987
  }
960
988
  }
961
- function ae(o) {
962
- const e = ne(o);
989
+ function le(r) {
990
+ const e = ae(r);
963
991
  if (!(e != null && e.exp) || typeof e.exp != "number") return !0;
964
992
  const t = Math.floor(Date.now() / 1e3);
965
993
  return e.exp < t + 60;
966
994
  }
967
- const le = {
995
+ const ce = {
968
996
  onOpen() {
969
997
  },
970
998
  onClose() {
@@ -976,8 +1004,8 @@ const le = {
976
1004
  onTransportChange() {
977
1005
  }
978
1006
  };
979
- async function ce(o, e, t) {
980
- const s = `${o.replace(/\/$/, "")}/widget/install`, i = await fetch(s, {
1007
+ async function de(r, e, t) {
1008
+ const s = `${r.replace(/\/$/, "")}/widget/install`, i = await fetch(s, {
981
1009
  method: "POST",
982
1010
  headers: { "Content-Type": "application/json", Origin: window.location.origin },
983
1011
  body: JSON.stringify({ token: e, organisationId: t })
@@ -985,29 +1013,29 @@ async function ce(o, e, t) {
985
1013
  if (!i.ok) throw new Error(`SpilkiWidget: install failed (${i.status})`);
986
1014
  return (await i.json()).accessToken;
987
1015
  }
988
- async function he(o, e) {
989
- const t = `${o.replace(/\/$/, "")}/widget/refresh`, s = await fetch(t, {
1016
+ async function he(r, e) {
1017
+ const t = `${r.replace(/\/$/, "")}/widget/refresh`, s = await fetch(t, {
990
1018
  method: "POST",
991
1019
  headers: { "X-Authorization": `Bearer ${e}`, Origin: window.location.origin }
992
1020
  });
993
1021
  if (!s.ok) throw new Error(`SpilkiWidget: refresh failed (${s.status})`);
994
1022
  return (await s.json()).accessToken;
995
1023
  }
996
- function de(o) {
1024
+ function pe(r) {
997
1025
  let e = !1, t = null;
998
1026
  const s = [], i = () => {
999
1027
  var n;
1000
- if (!o) return null;
1028
+ if (!r) return null;
1001
1029
  if (t) return t;
1002
1030
  try {
1003
- const l = (n = window.AudioContext) != null ? n : window.webkitAudioContext;
1004
- if (!l) return null;
1005
- t = new l();
1031
+ const a = (n = window.AudioContext) != null ? n : window.webkitAudioContext;
1032
+ if (!a) return null;
1033
+ t = new a();
1006
1034
  } catch {
1007
1035
  return null;
1008
1036
  }
1009
1037
  return t;
1010
- }, r = () => {
1038
+ }, o = () => {
1011
1039
  e = !0;
1012
1040
  try {
1013
1041
  const n = i();
@@ -1015,97 +1043,97 @@ function de(o) {
1015
1043
  });
1016
1044
  } catch {
1017
1045
  }
1018
- }, a = (n) => {
1019
- const l = () => {
1020
- r(), window.removeEventListener(n, l, !0);
1046
+ }, l = (n) => {
1047
+ const a = () => {
1048
+ o(), window.removeEventListener(n, a, !0);
1021
1049
  };
1022
- s.push({ type: n, listener: l }), window.addEventListener(n, l, { capture: !0, passive: !0 });
1050
+ s.push({ type: n, listener: a }), window.addEventListener(n, a, { capture: !0, passive: !0 });
1023
1051
  };
1024
- return a("pointerdown"), a("keydown"), {
1025
- markUserInteraction: r,
1052
+ return l("pointerdown"), l("keydown"), {
1053
+ markUserInteraction: o,
1026
1054
  play() {
1027
- if (!o || !e) return;
1055
+ if (!r || !e) return;
1028
1056
  const n = i();
1029
1057
  if (!n || n.state !== "running") return;
1030
- const l = n.createGain();
1031
- l.gain.value = 0.15, l.connect(n.destination);
1032
- const g = (h, d, m) => {
1058
+ const a = n.createGain();
1059
+ a.gain.value = 0.15, a.connect(n.destination);
1060
+ const g = (d, h, m) => {
1033
1061
  const f = n.createOscillator(), p = n.createGain();
1034
- f.type = "sine", f.frequency.setValueAtTime(h, d), p.gain.setValueAtTime(1e-4, d), p.gain.exponentialRampToValueAtTime(1, d + 0.012), p.gain.exponentialRampToValueAtTime(1e-4, d + m), f.connect(p), p.connect(l), f.start(d), f.stop(d + m + 0.02);
1062
+ f.type = "sine", f.frequency.setValueAtTime(d, h), p.gain.setValueAtTime(1e-4, h), p.gain.exponentialRampToValueAtTime(1, h + 0.012), p.gain.exponentialRampToValueAtTime(1e-4, h + m), f.connect(p), p.connect(a), f.start(h), f.stop(h + m + 0.02);
1035
1063
  }, u = n.currentTime;
1036
1064
  g(880, u, 0.08), g(1175, u + 0.09, 0.08);
1037
1065
  },
1038
1066
  destroy() {
1039
- s.forEach(({ type: n, listener: l }) => {
1040
- window.removeEventListener(n, l, !0);
1067
+ s.forEach(({ type: n, listener: a }) => {
1068
+ window.removeEventListener(n, a, !0);
1041
1069
  }), s.length = 0, t && t.close().catch(() => {
1042
1070
  }), t = null;
1043
1071
  }
1044
1072
  };
1045
1073
  }
1046
- function U(o) {
1047
- var I, O, M, B;
1048
- if (!o.org)
1074
+ function $(r) {
1075
+ var O, M, B, j;
1076
+ if (!r.org)
1049
1077
  throw new Error("SpilkiWidget: org is required");
1050
- const e = H(o);
1051
- G(e.allowedOriginsHint);
1052
- const t = { ...le, ...(I = e.hooks) != null ? I : {} }, s = new oe(e.org, { persist: e.persist }), i = de(e.sound);
1053
- let r = (O = s.accessToken) != null ? O : void 0, a = null;
1078
+ const e = z(r);
1079
+ q(e.allowedOriginsHint);
1080
+ const t = { ...ce, ...(O = e.hooks) != null ? O : {} }, s = new oe(e.org, { persist: e.persist }), i = pe(e.sound);
1081
+ let o = (M = s.accessToken) != null ? M : void 0, l = null;
1054
1082
  const n = () => {
1055
1083
  u.setBadge(s.countUnread());
1056
- }, l = () => {
1084
+ }, a = () => {
1057
1085
  s.markRead(), u.setBadge(0);
1058
- }, g = async () => a || (a = (async () => {
1086
+ }, g = async () => l || (l = (async () => {
1059
1087
  try {
1060
- if (r && ae(r))
1088
+ if (o && le(o))
1061
1089
  try {
1062
- r = await he(e.apiBase, r), s.persistAccessToken(r), d.setAccessToken(r);
1090
+ o = await he(e.apiBase, o), s.persistAccessToken(o), h.setAccessToken(o);
1063
1091
  return;
1064
1092
  } catch {
1065
- r = void 0, s.clearAccessToken();
1093
+ o = void 0, s.clearAccessToken();
1066
1094
  }
1067
- if (!r) {
1068
- if (!o.installationToken) throw new Error("SpilkiWidget: missing installationToken");
1069
- if (!o.org) throw new Error("SpilkiWidget: missing org");
1070
- r = await ce(e.apiBase, o.installationToken, o.org), s.persistAccessToken(r), d.setAccessToken(r);
1095
+ if (!o) {
1096
+ if (!r.installationToken) throw new Error("SpilkiWidget: missing installationToken");
1097
+ if (!r.org) throw new Error("SpilkiWidget: missing org");
1098
+ o = await de(e.apiBase, r.installationToken, r.org), s.persistAccessToken(o), h.setAccessToken(o);
1071
1099
  }
1072
1100
  } finally {
1073
- a = null;
1101
+ l = null;
1074
1102
  }
1075
- })(), a), u = R({
1103
+ })(), l), u = K({
1076
1104
  color: e.color,
1077
- position: (M = e.position) != null ? M : "bottom-right",
1105
+ position: (B = e.position) != null ? B : "bottom-right",
1078
1106
  onClick: () => {
1079
- s.snapshot.isOpen ? (s.close(), t.onClose()) : (i.markUserInteraction(), s.open(), l(), t.onOpen());
1107
+ s.snapshot.isOpen ? (s.close(), t.onClose()) : (i.markUserInteraction(), s.open(), a(), t.onOpen());
1080
1108
  }
1081
- }), h = new Q({
1109
+ }), d = new ee({
1082
1110
  color: e.color,
1083
1111
  theme: P(e.theme),
1084
- position: (B = e.position) != null ? B : "bottom-right",
1112
+ position: (j = e.position) != null ? j : "bottom-right",
1085
1113
  i18n: e.i18n,
1086
1114
  onClose: () => {
1087
1115
  s.close(), t.onClose();
1088
1116
  },
1089
1117
  onSend: (c) => {
1090
1118
  const k = s.addMessage({ author: "user", text: c });
1091
- k && (h.appendMessage(k), g().then(() => d.send(c, k.id)).catch((b) => {
1092
- t.onError(b), s.setConnected(!1), h.setOffline(!0);
1119
+ k && (d.appendMessage(k), g().then(() => h.send(c, k.id)).catch((b) => {
1120
+ t.onError(b), s.setConnected(!1), d.setOffline(!0);
1093
1121
  }));
1094
1122
  }
1095
- }), d = new ee(
1123
+ }), h = new te(
1096
1124
  {
1097
1125
  apiBase: e.apiBase,
1098
- accessToken: r,
1126
+ accessToken: o,
1099
1127
  org: e.org,
1100
1128
  sessionId: s.sessionId
1101
1129
  },
1102
1130
  {
1103
1131
  onOpen(c) {
1104
- A.transport = c, t.onTransportChange(c), s.setConnected(!0), h.setOffline(!1);
1132
+ C.transport = c, t.onTransportChange(c), s.setConnected(!0), d.setOffline(!1);
1105
1133
  },
1106
1134
  onMessage(c) {
1107
1135
  const b = c.suggestedReplies, y = s.addMessage(c);
1108
- y && (h.appendMessage(y), y.author === "bot" && b && b.length > 0 && h.setSuggestedReplies(b), !s.snapshot.isOpen && y.author === "bot" && (n(), i.play()), t.onMessage(y));
1136
+ y && (d.appendMessage(y), y.author === "bot" && b && b.length > 0 && d.setSuggestedReplies(b), !s.snapshot.isOpen && y.author === "bot" && (n(), i.play()), t.onMessage(y));
1109
1137
  },
1110
1138
  onTyping(c) {
1111
1139
  s.setTyping(c);
@@ -1114,11 +1142,11 @@ function U(o) {
1114
1142
  s.handleAgentEvent(c.eventType, c.active);
1115
1143
  },
1116
1144
  onError(c) {
1117
- t.onError(c), s.setConnected(!1), s.snapshot.isOpen && h.setOffline(!0);
1145
+ t.onError(c), s.setConnected(!1), s.snapshot.isOpen && d.setOffline(!0);
1118
1146
  }
1119
1147
  }
1120
1148
  );
1121
- u.mount(), h.mount();
1149
+ u.mount(), d.mount();
1122
1150
  const m = s.snapshot.messages, f = s.isSessionExpired(), p = s.getConversationGroups();
1123
1151
  if (m.length === 0 && e.welcome) {
1124
1152
  const c = {
@@ -1127,57 +1155,60 @@ function U(o) {
1127
1155
  text: e.welcome,
1128
1156
  ts: Date.now()
1129
1157
  };
1130
- s.addMessage(c), h.appendMessage(c);
1131
- } else f && m.length > 0 ? h.renderWithConversations(p, []) : p.length > 1 ? h.renderWithConversations(p.slice(0, -1), p[p.length - 1].messages) : h.updateMessages(m);
1132
- let S = !1;
1133
- const $ = s.subscribe(() => {
1158
+ s.addMessage(c), d.appendMessage(c);
1159
+ } else f && m.length > 0 ? d.renderWithConversations(p, []) : p.length > 1 ? d.renderWithConversations(p.slice(0, -1), p[p.length - 1].messages) : d.updateMessages(m);
1160
+ let T = !1;
1161
+ const W = s.subscribe(() => {
1134
1162
  const c = s.snapshot;
1135
- if (u.setOpen(c.isOpen), h.setAgentActivity(c.agentActivity, e.i18n), h.setOffline(!c.isConnected), h.updateTheme(P(e.theme)), c.isOpen) {
1136
- if (!S) {
1163
+ if (u.setOpen(c.isOpen), d.setAgentActivity(c.agentActivity, e.i18n), d.setOffline(!c.isConnected), d.updateTheme(P(e.theme)), c.isOpen) {
1164
+ if (!T) {
1137
1165
  const k = s.countUnread() > 0, b = s.lastReadTs;
1138
- l(), k && h.scrollToFirstUnread(b);
1166
+ a(), k && d.scrollToFirstUnread(b);
1139
1167
  }
1140
- S = !0, h.show();
1168
+ T = !0, d.show();
1141
1169
  } else
1142
- S = !1, h.hide();
1143
- }), W = m.length === 0 || f;
1170
+ T = !1, d.hide();
1171
+ }), F = m.length === 0 || f;
1144
1172
  n(), g().then(
1145
- () => d.connect().then((c) => {
1173
+ () => h.connect().then((c) => {
1146
1174
  var b, y;
1147
- e.persist && s.persistSession(c.sessionId), s.setConnected(!0), t.onTransportChange((b = d.kind) != null ? b : "ws");
1175
+ e.persist && s.persistSession(c.sessionId), s.setConnected(!0), t.onTransportChange((b = h.kind) != null ? b : "ws");
1148
1176
  const k = (y = c.suggestedReplies) != null ? y : [];
1149
- k.length > 0 && W && h.setSuggestedReplies(k);
1177
+ k.length > 0 && F && d.setSuggestedReplies(k);
1150
1178
  })
1151
1179
  ).catch((c) => {
1152
- t.onError(c), s.setConnected(!1), h.setOffline(!0);
1180
+ t.onError(c), s.setConnected(!1), d.setOffline(!0);
1153
1181
  });
1154
- const A = {
1182
+ const C = {
1155
1183
  transport: null,
1156
1184
  open() {
1157
- i.markUserInteraction(), s.open(), l(), t.onOpen();
1185
+ i.markUserInteraction(), s.open(), a(), t.onOpen();
1158
1186
  },
1159
1187
  close() {
1160
1188
  s.close(), t.onClose();
1161
1189
  },
1190
+ identify(c) {
1191
+ h.setUser(c);
1192
+ },
1162
1193
  destroy() {
1163
- $(), d.stop(), i.destroy(), u.destroy(), h.destroy();
1194
+ W(), h.stop(), i.destroy(), u.destroy(), d.destroy(), E === C && (E = null);
1164
1195
  }
1165
1196
  };
1166
- return A;
1197
+ return C;
1167
1198
  }
1168
1199
  function N() {
1169
1200
  var s, i;
1170
1201
  if (typeof document == "undefined") return;
1171
- const o = document.currentScript;
1172
- if (!o) return;
1173
- const e = o.dataset;
1202
+ const r = document.currentScript;
1203
+ if (!r) return;
1204
+ const e = r.dataset;
1174
1205
  if (e.autoinit === "false") return;
1175
1206
  const t = e.org;
1176
1207
  if (!t) {
1177
1208
  console.error("SpilkiWidget: data-org and is required for auto init");
1178
1209
  return;
1179
1210
  }
1180
- U({
1211
+ E = $({
1181
1212
  org: t,
1182
1213
  installationToken: e.installationToken,
1183
1214
  apiBase: e.apiBase,
@@ -1185,7 +1216,10 @@ function N() {
1185
1216
  theme: (i = e.theme) != null ? i : void 0
1186
1217
  });
1187
1218
  }
1188
- typeof window != "undefined" && (window.SpilkiWidget = window.SpilkiWidget || {}, window.SpilkiWidget.init = (o) => U(o));
1219
+ let E = null;
1220
+ typeof window != "undefined" && (window.SpilkiWidget = window.SpilkiWidget || {}, window.SpilkiWidget.init = (r) => (E = $(r), E), window.SpilkiWidget.identify = (r) => {
1221
+ E ? E.identify(r) : console.warn("SpilkiWidget: call init() before identify()");
1222
+ });
1189
1223
  N();
1190
1224
  N();
1191
1225
  //# sourceMappingURL=bootstrap.es.js.map