@sendystack/widget 0.1.1 → 0.1.2

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_session_${e}`, n = (e) => `sendystack_crawled_${e}`, r = (e) => `sendystack_greeted_${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>";
3
- async function c(e, t) {
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) {
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,7 +8,7 @@ async function c(e, t) {
8
8
  whiteLabel: !!n.whiteLabel
9
9
  };
10
10
  }
11
- function l(t) {
11
+ function f(t) {
12
12
  let n = t.position === "left" ? "left" : "right", r = document.createElement("style");
13
13
  r.textContent = `
14
14
  #${e} *{box-sizing:border-box;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;}
@@ -43,11 +43,28 @@ function l(t) {
43
43
  .ssk-id-text strong{display:block;font-size:14px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
44
44
  .ssk-status{display:flex;align-items:center;gap:5px;font-size:11px;opacity:.9;}
45
45
  .ssk-dot{width:6px;height:6px;border-radius:50%;background:#46e08a;box-shadow:0 0 7px #46e08a;}
46
- .ssk-closebtn{flex:0 0 auto;width:30px;height:30px;border:none;border-radius:8px;background:transparent;color:#fff;
46
+ .ssk-headbtns{display:flex;align-items:center;gap:2px;flex:0 0 auto;}
47
+ .ssk-iconbtn,.ssk-closebtn{flex:0 0 auto;width:30px;height:30px;border:none;border-radius:8px;background:transparent;color:#fff;
47
48
  cursor:pointer;display:flex;align-items:center;justify-content:center;opacity:.9;}
48
- .ssk-closebtn:hover{background:rgba(255,255,255,.18);}
49
+ .ssk-iconbtn:hover,.ssk-closebtn:hover{background:rgba(255,255,255,.18);}
49
50
  .ssk-body{flex:1;overflow-y:auto;padding:16px 14px 8px;display:flex;flex-direction:column;gap:10px;
50
51
  background:linear-gradient(180deg,#fff,#fbf6ef);min-height:0;}
52
+ .ssk-history{position:absolute;inset:0;background:#fff;display:flex;flex-direction:column;transform:translateX(-100%);
53
+ transition:transform .25s cubic-bezier(.16,1,.3,1);z-index:2;}
54
+ .ssk-history.shown{transform:translateX(0);}
55
+ .ssk-history-head{display:flex;align-items:center;gap:8px;padding:13px 14px;border-bottom:1px solid rgba(27,39,51,.08);flex-shrink:0;}
56
+ .ssk-history-head h4{margin:0;font-size:14px;font-weight:700;color:#1b2733;flex:1;}
57
+ .ssk-history-back{flex:0 0 auto;width:28px;height:28px;border:none;border-radius:8px;background:rgba(27,39,51,.06);
58
+ color:#1b2733;cursor:pointer;display:flex;align-items:center;justify-content:center;}
59
+ .ssk-history-back:hover{background:rgba(27,39,51,.1);}
60
+ .ssk-history-list{flex:1;overflow-y:auto;padding:8px;}
61
+ .ssk-history-empty{padding:20px 12px;text-align:center;font-size:13px;color:#9aa3ad;}
62
+ .ssk-history-item{display:block;width:100%;text-align:left;border:none;background:transparent;border-radius:11px;
63
+ padding:10px 11px;cursor:pointer;margin-bottom:2px;}
64
+ .ssk-history-item:hover{background:rgba(27,39,51,.05);}
65
+ .ssk-history-item.active{background:rgba(226,112,15,.1);}
66
+ .ssk-history-title{display:block;font-size:13.5px;font-weight:600;color:#1b2733;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
67
+ .ssk-history-time{display:block;font-size:11px;color:#9aa3ad;margin-top:2px;}
51
68
  .ssk-msg{display:flex;max-width:86%;}
52
69
  .ssk-msg.bot{align-self:flex-start;} .ssk-msg.user{align-self:flex-end;}
53
70
  .ssk-bubble-text{padding:10px 13px;border-radius:15px;font-size:14px;line-height:1.5;word-wrap:break-word;}
@@ -78,28 +95,57 @@ function l(t) {
78
95
  .ssk-foot a{color:inherit;text-decoration:underline;}
79
96
  `, document.head.appendChild(r);
80
97
  }
81
- function u(e) {
98
+ function p(e) {
82
99
  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>`;
83
100
  }
84
- function d(e, t) {
101
+ function m(e, t) {
85
102
  let n = document.createElement("div");
86
103
  n.className = `ssk-msg ${t.role}`;
87
104
  let r = document.createElement("div");
88
- r.className = "ssk-bubble-text", r.innerHTML = u(t.text), n.appendChild(r), e.appendChild(n), e.scrollTop = e.scrollHeight;
105
+ r.className = "ssk-bubble-text", r.innerHTML = p(t.text), n.appendChild(r), e.appendChild(n), e.scrollTop = e.scrollHeight;
89
106
  }
90
- function f(e, n) {
107
+ function ee(e) {
91
108
  try {
92
- sessionStorage.setItem(t(e), JSON.stringify(n.slice(-40)));
109
+ return JSON.parse(localStorage.getItem(t(e)) || "[]");
110
+ } catch {
111
+ return [];
112
+ }
113
+ }
114
+ function h(e, n) {
115
+ try {
116
+ localStorage.setItem(t(e), JSON.stringify(n.slice(0, 40)));
93
117
  } catch {}
94
118
  }
95
- function p(e) {
119
+ function g(e) {
96
120
  try {
97
- return JSON.parse(sessionStorage.getItem(t(e)) || "[]");
121
+ return localStorage.getItem(n(e));
98
122
  } catch {
99
- return [];
123
+ return null;
100
124
  }
101
125
  }
102
- function m() {
126
+ function _(e, t) {
127
+ try {
128
+ localStorage.setItem(n(e), t);
129
+ } catch {}
130
+ }
131
+ function v() {
132
+ return `s_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
133
+ }
134
+ function y(e) {
135
+ let t = e.find((e) => e.role === "user");
136
+ return t ? t.text.slice(0, 60) : "New conversation";
137
+ }
138
+ function te(e) {
139
+ let t = new Date(e), n = /* @__PURE__ */ new Date();
140
+ return t.toDateString() === n.toDateString() ? t.toLocaleTimeString(void 0, {
141
+ hour: "numeric",
142
+ minute: "2-digit"
143
+ }) : t.toLocaleDateString(void 0, {
144
+ month: "short",
145
+ day: "numeric"
146
+ });
147
+ }
148
+ function b() {
103
149
  let t = document.body.cloneNode(!0);
104
150
  t.querySelectorAll(`script,style,nav,header,footer,svg,noscript,#${e}`).forEach((e) => e.remove());
105
151
  let n = (t.textContent || "").replace(/\s+/g, " ").trim();
@@ -108,8 +154,8 @@ function m() {
108
154
  text: n
109
155
  };
110
156
  }
111
- async function h(e, t) {
112
- let { title: n, text: r } = m();
157
+ async function x(e, t) {
158
+ let { title: n, text: r } = b();
113
159
  r.length < 40 || await fetch(`${e}/ingest`, {
114
160
  method: "POST",
115
161
  headers: { "Content-Type": "application/json" },
@@ -123,123 +169,170 @@ async function h(e, t) {
123
169
  })
124
170
  }).catch(() => {});
125
171
  }
126
- async function g(e, t) {
127
- let r = !1;
172
+ async function S(e, t) {
173
+ let n = !1;
128
174
  try {
129
- r = !!sessionStorage.getItem(n(t)), sessionStorage.setItem(n(t), "1");
175
+ n = !!sessionStorage.getItem(r(t)), sessionStorage.setItem(r(t), "1");
130
176
  } catch {}
131
- r || await fetch(`${e}/crawlSite`, {
177
+ n || await fetch(`${e}/crawlSite`, {
132
178
  method: "POST",
133
179
  headers: { "Content-Type": "application/json" },
134
180
  body: JSON.stringify({ embedToken: t })
135
181
  }).catch(() => {});
136
182
  }
137
- async function _(t) {
183
+ async function C(t) {
138
184
  if (typeof document > "u" || document.getElementById(e)) return;
139
185
  let n = (t.token || "").trim();
140
186
  if (!n) {
141
187
  console.error("[Sendystack] init() called without a token");
142
188
  return;
143
189
  }
144
- let u = t.apiBase || "https://us-central1-sendystack-fab32.cloudfunctions.net", m;
190
+ let r = t.apiBase || "https://us-central1-sendystack-fab32.cloudfunctions.net", p;
145
191
  try {
146
- m = await c(u, n);
192
+ p = await d(r, n);
147
193
  } catch (e) {
148
194
  console.error("[Sendystack] failed to load widget config:", e);
149
195
  return;
150
196
  }
151
- l(m);
152
- let _ = document.createElement("div");
153
- _.id = e;
154
- let v = p(n), y = document.createElement("div");
155
- if (y.className = "ssk-body", v.length) for (let e of v) d(y, e);
156
- else v.push({
157
- role: "bot",
158
- text: m.welcomeMessage
159
- }), d(y, v[0]);
160
- let b = document.createElement("span");
161
- b.className = "ssk-avatar", b.innerHTML = s;
162
- let x = document.createElement("div");
163
- x.className = "ssk-id-text", x.innerHTML = `<strong>${m.assistantName}</strong><span class="ssk-status"><span class="ssk-dot"></span>Online</span>`;
164
- let S = document.createElement("div");
165
- S.className = "ssk-id", S.append(b, x);
166
- let C = document.createElement("button");
167
- C.className = "ssk-closebtn", C.innerHTML = a, C.setAttribute("aria-label", "Close chat");
168
- let w = document.createElement("div");
169
- w.className = "ssk-head", w.append(S, C);
170
- let T = document.createElement("div");
171
- T.className = "ssk-typing", T.hidden = !0, T.innerHTML = "<span class=\"ssk-dots\"><i></i><i></i><i></i></span>";
172
- let E = document.createElement("textarea");
173
- E.rows = 1, E.maxLength = 4e3, E.placeholder = "Ask me anything…";
174
- let D = document.createElement("button");
175
- D.className = "ssk-sendbtn", D.innerHTML = o, D.setAttribute("aria-label", "Send");
176
- let O = document.createElement("div");
177
- O.className = "ssk-inputrow", O.append(E, D);
178
- let k = document.createElement("div");
179
- k.className = "ssk-foot", k.textContent = m.whiteLabel ? "AI can make mistakes — confirm important details." : "AI can make mistakes — confirm important details. · Powered by Sendystack";
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);
201
+ E || (E = {
202
+ id: v(),
203
+ title: "New conversation",
204
+ messages: [],
205
+ updatedAt: Date.now()
206
+ }, C.unshift(E), T = E.id, _(n, T), h(n, C));
207
+ let D = document.createElement("div");
208
+ D.className = "ssk-body";
209
+ function O() {
210
+ D.innerHTML = "";
211
+ let e = E.messages.length ? E.messages : [{
212
+ role: "bot",
213
+ text: p.welcomeMessage
214
+ }];
215
+ for (let t of e) m(D, t);
216
+ }
217
+ O();
218
+ let k = document.createElement("span");
219
+ k.className = "ssk-avatar", k.innerHTML = s;
180
220
  let A = document.createElement("div");
181
- A.className = "ssk-panel", A.append(w, y, T, O, k);
182
- let j = document.createElement("button");
183
- j.className = "ssk-launcher", j.setAttribute("aria-label", "Open chat"), j.innerHTML = `<span class="ssk-open">${i}</span><span class="ssk-x" style="display:none">${a}</span><span class="ssk-pulse" aria-hidden="true"></span>`;
184
- let M = document.createElement("div");
185
- M.className = "ssk-greeting", M.textContent = m.welcomeMessage, _.append(A, M, j), document.body.appendChild(_), j.onclick = () => _.classList.toggle("open"), C.onclick = () => _.classList.remove("open");
186
- let N = !1;
187
- try {
188
- N = !!sessionStorage.getItem(r(n));
189
- } catch {}
190
- !N && !v.length && setTimeout(() => {
191
- _.classList.contains("open") || M.classList.add("shown");
192
- try {
193
- sessionStorage.setItem(r(n), "1");
194
- } catch {}
195
- setTimeout(() => M.classList.remove("shown"), 6e3);
221
+ A.className = "ssk-id-text", A.innerHTML = `<strong>${p.assistantName}</strong><span class="ssk-status"><span class="ssk-dot"></span>Online</span>`;
222
+ let j = document.createElement("div");
223
+ j.className = "ssk-id", j.append(k, A);
224
+ let M = document.createElement("button");
225
+ M.className = "ssk-iconbtn", M.innerHTML = c, M.setAttribute("aria-label", "Chat history");
226
+ let N = document.createElement("button");
227
+ N.className = "ssk-iconbtn", N.innerHTML = l, N.setAttribute("aria-label", "New chat");
228
+ let P = document.createElement("button");
229
+ P.className = "ssk-closebtn", P.innerHTML = a, P.setAttribute("aria-label", "Close chat");
230
+ let F = document.createElement("div");
231
+ F.className = "ssk-headbtns", F.append(M, N, P);
232
+ let I = document.createElement("div");
233
+ I.className = "ssk-head", I.append(j, F);
234
+ let L = document.createElement("button");
235
+ L.className = "ssk-history-back", L.innerHTML = u, L.setAttribute("aria-label", "Back to chat");
236
+ let R = document.createElement("h4");
237
+ R.textContent = "Chat history";
238
+ let z = document.createElement("div");
239
+ z.className = "ssk-history-head", z.append(L, R);
240
+ let B = document.createElement("div");
241
+ B.className = "ssk-history-list";
242
+ let V = document.createElement("div");
243
+ V.className = "ssk-history", V.append(z, B);
244
+ function H() {
245
+ if (B.innerHTML = "", !C.length) {
246
+ let e = document.createElement("div");
247
+ e.className = "ssk-history-empty", e.textContent = "No past conversations yet.", B.appendChild(e);
248
+ return;
249
+ }
250
+ for (let e of C) {
251
+ 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");
254
+ }, B.appendChild(t);
255
+ }
256
+ }
257
+ M.onclick = () => {
258
+ H(), V.classList.add("shown");
259
+ }, L.onclick = () => V.classList.remove("shown"), N.onclick = () => {
260
+ E.messages.length && (E = {
261
+ id: v(),
262
+ title: "New conversation",
263
+ messages: [],
264
+ updatedAt: Date.now()
265
+ }, C.unshift(E), T = E.id, _(n, T), h(n, C), O(), V.classList.remove("shown"));
266
+ };
267
+ let U = document.createElement("div");
268
+ U.className = "ssk-typing", U.hidden = !0, U.innerHTML = "<span class=\"ssk-dots\"><i></i><i></i><i></i></span>";
269
+ let W = document.createElement("textarea");
270
+ W.rows = 1, W.maxLength = 4e3, W.placeholder = "Ask me anything…";
271
+ let G = document.createElement("button");
272
+ G.className = "ssk-sendbtn", G.innerHTML = o, G.setAttribute("aria-label", "Send");
273
+ let K = document.createElement("div");
274
+ K.className = "ssk-inputrow", K.append(W, G);
275
+ 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";
277
+ let J = document.createElement("div");
278
+ J.className = "ssk-panel", J.append(I, D, U, K, q, V);
279
+ 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>`;
281
+ 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);
196
284
  }, 2500);
197
- function P(e) {
198
- T.hidden = !e, e && (y.scrollTop = y.scrollHeight);
285
+ function Z(e) {
286
+ U.hidden = !e, e && (D.scrollTop = D.scrollHeight);
287
+ }
288
+ 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);
199
292
  }
200
- async function F() {
201
- let e = E.value.trim();
293
+ async function $() {
294
+ let e = W.value.trim();
202
295
  if (!e) return;
203
- E.value = "";
296
+ W.value = "";
204
297
  let t = {
205
298
  role: "user",
206
299
  text: e
207
300
  };
208
- v.push(t), d(y, t), f(n, v), D.disabled = !0, P(!0);
301
+ E.messages.push(t), m(D, t), Q(), G.disabled = !0, Z(!0);
209
302
  try {
210
- let t = await (await fetch(`${u}/answer`, {
303
+ let t = await (await fetch(`${r}/answer`, {
211
304
  method: "POST",
212
305
  headers: { "Content-Type": "application/json" },
213
306
  body: JSON.stringify({
214
307
  embedToken: n,
215
308
  query: e
216
309
  })
217
- })).json(), r = {
310
+ })).json(), i = {
218
311
  role: "bot",
219
312
  text: t.ok ? t.reply : t.message || "Sorry, I couldn't process that right now."
220
313
  };
221
- v.push(r), d(y, r);
314
+ E.messages.push(i), m(D, i);
222
315
  } catch {
223
316
  let e = {
224
317
  role: "bot",
225
318
  text: "I couldn't reach the server. Please try again."
226
319
  };
227
- v.push(e), d(y, e);
320
+ E.messages.push(e), m(D, e);
228
321
  } finally {
229
- P(!1), D.disabled = !1, f(n, v);
322
+ Z(!1), G.disabled = !1, Q();
230
323
  }
231
324
  }
232
- D.onclick = F, E.addEventListener("keydown", (e) => {
233
- e.key === "Enter" && !e.shiftKey && (e.preventDefault(), F());
234
- }), h(u, n), g(u, n);
325
+ G.onclick = $, W.addEventListener("keydown", (e) => {
326
+ e.key === "Enter" && !e.shiftKey && (e.preventDefault(), $());
327
+ }), x(r, n), S(r, n);
235
328
  }
236
- function v() {
329
+ function w() {
237
330
  let e = document.currentScript || Array.from(document.getElementsByTagName("script")).find((e) => /widget(\.global)?\.js/.test(e.src)), t = e?.dataset.token;
238
- t && _({
331
+ t && C({
239
332
  token: t,
240
333
  apiBase: e?.dataset.apiBase
241
334
  });
242
335
  }
243
- typeof document < "u" && v();
336
+ typeof document < "u" && w();
244
337
  //#endregion
245
- export { _ as init };
338
+ export { C as init };
@@ -1,4 +1,4 @@
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=e=>`sendystack_session_${e}`,i=e=>`sendystack_crawled_${e}`,a=e=>`sendystack_greeted_${e}`,o=`<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>`;async function u(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 d(e){let t=e.position===`left`?`left`:`right`,r=document.createElement(`style`);r.textContent=`
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
2
  #${n} *{box-sizing:border-box;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;}
3
3
  #${n}{position:fixed;${t}:20px;bottom:20px;z-index:2147483000;}
4
4
  .ssk-launcher{position:relative;width:60px;height:60px;border-radius:50%;border:none;cursor:pointer;color:#fff;
@@ -31,11 +31,28 @@ var Sendystack=(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:`M
31
31
  .ssk-id-text strong{display:block;font-size:14px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
32
32
  .ssk-status{display:flex;align-items:center;gap:5px;font-size:11px;opacity:.9;}
33
33
  .ssk-dot{width:6px;height:6px;border-radius:50%;background:#46e08a;box-shadow:0 0 7px #46e08a;}
34
- .ssk-closebtn{flex:0 0 auto;width:30px;height:30px;border:none;border-radius:8px;background:transparent;color:#fff;
34
+ .ssk-headbtns{display:flex;align-items:center;gap:2px;flex:0 0 auto;}
35
+ .ssk-iconbtn,.ssk-closebtn{flex:0 0 auto;width:30px;height:30px;border:none;border-radius:8px;background:transparent;color:#fff;
35
36
  cursor:pointer;display:flex;align-items:center;justify-content:center;opacity:.9;}
36
- .ssk-closebtn:hover{background:rgba(255,255,255,.18);}
37
+ .ssk-iconbtn:hover,.ssk-closebtn:hover{background:rgba(255,255,255,.18);}
37
38
  .ssk-body{flex:1;overflow-y:auto;padding:16px 14px 8px;display:flex;flex-direction:column;gap:10px;
38
39
  background:linear-gradient(180deg,#fff,#fbf6ef);min-height:0;}
40
+ .ssk-history{position:absolute;inset:0;background:#fff;display:flex;flex-direction:column;transform:translateX(-100%);
41
+ transition:transform .25s cubic-bezier(.16,1,.3,1);z-index:2;}
42
+ .ssk-history.shown{transform:translateX(0);}
43
+ .ssk-history-head{display:flex;align-items:center;gap:8px;padding:13px 14px;border-bottom:1px solid rgba(27,39,51,.08);flex-shrink:0;}
44
+ .ssk-history-head h4{margin:0;font-size:14px;font-weight:700;color:#1b2733;flex:1;}
45
+ .ssk-history-back{flex:0 0 auto;width:28px;height:28px;border:none;border-radius:8px;background:rgba(27,39,51,.06);
46
+ color:#1b2733;cursor:pointer;display:flex;align-items:center;justify-content:center;}
47
+ .ssk-history-back:hover{background:rgba(27,39,51,.1);}
48
+ .ssk-history-list{flex:1;overflow-y:auto;padding:8px;}
49
+ .ssk-history-empty{padding:20px 12px;text-align:center;font-size:13px;color:#9aa3ad;}
50
+ .ssk-history-item{display:block;width:100%;text-align:left;border:none;background:transparent;border-radius:11px;
51
+ padding:10px 11px;cursor:pointer;margin-bottom:2px;}
52
+ .ssk-history-item:hover{background:rgba(27,39,51,.05);}
53
+ .ssk-history-item.active{background:rgba(226,112,15,.1);}
54
+ .ssk-history-title{display:block;font-size:13.5px;font-weight:600;color:#1b2733;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
55
+ .ssk-history-time{display:block;font-size:11px;color:#9aa3ad;margin-top:2px;}
39
56
  .ssk-msg{display:flex;max-width:86%;}
40
57
  .ssk-msg.bot{align-self:flex-start;} .ssk-msg.user{align-self:flex-end;}
41
58
  .ssk-bubble-text{padding:10px 13px;border-radius:15px;font-size:14px;line-height:1.5;word-wrap:break-word;}
@@ -64,4 +81,4 @@ var Sendystack=(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:`M
64
81
  .ssk-sendbtn:disabled{opacity:.6;cursor:default;transform:none;}
65
82
  .ssk-foot{text-align:center;font-size:10.5px;color:#9aa3ad;padding:0 8px 9px;background:#fff;flex-shrink:0;}
66
83
  .ssk-foot a{color:inherit;text-decoration:underline;}
67
- `,document.head.appendChild(r)}function f(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 p(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=f(t.text),n.appendChild(r),e.appendChild(n),e.scrollTop=e.scrollHeight}function m(e,t){try{sessionStorage.setItem(r(e),JSON.stringify(t.slice(-40)))}catch{}}function h(e){try{return JSON.parse(sessionStorage.getItem(r(e))||`[]`)}catch{return[]}}function g(){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 _(e,t){let{title:n,text:r}=g();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 v(e,t){let n=!1;try{n=!!sessionStorage.getItem(i(t)),sessionStorage.setItem(i(t),`1`)}catch{}n||await fetch(`${e}/crawlSite`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({embedToken:t})}).catch(()=>{})}async function y(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,f;try{f=await u(i,r)}catch(e){console.error(`[Sendystack] failed to load widget config:`,e);return}d(f);let g=document.createElement(`div`);g.id=n;let y=h(r),b=document.createElement(`div`);if(b.className=`ssk-body`,y.length)for(let e of y)p(b,e);else y.push({role:`bot`,text:f.welcomeMessage}),p(b,y[0]);let x=document.createElement(`span`);x.className=`ssk-avatar`,x.innerHTML=l;let S=document.createElement(`div`);S.className=`ssk-id-text`,S.innerHTML=`<strong>${f.assistantName}</strong><span class="ssk-status"><span class="ssk-dot"></span>Online</span>`;let C=document.createElement(`div`);C.className=`ssk-id`,C.append(x,S);let w=document.createElement(`button`);w.className=`ssk-closebtn`,w.innerHTML=s,w.setAttribute(`aria-label`,`Close chat`);let T=document.createElement(`div`);T.className=`ssk-head`,T.append(C,w);let E=document.createElement(`div`);E.className=`ssk-typing`,E.hidden=!0,E.innerHTML=`<span class="ssk-dots"><i></i><i></i><i></i></span>`;let D=document.createElement(`textarea`);D.rows=1,D.maxLength=4e3,D.placeholder=`Ask me anything…`;let O=document.createElement(`button`);O.className=`ssk-sendbtn`,O.innerHTML=c,O.setAttribute(`aria-label`,`Send`);let k=document.createElement(`div`);k.className=`ssk-inputrow`,k.append(D,O);let A=document.createElement(`div`);A.className=`ssk-foot`,A.textContent=f.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(T,b,E,k,A);let M=document.createElement(`button`);M.className=`ssk-launcher`,M.setAttribute(`aria-label`,`Open chat`),M.innerHTML=`<span class="ssk-open">${o}</span><span class="ssk-x" style="display:none">${s}</span><span class="ssk-pulse" aria-hidden="true"></span>`;let N=document.createElement(`div`);N.className=`ssk-greeting`,N.textContent=f.welcomeMessage,g.append(j,N,M),document.body.appendChild(g),M.onclick=()=>g.classList.toggle(`open`),w.onclick=()=>g.classList.remove(`open`);let P=!1;try{P=!!sessionStorage.getItem(a(r))}catch{}!P&&!y.length&&setTimeout(()=>{g.classList.contains(`open`)||N.classList.add(`shown`);try{sessionStorage.setItem(a(r),`1`)}catch{}setTimeout(()=>N.classList.remove(`shown`),6e3)},2500);function F(e){E.hidden=!e,e&&(b.scrollTop=b.scrollHeight)}async function I(){let e=D.value.trim();if(!e)return;D.value=``;let t={role:`user`,text:e};y.push(t),p(b,t),m(r,y),O.disabled=!0,F(!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.`};y.push(n),p(b,n)}catch{let e={role:`bot`,text:`I couldn't reach the server. Please try again.`};y.push(e),p(b,e)}finally{F(!1),O.disabled=!1,m(r,y)}}O.onclick=I,D.addEventListener(`keydown`,e=>{e.key===`Enter`&&!e.shiftKey&&(e.preventDefault(),I())}),_(i,r),v(i,r)}function b(){let e=document.currentScript||Array.from(document.getElementsByTagName(`script`)).find(e=>/widget(\.global)?\.js/.test(e.src)),t=e?.dataset.token;t&&y({token:t,apiBase:e?.dataset.apiBase})}return typeof document<`u`&&b(),e.init=y,e})({});
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})({});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sendystack/widget",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
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",