@locdo.tech/botiq-chat-sdk 0.5.0 → 0.7.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,1344 +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: 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,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAABL8SURBVHhe7ZsJjJzlecchbZo2aRPwScFAfM29s3N9M9/cO7O7c+zOzr0ze8ze3tN7+1yvD8Be29iYmEIrlaIaRIIIDW1URRE9BFFpAlXLYZNKxFLVVL1Q0yZSgCJhw7963u/7Zr95dyCtuuuDzCP99V0z7/c8v/d5nvedlX3LLTWrWc1qVrOa1axmNatZzWpWs5rV7Jrbc8899/nz588bz5w5lz219ODCyROnHz3xwNLTS8dPPk868cDSN5aOn3rs5ImTB8+ePps7f/a8kb7Dj/NLZadPn/7yw2cfzj146syF+48ev7y4cARHD9+PI4v3Y3HhGBYOHMHB/YeZ6Hxx4SiOLN7HRM+P37d0+cGTDz557sy5LI3Fj/+ZtVOnTpkeuO/4+YMHFv9l4cBh7NtzEOOjUxjqH2XaNTCG0V2TGB+ZqtDY8BSGB8fLn5kYnca++YM4uH8RCweP/PPJE6cfOXXqlJl/32fGji0c0y7sO3xhz9z+D/fM7cNg/whKnf3oLQ1goG8XgzI8OIGRoQmMDU8yQGoRRHpGIpCDfSPoLQ2yMYYGRjE/tw/zM3uvHjp45OtLS0tG/v03rU1NTf3azNSeY3PTe9/bPTaDrmIvujv6WPB9PUPo7x1iAIf6RxgYBZIaIsEb3bW7DI+ADfYPo793Fxujt3uAjdtZ7MXu8VnMz+7/YHZ679LRo0e/wPtzU9n09B7b9O7Zv6Xy6yz0oJjvZvAoa3q6COBgGSABoSxUAJIImqJy9g1IZUzfGSCAJQIoZSKNXcx1sffQBExOzL5GPvB+3RQ2MznXMzEy+T4FmM90opDrYhApSFJP1wD6SssAJYgj5R6nZJtypHskBq+Xsm+oDI/GUgB2tJfQnu1EPtPBnk+MTb1HvvD+3dA2NDB6aHhwjEHLporsSFlBwSklLGXhAAPQryrlwb5h1t+URUUtus/g9SxnHo1BojGVLKf3tec6kU23o5DvxMjQOMZ2TSzyft6QNrxr9ORA3zDSyTzSbXnk0kWWERQUiYdIEJjKPVHKRkkSTDoSOJICTp15JBqT4JUBZjuRyxSQTuaQbsti18AoxkcnTvH+3lDW1z1wiMoymcgg1ZZFJtmObKrAykmBqGSiUsrlTOwaRG+nBKbcG1lW0nEX+glc13LGKeDUmaeGR++kDMykcswX8kmaoMEF3u8bwrqKPYXO9h5EG1vR1ppGKpllWZhJFiogKoFS0JQ1TB29KJX60dXXh+7eXvSVBtDXJfW4vt5B9HUPse1Odx+JVnAJXHdHb0XZkug9DF6qgEwqzzKQACZaUog2taK7SOMPdvH+X1cbHBzT5DPFd5vDcYQCzcxZmvF0W45loZKJfDkX20ssGzs6SuiZHcGhb5/D4h+fRc/MMHo6B9DDsmwA3V396JsewaFvncXit8+hd24EnR0ErlSeEBqXlW26KMFLtpfh0YS2xNoQDkYQCbegmO/6YHRg9MbYdB8+fPjWYrbj1baWNBr8TQxgrDmBttZMGaKUiZUQmQgkZWVXF/Y+dRzP4q/xLL6P2SfuR0dXCd2FHlBWd3T3YP6J++Xnr2DPUyfYdwpZaSJYyWaLrOdR1mWo/yZzSCYIXopNKPlEvjXQBMfTBPGN4eHhX+XjueaWS7XvJ4fJOZphOjY2RJGIp5BoTaEtkVZlYx6Z1DLIHJVbuohidzeOfedRPIOXmQ49/xDae7pQJEAEuFTCoefP4Rn8Fb6Bl3H0O4+gUOpELlVkyqYLcr+TwEnwMuXMIzWFYmUfaaJpQgvZwlE+nmtqXV1d9yQT2fcbZefUijTFmeM0+xSIsrBIZSWVNgs82Y5csYiFZ8/gj/AqvolXsP/pk8h3dqI93YF26p0dndj/9Ck8hx/gObyKhW+eQa5QRKZNag9sxZfBKSVLao0nEY+2IdIotRa1aJKzyfwHvcXebXxc18xSsfSTidhy6apFM01NuyWSRGssJWXkCph5ZBM5ZDM5DB3cjYffeBoPvf4U+vaOIJstIJ8qIk/Zmi+i78A4zv7dUzh38WkMHBxnGZdKZNk4yipLGU/Q6D0MXqSN+aBUhrpKyOe2eArplvTX+biuiRXTRV1rtO0KD46f5VikFbFIggXTEpVhqkHKMPPtRRT6Syj0lZDPFWUwUtlTZuXzBRT7Sij0lpDPFpCkHitnGo1HE9QSS6Il2oZ4NIFocytiza3MBzU8NcxQIIJELHm1I9dh4ONbc0tGk48RkGrZR/cUNYYiiDS2sEyIM5AJVtosUygr4ykkW9JIxtNIxlIsKwhMknonbYeofyYySBL0ljR7rmRzIpZCK0GLSdlGYxM0ah/NjTEGT/GD95EU9DexCkk0t/0+H9+aWmtr623RUOw/ww1SQ66moK+xLLZ9aIoj0hxHtLmFiTKTMoV6VDyaKquFoMSpf2XkYxoJOo+l0UoZHJUWBZIETIJGY0rg4mgOxxAKNpffrwBUw1w+jyASiv00H4nczse5ZhYNRUuxphYE/MuQqingDTPRuVLSTaEoyw4GtKkFsWYKOMoUCccQoWck7pzARCi7GCwJmAKNJobGbApHyyWrvPcXKeBtRLw5gUQ0UeLjXDOLNMS+RZvmgK+6kzy08j2vCmQ4hnCoBa3hRizmgvidTjce7RDxWId0lM7l66KI0zkRuxN+JMMNDCoBpzFIjSEZnJx1PDz+mlckFAfFxMe5JhYMBr8U8jf/O5tlzklFSo+hoJpDcVYq6ucK3FAwgmRDED+aswEHtwD77gH23V1de+/Cu/s0+LNddpSa3Ky3hoPRilVV7YcakPp5NaDMF3/jOxQbH++qW5O3yRUiIBw8vzcEn6eB7fTJIb8nxKQGR5/x05Gesc+H0R704PKkAZheh6tT6/Hx1HpgdgMwS8d1kmZux8dT69hnsP+38b1RC7IBLys/STS2pIr3eUIsK5vCcTaZbBWWS1zxjfnvkya9Kdjk4uNddQv5Gsdo5n0EwxdmonOvpwHBQBMags3lez5fSDqqVH7mDcPjDiLnE/D2pJFBuzq9CT+b3Yofjunxw3ED3pow4K1xPS5P6vDh/N24OrMeH0+uw0cHtuNYmwCv6IdHBueTx6WJUeAp7yO/1D6Qj8xP1QSHQzGE/c0jfLyrbgFv+HebGuNlJ0hlcP5GBAKNCIckwIoILg/S6w3BLQaR9TlkgOuBuU24OFGHkq8eKa+AlMeJjMeBos+KCx0WXN17Lz6aXA/suwPPdNkQcLrh8TbC7105Pv/+srzSkfksr8ThQDOaG1sQEIOP8vGuunnFhj+lMqC0L8+0XJIk1msCTSy7KhxfIQIYQNYn4EcEcGYDML8Br4+bEXW74HAF4RLDcLpCEJ0+DIft+K/ZHcD0BmDvHXi+x4yg0wXR0wivJ1gVVrVr2vtRxnrdDcxvusdKmGJyh/6Ej3fVTRS8rxBANnvl1VXqJUqjJufKjisg6R6dl5/JAL2UgQap781vxGvjZsRddricfojOAIPncbiwJ2rFe3u3Sb1w/914vGiFV3DD7Qmz3suDoglUJlE5p/vUE6nfsd4XkERQqYS97uDLfLyrbi6H96KyqioLBS8WgIdgBeEVq8hNQTVAdEkAWQayhWMjLk8asZQw475WG44lrDjaasVDaQsu7TYBc5vZovLOvAFDITvcTj88YgN8bLzliaJzmhyl/yl9l+6x566A5Kt8nzKwIRiBKPhe4+NddRMdvrcIIA9NLWVlI0cJmHJURNceMcgyTA3wyvQmXJm9Ex/uuRdXSHvpKG9t5jfjo+kN+MncDpxM1KHB6YLHtTweP0l0j1UH9WW5Qsg3Bt0VYNAVfwPUegLNEB2eN/h4V90Em+dSyC/t9MvySNmoOEoBKBmqDo6BU6CWAdIqLJUwAWQ9bm6dLFpYZE2vw8ezG/DOrBaPpOvQLArwurxsIfJQZlUByN7lCsIjQ1P7vGLSCaDzGmSg0+75G6UJM8kwqFypKZNzEhy/5Bj1StpGuBvY7Lvl7KOjyxlARgXw6sxG/HRuG94cNeLNMRNeH6ejAW+NavCTOY1UwjMbcOXAVjyUsSIguOCSx1OASdkdYOO7XVTiQend8rXUhyth03P6WeoSvK/y8a66OazCC6z5+hvL+0CPpwEupw+iyw9RDLAjXZNY3/E0sCOJfYYpAKczgDQB3C0vInMb8ca4GQWPFTFRQNwtoEUUkHQ7MNtkwdu79fiYZeQ6/Hhah4JfkN6hgsdAqd5FPa5ib6rScglLG2nR4X6Bj3fVzW51XmCbYMoyGQZb6VQQFXDKc+W6AqBIAP1Is1VYXkTmNuL18ToGzy74IAheCM4ABMGPgEPAH7Sb2U86TK5je8KFVgdcggeu8qQsS3lXtQlUn1O20qLj94Qh2NxP8vGuujlt4gKVKjnAgAmSqAzoPpWHsh+ka3Uw5SyV5RQIoFMqYcoseR8YE52wOfwMnCAQQNrKOPF7+Xpg3xb2awQHvoqTaTucDg+cVQCq38e/V5ECkrKWStlush/g4111c5jFBPU3UaR9mp/1NQWiIrrnk/sc7eOo9/DOU1DLAPXSL5H5TXhzwoyC14K424kWD0lAm9uB8SYL/p5tZTbho6nbcWXfVhygDHR45D1j9Xfw96oCpGQQfLAZzRE+3lU303bTXVaz432WVaoMJLkc3orzimu5JypyOqlEfUh5aBtDPXAj26b8bHY73hw34tKEERfHTbg4YcKlcQP+g36FzN+Jq5PSLxb6Dv3cozEo+GrvYf5xKt8niPKRVnK71fVzl9G4iY93Tcxqtv/AS82ay7xPgsfDFR107YPD4UXC48Tl3XpgZj2uTm2Q/xpTRfSXmKnbGbz/PqDHfQkb3A6RARSF6hPF+1VNbGcgBmCrs3+fj3PNzGYWDrMXC/4KWMo5n4krn0lHu92NqCjg8pxV+nvgXlK1vwVK99+d347XxkxYTNgQEJxwOtxwyuP9fwDS0awzH+HjXDOzGqw6m1m4yhwmGDIQRU7BIzV3Jq8kWi0FOvdIzwUPHA43fIIL+xMufC1vw7mcDeeyNjycs0vKSjqXseJE0obRJgeaXQJcNhGCw83Gk7KZy3a+rD+lIgigo955lWLi41xTsxisL9LLpcXAC6fdUwGQApRA0bUMVZbyTGByw2ZzwWF1QbCpJcpywWmXzilj7XYPHHbKPPeK7OazqzLrV8IkyBSD1Wh7iY9vzc1qtGZEuwSHgPxv5CAQ6nsCQRAh2D0MDAPk8MBmdzPRucPhgd0hn9vdLGtJCsBPk3pSq8rphWAVYdSas3x8a26ZTOZzFqPtEmVgGZBDXAGJrpV7K87ZZ6SMVD+rJuV5tc+os7siy1XtYrmlLN+jrU+9wXrxlltu+Rwf3zWxOo0lSTPI+pkqQF52KlEW+Mpn1cQDU4+hjFMBUVgJXAHL3ysDd3pZ26jT1CX5uK6p1est36XNrJ0Lks7t1LfYUX1/+Z5aPLBqY9A5/5llqJWZqgbGP6cjLUAmrfnP+XiuudXtrPuq1WT/OfXCZUCVoJaB8Pp0iPznlQXLZqXPr8xSNTz+Xvm+w8X6Z32d7X2z1ryTj+e6mHmnucducbImT8FVygm7zSmfi3Lw0lHRCpgWSQ6rWD63Wl3lFX35O58+Cfwz6bkbNosA3U7DMB/HdTWD1vSYQw5qGYx0tFoIIIGUgloJuRKqAs1W72RiAOsFBlQ9STwo9fUnP3NCrzU8zvt/I9itRn3ddylIJUAJ3HImLkOtzEDKrorPyuAEm5uJzq1mGSBdc1lbCUudmeospO2SCJPeRH/z+xXe+RvCTJtNXzTqzS+VwdQ7YakXGByS+vyTZCPJwGgfR6Jz5Zqelz/7CRm8QgSS7fdM3zOZTF/k/b6hTKfTfcmgNfwFBUpOKzCUUlRfVxOVqgKs3P9keMp5eawq4JTv2NUALQIMWtMLuttuW/t/+7IaptPpPq/XGp60mO3MeZtZgvZ/lQKcla5VhKXOUX7GgFMmyuXPxGApkyFBtJht0O3Q/KH/Ri3bTzOdRjdXZ6j7gAUqZ94vEkEiWErGKQCV7FsBUMlai9weWHaK7JlJX/eBUWOc4f26qUy3XWfVa/Qv1tfZpIAVCHUSLHVWKQAJlnrxUJe+GiAPXylrS70NBo3hxe33bL85/6trNTNoDf0GrfHt+jorrHJJW2RgCkT1Od/v1M9XlLJZKlmL2QqdVndZp9EM8e//TNgdd9zxG5odmnG91vBGnbEe1noHKz+bauHgAarPy6DZfalcLfUOmAxm6HWGN3Ua3fjmzZtv7FV2lexWzbZtMcNO3RN6reEfDHoT6owWWM0SUALG9pO0kqt+kRC8+jo76kxWGPRm2hD/k3aH5sLWe7a2XLe/qFxv27Jlyxd23HWvc+s9Wye0O7SP63fq/1KzQ3tJu1P3j3qt8V91Wv2/aXZqf6zdqb2k1ehf0mzXXNh277aZrVu2em+7WbYl18MIrOYrX/nNrV/+8m/deeedv84/r1nNalazmtWsZjWrWc1qVrNfXvsfj+EYlqVWv8UAAAAASUVORK5CYII=";
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 };