@sendystack/widget 0.1.2 → 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 +74 -72
- package/dist/widget.global.js +13 -11
- package/package.json +1 -1
package/dist/index.es.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
//#region src/index.ts
|
|
2
|
-
var e = "sendystack-widget-root",
|
|
3
|
-
async function
|
|
2
|
+
var e = "https://app.sendystack.org/sendy_stack_favico.png", t = "sendystack-widget-root", n = (e) => `sendystack_sessions_${e}`, r = (e) => `sendystack_current_${e}`, i = (e) => `sendystack_crawled_${e}`, a = "<svg viewBox=\"0 0 24 24\" width=\"22\" height=\"22\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><path d=\"M6 6l12 12M18 6L6 18\"/></svg>", o = "<svg viewBox=\"0 0 24 24\" width=\"19\" height=\"19\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.9\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M4 12l16-7-7 16-2-7-7-2z\"/></svg>", s = "<svg viewBox=\"0 0 24 24\" width=\"18\" height=\"18\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.9\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"9\"/><path d=\"M12 7v5l3 3\"/></svg>", c = "<svg viewBox=\"0 0 24 24\" width=\"18\" height=\"18\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.9\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M12 5v14M5 12h14\"/></svg>", l = "<svg viewBox=\"0 0 24 24\" width=\"18\" height=\"18\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.9\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M15 18l-6-6 6-6\"/></svg>";
|
|
3
|
+
async function u(e, t) {
|
|
4
4
|
let n = await (await fetch(`${e}/embedConfig?embedToken=${encodeURIComponent(t)}`)).json();
|
|
5
5
|
if (!n.ok) throw Error(n.error || "failed to load widget config");
|
|
6
6
|
return {
|
|
@@ -8,37 +8,39 @@ async function d(e, t) {
|
|
|
8
8
|
whiteLabel: !!n.whiteLabel
|
|
9
9
|
};
|
|
10
10
|
}
|
|
11
|
-
function
|
|
12
|
-
let n =
|
|
11
|
+
function ee(e) {
|
|
12
|
+
let n = e.position === "left" ? "left" : "right", r = document.createElement("style");
|
|
13
13
|
r.textContent = `
|
|
14
|
-
#${
|
|
15
|
-
#${
|
|
16
|
-
.ssk-launcher{position:relative;width:60px;height:60px;border-radius:50%;border:none;cursor:pointer;color
|
|
17
|
-
background
|
|
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
|
-
|
|
23
|
-
.ssk-
|
|
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:
|
|
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
|
-
#${
|
|
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
|
-
#${
|
|
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,${
|
|
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
|
|
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;}
|
|
@@ -70,24 +72,24 @@ function f(t) {
|
|
|
70
72
|
.ssk-bubble-text{padding:10px 13px;border-radius:15px;font-size:14px;line-height:1.5;word-wrap:break-word;}
|
|
71
73
|
.ssk-msg.bot .ssk-bubble-text{background:#fff;border:1px solid rgba(27,39,51,.08);border-bottom-left-radius:5px;
|
|
72
74
|
box-shadow:0 5px 14px rgba(27,39,51,.05);color:#1b2733;}
|
|
73
|
-
.ssk-msg.user .ssk-bubble-text{background:linear-gradient(135deg,${
|
|
74
|
-
.ssk-bubble-text a{color:${
|
|
75
|
+
.ssk-msg.user .ssk-bubble-text{background:linear-gradient(135deg,${e.accent2},${e.accent});color:#fff;border-bottom-right-radius:5px;}
|
|
76
|
+
.ssk-bubble-text a{color:${e.accent};font-weight:700;text-decoration:underline;}
|
|
75
77
|
.ssk-msg.user .ssk-bubble-text a{color:#fff;}
|
|
76
78
|
.ssk-bubble-text p{margin:0 0 7px;} .ssk-bubble-text p:last-child{margin:0;}
|
|
77
79
|
.ssk-typing{padding:0 14px 10px;flex-shrink:0;}
|
|
78
80
|
.ssk-typing[hidden]{display:none;}
|
|
79
81
|
.ssk-dots{display:inline-flex;gap:5px;padding:10px 13px;background:#fff;border:1px solid rgba(27,39,51,.08);
|
|
80
82
|
border-radius:15px;border-bottom-left-radius:5px;}
|
|
81
|
-
.ssk-dots i{width:7px;height:7px;border-radius:50%;background:${
|
|
83
|
+
.ssk-dots i{width:7px;height:7px;border-radius:50%;background:${e.accent};animation:sskWave 1.2s ease-in-out infinite;}
|
|
82
84
|
.ssk-dots i:nth-child(2){animation-delay:.15s;} .ssk-dots i:nth-child(3){animation-delay:.3s;}
|
|
83
85
|
@keyframes sskWave{0%,60%,100%{transform:translateY(0);opacity:.5;}30%{transform:translateY(-5px);opacity:1;}}
|
|
84
86
|
.ssk-inputrow{display:flex;align-items:flex-end;gap:8px;padding:11px;border-top:1px solid rgba(27,39,51,.08);
|
|
85
87
|
background:#fff;flex-shrink:0;}
|
|
86
88
|
.ssk-inputrow textarea{flex:1;resize:none;max-height:90px;border:1px solid rgba(27,39,51,.14);border-radius:13px;
|
|
87
89
|
padding:10px 12px;font-size:14px;line-height:1.4;color:#1b2733;outline:none;background:#fbf7f1;}
|
|
88
|
-
.ssk-inputrow textarea:focus{border-color:${
|
|
90
|
+
.ssk-inputrow textarea:focus{border-color:${e.accent};background:#fff;}
|
|
89
91
|
.ssk-sendbtn{width:42px;height:42px;flex:0 0 auto;border:none;border-radius:12px;cursor:pointer;color:#fff;
|
|
90
|
-
background:linear-gradient(135deg,${
|
|
92
|
+
background:linear-gradient(135deg,${e.accent2},${e.accent});display:flex;align-items:center;justify-content:center;
|
|
91
93
|
box-shadow:0 9px 20px rgba(0,0,0,.18);transition:transform .15s;}
|
|
92
94
|
.ssk-sendbtn:hover{transform:translateY(-2px);}
|
|
93
95
|
.ssk-sendbtn:disabled{opacity:.6;cursor:default;transform:none;}
|
|
@@ -95,47 +97,47 @@ function f(t) {
|
|
|
95
97
|
.ssk-foot a{color:inherit;text-decoration:underline;}
|
|
96
98
|
`, document.head.appendChild(r);
|
|
97
99
|
}
|
|
98
|
-
function
|
|
100
|
+
function d(e) {
|
|
99
101
|
return `<p>${e.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").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
102
|
}
|
|
101
|
-
function
|
|
103
|
+
function f(e, t) {
|
|
102
104
|
let n = document.createElement("div");
|
|
103
105
|
n.className = `ssk-msg ${t.role}`;
|
|
104
106
|
let r = document.createElement("div");
|
|
105
|
-
r.className = "ssk-bubble-text", r.innerHTML =
|
|
107
|
+
r.className = "ssk-bubble-text", r.innerHTML = d(t.text), n.appendChild(r), e.appendChild(n), e.scrollTop = e.scrollHeight;
|
|
106
108
|
}
|
|
107
|
-
function
|
|
109
|
+
function p(e) {
|
|
108
110
|
try {
|
|
109
|
-
return JSON.parse(localStorage.getItem(
|
|
111
|
+
return JSON.parse(localStorage.getItem(n(e)) || "[]");
|
|
110
112
|
} catch {
|
|
111
113
|
return [];
|
|
112
114
|
}
|
|
113
115
|
}
|
|
114
|
-
function
|
|
116
|
+
function m(e, t) {
|
|
115
117
|
try {
|
|
116
|
-
localStorage.setItem(
|
|
118
|
+
localStorage.setItem(n(e), JSON.stringify(t.slice(0, 40)));
|
|
117
119
|
} catch {}
|
|
118
120
|
}
|
|
119
|
-
function
|
|
121
|
+
function h(e) {
|
|
120
122
|
try {
|
|
121
|
-
return localStorage.getItem(
|
|
123
|
+
return localStorage.getItem(r(e));
|
|
122
124
|
} catch {
|
|
123
125
|
return null;
|
|
124
126
|
}
|
|
125
127
|
}
|
|
126
|
-
function
|
|
128
|
+
function g(e, t) {
|
|
127
129
|
try {
|
|
128
|
-
localStorage.setItem(
|
|
130
|
+
localStorage.setItem(r(e), t);
|
|
129
131
|
} catch {}
|
|
130
132
|
}
|
|
131
|
-
function
|
|
133
|
+
function _() {
|
|
132
134
|
return `s_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
133
135
|
}
|
|
134
|
-
function
|
|
136
|
+
function v(e) {
|
|
135
137
|
let t = e.find((e) => e.role === "user");
|
|
136
138
|
return t ? t.text.slice(0, 60) : "New conversation";
|
|
137
139
|
}
|
|
138
|
-
function
|
|
140
|
+
function y(e) {
|
|
139
141
|
let t = new Date(e), n = /* @__PURE__ */ new Date();
|
|
140
142
|
return t.toDateString() === n.toDateString() ? t.toLocaleTimeString(void 0, {
|
|
141
143
|
hour: "numeric",
|
|
@@ -146,9 +148,9 @@ function te(e) {
|
|
|
146
148
|
});
|
|
147
149
|
}
|
|
148
150
|
function b() {
|
|
149
|
-
let
|
|
150
|
-
|
|
151
|
-
let n = (
|
|
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();
|
|
152
154
|
return {
|
|
153
155
|
title: document.title,
|
|
154
156
|
text: n
|
|
@@ -172,7 +174,7 @@ async function x(e, t) {
|
|
|
172
174
|
async function S(e, t) {
|
|
173
175
|
let n = !1;
|
|
174
176
|
try {
|
|
175
|
-
n = !!sessionStorage.getItem(
|
|
177
|
+
n = !!sessionStorage.getItem(i(t)), sessionStorage.setItem(i(t), "1");
|
|
176
178
|
} catch {}
|
|
177
179
|
n || await fetch(`${e}/crawlSite`, {
|
|
178
180
|
method: "POST",
|
|
@@ -180,51 +182,51 @@ async function S(e, t) {
|
|
|
180
182
|
body: JSON.stringify({ embedToken: t })
|
|
181
183
|
}).catch(() => {});
|
|
182
184
|
}
|
|
183
|
-
async function C(
|
|
184
|
-
if (typeof document > "u" || document.getElementById(
|
|
185
|
-
let
|
|
186
|
-
if (!
|
|
185
|
+
async function C(n) {
|
|
186
|
+
if (typeof document > "u" || document.getElementById(t)) return;
|
|
187
|
+
let r = (n.token || "").trim();
|
|
188
|
+
if (!r) {
|
|
187
189
|
console.error("[Sendystack] init() called without a token");
|
|
188
190
|
return;
|
|
189
191
|
}
|
|
190
|
-
let
|
|
192
|
+
let i = n.apiBase || "https://us-central1-sendystack-fab32.cloudfunctions.net", d;
|
|
191
193
|
try {
|
|
192
|
-
|
|
194
|
+
d = await u(i, r);
|
|
193
195
|
} catch (e) {
|
|
194
196
|
console.error("[Sendystack] failed to load widget config:", e);
|
|
195
197
|
return;
|
|
196
198
|
}
|
|
197
|
-
|
|
199
|
+
ee(d);
|
|
198
200
|
let b = document.createElement("div");
|
|
199
|
-
b.id =
|
|
200
|
-
let C =
|
|
201
|
+
b.id = t;
|
|
202
|
+
let C = p(r), w = C.length === 0, T = h(r), E = C.find((e) => e.id === T);
|
|
201
203
|
E || (E = {
|
|
202
|
-
id:
|
|
204
|
+
id: _(),
|
|
203
205
|
title: "New conversation",
|
|
204
206
|
messages: [],
|
|
205
207
|
updatedAt: Date.now()
|
|
206
|
-
}, C.unshift(E), T = E.id,
|
|
208
|
+
}, C.unshift(E), T = E.id, g(r, T), m(r, C));
|
|
207
209
|
let D = document.createElement("div");
|
|
208
210
|
D.className = "ssk-body";
|
|
209
211
|
function O() {
|
|
210
212
|
D.innerHTML = "";
|
|
211
213
|
let e = E.messages.length ? E.messages : [{
|
|
212
214
|
role: "bot",
|
|
213
|
-
text:
|
|
215
|
+
text: d.welcomeMessage
|
|
214
216
|
}];
|
|
215
|
-
for (let t of e)
|
|
217
|
+
for (let t of e) f(D, t);
|
|
216
218
|
}
|
|
217
219
|
O();
|
|
218
220
|
let k = document.createElement("span");
|
|
219
|
-
k.className = "ssk-avatar", k.innerHTML =
|
|
221
|
+
k.className = "ssk-avatar", k.innerHTML = `<img src="${e}" alt="" />`;
|
|
220
222
|
let A = document.createElement("div");
|
|
221
|
-
A.className = "ssk-id-text", A.innerHTML = `<strong>${
|
|
223
|
+
A.className = "ssk-id-text", A.innerHTML = `<strong>${d.assistantName}</strong><span class="ssk-status"><span class="ssk-dot"></span>Online</span>`;
|
|
222
224
|
let j = document.createElement("div");
|
|
223
225
|
j.className = "ssk-id", j.append(k, A);
|
|
224
226
|
let M = document.createElement("button");
|
|
225
|
-
M.className = "ssk-iconbtn", M.innerHTML =
|
|
227
|
+
M.className = "ssk-iconbtn", M.innerHTML = s, M.setAttribute("aria-label", "Chat history");
|
|
226
228
|
let N = document.createElement("button");
|
|
227
|
-
N.className = "ssk-iconbtn", N.innerHTML =
|
|
229
|
+
N.className = "ssk-iconbtn", N.innerHTML = c, N.setAttribute("aria-label", "New chat");
|
|
228
230
|
let P = document.createElement("button");
|
|
229
231
|
P.className = "ssk-closebtn", P.innerHTML = a, P.setAttribute("aria-label", "Close chat");
|
|
230
232
|
let F = document.createElement("div");
|
|
@@ -232,7 +234,7 @@ async function C(t) {
|
|
|
232
234
|
let I = document.createElement("div");
|
|
233
235
|
I.className = "ssk-head", I.append(j, F);
|
|
234
236
|
let L = document.createElement("button");
|
|
235
|
-
L.className = "ssk-history-back", L.innerHTML =
|
|
237
|
+
L.className = "ssk-history-back", L.innerHTML = l, L.setAttribute("aria-label", "Back to chat");
|
|
236
238
|
let R = document.createElement("h4");
|
|
237
239
|
R.textContent = "Chat history";
|
|
238
240
|
let z = document.createElement("div");
|
|
@@ -249,8 +251,8 @@ async function C(t) {
|
|
|
249
251
|
}
|
|
250
252
|
for (let e of C) {
|
|
251
253
|
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">${
|
|
253
|
-
E = e, T = e.id,
|
|
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");
|
|
254
256
|
}, B.appendChild(t);
|
|
255
257
|
}
|
|
256
258
|
}
|
|
@@ -258,11 +260,11 @@ async function C(t) {
|
|
|
258
260
|
H(), V.classList.add("shown");
|
|
259
261
|
}, L.onclick = () => V.classList.remove("shown"), N.onclick = () => {
|
|
260
262
|
E.messages.length && (E = {
|
|
261
|
-
id:
|
|
263
|
+
id: _(),
|
|
262
264
|
title: "New conversation",
|
|
263
265
|
messages: [],
|
|
264
266
|
updatedAt: Date.now()
|
|
265
|
-
}, C.unshift(E), T = E.id,
|
|
267
|
+
}, C.unshift(E), T = E.id, g(r, T), m(r, C), O(), V.classList.remove("shown"));
|
|
266
268
|
};
|
|
267
269
|
let U = document.createElement("div");
|
|
268
270
|
U.className = "ssk-typing", U.hidden = !0, U.innerHTML = "<span class=\"ssk-dots\"><i></i><i></i><i></i></span>";
|
|
@@ -273,22 +275,22 @@ async function C(t) {
|
|
|
273
275
|
let K = document.createElement("div");
|
|
274
276
|
K.className = "ssk-inputrow", K.append(W, G);
|
|
275
277
|
let q = document.createElement("div");
|
|
276
|
-
q.className = "ssk-foot", q.textContent =
|
|
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";
|
|
277
279
|
let J = document.createElement("div");
|
|
278
280
|
J.className = "ssk-panel", J.append(I, D, U, K, q, V);
|
|
279
281
|
let Y = document.createElement("button");
|
|
280
|
-
Y.className = "ssk-launcher", Y.setAttribute("aria-label", "Open chat"), Y.innerHTML = `<span class="ssk-open"
|
|
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>`;
|
|
281
283
|
let X = document.createElement("div");
|
|
282
|
-
X.className = "ssk-greeting", X.textContent =
|
|
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(() => {
|
|
283
285
|
b.classList.contains("open") || X.classList.add("shown"), setTimeout(() => X.classList.remove("shown"), 6e3);
|
|
284
286
|
}, 2500);
|
|
285
287
|
function Z(e) {
|
|
286
288
|
U.hidden = !e, e && (D.scrollTop = D.scrollHeight);
|
|
287
289
|
}
|
|
288
290
|
function Q() {
|
|
289
|
-
E.updatedAt = Date.now(), E.title =
|
|
291
|
+
E.updatedAt = Date.now(), E.title = v(E.messages);
|
|
290
292
|
let e = C.findIndex((e) => e.id === E.id);
|
|
291
|
-
e >= 0 && C.splice(e, 1), C.unshift(E),
|
|
293
|
+
e >= 0 && C.splice(e, 1), C.unshift(E), m(r, C);
|
|
292
294
|
}
|
|
293
295
|
async function $() {
|
|
294
296
|
let e = W.value.trim();
|
|
@@ -298,33 +300,33 @@ async function C(t) {
|
|
|
298
300
|
role: "user",
|
|
299
301
|
text: e
|
|
300
302
|
};
|
|
301
|
-
E.messages.push(t),
|
|
303
|
+
E.messages.push(t), f(D, t), Q(), G.disabled = !0, Z(!0);
|
|
302
304
|
try {
|
|
303
|
-
let t = await (await fetch(`${
|
|
305
|
+
let t = await (await fetch(`${i}/answer`, {
|
|
304
306
|
method: "POST",
|
|
305
307
|
headers: { "Content-Type": "application/json" },
|
|
306
308
|
body: JSON.stringify({
|
|
307
|
-
embedToken:
|
|
309
|
+
embedToken: r,
|
|
308
310
|
query: e
|
|
309
311
|
})
|
|
310
|
-
})).json(),
|
|
312
|
+
})).json(), n = {
|
|
311
313
|
role: "bot",
|
|
312
314
|
text: t.ok ? t.reply : t.message || "Sorry, I couldn't process that right now."
|
|
313
315
|
};
|
|
314
|
-
E.messages.push(
|
|
316
|
+
E.messages.push(n), f(D, n);
|
|
315
317
|
} catch {
|
|
316
318
|
let e = {
|
|
317
319
|
role: "bot",
|
|
318
320
|
text: "I couldn't reach the server. Please try again."
|
|
319
321
|
};
|
|
320
|
-
E.messages.push(e),
|
|
322
|
+
E.messages.push(e), f(D, e);
|
|
321
323
|
} finally {
|
|
322
324
|
Z(!1), G.disabled = !1, Q();
|
|
323
325
|
}
|
|
324
326
|
}
|
|
325
327
|
G.onclick = $, W.addEventListener("keydown", (e) => {
|
|
326
328
|
e.key === "Enter" && !e.shiftKey && (e.preventDefault(), $());
|
|
327
|
-
}), x(
|
|
329
|
+
}), x(i, r), S(i, r);
|
|
328
330
|
}
|
|
329
331
|
function w() {
|
|
330
332
|
let e = document.currentScript || Array.from(document.getElementsByTagName("script")).find((e) => /widget(\.global)?\.js/.test(e.src)), t = e?.dataset.token;
|
package/dist/widget.global.js
CHANGED
|
@@ -1,32 +1,34 @@
|
|
|
1
|
-
var Sendystack=(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var t=`https://us-central1-sendystack-fab32.cloudfunctions.net`,n=`sendystack-widget-root`,
|
|
2
|
-
#${
|
|
3
|
-
#${
|
|
4
|
-
.ssk-launcher{position:relative;width:60px;height:60px;border-radius:50%;border:none;cursor:pointer;color
|
|
5
|
-
background
|
|
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
|
-
|
|
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:
|
|
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
|
-
#${
|
|
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
|
-
#${
|
|
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
|
|
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;}
|
|
@@ -81,4 +83,4 @@ var Sendystack=(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:`M
|
|
|
81
83
|
.ssk-sendbtn:disabled{opacity:.6;cursor:default;transform:none;}
|
|
82
84
|
.ssk-foot{text-align:center;font-size:10.5px;color:#9aa3ad;padding:0 8px 9px;background:#fff;flex-shrink:0;}
|
|
83
85
|
.ssk-foot a{color:inherit;text-decoration:underline;}
|
|
84
|
-
`,document.head.appendChild(
|
|
86
|
+
`,document.head.appendChild(n)}function h(e){return`<p>${e.replace(/&/g,`&`).replace(/</g,`<`).replace(/>/g,`>`).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.
|
|
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",
|