@sendystack/widget 0.1.0 → 0.1.1

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,105 @@
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_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) {
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 l(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-closebtn{flex:0 0 auto;width:30px;height:30px;border:none;border-radius:8px;background:transparent;color:#fff;
47
+ cursor:pointer;display:flex;align-items:center;justify-content:center;opacity:.9;}
48
+ .ssk-closebtn:hover{background:rgba(255,255,255,.18);}
49
+ .ssk-body{flex:1;overflow-y:auto;padding:16px 14px 8px;display:flex;flex-direction:column;gap:10px;
50
+ background:linear-gradient(180deg,#fff,#fbf6ef);min-height:0;}
51
+ .ssk-msg{display:flex;max-width:86%;}
52
+ .ssk-msg.bot{align-self:flex-start;} .ssk-msg.user{align-self:flex-end;}
53
+ .ssk-bubble-text{padding:10px 13px;border-radius:15px;font-size:14px;line-height:1.5;word-wrap:break-word;}
54
+ .ssk-msg.bot .ssk-bubble-text{background:#fff;border:1px solid rgba(27,39,51,.08);border-bottom-left-radius:5px;
55
+ 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;}
58
+ .ssk-msg.user .ssk-bubble-text a{color:#fff;}
59
+ .ssk-bubble-text p{margin:0 0 7px;} .ssk-bubble-text p:last-child{margin:0;}
60
+ .ssk-typing{padding:0 14px 10px;flex-shrink:0;}
61
+ .ssk-typing[hidden]{display:none;}
62
+ .ssk-dots{display:inline-flex;gap:5px;padding:10px 13px;background:#fff;border:1px solid rgba(27,39,51,.08);
63
+ 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;}
65
+ .ssk-dots i:nth-child(2){animation-delay:.15s;} .ssk-dots i:nth-child(3){animation-delay:.3s;}
66
+ @keyframes sskWave{0%,60%,100%{transform:translateY(0);opacity:.5;}30%{transform:translateY(-5px);opacity:1;}}
67
+ .ssk-inputrow{display:flex;align-items:flex-end;gap:8px;padding:11px;border-top:1px solid rgba(27,39,51,.08);
68
+ background:#fff;flex-shrink:0;}
69
+ .ssk-inputrow textarea{flex:1;resize:none;max-height:90px;border:1px solid rgba(27,39,51,.14);border-radius:13px;
70
+ 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;}
72
+ .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;
74
+ box-shadow:0 9px 20px rgba(0,0,0,.18);transition:transform .15s;}
75
+ .ssk-sendbtn:hover{transform:translateY(-2px);}
76
+ .ssk-sendbtn:disabled{opacity:.6;cursor:default;transform:none;}
77
+ .ssk-foot{text-align:center;font-size:10.5px;color:#9aa3ad;padding:0 8px 9px;background:#fff;flex-shrink:0;}
78
+ .ssk-foot a{color:inherit;text-decoration:underline;}
29
79
  `, document.head.appendChild(r);
30
80
  }
31
- function a(e, t) {
81
+ function u(e) {
82
+ 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
+ }
84
+ function d(e, t) {
32
85
  let n = document.createElement("div");
33
- n.className = `ssk-msg ${t.role}`, n.textContent = t.text, e.appendChild(n), e.scrollTop = e.scrollHeight;
86
+ n.className = `ssk-msg ${t.role}`;
87
+ 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;
34
89
  }
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
- }));
90
+ function f(e, n) {
40
91
  try {
41
- sessionStorage.setItem(t(e), JSON.stringify(r.slice(-40)));
92
+ sessionStorage.setItem(t(e), JSON.stringify(n.slice(-40)));
42
93
  } catch {}
43
94
  }
44
- function s(e) {
95
+ function p(e) {
45
96
  try {
46
97
  return JSON.parse(sessionStorage.getItem(t(e)) || "[]");
47
98
  } catch {
48
99
  return [];
49
100
  }
50
101
  }
51
- function c() {
102
+ function m() {
52
103
  let t = document.body.cloneNode(!0);
53
104
  t.querySelectorAll(`script,style,nav,header,footer,svg,noscript,#${e}`).forEach((e) => e.remove());
54
105
  let n = (t.textContent || "").replace(/\s+/g, " ").trim();
@@ -57,8 +108,8 @@ function c() {
57
108
  text: n
58
109
  };
59
110
  }
60
- async function l(e, t) {
61
- let { title: n, text: r } = c();
111
+ async function h(e, t) {
112
+ let { title: n, text: r } = m();
62
113
  r.length < 40 || await fetch(`${e}/ingest`, {
63
114
  method: "POST",
64
115
  headers: { "Content-Type": "application/json" },
@@ -72,7 +123,7 @@ async function l(e, t) {
72
123
  })
73
124
  }).catch(() => {});
74
125
  }
75
- async function u(e, t) {
126
+ async function g(e, t) {
76
127
  let r = !1;
77
128
  try {
78
129
  r = !!sessionStorage.getItem(n(t)), sessionStorage.setItem(n(t), "1");
@@ -83,88 +134,112 @@ async function u(e, t) {
83
134
  body: JSON.stringify({ embedToken: t })
84
135
  }).catch(() => {});
85
136
  }
86
- async function d(t) {
137
+ async function _(t) {
87
138
  if (typeof document > "u" || document.getElementById(e)) return;
88
139
  let n = (t.token || "").trim();
89
140
  if (!n) {
90
141
  console.error("[Sendystack] init() called without a token");
91
142
  return;
92
143
  }
93
- let c = t.apiBase || "https://us-central1-sendystack-fab32.cloudfunctions.net", d;
144
+ let u = t.apiBase || "https://us-central1-sendystack-fab32.cloudfunctions.net", m;
94
145
  try {
95
- d = await r(c, n);
146
+ m = await c(u, n);
96
147
  } catch (e) {
97
148
  console.error("[Sendystack] failed to load widget config:", e);
98
149
  return;
99
150
  }
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, {
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({
108
157
  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";
121
- let b = document.createElement("div");
122
- b.className = "ssk-inputrow", b.append(v, y);
158
+ text: m.welcomeMessage
159
+ }), d(y, v[0]);
160
+ let b = document.createElement("span");
161
+ b.className = "ssk-avatar", b.innerHTML = s;
123
162
  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
- }
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";
180
+ 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);
196
+ }, 2500);
197
+ function P(e) {
198
+ T.hidden = !e, e && (y.scrollTop = y.scrollHeight);
199
+ }
200
+ async function F() {
201
+ let e = E.value.trim();
202
+ if (!e) return;
203
+ E.value = "";
204
+ let t = {
205
+ role: "user",
206
+ text: e
207
+ };
208
+ v.push(t), d(y, t), f(n, v), D.disabled = !0, P(!0);
209
+ try {
210
+ let t = await (await fetch(`${u}/answer`, {
211
+ method: "POST",
212
+ headers: { "Content-Type": "application/json" },
213
+ body: JSON.stringify({
214
+ embedToken: n,
215
+ query: e
216
+ })
217
+ })).json(), r = {
218
+ role: "bot",
219
+ text: t.ok ? t.reply : t.message || "Sorry, I couldn't process that right now."
220
+ };
221
+ v.push(r), d(y, r);
222
+ } catch {
223
+ let e = {
224
+ role: "bot",
225
+ text: "I couldn't reach the server. Please try again."
226
+ };
227
+ v.push(e), d(y, e);
228
+ } finally {
229
+ P(!1), D.disabled = !1, f(n, v);
155
230
  }
156
231
  }
157
- y.onclick = C, v.addEventListener("keydown", (e) => {
158
- e.key === "Enter" && C();
159
- }), l(c, n), u(c, n);
232
+ D.onclick = F, E.addEventListener("keydown", (e) => {
233
+ e.key === "Enter" && !e.shiftKey && (e.preventDefault(), F());
234
+ }), h(u, n), g(u, n);
160
235
  }
161
- function f() {
236
+ function v() {
162
237
  let e = document.currentScript || Array.from(document.getElementsByTagName("script")).find((e) => /widget(\.global)?\.js/.test(e.src)), t = e?.dataset.token;
163
- t && d({
238
+ t && _({
164
239
  token: t,
165
240
  apiBase: e?.dataset.apiBase
166
241
  });
167
242
  }
168
- typeof document < "u" && f();
243
+ typeof document < "u" && v();
169
244
  //#endregion
170
- export { d as init };
245
+ export { _ as init };
@@ -1,20 +1,67 @@
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=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);
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-closebtn{flex:0 0 auto;width:30px;height:30px;border:none;border-radius:8px;background:transparent;color:#fff;
35
+ cursor:pointer;display:flex;align-items:center;justify-content:center;opacity:.9;}
36
+ .ssk-closebtn:hover{background:rgba(255,255,255,.18);}
37
+ .ssk-body{flex:1;overflow-y:auto;padding:16px 14px 8px;display:flex;flex-direction:column;gap:10px;
38
+ background:linear-gradient(180deg,#fff,#fbf6ef);min-height:0;}
39
+ .ssk-msg{display:flex;max-width:86%;}
40
+ .ssk-msg.bot{align-self:flex-start;} .ssk-msg.user{align-self:flex-end;}
41
+ .ssk-bubble-text{padding:10px 13px;border-radius:15px;font-size:14px;line-height:1.5;word-wrap:break-word;}
42
+ .ssk-msg.bot .ssk-bubble-text{background:#fff;border:1px solid rgba(27,39,51,.08);border-bottom-left-radius:5px;
43
+ box-shadow:0 5px 14px rgba(27,39,51,.05);color:#1b2733;}
44
+ .ssk-msg.user .ssk-bubble-text{background:linear-gradient(135deg,${e.accent2},${e.accent});color:#fff;border-bottom-right-radius:5px;}
45
+ .ssk-bubble-text a{color:${e.accent};font-weight:700;text-decoration:underline;}
46
+ .ssk-msg.user .ssk-bubble-text a{color:#fff;}
47
+ .ssk-bubble-text p{margin:0 0 7px;} .ssk-bubble-text p:last-child{margin:0;}
48
+ .ssk-typing{padding:0 14px 10px;flex-shrink:0;}
49
+ .ssk-typing[hidden]{display:none;}
50
+ .ssk-dots{display:inline-flex;gap:5px;padding:10px 13px;background:#fff;border:1px solid rgba(27,39,51,.08);
51
+ border-radius:15px;border-bottom-left-radius:5px;}
52
+ .ssk-dots i{width:7px;height:7px;border-radius:50%;background:${e.accent};animation:sskWave 1.2s ease-in-out infinite;}
53
+ .ssk-dots i:nth-child(2){animation-delay:.15s;} .ssk-dots i:nth-child(3){animation-delay:.3s;}
54
+ @keyframes sskWave{0%,60%,100%{transform:translateY(0);opacity:.5;}30%{transform:translateY(-5px);opacity:1;}}
55
+ .ssk-inputrow{display:flex;align-items:flex-end;gap:8px;padding:11px;border-top:1px solid rgba(27,39,51,.08);
56
+ background:#fff;flex-shrink:0;}
57
+ .ssk-inputrow textarea{flex:1;resize:none;max-height:90px;border:1px solid rgba(27,39,51,.14);border-radius:13px;
58
+ padding:10px 12px;font-size:14px;line-height:1.4;color:#1b2733;outline:none;background:#fbf7f1;}
59
+ .ssk-inputrow textarea:focus{border-color:${e.accent};background:#fff;}
60
+ .ssk-sendbtn{width:42px;height:42px;flex:0 0 auto;border:none;border-radius:12px;cursor:pointer;color:#fff;
61
+ background:linear-gradient(135deg,${e.accent2},${e.accent});display:flex;align-items:center;justify-content:center;
62
+ box-shadow:0 9px 20px rgba(0,0,0,.18);transition:transform .15s;}
63
+ .ssk-sendbtn:hover{transform:translateY(-2px);}
64
+ .ssk-sendbtn:disabled{opacity:.6;cursor:default;transform:none;}
65
+ .ssk-foot{text-align:center;font-size:10.5px;color:#9aa3ad;padding:0 8px 9px;background:#fff;flex-shrink:0;}
66
+ .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})({});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sendystack/widget",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
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"