@locdo.tech/botiq-chat-sdk 0.1.0 → 0.3.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.
package/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 BotIQ (LocDo.Tech)
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 BotIQ (LocDo.Tech)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -21,13 +21,7 @@ You also need a **Widget Embed Key** (`sk-biq-...`) issued from your BotIQ admin
21
21
  ```ts
22
22
  import { init } from '@locdo.tech/botiq-chat-sdk';
23
23
 
24
- const dispose = init({
25
- apiKey: 'sk-biq-xxxxxxxxxxxxxxxx',
26
- apiUrl: 'https://api.botiq.vn',
27
- botName: 'SpaBot',
28
- position: 'bottom-right',
29
- primaryColor: '#F97316',
30
- });
24
+ const dispose = init({ apiKey: 'sk-biq-xxxxxxxxxxxxxxxx' });
31
25
 
32
26
  // Later, to remove the widget:
33
27
  dispose();
@@ -43,19 +37,11 @@ dispose();
43
37
  import { BotIQWidget } from '@locdo.tech/botiq-chat-sdk/react';
44
38
 
45
39
  export function App() {
46
- return (
47
- <BotIQWidget
48
- apiKey="sk-biq-xxxxxxxxxxxxxxxx"
49
- apiUrl="https://api.botiq.vn"
50
- botName="SpaBot"
51
- position="bottom-right"
52
- primaryColor="#F97316"
53
- />
54
- );
40
+ return <BotIQWidget apiKey="sk-biq-xxxxxxxxxxxxxxxx" />;
55
41
  }
56
42
  ```
57
43
 
58
- > The widget reinitialises only when `apiKey` changes. To apply other prop changes, unmount and remount the component.
44
+ > The widget reinitialises only when `apiKey` changes.
59
45
 
60
46
  ---
61
47
 
@@ -67,13 +53,7 @@ import { BotIQWidget } from '@locdo.tech/botiq-chat-sdk/vue';
67
53
  </script>
68
54
 
69
55
  <template>
70
- <BotIQWidget
71
- api-key="sk-biq-xxxxxxxxxxxxxxxx"
72
- api-url="https://api.botiq.vn"
73
- bot-name="SpaBot"
74
- position="bottom-right"
75
- primary-color="#F97316"
76
- />
56
+ <BotIQWidget api-key="sk-biq-xxxxxxxxxxxxxxxx" />
77
57
  </template>
78
58
  ```
79
59
 
@@ -81,15 +61,11 @@ import { BotIQWidget } from '@locdo.tech/botiq-chat-sdk/vue';
81
61
 
82
62
  ## Configuration
83
63
 
84
- | Prop | Type | Required | Default |
85
- |---|---|---|---|
86
- | `apiKey` | `string` | ✅ | — |
87
- | `apiUrl` | `string` | ✅ | — |
88
- | `botName` | `string` | ✅ | — |
89
- | `position` | `'bottom-right' \| 'bottom-left'` | — | `'bottom-right'` |
90
- | `primaryColor` | `string` (hex) | — | `'#F97316'` |
64
+ | Prop | Type | Required |
65
+ |---|---|---|
66
+ | `apiKey` | `string` | ✅ |
91
67
 
92
- The full visual design (colors, layout, greeting message) is **server-driven** via the `/widget/design` endpoint admin/tenant pick a preset in the BotIQ dashboard and the widget fetches it on init.
68
+ That's it. The bot name, colors, layout, position, greeting message, and all other visual options are configured by your admin/tenant in the BotIQ dashboard and fetched at runtime from `/widget/meta` keeping integration code dead simple and letting non-developers tweak the UI without re-deploying.
93
69
 
94
70
  ---
95
71
 
@@ -101,9 +77,6 @@ For static HTML (no bundler), use the CDN build:
101
77
  <script
102
78
  src="https://bot-q-frontend.vercel.app/widget.js"
103
79
  data-api-key="sk-biq-xxxxxxxxxxxxxxxx"
104
- data-api-url="https://api.botiq.vn"
105
- data-bot-name="SpaBot"
106
- data-position="bottom-right"
107
80
  ></script>
108
81
  ```
