@locdo.tech/botiq-chat-sdk 0.4.0 → 0.5.0

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.
@@ -1,943 +0,0 @@
1
- //#region src/core/config.ts
2
- var e = "https://bot-q-backend.vercel.app", t = {
3
- colors: {
4
- primary: "#F97316",
5
- header: "#F97316",
6
- userBubble: "#F97316",
7
- botBubble: "#1A1A1A",
8
- background: "#000000",
9
- text: "#FFFFFF",
10
- inputBackground: "#1A1A1A"
11
- },
12
- layout: {
13
- position: "bottom-right",
14
- buttonShape: "circle",
15
- width: 360,
16
- height: 520
17
- },
18
- content: {
19
- greeting: "",
20
- suggestionChips: [],
21
- placeholder: ""
22
- },
23
- font: "inter"
24
- }, n = {
25
- name: "BotIQ",
26
- design: t,
27
- widgetLanguage: "vi"
28
- }, r = /^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})$/, i = new Set(["bottom-right", "bottom-left"]), a = new Set(["circle", "square"]), o = new Set([
29
- "inter",
30
- "plus-jakarta",
31
- "poppins",
32
- "nunito",
33
- "dm-sans",
34
- "raleway",
35
- "lato",
36
- "playfair"
37
- ]), s = new Set([
38
- "none",
39
- "fade",
40
- "slide-up",
41
- "bounce"
42
- ]), c = new Set([
43
- "dots-bounce",
44
- "dots-pulse",
45
- "bar"
46
- ]);
47
- function l(e) {
48
- let t = e.colors;
49
- if (t.inputBackground ||= "#1A1A1A", ![
50
- t.primary,
51
- t.header,
52
- t.userBubble,
53
- t.botBubble,
54
- t.background,
55
- t.text,
56
- t.inputBackground
57
- ].every((e) => typeof e == "string" && r.test(e)) || !i.has(e.layout.position) || !a.has(e.layout.buttonShape) || !o.has(e.font)) return !1;
58
- let { width: n, height: l } = e.layout;
59
- if (!Number.isFinite(n) || n < 280 || n > 800 || !Number.isFinite(l) || l < 200 || l > 900) return !1;
60
- if (e.gradient) {
61
- let t = e.gradient;
62
- if (t.type !== "linear" && t.type !== "radial" || !r.test(t.from) || !r.test(t.to) || t.angle !== void 0 && (!Number.isFinite(t.angle) || t.angle < 0 || t.angle > 360)) return !1;
63
- }
64
- return !(e.animation && (!s.has(e.animation.bubbleOpen) || !c.has(e.animation.typingIndicator)) || e.customCSS !== void 0 && (typeof e.customCSS != "string" || e.customCSS.length > 8e3));
65
- }
66
- var u = 8e3;
67
- function d(e, t) {
68
- return fetch(`${t}/widget/meta`, {
69
- headers: { "X-Api-Key": e },
70
- referrerPolicy: "no-referrer-when-downgrade",
71
- signal: AbortSignal.timeout(u)
72
- });
73
- }
74
- async function f(e, r) {
75
- let i;
76
- try {
77
- i = await d(e, r);
78
- } catch {
79
- try {
80
- i = await d(e, r);
81
- } catch {
82
- return n;
83
- }
84
- }
85
- if (i.status === 401 || i.status === 403) return null;
86
- if (!i.ok) return n;
87
- let a;
88
- try {
89
- a = await i.json();
90
- } catch {
91
- return n;
92
- }
93
- let o = typeof a.name == "string" && a.name.length > 0 ? a.name : n.name, s = a.widgetLanguage === "en" ? "en" : "vi";
94
- return !a.design?.colors || !a.design?.layout || !a.design?.content || !l(a.design) ? {
95
- name: o,
96
- design: t,
97
- widgetLanguage: s
98
- } : {
99
- name: o,
100
- design: a.design,
101
- widgetLanguage: s
102
- };
103
- }
104
- function p(t) {
105
- return {
106
- apiKey: t.apiKey,
107
- apiUrl: e
108
- };
109
- }
110
- //#endregion
111
- //#region src/core/session.ts
112
- var m = "botiq:sessionId", h = "botiq:sessionId:", g = "botiq:history:", _ = 10;
113
- function v() {
114
- try {
115
- let e = localStorage.getItem(m);
116
- localStorage.removeItem(m), e && localStorage.removeItem(g + e);
117
- } catch {}
118
- }
119
- function y(e) {
120
- try {
121
- let t = h + e, n = localStorage.getItem(t);
122
- return n || (n = crypto.randomUUID(), localStorage.setItem(t, n)), n;
123
- } catch {
124
- return crypto.randomUUID();
125
- }
126
- }
127
- function b(e, t) {
128
- try {
129
- let n = localStorage.getItem(`${g}${e}:${t}`);
130
- return n ? JSON.parse(n) : [];
131
- } catch {
132
- return [];
133
- }
134
- }
135
- function x(e, t, n) {
136
- try {
137
- let r = [...b(e, t), ...n].slice(-_);
138
- localStorage.setItem(`${g}${e}:${t}`, JSON.stringify(r));
139
- } catch {}
140
- }
141
- //#endregion
142
- //#region src/core/api.ts
143
- async function S(e, t, n, r, i, a) {
144
- try {
145
- let o = await fetch(`${e}/widget/chat`, {
146
- method: "POST",
147
- headers: {
148
- "Content-Type": "application/json",
149
- "X-Api-Key": t
150
- },
151
- referrerPolicy: "no-referrer-when-downgrade",
152
- body: JSON.stringify({
153
- sessionId: n,
154
- message: r,
155
- history: i
156
- })
157
- });
158
- return o.ok ? (await o.json()).reply || a.errorMessage : o.status === 401 ? a.errorAuth : o.status === 403 ? a.errorForbidden : o.status === 429 ? a.errorQuota : a.errorGeneric;
159
- } catch {
160
- return a.errorMessage;
161
- }
162
- }
163
- var C = {
164
- messages: [],
165
- hasMore: !1
166
- };
167
- function w(e) {
168
- if (!e || typeof e != "object") return !1;
169
- let t = e;
170
- return typeof t.id == "string" && (t.role === "user" || t.role === "assistant") && typeof t.content == "string";
171
- }
172
- async function T(e, t, n, r, i) {
173
- try {
174
- let a = new URLSearchParams({
175
- sessionId: n,
176
- limit: String(i)
177
- });
178
- r && a.set("before", r);
179
- let o = await fetch(`${e}/widget/messages?${a.toString()}`, {
180
- method: "GET",
181
- headers: { "X-Api-Key": t },
182
- referrerPolicy: "no-referrer-when-downgrade"
183
- });
184
- if (!o.ok) return C;
185
- let s = await o.json();
186
- return {
187
- messages: Array.isArray(s.messages) ? s.messages.filter(w) : [],
188
- hasMore: s.hasMore === !0
189
- };
190
- } catch {
191
- return C;
192
- }
193
- }
194
- function ee(e, t, n, r) {
195
- return T(e, t, n, void 0, r);
196
- }
197
- function te(e, t, n, r, i) {
198
- return T(e, t, n, r, i);
199
- }
200
- //#endregion
201
- //#region src/core/state.ts
202
- var E = {
203
- messages: [],
204
- isLoading: !1,
205
- isOpen: !1,
206
- loadingOlder: !1
207
- }, D = /* @__PURE__ */ new Set();
208
- function O() {
209
- return E;
210
- }
211
- function k(e) {
212
- Object.assign(E, e);
213
- let t = {
214
- ...E,
215
- messages: [...E.messages]
216
- };
217
- D.forEach((e) => e(t));
218
- }
219
- function A(e) {
220
- return D.add(e), () => D.delete(e);
221
- }
222
- //#endregion
223
- //#region src/core/styles.ts
224
- var j = {
225
- inter: {
226
- url: "https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap",
227
- family: "'Inter', system-ui, -apple-system, sans-serif"
228
- },
229
- "plus-jakarta": {
230
- url: "https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600&display=swap",
231
- family: "'Plus Jakarta Sans', system-ui, -apple-system, sans-serif"
232
- },
233
- nunito: {
234
- url: "https://fonts.googleapis.com/css2?family=Nunito:wght@400;500;600&display=swap",
235
- family: "'Nunito', system-ui, -apple-system, sans-serif"
236
- },
237
- raleway: {
238
- url: "https://fonts.googleapis.com/css2?family=Raleway:wght@400;500;600&display=swap",
239
- family: "'Raleway', system-ui, -apple-system, sans-serif"
240
- },
241
- playfair: {
242
- url: "https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;500;600&display=swap",
243
- family: "'Playfair Display', Georgia, serif"
244
- }
245
- };
246
- function M(e, t) {
247
- if (!t) return e;
248
- let n = t.angle ?? 135;
249
- return t.type === "linear" ? `linear-gradient(${n}deg, ${t.from}, ${t.to})` : `radial-gradient(circle, ${t.from}, ${t.to})`;
250
- }
251
- function ne(e) {
252
- return e.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\\[0-9a-fA-F]{1,6}\s?/g, "x").replace(/@import\b[^;]*;?/gi, "").replace(/url\s*\(\s*["']?\s*(?!data:)[^)]*["']?\s*\)/gi, "url(\"\")").replace(/expression\s*\(/gi, "expression_(").replace(/javascript\s*:/gi, "blocked:").replace(/-moz-binding\s*:/gi, "").replace(/\bbehavior\s*:/gi, "");
253
- }
254
- function N(e) {
255
- let t = e?.bubbleOpen ?? "none", n = e?.typingIndicator ?? "dots-bounce";
256
- return [t === "fade" ? "@keyframes biq-open-fade { from { opacity: 0; } to { opacity: 1; } }\n.chat-window.open { animation: biq-open-fade .22s ease forwards; }" : t === "slide-up" ? "@keyframes biq-open-slide { from { opacity: 0; transform: scale(1) translateY(20px); } to { opacity: 1; transform: scale(1) translateY(0); } }\n.chat-window.open { animation: biq-open-slide .25s cubic-bezier(.22,1,.36,1) forwards; }" : t === "bounce" ? "@keyframes biq-open-bounce { 0% { opacity: 0; transform: scale(.85) translateY(8px); } 60% { transform: scale(1.03) translateY(-3px); } 100% { opacity: 1; transform: scale(1) translateY(0); } }\n.chat-window.open { animation: biq-open-bounce .35s cubic-bezier(.34,1.56,.64,1) forwards; }" : "", n === "dots-pulse" ? "@keyframes biq-pulse { 0%, 100% { opacity: 0.3; transform: scale(1); } 50% { opacity: 1; transform: scale(1.3); } }\n.typing span { animation: biq-pulse 1.2s infinite ease-in-out; }" : n === "bar" ? ".typing { gap: 3px; align-items: flex-end; }\n.typing span { width: 3px; height: 14px; border-radius: 2px; animation: biq-bar 1s infinite ease-in-out; }\n.typing span:nth-child(1) { animation-delay: 0s; }\n.typing span:nth-child(2) { animation-delay: .15s; height: 20px; }\n.typing span:nth-child(3) { animation-delay: .3s; }\n@keyframes biq-bar { 0%, 100% { transform: scaleY(.4); opacity: .5; } 50% { transform: scaleY(1); opacity: 1; } }" : ""].filter(Boolean).join("\n");
257
- }
258
- function P(e) {
259
- let { colors: t, layout: n, font: r, gradient: i, animation: a, customCSS: o, whiteLabel: s } = e, c = j[r] ?? j.inter, l = n.buttonShape === "square" ? "14px" : "50%", u = n.position === "bottom-left" ? "right: auto; left: 24px;" : "right: 24px;", d = n.position === "bottom-left" ? "bottom left" : "bottom right", f = n.position === "bottom-left" ? "right: auto; left: 0;" : "right: 0;", p = M(t.primary, i), m = M(t.header, i), h = M(t.userBubble, i), g = s ? ".botiq-badge { display: none !important; }" : "";
260
- return `
261
- @import url('${c.url}');
262
-
263
- :host {
264
- position: fixed;
265
- bottom: 24px;
266
- ${u}
267
- z-index: 2147483647;
268
- display: block;
269
- font-family: ${c.family};
270
- --color-primary: ${t.primary};
271
- --color-header: ${t.header};
272
- --color-user: ${t.userBubble};
273
- --color-bot: ${t.botBubble};
274
- --color-bg: ${t.background};
275
- --color-text: ${t.text};
276
- --color-input-bg: ${t.inputBackground};
277
- --gray-50: #1A1A1A;
278
- --gray-100: #222222;
279
- --gray-200: #2D2D2D;
280
- --gray-400: #9CA3AF;
281
- --shadow-sm: 0 2px 8px rgba(0,0,0,.4);
282
- --shadow-md: 0 8px 32px rgba(0,0,0,.5);
283
- --shadow-lg: 0 16px 48px rgba(0,0,0,.6);
284
- --radius-bubble: ${l};
285
- --radius-msg: 18px;
286
- --chat-w: ${n.width}px;
287
- --chat-h: ${n.height}px;
288
- }
289
-
290
- *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
291
-
292
- /* ── Bubble ─────────────────────────────── */
293
- .bubble {
294
- width: 56px;
295
- height: 56px;
296
- border-radius: var(--radius-bubble);
297
- background: ${p};
298
- border: none;
299
- cursor: pointer;
300
- display: flex;
301
- align-items: center;
302
- justify-content: center;
303
- box-shadow: var(--shadow-md);
304
- transition: transform .2s ease, box-shadow .2s ease;
305
- outline: none;
306
- }
307
-
308
- .bubble:hover { transform: scale(1.08); box-shadow: var(--shadow-lg); }
309
- .bubble:active { transform: scale(0.96); }
310
-
311
- .bubble svg { width: 26px; height: 26px; fill: #fff; transition: opacity .15s; }
312
- .bubble .icon-chat { opacity: 1; }
313
- .bubble .icon-close { opacity: 0; position: absolute; }
314
- .bubble.open .icon-chat { opacity: 0; }
315
- .bubble.open .icon-close { opacity: 1; }
316
-
317
- /* ── Chat Window ────────────────────────── */
318
- .chat-window {
319
- position: absolute;
320
- bottom: 72px;
321
- ${f}
322
- width: var(--chat-w);
323
- height: var(--chat-h);
324
- background: var(--color-bg);
325
- border-radius: 20px;
326
- box-shadow: var(--shadow-lg);
327
- display: flex;
328
- flex-direction: column;
329
- overflow: hidden;
330
- transform-origin: ${d};
331
- transform: scale(.94) translateY(8px);
332
- opacity: 0;
333
- pointer-events: none;
334
- transition: transform .22s cubic-bezier(.34,1.56,.64,1), opacity .18s ease;
335
- }
336
-
337
- .chat-window.open { transform: scale(1) translateY(0); opacity: 1; pointer-events: all; }
338
-
339
- /* ── Header ─────────────────────────────── */
340
- .chat-header {
341
- background: ${m};
342
- padding: 16px 16px 14px;
343
- display: flex;
344
- align-items: center;
345
- gap: 10px;
346
- flex-shrink: 0;
347
- }
348
-
349
- .avatar {
350
- width: 36px;
351
- height: 36px;
352
- border-radius: 50%;
353
- background: rgba(255,255,255,.2);
354
- display: flex;
355
- align-items: center;
356
- justify-content: center;
357
- flex-shrink: 0;
358
- overflow: hidden;
359
- }
360
-
361
- .avatar svg { width: 20px; height: 20px; fill: #fff; }
362
-
363
- .header-text { flex: 1; min-width: 0; }
364
-
365
- .bot-name {
366
- color: #fff;
367
- font-weight: 600;
368
- font-size: 15px;
369
- line-height: 1.2;
370
- display: block;
371
- white-space: nowrap;
372
- overflow: hidden;
373
- text-overflow: ellipsis;
374
- }
375
-
376
- .bot-status { color: rgba(255,255,255,.75); font-size: 12px; display: block; margin-top: 1px; }
377
-
378
- .close-btn {
379
- width: 30px;
380
- height: 30px;
381
- border-radius: 50%;
382
- background: rgba(255,255,255,.15);
383
- border: none;
384
- cursor: pointer;
385
- color: #fff;
386
- font-size: 18px;
387
- display: flex;
388
- align-items: center;
389
- justify-content: center;
390
- flex-shrink: 0;
391
- transition: background .15s;
392
- }
393
- .close-btn:hover { background: rgba(255,255,255,.25); }
394
-
395
- /* ── Messages ───────────────────────────── */
396
- .messages {
397
- flex: 1;
398
- overflow-y: auto;
399
- padding: 16px 14px;
400
- display: flex;
401
- flex-direction: column;
402
- gap: 10px;
403
- scroll-behavior: smooth;
404
- }
405
-
406
- .messages::-webkit-scrollbar { width: 4px; }
407
- .messages::-webkit-scrollbar-track { background: transparent; }
408
- .messages::-webkit-scrollbar-thumb { background: var(--gray-200); border-radius: 2px; }
409
-
410
- .message { display: flex; flex-direction: column; max-width: 82%; }
411
- .message.user { align-self: flex-end; align-items: flex-end; }
412
- .message.assistant { align-self: flex-start; align-items: flex-start; max-width: 92%; }
413
-
414
- .message-bubble {
415
- padding: 10px 14px;
416
- border-radius: var(--radius-msg);
417
- font-size: 14px;
418
- line-height: 1.5;
419
- white-space: normal;
420
- word-break: break-word;
421
- overflow-x: auto;
422
- }
423
-
424
- /* ── Markdown inline elements ───────────────────────── */
425
- .message-bubble strong { font-weight: 600; }
426
- .message-bubble em { font-style: italic; }
427
- .message-bubble a { color: var(--color-primary); text-decoration: underline; word-break: break-all; }
428
- .message-bubble code {
429
- font-family: monospace;
430
- font-size: 12px;
431
- padding: 1px 5px;
432
- border-radius: 4px;
433
- background: rgba(255,255,255,.1);
434
- white-space: pre-wrap;
435
- }
436
- .message-bubble del { text-decoration: line-through; opacity: .7; }
437
-
438
- /* ── Markdown headings ───────────────────────────────── */
439
- /* Kept modest — the chat bubble is small, so headings are only slightly
440
- larger than body text, never document-sized. */
441
- .message-bubble h1,
442
- .message-bubble h2,
443
- .message-bubble h3,
444
- .message-bubble h4,
445
- .message-bubble h5,
446
- .message-bubble h6 {
447
- margin: 8px 0 4px;
448
- font-weight: 600;
449
- line-height: 1.3;
450
- color: #fff;
451
- }
452
- .message-bubble h1 { font-size: 16px; }
453
- .message-bubble h2 { font-size: 15px; }
454
- .message-bubble h3 { font-size: 14px; }
455
- .message-bubble h4,
456
- .message-bubble h5,
457
- .message-bubble h6 { font-size: 13px; }
458
- .message-bubble :first-child { margin-top: 0; }
459
-
460
- /* ── Markdown horizontal rule ────────────────────────── */
461
- .message-bubble hr {
462
- border: 0;
463
- height: 1px;
464
- background: rgba(255,255,255,.15);
465
- margin: 8px 0;
466
- }
467
-
468
- /* ── Markdown tables ─────────────────────────────────── */
469
- .message-bubble table {
470
- border-collapse: collapse;
471
- min-width: 100%;
472
- font-size: 12.5px;
473
- display: block;
474
- overflow-x: auto;
475
- margin: 4px 0;
476
- }
477
- .message-bubble th,
478
- .message-bubble td {
479
- border: 1px solid rgba(255,255,255,.15);
480
- padding: 5px 10px;
481
- text-align: left;
482
- }
483
- .message-bubble th { background: rgba(255,255,255,.08); font-weight: 600; white-space: nowrap; }
484
- .message-bubble td { white-space: normal; }
485
- .message-bubble tr:nth-child(even) td { background: rgba(255,255,255,.04); }
486
-
487
- /* ── Markdown lists ──────────────────────────────────── */
488
- .message-bubble ul,
489
- .message-bubble ol { padding-left: 18px; margin: 2px 0; }
490
- .message-bubble li { margin: 2px 0; }
491
-
492
- .message.user .message-bubble {
493
- background: ${h};
494
- color: #fff;
495
- border-bottom-right-radius: 4px;
496
- white-space: pre-wrap;
497
- }
498
-
499
- .message.assistant .message-bubble {
500
- background: var(--color-bot);
501
- color: var(--color-text);
502
- border-bottom-left-radius: 4px;
503
- }
504
-
505
- /* ── Typing Indicator ───────────────────── */
506
- .typing {
507
- display: flex;
508
- align-items: center;
509
- gap: 4px;
510
- padding: 12px 14px;
511
- background: var(--color-bot);
512
- border-radius: var(--radius-msg);
513
- border-bottom-left-radius: 4px;
514
- align-self: flex-start;
515
- width: fit-content;
516
- }
517
-
518
- .typing span {
519
- width: 7px;
520
- height: 7px;
521
- border-radius: 50%;
522
- background: var(--color-primary);
523
- opacity: 0.5;
524
- animation: bounce 1.2s infinite ease-in-out;
525
- }
526
-
527
- .typing span:nth-child(2) { animation-delay: .2s; }
528
- .typing span:nth-child(3) { animation-delay: .4s; }
529
-
530
- @keyframes bounce {
531
- 0%, 60%, 100% { transform: translateY(0); }
532
- 30% { transform: translateY(-6px); }
533
- }
534
-
535
- /* ── Empty / Greeting State ─────────────── */
536
- .empty-state {
537
- flex: 1;
538
- display: flex;
539
- flex-direction: column;
540
- align-items: center;
541
- justify-content: center;
542
- gap: 8px;
543
- color: var(--gray-400);
544
- font-size: 13px;
545
- text-align: center;
546
- padding: 24px;
547
- }
548
-
549
- .empty-state svg { width: 40px; height: 40px; fill: var(--gray-200); margin-bottom: 4px; }
550
- .empty-state .greeting { color: var(--color-text); font-size: 13px; opacity: 0.7; }
551
-
552
- /* ── Suggestion Chips ───────────────────── */
553
- .chips {
554
- display: flex;
555
- flex-wrap: wrap;
556
- gap: 6px;
557
- justify-content: center;
558
- margin-top: 4px;
559
- }
560
-
561
- .chip {
562
- padding: 5px 12px;
563
- border-radius: 16px;
564
- border: 1.5px solid var(--color-primary);
565
- color: var(--color-primary);
566
- background: transparent;
567
- font-size: 12px;
568
- font-family: inherit;
569
- cursor: pointer;
570
- transition: background .15s, color .15s;
571
- }
572
-
573
- .chip:hover {
574
- background: var(--color-primary);
575
- color: #fff;
576
- }
577
-
578
- /* ── Footer / Input ─────────────────────── */
579
- .chat-footer {
580
- border-top: 1px solid var(--gray-100);
581
- padding: 10px 12px;
582
- background: var(--color-bg);
583
- flex-shrink: 0;
584
- }
585
-
586
- .input-row { display: flex; align-items: flex-end; gap: 8px; }
587
-
588
- /* Subtle attribution — half-opacity, hover lifts to full. Doesn't steal focus
589
- from tenant branding but reminds the customer this is powered by BotIQ. */
590
- .botiq-badge {
591
- display: block;
592
- text-align: center;
593
- font-size: 10px;
594
- font-weight: 400;
595
- color: var(--color-text);
596
- opacity: 0.4;
597
- text-decoration: none;
598
- margin-top: 6px;
599
- letter-spacing: 0.02em;
600
- transition: opacity .15s;
601
- }
602
- .botiq-badge:hover { opacity: 0.85; }
603
- .botiq-badge-name { font-weight: 600; color: var(--color-primary); }
604
-
605
- .input {
606
- flex: 1;
607
- min-height: 38px;
608
- max-height: 100px;
609
- border: 1.5px solid var(--gray-200);
610
- border-radius: 12px;
611
- padding: 9px 12px;
612
- font-family: inherit;
613
- font-size: 14px;
614
- color: var(--color-text);
615
- resize: none;
616
- outline: none;
617
- background: var(--color-input-bg);
618
- line-height: 1.4;
619
- overflow-y: auto;
620
- transition: border-color .15s;
621
- }
622
-
623
- .input:focus { border-color: var(--color-primary); background: var(--color-input-bg); }
624
- .input::placeholder { color: var(--gray-400); }
625
-
626
- .send-btn {
627
- width: 38px;
628
- height: 38px;
629
- border-radius: 10px;
630
- background: ${p};
631
- border: none;
632
- cursor: pointer;
633
- display: flex;
634
- align-items: center;
635
- justify-content: center;
636
- flex-shrink: 0;
637
- transition: filter .15s, transform .1s;
638
- }
639
-
640
- .send-btn:hover { filter: brightness(0.88); }
641
- .send-btn:active { transform: scale(0.94); }
642
- .send-btn:disabled { background: var(--gray-200); cursor: not-allowed; filter: none; }
643
- .send-btn svg { width: 18px; height: 18px; fill: #fff; }
644
-
645
- ${g}
646
- ${N(a)}
647
- ${o ? ne(o) : ""}
648
- `;
649
- }
650
- //#endregion
651
- //#region src/core/assets.ts
652
- var F = "data:image/png;base64,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";
653
- //#endregion
654
- //#region src/core/history-pager.ts
655
- function I() {
656
- return {
657
- oldestCursor: null,
658
- hasMore: !1,
659
- cursorReady: !1
660
- };
661
- }
662
- function L(e) {
663
- return {
664
- oldestCursor: e.messages[0]?.id ?? null,
665
- hasMore: e.hasMore,
666
- cursorReady: e.messages.length > 0
667
- };
668
- }
669
- function R(e, t) {
670
- return {
671
- oldestCursor: t.messages[0]?.id ?? e.oldestCursor,
672
- hasMore: t.hasMore,
673
- cursorReady: e.cursorReady
674
- };
675
- }
676
- function z(e, t) {
677
- return e.cursorReady && e.hasMore && !t && e.oldestCursor !== null;
678
- }
679
- function B(e, t, n) {
680
- return e + (n - t);
681
- }
682
- //#endregion
683
- //#region src/core/ui.ts
684
- var V = "https://bot-q-frontend.vercel.app/", H = "<svg viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M20 2H4a2 2 0 00-2 2v18l4-4h14a2 2 0 002-2V4a2 2 0 00-2-2z\"/>\n</svg>", U = "<svg viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z\"/>\n</svg>", W = `<img src="${F}" alt="" style="width:100%;height:100%;object-fit:cover;border-radius:50%" />`, G = "<svg viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M2.01 21L23 12 2.01 3 2 10l15 2-15 2z\"/>\n</svg>", K = "<svg viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M20 2H4a2 2 0 00-2 2v18l4-4h14a2 2 0 002-2V4a2 2 0 00-2-2zm-2 10H6v-2h12v2zm0-3H6V7h12v2z\"/>\n</svg>";
685
- function q(e) {
686
- if (!e || e.type === "icon") return W;
687
- if (e.type === "emoji") return `<span style="font-size:22px;line-height:1;display:flex;align-items:center;justify-content:center;width:100%;height:100%">${J(e.value)}</span>`;
688
- if (e.type === "initials") {
689
- let t = e.bgColor ?? "#F97316";
690
- return `<div style="width:100%;height:100%;border-radius:50%;background:${/^#[0-9A-Fa-f]{3,6}$/.test(t) ? t : "#F97316"};display:flex;align-items:center;justify-content:center;font-size:14px;font-weight:700;color:#fff">${J(e.value.slice(0, 2).toUpperCase())}</div>`;
691
- }
692
- return e.type === "image" ? `<img src="${J(e.value)}" style="width:100%;height:100%;object-fit:cover;border-radius:50%" alt="" />` : W;
693
- }
694
- function J(e) {
695
- return e.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#x27;");
696
- }
697
- function Y(e) {
698
- let t = [];
699
- return e = e.replace(/`([^`]+)`/g, (e, n) => (t.push(`<code>${n}</code>`), `\x00CODE${t.length - 1}\x00`)), e = e.replace(/\*\*\*(.+?)\*\*\*/g, "<strong><em>$1</em></strong>"), e = e.replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>"), e = e.replace(/(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/g, "<em>$1</em>"), e = e.replace(/~~(.+?)~~/g, "<del>$1</del>"), e = e.replace(/__(.+?)__/g, "<strong>$1</strong>"), e = e.replace(/(^|\s)_(?!_)(.+?)_(?!_)(?=\s|$)/g, "$1<em>$2</em>"), e = e.replace(/\[([^\]]+)\]\(([^()]*(?:\([^()]*\))*[^()]*)\)/g, (e, t, n) => /^https?:\/\//.test(n) ? `<a href="${n}" target="_blank" rel="noopener noreferrer">${t}</a>` : t), e = e.replace(/\x00CODE(\d+)\x00/g, (e, n) => t[Number(n)]), e;
700
- }
701
- function X(e) {
702
- return /^\|[\s\-:|]+\|$/.test(e);
703
- }
704
- function Z(e) {
705
- return e.replace(/^\|/, "").replace(/\|$/, "").split("|").map((e) => e.trim());
706
- }
707
- function re(e) {
708
- let t = J(e).replace(/\x00/g, "").split("\n"), n = [], r = 0;
709
- for (; r < t.length;) {
710
- let e = t[r].trim();
711
- if (e.startsWith("|") && e.endsWith("|")) {
712
- let e = [];
713
- for (; r < t.length && t[r].trim().startsWith("|") && t[r].trim().endsWith("|");) e.push(t[r].trim()), r++;
714
- let i = e.findIndex(X), a = i > 0 ? e.slice(0, i) : [], o = i >= 0 ? e.slice(i + 1) : e, s = "<table>";
715
- a.length > 0 && (s += "<thead>" + a.map((e) => "<tr>" + Z(e).map((e) => `<th>${Y(e)}</th>`).join("") + "</tr>").join("") + "</thead>"), o.length > 0 && (s += "<tbody>" + o.map((e) => "<tr>" + Z(e).map((e) => `<td>${Y(e)}</td>`).join("") + "</tr>").join("") + "</tbody>"), s += "</table>", n.push(s);
716
- continue;
717
- }
718
- if (/^-{3,}$/.test(e)) {
719
- n.push("<hr>"), r++;
720
- continue;
721
- }
722
- let i = e.match(/^(#{1,6})\s+(.*)$/);
723
- if (i) {
724
- let e = i[1].length;
725
- n.push(`<h${e}>${Y(i[2])}</h${e}>`), r++;
726
- continue;
727
- }
728
- if (/^[-*]\s/.test(e)) {
729
- let e = [];
730
- for (; r < t.length && /^\s*[-*]\s/.test(t[r]);) e.push(t[r].trim().replace(/^[-*]\s+/, "")), r++;
731
- n.push("<ul>" + e.map((e) => `<li>${Y(e)}</li>`).join("") + "</ul>");
732
- continue;
733
- }
734
- if (/^\d+\.\s/.test(e)) {
735
- let e = [];
736
- for (; r < t.length && /^\s*\d+\.\s/.test(t[r]);) e.push(t[r].trim().replace(/^\d+\.\s+/, "")), r++;
737
- n.push("<ol>" + e.map((e) => `<li>${Y(e)}</li>`).join("") + "</ol>");
738
- continue;
739
- }
740
- if (e === "") {
741
- n.push("<br>"), r++;
742
- continue;
743
- }
744
- n.push(Y(e)), n.push("<br>"), r++;
745
- }
746
- for (; n.length > 0 && n[n.length - 1] === "<br>";) n.pop();
747
- return n.join("");
748
- }
749
- function ie(e, t, n, r, i, a) {
750
- let { name: o, design: s } = t, c, l, u, d, f, p, { content: m } = s;
751
- function h(e) {
752
- e.setAttribute("data-position", s.layout.position), c = e.attachShadow({ mode: "closed" });
753
- let t = document.createElement("style");
754
- t.textContent = P(s), c.appendChild(t), l = document.createElement("button"), l.className = "bubble", l.setAttribute("aria-label", n.ariaOpenChat), l.innerHTML = `
755
- <span class="icon-chat">${H}</span>
756
- <span class="icon-close">${U}</span>
757
- `, l.addEventListener("click", i), c.appendChild(l), u = document.createElement("div"), u.className = "chat-window", u.setAttribute("role", "dialog"), u.setAttribute("aria-label", `${o} chat`), u.innerHTML = `
758
- <div class="chat-header">
759
- <div class="avatar">${q(s.avatar)}</div>
760
- <div class="header-text">
761
- <span class="bot-name">${J(o)}</span>
762
- <span class="bot-status">${J(n.statusOnline)}</span>
763
- </div>
764
- <button class="close-btn" aria-label="${J(n.ariaCloseChat)}">×</button>
765
- </div>
766
- <div class="messages" id="messages-container" role="log" aria-live="polite" aria-atomic="false"></div>
767
- <div class="chat-footer">
768
- <div class="input-row">
769
- <textarea
770
- class="input"
771
- placeholder="${J(m.placeholder || n.inputPlaceholder)}"
772
- rows="1"
773
- maxlength="2000"
774
- aria-label="Message input"
775
- ></textarea>
776
- <button class="send-btn" aria-label="${J(n.ariaSendMessage)}">
777
- ${G}
778
- </button>
779
- </div>
780
- <a class="botiq-badge" href="${V}" target="_blank" rel="noopener noreferrer">
781
- ${J(n.poweredBy)} <span class="botiq-badge-name">BotIQ</span>
782
- </a>
783
- </div>
784
- `, u.querySelector(".close-btn").addEventListener("click", i), d = u.querySelector("#messages-container");
785
- let r = null;
786
- d.addEventListener("scroll", () => {
787
- d.scrollTop <= 48 && !r && (r = setTimeout(() => {
788
- r = null;
789
- }, 150), a());
790
- }), f = u.querySelector(".input"), p = u.querySelector(".send-btn"), f.addEventListener("input", () => {
791
- f.style.height = "auto", f.style.height = Math.min(f.scrollHeight, 100) + "px";
792
- }), f.addEventListener("keydown", (e) => {
793
- e.key === "Enter" && !e.shiftKey && (e.preventDefault(), g());
794
- }), p.addEventListener("click", g), c.appendChild(u);
795
- }
796
- function g() {
797
- let e = f.value.trim();
798
- !e || p.disabled || (f.value = "", f.style.height = "auto", r(e));
799
- }
800
- function _(e) {
801
- if (e.messages.length === 0 && !e.isLoading) {
802
- let e = m.suggestionChips.length > 0 ? `<div class="chips">${m.suggestionChips.map((e) => `<button class="chip">${J(e)}</button>`).join("")}</div>` : "";
803
- d.innerHTML = `
804
- <div class="empty-state">
805
- ${K}
806
- <span class="greeting">${J(m.greeting || n.defaultGreeting)}</span>
807
- ${e}
808
- </div>
809
- `, d.querySelectorAll(".chip").forEach((e) => {
810
- e.addEventListener("click", () => r(e.textContent ?? ""));
811
- });
812
- return;
813
- }
814
- let t = e.messages.map((e) => `
815
- <div class="message ${e.role}">
816
- <div class="message-bubble">${e.role === "assistant" ? re(e.content) : J(e.content)}</div>
817
- </div>
818
- `).join(""), i = e.loadingOlder ? "<div class=\"typing\"><span></span><span></span><span></span></div>" : "", a = e.isLoading ? "<div class=\"typing\"><span></span><span></span><span></span></div>" : "", o = d.scrollHeight - d.scrollTop - d.clientHeight < 80, s = d.scrollTop, c = d.scrollHeight;
819
- d.innerHTML = i + t + a, o ? d.scrollTop = d.scrollHeight : d.scrollTop = B(s, c, d.scrollHeight);
820
- }
821
- function v(e) {
822
- e.isOpen ? (u.classList.add("open"), l.classList.add("open"), l.setAttribute("aria-label", n.ariaCloseChat), requestAnimationFrame(() => f.focus())) : (u.classList.remove("open"), l.classList.remove("open"), l.setAttribute("aria-label", n.ariaOpenChat)), p.disabled = e.isLoading, _(e);
823
- }
824
- return {
825
- mount: h,
826
- update: v
827
- };
828
- }
829
- //#endregion
830
- //#region src/i18n/vi.ts
831
- var ae = {
832
- statusOnline: "Trực tuyến",
833
- inputPlaceholder: "Nhắn tin...",
834
- defaultGreeting: "Xin chào! Tôi có thể giúp gì cho bạn?",
835
- errorMessage: "Xin lỗi, không thể kết nối. Vui lòng thử lại.",
836
- errorAuth: "API key không hợp lệ.",
837
- errorForbidden: "Widget không được phép trên trang này.",
838
- errorQuota: "Bot đã đạt giới hạn tin nhắn tháng này.",
839
- errorGeneric: "Đang gặp sự cố kỹ thuật, vui lòng liên hệ trực tiếp.",
840
- typingIndicator: "Đang soạn tin...",
841
- quotaExceeded: "Bot đã hết lượt chat tháng này.",
842
- trialExpired: "Dịch vụ tạm dừng. Vui lòng liên hệ hỗ trợ.",
843
- poweredBy: "Powered by",
844
- ariaOpenChat: "Mở khung chat",
845
- ariaCloseChat: "Đóng khung chat",
846
- ariaSendMessage: "Gửi tin nhắn"
847
- }, oe = {
848
- statusOnline: "Online",
849
- inputPlaceholder: "Type a message...",
850
- defaultGreeting: "Hello! How can I help you?",
851
- errorMessage: "Sorry, something went wrong. Please try again.",
852
- errorAuth: "Invalid API key.",
853
- errorForbidden: "Widget is not allowed on this page.",
854
- errorQuota: "This bot has reached its monthly message limit.",
855
- errorGeneric: "Technical issue, please contact us directly.",
856
- typingIndicator: "Typing...",
857
- quotaExceeded: "This bot has reached its monthly chat limit.",
858
- trialExpired: "Service paused. Please contact support.",
859
- poweredBy: "Powered by",
860
- ariaOpenChat: "Open chat",
861
- ariaCloseChat: "Close chat",
862
- ariaSendMessage: "Send message"
863
- };
864
- //#endregion
865
- //#region src/i18n/index.ts
866
- function Q(e) {
867
- return e === "en" ? oe : ae;
868
- }
869
- //#endregion
870
- //#region src/builds/npm/index.ts
871
- var $ = 10;
872
- function se(e) {
873
- if (!e.apiKey) return console.warn("[BotIQ] apiKey is required"), () => void 0;
874
- let t = p(e);
875
- v();
876
- let n = t.apiKey.slice(-16), r = y(n), i = b(n, r);
877
- k({ messages: i });
878
- let a = I(), o = document.createElement("div");
879
- document.body.appendChild(o);
880
- let s = () => void 0, c = !1, l = Q(void 0);
881
- f(t.apiKey, t.apiUrl).then((e) => {
882
- if (c) return;
883
- if (e === null) {
884
- console.warn("[BotIQ] Widget not authorised on this origin — skipped mount"), o.remove();
885
- return;
886
- }
887
- l = Q(e.widgetLanguage);
888
- let n = ie(t, e, l, h, g, m);
889
- n.mount(o), s = A((e) => n.update(e)), n.update(O()), i.length > 0 && d();
890
- });
891
- function u(e) {
892
- return {
893
- role: e.role,
894
- content: e.content
895
- };
896
- }
897
- async function d() {
898
- let e = await ee(t.apiUrl, t.apiKey, r, $);
899
- e.messages.length > 0 && (a = L(e), k({ messages: e.messages.map(u) }));
900
- }
901
- async function m() {
902
- if (z(a, O().loadingOlder)) {
903
- k({ loadingOlder: !0 });
904
- try {
905
- let e = await te(t.apiUrl, t.apiKey, r, a.oldestCursor, $);
906
- e.messages.length > 0 && k({ messages: [...e.messages.map(u), ...O().messages] }), a = R(a, e);
907
- } finally {
908
- k({ loadingOlder: !1 });
909
- }
910
- }
911
- }
912
- function h(e) {
913
- let i = O();
914
- if (i.isLoading) return;
915
- let a = {
916
- role: "user",
917
- content: e
918
- };
919
- k({
920
- messages: [...i.messages, a],
921
- isLoading: !0
922
- }), S(t.apiUrl, t.apiKey, r, e, i.messages, l).then((e) => {
923
- let t = {
924
- role: "assistant",
925
- content: e
926
- };
927
- k({
928
- messages: [...O().messages, t],
929
- isLoading: !1
930
- }), x(n, r, [a, t]);
931
- }).catch(() => {
932
- k({ isLoading: !1 });
933
- });
934
- }
935
- function g() {
936
- k({ isOpen: !O().isOpen });
937
- }
938
- return () => {
939
- c = !0, s(), o.remove();
940
- };
941
- }
942
- //#endregion
943
- export { se as t };