@sendystack/widget 0.1.0 → 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.d.ts CHANGED
@@ -3,6 +3,13 @@
3
3
  * - a plain <script data-token="emb_..."> tag (auto-inits from its own tag), or
4
4
  * - an npm import: `import { init } from "@sendystack/widget"`.
5
5
  *
6
+ * UI/UX deliberately mirrors the WordPress plugin's widget (plugin-chat/assets/{css,js}/widget.js)
7
+ * so the experience feels the same regardless of integration path: pulsing
8
+ * launcher, greeting nudge, typing indicator, markdown-lite replies, footer
9
+ * branding. The backend call shape differs (embed token + the shared cloud
10
+ * `answer`/`ingest`/`crawlSite` functions, vs. the plugin's own WordPress
11
+ * REST routes), but the visual language is the same product.
12
+ *
6
13
  * On load it fetches appearance (never secrets) from the dashboard, renders
7
14
  * the bubble+panel, wires chat through the embed-token `answer` endpoint,
8
15
  * reports the current page's text to `ingest` so the page is searchable
package/dist/index.es.js CHANGED
@@ -1,54 +1,151 @@
1
1
  //#region src/index.ts
2
- var e = "sendystack-widget-root", t = (e) => `sendystack_session_${e}`, n = (e) => `sendystack_crawled_${e}`;
3
- async function r(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
- return n.appearance;
6
+ return {
7
+ ...n.appearance,
8
+ whiteLabel: !!n.whiteLabel
9
+ };
7
10
  }
