@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.
@@ -0,0 +1,1344 @@
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: 400,
16
+ height: 640
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 || e.layout.fillHeight !== void 0 && typeof e.layout.fillHeight != "boolean") 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", c = Array.isArray(a.pageActions) ? a.pageActions : void 0, u = typeof a.contactInfo == "string" ? a.contactInfo : void 0;
94
+ return !a.design?.colors || !a.design?.layout || !a.design?.content || !l(a.design) ? {
95
+ name: o,
96
+ design: t,
97
+ widgetLanguage: s,
98
+ ...c ? { pageActions: c } : {},
99
+ ...u ? { contactInfo: u } : {}
100
+ } : {
101
+ name: o,
102
+ design: a.design,
103
+ widgetLanguage: s,
104
+ ...c ? { pageActions: c } : {},
105
+ ...u ? { contactInfo: u } : {}
106
+ };
107
+ }
108
+ function p(t) {
109
+ return {
110
+ apiKey: t.apiKey,
111
+ apiUrl: e
112
+ };
113
+ }
114
+ //#endregion
115
+ //#region src/core/session.ts
116
+ var m = "botiq:sessionId", h = "botiq:sessionId:", g = "botiq:history:", _ = 10;
117
+ function v() {
118
+ try {
119
+ let e = localStorage.getItem(m);
120
+ localStorage.removeItem(m), e && localStorage.removeItem(g + e);
121
+ } catch {}
122
+ }
123
+ function y(e) {
124
+ try {
125
+ let t = h + e, n = localStorage.getItem(t);
126
+ return n || (n = crypto.randomUUID(), localStorage.setItem(t, n)), n;
127
+ } catch {
128
+ return crypto.randomUUID();
129
+ }
130
+ }
131
+ function b(e, t) {
132
+ try {
133
+ let n = localStorage.getItem(`${g}${e}:${t}`);
134
+ return n ? JSON.parse(n) : [];
135
+ } catch {
136
+ return [];
137
+ }
138
+ }
139
+ function x(e, t, n) {
140
+ try {
141
+ let r = [...b(e, t), ...n].slice(-_);
142
+ localStorage.setItem(`${g}${e}:${t}`, JSON.stringify(r));
143
+ } catch {}
144
+ }
145
+ //#endregion
146
+ //#region src/core/action-registry.ts
147
+ var S = /^[a-z][a-z0-9_]{2,40}$/, C = new Set([
148
+ "low",
149
+ "medium",
150
+ "high"
151
+ ]), w = new class {
152
+ actions = /* @__PURE__ */ new Map();
153
+ declarativeRunner;
154
+ constructor(e) {
155
+ this.declarativeRunner = e;
156
+ }
157
+ validate(e) {
158
+ if (!S.test(e.name)) throw Error(`Invalid action name: "${e.name}"`);
159
+ if (!C.has(e.riskLevel)) throw Error(`Invalid riskLevel: ${e.riskLevel}`);
160
+ if (typeof e.description != "string" || e.description.length < 5) throw Error(`description ≥5 chars required for ${e.name}`);
161
+ }
162
+ seed(e) {
163
+ for (let t of e) try {
164
+ this.validate(t), this.actions.set(t.name, {
165
+ ...t,
166
+ kind: "declarative"
167
+ });
168
+ } catch (e) {
169
+ console.warn("[BotIQ] skipped invalid page action: " + e.message);
170
+ }
171
+ }
172
+ register(e) {
173
+ for (let t of e) {
174
+ if (this.validate(t), typeof t.execute != "function") throw Error(`execute must be a function for ${t.name}`);
175
+ this.actions.set(t.name, {
176
+ ...t,
177
+ kind: "js"
178
+ });
179
+ }
180
+ }
181
+ has(e) {
182
+ return this.actions.has(e);
183
+ }
184
+ get(e) {
185
+ return this.actions.get(e);
186
+ }
187
+ getDefinitions() {
188
+ return Array.from(this.actions.values()).map((e) => ({
189
+ name: e.name,
190
+ description: e.description,
191
+ riskLevel: e.riskLevel,
192
+ params: e.params
193
+ }));
194
+ }
195
+ setDeclarativeRunner(e) {
196
+ this.declarativeRunner = e;
197
+ }
198
+ async execute(e, t) {
199
+ let n = this.actions.get(e);
200
+ if (!n) throw Error(`Action "${e}" not registered`);
201
+ if (n.kind === "js") {
202
+ await n.execute(t);
203
+ return;
204
+ }
205
+ if (!this.declarativeRunner) throw Error("No declarative runner configured");
206
+ await this.declarativeRunner(n, t);
207
+ }
208
+ }();
209
+ //#endregion
210
+ //#region src/core/api.ts
211
+ async function ee(e, t, n, r, i, a) {
212
+ try {
213
+ let o = w.getDefinitions(), s = await fetch(`${e}/widget/chat`, {
214
+ method: "POST",
215
+ headers: {
216
+ "Content-Type": "application/json",
217
+ "X-Api-Key": t
218
+ },
219
+ referrerPolicy: "no-referrer-when-downgrade",
220
+ body: JSON.stringify({
221
+ sessionId: n,
222
+ message: r,
223
+ history: i,
224
+ ...o.length > 0 ? { availableActions: o } : {}
225
+ })
226
+ });
227
+ if (!s.ok) return s.status === 401 ? {
228
+ reply: a.errorAuth,
229
+ error: !0
230
+ } : s.status === 403 ? {
231
+ reply: a.errorForbidden,
232
+ error: !0
233
+ } : s.status === 429 ? {
234
+ reply: a.errorQuota,
235
+ error: !0
236
+ } : {
237
+ reply: a.errorGeneric,
238
+ error: !0
239
+ };
240
+ let c = await s.json();
241
+ return {
242
+ reply: c.reply || a.errorMessage,
243
+ pageAction: c.pageAction
244
+ };
245
+ } catch {
246
+ return {
247
+ reply: a.errorMessage,
248
+ error: !0
249
+ };
250
+ }
251
+ }
252
+ var T = {
253
+ messages: [],
254
+ hasMore: !1
255
+ };
256
+ function te(e) {
257
+ if (!e || typeof e != "object") return !1;
258
+ let t = e;
259
+ return typeof t.id == "string" && (t.role === "user" || t.role === "assistant") && typeof t.content == "string";
260
+ }
261
+ async function E(e, t, n, r, i) {
262
+ try {
263
+ let a = new URLSearchParams({
264
+ sessionId: n,
265
+ limit: String(i)
266
+ });
267
+ r && a.set("before", r);
268
+ let o = await fetch(`${e}/widget/messages?${a.toString()}`, {
269
+ method: "GET",
270
+ headers: { "X-Api-Key": t },
271
+ referrerPolicy: "no-referrer-when-downgrade"
272
+ });
273
+ if (!o.ok) return T;
274
+ let s = await o.json();
275
+ return {
276
+ messages: Array.isArray(s.messages) ? s.messages.filter(te) : [],
277
+ hasMore: s.hasMore === !0
278
+ };
279
+ } catch {
280
+ return T;
281
+ }
282
+ }
283
+ function D(e, t, n, r) {
284
+ return E(e, t, n, void 0, r);
285
+ }
286
+ function O(e, t, n, r, i) {
287
+ return E(e, t, n, r, i);
288
+ }
289
+ //#endregion
290
+ //#region src/core/state.ts
291
+ var k = {
292
+ messages: [],
293
+ isLoading: !1,
294
+ isOpen: !1,
295
+ loadingOlder: !1
296
+ }, A = /* @__PURE__ */ new Set();
297
+ function j() {
298
+ return k;
299
+ }
300
+ function M(e) {
301
+ Object.assign(k, e);
302
+ let t = {
303
+ ...k,
304
+ messages: [...k.messages]
305
+ };
306
+ A.forEach((e) => e(t));
307
+ }
308
+ function N(e) {
309
+ return A.add(e), () => A.delete(e);
310
+ }
311
+ //#endregion
312
+ //#region src/core/styles.ts
313
+ var P = {
314
+ inter: {
315
+ url: "https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap",
316
+ family: "'Inter', system-ui, -apple-system, sans-serif"
317
+ },
318
+ "plus-jakarta": {
319
+ url: "https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600&display=swap",
320
+ family: "'Plus Jakarta Sans', system-ui, -apple-system, sans-serif"
321
+ },
322
+ nunito: {
323
+ url: "https://fonts.googleapis.com/css2?family=Nunito:wght@400;500;600&display=swap",
324
+ family: "'Nunito', system-ui, -apple-system, sans-serif"
325
+ },
326
+ raleway: {
327
+ url: "https://fonts.googleapis.com/css2?family=Raleway:wght@400;500;600&display=swap",
328
+ family: "'Raleway', system-ui, -apple-system, sans-serif"
329
+ },
330
+ playfair: {
331
+ url: "https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;500;600&display=swap",
332
+ family: "'Playfair Display', Georgia, serif"
333
+ }
334
+ };
335
+ function F(e, t) {
336
+ if (!t) return e;
337
+ let n = t.angle ?? 135;
338
+ return t.type === "linear" ? `linear-gradient(${n}deg, ${t.from}, ${t.to})` : `radial-gradient(circle, ${t.from}, ${t.to})`;
339
+ }
340
+ function ne(e) {
341
+ 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, "");
342
+ }
343
+ function re(e) {
344
+ let t = e?.bubbleOpen ?? "none", n = e?.typingIndicator ?? "dots-bounce";
345
+ 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");
346
+ }
347
+ var ie = "\n.botiq-confirm-overlay { position:absolute; inset:0; background:rgba(0,0,0,.7);\n display:flex; align-items:center; justify-content:center; z-index:999; }\n.botiq-confirm-dialog { background:#1A1A1A; border:1px solid #2D2D2D; border-radius:12px;\n padding:16px; max-width:320px; color:#fff; font-size:14px; }\n.botiq-confirm-header { font-weight:600; margin-bottom:8px; }\n.botiq-confirm-reason { color:#9CA3AF; margin-bottom:8px; }\n.botiq-confirm-params { background:#0a0a0a; padding:8px; border-radius:6px; font-size:12px;\n max-height:120px; overflow:auto; white-space:pre-wrap; }\n.botiq-confirm-actions { display:flex; gap:8px; margin-top:12px; }\n.botiq-confirm-yes { flex:1; padding:8px 12px; border-radius:6px; border:none;\n background:#F97316; color:#fff; cursor:pointer; }\n.botiq-confirm-yes[data-risk=\"high\"] { background:#DC2626; }\n.botiq-confirm-no { flex:1; padding:8px 12px; border-radius:6px; background:transparent;\n border:1px solid #2D2D2D; color:#fff; cursor:pointer; }\n.botiq-confirm-countdown { margin-top:8px; font-size:12px; color:#9CA3AF; }\n";
348
+ function ae(e) {
349
+ let { colors: t, layout: n, font: r, gradient: i, animation: a, customCSS: o, whiteLabel: s } = e, c = P[r] ?? P.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 = n.fillHeight ? `min(${n.height}px, calc(100dvh - 48px))` : `${n.height}px`, m = F(t.primary, i), h = F(t.header, i), g = F(t.userBubble, i), _ = s ? ".botiq-badge { display: none !important; }" : "";
350
+ return `
351
+ @import url('${c.url}');
352
+
353
+ :host {
354
+ position: fixed;
355
+ bottom: 24px;
356
+ ${u}
357
+ z-index: 2147483647;
358
+ display: block;
359
+ font-family: ${c.family};
360
+ --color-primary: ${t.primary};
361
+ --color-header: ${t.header};
362
+ --color-user: ${t.userBubble};
363
+ --color-bot: ${t.botBubble};
364
+ --color-bg: ${t.background};
365
+ --color-text: ${t.text};
366
+ --color-input-bg: ${t.inputBackground};
367
+ --gray-50: #1A1A1A;
368
+ --gray-100: #222222;
369
+ --gray-200: #2D2D2D;
370
+ --gray-400: #9CA3AF;
371
+ --shadow-sm: 0 2px 8px rgba(0,0,0,.4);
372
+ --shadow-md: 0 8px 32px rgba(0,0,0,.5);
373
+ --shadow-lg: 0 16px 48px rgba(0,0,0,.6);
374
+ --radius-bubble: ${l};
375
+ --radius-msg: 18px;
376
+ --chat-w: ${n.width}px;
377
+ --chat-h: ${p};
378
+ }
379
+
380
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
381
+
382
+ /* ── Bubble ─────────────────────────────── */
383
+ .bubble {
384
+ width: 56px;
385
+ height: 56px;
386
+ border-radius: var(--radius-bubble);
387
+ background: ${m};
388
+ border: none;
389
+ cursor: pointer;
390
+ display: flex;
391
+ align-items: center;
392
+ justify-content: center;
393
+ box-shadow: var(--shadow-md);
394
+ transition: transform .2s ease, box-shadow .2s ease;
395
+ outline: none;
396
+ }
397
+
398
+ .bubble:hover { transform: scale(1.08); box-shadow: var(--shadow-lg); }
399
+ .bubble:active { transform: scale(0.96); }
400
+
401
+ .bubble svg { width: 26px; height: 26px; fill: #fff; transition: opacity .15s; }
402
+ .bubble .icon-chat { opacity: 1; }
403
+ .bubble .icon-close { opacity: 0; position: absolute; }
404
+ .bubble.open .icon-chat { opacity: 0; }
405
+ .bubble.open .icon-close { opacity: 1; }
406
+
407
+ /* ── Chat Window ────────────────────────── */
408
+ .chat-window {
409
+ position: absolute;
410
+ bottom: 72px;
411
+ ${f}
412
+ width: var(--chat-w);
413
+ height: var(--chat-h);
414
+ background: var(--color-bg);
415
+ border-radius: 20px;
416
+ box-shadow: var(--shadow-lg);
417
+ display: flex;
418
+ flex-direction: column;
419
+ overflow: hidden;
420
+ transform-origin: ${d};
421
+ transform: scale(.94) translateY(8px);
422
+ opacity: 0;
423
+ pointer-events: none;
424
+ transition: transform .22s cubic-bezier(.34,1.56,.64,1), opacity .18s ease;
425
+ }
426
+
427
+ .chat-window.open { transform: scale(1) translateY(0); opacity: 1; pointer-events: all; }
428
+
429
+ /* ── Header ─────────────────────────────── */
430
+ .chat-header {
431
+ background: ${h};
432
+ padding: 16px 16px 14px;
433
+ display: flex;
434
+ align-items: center;
435
+ gap: 10px;
436
+ flex-shrink: 0;
437
+ }
438
+
439
+ .avatar {
440
+ width: 36px;
441
+ height: 36px;
442
+ border-radius: 50%;
443
+ background: rgba(255,255,255,.2);
444
+ display: flex;
445
+ align-items: center;
446
+ justify-content: center;
447
+ flex-shrink: 0;
448
+ overflow: hidden;
449
+ }
450
+
451
+ .avatar svg { width: 20px; height: 20px; fill: #fff; }
452
+
453
+ .header-text { flex: 1; min-width: 0; }
454
+
455
+ .bot-name {
456
+ color: #fff;
457
+ font-weight: 600;
458
+ font-size: 15px;
459
+ line-height: 1.2;
460
+ display: block;
461
+ white-space: nowrap;
462
+ overflow: hidden;
463
+ text-overflow: ellipsis;
464
+ }
465
+
466
+ .bot-status { color: rgba(255,255,255,.75); font-size: 12px; display: block; margin-top: 1px; }
467
+
468
+ .close-btn {
469
+ width: 30px;
470
+ height: 30px;
471
+ border-radius: 50%;
472
+ background: rgba(255,255,255,.15);
473
+ border: none;
474
+ cursor: pointer;
475
+ color: #fff;
476
+ font-size: 18px;
477
+ display: flex;
478
+ align-items: center;
479
+ justify-content: center;
480
+ flex-shrink: 0;
481
+ transition: background .15s;
482
+ }
483
+ .close-btn:hover { background: rgba(255,255,255,.25); }
484
+
485
+ /* ── Messages ───────────────────────────── */
486
+ .messages {
487
+ flex: 1;
488
+ overflow-y: auto;
489
+ padding: 16px 14px;
490
+ display: flex;
491
+ flex-direction: column;
492
+ gap: 10px;
493
+ scroll-behavior: smooth;
494
+ }
495
+
496
+ .messages::-webkit-scrollbar { width: 4px; }
497
+ .messages::-webkit-scrollbar-track { background: transparent; }
498
+ .messages::-webkit-scrollbar-thumb { background: var(--gray-200); border-radius: 2px; }
499
+
500
+ .message { display: flex; flex-direction: column; max-width: 82%; }
501
+ .message.user { align-self: flex-end; align-items: flex-end; }
502
+ .message.assistant { align-self: flex-start; align-items: flex-start; max-width: 92%; }
503
+
504
+ .message-bubble {
505
+ padding: 10px 14px;
506
+ border-radius: var(--radius-msg);
507
+ font-size: 14px;
508
+ line-height: 1.5;
509
+ white-space: normal;
510
+ word-break: break-word;
511
+ overflow-x: auto;
512
+ }
513
+
514
+ /* ── Markdown inline elements ───────────────────────── */
515
+ .message-bubble strong { font-weight: 600; }
516
+ .message-bubble em { font-style: italic; }
517
+ .message-bubble a { color: var(--color-primary); text-decoration: underline; word-break: break-all; }
518
+ .message-bubble code {
519
+ font-family: monospace;
520
+ font-size: 12px;
521
+ padding: 1px 5px;
522
+ border-radius: 4px;
523
+ background: rgba(255,255,255,.1);
524
+ white-space: pre-wrap;
525
+ }
526
+ .message-bubble del { text-decoration: line-through; opacity: .7; }
527
+
528
+ /* ── Markdown headings ───────────────────────────────── */
529
+ /* Kept modest — the chat bubble is small, so headings are only slightly
530
+ larger than body text, never document-sized. */
531
+ .message-bubble h1,
532
+ .message-bubble h2,
533
+ .message-bubble h3,
534
+ .message-bubble h4,
535
+ .message-bubble h5,
536
+ .message-bubble h6 {
537
+ margin: 8px 0 4px;
538
+ font-weight: 600;
539
+ line-height: 1.3;
540
+ color: #fff;
541
+ }
542
+ .message-bubble h1 { font-size: 16px; }
543
+ .message-bubble h2 { font-size: 15px; }
544
+ .message-bubble h3 { font-size: 14px; }
545
+ .message-bubble h4,
546
+ .message-bubble h5,
547
+ .message-bubble h6 { font-size: 13px; }
548
+ .message-bubble :first-child { margin-top: 0; }
549
+
550
+ /* ── Markdown horizontal rule ────────────────────────── */
551
+ .message-bubble hr {
552
+ border: 0;
553
+ height: 1px;
554
+ background: rgba(255,255,255,.15);
555
+ margin: 8px 0;
556
+ }
557
+
558
+ /* ── Markdown tables ─────────────────────────────────── */
559
+ .message-bubble table {
560
+ border-collapse: collapse;
561
+ min-width: 100%;
562
+ font-size: 12.5px;
563
+ display: block;
564
+ overflow-x: auto;
565
+ margin: 4px 0;
566
+ }
567
+ .message-bubble th,
568
+ .message-bubble td {
569
+ border: 1px solid rgba(255,255,255,.15);
570
+ padding: 5px 10px;
571
+ text-align: left;
572
+ }
573
+ .message-bubble th { background: rgba(255,255,255,.08); font-weight: 600; white-space: nowrap; }
574
+ .message-bubble td { white-space: normal; }
575
+ .message-bubble tr:nth-child(even) td { background: rgba(255,255,255,.04); }
576
+
577
+ /* ── Markdown lists ──────────────────────────────────── */
578
+ .message-bubble ul,
579
+ .message-bubble ol { padding-left: 18px; margin: 2px 0; }
580
+ .message-bubble li { margin: 2px 0; }
581
+
582
+ .message.user .message-bubble {
583
+ background: ${g};
584
+ color: #fff;
585
+ border-bottom-right-radius: 4px;
586
+ white-space: pre-wrap;
587
+ }
588
+
589
+ .message.assistant .message-bubble {
590
+ background: var(--color-bot);
591
+ color: var(--color-text);
592
+ border-bottom-left-radius: 4px;
593
+ }
594
+
595
+ /* ── Message Metadata + Day Separators ──── */
596
+ .msg-meta {
597
+ font-size: 10px;
598
+ color: var(--gray-400);
599
+ margin-top: 3px;
600
+ padding: 0 2px;
601
+ }
602
+ .day-sep {
603
+ display: flex;
604
+ align-items: center;
605
+ justify-content: center;
606
+ margin: 6px 0 2px;
607
+ }
608
+ .day-sep span {
609
+ font-size: 10px;
610
+ color: var(--gray-400);
611
+ background: var(--gray-50);
612
+ padding: 2px 10px;
613
+ border-radius: 10px;
614
+ }
615
+
616
+ /* ── Error Bubble + Retry Button ────────── */
617
+ .message.error .message-bubble { border: 1px solid #DC2626; }
618
+ .retry-btn {
619
+ margin-top: 4px;
620
+ align-self: flex-start;
621
+ font-size: 12px;
622
+ font-family: inherit;
623
+ color: var(--color-primary);
624
+ background: transparent;
625
+ border: 1px solid var(--color-primary);
626
+ border-radius: 12px;
627
+ padding: 3px 10px;
628
+ cursor: pointer;
629
+ }
630
+ .retry-btn:hover { background: var(--color-primary); color: #fff; }
631
+ .msg-contact { font-size: 11px; color: var(--gray-400); margin-top: 3px; }
632
+
633
+ /* ── Typing Indicator ───────────────────── */
634
+ .typing {
635
+ display: flex;
636
+ align-items: center;
637
+ gap: 4px;
638
+ padding: 12px 14px;
639
+ background: var(--color-bot);
640
+ border-radius: var(--radius-msg);
641
+ border-bottom-left-radius: 4px;
642
+ align-self: flex-start;
643
+ width: fit-content;
644
+ }
645
+
646
+ .typing span {
647
+ width: 7px;
648
+ height: 7px;
649
+ border-radius: 50%;
650
+ background: var(--color-primary);
651
+ opacity: 0.5;
652
+ animation: bounce 1.2s infinite ease-in-out;
653
+ }
654
+
655
+ .typing span:nth-child(2) { animation-delay: .2s; }
656
+ .typing span:nth-child(3) { animation-delay: .4s; }
657
+
658
+ @keyframes bounce {
659
+ 0%, 60%, 100% { transform: translateY(0); }
660
+ 30% { transform: translateY(-6px); }
661
+ }
662
+
663
+ /* ── Empty / Greeting State ─────────────── */
664
+ .empty-state {
665
+ flex: 1;
666
+ display: flex;
667
+ flex-direction: column;
668
+ align-items: center;
669
+ justify-content: center;
670
+ gap: 8px;
671
+ color: var(--gray-400);
672
+ font-size: 13px;
673
+ text-align: center;
674
+ padding: 24px;
675
+ }
676
+
677
+ .empty-state svg { width: 40px; height: 40px; fill: var(--gray-200); margin-bottom: 4px; }
678
+ .empty-state .greeting { color: var(--color-text); font-size: 13px; opacity: 0.7; }
679
+
680
+ /* ── Suggestion Chips ───────────────────── */
681
+ .chips {
682
+ display: flex;
683
+ flex-wrap: wrap;
684
+ gap: 6px;
685
+ justify-content: center;
686
+ margin-top: 4px;
687
+ }
688
+
689
+ .chip {
690
+ padding: 5px 12px;
691
+ border-radius: 16px;
692
+ border: 1.5px solid var(--color-primary);
693
+ color: var(--color-primary);
694
+ background: transparent;
695
+ font-size: 12px;
696
+ font-family: inherit;
697
+ cursor: pointer;
698
+ transition: background .15s, color .15s;
699
+ }
700
+
701
+ .chip:hover {
702
+ background: var(--color-primary);
703
+ color: #fff;
704
+ }
705
+
706
+ /* ── Footer / Input ─────────────────────── */
707
+ .chat-footer {
708
+ border-top: 1px solid var(--gray-100);
709
+ padding: 10px 12px;
710
+ background: var(--color-bg);
711
+ flex-shrink: 0;
712
+ }
713
+
714
+ .input-row { display: flex; align-items: flex-end; gap: 8px; }
715
+
716
+ /* Subtle attribution — half-opacity, hover lifts to full. Doesn't steal focus
717
+ from tenant branding but reminds the customer this is powered by BotIQ. */
718
+ .botiq-badge {
719
+ display: block;
720
+ text-align: center;
721
+ font-size: 10px;
722
+ font-weight: 400;
723
+ color: var(--color-text);
724
+ opacity: 0.4;
725
+ text-decoration: none;
726
+ margin-top: 6px;
727
+ letter-spacing: 0.02em;
728
+ transition: opacity .15s;
729
+ }
730
+ .botiq-badge:hover { opacity: 0.85; }
731
+ .botiq-badge-name { font-weight: 600; color: var(--color-primary); }
732
+
733
+ .input {
734
+ flex: 1;
735
+ min-height: 38px;
736
+ max-height: 100px;
737
+ border: 1.5px solid var(--gray-200);
738
+ border-radius: 12px;
739
+ padding: 9px 12px;
740
+ font-family: inherit;
741
+ font-size: 14px;
742
+ color: var(--color-text);
743
+ resize: none;
744
+ outline: none;
745
+ background: var(--color-input-bg);
746
+ line-height: 1.4;
747
+ overflow-y: auto;
748
+ transition: border-color .15s;
749
+ }
750
+
751
+ .input:focus { border-color: var(--color-primary); background: var(--color-input-bg); }
752
+ .input::placeholder { color: var(--gray-400); }
753
+
754
+ .send-btn {
755
+ width: 38px;
756
+ height: 38px;
757
+ border-radius: 10px;
758
+ background: ${m};
759
+ border: none;
760
+ cursor: pointer;
761
+ display: flex;
762
+ align-items: center;
763
+ justify-content: center;
764
+ flex-shrink: 0;
765
+ transition: filter .15s, transform .1s;
766
+ }
767
+
768
+ .send-btn:hover { filter: brightness(0.88); }
769
+ .send-btn:active { transform: scale(0.94); }
770
+ .send-btn:disabled { background: var(--gray-200); cursor: not-allowed; filter: none; }
771
+ .send-btn svg { width: 18px; height: 18px; fill: #fff; }
772
+
773
+ .emoji-btn {
774
+ width: 34px; height: 38px;
775
+ background: transparent; border: none; cursor: pointer;
776
+ font-size: 18px; line-height: 1; flex-shrink: 0;
777
+ }
778
+ .emoji-panel {
779
+ display: grid;
780
+ grid-template-columns: repeat(8, 1fr);
781
+ gap: 2px;
782
+ margin-top: 8px;
783
+ max-height: 140px;
784
+ overflow-y: auto;
785
+ }
786
+ .emoji-panel[hidden] { display: none; }
787
+ .emoji-item {
788
+ background: transparent; border: none; cursor: pointer;
789
+ font-size: 18px; padding: 4px; border-radius: 6px;
790
+ }
791
+ .emoji-item:hover { background: var(--gray-100); }
792
+
793
+ ${_}
794
+ ${re(a)}
795
+ ${o ? ne(o) : ""}
796
+ ${ie}
797
+ @media (max-width: 480px) {
798
+ :host { bottom: 0; right: 0; left: 0; }
799
+ .chat-window {
800
+ width: 100vw;
801
+ height: 100dvh;
802
+ bottom: 0;
803
+ right: 0;
804
+ left: 0;
805
+ border-radius: 0;
806
+ }
807
+ .bubble { position: fixed; bottom: 16px; right: 16px; }
808
+ }
809
+ `;
810
+ }
811
+ //#endregion
812
+ //#region src/core/assets.ts
813
+ var oe = "data:image/png;base64,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";
814
+ //#endregion
815
+ //#region src/core/history-pager.ts
816
+ function se() {
817
+ return {
818
+ oldestCursor: null,
819
+ hasMore: !1,
820
+ cursorReady: !1
821
+ };
822
+ }
823
+ function ce(e) {
824
+ return {
825
+ oldestCursor: e.messages[0]?.id ?? null,
826
+ hasMore: e.hasMore,
827
+ cursorReady: e.messages.length > 0
828
+ };
829
+ }
830
+ function I(e, t) {
831
+ return {
832
+ oldestCursor: t.messages[0]?.id ?? e.oldestCursor,
833
+ hasMore: t.hasMore,
834
+ cursorReady: e.cursorReady
835
+ };
836
+ }
837
+ function L(e, t) {
838
+ return e.cursorReady && e.hasMore && !t && e.oldestCursor !== null;
839
+ }
840
+ function R(e, t, n) {
841
+ return e + (n - t);
842
+ }
843
+ //#endregion
844
+ //#region src/core/confirm-dialog.ts
845
+ function z(e) {
846
+ return e.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#x27;");
847
+ }
848
+ function B(e, t) {
849
+ let n = t.labels?.confirm ?? "Đồng ý", r = t.labels?.cancel ?? "Huỷ", i = t.labels?.countdownPrefix ?? "Tự động xác nhận sau ", a = t.labels?.countdownSuffix ?? "s…";
850
+ return new Promise((o) => {
851
+ let s = document.createElement("div");
852
+ s.className = "botiq-confirm-overlay", s.innerHTML = `
853
+ <div class="botiq-confirm-dialog" role="dialog" aria-modal="true">
854
+ <div class="botiq-confirm-header">${z(t.title)}</div>
855
+ <div class="botiq-confirm-body">
856
+ <div class="botiq-confirm-reason">${z(t.reason)}</div>
857
+ <pre class="botiq-confirm-params">${z(t.paramsDisplay)}</pre>
858
+ </div>
859
+ <div class="botiq-confirm-actions">
860
+ <button class="botiq-confirm-yes" data-risk="${t.riskLevel}" type="button">${z(n)}</button>
861
+ <button class="botiq-confirm-no" type="button">${z(r)}</button>
862
+ </div>
863
+ ${t.autoConfirmAfterSec ? `<div class="botiq-confirm-countdown">${z(i)}<span class="botiq-confirm-secs">${t.autoConfirmAfterSec}</span>${z(a)}</div>` : ""}
864
+ </div>`, e.appendChild(s);
865
+ let c = !1, l = null, u = (t) => {
866
+ c || (c = !0, l && clearInterval(l), e.removeChild(s), o(t));
867
+ };
868
+ if (s.querySelector(".botiq-confirm-yes").addEventListener("click", () => u(!0)), s.querySelector(".botiq-confirm-no").addEventListener("click", () => u(!1)), t.autoConfirmAfterSec && t.autoConfirmAfterSec > 0) {
869
+ let e = t.autoConfirmAfterSec, n = s.querySelector(".botiq-confirm-secs");
870
+ l = setInterval(() => {
871
+ --e, n && (n.textContent = String(e)), e <= 0 && u(!0);
872
+ }, 1e3);
873
+ }
874
+ });
875
+ }
876
+ //#endregion
877
+ //#region src/core/timeago.ts
878
+ function V(e) {
879
+ return String(e).padStart(2, "0");
880
+ }
881
+ function H(e, t, n) {
882
+ let r = Math.max(0, Math.floor((t - e) / 1e3));
883
+ if (r < 60) return n === "vi" ? "vừa xong" : "just now";
884
+ let i = Math.floor(r / 60);
885
+ if (i < 60) return n === "vi" ? `${i} phút` : `${i} min`;
886
+ let a = new Date(e);
887
+ return `${V(a.getHours())}:${V(a.getMinutes())}`;
888
+ }
889
+ function U(e, t, n) {
890
+ let r = (e) => {
891
+ let t = new Date(e);
892
+ return new Date(t.getFullYear(), t.getMonth(), t.getDate()).getTime();
893
+ }, i = Math.round((r(t) - r(e)) / 864e5);
894
+ if (i <= 0) return n === "vi" ? "Hôm nay" : "Today";
895
+ if (i === 1) return n === "vi" ? "Hôm qua" : "Yesterday";
896
+ let a = new Date(e);
897
+ return `${V(a.getDate())}/${V(a.getMonth() + 1)}/${a.getFullYear()}`;
898
+ }
899
+ //#endregion
900
+ //#region src/core/emoji.ts
901
+ function W(e, t) {
902
+ let n = e.selectionStart ?? e.value.length, r = e.selectionEnd ?? e.value.length;
903
+ e.value = e.value.slice(0, n) + t + e.value.slice(r), e.selectionStart = e.selectionEnd = n + t.length, e.dispatchEvent(new Event("input", { bubbles: !0 }));
904
+ }
905
+ var G = null;
906
+ async function le() {
907
+ return G || (G = (await import("./emoji-data-DWiYsBZI.js")).EMOJIS, G);
908
+ }
909
+ function ue(e) {
910
+ let t = document.createElement("button");
911
+ t.type = "button", t.className = "emoji-btn", t.setAttribute("aria-label", "Emoji"), t.textContent = "🙂";
912
+ let n = document.createElement("div");
913
+ n.className = "emoji-panel", n.hidden = !0;
914
+ let r = !1;
915
+ return t.addEventListener("click", async () => {
916
+ if (!r) {
917
+ let t = await le();
918
+ for (let r of t) {
919
+ let t = document.createElement("button");
920
+ t.type = "button", t.className = "emoji-item", t.textContent = r, t.addEventListener("click", () => {
921
+ e(r), n.hidden = !0;
922
+ }), n.appendChild(t);
923
+ }
924
+ r = !0;
925
+ }
926
+ n.hidden = !n.hidden;
927
+ }), {
928
+ button: t,
929
+ panel: n
930
+ };
931
+ }
932
+ //#endregion
933
+ //#region src/core/ui.ts
934
+ var de = "https://bot-q-frontend.vercel.app/", fe = "<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>", pe = "<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>", K = `<img src="${oe}" alt="" style="width:100%;height:100%;object-fit:cover;border-radius:50%" />`, me = "<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>", he = "<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>";
935
+ function ge(e) {
936
+ if (!e || e.type === "icon") return K;
937
+ 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%">${q(e.value)}</span>`;
938
+ if (e.type === "initials") {
939
+ let t = e.bgColor ?? "#F97316";
940
+ 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">${q(e.value.slice(0, 2).toUpperCase())}</div>`;
941
+ }
942
+ return e.type === "image" ? `<img src="${q(e.value)}" style="width:100%;height:100%;object-fit:cover;border-radius:50%" alt="" />` : K;
943
+ }
944
+ function q(e) {
945
+ return e.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#x27;");
946
+ }
947
+ function J(e) {
948
+ let t = [];
949
+ 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;
950
+ }
951
+ function _e(e) {
952
+ return /^\|[\s\-:|]+\|$/.test(e);
953
+ }
954
+ function Y(e) {
955
+ return e.replace(/^\|/, "").replace(/\|$/, "").split("|").map((e) => e.trim());
956
+ }
957
+ function ve(e) {
958
+ let t = q(e).replace(/\x00/g, "").split("\n"), n = [], r = 0;
959
+ for (; r < t.length;) {
960
+ let e = t[r].trim();
961
+ if (e.startsWith("|") && e.endsWith("|")) {
962
+ let e = [];
963
+ for (; r < t.length && t[r].trim().startsWith("|") && t[r].trim().endsWith("|");) e.push(t[r].trim()), r++;
964
+ let i = e.findIndex(_e), a = i > 0 ? e.slice(0, i) : [], o = i >= 0 ? e.slice(i + 1) : e, s = "<table>";
965
+ a.length > 0 && (s += "<thead>" + a.map((e) => "<tr>" + Y(e).map((e) => `<th>${J(e)}</th>`).join("") + "</tr>").join("") + "</thead>"), o.length > 0 && (s += "<tbody>" + o.map((e) => "<tr>" + Y(e).map((e) => `<td>${J(e)}</td>`).join("") + "</tr>").join("") + "</tbody>"), s += "</table>", n.push(s);
966
+ continue;
967
+ }
968
+ if (/^-{3,}$/.test(e)) {
969
+ n.push("<hr>"), r++;
970
+ continue;
971
+ }
972
+ let i = e.match(/^(#{1,6})\s+(.*)$/);
973
+ if (i) {
974
+ let e = i[1].length;
975
+ n.push(`<h${e}>${J(i[2])}</h${e}>`), r++;
976
+ continue;
977
+ }
978
+ if (/^[-*]\s/.test(e)) {
979
+ let e = [];
980
+ for (; r < t.length && /^\s*[-*]\s/.test(t[r]);) e.push(t[r].trim().replace(/^[-*]\s+/, "")), r++;
981
+ n.push("<ul>" + e.map((e) => `<li>${J(e)}</li>`).join("") + "</ul>");
982
+ continue;
983
+ }
984
+ if (/^\d+\.\s/.test(e)) {
985
+ let e = [];
986
+ for (; r < t.length && /^\s*\d+\.\s/.test(t[r]);) e.push(t[r].trim().replace(/^\d+\.\s+/, "")), r++;
987
+ n.push("<ol>" + e.map((e) => `<li>${J(e)}</li>`).join("") + "</ol>");
988
+ continue;
989
+ }
990
+ if (e === "") {
991
+ n.push("<br>"), r++;
992
+ continue;
993
+ }
994
+ n.push(J(e)), n.push("<br>"), r++;
995
+ }
996
+ for (; n.length > 0 && n[n.length - 1] === "<br>";) n.pop();
997
+ return n.join("");
998
+ }
999
+ function ye(e, t) {
1000
+ let { strings: n, content: r, botName: i, lang: a, now: o, contactInfo: s } = t;
1001
+ if (e.messages.length === 0 && !e.isLoading) {
1002
+ let e = r.suggestionChips.length > 0 ? `<div class="chips">${r.suggestionChips.map((e) => `<button class="chip">${q(e)}</button>`).join("")}</div>` : "";
1003
+ return `
1004
+ <div class="empty-state">
1005
+ ${he}
1006
+ <span class="greeting">${q(r.greeting || n.defaultGreeting)}</span>
1007
+ ${e}
1008
+ </div>
1009
+ `;
1010
+ }
1011
+ let c = "", l = e.messages.map((e) => {
1012
+ let t = e.ts ?? o, r = U(t, o, a), l = r === c ? "" : `<div class="day-sep"><span>${q(r)}</span></div>`;
1013
+ c = r;
1014
+ let u = e.role === "assistant" ? ve(e.content) : q(e.content), d = e.role === "assistant" && !e.error ? `<div class="msg-meta">${q(i)} · ${q(H(t, o, a))}</div>` : "", f = e.error ? `<button class="retry-btn" type="button">↻ ${q(n.retry)}</button>` : "", p = e.error && s ? `<div class="msg-contact">${q(s)}</div>` : "";
1015
+ return `${l}<div class="${`message ${e.role}${e.error ? " error" : ""}`}"><div class="message-bubble">${u}</div>${f}${p}${d}</div>`;
1016
+ }).join(""), u = e.loadingOlder ? "<div class=\"typing\"><span></span><span></span><span></span></div>" : "", d = e.isLoading ? "<div class=\"typing\"><span></span><span></span><span></span></div>" : "";
1017
+ return u + l + d;
1018
+ }
1019
+ function be(e, t, n, r, i, a, o) {
1020
+ let { name: s, design: c } = t, l, u, d, f, p, m, { content: h } = c;
1021
+ function g(e) {
1022
+ e.setAttribute("data-position", c.layout.position), l = e.attachShadow({ mode: "closed" });
1023
+ let t = document.createElement("style");
1024
+ t.textContent = ae(c), l.appendChild(t), u = document.createElement("button"), u.className = "bubble", u.setAttribute("aria-label", n.ariaOpenChat), u.innerHTML = `
1025
+ <span class="icon-chat">${fe}</span>
1026
+ <span class="icon-close">${pe}</span>
1027
+ `, u.addEventListener("click", i), l.appendChild(u), d = document.createElement("div"), d.className = "chat-window", d.setAttribute("role", "dialog"), d.setAttribute("aria-label", `${s} chat`), d.innerHTML = `
1028
+ <div class="chat-header">
1029
+ <div class="avatar">${ge(c.avatar)}</div>
1030
+ <div class="header-text">
1031
+ <span class="bot-name">${q(s)}</span>
1032
+ <span class="bot-status">${q(n.statusOnline)}</span>
1033
+ </div>
1034
+ <button class="close-btn" aria-label="${q(n.ariaCloseChat)}">×</button>
1035
+ </div>
1036
+ <div class="messages" id="messages-container" role="log" aria-live="polite" aria-atomic="false"></div>
1037
+ <div class="chat-footer">
1038
+ <div class="input-row">
1039
+ <textarea
1040
+ class="input"
1041
+ placeholder="${q(h.placeholder || n.inputPlaceholder)}"
1042
+ rows="1"
1043
+ maxlength="2000"
1044
+ aria-label="Message input"
1045
+ ></textarea>
1046
+ <button class="send-btn" aria-label="${q(n.ariaSendMessage)}">
1047
+ ${me}
1048
+ </button>
1049
+ </div>
1050
+ <a class="botiq-badge" href="${de}" target="_blank" rel="noopener noreferrer">
1051
+ ${q(n.poweredBy)} <span class="botiq-badge-name">BotIQ</span>
1052
+ </a>
1053
+ </div>
1054
+ `, d.querySelector(".close-btn").addEventListener("click", i), f = d.querySelector("#messages-container");
1055
+ let r = null;
1056
+ f.addEventListener("scroll", () => {
1057
+ f.scrollTop <= 48 && !r && (r = setTimeout(() => {
1058
+ r = null;
1059
+ }, 150), a());
1060
+ }), p = d.querySelector(".input"), m = d.querySelector(".send-btn"), p.addEventListener("input", () => {
1061
+ p.style.height = "auto", p.style.height = Math.min(p.scrollHeight, 100) + "px";
1062
+ }), p.addEventListener("keydown", (e) => {
1063
+ e.key === "Enter" && !e.shiftKey && (e.preventDefault(), _());
1064
+ }), m.addEventListener("click", _);
1065
+ let o = d.querySelector(".input-row"), { button: g, panel: v } = ue((e) => {
1066
+ W(p, e), p.focus();
1067
+ });
1068
+ o.insertBefore(g, m), d.querySelector(".chat-footer").appendChild(v), l.appendChild(d);
1069
+ }
1070
+ function _() {
1071
+ let e = p.value.trim();
1072
+ !e || m.disabled || (p.value = "", p.style.height = "auto", r(e));
1073
+ }
1074
+ function v(e) {
1075
+ let i = f.scrollHeight - f.scrollTop - f.clientHeight < 80, a = f.scrollTop, c = f.scrollHeight;
1076
+ f.innerHTML = ye(e, {
1077
+ strings: n,
1078
+ content: h,
1079
+ botName: s,
1080
+ lang: t.widgetLanguage,
1081
+ now: Date.now(),
1082
+ contactInfo: t.contactInfo
1083
+ }), f.querySelectorAll(".chip").forEach((e) => {
1084
+ e.addEventListener("click", () => r(e.textContent ?? ""));
1085
+ }), f.querySelectorAll(".retry-btn").forEach((e) => {
1086
+ e.addEventListener("click", () => o());
1087
+ }), i ? f.scrollTop = f.scrollHeight : f.scrollTop = R(a, c, f.scrollHeight);
1088
+ }
1089
+ function y(e) {
1090
+ e.isOpen ? (d.classList.add("open"), u.classList.add("open"), u.setAttribute("aria-label", n.ariaCloseChat), requestAnimationFrame(() => p.focus())) : (d.classList.remove("open"), u.classList.remove("open"), u.setAttribute("aria-label", n.ariaOpenChat)), m.disabled = e.isLoading, v(e);
1091
+ }
1092
+ function b(e) {
1093
+ return B(l, {
1094
+ ...e,
1095
+ labels: {
1096
+ confirm: n.confirmYes,
1097
+ cancel: n.confirmNo,
1098
+ countdownPrefix: n.confirmCountdownPrefix,
1099
+ countdownSuffix: n.confirmCountdownSuffix
1100
+ }
1101
+ });
1102
+ }
1103
+ return {
1104
+ mount: g,
1105
+ update: y,
1106
+ confirmAction: b
1107
+ };
1108
+ }
1109
+ //#endregion
1110
+ //#region src/i18n/vi.ts
1111
+ var xe = {
1112
+ statusOnline: "Trực tuyến",
1113
+ inputPlaceholder: "Nhắn tin...",
1114
+ defaultGreeting: "Xin chào! Tôi có thể giúp gì cho bạn?",
1115
+ errorMessage: "Xin lỗi, không thể kết nối. Vui lòng thử lại.",
1116
+ errorAuth: "API key không hợp lệ.",
1117
+ errorForbidden: "Widget không được phép trên trang này.",
1118
+ errorQuota: "Bot đã đạt giới hạn tin nhắn tháng này.",
1119
+ errorGeneric: "Đang gặp sự cố kỹ thuật, vui lòng liên hệ trực tiếp.",
1120
+ typingIndicator: "Đang soạn tin...",
1121
+ quotaExceeded: "Bot đã hết lượt chat tháng này.",
1122
+ trialExpired: "Dịch vụ tạm dừng. Vui lòng liên hệ hỗ trợ.",
1123
+ poweredBy: "Powered by",
1124
+ ariaOpenChat: "Mở khung chat",
1125
+ ariaCloseChat: "Đóng khung chat",
1126
+ ariaSendMessage: "Gửi tin nhắn",
1127
+ actionDone: "✓ Đã thực hiện.",
1128
+ actionCancelled: "Đã huỷ. Bạn cần gì khác không?",
1129
+ actionUnavailable: "Hành động không khả dụng.",
1130
+ actionError: "Có lỗi khi thực hiện.",
1131
+ confirmYes: "Đồng ý",
1132
+ confirmNo: "Huỷ",
1133
+ confirmCountdownPrefix: "Tự động xác nhận sau ",
1134
+ confirmCountdownSuffix: "s…",
1135
+ retry: "Thử lại"
1136
+ }, Se = {
1137
+ statusOnline: "Online",
1138
+ inputPlaceholder: "Type a message...",
1139
+ defaultGreeting: "Hello! How can I help you?",
1140
+ errorMessage: "Sorry, something went wrong. Please try again.",
1141
+ errorAuth: "Invalid API key.",
1142
+ errorForbidden: "Widget is not allowed on this page.",
1143
+ errorQuota: "This bot has reached its monthly message limit.",
1144
+ errorGeneric: "Technical issue, please contact us directly.",
1145
+ typingIndicator: "Typing...",
1146
+ quotaExceeded: "This bot has reached its monthly chat limit.",
1147
+ trialExpired: "Service paused. Please contact support.",
1148
+ poweredBy: "Powered by",
1149
+ ariaOpenChat: "Open chat",
1150
+ ariaCloseChat: "Close chat",
1151
+ ariaSendMessage: "Send message",
1152
+ actionDone: "✓ Done.",
1153
+ actionCancelled: "Cancelled. Anything else?",
1154
+ actionUnavailable: "Action unavailable.",
1155
+ actionError: "Something went wrong.",
1156
+ confirmYes: "Confirm",
1157
+ confirmNo: "Cancel",
1158
+ confirmCountdownPrefix: "Auto-confirming in ",
1159
+ confirmCountdownSuffix: "s…",
1160
+ retry: "Retry"
1161
+ };
1162
+ //#endregion
1163
+ //#region src/i18n/index.ts
1164
+ function X(e) {
1165
+ return e === "en" ? Se : xe;
1166
+ }
1167
+ //#endregion
1168
+ //#region src/core/declarative-executor.ts
1169
+ var Z = /^[a-zA-Z0-9_-]*$/;
1170
+ function Q(e, t) {
1171
+ return e.replace(/\{([a-zA-Z][a-zA-Z0-9_]*)\}/g, (e, n) => {
1172
+ let r = t[n], i = r == null ? "" : String(r);
1173
+ if (!Z.test(i)) throw Error(`Unsafe param value for "${n}"`);
1174
+ return i;
1175
+ });
1176
+ }
1177
+ function Ce(e, t) {
1178
+ let n = typeof location < "u" ? location.origin : "https://placeholder.local", r, i;
1179
+ try {
1180
+ r = new URL(e, n), i = new URL(t.replace(/\{[a-zA-Z][a-zA-Z0-9_]*\}/g, "x"), n);
1181
+ } catch {
1182
+ throw Error("Invalid navigation URL");
1183
+ }
1184
+ if (!/^https?:$/.test(r.protocol)) throw Error("Navigation must be http(s)");
1185
+ if (r.origin !== i.origin) throw Error("Navigation origin not allowed");
1186
+ }
1187
+ async function we(e, t) {
1188
+ let n = e.config;
1189
+ if (e.operation === "click") {
1190
+ let e = Q(String(n.selectorTemplate ?? ""), t), r = document.querySelector(e);
1191
+ if (!r) throw Error(`No element for selector: ${e}`);
1192
+ r.click();
1193
+ return;
1194
+ }
1195
+ if (e.operation === "fill") {
1196
+ let e = Array.isArray(n.fields) ? n.fields : [];
1197
+ for (let n of e) {
1198
+ let e = document.querySelector(n.selector);
1199
+ if (!e) throw Error(`No element for selector: ${n.selector}`);
1200
+ e.value = String(t[n.param] ?? ""), e.dispatchEvent(new Event("input", { bubbles: !0 })), e.dispatchEvent(new Event("change", { bubbles: !0 }));
1201
+ }
1202
+ return;
1203
+ }
1204
+ if (e.operation === "navigate") {
1205
+ let e = String(n.urlTemplate ?? ""), r = Q(e, t);
1206
+ Ce(r, e), location.assign(r);
1207
+ return;
1208
+ }
1209
+ throw Error(`Unknown operation: ${String(e.operation)}`);
1210
+ }
1211
+ //#endregion
1212
+ //#region src/core/page-action-executor.ts
1213
+ async function Te(e, t) {
1214
+ let n = t.registry.get(e.actionName);
1215
+ if (!n) return {
1216
+ status: "unknown",
1217
+ message: `Action "${e.actionName}" chưa được đăng ký.`
1218
+ };
1219
+ if (n.riskLevel !== "low" && !await t.confirm({
1220
+ title: n.description,
1221
+ reason: e.reason,
1222
+ paramsDisplay: JSON.stringify(e.params, null, 2),
1223
+ riskLevel: n.riskLevel,
1224
+ autoConfirmAfterSec: n.riskLevel === "medium" ? 10 : null
1225
+ })) return { status: "cancelled" };
1226
+ try {
1227
+ return await t.registry.execute(e.actionName, e.params), { status: "executed" };
1228
+ } catch (e) {
1229
+ return {
1230
+ status: "error",
1231
+ message: e instanceof Error ? e.message : "unknown error"
1232
+ };
1233
+ }
1234
+ }
1235
+ //#endregion
1236
+ //#region src/builds/npm/index.ts
1237
+ var $ = 10;
1238
+ function Ee(e) {
1239
+ if (w.setDeclarativeRunner(we), typeof window < "u" && (window.botiq = window.botiq ?? {}, window.botiq.registerActions = (e) => w.register(e)), !e.apiKey) return console.warn("[BotIQ] apiKey is required"), () => void 0;
1240
+ let t = p(e);
1241
+ v();
1242
+ let n = t.apiKey.slice(-16), r = y(n), i = b(n, r);
1243
+ M({ messages: i });
1244
+ let a = se(), o = document.createElement("div");
1245
+ document.body.appendChild(o);
1246
+ let s = () => void 0, c = !1, l = X(void 0), u = null;
1247
+ f(t.apiKey, t.apiUrl).then((e) => {
1248
+ if (c) return;
1249
+ if (e === null) {
1250
+ console.warn("[BotIQ] Widget not authorised on this origin — skipped mount"), o.remove();
1251
+ return;
1252
+ }
1253
+ l = X(e.widgetLanguage), e.pageActions?.length && w.seed(e.pageActions);
1254
+ let n = be(t, e, l, S, T, h, C);
1255
+ u = n, n.mount(o), s = N((e) => n.update(e)), n.update(j()), i.length > 0 && m();
1256
+ });
1257
+ function d(e) {
1258
+ let t = Date.parse(e.createdAt);
1259
+ return {
1260
+ role: e.role,
1261
+ content: e.content,
1262
+ ...Number.isFinite(t) ? { ts: t } : {}
1263
+ };
1264
+ }
1265
+ async function m() {
1266
+ let e = await D(t.apiUrl, t.apiKey, r, $);
1267
+ e.messages.length > 0 && (a = ce(e), M({ messages: e.messages.map(d) }));
1268
+ }
1269
+ async function h() {
1270
+ if (L(a, j().loadingOlder)) {
1271
+ M({ loadingOlder: !0 });
1272
+ try {
1273
+ let e = await O(t.apiUrl, t.apiKey, r, a.oldestCursor, $);
1274
+ e.messages.length > 0 && M({ messages: [...e.messages.map(d), ...j().messages] }), a = I(a, e);
1275
+ } finally {
1276
+ M({ loadingOlder: !1 });
1277
+ }
1278
+ }
1279
+ }
1280
+ let g = "";
1281
+ async function _(e) {
1282
+ M({ isLoading: !0 });
1283
+ try {
1284
+ let i = await ee(t.apiUrl, t.apiKey, r, e, j().messages.filter((e) => !e.error), l), a = {
1285
+ role: "assistant",
1286
+ content: i.reply,
1287
+ ts: Date.now(),
1288
+ ...i.error ? { error: !0 } : {}
1289
+ };
1290
+ if (M({
1291
+ messages: [...j().messages, a],
1292
+ isLoading: !1
1293
+ }), i.error || x(n, r, [a]), !i.error && i.pageAction && u) {
1294
+ let e = u;
1295
+ try {
1296
+ let t = await Te(i.pageAction, {
1297
+ registry: w,
1298
+ confirm: (t) => e.confirmAction(t)
1299
+ });
1300
+ if (!u) return;
1301
+ let a = {
1302
+ role: "assistant",
1303
+ content: t.status === "executed" ? l.actionDone : t.status === "cancelled" ? l.actionCancelled : t.status === "unknown" ? `⚠ ${t.message ?? l.actionUnavailable}` : `⚠ ${t.message ?? l.actionError}`,
1304
+ ts: Date.now()
1305
+ };
1306
+ M({ messages: [...j().messages, a] }), x(n, r, [a]);
1307
+ } catch {
1308
+ if (!u) return;
1309
+ let e = {
1310
+ role: "assistant",
1311
+ content: l.actionError,
1312
+ ts: Date.now()
1313
+ };
1314
+ M({ messages: [...j().messages, e] }), x(n, r, [e]);
1315
+ }
1316
+ }
1317
+ } catch {
1318
+ M({ isLoading: !1 });
1319
+ }
1320
+ }
1321
+ async function S(e) {
1322
+ if (j().isLoading) return;
1323
+ g = e;
1324
+ let t = {
1325
+ role: "user",
1326
+ content: e,
1327
+ ts: Date.now()
1328
+ };
1329
+ M({ messages: [...j().messages, t] }), x(n, r, [t]), await _(e);
1330
+ }
1331
+ function C() {
1332
+ if (j().isLoading || !g) return;
1333
+ let e = j().messages;
1334
+ M({ messages: e.length > 0 && e[e.length - 1].error ? e.slice(0, -1) : e }), _(g);
1335
+ }
1336
+ function T() {
1337
+ M({ isOpen: !j().isOpen });
1338
+ }
1339
+ return () => {
1340
+ c = !0, u = null, s(), o.remove();
1341
+ };
1342
+ }
1343
+ //#endregion
1344
+ export { Ee as t };