@locdo.tech/botiq-chat-sdk 0.2.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-Bo1fPZM3.js";
1
+ import { t as e } from "./npm-D7UnWA_n.js";
2
2
  export { e as init };
@@ -1,5 +1,5 @@
1
1
  //#region src/core/config.ts
2
- var e = "http://localhost:3001", t = {
2
+ var e = "https://bot-q-backend.vercel.app", t = {
3
3
  name: "BotIQ",
4
4
  design: {
5
5
  colors: {
@@ -17,18 +17,33 @@ var e = "http://localhost:3001", 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",
29
- "poppins"
30
+ "poppins",
31
+ "nunito",
32
+ "dm-sans",
33
+ "raleway",
34
+ "lato",
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"
30
45
  ]);
31
- function o(e) {
46
+ function c(e) {
32
47
  let t = e.colors;
33
48
  if (![
34
49
  t.primary,
@@ -38,10 +53,15 @@ function o(e) {
38
53
  t.background,
39
54
  t.text
40
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;
41
- let { width: o, height: s } = e.layout;
42
- 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));
43
63
  }
44
- async function s(e, n) {
64
+ async function l(e, n) {
45
65
  try {
46
66
  let r = await fetch(`${n}/widget/meta`, {
47
67
  headers: { "X-Api-Key": e },
@@ -49,15 +69,18 @@ async function s(e, n) {
49
69
  });
50
70
  if (!r.ok) return t;
51
71
  let i = await r.json();
52
- return !i?.design?.colors || !i?.design?.layout || !i?.design?.content || !o(i.design) ? t : {
53
- name: typeof i.name == "string" && i.name.length > 0 ? i.name : "BotIQ",
54
- 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
55
78
  };
56
79
  } catch {
57
80
  return t;
58
81
  }
59
82
  }
60
- function c(t) {
83
+ function u(t) {
61
84
  return {
62
85
  apiKey: t.apiKey,
63
86
  apiUrl: e
@@ -65,39 +88,34 @@ function c(t) {
65
88
  }
66
89
  //#endregion
67
90
  //#region src/core/session.ts
68
- var l = "botiq:sessionId", u = "botiq:history:", d = 10;
69
- function f() {
91
+ var d = "botiq:sessionId", f = "botiq:history:", p = 10;
92
+ function m() {
70
93
  try {
71
- let e = localStorage.getItem(l);
72
- 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;
73
96
  } catch {
74
97
  return crypto.randomUUID();
75
98
  }
76
99
  }
77
- function p(e) {
100
+ function h(e) {
78
101
  try {
79
- let t = localStorage.getItem(u + e);
102
+ let t = localStorage.getItem(f + e);
80
103
  return t ? JSON.parse(t) : [];
81
104
  } catch {
82
105
  return [];
83
106
  }
84
107
  }
85
- function m(e, t) {
108
+ function g(e, t) {
86
109
  try {
87
- let n = [...p(e), ...t].slice(-d);
88
- localStorage.setItem(u + e, JSON.stringify(n));
110
+ let n = [...h(e), ...t].slice(-p);
111
+ localStorage.setItem(f + e, JSON.stringify(n));
89
112
  } catch {}
90
113
  }
91
114
  //#endregion
92
115
  //#region src/core/api.ts
93
- var h = {
94
- 401: "API key không hợp lệ.",
95
- 403: "Widget không được phép trên trang này.",
96
- 429: "Bot đã đạt giới hạn tin nhắn tháng này."
97
- };
98
- async function g(e, t, n, r, i) {
116
+ async function _(e, t, n, r, i, a) {
99
117
  try {
100
- let a = await fetch(`${e}/widget/chat`, {
118
+ let o = await fetch(`${e}/widget/chat`, {
101
119
  method: "POST",
102
120
  headers: {
103
121
  "Content-Type": "application/json",
@@ -109,35 +127,35 @@ async function g(e, t, n, r, i) {
109
127
  history: i
110
128
  })
111
129
  });
112
- 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;
113
131
  } catch {
114
- return "Xin lỗi, không thể kết nối. Vui lòng thử lại.";
132
+ return a.errorMessage;
115
133
  }
116
134
  }
117
135
  //#endregion
118
136
  //#region src/core/state.ts
119
- var _ = {
137
+ var v = {
120
138
  messages: [],
121
139
  isLoading: !1,
122
140
  isOpen: !1
123
- }, v = /* @__PURE__ */ new Set();
124
- function y() {
125
- return _;
141
+ }, y = /* @__PURE__ */ new Set();
142
+ function b() {
143
+ return v;
126
144
  }
127
- function b(e) {
128
- Object.assign(_, e);
145
+ function x(e) {
146
+ Object.assign(v, e);
129
147
  let t = {
130
- ..._,
131
- messages: [..._.messages]
148
+ ...v,
149
+ messages: [...v.messages]
132
150
  };
133
- v.forEach((e) => e(t));
151
+ y.forEach((e) => e(t));
134
152
  }
135
- function x(e) {
136
- return v.add(e), () => v.delete(e);
153
+ function S(e) {
154
+ return y.add(e), () => y.delete(e);
137
155
  }
138
156
  //#endregion
139
157
  //#region src/core/styles.ts
140
- var S = {
158
+ var C = {
141
159
  inter: {
142
160
  url: "https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap",
143
161
  family: "'Inter', system-ui, -apple-system, sans-serif"
@@ -149,20 +167,52 @@ var S = {
149
167
  poppins: {
150
168
  url: "https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600&display=swap",
151
169
  family: "'Poppins', system-ui, -apple-system, sans-serif"
170
+ },
171
+ nunito: {
172
+ url: "https://fonts.googleapis.com/css2?family=Nunito:wght@400;500;600&display=swap",
173
+ family: "'Nunito', system-ui, -apple-system, sans-serif"
174
+ },
175
+ "dm-sans": {
176
+ url: "https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600&display=swap",
177
+ family: "'DM Sans', system-ui, -apple-system, sans-serif"
178
+ },
179
+ raleway: {
180
+ url: "https://fonts.googleapis.com/css2?family=Raleway:wght@400;500;600&display=swap",
181
+ family: "'Raleway', system-ui, -apple-system, sans-serif"
182
+ },
183
+ lato: {
184
+ url: "https://fonts.googleapis.com/css2?family=Lato:wght@400;700&display=swap",
185
+ family: "'Lato', system-ui, -apple-system, sans-serif"
186
+ },
187
+ playfair: {
188
+ url: "https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;500;600&display=swap",
189
+ family: "'Playfair Display', Georgia, serif"
152
190
  }
153
191
  };
154
- function C(e) {
155
- 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; }" : "";
156
206
  return `
157
- @import url('${i.url}');
207
+ @import url('${c.url}');
158
208
 
159
209
  :host {
160
210
  position: fixed;
161
211
  bottom: 24px;
162
- ${o}
212
+ ${u}
163
213
  z-index: 2147483647;
164
214
  display: block;
165
- font-family: ${i.family};
215
+ font-family: ${c.family};
166
216
  --color-primary: ${t.primary};
167
217
  --color-header: ${t.header};
168
218
  --color-user: ${t.userBubble};
@@ -176,7 +226,7 @@ function C(e) {
176
226
  --shadow-sm: 0 2px 8px rgba(0,0,0,.4);
177
227
  --shadow-md: 0 8px 32px rgba(0,0,0,.5);
178
228
  --shadow-lg: 0 16px 48px rgba(0,0,0,.6);
179
- --radius-bubble: ${a};
229
+ --radius-bubble: ${l};
180
230
  --radius-msg: 18px;
181
231
  --chat-w: ${n.width}px;
182
232
  --chat-h: ${n.height}px;
@@ -189,7 +239,7 @@ function C(e) {
189
239
  width: 56px;
190
240
  height: 56px;
191
241
  border-radius: var(--radius-bubble);
192
- background: var(--color-primary);
242
+ background: ${p};
193
243
  border: none;
194
244
  cursor: pointer;
195
245
  display: flex;
@@ -213,7 +263,7 @@ function C(e) {
213
263
  .chat-window {
214
264
  position: absolute;
215
265
  bottom: 72px;
216
- ${c}
266
+ ${f}
217
267
  width: var(--chat-w);
218
268
  height: var(--chat-h);
219
269
  background: var(--color-bg);
@@ -222,7 +272,7 @@ function C(e) {
222
272
  display: flex;
223
273
  flex-direction: column;
224
274
  overflow: hidden;
225
- transform-origin: ${s};
275
+ transform-origin: ${d};
226
276
  transform: scale(.94) translateY(8px);
227
277
  opacity: 0;
228
278
  pointer-events: none;
@@ -233,7 +283,7 @@ function C(e) {
233
283
 
234
284
  /* ── Header ─────────────────────────────── */
235
285
  .chat-header {
236
- background: var(--color-header);
286
+ background: ${m};
237
287
  padding: 16px 16px 14px;
238
288
  display: flex;
239
289
  align-items: center;
@@ -250,6 +300,7 @@ function C(e) {
250
300
  align-items: center;
251
301
  justify-content: center;
252
302
  flex-shrink: 0;
303
+ overflow: hidden;
253
304
  }
254
305
 
255
306
  .avatar svg { width: 20px; height: 20px; fill: #fff; }
@@ -315,7 +366,7 @@ function C(e) {
315
366
  }
316
367
 
317
368
  .message.user .message-bubble {
318
- background: var(--color-user);
369
+ background: ${h};
319
370
  color: #fff;
320
371
  border-bottom-right-radius: 4px;
321
372
  }
@@ -451,7 +502,7 @@ function C(e) {
451
502
  width: 38px;
452
503
  height: 38px;
453
504
  border-radius: 10px;
454
- background: var(--color-primary);
505
+ background: ${p};
455
506
  border: none;
456
507
  cursor: pointer;
457
508
  display: flex;
@@ -465,131 +516,189 @@ function C(e) {
465
516
  .send-btn:active { transform: scale(0.94); }
466
517
  .send-btn:disabled { background: var(--gray-200); cursor: not-allowed; filter: none; }
467
518
  .send-btn svg { width: 18px; height: 18px; fill: #fff; }
519
+
520
+ ${g}
521
+ ${E(a)}
522
+ ${o ? T(o) : ""}
468
523
  `;
469
524
  }
470
525
  //#endregion
471
526
  //#region src/core/ui.ts
472
- 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>";
473
- function A(e) {
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;
536
+ }
537
+ function F(e) {
474
538
  return e.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#x27;");
475
539
  }
476
- function j(e, t, n, r) {
477
- let { name: i, design: a } = t, o, s, c, l, u, d, { content: f } = a;
478
- function p(e) {
479
- 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" });
480
548
  let t = document.createElement("style");
481
- t.textContent = C(a), o.appendChild(t), s = document.createElement("button"), s.className = "bubble", s.setAttribute("aria-label", `Open ${i} chat`), s.innerHTML = `
482
- <span class="icon-chat">${T}</span>
483
- <span class="icon-close">${E}</span>
484
- `, 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 = `
485
553
  <div class="chat-header">
486
- <div class="avatar">${D}</div>
554
+ <div class="avatar">${P(o.avatar)}</div>
487
555
  <div class="header-text">
488
- <span class="bot-name">${A(i)}</span>
489
- <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>
490
558
  </div>
491
- <button class="close-btn" aria-label="Close chat">×</button>
559
+ <button class="close-btn" aria-label="${F(n.ariaCloseChat)}">×</button>
492
560
  </div>
493
561
  <div class="messages" id="messages-container"></div>
494
562
  <div class="chat-footer">
495
563
  <div class="input-row">
496
564
  <textarea
497
565
  class="input"
498
- placeholder="${A(f.placeholder)}"
566
+ placeholder="${F(p.placeholder || n.inputPlaceholder)}"
499
567
  rows="1"
500
568
  maxlength="2000"
501
569
  aria-label="Message input"
502
570
  ></textarea>
503
- <button class="send-btn" aria-label="Send message">
504
- ${O}
571
+ <button class="send-btn" aria-label="${F(n.ariaSendMessage)}">
572
+ ${M}
505
573
  </button>
506
574
  </div>
507
- <a class="botiq-badge" href="${w}" target="_blank" rel="noopener noreferrer">
508
- 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>
509
577
  </a>
510
578
  </div>
511
- `, c.querySelector(".close-btn").addEventListener("click", r), l = c.querySelector("#messages-container"), u = c.querySelector(".input"), d = c.querySelector(".send-btn"), u.addEventListener("input", () => {
512
- u.style.height = "auto", u.style.height = Math.min(u.scrollHeight, 100) + "px";
513
- }), u.addEventListener("keydown", (e) => {
514
- e.key === "Enter" && !e.shiftKey && (e.preventDefault(), m());
515
- }), 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);
516
584
  }
517
- function m() {
518
- let e = u.value.trim();
519
- !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));
520
588
  }
521
- function h(e) {
589
+ function g(e) {
522
590
  if (e.messages.length === 0 && !e.isLoading) {
523
- let e = f.suggestionChips.length > 0 ? `<div class="chips">${f.suggestionChips.map((e) => `<button class="chip">${A(e)}</button>`).join("")}</div>` : "";
524
- 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 = `
525
593
  <div class="empty-state">
526
- ${k}
527
- <span class="greeting">${A(f.greeting)}</span>
594
+ ${N}
595
+ <span class="greeting">${F(p.greeting || n.defaultGreeting)}</span>
528
596
  ${e}
529
597
  </div>
530
- `, l.querySelectorAll(".chip").forEach((e) => {
531
- e.addEventListener("click", () => n(e.textContent ?? ""));
598
+ `, u.querySelectorAll(".chip").forEach((e) => {
599
+ e.addEventListener("click", () => r(e.textContent ?? ""));
532
600
  });
533
601
  return;
534
602
  }
535
603
  let t = e.messages.map((e) => `
536
604
  <div class="message ${e.role}">
537
- <div class="message-bubble">${A(e.content)}</div>
605
+ <div class="message-bubble">${e.role === "assistant" ? I(e.content) : F(e.content)}</div>
538
606
  </div>
539
- `).join(""), r = e.isLoading ? "<div class=\"typing\"><span></span><span></span><span></span></div>" : "";
540
- 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;
541
609
  }
542
- function g(e) {
543
- 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);
544
612
  }
545
613
  return {
546
- mount: p,
547
- update: g
614
+ mount: m,
615
+ update: _
548
616
  };
549
617
  }
550
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
551
659
  //#region src/builds/npm/index.ts
552
- function M(e) {
660
+ function V(e) {
553
661
  if (!e.apiKey) return console.warn("[BotIQ] apiKey is required"), () => void 0;
554
- let t = c(e), n = f();
555
- b({ messages: p(n) });
662
+ let t = u(e), n = m();
663
+ x({ messages: h(n) });
556
664
  let r = document.createElement("div");
557
665
  document.body.appendChild(r);
558
- let i = () => void 0, a = !1;
559
- 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) => {
560
668
  if (a) return;
561
- let n = j(t, e, o, l);
562
- 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());
563
672
  });
564
- function o(e) {
565
- let r = y();
673
+ function s(e) {
674
+ let r = b();
566
675
  if (r.isLoading) return;
567
676
  let i = {
568
677
  role: "user",
569
678
  content: e
570
679
  };
571
- b({
680
+ x({
572
681
  messages: [...r.messages, i],
573
682
  isLoading: !0
574
- }), g(t.apiUrl, t.apiKey, n, e, r.messages).then((e) => {
683
+ }), _(t.apiUrl, t.apiKey, n, e, r.messages, o).then((e) => {
575
684
  let t = {
576
685
  role: "assistant",
577
686
  content: e
578
687
  };
579
- b({
580
- messages: [...y().messages, t],
688
+ x({
689
+ messages: [...b().messages, t],
581
690
  isLoading: !1
582
- }), m(n, [i, t]);
691
+ }), g(n, [i, t]);
583
692
  }).catch(() => {
584
- b({ isLoading: !1 });
693
+ x({ isLoading: !1 });
585
694
  });
586
695
  }
587
- function l() {
588
- b({ isOpen: !y().isOpen });
696
+ function c() {
697
+ x({ isOpen: !b().isOpen });
589
698
  }
590
699
  return () => {
591
700
  a = !0, i(), r.remove();
592
701
  };
593
702
  }
594
703
  //#endregion
595
- export { M as t };
704
+ export { V as t };
package/dist/sdk/react.js CHANGED
@@ -1,4 +1,4 @@
1
- import { t as e } from "./npm-Bo1fPZM3.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-Bo1fPZM3.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.2.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",