8
- function i(t) {
11
+ function f(t) {
9
12
  let n = t.position === "left" ? "left" : "right", r = document.createElement("style");
10
13
  r.textContent = `
11
- #${e} *{box-sizing:border-box;}
12
- .ssk-bubble{position:fixed;${n}:20px;bottom:20px;width:58px;height:58px;border-radius:50%;
13
- background:linear-gradient(135deg,${t.accent2},${t.accent});color:#fff;display:grid;
14
- place-items:center;cursor:pointer;box-shadow:0 12px 28px rgba(0,0,0,.22);z-index:2147483000;border:none;font-size:24px;}
15
- .ssk-panel{position:fixed;${n}:20px;bottom:90px;width:340px;max-height:70vh;background:#fff;border-radius:16px;
16
- box-shadow:0 24px 60px rgba(0,0,0,.25);display:none;flex-direction:column;overflow:hidden;z-index:2147483000;
17
- font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;}
18
- .ssk-panel.open{display:flex;}
19
- .ssk-head{background:linear-gradient(135deg,${t.accent2},${t.accent});color:#fff;padding:14px 16px;
20
- display:flex;align-items:center;justify-content:space-between;font-weight:700;font-size:14px;flex-shrink:0;}
21
- .ssk-head button{background:none;border:none;color:#fff;font-size:16px;cursor:pointer;opacity:.85;padding:0;}
22
- .ssk-body{flex:1;overflow-y:auto;padding:14px;background:#faf9f7;display:flex;flex-direction:column;gap:8px;min-height:200px;}
23
- .ssk-msg{max-width:85%;padding:9px 12px;border-radius:12px;font-size:13.5px;line-height:1.45;white-space:pre-wrap;}
24
- .ssk-msg.bot{background:#fff;border:1px solid rgba(0,0,0,.08);align-self:flex-start;color:#1b2733;}
25
- .ssk-msg.user{background:${t.accent};color:#fff;align-self:flex-end;}
26
- .ssk-inputrow{display:flex;border-top:1px solid rgba(0,0,0,.08);padding:8px;gap:6px;flex-shrink:0;}
27
- .ssk-inputrow input{flex:1;border:1px solid rgba(0,0,0,.12);border-radius:10px;padding:9px 11px;font-size:13.5px;outline:none;}
28
- .ssk-inputrow button{background:${t.accent};color:#fff;border:none;border-radius:10px;padding:0 14px;cursor:pointer;font-weight:700;}
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);
18
+ display:flex;align-items:center;justify-content:center;transition:transform .25s cubic-bezier(.16,1,.3,1);margin-left:auto;}
19
+ .ssk-launcher:hover{transform:translateY(-3px) scale(1.04);}
20
+ .ssk-launcher:active{transform:scale(.96);}
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;}
24
+ @keyframes sskPulse{0%{transform:scale(1);opacity:.5;}70%,100%{transform:scale(1.7);opacity:0;}}
25
+ @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
+ color:#1b2733;font-size:13.5px;line-height:1.4;font-weight:600;box-shadow:0 16px 38px rgba(27,39,51,.18);
28
+ transform:translateY(8px);opacity:0;pointer-events:none;transition:opacity .3s,transform .3s;}
29
+ .ssk-greeting.shown{opacity:1;transform:translateY(0);}
30
+ #${e}.open .ssk-greeting{display:none;}
31
+ .ssk-panel{position:absolute;bottom:74px;${n}:0;width:368px;max-width:calc(100vw - 32px);height:560px;
32
+ max-height:calc(100vh - 110px);background:#fff;border-radius:20px;overflow:hidden;display:flex;flex-direction:column;
33
+ box-shadow:0 36px 80px rgba(20,20,30,.3);border:1px solid rgba(27,39,51,.08);
34
+ transform:translateY(14px) scale(.96);opacity:0;pointer-events:none;transform-origin:bottom ${n};
35
+ 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
+ .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
+ .ssk-id{display:flex;align-items:center;gap:9px;min-width:0;}
40
+ .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
+ .ssk-id-text{min-width:0;}
43
+ .ssk-id-text strong{display:block;font-size:14px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
44
+ .ssk-status{display:flex;align-items:center;gap:5px;font-size:11px;opacity:.9;}
45
+ .ssk-dot{width:6px;height:6px;border-radius:50%;background:#46e08a;box-shadow:0 0 7px #46e08a;}
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;
48
+ cursor:pointer;display:flex;align-items:center;justify-content:center;opacity:.9;}
49
+ .ssk-iconbtn:hover,.ssk-closebtn:hover{background:rgba(255,255,255,.18);}
50
+ .ssk-body{flex:1;overflow-y:auto;padding:16px 14px 8px;display:flex;flex-direction:column;gap:10px;
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;}
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-bubble-text{padding:10px 13px;border-radius:15px;font-size:14px;line-height:1.5;word-wrap:break-word;}
71
+ .ssk-msg.bot .ssk-bubble-text{background:#fff;border:1px solid rgba(27,39,51,.08);border-bottom-left-radius:5px;
72
+ 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 a{color:#fff;}
76
+ .ssk-bubble-text p{margin:0 0 7px;} .ssk-bubble-text p:last-child{margin:0;}
77
+ .ssk-typing{padding:0 14px 10px;flex-shrink:0;}
78
+ .ssk-typing[hidden]{display:none;}
79
+ .ssk-dots{display:inline-flex;gap:5px;padding:10px 13px;background:#fff;border:1px solid rgba(27,39,51,.08);
80
+ 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;}
82
+ .ssk-dots i:nth-child(2){animation-delay:.15s;} .ssk-dots i:nth-child(3){animation-delay:.3s;}
83
+ @keyframes sskWave{0%,60%,100%{transform:translateY(0);opacity:.5;}30%{transform:translateY(-5px);opacity:1;}}
84
+ .ssk-inputrow{display:flex;align-items:flex-end;gap:8px;padding:11px;border-top:1px solid rgba(27,39,51,.08);
85
+ background:#fff;flex-shrink:0;}
86
+ .ssk-inputrow textarea{flex:1;resize:none;max-height:90px;border:1px solid rgba(27,39,51,.14);border-radius:13px;
87
+ 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;}
89
+ .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;
91
+ box-shadow:0 9px 20px rgba(0,0,0,.18);transition:transform .15s;}
92
+ .ssk-sendbtn:hover{transform:translateY(-2px);}
93
+ .ssk-sendbtn:disabled{opacity:.6;cursor:default;transform:none;}
94
+ .ssk-foot{text-align:center;font-size:10.5px;color:#9aa3ad;padding:0 8px 9px;background:#fff;flex-shrink:0;}
95
+ .ssk-foot a{color:inherit;text-decoration:underline;}
29
96
  `, document.head.appendChild(r);
30
97
  }
31
- function a(e, t) {
98
+ function p(e) {
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>`;
100
+ }
101
+ function m(e, t) {
32
102
  let n = document.createElement("div");
33
- n.className = `ssk-msg ${t.role}`, n.textContent = t.text, e.appendChild(n), e.scrollTop = e.scrollHeight;
103
+ n.className = `ssk-msg ${t.role}`;
104
+ 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;
106
+ }
107
+ function ee(e) {
108
+ try {
109
+ return JSON.parse(localStorage.getItem(t(e)) || "[]");
110
+ } catch {
111
+ return [];
112
+ }
34
113
  }
35
- function o(e, n) {
36
- let r = Array.from(n.children).map((e) => ({
37
- role: e.classList.contains("user") ? "user" : "bot",
38
- text: e.textContent || ""
39
- }));
114
+ function h(e, n) {
40
115
  try {
41
- sessionStorage.setItem(t(e), JSON.stringify(r.slice(-40)));
116
+ localStorage.setItem(t(e), JSON.stringify(n.slice(0, 40)));
42
117
  } catch {}
43
118
  }
44
- function s(e) {
119
+ function g(e) {
45
120
  try {
46
- return JSON.parse(sessionStorage.getItem(t(e)) || "[]");
121
+ return localStorage.getItem(n(e));
47
122
  } catch {
48
- return [];
123
+ return null;
49
124
  }
50
125
  }
51
- function c() {
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() {
52
149
  let t = document.body.cloneNode(!0);
53
150
  t.querySelectorAll(`script,style,nav,header,footer,svg,noscript,#${e}`).forEach((e) => e.remove());
54
151
  let n = (t.textContent || "").replace(/\s+/g, " ").trim();
@@ -57,8 +154,8 @@ function c() {
57
154
  text: n
58
155
  };
59
156
  }
60
- async function l(e, t) {
61
- let { title: n, text: r } = c();
157
+ async function x(e, t) {
158
+ let { title: n, text: r } = b();
62
159
  r.length < 40 || await fetch(`${e}/ingest`, {
63
160
  method: "POST",
64
161
  headers: { "Content-Type": "application/json" },
@@ -72,99 +169,170 @@ async function l(e, t) {
72
169
  })
73
170
  }).catch(() => {});
74
171
  }
75
- async function u(e, t) {
76
- let r = !1;
172
+ async function S(e, t) {
173
+ let n = !1;
77
174
  try {
78
- r = !!sessionStorage.getItem(n(t)), sessionStorage.setItem(n(t), "1");
175
+ n = !!sessionStorage.getItem(r(t)), sessionStorage.setItem(r(t), "1");
79
176
  } catch {}
80
- r || await fetch(`${e}/crawlSite`, {
177
+ n || await fetch(`${e}/crawlSite`, {
81
178
  method: "POST",
82
179
  headers: { "Content-Type": "application/json" },
83
180
  body: JSON.stringify({ embedToken: t })
84
181
  }).catch(() => {});
85
182
  }
86
- async function d(t) {
183
+ async function C(t) {
87
184
  if (typeof document > "u" || document.getElementById(e)) return;
88
185
  let n = (t.token || "").trim();
89
186
  if (!n) {
90
187
  console.error("[Sendystack] init() called without a token");
91
188
  return;
92
189
  }
93
- let c = t.apiBase || "https://us-central1-sendystack-fab32.cloudfunctions.net", d;
190
+ let r = t.apiBase || "https://us-central1-sendystack-fab32.cloudfunctions.net", p;
94
191
  try {
95
- d = await r(c, n);
192
+ p = await d(r, n);
96
193
  } catch (e) {
97
194
  console.error("[Sendystack] failed to load widget config:", e);
98
195
  return;
99
196
  }
100
- i(d);
101
- let f = document.createElement("div");
102
- f.id = e;
103
- let p = document.createElement("div");
104
- p.className = "ssk-body";
105
- let m = s(n);
106
- if (m.length) for (let e of m) a(p, e);
107
- else a(p, {
108
- role: "bot",
109
- text: d.welcomeMessage
110
- });
111
- let h = document.createElement("div");
112
- h.className = "ssk-head";
113
- let g = document.createElement("span");
114
- g.textContent = d.assistantName;
115
- let _ = document.createElement("button");
116
- _.textContent = "✕", h.append(g, _);
117
- let v = document.createElement("input");
118
- v.type = "text", v.placeholder = "Ask anything…";
119
- let y = document.createElement("button");
120
- y.textContent = "Send";
197
+ f(p);
121
198
  let b = document.createElement("div");
122
- b.className = "ssk-inputrow", b.append(v, y);
123
- let x = document.createElement("div");
124
- x.className = "ssk-panel", x.append(h, p, b);
125
- let S = document.createElement("button");
126
- S.className = "ssk-bubble", S.textContent = "💬", S.setAttribute("aria-label", "Open chat"), f.append(x, S), document.body.appendChild(f), S.onclick = () => x.classList.toggle("open"), _.onclick = () => x.classList.remove("open");
127
- async function C() {
128
- let e = v.value.trim();
129
- if (e) {
130
- v.value = "", a(p, {
131
- role: "user",
132
- text: e
133
- }), o(n, p), y.disabled = !0;
134
- try {
135
- let t = await (await fetch(`${c}/answer`, {
136
- method: "POST",
137
- headers: { "Content-Type": "application/json" },
138
- body: JSON.stringify({
139
- embedToken: n,
140
- query: e
141
- })
142
- })).json();
143
- a(p, {
144
- role: "bot",
145
- text: t.ok ? t.reply : "Sorry, I couldn't process that right now."
146
- });
147
- } catch {
148
- a(p, {
149
- role: "bot",
150
- text: "Sorry, something went wrong. Please try again."
151
- });
152
- } finally {
153
- y.disabled = !1, o(n, p);
154
- }
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;
220
+ 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>`;
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);
284
+ }, 2500);
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);
292
+ }
293
+ async function $() {
294
+ let e = W.value.trim();
295
+ if (!e) return;
296
+ W.value = "";
297
+ let t = {
298
+ role: "user",
299
+ text: e
300
+ };
301
+ E.messages.push(t), m(D, t), Q(), G.disabled = !0, Z(!0);
302
+ try {
303
+ let t = await (await fetch(`${r}/answer`, {
304
+ method: "POST",
305
+ headers: { "Content-Type": "application/json" },
306
+ body: JSON.stringify({
307
+ embedToken: n,
308
+ query: e
309
+ })
310
+ })).json(), i = {
311
+ role: "bot",
312
+ text: t.ok ? t.reply : t.message || "Sorry, I couldn't process that right now."
313
+ };
314
+ E.messages.push(i), m(D, i);
315
+ } catch {
316
+ let e = {
317
+ role: "bot",
318
+ text: "I couldn't reach the server. Please try again."
319
+ };
320
+ E.messages.push(e), m(D, e);
321
+ } finally {
322
+ Z(!1), G.disabled = !1, Q();
155
323
  }
156
324
  }
157
- y.onclick = C, v.addEventListener("keydown", (e) => {
158
- e.key === "Enter" && C();
159
- }), l(c, n), u(c, n);
325
+ G.onclick = $, W.addEventListener("keydown", (e) => {
326
+ e.key === "Enter" && !e.shiftKey && (e.preventDefault(), $());
327
+ }), x(r, n), S(r, n);
160
328
  }
161
- function f() {
329
+ function w() {
162
330
  let e = document.currentScript || Array.from(document.getElementsByTagName("script")).find((e) => /widget(\.global)?\.js/.test(e.src)), t = e?.dataset.token;
163
- t && d({
331
+ t && C({
164
332
  token: t,
165
333
  apiBase: e?.dataset.apiBase
166
334
  });
167
335
  }
168
- typeof document < "u" && f();
336
+ typeof document < "u" && w();
169
337
  //#endregion
170
- export { d as init };
338
+ export { C as init };
@@ -1,20 +1,84 @@
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}`;async function a(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}function o(e){let t=e.position===`left`?`left`:`right`,r=document.createElement(`style`);r.textContent=`
2
- #${n} *{box-sizing:border-box;}
3
- .ssk-bubble{position:fixed;${t}:20px;bottom:20px;width:58px;height:58px;border-radius:50%;
4
- background:linear-gradient(135deg,${e.accent2},${e.accent});color:#fff;display:grid;
5
- place-items:center;cursor:pointer;box-shadow:0 12px 28px rgba(0,0,0,.22);z-index:2147483000;border:none;font-size:24px;}
6
- .ssk-panel{position:fixed;${t}:20px;bottom:90px;width:340px;max-height:70vh;background:#fff;border-radius:16px;
7
- box-shadow:0 24px 60px rgba(0,0,0,.25);display:none;flex-direction:column;overflow:hidden;z-index:2147483000;
8
- font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;}
9
- .ssk-panel.open{display:flex;}
10
- .ssk-head{background:linear-gradient(135deg,${e.accent2},${e.accent});color:#fff;padding:14px 16px;
11
- display:flex;align-items:center;justify-content:space-between;font-weight:700;font-size:14px;flex-shrink:0;}
12
- .ssk-head button{background:none;border:none;color:#fff;font-size:16px;cursor:pointer;opacity:.85;padding:0;}
13
- .ssk-body{flex:1;overflow-y:auto;padding:14px;background:#faf9f7;display:flex;flex-direction:column;gap:8px;min-height:200px;}
14
- .ssk-msg{max-width:85%;padding:9px 12px;border-radius:12px;font-size:13.5px;line-height:1.45;white-space:pre-wrap;}
15
- .ssk-msg.bot{background:#fff;border:1px solid rgba(0,0,0,.08);align-self:flex-start;color:#1b2733;}
16
- .ssk-msg.user{background:${e.accent};color:#fff;align-self:flex-end;}
17
- .ssk-inputrow{display:flex;border-top:1px solid rgba(0,0,0,.08);padding:8px;gap:6px;flex-shrink:0;}
18
- .ssk-inputrow input{flex:1;border:1px solid rgba(0,0,0,.12);border-radius:10px;padding:9px 11px;font-size:13.5px;outline:none;}
19
- .ssk-inputrow button{background:${e.accent};color:#fff;border:none;border-radius:10px;padding:0 14px;cursor:pointer;font-weight:700;}
20
- `,document.head.appendChild(r)}function s(e,t){let n=document.createElement(`div`);n.className=`ssk-msg ${t.role}`,n.textContent=t.text,e.appendChild(n),e.scrollTop=e.scrollHeight}function c(e,t){let n=Array.from(t.children).map(e=>({role:e.classList.contains(`user`)?`user`:`bot`,text:e.textContent||``}));try{sessionStorage.setItem(r(e),JSON.stringify(n.slice(-40)))}catch{}}function l(e){try{return JSON.parse(sessionStorage.getItem(r(e))||`[]`)}catch{return[]}}function u(){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 d(e,t){let{title:n,text:r}=u();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 f(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 p(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,u;try{u=await a(i,r)}catch(e){console.error(`[Sendystack] failed to load widget config:`,e);return}o(u);let p=document.createElement(`div`);p.id=n;let m=document.createElement(`div`);m.className=`ssk-body`;let h=l(r);if(h.length)for(let e of h)s(m,e);else s(m,{role:`bot`,text:u.welcomeMessage});let g=document.createElement(`div`);g.className=`ssk-head`;let _=document.createElement(`span`);_.textContent=u.assistantName;let v=document.createElement(`button`);v.textContent=`✕`,g.append(_,v);let y=document.createElement(`input`);y.type=`text`,y.placeholder=`Ask anything…`;let b=document.createElement(`button`);b.textContent=`Send`;let x=document.createElement(`div`);x.className=`ssk-inputrow`,x.append(y,b);let S=document.createElement(`div`);S.className=`ssk-panel`,S.append(g,m,x);let C=document.createElement(`button`);C.className=`ssk-bubble`,C.textContent=`💬`,C.setAttribute(`aria-label`,`Open chat`),p.append(S,C),document.body.appendChild(p),C.onclick=()=>S.classList.toggle(`open`),v.onclick=()=>S.classList.remove(`open`);async function w(){let e=y.value.trim();if(e){y.value=``,s(m,{role:`user`,text:e}),c(r,m),b.disabled=!0;try{let t=await(await fetch(`${i}/answer`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({embedToken:r,query:e})})).json();s(m,{role:`bot`,text:t.ok?t.reply:`Sorry, I couldn't process that right now.`})}catch{s(m,{role:`bot`,text:`Sorry, something went wrong. Please try again.`})}finally{b.disabled=!1,c(r,m)}}}b.onclick=w,y.addEventListener(`keydown`,e=>{e.key===`Enter`&&w()}),d(i,r),f(i,r)}function m(){let e=document.currentScript||Array.from(document.getElementsByTagName(`script`)).find(e=>/widget(\.global)?\.js/.test(e.src)),t=e?.dataset.token;t&&p({token:t,apiBase:e?.dataset.apiBase})}return typeof document<`u`&&m(),e.init=p,e})({});
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);
6
+ display:flex;align-items:center;justify-content:center;transition:transform .25s cubic-bezier(.16,1,.3,1);margin-left:auto;}
7
+ .ssk-launcher:hover{transform:translateY(-3px) scale(1.04);}
8
+ .ssk-launcher:active{transform:scale(.96);}
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;}
11
+ .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
+ @keyframes sskPulse{0%{transform:scale(1);opacity:.5;}70%,100%{transform:scale(1.7);opacity:0;}}
13
+ @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
+ color:#1b2733;font-size:13.5px;line-height:1.4;font-weight:600;box-shadow:0 16px 38px rgba(27,39,51,.18);
16
+ transform:translateY(8px);opacity:0;pointer-events:none;transition:opacity .3s,transform .3s;}
17
+ .ssk-greeting.shown{opacity:1;transform:translateY(0);}
18
+ #${n}.open .ssk-greeting{display:none;}
19
+ .ssk-panel{position:absolute;bottom:74px;${t}:0;width:368px;max-width:calc(100vw - 32px);height:560px;
20
+ max-height:calc(100vh - 110px);background:#fff;border-radius:20px;overflow:hidden;display:flex;flex-direction:column;
21
+ box-shadow:0 36px 80px rgba(20,20,30,.3);border:1px solid rgba(27,39,51,.08);
22
+ transform:translateY(14px) scale(.96);opacity:0;pointer-events:none;transform-origin:bottom ${t};
23
+ 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
+ .ssk-head{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:13px 14px;flex-shrink:0;
26
+ background:linear-gradient(135deg,${e.accent2},${e.accent});color:#fff;}
27
+ .ssk-id{display:flex;align-items:center;gap:9px;min-width:0;}
28
+ .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
+ .ssk-id-text{min-width:0;}
31
+ .ssk-id-text strong{display:block;font-size:14px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
32
+ .ssk-status{display:flex;align-items:center;gap:5px;font-size:11px;opacity:.9;}
33
+ .ssk-dot{width:6px;height:6px;border-radius:50%;background:#46e08a;box-shadow:0 0 7px #46e08a;}
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;
36
+ cursor:pointer;display:flex;align-items:center;justify-content:center;opacity:.9;}
37
+ .ssk-iconbtn:hover,.ssk-closebtn:hover{background:rgba(255,255,255,.18);}
38
+ .ssk-body{flex:1;overflow-y:auto;padding:16px 14px 8px;display:flex;flex-direction:column;gap:10px;
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;}
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-bubble-text{padding:10px 13px;border-radius:15px;font-size:14px;line-height:1.5;word-wrap:break-word;}
59
+ .ssk-msg.bot .ssk-bubble-text{background:#fff;border:1px solid rgba(27,39,51,.08);border-bottom-left-radius:5px;
60
+ box-shadow:0 5px 14px rgba(27,39,51,.05);color:#1b2733;}
61
+ .ssk-msg.user .ssk-bubble-text{background:linear-gradient(135deg,${e.accent2},${e.accent});color:#fff;border-bottom-right-radius:5px;}
62
+ .ssk-bubble-text a{color:${e.accent};font-weight:700;text-decoration:underline;}
63
+ .ssk-msg.user .ssk-bubble-text a{color:#fff;}
64
+ .ssk-bubble-text p{margin:0 0 7px;} .ssk-bubble-text p:last-child{margin:0;}
65
+ .ssk-typing{padding:0 14px 10px;flex-shrink:0;}
66
+ .ssk-typing[hidden]{display:none;}
67
+ .ssk-dots{display:inline-flex;gap:5px;padding:10px 13px;background:#fff;border:1px solid rgba(27,39,51,.08);
68
+ border-radius:15px;border-bottom-left-radius:5px;}
69
+ .ssk-dots i{width:7px;height:7px;border-radius:50%;background:${e.accent};animation:sskWave 1.2s ease-in-out infinite;}
70
+ .ssk-dots i:nth-child(2){animation-delay:.15s;} .ssk-dots i:nth-child(3){animation-delay:.3s;}
71
+ @keyframes sskWave{0%,60%,100%{transform:translateY(0);opacity:.5;}30%{transform:translateY(-5px);opacity:1;}}
72
+ .ssk-inputrow{display:flex;align-items:flex-end;gap:8px;padding:11px;border-top:1px solid rgba(27,39,51,.08);
73
+ background:#fff;flex-shrink:0;}
74
+ .ssk-inputrow textarea{flex:1;resize:none;max-height:90px;border:1px solid rgba(27,39,51,.14);border-radius:13px;
75
+ padding:10px 12px;font-size:14px;line-height:1.4;color:#1b2733;outline:none;background:#fbf7f1;}
76
+ .ssk-inputrow textarea:focus{border-color:${e.accent};background:#fff;}
77
+ .ssk-sendbtn{width:42px;height:42px;flex:0 0 auto;border:none;border-radius:12px;cursor:pointer;color:#fff;
78
+ background:linear-gradient(135deg,${e.accent2},${e.accent});display:flex;align-items:center;justify-content:center;
79
+ box-shadow:0 9px 20px rgba(0,0,0,.18);transition:transform .15s;}
80
+ .ssk-sendbtn:hover{transform:translateY(-2px);}
81
+ .ssk-sendbtn:disabled{opacity:.6;cursor:default;transform:none;}
82
+ .ssk-foot{text-align:center;font-size:10.5px;color:#9aa3ad;padding:0 8px 9px;background:#fff;flex-shrink:0;}
83
+ .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})({});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sendystack/widget",
3
- "version": "0.1.0",
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",
@@ -35,7 +35,7 @@
35
35
  "homepage": "https://sendystack.org",
36
36
  "repository": {
37
37
  "type": "git",
38
- "url": "https://github.com/sendystack/chat"
38
+ "url": "git+https://github.com/sendystack/chat.git"
39
39
  },
40
40
  "publishConfig": {
41
41
  "access": "public"