109
82
 
@@ -117,6 +90,17 @@ The Widget Embed Key (`sk-biq-...`) is **safe to expose** in HTML/source. Each k
117
90
 
118
91
  ---
119
92
 
93
+ ## Migrating from 0.1.x
94
+
95
+ `0.2.0` removes the following props from `init()`, `<BotIQWidget>`, and the `<script>` tag — they were redundant because the same values come from your bot config on the backend:
96
+
97
+ - `apiUrl` — production URL is now baked into the bundle
98
+ - `botName`, `position`, `primaryColor`, `data-bot-name`, `data-position`, `data-primary-color`, `data-api-url`
99
+
100
+ If you were setting these from snippet config, the dashboard values now win. Visit **Admin → Bots → Design** to confirm the bot name and colors look right after upgrading.
101
+
102
+ ---
103
+
120
104
  ## License
121
105
 
122
106
  MIT © [BotIQ (LocDo.Tech)](https://botiq.vn)
package/dist/sdk/index.js CHANGED
@@ -1,2 +1,2 @@
1
- import { t as e } from "./npm-Dbo9SB2H.js";
1
+ import { t as e } from "./npm-6G46H82M.js";
2
2
  export { e as init };
@@ -1,69 +1,106 @@
1
1
  //#region src/core/config.ts
2
- var e = {
3
- colors: {
4
- primary: "#F97316",
5
- header: "#F97316",
6
- userBubble: "#F97316",
7
- botBubble: "#1A1A1A",
8
- background: "#000000",
9
- text: "#FFFFFF"
10
- },
11
- layout: {
12
- position: "bottom-right",
13
- buttonShape: "circle",
14
- width: 360,
15
- height: 520
16
- },
17
- content: {
18
- greeting: "Xin chào! Tôi có thể giúp gì cho bạn?",
19
- suggestionChips: [],
20
- placeholder: "Nhắn tin..."
21
- },
22
- font: "inter"
23
- }, t = /^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})$/;
24
- function n(e) {
25
- let n = e.primaryColor ?? "";
2
+ var e = "https://bot-q-backend.vercel.app", t = {
3
+ name: "BotIQ",
4
+ design: {
5
+ colors: {
6
+ primary: "#F97316",
7
+ header: "#F97316",
8
+ userBubble: "#F97316",
9
+ botBubble: "#1A1A1A",
10
+ background: "#000000",
11
+ text: "#FFFFFF"
12
+ },
13
+ layout: {
14
+ position: "bottom-right",
15
+ buttonShape: "circle",
16
+ width: 360,
17
+ height: 520
18
+ },
19
+ content: {
20
+ greeting: "Xin chào! Tôi có thể giúp gì cho bạn?",
21
+ suggestionChips: [],
22
+ placeholder: "Nhắn tin..."
23
+ },
24
+ font: "inter"
25
+ }
26
+ }, 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
+ "inter",
28
+ "plus-jakarta",
29
+ "poppins",
30
+ "nunito",
31
+ "dm-sans",
32
+ "raleway",
33
+ "lato",
34
+ "playfair"
35
+ ]);
36
+ function o(e) {
37
+ let t = e.colors;
38
+ if (![
39
+ t.primary,
40
+ t.header,
41
+ t.userBubble,
42
+ t.botBubble,
43
+ t.background,
44
+ t.text
45
+ ].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);
48
+ }
49
+ async function s(e, n) {
50
+ try {
51
+ let r = await fetch(`${n}/widget/meta`, {
52
+ headers: { "X-Api-Key": e },
53
+ signal: AbortSignal.timeout(5e3)
54
+ });
55
+ if (!r.ok) return t;
56
+ 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
60
+ };
61
+ } catch {
62
+ return t;
63
+ }
64
+ }
65
+ function c(t) {
26
66
  return {
27
- apiKey: e.apiKey,
28
- apiUrl: (e.apiUrl ?? "https://api.botiq.vn").replace(/\/$/, ""),
29
- botName: e.botName ?? "BotIQ",
30
- position: e.position === "bottom-left" ? "bottom-left" : "bottom-right",
31
- primaryColor: t.test(n) ? n : "#F97316"
67
+ apiKey: t.apiKey,
68
+ apiUrl: e
32
69
  };
33
70
  }
