@sendystack/widget 0.1.2 → 0.1.4

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/index.es.js CHANGED
@@ -1,6 +1,6 @@
1
1
  //#region src/index.ts
2
- var e = "sendystack-widget-root", t = (e) => `sendystack_sessions_${e}`, n = (e) => `sendystack_current_${e}`, r = (e) => `sendystack_crawled_${e}`, i = "<svg viewBox=\"0 0 24 24\" width=\"26\" height=\"26\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.9\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M4 5h13a2 2 0 0 1 2 2v7a2 2 0 0 1-2 2H9l-5 4v-4a2 2 0 0 1-2-2V7a2 2 0 0 1 2-2z\"/></svg>", a = "<svg viewBox=\"0 0 24 24\" width=\"22\" height=\"22\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><path d=\"M6 6l12 12M18 6L6 18\"/></svg>", o = "<svg viewBox=\"0 0 24 24\" width=\"19\" height=\"19\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.9\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M4 12l16-7-7 16-2-7-7-2z\"/></svg>", s = "<svg viewBox=\"0 0 24 24\" width=\"18\" height=\"18\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.9\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M12 3l2.5 5 5 .7-3.7 3.5.9 5L12 19.8 7.3 17.2l.9-5L4.5 8.7l5-.7z\"/></svg>", c = "<svg viewBox=\"0 0 24 24\" width=\"18\" height=\"18\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.9\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"9\"/><path d=\"M12 7v5l3 3\"/></svg>", l = "<svg viewBox=\"0 0 24 24\" width=\"18\" height=\"18\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.9\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M12 5v14M5 12h14\"/></svg>", u = "<svg viewBox=\"0 0 24 24\" width=\"18\" height=\"18\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.9\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M15 18l-6-6 6-6\"/></svg>";
3
- async function d(e, t) {
2
+ var e = "https://app.sendystack.org/sendy_stack_favico.png", t = "sendystack-widget-root", n = (e) => `sendystack_sessions_${e}`, r = (e) => `sendystack_current_${e}`, i = (e) => `sendystack_crawled_${e}`, a = "<svg viewBox=\"0 0 24 24\" width=\"22\" height=\"22\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><path d=\"M6 6l12 12M18 6L6 18\"/></svg>", o = "<svg viewBox=\"0 0 24 24\" width=\"19\" height=\"19\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.9\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M4 12l16-7-7 16-2-7-7-2z\"/></svg>", s = "<svg viewBox=\"0 0 24 24\" width=\"18\" height=\"18\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.9\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"9\"/><path d=\"M12 7v5l3 3\"/></svg>", c = "<svg viewBox=\"0 0 24 24\" width=\"18\" height=\"18\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.9\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M12 5v14M5 12h14\"/></svg>", l = "<svg viewBox=\"0 0 24 24\" width=\"18\" height=\"18\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.9\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M15 18l-6-6 6-6\"/></svg>";
3
+ async function u(e, t) {
4
4
  let n = await (await fetch(`${e}/embedConfig?embedToken=${encodeURIComponent(t)}`)).json();
5
5
  if (!n.ok) throw Error(n.error || "failed to load widget config");
6
6
  return {
@@ -8,37 +8,39 @@ async function d(e, t) {
8
8
  whiteLabel: !!n.whiteLabel
9
9
  };
10
10
  }
11
- function f(t) {
12
- let n = t.position === "left" ? "left" : "right", r = document.createElement("style");
11
+ function d(e) {
12
+ let n = e.position === "left" ? "left" : "right", r = document.createElement("style");
13
13
  r.textContent = `
14
- #${e} *{box-sizing:border-box;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;}
15
- #${e}{position:fixed;${n}:20px;bottom:20px;z-index:2147483000;}
16
- .ssk-launcher{position:relative;width:60px;height:60px;border-radius:50%;border:none;cursor:pointer;color:#fff;
17
- background:linear-gradient(140deg,${t.accent2},${t.accent});box-shadow:0 16px 36px rgba(0,0,0,.28);
14
+ #${t} *{box-sizing:border-box;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;}
15
+ #${t}{position:fixed;${n}:20px;bottom:20px;z-index:2147483000;}
16
+ .ssk-launcher{position:relative;width:60px;height:60px;border-radius:50%;border:none;cursor:pointer;color:${e.accent};
17
+ background:#fff;box-shadow:0 16px 36px rgba(0,0,0,.22),0 0 0 1px rgba(27,39,51,.05);
18
18
  display:flex;align-items:center;justify-content:center;transition:transform .25s cubic-bezier(.16,1,.3,1);margin-left:auto;}
19
19
  .ssk-launcher:hover{transform:translateY(-3px) scale(1.04);}
20
20
  .ssk-launcher:active{transform:scale(.96);}
21
21
  .ssk-launcher .ssk-open{display:flex;} .ssk-launcher .ssk-x{display:none;}
22
- #${e}.open .ssk-launcher .ssk-open{display:none;} #${e}.open .ssk-launcher .ssk-x{display:flex;}
23
- .ssk-pulse{position:absolute;inset:0;border-radius:50%;background:${t.accent};opacity:.5;z-index:-1;animation:sskPulse 2.4s ease-out infinite;}
22
+ .ssk-launcher .ssk-open img{width:32px;height:32px;object-fit:contain;}
23
+ #${t}.open .ssk-launcher .ssk-open{display:none;} #${t}.open .ssk-launcher .ssk-x{display:flex;}
24
+ .ssk-pulse{position:absolute;inset:0;border-radius:50%;background:${e.accent};opacity:.5;z-index:-1;animation:sskPulse 2.4s ease-out infinite;}
24
25
  @keyframes sskPulse{0%{transform:scale(1);opacity:.5;}70%,100%{transform:scale(1.7);opacity:0;}}
25
26
  @media(prefers-reduced-motion:reduce){.ssk-pulse{animation:none;}}
26
- .ssk-greeting{position:absolute;bottom:72px;${n}:0;max-width:220px;padding:11px 14px;border-radius:14px;background:#fff;
27
+ .ssk-greeting{position:absolute;bottom:72px;${n}:0;width:max-content;max-width:min(300px,calc(100vw - 48px));padding:11px 16px;border-radius:14px;background:#fff;
27
28
  color:#1b2733;font-size:13.5px;line-height:1.4;font-weight:600;box-shadow:0 16px 38px rgba(27,39,51,.18);
28
29
  transform:translateY(8px);opacity:0;pointer-events:none;transition:opacity .3s,transform .3s;}
29
30
  .ssk-greeting.shown{opacity:1;transform:translateY(0);}
30
- #${e}.open .ssk-greeting{display:none;}
31
+ #${t}.open .ssk-greeting{display:none;}
31
32
  .ssk-panel{position:absolute;bottom:74px;${n}:0;width:368px;max-width:calc(100vw - 32px);height:560px;
32
33
  max-height:calc(100vh - 110px);background:#fff;border-radius:20px;overflow:hidden;display:flex;flex-direction:column;
33
34
  box-shadow:0 36px 80px rgba(20,20,30,.3);border:1px solid rgba(27,39,51,.08);
34
35
  transform:translateY(14px) scale(.96);opacity:0;pointer-events:none;transform-origin:bottom ${n};
35
36
  transition:transform .25s cubic-bezier(.16,1,.3,1),opacity .2s;}
36
- #${e}.open .ssk-panel{transform:translateY(0) scale(1);opacity:1;pointer-events:auto;}
37
+ #${t}.open .ssk-panel{transform:translateY(0) scale(1);opacity:1;pointer-events:auto;}
37
38
  .ssk-head{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:13px 14px;flex-shrink:0;
38
- background:linear-gradient(135deg,${t.accent2},${t.accent});color:#fff;}
39
+ background:linear-gradient(135deg,${e.accent2},${e.accent});color:#fff;}
39
40
  .ssk-id{display:flex;align-items:center;gap:9px;min-width:0;}
40
41
  .ssk-avatar{width:36px;height:36px;border-radius:50%;display:flex;align-items:center;justify-content:center;
41
- background:rgba(255,255,255,.22);flex:0 0 auto;}
42
+ background:#fff;flex:0 0 auto;}
43
+ .ssk-avatar img{width:22px;height:22px;object-fit:contain;}
42
44
  .ssk-id-text{min-width:0;}
43
45
  .ssk-id-text strong{display:block;font-size:14px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
44
46
  .ssk-status{display:flex;align-items:center;gap:5px;font-size:11px;opacity:.9;}
@@ -65,29 +67,41 @@ function f(t) {
65
67
  .ssk-history-item.active{background:rgba(226,112,15,.1);}
66
68
  .ssk-history-title{display:block;font-size:13.5px;font-weight:600;color:#1b2733;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
67
69
  .ssk-history-time{display:block;font-size:11px;color:#9aa3ad;margin-top:2px;}
68
- .ssk-msg{display:flex;max-width:86%;}
69
- .ssk-msg.bot{align-self:flex-start;} .ssk-msg.user{align-self:flex-end;}
70
+ .ssk-msg{display:flex;flex-direction:column;align-items:flex-start;max-width:86%;}
71
+ .ssk-msg.bot{align-self:flex-start;} .ssk-msg.user{align-self:flex-end;align-items:flex-end;}
70
72
  .ssk-bubble-text{padding:10px 13px;border-radius:15px;font-size:14px;line-height:1.5;word-wrap:break-word;}
71
73
  .ssk-msg.bot .ssk-bubble-text{background:#fff;border:1px solid rgba(27,39,51,.08);border-bottom-left-radius:5px;
72
74
  box-shadow:0 5px 14px rgba(27,39,51,.05);color:#1b2733;}
73
- .ssk-msg.user .ssk-bubble-text{background:linear-gradient(135deg,${t.accent2},${t.accent});color:#fff;border-bottom-right-radius:5px;}
74
- .ssk-bubble-text a{color:${t.accent};font-weight:700;text-decoration:underline;}
75
+ .ssk-msg.user .ssk-bubble-text{background:linear-gradient(135deg,${e.accent2},${e.accent});color:#fff;border-bottom-right-radius:5px;}
76
+ .ssk-bubble-text a{color:${e.accent};font-weight:700;text-decoration:underline;}
75
77
  .ssk-msg.user .ssk-bubble-text a{color:#fff;}
76
78
  .ssk-bubble-text p{margin:0 0 7px;} .ssk-bubble-text p:last-child{margin:0;}
79
+ .ssk-sources{display:flex;flex-wrap:wrap;gap:6px;margin-top:7px;}
80
+ .ssk-source-pill{display:inline-flex;align-items:center;padding:5px 11px;border-radius:999px;
81
+ background:rgba(27,39,51,.05);color:${e.accent};font-size:12px;font-weight:600;
82
+ text-decoration:none;border:1px solid rgba(27,39,51,.08);}
83
+ .ssk-source-pill:hover{background:rgba(27,39,51,.08);}
84
+ .ssk-action-btn{margin-top:7px;border:none;border-radius:12px;padding:9px 15px;font-size:13px;font-weight:700;
85
+ cursor:pointer;color:#fff;background:linear-gradient(135deg,${e.accent2},${e.accent});
86
+ box-shadow:0 6px 16px rgba(0,0,0,.15);transition:transform .15s;}
87
+ .ssk-action-btn:hover{transform:translateY(-1px);}
88
+ .ssk-action-btn:disabled{cursor:default;transform:none;}
89
+ .ssk-action-btn.ssk-action-done{opacity:.75;}
90
+ .ssk-action-btn.ssk-action-missing{background:rgba(27,39,51,.08);color:#6b7280;box-shadow:none;}
77
91
  .ssk-typing{padding:0 14px 10px;flex-shrink:0;}
78
92
  .ssk-typing[hidden]{display:none;}
79
93
  .ssk-dots{display:inline-flex;gap:5px;padding:10px 13px;background:#fff;border:1px solid rgba(27,39,51,.08);
80
94
  border-radius:15px;border-bottom-left-radius:5px;}
81
- .ssk-dots i{width:7px;height:7px;border-radius:50%;background:${t.accent};animation:sskWave 1.2s ease-in-out infinite;}
95
+ .ssk-dots i{width:7px;height:7px;border-radius:50%;background:${e.accent};animation:sskWave 1.2s ease-in-out infinite;}
82
96
  .ssk-dots i:nth-child(2){animation-delay:.15s;} .ssk-dots i:nth-child(3){animation-delay:.3s;}
83
97
  @keyframes sskWave{0%,60%,100%{transform:translateY(0);opacity:.5;}30%{transform:translateY(-5px);opacity:1;}}
84
98
  .ssk-inputrow{display:flex;align-items:flex-end;gap:8px;padding:11px;border-top:1px solid rgba(27,39,51,.08);
85
99
  background:#fff;flex-shrink:0;}
86
100
  .ssk-inputrow textarea{flex:1;resize:none;max-height:90px;border:1px solid rgba(27,39,51,.14);border-radius:13px;
87
101
  padding:10px 12px;font-size:14px;line-height:1.4;color:#1b2733;outline:none;background:#fbf7f1;}
88
- .ssk-inputrow textarea:focus{border-color:${t.accent};background:#fff;}
102
+ .ssk-inputrow textarea:focus{border-color:${e.accent};background:#fff;}
89
103
  .ssk-sendbtn{width:42px;height:42px;flex:0 0 auto;border:none;border-radius:12px;cursor:pointer;color:#fff;
90
- background:linear-gradient(135deg,${t.accent2},${t.accent});display:flex;align-items:center;justify-content:center;
104
+ background:linear-gradient(135deg,${e.accent2},${e.accent});display:flex;align-items:center;justify-content:center;
91
105
  box-shadow:0 9px 20px rgba(0,0,0,.18);transition:transform .15s;}
92
106
  .ssk-sendbtn:hover{transform:translateY(-2px);}
93
107
  .ssk-sendbtn:disabled{opacity:.6;cursor:default;transform:none;}
@@ -95,47 +109,78 @@ function f(t) {
95
109
  .ssk-foot a{color:inherit;text-decoration:underline;}
96
110
  `, document.head.appendChild(r);
97
111
  }
98
- function p(e) {
112
+ function f(e) {
99
113
  return `<p>${e.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/g, "<a href=\"$2\" target=\"_blank\" rel=\"noopener\">$1</a>").replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>").replace(/\n{2,}/g, "</p><p>").replace(/\n/g, "<br>")}</p>`;
100
114
  }
115
+ function p(e) {
116
+ let t = document.createElement("button");
117
+ t.type = "button", t.className = "ssk-action-btn", t.textContent = e.label;
118
+ let n = !e.confirmRequired;
119
+ return t.onclick = () => {
120
+ let r = e.selector ? document.querySelector(e.selector) : null;
121
+ if (!r) {
122
+ if (e.fallbackUrl) {
123
+ location.href = e.fallbackUrl;
124
+ return;
125
+ }
126
+ t.textContent = "Couldn't find that on this page", t.classList.add("ssk-action-missing"), t.disabled = !0;
127
+ return;
128
+ }
129
+ if (!n) {
130
+ n = !0, t.textContent = `Click to confirm: ${e.label}`;
131
+ return;
132
+ }
133
+ r.click(), t.textContent = "Done ✓", t.disabled = !0, t.classList.add("ssk-action-done");
134
+ }, t;
135
+ }
101
136
  function m(e, t) {
102
137
  let n = document.createElement("div");
103
138
  n.className = `ssk-msg ${t.role}`;
104
139
  let r = document.createElement("div");
105
- r.className = "ssk-bubble-text", r.innerHTML = p(t.text), n.appendChild(r), e.appendChild(n), e.scrollTop = e.scrollHeight;
140
+ if (r.className = "ssk-bubble-text", r.innerHTML = f(t.text), n.appendChild(r), t.sources?.length) {
141
+ let e = document.createElement("div");
142
+ e.className = "ssk-sources";
143
+ for (let n of t.sources) {
144
+ let t = document.createElement("a");
145
+ t.className = "ssk-source-pill", t.href = n.url, t.textContent = n.title || n.url, e.appendChild(t);
146
+ }
147
+ n.appendChild(e);
148
+ }
149
+ for (let e of t.actions || []) n.appendChild(p(e));
150
+ e.appendChild(n), e.scrollTop = e.scrollHeight;
106
151
  }
107
- function ee(e) {
152
+ function h(e) {
108
153
  try {
109
- return JSON.parse(localStorage.getItem(t(e)) || "[]");
154
+ return JSON.parse(localStorage.getItem(n(e)) || "[]");
110
155
  } catch {
111
156
  return [];
112
157
  }
113
158
  }
114
- function h(e, n) {
159
+ function g(e, t) {
115
160
  try {
116
- localStorage.setItem(t(e), JSON.stringify(n.slice(0, 40)));
161
+ localStorage.setItem(n(e), JSON.stringify(t.slice(0, 40)));
117
162
  } catch {}
118
163
  }
119
- function g(e) {
164
+ function _(e) {
120
165
  try {
121
- return localStorage.getItem(n(e));
166
+ return localStorage.getItem(r(e));
122
167
  } catch {
123
168
  return null;
124
169
  }
125
170
  }
126
- function _(e, t) {
171
+ function v(e, t) {
127
172
  try {
128
- localStorage.setItem(n(e), t);
173
+ localStorage.setItem(r(e), t);
129
174
  } catch {}
130
175
  }
131
- function v() {
176
+ function y() {
132
177
  return `s_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
133
178
  }
134
- function y(e) {
179
+ function b(e) {
135
180
  let t = e.find((e) => e.role === "user");
136
181
  return t ? t.text.slice(0, 60) : "New conversation";
137
182
  }
138
- function te(e) {
183
+ function x(e) {
139
184
  let t = new Date(e), n = /* @__PURE__ */ new Date();
140
185
  return t.toDateString() === n.toDateString() ? t.toLocaleTimeString(void 0, {
141
186
  hour: "numeric",
@@ -145,17 +190,17 @@ function te(e) {
145
190
  day: "numeric"
146
191
  });
147
192
  }
148
- function b() {
149
- let t = document.body.cloneNode(!0);
150
- t.querySelectorAll(`script,style,nav,header,footer,svg,noscript,#${e}`).forEach((e) => e.remove());
151
- let n = (t.textContent || "").replace(/\s+/g, " ").trim();
193
+ function S() {
194
+ let e = document.body.cloneNode(!0);
195
+ e.querySelectorAll(`script,style,nav,header,footer,svg,noscript,#${t}`).forEach((e) => e.remove());
196
+ let n = (e.textContent || "").replace(/\s+/g, " ").trim();
152
197
  return {
153
198
  title: document.title,
154
199
  text: n
155
200
  };
156
201
  }
157
- async function x(e, t) {
158
- let { title: n, text: r } = b();
202
+ async function C(e, t) {
203
+ let { title: n, text: r } = S();
159
204
  r.length < 40 || await fetch(`${e}/ingest`, {
160
205
  method: "POST",
161
206
  headers: { "Content-Type": "application/json" },
@@ -169,10 +214,10 @@ async function x(e, t) {
169
214
  })
170
215
  }).catch(() => {});
171
216
  }
172
- async function S(e, t) {
217
+ async function ee(e, t) {
173
218
  let n = !1;
174
219
  try {
175
- n = !!sessionStorage.getItem(r(t)), sessionStorage.setItem(r(t), "1");
220
+ n = !!sessionStorage.getItem(i(t)), sessionStorage.setItem(i(t), "1");
176
221
  } catch {}
177
222
  n || await fetch(`${e}/crawlSite`, {
178
223
  method: "POST",
@@ -180,51 +225,51 @@ async function S(e, t) {
180
225
  body: JSON.stringify({ embedToken: t })
181
226
  }).catch(() => {});
182
227
  }
183
- async function C(t) {
184
- if (typeof document > "u" || document.getElementById(e)) return;
185
- let n = (t.token || "").trim();
186
- if (!n) {
228
+ async function w(n) {
229
+ if (typeof document > "u" || document.getElementById(t)) return;
230
+ let r = (n.token || "").trim();
231
+ if (!r) {
187
232
  console.error("[Sendystack] init() called without a token");
188
233
  return;
189
234
  }
190
- let r = t.apiBase || "https://us-central1-sendystack-fab32.cloudfunctions.net", p;
235
+ let i = n.apiBase || "https://us-central1-sendystack-fab32.cloudfunctions.net", f;
191
236
  try {
192
- p = await d(r, n);
237
+ f = await u(i, r);
193
238
  } catch (e) {
194
239
  console.error("[Sendystack] failed to load widget config:", e);
195
240
  return;
196
241
  }
197
- f(p);
198
- let b = document.createElement("div");
199
- b.id = e;
200
- let C = ee(n), w = C.length === 0, T = g(n), E = C.find((e) => e.id === T);
242
+ d(f);
243
+ let p = document.createElement("div");
244
+ p.id = t;
245
+ let S = h(r), w = S.length === 0, T = _(r), E = S.find((e) => e.id === T);
201
246
  E || (E = {
202
- id: v(),
247
+ id: y(),
203
248
  title: "New conversation",
204
249
  messages: [],
205
250
  updatedAt: Date.now()
206
- }, C.unshift(E), T = E.id, _(n, T), h(n, C));
251
+ }, S.unshift(E), T = E.id, v(r, T), g(r, S));
207
252
  let D = document.createElement("div");
208
253
  D.className = "ssk-body";
209
254
  function O() {
210
255
  D.innerHTML = "";
211
256
  let e = E.messages.length ? E.messages : [{
212
257
  role: "bot",
213
- text: p.welcomeMessage
258
+ text: f.welcomeMessage
214
259
  }];
215
260
  for (let t of e) m(D, t);
216
261
  }
217
262
  O();
218
263
  let k = document.createElement("span");
219
- k.className = "ssk-avatar", k.innerHTML = s;
264
+ k.className = "ssk-avatar", k.innerHTML = `<img src="${e}" alt="" />`;
220
265
  let A = document.createElement("div");
221
- A.className = "ssk-id-text", A.innerHTML = `<strong>${p.assistantName}</strong><span class="ssk-status"><span class="ssk-dot"></span>Online</span>`;
266
+ A.className = "ssk-id-text", A.innerHTML = `<strong>${f.assistantName}</strong><span class="ssk-status"><span class="ssk-dot"></span>Online</span>`;
222
267
  let j = document.createElement("div");
223
268
  j.className = "ssk-id", j.append(k, A);
224
269
  let M = document.createElement("button");
225
- M.className = "ssk-iconbtn", M.innerHTML = c, M.setAttribute("aria-label", "Chat history");
270
+ M.className = "ssk-iconbtn", M.innerHTML = s, M.setAttribute("aria-label", "Chat history");
226
271
  let N = document.createElement("button");
227
- N.className = "ssk-iconbtn", N.innerHTML = l, N.setAttribute("aria-label", "New chat");
272
+ N.className = "ssk-iconbtn", N.innerHTML = c, N.setAttribute("aria-label", "New chat");
228
273
  let P = document.createElement("button");
229
274
  P.className = "ssk-closebtn", P.innerHTML = a, P.setAttribute("aria-label", "Close chat");
230
275
  let F = document.createElement("div");
@@ -232,7 +277,7 @@ async function C(t) {
232
277
  let I = document.createElement("div");
233
278
  I.className = "ssk-head", I.append(j, F);
234
279
  let L = document.createElement("button");
235
- L.className = "ssk-history-back", L.innerHTML = u, L.setAttribute("aria-label", "Back to chat");
280
+ L.className = "ssk-history-back", L.innerHTML = l, L.setAttribute("aria-label", "Back to chat");
236
281
  let R = document.createElement("h4");
237
282
  R.textContent = "Chat history";
238
283
  let z = document.createElement("div");
@@ -242,15 +287,15 @@ async function C(t) {
242
287
  let V = document.createElement("div");
243
288
  V.className = "ssk-history", V.append(z, B);
244
289
  function H() {
245
- if (B.innerHTML = "", !C.length) {
290
+ if (B.innerHTML = "", !S.length) {
246
291
  let e = document.createElement("div");
247
292
  e.className = "ssk-history-empty", e.textContent = "No past conversations yet.", B.appendChild(e);
248
293
  return;
249
294
  }
250
- for (let e of C) {
295
+ for (let e of S) {
251
296
  let t = document.createElement("button");
252
- t.className = `ssk-history-item${e.id === T ? " active" : ""}`, t.innerHTML = `<span class="ssk-history-title">${e.title}</span><span class="ssk-history-time">${te(e.updatedAt)}</span>`, t.onclick = () => {
253
- E = e, T = e.id, _(n, T), O(), H(), V.classList.remove("shown");
297
+ t.className = `ssk-history-item${e.id === T ? " active" : ""}`, t.innerHTML = `<span class="ssk-history-title">${e.title}</span><span class="ssk-history-time">${x(e.updatedAt)}</span>`, t.onclick = () => {
298
+ E = e, T = e.id, v(r, T), O(), H(), V.classList.remove("shown");
254
299
  }, B.appendChild(t);
255
300
  }
256
301
  }
@@ -258,11 +303,11 @@ async function C(t) {
258
303
  H(), V.classList.add("shown");
259
304
  }, L.onclick = () => V.classList.remove("shown"), N.onclick = () => {
260
305
  E.messages.length && (E = {
261
- id: v(),
306
+ id: y(),
262
307
  title: "New conversation",
263
308
  messages: [],
264
309
  updatedAt: Date.now()
265
- }, C.unshift(E), T = E.id, _(n, T), h(n, C), O(), V.classList.remove("shown"));
310
+ }, S.unshift(E), T = E.id, v(r, T), g(r, S), O(), V.classList.remove("shown"));
266
311
  };
267
312
  let U = document.createElement("div");
268
313
  U.className = "ssk-typing", U.hidden = !0, U.innerHTML = "<span class=\"ssk-dots\"><i></i><i></i><i></i></span>";
@@ -273,22 +318,22 @@ async function C(t) {
273
318
  let K = document.createElement("div");
274
319
  K.className = "ssk-inputrow", K.append(W, G);
275
320
  let q = document.createElement("div");
276
- q.className = "ssk-foot", q.textContent = p.whiteLabel ? "AI can make mistakes — confirm important details." : "AI can make mistakes — confirm important details. · Powered by Sendystack";
321
+ q.className = "ssk-foot", q.textContent = f.whiteLabel ? "AI can make mistakes — confirm important details." : "AI can make mistakes — confirm important details. · Powered by Sendystack";
277
322
  let J = document.createElement("div");
278
323
  J.className = "ssk-panel", J.append(I, D, U, K, q, V);
279
324
  let Y = document.createElement("button");
280
- Y.className = "ssk-launcher", Y.setAttribute("aria-label", "Open chat"), Y.innerHTML = `<span class="ssk-open">${i}</span><span class="ssk-x" style="display:none">${a}</span><span class="ssk-pulse" aria-hidden="true"></span>`;
325
+ Y.className = "ssk-launcher", Y.setAttribute("aria-label", "Open chat"), Y.innerHTML = `<span class="ssk-open"><img src="${e}" alt="" /></span><span class="ssk-x" style="display:none">${a}</span><span class="ssk-pulse" aria-hidden="true"></span>`;
281
326
  let X = document.createElement("div");
282
- X.className = "ssk-greeting", X.textContent = p.welcomeMessage, b.append(J, X, Y), document.body.appendChild(b), Y.onclick = () => b.classList.toggle("open"), P.onclick = () => b.classList.remove("open"), w && setTimeout(() => {
283
- b.classList.contains("open") || X.classList.add("shown"), setTimeout(() => X.classList.remove("shown"), 6e3);
327
+ X.className = "ssk-greeting", X.textContent = f.welcomeMessage, p.append(J, X, Y), document.body.appendChild(p), Y.onclick = () => p.classList.toggle("open"), P.onclick = () => p.classList.remove("open"), w && setTimeout(() => {
328
+ p.classList.contains("open") || X.classList.add("shown"), setTimeout(() => X.classList.remove("shown"), 6e3);
284
329
  }, 2500);
285
330
  function Z(e) {
286
331
  U.hidden = !e, e && (D.scrollTop = D.scrollHeight);
287
332
  }
288
333
  function Q() {
289
- E.updatedAt = Date.now(), E.title = y(E.messages);
290
- let e = C.findIndex((e) => e.id === E.id);
291
- e >= 0 && C.splice(e, 1), C.unshift(E), h(n, C);
334
+ E.updatedAt = Date.now(), E.title = b(E.messages);
335
+ let e = S.findIndex((e) => e.id === E.id);
336
+ e >= 0 && S.splice(e, 1), S.unshift(E), g(r, S);
292
337
  }
293
338
  async function $() {
294
339
  let e = W.value.trim();
@@ -300,18 +345,20 @@ async function C(t) {
300
345
  };
301
346
  E.messages.push(t), m(D, t), Q(), G.disabled = !0, Z(!0);
302
347
  try {
303
- let t = await (await fetch(`${r}/answer`, {
348
+ let t = await (await fetch(`${i}/answer`, {
304
349
  method: "POST",
305
350
  headers: { "Content-Type": "application/json" },
306
351
  body: JSON.stringify({
307
- embedToken: n,
352
+ embedToken: r,
308
353
  query: e
309
354
  })
310
- })).json(), i = {
355
+ })).json(), n = {
311
356
  role: "bot",
312
- text: t.ok ? t.reply : t.message || "Sorry, I couldn't process that right now."
357
+ text: t.ok ? t.reply : t.message || "Sorry, I couldn't process that right now.",
358
+ sources: Array.isArray(t.sources) && t.sources.length ? t.sources : void 0,
359
+ actions: Array.isArray(t.actions) && t.actions.length ? t.actions : void 0
313
360
  };
314
- E.messages.push(i), m(D, i);
361
+ E.messages.push(n), m(D, n);
315
362
  } catch {
316
363
  let e = {
317
364
  role: "bot",
@@ -324,15 +371,15 @@ async function C(t) {
324
371
  }
325
372
  G.onclick = $, W.addEventListener("keydown", (e) => {
326
373
  e.key === "Enter" && !e.shiftKey && (e.preventDefault(), $());
327
- }), x(r, n), S(r, n);
374
+ }), C(i, r), ee(i, r);
328
375
  }
329
- function w() {
376
+ function T() {
330
377
  let e = document.currentScript || Array.from(document.getElementsByTagName("script")).find((e) => /widget(\.global)?\.js/.test(e.src)), t = e?.dataset.token;
331
- t && C({
378
+ t && w({
332
379
  token: t,
333
380
  apiBase: e?.dataset.apiBase
334
381
  });
335
382
  }
336
- typeof document < "u" && w();
383
+ typeof document < "u" && T();
337
384
  //#endregion
338
- export { C as init };
385
+ export { w as init };
@@ -1,32 +1,34 @@
1
- var Sendystack=(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var t=`https://us-central1-sendystack-fab32.cloudfunctions.net`,n=`sendystack-widget-root`,r=40,i=e=>`sendystack_sessions_${e}`,a=e=>`sendystack_current_${e}`,o=e=>`sendystack_crawled_${e}`,ee=`<svg viewBox="0 0 24 24" width="26" height="26" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M4 5h13a2 2 0 0 1 2 2v7a2 2 0 0 1-2 2H9l-5 4v-4a2 2 0 0 1-2-2V7a2 2 0 0 1 2-2z"/></svg>`,s=`<svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M6 6l12 12M18 6L6 18"/></svg>`,c=`<svg viewBox="0 0 24 24" width="19" height="19" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M4 12l16-7-7 16-2-7-7-2z"/></svg>`,l=`<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3l2.5 5 5 .7-3.7 3.5.9 5L12 19.8 7.3 17.2l.9-5L4.5 8.7l5-.7z"/></svg>`,u=`<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 3"/></svg>`,d=`<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14M5 12h14"/></svg>`,f=`<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M15 18l-6-6 6-6"/></svg>`;async function p(e,t){let n=await(await fetch(`${e}/embedConfig?embedToken=${encodeURIComponent(t)}`)).json();if(!n.ok)throw Error(n.error||`failed to load widget config`);return{...n.appearance,whiteLabel:!!n.whiteLabel}}function m(e){let t=e.position===`left`?`left`:`right`,r=document.createElement(`style`);r.textContent=`
2
- #${n} *{box-sizing:border-box;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;}
3
- #${n}{position:fixed;${t}:20px;bottom:20px;z-index:2147483000;}
4
- .ssk-launcher{position:relative;width:60px;height:60px;border-radius:50%;border:none;cursor:pointer;color:#fff;
5
- background:linear-gradient(140deg,${e.accent2},${e.accent});box-shadow:0 16px 36px rgba(0,0,0,.28);
1
+ var Sendystack=(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var t=`https://us-central1-sendystack-fab32.cloudfunctions.net`,n=`https://app.sendystack.org/sendy_stack_favico.png`,r=`sendystack-widget-root`,i=40,a=e=>`sendystack_sessions_${e}`,o=e=>`sendystack_current_${e}`,s=e=>`sendystack_crawled_${e}`,c=`<svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M6 6l12 12M18 6L6 18"/></svg>`,l=`<svg viewBox="0 0 24 24" width="19" height="19" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M4 12l16-7-7 16-2-7-7-2z"/></svg>`,u=`<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 3"/></svg>`,d=`<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14M5 12h14"/></svg>`,f=`<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M15 18l-6-6 6-6"/></svg>`;async function p(e,t){let n=await(await fetch(`${e}/embedConfig?embedToken=${encodeURIComponent(t)}`)).json();if(!n.ok)throw Error(n.error||`failed to load widget config`);return{...n.appearance,whiteLabel:!!n.whiteLabel}}function m(e){let t=e.position===`left`?`left`:`right`,n=document.createElement(`style`);n.textContent=`
2
+ #${r} *{box-sizing:border-box;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;}
3
+ #${r}{position:fixed;${t}:20px;bottom:20px;z-index:2147483000;}
4
+ .ssk-launcher{position:relative;width:60px;height:60px;border-radius:50%;border:none;cursor:pointer;color:${e.accent};
5
+ background:#fff;box-shadow:0 16px 36px rgba(0,0,0,.22),0 0 0 1px rgba(27,39,51,.05);
6
6
  display:flex;align-items:center;justify-content:center;transition:transform .25s cubic-bezier(.16,1,.3,1);margin-left:auto;}
7
7
  .ssk-launcher:hover{transform:translateY(-3px) scale(1.04);}
8
8
  .ssk-launcher:active{transform:scale(.96);}
9
9
  .ssk-launcher .ssk-open{display:flex;} .ssk-launcher .ssk-x{display:none;}
10
- #${n}.open .ssk-launcher .ssk-open{display:none;} #${n}.open .ssk-launcher .ssk-x{display:flex;}
10
+ .ssk-launcher .ssk-open img{width:32px;height:32px;object-fit:contain;}
11
+ #${r}.open .ssk-launcher .ssk-open{display:none;} #${r}.open .ssk-launcher .ssk-x{display:flex;}
11
12
  .ssk-pulse{position:absolute;inset:0;border-radius:50%;background:${e.accent};opacity:.5;z-index:-1;animation:sskPulse 2.4s ease-out infinite;}
12
13
  @keyframes sskPulse{0%{transform:scale(1);opacity:.5;}70%,100%{transform:scale(1.7);opacity:0;}}
13
14
  @media(prefers-reduced-motion:reduce){.ssk-pulse{animation:none;}}
14
- .ssk-greeting{position:absolute;bottom:72px;${t}:0;max-width:220px;padding:11px 14px;border-radius:14px;background:#fff;
15
+ .ssk-greeting{position:absolute;bottom:72px;${t}:0;width:max-content;max-width:min(300px,calc(100vw - 48px));padding:11px 16px;border-radius:14px;background:#fff;
15
16
  color:#1b2733;font-size:13.5px;line-height:1.4;font-weight:600;box-shadow:0 16px 38px rgba(27,39,51,.18);
16
17
  transform:translateY(8px);opacity:0;pointer-events:none;transition:opacity .3s,transform .3s;}
17
18
  .ssk-greeting.shown{opacity:1;transform:translateY(0);}
18
- #${n}.open .ssk-greeting{display:none;}
19
+ #${r}.open .ssk-greeting{display:none;}
19
20
  .ssk-panel{position:absolute;bottom:74px;${t}:0;width:368px;max-width:calc(100vw - 32px);height:560px;
20
21
  max-height:calc(100vh - 110px);background:#fff;border-radius:20px;overflow:hidden;display:flex;flex-direction:column;
21
22
  box-shadow:0 36px 80px rgba(20,20,30,.3);border:1px solid rgba(27,39,51,.08);
22
23
  transform:translateY(14px) scale(.96);opacity:0;pointer-events:none;transform-origin:bottom ${t};
23
24
  transition:transform .25s cubic-bezier(.16,1,.3,1),opacity .2s;}
24
- #${n}.open .ssk-panel{transform:translateY(0) scale(1);opacity:1;pointer-events:auto;}
25
+ #${r}.open .ssk-panel{transform:translateY(0) scale(1);opacity:1;pointer-events:auto;}
25
26
  .ssk-head{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:13px 14px;flex-shrink:0;
26
27
  background:linear-gradient(135deg,${e.accent2},${e.accent});color:#fff;}
27
28
  .ssk-id{display:flex;align-items:center;gap:9px;min-width:0;}
28
29
  .ssk-avatar{width:36px;height:36px;border-radius:50%;display:flex;align-items:center;justify-content:center;
29
- background:rgba(255,255,255,.22);flex:0 0 auto;}
30
+ background:#fff;flex:0 0 auto;}
31
+ .ssk-avatar img{width:22px;height:22px;object-fit:contain;}
30
32
  .ssk-id-text{min-width:0;}
31
33
  .ssk-id-text strong{display:block;font-size:14px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
32
34
  .ssk-status{display:flex;align-items:center;gap:5px;font-size:11px;opacity:.9;}
@@ -53,8 +55,8 @@ var Sendystack=(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:`M
53
55
  .ssk-history-item.active{background:rgba(226,112,15,.1);}
54
56
  .ssk-history-title{display:block;font-size:13.5px;font-weight:600;color:#1b2733;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
55
57
  .ssk-history-time{display:block;font-size:11px;color:#9aa3ad;margin-top:2px;}
56
- .ssk-msg{display:flex;max-width:86%;}
57
- .ssk-msg.bot{align-self:flex-start;} .ssk-msg.user{align-self:flex-end;}
58
+ .ssk-msg{display:flex;flex-direction:column;align-items:flex-start;max-width:86%;}
59
+ .ssk-msg.bot{align-self:flex-start;} .ssk-msg.user{align-self:flex-end;align-items:flex-end;}
58
60
  .ssk-bubble-text{padding:10px 13px;border-radius:15px;font-size:14px;line-height:1.5;word-wrap:break-word;}
59
61
  .ssk-msg.bot .ssk-bubble-text{background:#fff;border:1px solid rgba(27,39,51,.08);border-bottom-left-radius:5px;
60
62
  box-shadow:0 5px 14px rgba(27,39,51,.05);color:#1b2733;}
@@ -62,6 +64,18 @@ var Sendystack=(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:`M
62
64
  .ssk-bubble-text a{color:${e.accent};font-weight:700;text-decoration:underline;}
63
65
  .ssk-msg.user .ssk-bubble-text a{color:#fff;}
64
66
  .ssk-bubble-text p{margin:0 0 7px;} .ssk-bubble-text p:last-child{margin:0;}
67
+ .ssk-sources{display:flex;flex-wrap:wrap;gap:6px;margin-top:7px;}
68
+ .ssk-source-pill{display:inline-flex;align-items:center;padding:5px 11px;border-radius:999px;
69
+ background:rgba(27,39,51,.05);color:${e.accent};font-size:12px;font-weight:600;
70
+ text-decoration:none;border:1px solid rgba(27,39,51,.08);}
71
+ .ssk-source-pill:hover{background:rgba(27,39,51,.08);}
72
+ .ssk-action-btn{margin-top:7px;border:none;border-radius:12px;padding:9px 15px;font-size:13px;font-weight:700;
73
+ cursor:pointer;color:#fff;background:linear-gradient(135deg,${e.accent2},${e.accent});
74
+ box-shadow:0 6px 16px rgba(0,0,0,.15);transition:transform .15s;}
75
+ .ssk-action-btn:hover{transform:translateY(-1px);}
76
+ .ssk-action-btn:disabled{cursor:default;transform:none;}
77
+ .ssk-action-btn.ssk-action-done{opacity:.75;}
78
+ .ssk-action-btn.ssk-action-missing{background:rgba(27,39,51,.08);color:#6b7280;box-shadow:none;}
65
79
  .ssk-typing{padding:0 14px 10px;flex-shrink:0;}
66
80
  .ssk-typing[hidden]{display:none;}
67
81
  .ssk-dots{display:inline-flex;gap:5px;padding:10px 13px;background:#fff;border:1px solid rgba(27,39,51,.08);
@@ -81,4 +95,4 @@ var Sendystack=(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:`M
81
95
  .ssk-sendbtn:disabled{opacity:.6;cursor:default;transform:none;}
82
96
  .ssk-foot{text-align:center;font-size:10.5px;color:#9aa3ad;padding:0 8px 9px;background:#fff;flex-shrink:0;}
83
97
  .ssk-foot a{color:inherit;text-decoration:underline;}
84
- `,document.head.appendChild(r)}function h(e){return`<p>${e.replace(/&/g,`&amp;`).replace(/</g,`&lt;`).replace(/>/g,`&gt;`).replace(/\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/g,`<a href="$2" target="_blank" rel="noopener">$1</a>`).replace(/\*\*([^*]+)\*\*/g,`<strong>$1</strong>`).replace(/\n{2,}/g,`</p><p>`).replace(/\n/g,`<br>`)}</p>`}function g(e,t){let n=document.createElement(`div`);n.className=`ssk-msg ${t.role}`;let r=document.createElement(`div`);r.className=`ssk-bubble-text`,r.innerHTML=h(t.text),n.appendChild(r),e.appendChild(n),e.scrollTop=e.scrollHeight}function te(e){try{return JSON.parse(localStorage.getItem(i(e))||`[]`)}catch{return[]}}function _(e,t){try{localStorage.setItem(i(e),JSON.stringify(t.slice(0,r)))}catch{}}function v(e){try{return localStorage.getItem(a(e))}catch{return null}}function y(e,t){try{localStorage.setItem(a(e),t)}catch{}}function b(){return`s_${Date.now()}_${Math.random().toString(36).slice(2,8)}`}function x(e){let t=e.find(e=>e.role===`user`);return t?t.text.slice(0,60):`New conversation`}function S(e){let t=new Date(e),n=new Date;return t.toDateString()===n.toDateString()?t.toLocaleTimeString(void 0,{hour:`numeric`,minute:`2-digit`}):t.toLocaleDateString(void 0,{month:`short`,day:`numeric`})}function C(){let e=document.body.cloneNode(!0);e.querySelectorAll(`script,style,nav,header,footer,svg,noscript,#${n}`).forEach(e=>e.remove());let t=(e.textContent||``).replace(/\s+/g,` `).trim();return{title:document.title,text:t}}async function ne(e,t){let{title:n,text:r}=C();r.length<40||await fetch(`${e}/ingest`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({embedToken:t,items:[{url:location.href,title:n,text:r}]})}).catch(()=>{})}async function w(e,t){let n=!1;try{n=!!sessionStorage.getItem(o(t)),sessionStorage.setItem(o(t),`1`)}catch{}n||await fetch(`${e}/crawlSite`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({embedToken:t})}).catch(()=>{})}async function T(e){if(typeof document>`u`||document.getElementById(n))return;let r=(e.token||``).trim();if(!r){console.error(`[Sendystack] init() called without a token`);return}let i=e.apiBase||t,a;try{a=await p(i,r)}catch(e){console.error(`[Sendystack] failed to load widget config:`,e);return}m(a);let o=document.createElement(`div`);o.id=n;let h=te(r),C=h.length===0,T=v(r),E=h.find(e=>e.id===T);E||(E={id:b(),title:`New conversation`,messages:[],updatedAt:Date.now()},h.unshift(E),T=E.id,y(r,T),_(r,h));let D=document.createElement(`div`);D.className=`ssk-body`;function O(){D.innerHTML=``;let e=E.messages.length?E.messages:[{role:`bot`,text:a.welcomeMessage}];for(let t of e)g(D,t)}O();let k=document.createElement(`span`);k.className=`ssk-avatar`,k.innerHTML=l;let A=document.createElement(`div`);A.className=`ssk-id-text`,A.innerHTML=`<strong>${a.assistantName}</strong><span class="ssk-status"><span class="ssk-dot"></span>Online</span>`;let j=document.createElement(`div`);j.className=`ssk-id`,j.append(k,A);let M=document.createElement(`button`);M.className=`ssk-iconbtn`,M.innerHTML=u,M.setAttribute(`aria-label`,`Chat history`);let N=document.createElement(`button`);N.className=`ssk-iconbtn`,N.innerHTML=d,N.setAttribute(`aria-label`,`New chat`);let P=document.createElement(`button`);P.className=`ssk-closebtn`,P.innerHTML=s,P.setAttribute(`aria-label`,`Close chat`);let F=document.createElement(`div`);F.className=`ssk-headbtns`,F.append(M,N,P);let I=document.createElement(`div`);I.className=`ssk-head`,I.append(j,F);let L=document.createElement(`button`);L.className=`ssk-history-back`,L.innerHTML=f,L.setAttribute(`aria-label`,`Back to chat`);let R=document.createElement(`h4`);R.textContent=`Chat history`;let z=document.createElement(`div`);z.className=`ssk-history-head`,z.append(L,R);let B=document.createElement(`div`);B.className=`ssk-history-list`;let V=document.createElement(`div`);V.className=`ssk-history`,V.append(z,B);function H(){if(B.innerHTML=``,!h.length){let e=document.createElement(`div`);e.className=`ssk-history-empty`,e.textContent=`No past conversations yet.`,B.appendChild(e);return}for(let e of h){let t=document.createElement(`button`);t.className=`ssk-history-item${e.id===T?` active`:``}`,t.innerHTML=`<span class="ssk-history-title">${e.title}</span><span class="ssk-history-time">${S(e.updatedAt)}</span>`,t.onclick=()=>{E=e,T=e.id,y(r,T),O(),H(),V.classList.remove(`shown`)},B.appendChild(t)}}M.onclick=()=>{H(),V.classList.add(`shown`)},L.onclick=()=>V.classList.remove(`shown`),N.onclick=()=>{E.messages.length&&(E={id:b(),title:`New conversation`,messages:[],updatedAt:Date.now()},h.unshift(E),T=E.id,y(r,T),_(r,h),O(),V.classList.remove(`shown`))};let U=document.createElement(`div`);U.className=`ssk-typing`,U.hidden=!0,U.innerHTML=`<span class="ssk-dots"><i></i><i></i><i></i></span>`;let W=document.createElement(`textarea`);W.rows=1,W.maxLength=4e3,W.placeholder=`Ask me anything…`;let G=document.createElement(`button`);G.className=`ssk-sendbtn`,G.innerHTML=c,G.setAttribute(`aria-label`,`Send`);let K=document.createElement(`div`);K.className=`ssk-inputrow`,K.append(W,G);let q=document.createElement(`div`);q.className=`ssk-foot`,q.textContent=a.whiteLabel?`AI can make mistakes — confirm important details.`:`AI can make mistakes — confirm important details. · Powered by Sendystack`;let J=document.createElement(`div`);J.className=`ssk-panel`,J.append(I,D,U,K,q,V);let Y=document.createElement(`button`);Y.className=`ssk-launcher`,Y.setAttribute(`aria-label`,`Open chat`),Y.innerHTML=`<span class="ssk-open">${ee}</span><span class="ssk-x" style="display:none">${s}</span><span class="ssk-pulse" aria-hidden="true"></span>`;let X=document.createElement(`div`);X.className=`ssk-greeting`,X.textContent=a.welcomeMessage,o.append(J,X,Y),document.body.appendChild(o),Y.onclick=()=>o.classList.toggle(`open`),P.onclick=()=>o.classList.remove(`open`),C&&setTimeout(()=>{o.classList.contains(`open`)||X.classList.add(`shown`),setTimeout(()=>X.classList.remove(`shown`),6e3)},2500);function Z(e){U.hidden=!e,e&&(D.scrollTop=D.scrollHeight)}function Q(){E.updatedAt=Date.now(),E.title=x(E.messages);let e=h.findIndex(e=>e.id===E.id);e>=0&&h.splice(e,1),h.unshift(E),_(r,h)}async function $(){let e=W.value.trim();if(!e)return;W.value=``;let t={role:`user`,text:e};E.messages.push(t),g(D,t),Q(),G.disabled=!0,Z(!0);try{let t=await(await fetch(`${i}/answer`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({embedToken:r,query:e})})).json(),n={role:`bot`,text:t.ok?t.reply:t.message||`Sorry, I couldn't process that right now.`};E.messages.push(n),g(D,n)}catch{let e={role:`bot`,text:`I couldn't reach the server. Please try again.`};E.messages.push(e),g(D,e)}finally{Z(!1),G.disabled=!1,Q()}}G.onclick=$,W.addEventListener(`keydown`,e=>{e.key===`Enter`&&!e.shiftKey&&(e.preventDefault(),$())}),ne(i,r),w(i,r)}function E(){let e=document.currentScript||Array.from(document.getElementsByTagName(`script`)).find(e=>/widget(\.global)?\.js/.test(e.src)),t=e?.dataset.token;t&&T({token:t,apiBase:e?.dataset.apiBase})}return typeof document<`u`&&E(),e.init=T,e})({});
98
+ `,document.head.appendChild(n)}function h(e){return`<p>${e.replace(/&/g,`&amp;`).replace(/</g,`&lt;`).replace(/>/g,`&gt;`).replace(/\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/g,`<a href="$2" target="_blank" rel="noopener">$1</a>`).replace(/\*\*([^*]+)\*\*/g,`<strong>$1</strong>`).replace(/\n{2,}/g,`</p><p>`).replace(/\n/g,`<br>`)}</p>`}function g(e){let t=document.createElement(`button`);t.type=`button`,t.className=`ssk-action-btn`,t.textContent=e.label;let n=!e.confirmRequired;return t.onclick=()=>{let r=e.selector?document.querySelector(e.selector):null;if(!r){if(e.fallbackUrl){location.href=e.fallbackUrl;return}t.textContent=`Couldn't find that on this page`,t.classList.add(`ssk-action-missing`),t.disabled=!0;return}if(!n){n=!0,t.textContent=`Click to confirm: ${e.label}`;return}r.click(),t.textContent=`Done ✓`,t.disabled=!0,t.classList.add(`ssk-action-done`)},t}function _(e,t){let n=document.createElement(`div`);n.className=`ssk-msg ${t.role}`;let r=document.createElement(`div`);if(r.className=`ssk-bubble-text`,r.innerHTML=h(t.text),n.appendChild(r),t.sources?.length){let e=document.createElement(`div`);e.className=`ssk-sources`;for(let n of t.sources){let t=document.createElement(`a`);t.className=`ssk-source-pill`,t.href=n.url,t.textContent=n.title||n.url,e.appendChild(t)}n.appendChild(e)}for(let e of t.actions||[])n.appendChild(g(e));e.appendChild(n),e.scrollTop=e.scrollHeight}function v(e){try{return JSON.parse(localStorage.getItem(a(e))||`[]`)}catch{return[]}}function y(e,t){try{localStorage.setItem(a(e),JSON.stringify(t.slice(0,i)))}catch{}}function b(e){try{return localStorage.getItem(o(e))}catch{return null}}function x(e,t){try{localStorage.setItem(o(e),t)}catch{}}function S(){return`s_${Date.now()}_${Math.random().toString(36).slice(2,8)}`}function ee(e){let t=e.find(e=>e.role===`user`);return t?t.text.slice(0,60):`New conversation`}function te(e){let t=new Date(e),n=new Date;return t.toDateString()===n.toDateString()?t.toLocaleTimeString(void 0,{hour:`numeric`,minute:`2-digit`}):t.toLocaleDateString(void 0,{month:`short`,day:`numeric`})}function C(){let e=document.body.cloneNode(!0);e.querySelectorAll(`script,style,nav,header,footer,svg,noscript,#${r}`).forEach(e=>e.remove());let t=(e.textContent||``).replace(/\s+/g,` `).trim();return{title:document.title,text:t}}async function w(e,t){let{title:n,text:r}=C();r.length<40||await fetch(`${e}/ingest`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({embedToken:t,items:[{url:location.href,title:n,text:r}]})}).catch(()=>{})}async function T(e,t){let n=!1;try{n=!!sessionStorage.getItem(s(t)),sessionStorage.setItem(s(t),`1`)}catch{}n||await fetch(`${e}/crawlSite`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({embedToken:t})}).catch(()=>{})}async function E(e){if(typeof document>`u`||document.getElementById(r))return;let i=(e.token||``).trim();if(!i){console.error(`[Sendystack] init() called without a token`);return}let a=e.apiBase||t,o;try{o=await p(a,i)}catch(e){console.error(`[Sendystack] failed to load widget config:`,e);return}m(o);let s=document.createElement(`div`);s.id=r;let h=v(i),g=h.length===0,C=b(i),E=h.find(e=>e.id===C);E||(E={id:S(),title:`New conversation`,messages:[],updatedAt:Date.now()},h.unshift(E),C=E.id,x(i,C),y(i,h));let D=document.createElement(`div`);D.className=`ssk-body`;function O(){D.innerHTML=``;let e=E.messages.length?E.messages:[{role:`bot`,text:o.welcomeMessage}];for(let t of e)_(D,t)}O();let k=document.createElement(`span`);k.className=`ssk-avatar`,k.innerHTML=`<img src="${n}" alt="" />`;let A=document.createElement(`div`);A.className=`ssk-id-text`,A.innerHTML=`<strong>${o.assistantName}</strong><span class="ssk-status"><span class="ssk-dot"></span>Online</span>`;let j=document.createElement(`div`);j.className=`ssk-id`,j.append(k,A);let M=document.createElement(`button`);M.className=`ssk-iconbtn`,M.innerHTML=u,M.setAttribute(`aria-label`,`Chat history`);let N=document.createElement(`button`);N.className=`ssk-iconbtn`,N.innerHTML=d,N.setAttribute(`aria-label`,`New chat`);let P=document.createElement(`button`);P.className=`ssk-closebtn`,P.innerHTML=c,P.setAttribute(`aria-label`,`Close chat`);let F=document.createElement(`div`);F.className=`ssk-headbtns`,F.append(M,N,P);let I=document.createElement(`div`);I.className=`ssk-head`,I.append(j,F);let L=document.createElement(`button`);L.className=`ssk-history-back`,L.innerHTML=f,L.setAttribute(`aria-label`,`Back to chat`);let R=document.createElement(`h4`);R.textContent=`Chat history`;let z=document.createElement(`div`);z.className=`ssk-history-head`,z.append(L,R);let B=document.createElement(`div`);B.className=`ssk-history-list`;let V=document.createElement(`div`);V.className=`ssk-history`,V.append(z,B);function H(){if(B.innerHTML=``,!h.length){let e=document.createElement(`div`);e.className=`ssk-history-empty`,e.textContent=`No past conversations yet.`,B.appendChild(e);return}for(let e of h){let t=document.createElement(`button`);t.className=`ssk-history-item${e.id===C?` active`:``}`,t.innerHTML=`<span class="ssk-history-title">${e.title}</span><span class="ssk-history-time">${te(e.updatedAt)}</span>`,t.onclick=()=>{E=e,C=e.id,x(i,C),O(),H(),V.classList.remove(`shown`)},B.appendChild(t)}}M.onclick=()=>{H(),V.classList.add(`shown`)},L.onclick=()=>V.classList.remove(`shown`),N.onclick=()=>{E.messages.length&&(E={id:S(),title:`New conversation`,messages:[],updatedAt:Date.now()},h.unshift(E),C=E.id,x(i,C),y(i,h),O(),V.classList.remove(`shown`))};let U=document.createElement(`div`);U.className=`ssk-typing`,U.hidden=!0,U.innerHTML=`<span class="ssk-dots"><i></i><i></i><i></i></span>`;let W=document.createElement(`textarea`);W.rows=1,W.maxLength=4e3,W.placeholder=`Ask me anything…`;let G=document.createElement(`button`);G.className=`ssk-sendbtn`,G.innerHTML=l,G.setAttribute(`aria-label`,`Send`);let K=document.createElement(`div`);K.className=`ssk-inputrow`,K.append(W,G);let q=document.createElement(`div`);q.className=`ssk-foot`,q.textContent=o.whiteLabel?`AI can make mistakes — confirm important details.`:`AI can make mistakes — confirm important details. · Powered by Sendystack`;let J=document.createElement(`div`);J.className=`ssk-panel`,J.append(I,D,U,K,q,V);let Y=document.createElement(`button`);Y.className=`ssk-launcher`,Y.setAttribute(`aria-label`,`Open chat`),Y.innerHTML=`<span class="ssk-open"><img src="${n}" alt="" /></span><span class="ssk-x" style="display:none">${c}</span><span class="ssk-pulse" aria-hidden="true"></span>`;let X=document.createElement(`div`);X.className=`ssk-greeting`,X.textContent=o.welcomeMessage,s.append(J,X,Y),document.body.appendChild(s),Y.onclick=()=>s.classList.toggle(`open`),P.onclick=()=>s.classList.remove(`open`),g&&setTimeout(()=>{s.classList.contains(`open`)||X.classList.add(`shown`),setTimeout(()=>X.classList.remove(`shown`),6e3)},2500);function Z(e){U.hidden=!e,e&&(D.scrollTop=D.scrollHeight)}function Q(){E.updatedAt=Date.now(),E.title=ee(E.messages);let e=h.findIndex(e=>e.id===E.id);e>=0&&h.splice(e,1),h.unshift(E),y(i,h)}async function $(){let e=W.value.trim();if(!e)return;W.value=``;let t={role:`user`,text:e};E.messages.push(t),_(D,t),Q(),G.disabled=!0,Z(!0);try{let t=await(await fetch(`${a}/answer`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({embedToken:i,query:e})})).json(),n={role:`bot`,text:t.ok?t.reply:t.message||`Sorry, I couldn't process that right now.`,sources:Array.isArray(t.sources)&&t.sources.length?t.sources:void 0,actions:Array.isArray(t.actions)&&t.actions.length?t.actions:void 0};E.messages.push(n),_(D,n)}catch{let e={role:`bot`,text:`I couldn't reach the server. Please try again.`};E.messages.push(e),_(D,e)}finally{Z(!1),G.disabled=!1,Q()}}G.onclick=$,W.addEventListener(`keydown`,e=>{e.key===`Enter`&&!e.shiftKey&&(e.preventDefault(),$())}),w(a,i),T(a,i)}function D(){let e=document.currentScript||Array.from(document.getElementsByTagName(`script`)).find(e=>/widget(\.global)?\.js/.test(e.src)),t=e?.dataset.token;t&&E({token:t,apiBase:e?.dataset.apiBase})}return typeof document<`u`&&D(),e.init=E,e})({});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sendystack/widget",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Universal AI chat widget for any website -- auto-injects the chatbot, crawls your site into the knowledge base, and feeds Claude/ChatGPT/Gemini via MCP. Appearance is managed at app.sendystack.org.",
5
5
  "type": "module",
6
6
  "license": "MIT",