@locdo.tech/botiq-chat-sdk 0.1.0 → 0.2.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-Bo1fPZM3.js";
2
2
  export { e as init };
@@ -1,69 +1,101 @@
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 = "http://localhost:3001", 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
+ ]);
31
+ function o(e) {
32
+ let t = e.colors;
33
+ if (![
34
+ t.primary,
35
+ t.header,
36
+ t.userBubble,
37
+ t.botBubble,
38
+ t.background,
39
+ t.text
40
+ ].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);
43
+ }
44
+ async function s(e, n) {
45
+ try {
46
+ let r = await fetch(`${n}/widget/meta`, {
47
+ headers: { "X-Api-Key": e },
48
+ signal: AbortSignal.timeout(5e3)
49
+ });
50
+ if (!r.ok) return t;
51
+ 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
55
+ };
56
+ } catch {
57
+ return t;
58
+ }
59
+ }
60
+ function c(t) {
26
61
  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"
62
+ apiKey: t.apiKey,
63
+ apiUrl: e
32
64
  };
33
65
  }
34
66
  //#endregion
35
67
  //#region src/core/session.ts
36
- var r = "botiq:sessionId", i = "botiq:history:", a = 10;
37
- function o() {
68
+ var l = "botiq:sessionId", u = "botiq:history:", d = 10;
69
+ function f() {
38
70
  try {
39
- let e = localStorage.getItem(r);
40
- return e || (e = crypto.randomUUID(), localStorage.setItem(r, e)), e;
71
+ let e = localStorage.getItem(l);
72
+ return e || (e = crypto.randomUUID(), localStorage.setItem(l, e)), e;
41
73
  } catch {
42
74
  return crypto.randomUUID();
43
75
  }
44
76
  }
45
- function s(e) {
77
+ function p(e) {
46
78
  try {
47
- let t = localStorage.getItem(i + e);
79
+ let t = localStorage.getItem(u + e);
48
80
  return t ? JSON.parse(t) : [];
49
81
  } catch {
50
82
  return [];
51
83
  }
52
84
  }
53
- function c(e, t) {
85
+ function m(e, t) {
54
86
  try {
55
- let n = [...s(e), ...t].slice(-a);
56
- localStorage.setItem(i + e, JSON.stringify(n));
87
+ let n = [...p(e), ...t].slice(-d);
88
+ localStorage.setItem(u + e, JSON.stringify(n));
57
89
  } catch {}
58
90
  }
59
91
  //#endregion
60
92
  //#region src/core/api.ts
61
- var l = {
93
+ var h = {
62
94
  401: "API key không hợp lệ.",
63
95
  403: "Widget không được phép trên trang này.",
64
96
  429: "Bot đã đạt giới hạn tin nhắn tháng này."
65
97
  };
66
- async function u(e, t, n, r, i) {
98
+ async function g(e, t, n, r, i) {
67
99
  try {
68
100
  let a = await fetch(`${e}/widget/chat`, {
69
101
  method: "POST",
@@ -77,35 +109,35 @@ async function u(e, t, n, r, i) {
77
109
  history: i
78
110
  })
79
111
  });
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.";
112
+ 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
113
  } catch {
82
114
  return "Xin lỗi, không thể kết nối. Vui lòng thử lại.";
83
115
  }
84
116
  }
85
117
  //#endregion
86
118
  //#region src/core/state.ts
87
- var d = {
119
+ var _ = {
88
120
  messages: [],
89
121
  isLoading: !1,
90
122
  isOpen: !1
91
- }, f = /* @__PURE__ */ new Set();
92
- function p() {
93
- return d;
123
+ }, v = /* @__PURE__ */ new Set();
124
+ function y() {
125
+ return _;
94
126
  }
95
- function m(e) {
96
- Object.assign(d, e);
127
+ function b(e) {
128
+ Object.assign(_, e);
97
129
  let t = {
98
- ...d,
99
- messages: [...d.messages]
130
+ ..._,
131
+ messages: [..._.messages]
100
132
  };
101
- f.forEach((e) => e(t));
133
+ v.forEach((e) => e(t));
102
134
  }
103
- function h(e) {
104
- return f.add(e), () => f.delete(e);
135
+ function x(e) {
136
+ return v.add(e), () => v.delete(e);
105
137
  }
106
138
  //#endregion
107
139
  //#region src/core/styles.ts
108
- var g = {
140
+ var S = {
109
141
  inter: {
110
142
  url: "https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap",
111
143
  family: "'Inter', system-ui, -apple-system, sans-serif"
@@ -119,8 +151,8 @@ var g = {
119
151
  family: "'Poppins', system-ui, -apple-system, sans-serif"
120
152
  }
121
153
  };
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;";
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;";
124
156
  return `
125
157
  @import url('${i.url}');
126
158
 
@@ -377,6 +409,23 @@ function _(e) {
377
409
 
378
410
  .input-row { display: flex; align-items: flex-end; gap: 8px; }
379
411
 
412
+ /* Subtle attribution — half-opacity, hover lifts to full. Doesn't steal focus
413
+ from tenant branding but reminds the customer this is powered by BotIQ. */
414
+ .botiq-badge {
415
+ display: block;
416
+ text-align: center;
417
+ font-size: 10px;
418
+ font-weight: 400;
419
+ color: var(--color-text);
420
+ opacity: 0.4;
421
+ text-decoration: none;
422
+ margin-top: 6px;
423
+ letter-spacing: 0.02em;
424
+ transition: opacity .15s;
425
+ }
426
+ .botiq-badge:hover { opacity: 0.85; }
427
+ .botiq-badge-name { font-weight: 600; color: var(--color-primary); }
428
+
380
429
  .input {
381
430
  flex: 1;
382
431
  min-height: 38px;
@@ -420,23 +469,23 @@ function _(e) {
420
469
  }
421
470
  //#endregion
422
471
  //#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) {
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) {
425
474
  return e.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#x27;");
426
475
  }
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 = `
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" });
480
+ 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 = `
436
485
  <div class="chat-header">
437
- <div class="avatar">${b}</div>
486
+ <div class="avatar">${D}</div>
438
487
  <div class="header-text">
439
- <span class="bot-name">${C(e.botName)}</span>
488
+ <span class="bot-name">${A(i)}</span>
440
489
  <span class="bot-status">Trực tuyến</span>
441
490
  </div>
442
491
  <button class="close-btn" aria-label="Close chat">×</button>
@@ -446,108 +495,101 @@ function w(e, t, n, r) {
446
495
  <div class="input-row">
447
496
  <textarea
448
497
  class="input"
449
- placeholder="${C(u.placeholder)}"
498
+ placeholder="${A(f.placeholder)}"
450
499
  rows="1"
451
500
  maxlength="2000"
452
501
  aria-label="Message input"
453
502
  ></textarea>
454
503
  <button class="send-btn" aria-label="Send message">
455
- ${x}
504
+ ${O}
456
505
  </button>
457
506
  </div>
507
+ <a class="botiq-badge" href="${w}" target="_blank" rel="noopener noreferrer">
508
+ Powered by <span class="botiq-badge-name">BotIQ</span>
509
+ </a>
458
510
  </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);
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);
464
516
  }
465
- function f() {
466
- let e = c.value.trim();
467
- !e || l.disabled || (c.value = "", c.style.height = "auto", n(e));
517
+ function m() {
518
+ let e = u.value.trim();
519
+ !e || d.disabled || (u.value = "", u.style.height = "auto", n(e));
468
520
  }
469
- function p(e) {
521
+ function h(e) {
470
522
  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 = `
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 = `
473
525
  <div class="empty-state">
474
- ${S}
475
- <span class="greeting">${C(u.greeting)}</span>
526
+ ${k}
527
+ <span class="greeting">${A(f.greeting)}</span>
476
528
  ${e}
477
529
  </div>
478
- `, s.querySelectorAll(".chip").forEach((e) => {
530
+ `, l.querySelectorAll(".chip").forEach((e) => {
479
531
  e.addEventListener("click", () => n(e.textContent ?? ""));
480
532
  });
481
533
  return;
482
534
  }
483
535
  let t = e.messages.map((e) => `
484
536
  <div class="message ${e.role}">
485
- <div class="message-bubble">${C(e.content)}</div>
537
+ <div class="message-bubble">${A(e.content)}</div>
486
538
  </div>
487
539
  `).join(""), r = e.isLoading ? "<div class=\"typing\"><span></span><span></span><span></span></div>" : "";
488
- s.innerHTML = t + r, s.scrollTop = s.scrollHeight;
540
+ l.innerHTML = t + r, l.scrollTop = l.scrollHeight;
489
541
  }
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);
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);
492
544
  }
493
545
  return {
494
- mount: d,
495
- update: m
546
+ mount: p,
547
+ update: g
496
548
  };
497
549
  }
498
550
  //#endregion
499
551
  //#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 = {
552
+ function M(e) {
553
+ if (!e.apiKey) return console.warn("[BotIQ] apiKey is required"), () => void 0;
554
+ let t = c(e), n = f();
555
+ b({ messages: p(n) });
556
+ let r = document.createElement("div");
557
+ document.body.appendChild(r);
558
+ let i = () => void 0, a = !1;
559
+ s(t.apiKey, t.apiUrl).then((e) => {
560
+ if (a) return;
561
+ let n = j(t, e, o, l);
562
+ n.mount(r), i = x((e) => n.update(e)), n.update(y());
563
+ });
564
+ function o(e) {
565
+ let r = y();
566
+ if (r.isLoading) return;
567
+ let i = {
526
568
  role: "user",
527
569
  content: e
528
570
  };
529
- m({
530
- messages: [...t.messages, n],
571
+ b({
572
+ messages: [...r.messages, i],
531
573
  isLoading: !0
532
- }), u(r.apiUrl, r.apiKey, a, e, t.messages).then((e) => {
574
+ }), g(t.apiUrl, t.apiKey, n, e, r.messages).then((e) => {
533
575
  let t = {
534
576
  role: "assistant",
535
577
  content: e
536
578
  };
537
- m({
538
- messages: [...p().messages, t],
579
+ b({
580
+ messages: [...y().messages, t],
539
581
  isLoading: !1
540
- }), c(a, [n, t]);
582
+ }), m(n, [i, t]);
541
583
  }).catch(() => {
542
- m({ isLoading: !1 });
584
+ b({ isLoading: !1 });
543
585
  });
544
586
  }
545
- function _() {
546
- m({ isOpen: !p().isOpen });
587
+ function l() {
588
+ b({ isOpen: !y().isOpen });
547
589
  }
548
590
  return () => {
549
- f(), l.remove();
591
+ a = !0, i(), r.remove();
550
592
  };
551
593
  }
552
594
  //#endregion
553
- export { T as t };
595
+ export { M 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-Bo1fPZM3.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-Bo1fPZM3.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.2.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
+ }