34
71
  //#endregion
35
72
  //#region src/core/session.ts
36
- var r = "botiq:sessionId", i = "botiq:history:", a = 10;
37
- function o() {
73
+ var l = "botiq:sessionId", u = "botiq:history:", d = 10;
74
+ function f() {
38
75
  try {
39
- let e = localStorage.getItem(r);
40
- return e || (e = crypto.randomUUID(), localStorage.setItem(r, e)), e;
76
+ let e = localStorage.getItem(l);
77
+ return e || (e = crypto.randomUUID(), localStorage.setItem(l, e)), e;
41
78
  } catch {
42
79
  return crypto.randomUUID();
43
80
  }
44
81
  }
45
- function s(e) {
82
+ function p(e) {
46
83
  try {
47
- let t = localStorage.getItem(i + e);
84
+ let t = localStorage.getItem(u + e);
48
85
  return t ? JSON.parse(t) : [];
49
86
  } catch {
50
87
  return [];
51
88
  }
52
89
  }
53
- function c(e, t) {
90
+ function m(e, t) {
54
91
  try {
55
- let n = [...s(e), ...t].slice(-a);
56
- localStorage.setItem(i + e, JSON.stringify(n));
92
+ let n = [...p(e), ...t].slice(-d);
93
+ localStorage.setItem(u + e, JSON.stringify(n));
57
94
  } catch {}
58
95
  }
59
96
  //#endregion
60
97
  //#region src/core/api.ts
61
- var l = {
98
+ var h = {
62
99
  401: "API key không hợp lệ.",
63
100
  403: "Widget không được phép trên trang này.",
64
101
  429: "Bot đã đạt giới hạn tin nhắn tháng này."
65
102
  };
66
- async function u(e, t, n, r, i) {
103
+ async function g(e, t, n, r, i) {
67
104
  try {
68
105
  let a = await fetch(`${e}/widget/chat`, {
69
106
  method: "POST",
@@ -77,35 +114,35 @@ async function u(e, t, n, r, i) {
77
114
  history: i
78
115
  })
79
116
  });
80
- return a.ok ? (await a.json()).reply || "Không có phản hồi từ bot." : l[a.status] ?? "Đang gặp sự cố kỹ thuật, vui lòng liên hệ trực tiếp.";
117
+ return a.ok ? (await a.json()).reply || "Không có 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.";
81
118
  } catch {
82
119
  return "Xin lỗi, không thể kết nối. Vui lòng thử lại.";
83
120
  }
84
121
  }
85
122
  //#endregion
86
123
  //#region src/core/state.ts
87
- var d = {
124
+ var _ = {
88
125
  messages: [],
89
126
  isLoading: !1,
90
127
  isOpen: !1
91
- }, f = /* @__PURE__ */ new Set();
92
- function p() {
93
- return d;
128
+ }, v = /* @__PURE__ */ new Set();
129
+ function y() {
130
+ return _;
94
131
  }
95
- function m(e) {
96
- Object.assign(d, e);
132
+ function b(e) {
133
+ Object.assign(_, e);
97
134
  let t = {
98
- ...d,
99
- messages: [...d.messages]
135
+ ..._,
136
+ messages: [..._.messages]
100
137
  };
101
- f.forEach((e) => e(t));
138
+ v.forEach((e) => e(t));
102
139
  }
103
- function h(e) {
104
- return f.add(e), () => f.delete(e);
140
+ function x(e) {
141
+ return v.add(e), () => v.delete(e);
105
142
  }
106
143
  //#endregion
107
144
  //#region src/core/styles.ts
108
- var g = {
145
+ var S = {
109
146
  inter: {
110
147
  url: "https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap",
111
148
  family: "'Inter', system-ui, -apple-system, sans-serif"
@@ -117,10 +154,30 @@ var g = {
117
154
  poppins: {
118
155
  url: "https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600&display=swap",
119
156
  family: "'Poppins', system-ui, -apple-system, sans-serif"
157
+ },
158
+ nunito: {
159
+ url: "https://fonts.googleapis.com/css2?family=Nunito:wght@400;500;600&display=swap",
160
+ family: "'Nunito', system-ui, -apple-system, sans-serif"
161
+ },
162
+ "dm-sans": {
163
+ url: "https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600&display=swap",
164
+ family: "'DM Sans', system-ui, -apple-system, sans-serif"
165
+ },
166
+ raleway: {
167
+ url: "https://fonts.googleapis.com/css2?family=Raleway:wght@400;500;600&display=swap",
168
+ family: "'Raleway', system-ui, -apple-system, sans-serif"
169
+ },
170
+ lato: {
171
+ url: "https://fonts.googleapis.com/css2?family=Lato:wght@400;700&display=swap",
172
+ family: "'Lato', system-ui, -apple-system, sans-serif"
173
+ },
174
+ playfair: {
175
+ url: "https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;500;600&display=swap",
176
+ family: "'Playfair Display', Georgia, serif"
120
177
  }
121
178
  };
122
- function _(e) {
123
- let { colors: t, layout: n, font: r } = e, i = g[r] ?? g.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;";
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;";
124
181
  return `
125
182
  @import url('${i.url}');
126
183
 
@@ -218,6 +275,7 @@ function _(e) {
218
275
  align-items: center;
219
276
  justify-content: center;
220
277
  flex-shrink: 0;
278
+ overflow: hidden;
221
279
  }
222
280
 
223
281
  .avatar svg { width: 20px; height: 20px; fill: #fff; }
@@ -377,6 +435,23 @@ function _(e) {
377
435
 
378
436
  .input-row { display: flex; align-items: flex-end; gap: 8px; }
379
437
 
438
+ /* Subtle attribution — half-opacity, hover lifts to full. Doesn't steal focus
439
+ from tenant branding but reminds the customer this is powered by BotIQ. */
440
+ .botiq-badge {
441
+ display: block;
442
+ text-align: center;
443
+ font-size: 10px;
444
+ font-weight: 400;
445
+ color: var(--color-text);
446
+ opacity: 0.4;
447
+ text-decoration: none;
448
+ margin-top: 6px;
449
+ letter-spacing: 0.02em;
450
+ transition: opacity .15s;
451
+ }
452
+ .botiq-badge:hover { opacity: 0.85; }
453
+ .botiq-badge-name { font-weight: 600; color: var(--color-primary); }
454
+
380
455
  .input {
381
456
  flex: 1;
382
457
  min-height: 38px;
@@ -420,23 +495,26 @@ function _(e) {
420
495
  }
421
496
  //#endregion
422
497
  //#region src/core/ui.ts
423
- var v = "<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>", y = "<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>", b = "<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>", x = "<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>", S = "<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>";
424
- function C(e) {
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;
501
+ }
502
+ function j(e) {
425
503
  return e.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#x27;");
426
504
  }
427
- function w(e, t, n, r) {
428
- let i, a, o, s, c, l, { content: u } = t;
429
- function d(n) {
430
- n.setAttribute("data-position", t.layout.position), i = n.attachShadow({ mode: "open" });
431
- let d = document.createElement("style");
432
- d.textContent = _(t), i.appendChild(d), a = document.createElement("button"), a.className = "bubble", a.setAttribute("aria-label", `Open ${e.botName} chat`), a.innerHTML = `
433
- <span class="icon-chat">${v}</span>
434
- <span class="icon-close">${y}</span>
435
- `, a.addEventListener("click", r), i.appendChild(a), o = document.createElement("div"), o.className = "chat-window", o.setAttribute("role", "dialog"), o.setAttribute("aria-label", `${e.botName} chat`), o.innerHTML = `
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" });
509
+ 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 = `
436
514
  <div class="chat-header">
437
- <div class="avatar">${b}</div>
515
+ <div class="avatar">${A(a.avatar)}</div>
438
516
  <div class="header-text">
439
- <span class="bot-name">${C(e.botName)}</span>
517
+ <span class="bot-name">${j(i)}</span>
440
518
  <span class="bot-status">Trực tuyến</span>
441
519
  </div>
442
520
  <button class="close-btn" aria-label="Close chat">×</button>
@@ -446,108 +524,101 @@ function w(e, t, n, r) {
446
524
  <div class="input-row">
447
525
  <textarea
448
526
  class="input"
449
- placeholder="${C(u.placeholder)}"
527
+ placeholder="${j(f.placeholder)}"
450
528
  rows="1"
451
529
  maxlength="2000"
452
530
  aria-label="Message input"
453
531
  ></textarea>
454
532
  <button class="send-btn" aria-label="Send message">
455
- ${x}
533
+ ${O}
456
534
  </button>
457
535
  </div>
536
+ <a class="botiq-badge" href="${w}" target="_blank" rel="noopener noreferrer">
537
+ Powered by <span class="botiq-badge-name">BotIQ</span>
538
+ </a>
458
539
  </div>
459
- `, o.querySelector(".close-btn").addEventListener("click", r), s = o.querySelector("#messages-container"), c = o.querySelector(".input"), l = o.querySelector(".send-btn"), c.addEventListener("input", () => {
460
- c.style.height = "auto", c.style.height = Math.min(c.scrollHeight, 100) + "px";
461
- }), c.addEventListener("keydown", (e) => {
462
- e.key === "Enter" && !e.shiftKey && (e.preventDefault(), f());
463
- }), l.addEventListener("click", f), i.appendChild(o);
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);
464
545
  }
465
- function f() {
466
- let e = c.value.trim();
467
- !e || l.disabled || (c.value = "", c.style.height = "auto", n(e));
546
+ function m() {
547
+ let e = u.value.trim();
548
+ !e || d.disabled || (u.value = "", u.style.height = "auto", n(e));
468
549
  }
469
- function p(e) {
550
+ function h(e) {
470
551
  if (e.messages.length === 0 && !e.isLoading) {
471
- let e = u.suggestionChips.length > 0 ? `<div class="chips">${u.suggestionChips.map((e) => `<button class="chip">${C(e)}</button>`).join("")}</div>` : "";
472
- s.innerHTML = `
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 = `
473
554
  <div class="empty-state">
474
- ${S}
475
- <span class="greeting">${C(u.greeting)}</span>
555
+ ${k}
556
+ <span class="greeting">${j(f.greeting)}</span>
476
557
  ${e}
477
558
  </div>
478
- `, s.querySelectorAll(".chip").forEach((e) => {
559
+ `, l.querySelectorAll(".chip").forEach((e) => {
479
560
  e.addEventListener("click", () => n(e.textContent ?? ""));
480
561
  });
481
562
  return;
482
563
  }
483
564
  let t = e.messages.map((e) => `
484
565
  <div class="message ${e.role}">
485
- <div class="message-bubble">${C(e.content)}</div>
566
+ <div class="message-bubble">${j(e.content)}</div>
486
567
  </div>
487
568
  `).join(""), r = e.isLoading ? "<div class=\"typing\"><span></span><span></span><span></span></div>" : "";
488
- s.innerHTML = t + r, s.scrollTop = s.scrollHeight;
569
+ l.innerHTML = t + r, l.scrollTop = l.scrollHeight;
489
570
  }
490
- function m(t) {
491
- t.isOpen ? (o.classList.add("open"), a.classList.add("open"), a.setAttribute("aria-label", `Close ${e.botName} chat`), requestAnimationFrame(() => c.focus())) : (o.classList.remove("open"), a.classList.remove("open"), a.setAttribute("aria-label", `Open ${e.botName} chat`)), l.disabled = t.isLoading, p(t);
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);
492
573
  }
493
574
  return {
494
- mount: d,
495
- update: m
575
+ mount: p,
576
+ update: g
496
577
  };
497
578
  }
498
579
  //#endregion
499
580
  //#region src/builds/npm/index.ts
500
- function T(t) {
501
- if (!t.apiKey) return console.warn("[BotIQ] apiKey is required"), () => void 0;
502
- let r = n(t), i = {
503
- ...e,
504
- colors: {
505
- ...e.colors,
506
- primary: r.primaryColor,
507
- header: r.primaryColor,
508
- userBubble: r.primaryColor
509
- },
510
- layout: {
511
- ...e.layout,
512
- position: r.position
513
- }
514
- }, a = o();
515
- m({ messages: s(a) });
516
- let l = document.createElement("div");
517
- document.body.appendChild(l);
518
- let d = w(r, i, g, _);
519
- d.mount(l);
520
- let f = h((e) => d.update(e));
521
- d.update(p());
522
- function g(e) {
523
- let t = p();
524
- if (t.isLoading) return;
525
- let n = {
581
+ function N(e) {
582
+ if (!e.apiKey) return console.warn("[BotIQ] apiKey is required"), () => void 0;
583
+ let t = c(e), n = f();
584
+ b({ messages: p(n) });
585
+ let r = document.createElement("div");
586
+ document.body.appendChild(r);
587
+ let i = () => void 0, a = !1;
588
+ s(t.apiKey, t.apiUrl).then((e) => {
589
+ if (a) return;
590
+ let n = M(t, e, o, l);
591
+ n.mount(r), i = x((e) => n.update(e)), n.update(y());
592
+ });
593
+ function o(e) {
594
+ let r = y();
595
+ if (r.isLoading) return;
596
+ let i = {
526
597
  role: "user",
527
598
  content: e
528
599
  };
529
- m({
530
- messages: [...t.messages, n],
600
+ b({
601
+ messages: [...r.messages, i],
531
602
  isLoading: !0
532
- }), u(r.apiUrl, r.apiKey, a, e, t.messages).then((e) => {
603
+ }), g(t.apiUrl, t.apiKey, n, e, r.messages).then((e) => {
533
604
  let t = {
534
605
  role: "assistant",
535
606
  content: e
536
607
  };
537
- m({
538
- messages: [...p().messages, t],
608
+ b({
609
+ messages: [...y().messages, t],
539
610
  isLoading: !1
540
- }), c(a, [n, t]);
611
+ }), m(n, [i, t]);
541
612
  }).catch(() => {
542
- m({ isLoading: !1 });
613
+ b({ isLoading: !1 });
543
614
  });
544
615
  }
545
- function _() {
546
- m({ isOpen: !p().isOpen });
616
+ function l() {
617
+ b({ isOpen: !y().isOpen });
547
618
  }
548
619
  return () => {
549
- f(), l.remove();
620
+ a = !0, i(), r.remove();
550
621
  };
551
622
  }
552
623
  //#endregion
553
- export { T as t };
624
+ export { N as t };
package/dist/sdk/react.js CHANGED
@@ -1,4 +1,4 @@
1
- import { t as e } from "./npm-Dbo9SB2H.js";
1
+ import { t as e } from "./npm-6G46H82M.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.d.ts CHANGED
@@ -1,50 +1,13 @@
1
- import { PropType } from 'vue';
2
1
  import { BotIQConfig } from './index.ts';
2
+ export type { BotIQConfig };
3
3
  export declare const BotIQWidget: import('vue').DefineComponent<import('vue').ExtractPropTypes<{
4
4
  apiKey: {
5
5
  type: StringConstructor;
6
6
  required: true;
7
7
  };
8
- apiUrl: {
9
- type: StringConstructor;
10
- default: undefined;
11
- };
12
- botName: {
13
- type: StringConstructor;
14
- default: undefined;
15
- };
16
- position: {
17
- type: PropType<BotIQConfig["position"]>;
18
- default: undefined;
19
- };
20
- primaryColor: {
21
- type: StringConstructor;
22
- default: undefined;
23
- };
24
8
  }>, () => null, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<import('vue').ExtractPropTypes<{
25
9
  apiKey: {
26
10
  type: StringConstructor;
27
11
  required: true;
28
12
  };
29
- apiUrl: {
30
- type: StringConstructor;
31
- default: undefined;
32
- };
33
- botName: {
34
- type: StringConstructor;
35
- default: undefined;
36
- };
37
- position: {
38
- type: PropType<BotIQConfig["position"]>;
39
- default: undefined;
40
- };
41
- primaryColor: {
42
- type: StringConstructor;
43
- default: undefined;
44
- };
45
- }>> & Readonly<{}>, {
46
- primaryColor: string;
47
- apiUrl: string;
48
- botName: string;
49
- position: "bottom-right" | "bottom-left" | undefined;
50
- }, {}, {}, {}, string, import('vue').ComponentProvideOptions, true, {}, any>;
13
+ }>> & Readonly<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, true, {}, any>;
package/dist/sdk/vue.js CHANGED
@@ -1,40 +1,16 @@
1
- import { t as e } from "./npm-Dbo9SB2H.js";
1
+ import { t as e } from "./npm-6G46H82M.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({
5
5
  name: "BotIQWidget",
6
- props: {
7
- apiKey: {
8
- type: String,
9
- required: !0
10
- },
11
- apiUrl: {
12
- type: String,
13
- default: void 0
14
- },
15
- botName: {
16
- type: String,
17
- default: void 0
18
- },
19
- position: {
20
- type: String,
21
- default: void 0
22
- },
23
- primaryColor: {
24
- type: String,
25
- default: void 0
26
- }
27
- },
6
+ props: { apiKey: {
7
+ type: String,
8
+ required: !0
9
+ } },
28
10
  setup(t) {
29
11
  let i;
30
12
  return n(() => {
31
- i = e({
32
- apiKey: t.apiKey,
33
- apiUrl: t.apiUrl,
34
- botName: t.botName,
35
- position: t.position,
36
- primaryColor: t.primaryColor
37
- });
13
+ i = e({ apiKey: t.apiKey });
38
14
  }), r(() => {
39
15
  i?.();
40
16
  }), () => null;
package/package.json CHANGED
@@ -1,87 +1,87 @@
1
- {
2
- "name": "@locdo.tech/botiq-chat-sdk",
3
- "version": "0.1.0",
4
- "description": "BotIQ chat widget SDK — embed AI chatbot into any website with vanilla JS, React, or Vue.",
5
- "keywords": [
6
- "botiq",
7
- "chatbot",
8
- "ai",
9
- "widget",
10
- "react",
11
- "vue",
12
- "customer-support"
13
- ],
14
- "license": "MIT",
15
- "author": "BotIQ (LocDo.Tech)",
16
- "homepage": "https://botiq.vn",
17
- "repository": {
18
- "type": "git",
19
- "url": "git+https://github.com/locdo-tech/botiq.git",
20
- "directory": "apps/widget"
21
- },
22
- "bugs": {
23
- "url": "https://github.com/locdo-tech/botiq/issues"
24
- },
25
- "type": "module",
26
- "main": "./dist/sdk/index.js",
27
- "module": "./dist/sdk/index.js",
28
- "types": "./dist/sdk/index.d.ts",
29
- "exports": {
30
- ".": {
31
- "import": "./dist/sdk/index.js",
32
- "types": "./dist/sdk/index.d.ts"
33
- },
34
- "./react": {
35
- "import": "./dist/sdk/react.js",
36
- "types": "./dist/sdk/react.d.ts"
37
- },
38
- "./vue": {
39
- "import": "./dist/sdk/vue.js",
40
- "types": "./dist/sdk/vue.d.ts"
41
- }
42
- },
43
- "files": [
44
- "dist/sdk",
45
- "README.md",
46
- "LICENSE"
47
- ],
48
- "publishConfig": {
49
- "access": "public"
50
- },
51
- "sideEffects": false,
52
- "peerDependencies": {
53
- "react": ">=17",
54
- "vue": ">=3"
55
- },
56
- "peerDependenciesMeta": {
57
- "react": {
58
- "optional": true
59
- },
60
- "vue": {
61
- "optional": true
62
- }
63
- },
64
- "scripts": {
65
- "dev": "vite",
66
- "build:cdn": "tsc && vite build",
67
- "build:sdk": "tsc -p tsconfig.sdk.json --noEmit && vite build --config vite.config.sdk.ts",
68
- "build": "npm run build:cdn && npm run build:sdk",
69
- "preview": "vite preview",
70
- "test": "vitest run",
71
- "test:watch": "vitest",
72
- "prepublishOnly": "npm run build:sdk"
73
- },
74
- "devDependencies": {
75
- "@types/react": "^19.2.14",
76
- "@types/react-dom": "^19.2.3",
77
- "@vitest/coverage-v8": "^4.1.6",
78
- "jsdom": "^29.1.1",
79
- "react": "^19.2.6",
80
- "react-dom": "^19.2.6",
81
- "typescript": "~6.0.2",
82
- "vite": "^8.0.12",
83
- "vite-plugin-dts": "^5.0.0",
84
- "vitest": "^4.1.6",
85
- "vue": "^3.5.34"
86
- }
87
- }
1
+ {
2
+ "name": "@locdo.tech/botiq-chat-sdk",
3
+ "version": "0.3.0",
4
+ "description": "BotIQ chat widget SDK — embed AI chatbot into any website with vanilla JS, React, or Vue.",
5
+ "keywords": [
6
+ "botiq",
7
+ "chatbot",
8
+ "ai",
9
+ "widget",
10
+ "react",
11
+ "vue",
12
+ "customer-support"
13
+ ],
14
+ "license": "MIT",
15
+ "author": "BotIQ (LocDo.Tech)",
16
+ "homepage": "https://botiq.vn",
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/locdo-tech/botiq.git",
20
+ "directory": "apps/widget"
21
+ },
22
+ "bugs": {
23
+ "url": "https://github.com/locdo-tech/botiq/issues"
24
+ },
25
+ "type": "module",
26
+ "main": "./dist/sdk/index.js",
27
+ "module": "./dist/sdk/index.js",
28
+ "types": "./dist/sdk/index.d.ts",
29
+ "exports": {
30
+ ".": {
31
+ "import": "./dist/sdk/index.js",
32
+ "types": "./dist/sdk/index.d.ts"
33
+ },
34
+ "./react": {
35
+ "import": "./dist/sdk/react.js",
36
+ "types": "./dist/sdk/react.d.ts"
37
+ },
38
+ "./vue": {
39
+ "import": "./dist/sdk/vue.js",
40
+ "types": "./dist/sdk/vue.d.ts"
41
+ }
42
+ },
43
+ "files": [
44
+ "dist/sdk",
45
+ "README.md",
46
+ "LICENSE"
47
+ ],
48
+ "publishConfig": {
49
+ "access": "public"
50
+ },
51
+ "sideEffects": false,
52
+ "peerDependencies": {
53
+ "react": ">=17",
54
+ "vue": ">=3"
55
+ },
56
+ "peerDependenciesMeta": {
57
+ "react": {
58
+ "optional": true
59
+ },
60
+ "vue": {
61
+ "optional": true
62
+ }
63
+ },
64
+ "scripts": {
65
+ "dev": "vite",
66
+ "build:cdn": "tsc && vite build",
67
+ "build:sdk": "tsc -p tsconfig.sdk.json --noEmit && vite build --config vite.config.sdk.ts",
68
+ "build": "npm run build:cdn && npm run build:sdk",
69
+ "preview": "vite preview",
70
+ "test": "vitest run",
71
+ "test:watch": "vitest",
72
+ "prepublishOnly": "npm run build:sdk"
73
+ },
74
+ "devDependencies": {
75
+ "@types/react": "^19.2.14",
76
+ "@types/react-dom": "^19.2.3",
77
+ "@vitest/coverage-v8": "^4.1.6",
78
+ "jsdom": "^29.1.1",
79
+ "react": "^19.2.6",
80
+ "react-dom": "^19.2.6",
81
+ "typescript": "~6.0.2",
82
+ "vite": "^8.0.12",
83
+ "vite-plugin-dts": "^5.0.0",
84
+ "vitest": "^4.1.6",
85
+ "vue": "^3.5.34"
86
+ }
87
+ }