@spilki/widget 1.0.33 → 1.0.35

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.
package/dist/widget.es.js CHANGED
@@ -1,4 +1,4 @@
1
- const W = `
1
+ const F = `
2
2
  <style>
3
3
  :host {
4
4
  all: initial;
@@ -71,13 +71,13 @@ const W = `
71
71
  <span class="badge" hidden aria-hidden="true">0</span>
72
72
  </button>
73
73
  `;
74
- function F(o) {
74
+ function R(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 = W;
78
+ t.innerHTML = F;
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 F(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 R = "https://api.spilki.app", C = {
101
+ const K = "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 R = "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: R,
112
+ apiBase: K,
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 K(o) {
122
- var t, s, i, r, a, n, l, g;
123
- const e = { ...C, ...(t = o.i18n) != null ? t : {} };
121
+ function H(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 H(o = "msg") {
138
- return typeof crypto != "undefined" && crypto.randomUUID ? crypto.randomUUID() : `${o}-${Math.random().toString(16).slice(2)}`;
137
+ function z(r = "msg") {
138
+ return typeof crypto != "undefined" && crypto.randomUUID ? crypto.randomUUID() : `${r}-${Math.random().toString(16).slice(2)}`;
139
139
  }
140
- function z() {
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 _() {
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 : z() ? "dark" : "light";
144
+ function P(r) {
145
+ return r === "light" || r === "dark" ? r : _() ? "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 _(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
157
  const G = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
158
- function q(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}` : `${G[e.getMonth()]} ${e.getDate()}, ${i}`;
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}` : `${G[e.getMonth()]} ${e.getDate()}, ${i}`;
161
161
  }
162
- const Y = ':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, V = 2 * 60 * 1e3, J = 200, X = [
162
+ const Y = ':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, J = 2 * 60 * 1e3, X = 200, Z = [
163
163
  "😀",
164
164
  "😂",
165
165
  "🤣",
@@ -211,10 +211,10 @@ const Y = ':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 E(r) {
215
+ return r.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
216
216
  }
217
- class Z {
217
+ class Q {
218
218
  constructor(e) {
219
219
  this.options = e, this.relativeTimeFormat = new Intl.RelativeTimeFormat(void 0, {
220
220
  numeric: "auto"
@@ -229,7 +229,7 @@ class Z {
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 = E(e.i18n.title), s = E(e.i18n.typing), i = E(e.i18n.offline), o = E(e.i18n.placeholder), l = E(e.i18n.sendLabel);
233
233
  this.shadow.innerHTML = `
234
234
  <style>${Y}</style>
235
235
  <div class="wrapper" role="dialog" aria-modal="true" aria-label="${t}">
@@ -248,7 +248,7 @@ class Z {
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 Z {
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 Z {
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 Z {
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 Z {
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 = q(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 Z {
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 Z {
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 <= V && 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 <= J && 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 Z {
458
458
  return new Date(t.getFullYear(), t.getMonth(), t.getDate()).getTime();
459
459
  }
460
460
  isScrolledUp() {
461
- return this.distanceFromBottom() > J;
461
+ return this.distanceFromBottom() > X;
462
462
  }
463
463
  distanceFromBottom() {
464
464
  return this.messagesEl.scrollHeight - this.messagesEl.scrollTop - this.messagesEl.clientHeight;
@@ -505,7 +505,7 @@ class Z {
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(), X.forEach((e) => {
508
+ this.emojiPickerEl.replaceChildren(), Z.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 Z {
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 Z {
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 Q {
536
+ const v = 1500, L = 8e3;
537
+ class ee {
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 Q {
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 Q {
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 Q {
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 Q {
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 Q {
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 Q {
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,20 +690,20 @@ class Q {
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: h }) => {
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(h, { stream: !0 });
703
+ const d = a.split(`
676
704
 
677
705
  `);
678
- l = (m = d.pop()) != null ? m : "";
706
+ a = (m = d.pop()) != null ? m : "";
679
707
  for (const f of d)
680
708
  for (const p of f.split(`
681
709
  `))
@@ -686,8 +714,8 @@ class Q {
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 Q {
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 Q {
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,22 +763,22 @@ class Q {
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" && te(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" && se(e.payload) && this.handlers.onAgentEvent(e.payload);
739
767
  return;
740
768
  }
741
769
  this.handlers.onMessage(e);
742
770
  }
743
771
  }
744
- const ee = /* @__PURE__ */ new Set(["TYPING", "THINKING", "SEARCHING", "EXECUTING_TOOL"]);
745
- function te(o) {
746
- if (typeof o != "object" || o === null) return !1;
747
- const e = o;
748
- return ee.has(e.eventType) && typeof e.active == "boolean";
772
+ const te = /* @__PURE__ */ new Set(["TYPING", "THINKING", "SEARCHING", "EXECUTING_TOOL"]);
773
+ function se(r) {
774
+ if (typeof r != "object" || r === null) return !1;
775
+ const e = r;
776
+ return te.has(e.eventType);
749
777
  }
750
- const L = 30 * 60 * 1e3, se = 3e4, T = 30;
751
- class ie {
778
+ const U = 30 * 60 * 1e3, ie = 3e4, A = 30;
779
+ class re {
752
780
  constructor(e, t) {
753
- this.org = e, this.listeners = /* @__PURE__ */ new Set(), this.activityTimer = null, this.activeEvents = /* @__PURE__ */ new Set(), this.state = {
781
+ this.org = e, this.listeners = /* @__PURE__ */ new Set(), this.activityTimer = null, this.state = {
754
782
  isOpen: !1,
755
783
  agentActivity: null,
756
784
  isConnected: !1,
@@ -774,27 +802,16 @@ class ie {
774
802
  close() {
775
803
  this.state.isOpen && (this.state.isOpen = !1, this.emit());
776
804
  }
777
- handleAgentEvent(e, t) {
778
- t ? this.activeEvents.add(e) : this.activeEvents.delete(e), this.updateDisplayedActivity();
805
+ handleAgentEvent(e) {
806
+ this.activityTimer && (clearTimeout(this.activityTimer), this.activityTimer = null), this.activityTimer = setTimeout(() => {
807
+ this.state.agentActivity = null, this.activityTimer = null, this.emit();
808
+ }, ie), this.state.agentActivity !== e && (this.state.agentActivity = e, this.emit());
779
809
  }
780
- setTyping(e) {
781
- this.handleAgentEvent("TYPING", e);
810
+ clearAgentActivity() {
811
+ this.resetAgentActivity() && this.emit();
782
812
  }
783
- updateDisplayedActivity() {
784
- this.activityTimer && (clearTimeout(this.activityTimer), this.activityTimer = null), this.activeEvents.size > 0 && (this.activityTimer = setTimeout(() => {
785
- this.activeEvents.clear(), this.state.agentActivity = null, this.activityTimer = null, this.emit();
786
- }, se));
787
- const e = this.resolveTopActivity();
788
- this.state.agentActivity !== e && (this.state.agentActivity = e, this.emit());
789
- }
790
- resolveTopActivity() {
791
- var t;
792
- return this.activeEvents.size === 0 ? null : (t = [
793
- "EXECUTING_TOOL",
794
- "SEARCHING",
795
- "THINKING",
796
- "TYPING"
797
- ].find((s) => this.activeEvents.has(s))) != null ? t : null;
813
+ resetAgentActivity() {
814
+ return this.activityTimer && (clearTimeout(this.activityTimer), this.activityTimer = null), this.state.agentActivity === null ? !1 : (this.state.agentActivity = null, !0);
798
815
  }
799
816
  setConnected(e) {
800
817
  this.state.isConnected !== e && (this.state.isConnected = e, this.emit());
@@ -802,15 +819,15 @@ class ie {
802
819
  addMessage(e) {
803
820
  var s, i;
804
821
  const t = {
805
- id: (s = e.id) != null ? s : H("msg"),
822
+ id: (s = e.id) != null ? s : z("msg"),
806
823
  ts: (i = e.ts) != null ? i : Date.now(),
807
824
  author: e.author,
808
825
  text: e.text
809
826
  };
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);
827
+ return this.state.messages.some((o) => o.id === t.id) ? null : (this.state.messages = S([...this.state.messages, t], A), this.resetAgentActivity(), this.persistMessages(), this.touchActivity(), this.emit(), t);
811
828
  }
812
829
  setMessages(e) {
813
- this.state.messages = x(e, T), this.persistMessages(), this.emit();
830
+ this.state.messages = S(e, A), this.persistMessages(), this.emit();
814
831
  }
815
832
  clearMessages() {
816
833
  this.state.messages = [], this.persistMessages(), this.emit();
@@ -902,7 +919,7 @@ class ie {
902
919
  }
903
920
  isSessionExpired() {
904
921
  const e = this.lastActivityTs;
905
- return e === 0 ? !1 : Date.now() - e >= L;
922
+ return e === 0 ? !1 : Date.now() - e >= U;
906
923
  }
907
924
  getConversationGroups() {
908
925
  const e = this.state.messages;
@@ -910,7 +927,7 @@ class ie {
910
927
  const t = [];
911
928
  let s = { startTs: e[0].ts, messages: [e[0]] };
912
929
  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]);
930
+ 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
931
  return t.push(s), t;
915
932
  }
916
933
  emit() {
@@ -930,14 +947,14 @@ class ie {
930
947
  const e = localStorage.getItem(this.historyKey);
931
948
  if (!e) return [];
932
949
  const t = JSON.parse(e);
933
- return Array.isArray(t) ? x(t, T) : [];
950
+ return Array.isArray(t) ? S(t, A) : [];
934
951
  } catch (e) {
935
952
  return console.error("SpilkiWidget: unable to load messages", e), [];
936
953
  }
937
954
  }
938
955
  }
939
- function oe(o) {
940
- const e = o.replace(/-/g, "+").replace(/_/g, "/"), t = e.padEnd(e.length + (4 - e.length % 4) % 4, "=");
956
+ function oe(r) {
957
+ const e = r.replace(/-/g, "+").replace(/_/g, "/"), t = e.padEnd(e.length + (4 - e.length % 4) % 4, "=");
941
958
  if (typeof atob == "function")
942
959
  return decodeURIComponent(
943
960
  Array.prototype.map.call(atob(t), (i) => `%${`00${i.charCodeAt(0).toString(16)}`.slice(-2)}`).join("")
@@ -947,9 +964,9 @@ function oe(o) {
947
964
  return s.from(t, "base64").toString("utf8");
948
965
  throw new Error("SpilkiWidget: no base64 decoder available");
949
966
  }
950
- function re(o) {
951
- if (!o) return null;
952
- const e = o.split(".");
967
+ function ne(r) {
968
+ if (!r) return null;
969
+ const e = r.split(".");
953
970
  if (e.length < 2) return null;
954
971
  try {
955
972
  const t = oe(e[1]);
@@ -958,13 +975,13 @@ function re(o) {
958
975
  return console.error("SpilkiWidget: unable to parse JWT", t), null;
959
976
  }
960
977
  }
961
- function ne(o) {
962
- const e = re(o);
978
+ function ae(r) {
979
+ const e = ne(r);
963
980
  if (!(e != null && e.exp) || typeof e.exp != "number") return !0;
964
981
  const t = Math.floor(Date.now() / 1e3);
965
982
  return e.exp < t + 60;
966
983
  }
967
- const ae = {
984
+ const le = {
968
985
  onOpen() {
969
986
  },
970
987
  onClose() {
@@ -976,8 +993,8 @@ const ae = {
976
993
  onTransportChange() {
977
994
  }
978
995
  };
979
- async function le(o, e, t) {
980
- const s = `${o.replace(/\/$/, "")}/widget/install`, i = await fetch(s, {
996
+ async function ce(r, e, t) {
997
+ const s = `${r.replace(/\/$/, "")}/widget/install`, i = await fetch(s, {
981
998
  method: "POST",
982
999
  headers: { "Content-Type": "application/json", Origin: window.location.origin },
983
1000
  body: JSON.stringify({ token: e, organisationId: t })
@@ -985,29 +1002,29 @@ async function le(o, e, t) {
985
1002
  if (!i.ok) throw new Error(`SpilkiWidget: install failed (${i.status})`);
986
1003
  return (await i.json()).accessToken;
987
1004
  }
988
- async function ce(o, e) {
989
- const t = `${o.replace(/\/$/, "")}/widget/refresh`, s = await fetch(t, {
1005
+ async function he(r, e) {
1006
+ const t = `${r.replace(/\/$/, "")}/widget/refresh`, s = await fetch(t, {
990
1007
  method: "POST",
991
1008
  headers: { "X-Authorization": `Bearer ${e}`, Origin: window.location.origin }
992
1009
  });
993
1010
  if (!s.ok) throw new Error(`SpilkiWidget: refresh failed (${s.status})`);
994
1011
  return (await s.json()).accessToken;
995
1012
  }
996
- function he(o) {
1013
+ function de(r) {
997
1014
  let e = !1, t = null;
998
1015
  const s = [], i = () => {
999
1016
  var n;
1000
- if (!o) return null;
1017
+ if (!r) return null;
1001
1018
  if (t) return t;
1002
1019
  try {
1003
- const l = (n = window.AudioContext) != null ? n : window.webkitAudioContext;
1004
- if (!l) return null;
1005
- t = new l();
1020
+ const a = (n = window.AudioContext) != null ? n : window.webkitAudioContext;
1021
+ if (!a) return null;
1022
+ t = new a();
1006
1023
  } catch {
1007
1024
  return null;
1008
1025
  }
1009
1026
  return t;
1010
- }, r = () => {
1027
+ }, o = () => {
1011
1028
  e = !0;
1012
1029
  try {
1013
1030
  const n = i();
@@ -1015,73 +1032,73 @@ function he(o) {
1015
1032
  });
1016
1033
  } catch {
1017
1034
  }
1018
- }, a = (n) => {
1019
- const l = () => {
1020
- r(), window.removeEventListener(n, l, !0);
1035
+ }, l = (n) => {
1036
+ const a = () => {
1037
+ o(), window.removeEventListener(n, a, !0);
1021
1038
  };
1022
- s.push({ type: n, listener: l }), window.addEventListener(n, l, { capture: !0, passive: !0 });
1039
+ s.push({ type: n, listener: a }), window.addEventListener(n, a, { capture: !0, passive: !0 });
1023
1040
  };
1024
- return a("pointerdown"), a("keydown"), {
1025
- markUserInteraction: r,
1041
+ return l("pointerdown"), l("keydown"), {
1042
+ markUserInteraction: o,
1026
1043
  play() {
1027
- if (!o || !e) return;
1044
+ if (!r || !e) return;
1028
1045
  const n = i();
1029
1046
  if (!n || n.state !== "running") return;
1030
- const l = n.createGain();
1031
- l.gain.value = 0.15, l.connect(n.destination);
1047
+ const a = n.createGain();
1048
+ a.gain.value = 0.15, a.connect(n.destination);
1032
1049
  const g = (h, d, m) => {
1033
1050
  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);
1051
+ 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(a), f.start(d), f.stop(d + m + 0.02);
1035
1052
  }, u = n.currentTime;
1036
1053
  g(880, u, 0.08), g(1175, u + 0.09, 0.08);
1037
1054
  },
1038
1055
  destroy() {
1039
- s.forEach(({ type: n, listener: l }) => {
1040
- window.removeEventListener(n, l, !0);
1056
+ s.forEach(({ type: n, listener: a }) => {
1057
+ window.removeEventListener(n, a, !0);
1041
1058
  }), s.length = 0, t && t.close().catch(() => {
1042
1059
  }), t = null;
1043
1060
  }
1044
1061
  };
1045
1062
  }
1046
- function U(o) {
1047
- var I, O, M, B;
1048
- if (!o.org)
1063
+ function $(r) {
1064
+ var O, M, B, j;
1065
+ if (!r.org)
1049
1066
  throw new Error("SpilkiWidget: org is required");
1050
- const e = K(o);
1051
- _(e.allowedOriginsHint);
1052
- const t = { ...ae, ...(I = e.hooks) != null ? I : {} }, s = new ie(e.org, { persist: e.persist }), i = he(e.sound);
1053
- let r = (O = s.accessToken) != null ? O : void 0, a = null;
1067
+ const e = H(r);
1068
+ q(e.allowedOriginsHint);
1069
+ const t = { ...le, ...(O = e.hooks) != null ? O : {} }, s = new re(e.org, { persist: e.persist }), i = de(e.sound);
1070
+ let o = (M = s.accessToken) != null ? M : void 0, l = null;
1054
1071
  const n = () => {
1055
1072
  u.setBadge(s.countUnread());
1056
- }, l = () => {
1073
+ }, a = () => {
1057
1074
  s.markRead(), u.setBadge(0);
1058
- }, g = async () => a || (a = (async () => {
1075
+ }, g = async () => l || (l = (async () => {
1059
1076
  try {
1060
- if (r && ne(r))
1077
+ if (o && ae(o))
1061
1078
  try {
1062
- r = await ce(e.apiBase, r), s.persistAccessToken(r), d.setAccessToken(r);
1079
+ o = await he(e.apiBase, o), s.persistAccessToken(o), d.setAccessToken(o);
1063
1080
  return;
1064
1081
  } catch {
1065
- r = void 0, s.clearAccessToken();
1082
+ o = void 0, s.clearAccessToken();
1066
1083
  }
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 le(e.apiBase, o.installationToken, o.org), s.persistAccessToken(r), d.setAccessToken(r);
1084
+ if (!o) {
1085
+ if (!r.installationToken) throw new Error("SpilkiWidget: missing installationToken");
1086
+ if (!r.org) throw new Error("SpilkiWidget: missing org");
1087
+ o = await ce(e.apiBase, r.installationToken, r.org), s.persistAccessToken(o), d.setAccessToken(o);
1071
1088
  }
1072
1089
  } finally {
1073
- a = null;
1090
+ l = null;
1074
1091
  }
1075
- })(), a), u = F({
1092
+ })(), l), u = R({
1076
1093
  color: e.color,
1077
- position: (M = e.position) != null ? M : "bottom-right",
1094
+ position: (B = e.position) != null ? B : "bottom-right",
1078
1095
  onClick: () => {
1079
- s.snapshot.isOpen ? (s.close(), t.onClose()) : (i.markUserInteraction(), s.open(), l(), t.onOpen());
1096
+ s.snapshot.isOpen ? (s.close(), t.onClose()) : (i.markUserInteraction(), s.open(), a(), t.onOpen());
1080
1097
  }
1081
- }), h = new Z({
1098
+ }), h = new Q({
1082
1099
  color: e.color,
1083
1100
  theme: P(e.theme),
1084
- position: (B = e.position) != null ? B : "bottom-right",
1101
+ position: (j = e.position) != null ? j : "bottom-right",
1085
1102
  i18n: e.i18n,
1086
1103
  onClose: () => {
1087
1104
  s.close(), t.onClose();
@@ -1092,26 +1109,26 @@ function U(o) {
1092
1109
  t.onError(b), s.setConnected(!1), h.setOffline(!0);
1093
1110
  }));
1094
1111
  }
1095
- }), d = new Q(
1112
+ }), d = new ee(
1096
1113
  {
1097
1114
  apiBase: e.apiBase,
1098
- accessToken: r,
1115
+ accessToken: o,
1099
1116
  org: e.org,
1100
1117
  sessionId: s.sessionId
1101
1118
  },
1102
1119
  {
1103
1120
  onOpen(c) {
1104
- A.transport = c, t.onTransportChange(c), s.setConnected(!0), h.setOffline(!1);
1121
+ C.transport = c, t.onTransportChange(c), s.setConnected(!0), h.setOffline(!1);
1105
1122
  },
1106
1123
  onMessage(c) {
1107
1124
  const b = c.suggestedReplies, y = s.addMessage(c);
1108
1125
  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));
1109
1126
  },
1110
- onTyping(c) {
1111
- s.setTyping(c);
1127
+ onTyping() {
1128
+ s.handleAgentEvent("TYPING");
1112
1129
  },
1113
1130
  onAgentEvent(c) {
1114
- s.handleAgentEvent(c.eventType, c.active);
1131
+ s.handleAgentEvent(c.eventType);
1115
1132
  },
1116
1133
  onError(c) {
1117
1134
  t.onError(c), s.setConnected(!1), s.snapshot.isOpen && h.setOffline(!0);
@@ -1129,55 +1146,58 @@ function U(o) {
1129
1146
  };
1130
1147
  s.addMessage(c), h.appendMessage(c);
1131
1148
  } 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;
1149
+ let T = !1;
1133
1150
  const N = s.subscribe(() => {
1134
1151
  const c = s.snapshot;
1135
1152
  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) {
1153
+ if (!T) {
1137
1154
  const k = s.countUnread() > 0, b = s.lastReadTs;
1138
- l(), k && h.scrollToFirstUnread(b);
1155
+ a(), k && h.scrollToFirstUnread(b);
1139
1156
  }
1140
- S = !0, h.show();
1157
+ T = !0, h.show();
1141
1158
  } else
1142
- S = !1, h.hide();
1143
- }), $ = m.length === 0 || f;
1159
+ T = !1, h.hide();
1160
+ }), W = m.length === 0 || f;
1144
1161
  n(), g().then(
1145
1162
  () => d.connect().then((c) => {
1146
1163
  var b, y;
1147
1164
  e.persist && s.persistSession(c.sessionId), s.setConnected(!0), t.onTransportChange((b = d.kind) != null ? b : "ws");
1148
1165
  const k = (y = c.suggestedReplies) != null ? y : [];
1149
- k.length > 0 && $ && h.setSuggestedReplies(k);
1166
+ k.length > 0 && W && h.setSuggestedReplies(k);
1150
1167
  })
1151
1168
  ).catch((c) => {
1152
1169
  t.onError(c), s.setConnected(!1), h.setOffline(!0);
1153
1170
  });
1154
- const A = {
1171
+ const C = {
1155
1172
  transport: null,
1156
1173
  open() {
1157
- i.markUserInteraction(), s.open(), l(), t.onOpen();
1174
+ i.markUserInteraction(), s.open(), a(), t.onOpen();
1158
1175
  },
1159
1176
  close() {
1160
1177
  s.close(), t.onClose();
1161
1178
  },
1179
+ identify(c) {
1180
+ d.setUser(c);
1181
+ },
1162
1182
  destroy() {
1163
- N(), d.stop(), i.destroy(), u.destroy(), h.destroy();
1183
+ N(), d.stop(), i.destroy(), u.destroy(), h.destroy(), x === C && (x = null);
1164
1184
  }
1165
1185
  };
1166
- return A;
1186
+ return C;
1167
1187
  }
1168
- function de() {
1188
+ function pe() {
1169
1189
  var s, i;
1170
1190
  if (typeof document == "undefined") return;
1171
- const o = document.currentScript;
1172
- if (!o) return;
1173
- const e = o.dataset;
1191
+ const r = document.currentScript;
1192
+ if (!r) return;
1193
+ const e = r.dataset;
1174
1194
  if (e.autoinit === "false") return;
1175
1195
  const t = e.org;
1176
1196
  if (!t) {
1177
1197
  console.error("SpilkiWidget: data-org and is required for auto init");
1178
1198
  return;
1179
1199
  }
1180
- U({
1200
+ x = $({
1181
1201
  org: t,
1182
1202
  installationToken: e.installationToken,
1183
1203
  apiBase: e.apiBase,
@@ -1185,10 +1205,13 @@ function de() {
1185
1205
  theme: (i = e.theme) != null ? i : void 0
1186
1206
  });
1187
1207
  }
1188
- typeof window != "undefined" && (window.SpilkiWidget = window.SpilkiWidget || {}, window.SpilkiWidget.init = (o) => U(o));
1189
- de();
1208
+ let x = null;
1209
+ typeof window != "undefined" && (window.SpilkiWidget = window.SpilkiWidget || {}, window.SpilkiWidget.init = (r) => (x = $(r), x), window.SpilkiWidget.identify = (r) => {
1210
+ x ? x.identify(r) : console.warn("SpilkiWidget: call init() before identify()");
1211
+ });
1212
+ pe();
1190
1213
  export {
1191
- de as autoInit,
1192
- U as initSpilkiWidget
1214
+ pe as autoInit,
1215
+ $ as initSpilkiWidget
1193
1216
  };
1194
1217
  //# sourceMappingURL=widget.es.js.map