@sendystack/widget 0.1.1 → 0.1.3

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