@locdo.tech/botiq-chat-sdk 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/sdk/index.js CHANGED
@@ -1,2 +1,2 @@
1
- import { t as e } from "./npm-6G46H82M.js";
1
+ import { t as e } from "./npm-D7UnWA_n.js";
2
2
  export { e as init };
@@ -17,12 +17,13 @@ var e = "https://bot-q-backend.vercel.app", t = {
17
17
  height: 520
18
18
  },
19
19
  content: {
20
- greeting: "Xin chào! Tôi có thể giúp gì cho bạn?",
20
+ greeting: "",
21
21
  suggestionChips: [],
22
- placeholder: "Nhắn tin..."
22
+ placeholder: ""
23
23
  },
24
24
  font: "inter"
25
- }
25
+ },
26
+ widgetLanguage: "vi"
26
27
  }, n = /^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})$/, r = new Set(["bottom-right", "bottom-left"]), i = new Set(["circle", "square"]), a = new Set([
27
28
  "inter",
28
29
  "plus-jakarta",
@@ -32,8 +33,17 @@ var e = "https://bot-q-backend.vercel.app", t = {
32
33
  "raleway",
33
34
  "lato",
34
35
  "playfair"
36
+ ]), o = new Set([
37
+ "none",
38
+ "fade",
39
+ "slide-up",
40
+ "bounce"
41
+ ]), s = new Set([
42
+ "dots-bounce",
43
+ "dots-pulse",
44
+ "bar"
35
45
  ]);
36
- function o(e) {
46
+ function c(e) {
37
47
  let t = e.colors;
38
48
  if (![
39
49
  t.primary,
@@ -43,10 +53,15 @@ function o(e) {
43
53
  t.background,
44
54
  t.text
45
55
  ].every((e) => typeof e == "string" && n.test(e)) || !r.has(e.layout.position) || !i.has(e.layout.buttonShape) || !a.has(e.font)) return !1;
46
- let { width: o, height: s } = e.layout;
47
- return !(!Number.isFinite(o) || o < 280 || o > 800 || !Number.isFinite(s) || s < 200 || s > 900);
56
+ let { width: c, height: l } = e.layout;
57
+ if (!Number.isFinite(c) || c < 280 || c > 800 || !Number.isFinite(l) || l < 200 || l > 900) return !1;
58
+ if (e.gradient) {
59
+ let t = e.gradient;
60
+ if (t.type !== "linear" && t.type !== "radial" || !n.test(t.from) || !n.test(t.to) || t.angle !== void 0 && (!Number.isFinite(t.angle) || t.angle < 0 || t.angle > 360)) return !1;
61
+ }
62
+ return !(e.animation && (!o.has(e.animation.bubbleOpen) || !s.has(e.animation.typingIndicator)) || e.customCSS !== void 0 && (typeof e.customCSS != "string" || e.customCSS.length > 8e3));
48
63
  }
49
- async function s(e, n) {
64
+ async function l(e, n) {
50
65
  try {
51
66
  let r = await fetch(`${n}/widget/meta`, {
52
67
  headers: { "X-Api-Key": e },
@@ -54,15 +69,18 @@ async function s(e, n) {
54
69
  });
55
70
  if (!r.ok) return t;
56
71
  let i = await r.json();
57
- return !i?.design?.colors || !i?.design?.layout || !i?.design?.content || !o(i.design) ? t : {
58
- name: typeof i.name == "string" && i.name.length > 0 ? i.name : "BotIQ",
59
- design: i.design
72
+ if (!i?.design?.colors || !i?.design?.layout || !i?.design?.content || !c(i.design)) return t;
73
+ let a = typeof i.name == "string" && i.name.length > 0 ? i.name : "BotIQ", o = i.widgetLanguage === "en" ? "en" : "vi";
74
+ return {
75
+ name: a,
76
+ design: i.design,
77
+ widgetLanguage: o
60
78
  };
61
79
  } catch {
62
80
  return t;
63
81
  }
64
82
  }
65
- function c(t) {
83
+ function u(t) {
66
84
  return {
67
85
  apiKey: t.apiKey,
68
86
  apiUrl: e
@@ -70,39 +88,34 @@ function c(t) {
70
88
  }
71
89
  //#endregion
72
90
  //#region src/core/session.ts
73
- var l = "botiq:sessionId", u = "botiq:history:", d = 10;
74
- function f() {
91
+ var d = "botiq:sessionId", f = "botiq:history:", p = 10;
92
+ function m() {
75
93
  try {
76
- let e = localStorage.getItem(l);
77
- return e || (e = crypto.randomUUID(), localStorage.setItem(l, e)), e;
94
+ let e = localStorage.getItem(d);
95
+ return e || (e = crypto.randomUUID(), localStorage.setItem(d, e)), e;
78
96
  } catch {
79
97
  return crypto.randomUUID();
80
98
  }
81
99
  }
82
- function p(e) {
100
+ function h(e) {
83
101
  try {
84
- let t = localStorage.getItem(u + e);
102
+ let t = localStorage.getItem(f + e);
85
103
  return t ? JSON.parse(t) : [];
86
104
  } catch {
87
105
  return [];
88
106
  }
89
107
  }
90
- function m(e, t) {
108
+ function g(e, t) {
91
109
  try {
92
- let n = [...p(e), ...t].slice(-d);
93
- localStorage.setItem(u + e, JSON.stringify(n));
110
+ let n = [...h(e), ...t].slice(-p);
111
+ localStorage.setItem(f + e, JSON.stringify(n));
94
112
  } catch {}
95
113
  }
96
114
  //#endregion
97
115
  //#region src/core/api.ts
98
- var h = {
99
- 401: "API key không hợp lệ.",
100
- 403: "Widget không được phép trên trang này.",
101
- 429: "Bot đã đạt giới hạn tin nhắn tháng này."
102
- };
103
- async function g(e, t, n, r, i) {
116
+ async function _(e, t, n, r, i, a) {
104
117
  try {
105
- let a = await fetch(`${e}/widget/chat`, {
118
+ let o = await fetch(`${e}/widget/chat`, {
106
119
  method: "POST",
107
120
  headers: {
108
121
  "Content-Type": "application/json",
@@ -114,35 +127,35 @@ async function g(e, t, n, r, i) {
114
127
  history: i
115
128
  })
116
129
  });
117
- return a.ok ? (await a.json()).reply || "Không phản hồi từ bot." : h[a.status] ?? "Đang gặp sự cố kỹ thuật, vui lòng liên hệ trực tiếp.";
130
+ return o.ok ? (await o.json()).reply || a.errorMessage : o.status === 401 ? a.errorAuth : o.status === 403 ? a.errorForbidden : o.status === 429 ? a.errorQuota : a.errorGeneric;
118
131
  } catch {
119
- return "Xin lỗi, không thể kết nối. Vui lòng thử lại.";
132
+ return a.errorMessage;
120
133
  }
121
134
  }
122
135
  //#endregion
123
136
  //#region src/core/state.ts
124
- var _ = {
137
+ var v = {
125
138
  messages: [],
126
139
  isLoading: !1,
127
140
  isOpen: !1
128
- }, v = /* @__PURE__ */ new Set();
129
- function y() {
130
- return _;
141
+ }, y = /* @__PURE__ */ new Set();
142
+ function b() {
143
+ return v;
131
144
  }
132
- function b(e) {
133
- Object.assign(_, e);
145
+ function x(e) {
146
+ Object.assign(v, e);
134
147
  let t = {
135
- ..._,
136
- messages: [..._.messages]
148
+ ...v,
149
+ messages: [...v.messages]
137
150
  };
138
- v.forEach((e) => e(t));
151
+ y.forEach((e) => e(t));
139
152
  }
140
- function x(e) {
141
- return v.add(e), () => v.delete(e);
153
+ function S(e) {
154
+ return y.add(e), () => y.delete(e);
142
155
  }
143
156
  //#endregion
144
157
  //#region src/core/styles.ts
145
- var S = {
158
+ var C = {
146
159
  inter: {
147
160
  url: "https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap",
148
161
  family: "'Inter', system-ui, -apple-system, sans-serif"
@@ -176,18 +189,30 @@ var S = {
176
189
  family: "'Playfair Display', Georgia, serif"
177
190
  }
178
191
  };
179
- function C(e) {
180
- let { colors: t, layout: n, font: r } = e, i = S[r] ?? S.inter, a = n.buttonShape === "square" ? "14px" : "50%", o = n.position === "bottom-left" ? "right: auto; left: 24px;" : "right: 24px;", s = n.position === "bottom-left" ? "bottom left" : "bottom right", c = n.position === "bottom-left" ? "right: auto; left: 0;" : "right: 0;";
192
+ function w(e, t) {
193
+ if (!t) return e;
194
+ let n = t.angle ?? 135;
195
+ return t.type === "linear" ? `linear-gradient(${n}deg, ${t.from}, ${t.to})` : `radial-gradient(circle, ${t.from}, ${t.to})`;
196
+ }
197
+ function T(e) {
198
+ 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, "");
199
+ }
200
+ function E(e) {
201
+ let t = e?.bubbleOpen ?? "none", n = e?.typingIndicator ?? "dots-bounce";
202
+ 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");
203
+ }
204
+ function D(e) {
205
+ let { colors: t, layout: n, font: r, gradient: i, animation: a, customCSS: o, whiteLabel: s } = e, c = C[r] ?? C.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 = w(t.primary, i), m = w(t.header, i), h = w(t.userBubble, i), g = s ? ".botiq-badge { display: none !important; }" : "";
181
206
  return `
182
- @import url('${i.url}');
207
+ @import url('${c.url}');
183
208
 
184
209
  :host {
185
210
  position: fixed;
186
211
  bottom: 24px;
187
- ${o}
212
+ ${u}
188
213
  z-index: 2147483647;
189
214
  display: block;
190
- font-family: ${i.family};
215
+ font-family: ${c.family};
191
216
  --color-primary: ${t.primary};
192
217
  --color-header: ${t.header};
193
218
  --color-user: ${t.userBubble};
@@ -201,7 +226,7 @@ function C(e) {
201
226
  --shadow-sm: 0 2px 8px rgba(0,0,0,.4);
202
227
  --shadow-md: 0 8px 32px rgba(0,0,0,.5);
203
228
  --shadow-lg: 0 16px 48px rgba(0,0,0,.6);
204
- --radius-bubble: ${a};
229
+ --radius-bubble: ${l};
205
230
  --radius-msg: 18px;
206
231
  --chat-w: ${n.width}px;
207
232
  --chat-h: ${n.height}px;
@@ -214,7 +239,7 @@ function C(e) {
214
239
  width: 56px;
215
240
  height: 56px;
216
241
  border-radius: var(--radius-bubble);
217
- background: var(--color-primary);
242
+ background: ${p};
218
243
  border: none;
219
244
  cursor: pointer;
220
245
  display: flex;
@@ -238,7 +263,7 @@ function C(e) {
238
263
  .chat-window {
239
264
  position: absolute;
240
265
  bottom: 72px;
241
- ${c}
266
+ ${f}
242
267
  width: var(--chat-w);
243
268
  height: var(--chat-h);
244
269
  background: var(--color-bg);
@@ -247,7 +272,7 @@ function C(e) {
247
272
  display: flex;
248
273
  flex-direction: column;
249
274
  overflow: hidden;
250
- transform-origin: ${s};
275
+ transform-origin: ${d};
251
276
  transform: scale(.94) translateY(8px);
252
277
  opacity: 0;
253
278
  pointer-events: none;
@@ -258,7 +283,7 @@ function C(e) {
258
283
 
259
284
  /* ── Header ─────────────────────────────── */
260
285
  .chat-header {
261
- background: var(--color-header);
286
+ background: ${m};
262
287
  padding: 16px 16px 14px;
263
288
  display: flex;
264
289
  align-items: center;
@@ -341,7 +366,7 @@ function C(e) {
341
366
  }
342
367
 
343
368
  .message.user .message-bubble {
344
- background: var(--color-user);
369
+ background: ${h};
345
370
  color: #fff;
346
371
  border-bottom-right-radius: 4px;
347
372
  }
@@ -477,7 +502,7 @@ function C(e) {
477
502
  width: 38px;
478
503
  height: 38px;
479
504
  border-radius: 10px;
480
- background: var(--color-primary);
505
+ background: ${p};
481
506
  border: none;
482
507
  cursor: pointer;
483
508
  display: flex;
@@ -491,134 +516,189 @@ function C(e) {
491
516
  .send-btn:active { transform: scale(0.94); }
492
517
  .send-btn:disabled { background: var(--gray-200); cursor: not-allowed; filter: none; }
493
518
  .send-btn svg { width: 18px; height: 18px; fill: #fff; }
519
+
520
+ ${g}
521
+ ${E(a)}
522
+ ${o ? T(o) : ""}
494
523
  `;
495
524
  }
496
525
  //#endregion
497
526
  //#region src/core/ui.ts
498
- var w = "https://bot-q-frontend.vercel.app/", T = "<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>", E = "<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>", D = "<svg viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M12 2a2 2 0 012 2c0 .74-.4 1.38-1 1.72V7h3a3 3 0 013 3v8a3 3 0 01-3 3H8a3 3 0 01-3-3v-8a3 3 0 013-3h3V5.72A2 2 0 0110 4a2 2 0 012-2zm-2 9a1.5 1.5 0 100 3 1.5 1.5 0 000-3zm4 0a1.5 1.5 0 100 3 1.5 1.5 0 000-3z\"/>\n</svg>", O = "<svg viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M2.01 21L23 12 2.01 3 2 10l15 2-15 2z\"/>\n</svg>", k = "<svg viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M20 2H4a2 2 0 00-2 2v18l4-4h14a2 2 0 002-2V4a2 2 0 00-2-2zm-2 10H6v-2h12v2zm0-3H6V7h12v2z\"/>\n</svg>";
499
- function A(e) {
500
- return !e || e.type === "icon" ? D : e.type === "emoji" ? `<span style="font-size:22px;line-height:1;display:flex;align-items:center;justify-content:center;width:100%;height:100%">${j(e.value)}</span>` : e.type === "initials" ? `<div style="width:100%;height:100%;border-radius:50%;background:${e.bgColor ?? "#F97316"};display:flex;align-items:center;justify-content:center;font-size:14px;font-weight:700;color:#fff">${j(e.value.slice(0, 2).toUpperCase())}</div>` : e.type === "image" ? `<img src="${j(e.value)}" style="width:100%;height:100%;object-fit:cover;border-radius:50%" alt="" />` : D;
527
+ var O = "https://bot-q-frontend.vercel.app/", k = "<svg viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M20 2H4a2 2 0 00-2 2v18l4-4h14a2 2 0 002-2V4a2 2 0 00-2-2z\"/>\n</svg>", A = "<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>", j = "<svg viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M12 2a2 2 0 012 2c0 .74-.4 1.38-1 1.72V7h3a3 3 0 013 3v8a3 3 0 01-3 3H8a3 3 0 01-3-3v-8a3 3 0 013-3h3V5.72A2 2 0 0110 4a2 2 0 012-2zm-2 9a1.5 1.5 0 100 3 1.5 1.5 0 000-3zm4 0a1.5 1.5 0 100 3 1.5 1.5 0 000-3z\"/>\n</svg>", M = "<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>", N = "<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>";
528
+ function P(e) {
529
+ if (!e || e.type === "icon") return j;
530
+ 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%">${F(e.value)}</span>`;
531
+ if (e.type === "initials") {
532
+ let t = e.bgColor ?? "#F97316";
533
+ 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">${F(e.value.slice(0, 2).toUpperCase())}</div>`;
534
+ }
535
+ return e.type === "image" ? `<img src="${F(e.value)}" style="width:100%;height:100%;object-fit:cover;border-radius:50%" alt="" />` : j;
501
536
  }
502
- function j(e) {
537
+ function F(e) {
503
538
  return e.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#x27;");
504
539
  }
505
- function M(e, t, n, r) {
506
- let { name: i, design: a } = t, o, s, c, l, u, d, { content: f } = a;
507
- function p(e) {
508
- e.setAttribute("data-position", a.layout.position), o = e.attachShadow({ mode: "open" });
540
+ function I(e) {
541
+ let t = F(e).replace(/\x00/g, ""), n = [];
542
+ return t = t.replace(/`([^`]+)`/g, (e, t) => (n.push(`<code>${t}</code>`), `\x00CODE${n.length - 1}\x00`)), t = t.replace(/\*\*\*(.+?)\*\*\*/g, "<strong><em>$1</em></strong>"), t = t.replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>"), t = t.replace(/(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/g, "<em>$1</em>"), t = t.replace(/\[([^\]]+)\]\((https?:\/\/[^)]+)\)/g, "<a href=\"$2\" target=\"_blank\" rel=\"noopener noreferrer\">$1</a>"), t = t.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1"), t = t.replace(/\n/g, "<br>"), t = t.replace(/\x00CODE(\d+)\x00/g, (e, t) => n[Number(t)]), t;
543
+ }
544
+ function L(e, t, n, r, i) {
545
+ let { name: a, design: o } = t, s, c, l, u, d, f, { content: p } = o;
546
+ function m(e) {
547
+ e.setAttribute("data-position", o.layout.position), s = e.attachShadow({ mode: "open" });
509
548
  let t = document.createElement("style");
510
- t.textContent = C(a), o.appendChild(t), s = document.createElement("button"), s.className = "bubble", s.setAttribute("aria-label", `Open ${i} chat`), s.innerHTML = `
511
- <span class="icon-chat">${T}</span>
512
- <span class="icon-close">${E}</span>
513
- `, s.addEventListener("click", r), o.appendChild(s), c = document.createElement("div"), c.className = "chat-window", c.setAttribute("role", "dialog"), c.setAttribute("aria-label", `${i} chat`), c.innerHTML = `
549
+ t.textContent = D(o), s.appendChild(t), c = document.createElement("button"), c.className = "bubble", c.setAttribute("aria-label", n.ariaOpenChat), c.innerHTML = `
550
+ <span class="icon-chat">${k}</span>
551
+ <span class="icon-close">${A}</span>
552
+ `, c.addEventListener("click", i), s.appendChild(c), l = document.createElement("div"), l.className = "chat-window", l.setAttribute("role", "dialog"), l.setAttribute("aria-label", `${a} chat`), l.innerHTML = `
514
553
  <div class="chat-header">
515
- <div class="avatar">${A(a.avatar)}</div>
554
+ <div class="avatar">${P(o.avatar)}</div>
516
555
  <div class="header-text">
517
- <span class="bot-name">${j(i)}</span>
518
- <span class="bot-status">Trực tuyến</span>
556
+ <span class="bot-name">${F(a)}</span>
557
+ <span class="bot-status">${F(n.statusOnline)}</span>
519
558
  </div>
520
- <button class="close-btn" aria-label="Close chat">×</button>
559
+ <button class="close-btn" aria-label="${F(n.ariaCloseChat)}">×</button>
521
560
  </div>
522
561
  <div class="messages" id="messages-container"></div>
523
562
  <div class="chat-footer">
524
563
  <div class="input-row">
525
564
  <textarea
526
565
  class="input"
527
- placeholder="${j(f.placeholder)}"
566
+ placeholder="${F(p.placeholder || n.inputPlaceholder)}"
528
567
  rows="1"
529
568
  maxlength="2000"
530
569
  aria-label="Message input"
531
570
  ></textarea>
532
- <button class="send-btn" aria-label="Send message">
533
- ${O}
571
+ <button class="send-btn" aria-label="${F(n.ariaSendMessage)}">
572
+ ${M}
534
573
  </button>
535
574
  </div>
536
- <a class="botiq-badge" href="${w}" target="_blank" rel="noopener noreferrer">
537
- Powered by <span class="botiq-badge-name">BotIQ</span>
575
+ <a class="botiq-badge" href="${O}" target="_blank" rel="noopener noreferrer">
576
+ ${F(n.poweredBy)} <span class="botiq-badge-name">BotIQ</span>
538
577
  </a>
539
578
  </div>
540
- `, c.querySelector(".close-btn").addEventListener("click", r), l = c.querySelector("#messages-container"), u = c.querySelector(".input"), d = c.querySelector(".send-btn"), u.addEventListener("input", () => {
541
- u.style.height = "auto", u.style.height = Math.min(u.scrollHeight, 100) + "px";
542
- }), u.addEventListener("keydown", (e) => {
543
- e.key === "Enter" && !e.shiftKey && (e.preventDefault(), m());
544
- }), d.addEventListener("click", m), o.appendChild(c);
579
+ `, l.querySelector(".close-btn").addEventListener("click", i), u = l.querySelector("#messages-container"), d = l.querySelector(".input"), f = l.querySelector(".send-btn"), d.addEventListener("input", () => {
580
+ d.style.height = "auto", d.style.height = Math.min(d.scrollHeight, 100) + "px";
581
+ }), d.addEventListener("keydown", (e) => {
582
+ e.key === "Enter" && !e.shiftKey && (e.preventDefault(), h());
583
+ }), f.addEventListener("click", h), s.appendChild(l);
545
584
  }
546
- function m() {
547
- let e = u.value.trim();
548
- !e || d.disabled || (u.value = "", u.style.height = "auto", n(e));
585
+ function h() {
586
+ let e = d.value.trim();
587
+ !e || f.disabled || (d.value = "", d.style.height = "auto", r(e));
549
588
  }
550
- function h(e) {
589
+ function g(e) {
551
590
  if (e.messages.length === 0 && !e.isLoading) {
552
- let e = f.suggestionChips.length > 0 ? `<div class="chips">${f.suggestionChips.map((e) => `<button class="chip">${j(e)}</button>`).join("")}</div>` : "";
553
- l.innerHTML = `
591
+ let e = p.suggestionChips.length > 0 ? `<div class="chips">${p.suggestionChips.map((e) => `<button class="chip">${F(e)}</button>`).join("")}</div>` : "";
592
+ u.innerHTML = `
554
593
  <div class="empty-state">
555
- ${k}
556
- <span class="greeting">${j(f.greeting)}</span>
594
+ ${N}
595
+ <span class="greeting">${F(p.greeting || n.defaultGreeting)}</span>
557
596
  ${e}
558
597
  </div>
559
- `, l.querySelectorAll(".chip").forEach((e) => {
560
- e.addEventListener("click", () => n(e.textContent ?? ""));
598
+ `, u.querySelectorAll(".chip").forEach((e) => {
599
+ e.addEventListener("click", () => r(e.textContent ?? ""));
561
600
  });
562
601
  return;
563
602
  }
564
603
  let t = e.messages.map((e) => `
565
604
  <div class="message ${e.role}">
566
- <div class="message-bubble">${j(e.content)}</div>
605
+ <div class="message-bubble">${e.role === "assistant" ? I(e.content) : F(e.content)}</div>
567
606
  </div>
568
- `).join(""), r = e.isLoading ? "<div class=\"typing\"><span></span><span></span><span></span></div>" : "";
569
- l.innerHTML = t + r, l.scrollTop = l.scrollHeight;
607
+ `).join(""), i = e.isLoading ? "<div class=\"typing\"><span></span><span></span><span></span></div>" : "";
608
+ u.innerHTML = t + i, u.scrollTop = u.scrollHeight;
570
609
  }
571
- function g(e) {
572
- e.isOpen ? (c.classList.add("open"), s.classList.add("open"), s.setAttribute("aria-label", `Close ${i} chat`), requestAnimationFrame(() => u.focus())) : (c.classList.remove("open"), s.classList.remove("open"), s.setAttribute("aria-label", `Open ${i} chat`)), d.disabled = e.isLoading, h(e);
610
+ function _(e) {
611
+ e.isOpen ? (l.classList.add("open"), c.classList.add("open"), c.setAttribute("aria-label", n.ariaCloseChat), requestAnimationFrame(() => d.focus())) : (l.classList.remove("open"), c.classList.remove("open"), c.setAttribute("aria-label", n.ariaOpenChat)), f.disabled = e.isLoading, g(e);
573
612
  }
574
613
  return {
575
- mount: p,
576
- update: g
614
+ mount: m,
615
+ update: _
577
616
  };
578
617
  }
579
618
  //#endregion
619
+ //#region src/i18n/vi.ts
620
+ var R = {
621
+ statusOnline: "Trực tuyến",
622
+ inputPlaceholder: "Nhắn tin...",
623
+ defaultGreeting: "Xin chào! Tôi có thể giúp gì cho bạn?",
624
+ errorMessage: "Xin lỗi, không thể kết nối. Vui lòng thử lại.",
625
+ errorAuth: "API key không hợp lệ.",
626
+ errorForbidden: "Widget không được phép trên trang này.",
627
+ errorQuota: "Bot đã đạt giới hạn tin nhắn tháng này.",
628
+ errorGeneric: "Đang gặp sự cố kỹ thuật, vui lòng liên hệ trực tiếp.",
629
+ typingIndicator: "Đang soạn tin...",
630
+ quotaExceeded: "Bot đã hết lượt chat tháng này.",
631
+ trialExpired: "Dịch vụ tạm dừng. Vui lòng liên hệ hỗ trợ.",
632
+ poweredBy: "Powered by",
633
+ ariaOpenChat: "Mở khung chat",
634
+ ariaCloseChat: "Đóng khung chat",
635
+ ariaSendMessage: "Gửi tin nhắn"
636
+ }, z = {
637
+ statusOnline: "Online",
638
+ inputPlaceholder: "Type a message...",
639
+ defaultGreeting: "Hello! How can I help you?",
640
+ errorMessage: "Sorry, something went wrong. Please try again.",
641
+ errorAuth: "Invalid API key.",
642
+ errorForbidden: "Widget is not allowed on this page.",
643
+ errorQuota: "This bot has reached its monthly message limit.",
644
+ errorGeneric: "Technical issue, please contact us directly.",
645
+ typingIndicator: "Typing...",
646
+ quotaExceeded: "This bot has reached its monthly chat limit.",
647
+ trialExpired: "Service paused. Please contact support.",
648
+ poweredBy: "Powered by",
649
+ ariaOpenChat: "Open chat",
650
+ ariaCloseChat: "Close chat",
651
+ ariaSendMessage: "Send message"
652
+ };
653
+ //#endregion
654
+ //#region src/i18n/index.ts
655
+ function B(e) {
656
+ return e === "en" ? z : R;
657
+ }
658
+ //#endregion
580
659
  //#region src/builds/npm/index.ts
581
- function N(e) {
660
+ function V(e) {
582
661
  if (!e.apiKey) return console.warn("[BotIQ] apiKey is required"), () => void 0;
583
- let t = c(e), n = f();
584
- b({ messages: p(n) });
662
+ let t = u(e), n = m();
663
+ x({ messages: h(n) });
585
664
  let r = document.createElement("div");
586
665
  document.body.appendChild(r);
587
- let i = () => void 0, a = !1;
588
- s(t.apiKey, t.apiUrl).then((e) => {
666
+ let i = () => void 0, a = !1, o = B(void 0);
667
+ l(t.apiKey, t.apiUrl).then((e) => {
589
668
  if (a) return;
590
- let n = M(t, e, o, l);
591
- n.mount(r), i = x((e) => n.update(e)), n.update(y());
669
+ o = B(e.widgetLanguage);
670
+ let n = L(t, e, o, s, c);
671
+ n.mount(r), i = S((e) => n.update(e)), n.update(b());
592
672
  });
593
- function o(e) {
594
- let r = y();
673
+ function s(e) {
674
+ let r = b();
595
675
  if (r.isLoading) return;
596
676
  let i = {
597
677
  role: "user",
598
678
  content: e
599
679
  };
600
- b({
680
+ x({
601
681
  messages: [...r.messages, i],
602
682
  isLoading: !0
603
- }), g(t.apiUrl, t.apiKey, n, e, r.messages).then((e) => {
683
+ }), _(t.apiUrl, t.apiKey, n, e, r.messages, o).then((e) => {
604
684
  let t = {
605
685
  role: "assistant",
606
686
  content: e
607
687
  };
608
- b({
609
- messages: [...y().messages, t],
688
+ x({
689
+ messages: [...b().messages, t],
610
690
  isLoading: !1
611
- }), m(n, [i, t]);
691
+ }), g(n, [i, t]);
612
692
  }).catch(() => {
613
- b({ isLoading: !1 });
693
+ x({ isLoading: !1 });
614
694
  });
615
695
  }
616
- function l() {
617
- b({ isOpen: !y().isOpen });
696
+ function c() {
697
+ x({ isOpen: !b().isOpen });
618
698
  }
619
699
  return () => {
620
700
  a = !0, i(), r.remove();
621
701
  };
622
702
  }
623
703
  //#endregion
624
- export { N as t };
704
+ export { V as t };
package/dist/sdk/react.js CHANGED
@@ -1,4 +1,4 @@
1
- import { t as e } from "./npm-6G46H82M.js";
1
+ import { t as e } from "./npm-D7UnWA_n.js";
2
2
  import { useEffect as t } from "react";
3
3
  //#region src/builds/npm/react.tsx
4
4
  function n(n) {
package/dist/sdk/vue.js CHANGED
@@ -1,4 +1,4 @@
1
- import { t as e } from "./npm-6G46H82M.js";
1
+ import { t as e } from "./npm-D7UnWA_n.js";
2
2
  import { defineComponent as t, onMounted as n, onUnmounted as r } from "vue";
3
3
  //#region src/builds/npm/vue.ts
4
4
  var i = t({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@locdo.tech/botiq-chat-sdk",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "BotIQ chat widget SDK — embed AI chatbot into any website with vanilla JS, React, or Vue.",
5
5
  "keywords": [
6
6
  "botiq",