@mobil80-dev/chatbot-widget 1.0.9 → 2.0.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.
Files changed (3) hide show
  1. package/README.md +66 -17
  2. package/index.js +297 -214
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -4,33 +4,60 @@ A lightweight JavaScript chatbot widget powered by VaultChat, designed to be emb
4
4
 
5
5
  ## ✨ Features
6
6
 
7
- - Floating chat button
7
+ - Floating chat button (fully customizable)
8
8
  - Chat card UI
9
- - API-based chatbot responses
10
- - No iframe
11
- - No dependencies
9
+ - API-based chatbot responses
10
+ - Light & Dark theme support
11
+ - No iframe
12
+ - No dependencies
12
13
  - Easy setup
13
14
 
14
15
  ## 📦 Installation
15
- ``` bash
16
+
17
+ ```bash
16
18
  npm install @mobil80-dev/chatbot-widget
17
19
  ```
18
20
 
21
+ ## 🎨 Theme (Light / Dark)
22
+
23
+ VaultChat supports light and dark themes to match your application UI.
24
+ This helps improve text visibility and appearance when used inside dark-themed websites.
25
+
26
+ ```
27
+ theme: 'light' | 'dark'
28
+ ```
29
+
30
+ ## ✅ Supported Button Types
31
+
32
+ You can use text, image, or custom HTML as the floating button.
33
+
34
+ ## ✅ Supported Button Shapes
35
+
36
+ You can use circle, square, pill as the floating button shape.
37
+
19
38
  ## 🌐 HTML / CSS
39
+
20
40
  ### 🚀 Usage
41
+
21
42
  ```
22
43
  <script type="module">
23
44
  import VaultChat from 'https://unpkg.com/@mobil80-dev/chatbot-widget/dist/index.js'
24
45
 
25
46
  VaultChat.init({
26
47
  apiKey: 'YOUR_API_KEY',
27
- primaryColor: '#7c3aed'
48
+ primaryColor: '#7c3aed',
49
+ theme: 'light' | 'dark'
50
+ buttonContent: '💬', // content | image Url | emote
51
+ buttonType: 'text', // text | image
52
+ buttonShape: 'circle' // circle | square | pill
28
53
  })
29
54
  </script>
30
55
  ```
31
56
 
32
57
  ## 🧩 Vue
58
+
33
59
  ### 🚀 Usage (Vue 3)
60
+
34
61
  ```
35
62
  import { onMounted } from 'vue'
36
63
  import VaultChat from '@mobil80-dev/chatbot-widget'
@@ -38,21 +65,31 @@ import VaultChat from '@mobil80-dev/chatbot-widget'
38
65
  onMounted(() => {
39
66
  VaultChat.init({
40
67
  apiKey: 'YOUR_API_KEY',
41
- primaryColor: '#7c3aed'
68
+ primaryColor: '#7c3aed',
69
+ theme: 'light' | 'dark'
70
+ buttonContent: '💬', // content | image Url | emote
71
+ buttonType: 'test', // text | image
72
+ buttonShape: 'circle' // circle | square | pill
42
73
  })
43
74
  })
44
75
  ```
76
+
45
77
  ## ⚛️ React
46
78
 
47
79
  ### 🚀 Usage
48
- ```
80
+
81
+ ```
49
82
  import { useEffect } from 'react'
50
83
  import VaultChat from '@mobil80-dev/chatbot-widget'
51
84
 
52
85
  useEffect(() => {
53
86
  VaultChat.init({
54
87
  apiKey: 'YOUR_API_KEY',
55
- primaryColor: '#7c3aed'
88
+ primaryColor: '#7c3aed',
89
+ theme: 'light' | 'dark'
90
+ buttonContent: '💬', // content | image Url | emote
91
+ buttonType: 'text', // text | image
92
+ buttonShape: 'circle' // circle | square | pill
56
93
  })
57
94
  }, [])
58
95
  ```
@@ -60,29 +97,41 @@ useEffect(() => {
60
97
  ## 🅰️ Angular
61
98
 
62
99
  ### 🚀 Usage
63
- ```
100
+
101
+ ```
64
102
  import VaultChat from '@mobil80-dev/chatbot-widget'
65
103
 
66
104
  VaultChat.init({
67
105
  apiKey: 'YOUR_API_KEY',
68
- primaryColor: '#7c3aed'
106
+ primaryColor: '#7c3aed',
107
+ theme: 'light' | 'dark'
108
+ buttonContent: '💬', // content | image Url | emote
109
+ buttonType: 'text', // text | image
110
+ buttonShape: 'circle' // circle | square | pill
69
111
  })
70
112
 
71
113
  ```
72
114
 
73
-
74
115
  📌 Call this inside ngOnInit() of your root or layout component.
75
116
 
76
- ## ⚙️ Configuration
117
+ ## ⚙️ Configuration Reference
118
+
77
119
  ```
78
- Option Type Description
79
- apiKey string Your VaultChat API key
80
- primaryColor string Primary UI theme color
120
+ | Option | Type | Description |
121
+ | ------------- | ------ | ----------------------------------- |
122
+ | apiKey | string | Your VaultChat API key |
123
+ | primaryColor | string | Primary UI color |
124
+ | theme | string | `light` or `dark` |
125
+ | buttonContent | string | text, image URL, |
126
+ | buttonType | string | `text` | `image` |
127
+ | buttonShape | string | `circle` | `square` | `pill` |
128
+
81
129
 
82
130
  ```
131
+
83
132
  ## 📄 License
84
133
 
85
134
  ```
86
135
  MIT
87
136
  Mobil80
88
- ```
137
+ ```
package/index.js CHANGED
@@ -1,232 +1,315 @@
1
- const VaultChat = {
2
- init (config = {}) {
3
- /* ============================
4
- THEME RESOLUTION
5
- ============================ */
6
- const theme =
7
- config.theme === 'dark'
8
- ? 'dark'
9
- : config.theme === 'light'
10
- ? 'light'
11
- : window.matchMedia('(prefers-color-scheme: dark)').matches
12
- ? 'dark'
13
- : 'light'
14
-
15
- const colors =
16
- theme === 'dark'
17
- ? {
18
- cardBg: '#0f172a',
19
- headerBorder: '#1e293b',
20
- text: '#e5e7eb',
21
- mutedText: '#94a3b8',
22
- inputBg: '#020617',
23
- inputBorder: '#334155',
24
- botBubble: '#1e293b',
25
- userText: '#ffffff',
26
- messagesBg: '#020617'
27
- }
28
- : {
29
- cardBg: '#ffffff',
30
- headerBorder: '#e5e7eb',
31
- text: '#0f172a',
32
- mutedText: '#64748b',
33
- inputBg: '#ffffff',
34
- inputBorder: '#cbd5f5',
35
- botBubble: '#e5e7eb',
36
- userText: '#ffffff',
37
- messagesBg: '#f8fafc'
38
- }
39
-
40
- const primaryColor = config.primaryColor || '#2563eb'
41
-
42
- /* ============================
43
- FLOATING BUTTON
44
- ============================ */
45
- const button = document.createElement('div')
46
-
47
- if (config.buttonHtml) {
48
- button.innerHTML = config.buttonHtml
49
- } else if (config.buttonIconUrl) {
50
- const img = document.createElement('img')
51
- img.src = config.buttonIconUrl
52
- img.style.width = '26px'
53
- img.style.height = '26px'
54
- img.style.objectFit = 'contain'
55
- button.appendChild(img)
56
- } else if (config.buttonText) {
57
- button.textContent = config.buttonText
58
- } else {
59
- button.textContent = '💬'
60
- }
61
-
62
- Object.assign(button.style, {
63
- position: 'fixed',
64
- bottom: '20px',
65
- right: '20px',
66
- width: '56px',
67
- height: '56px',
68
- borderRadius: '50%',
69
- background: primaryColor,
70
- color: '#fff',
71
- display: 'flex',
72
- alignItems: 'center',
73
- justifyContent: 'center',
74
- cursor: 'pointer',
75
- fontSize: '24px',
76
- zIndex: 999999
77
- })
1
+ /* =========================================================
2
+ GLOBAL SINGLETON REFERENCES
3
+ ========================================================= */
4
+ let vcButton = null
5
+ let vcCard = null
6
+ let loaderEl = null
7
+ let stylesInjected = false
8
+ let isOpen = false
78
9
 
79
- /* ============================
80
- CHAT CARD
81
- ============================ */
82
- const card = document.createElement('div')
83
-
84
- card.innerHTML = `
85
- <div style="
86
- padding:12px;
87
- border-bottom:1px solid ${colors.headerBorder};
88
- color:${colors.mutedText};
89
- font-size:14px;
90
- user-select:none">
91
- Powered by <strong>VaultChat</strong>
92
- </div>
93
-
94
- <div id="vc-messages" style="
95
- flex:1;
96
- padding:12px;
97
- overflow-y:auto;
98
- background:${colors.messagesBg};
99
- color:${colors.text}">
100
- </div>
101
-
102
- <div style="
103
- display:flex;
104
- padding:10px;
105
- border-top:1px solid ${colors.headerBorder};
106
- background:${colors.cardBg}">
107
- <input
108
- id="vc-input"
109
- placeholder="Type your message..."
110
- style="
111
- flex:1;
112
- padding:10px;
113
- border-radius:8px;
114
- border:1px solid ${colors.inputBorder};
115
- background:${colors.inputBg};
116
- color:${colors.text};
117
- outline:none"
118
- />
119
- <button id="vc-send" style="
120
- margin-left:8px;
121
- padding:0 14px;
122
- border:none;
123
- border-radius:8px;
124
- background:${primaryColor};
125
- color:white;
126
- cursor:pointer">
127
-
128
- </button>
129
- </div>
130
- `
131
-
132
- Object.assign(card.style, {
133
- position: 'fixed',
134
- bottom: '90px',
135
- right: '20px',
136
- width: '400px',
137
- height: '420px',
138
- background: colors.cardBg,
139
- borderRadius: '12px',
140
- boxShadow: '0 10px 30px rgba(0,0,0,.3)',
141
- zIndex: 999999,
142
- display: 'none',
143
- flexDirection: 'column'
144
- })
10
+ /* =========================================================
11
+ STYLE INJECTION (ONCE)
12
+ ========================================================= */
13
+ function injectStylesOnce () {
14
+ if (stylesInjected) return
15
+ stylesInjected = true
145
16
 
146
- let open = false
147
- button.onclick = () => {
148
- open = !open
149
- card.style.display = open ? 'flex' : 'none'
17
+ const style = document.createElement('style')
18
+ style.innerHTML = `
19
+ .vc-fab {
20
+ box-shadow: 0 8px 20px rgba(0,0,0,.25);
21
+ transition: box-shadow .2s ease, transform .2s ease;
22
+ }
23
+ .vc-fab:hover {
24
+ box-shadow: 0 12px 28px rgba(0,0,0,.3);
150
25
  }
151
26
 
152
- document.body.appendChild(button)
153
- document.body.appendChild(card)
154
-
155
- /* ============================
156
- CHAT LOGIC
157
- ============================ */
158
- const messages = card.querySelector('#vc-messages')
159
- const input = card.querySelector('#vc-input')
160
- const sendBtn = card.querySelector('#vc-send')
161
-
162
- function addUserMessage (text) {
163
- const div = document.createElement('div')
164
- div.style.cssText = `
165
- background:${primaryColor};
166
- color:${colors.userText};
167
- padding:8px 12px;
168
- border-radius:12px;
169
- max-width:80%;
170
- margin:8px 0 8px auto`
171
- div.textContent = text
172
- messages.appendChild(div)
173
- messages.scrollTop = messages.scrollHeight
27
+ .vc-loader {
28
+ display: inline-flex;
29
+ gap: 6px;
30
+ padding: 8px 12px;
31
+ border-radius: 12px;
32
+ max-width: 80%;
33
+ margin-bottom: 8px;
174
34
  }
35
+ .vc-loader span {
36
+ width: 6px;
37
+ height: 6px;
38
+ background: var(--vc-text-muted);
39
+ border-radius: 50%;
40
+ animation: vc-bounce 1.4s infinite ease-in-out both;
41
+ }
42
+ .vc-loader span:nth-child(1) { animation-delay: -0.32s; }
43
+ .vc-loader span:nth-child(2) { animation-delay: -0.16s; }
175
44
 
176
- function addBotMessage (text) {
177
- const div = document.createElement('div')
178
- div.style.cssText = `
179
- background:${colors.botBubble};
180
- color:${colors.text};
181
- padding:8px 12px;
182
- border-radius:12px;
183
- max-width:80%;
184
- margin-bottom:8px`
185
- div.textContent = text
186
- messages.appendChild(div)
187
- messages.scrollTop = messages.scrollHeight
45
+ @keyframes vc-bounce {
46
+ 0%, 80%, 100% { transform: scale(0); }
47
+ 40% { transform: scale(1); }
188
48
  }
49
+ `
50
+ document.head.appendChild(style)
51
+ }
189
52
 
190
- async function sendMessage () {
191
- const text = input.value.trim()
192
- if (!text) return
53
+ /* =========================================================
54
+ BUTTON
55
+ ========================================================= */
56
+ function getButton () {
57
+ if (vcButton) return vcButton
193
58
 
194
- addUserMessage(text)
195
- input.value = ''
59
+ vcButton = document.createElement('div')
60
+ vcButton.className = 'vc-fab'
61
+ document.body.appendChild(vcButton)
196
62
 
197
- // 🔐 API key missing → internal response only
198
- if (!config.apiKey) {
199
- addBotMessage(
200
- '⚠️ API key not configured. Please add apiKey in VaultChat.init().'
201
- )
202
- return
203
- }
63
+ return vcButton
64
+ }
204
65
 
205
- try {
206
- const res = await fetch('https://api.vaultchat.io/askChatbot', {
207
- method: 'POST',
208
- headers: { 'Content-Type': 'application/json' },
209
- body: JSON.stringify({
210
- api_key: config.apiKey,
211
- question: text
212
- })
213
- })
214
-
215
- const data = await res.json()
216
- const reply =
217
- data?.data?.blocks?.map(b => b.text).join('\n') ||
218
- 'No response received'
219
-
220
- addBotMessage(reply)
221
- } catch {
222
- addBotMessage('⚠️ Something went wrong')
66
+ function updateButton (config) {
67
+ const button = getButton()
68
+ button.innerHTML = ''
69
+
70
+ const {
71
+ buttonType = 'text',
72
+ buttonContent = 'Chat',
73
+ buttonShape = 'circle',
74
+ primaryColor = '#2563eb'
75
+ } = config
76
+
77
+ const isText = buttonType === 'text'
78
+
79
+ if (buttonType === 'image') {
80
+ const img = document.createElement('img')
81
+ img.src = buttonContent
82
+ img.style.width = '26px'
83
+ img.style.height = '26px'
84
+ img.style.objectFit = 'contain'
85
+ button.appendChild(img)
86
+ } else {
87
+ button.textContent = buttonContent
88
+ }
89
+
90
+ const borderRadius =
91
+ buttonShape === 'pill' ? '999px' : buttonShape === 'square' ? '8px' : '50%'
92
+
93
+ Object.assign(button.style, {
94
+ position: 'fixed',
95
+ bottom: '20px',
96
+ right: '20px',
97
+ width: isText ? 'auto' : '56px',
98
+ height: isText ? 'auto' : '56px',
99
+ minWidth: '56px',
100
+ minHeight: '56px',
101
+ padding: isText ? '10px 16px' : '0',
102
+ borderRadius,
103
+ background: primaryColor,
104
+ color: '#fff',
105
+ display: 'flex',
106
+ alignItems: 'center',
107
+ justifyContent: 'center',
108
+ cursor: 'pointer',
109
+ fontSize: '18px',
110
+ fontWeight: '500',
111
+ userSelect: 'none',
112
+ zIndex: 999999
113
+ })
114
+ }
115
+
116
+ /* =========================================================
117
+ CARD
118
+ ========================================================= */
119
+ function getCard () {
120
+ if (vcCard) return vcCard
121
+
122
+ vcCard = document.createElement('div')
123
+ document.body.appendChild(vcCard)
124
+ return vcCard
125
+ }
126
+
127
+ function updateCard (config) {
128
+ const card = getCard()
129
+
130
+ const theme =
131
+ config.theme === 'dark'
132
+ ? 'dark'
133
+ : config.theme === 'light'
134
+ ? 'light'
135
+ : window.matchMedia('(prefers-color-scheme: dark)').matches
136
+ ? 'dark'
137
+ : 'light'
138
+
139
+ const isDark = theme === 'dark'
140
+
141
+ const colors = isDark
142
+ ? {
143
+ cardBg: '#0f172a',
144
+ headerBorder: '#1e293b',
145
+ text: '#e5e7eb',
146
+ mutedText: '#94a3b8',
147
+ inputBg: '#020617',
148
+ inputBorder: '#334155',
149
+ botBubble: '#1e293b',
150
+ userText: '#ffffff',
151
+ messagesBg: '#020617'
152
+ }
153
+ : {
154
+ cardBg: '#ffffff',
155
+ headerBorder: '#e5e7eb',
156
+ text: '#0f172a',
157
+ mutedText: '#64748b',
158
+ inputBg: '#ffffff',
159
+ inputBorder: '#cbd5f5',
160
+ botBubble: '#e5e7eb',
161
+ userText: '#ffffff',
162
+ messagesBg: '#f8fafc'
223
163
  }
164
+
165
+ const primaryColor = config.primaryColor || '#2563eb'
166
+
167
+ card.innerHTML = `
168
+ <div style="padding:12px;border-bottom:1px solid ${colors.headerBorder};color:${colors.mutedText}">
169
+ Powered by <strong>VaultChat</strong>
170
+ </div>
171
+
172
+ <div id="vc-messages" style="
173
+ flex:1;
174
+ padding:12px;
175
+ overflow-y:auto;
176
+ background:${colors.messagesBg};
177
+ color:${colors.text}">
178
+ </div>
179
+
180
+ <div style="display:flex;padding:10px;border-top:1px solid ${colors.headerBorder};background:${colors.cardBg}">
181
+ <input id="vc-input" placeholder="Type your message..."
182
+ style="flex:1;padding:10px;border-radius:8px;border:1px solid ${colors.inputBorder};
183
+ background:${colors.inputBg};color:${colors.text}" />
184
+ <button id="vc-send" style="
185
+ margin-left:8px;padding:0 14px;border:none;border-radius:8px;
186
+ background:${primaryColor};color:white;cursor:pointer">➤</button>
187
+ </div>
188
+ `
189
+
190
+ Object.assign(card.style, {
191
+ position: 'fixed',
192
+ bottom: '90px',
193
+ right: '20px',
194
+ width: '400px',
195
+ height: '420px',
196
+ background: colors.cardBg,
197
+ borderRadius: '12px',
198
+ boxShadow: '0 10px 30px rgba(0,0,0,.3)',
199
+ zIndex: 999999,
200
+ display: isOpen ? 'flex' : 'none',
201
+ flexDirection: 'column'
202
+ })
203
+
204
+ card.style.setProperty('--vc-text-muted', colors.mutedText)
205
+ card.style.setProperty('--vc-bot-bg', colors.botBubble)
206
+
207
+ const messages = card.querySelector('#vc-messages')
208
+ const input = card.querySelector('#vc-input')
209
+ const sendBtn = card.querySelector('#vc-send')
210
+
211
+ function sendMessage () {
212
+ const text = input.value.trim()
213
+ if (!text) return
214
+
215
+ const userMsg = document.createElement('div')
216
+ userMsg.style.cssText = `
217
+ background:${config.primaryColor || '#2563eb'};
218
+ color:${config.theme === 'dark' ? '#fff' : '#fff'};
219
+ padding:8px 12px;
220
+ border-radius:12px;
221
+ max-width:80%;
222
+ margin:8px 0 8px auto`
223
+ userMsg.textContent = text
224
+ messages.appendChild(userMsg)
225
+ input.value = ''
226
+ messages.scrollTop = messages.scrollHeight
227
+
228
+ if (!config.apiKey) {
229
+ addBotMessage('⚠️ API key not configured')
230
+ return
224
231
  }
225
232
 
226
- sendBtn.onclick = sendMessage
227
- input.addEventListener('keydown', e => {
228
- if (e.key === 'Enter') sendMessage()
233
+ showLoader(messages, sendBtn)
234
+
235
+ fetch('https://api.vaultchat.io/askChatbot', {
236
+ method: 'POST',
237
+ headers: { 'Content-Type': 'application/json' },
238
+ body: JSON.stringify({ api_key: config.apiKey, question: text })
229
239
  })
240
+ .then(res => res.json())
241
+ .then(data => {
242
+ hideLoader(sendBtn)
243
+ addBotMessage(
244
+ data?.data?.blocks?.map(b => b.text).join('\n') || 'No response'
245
+ )
246
+ })
247
+ .catch(() => {
248
+ hideLoader(sendBtn)
249
+ addBotMessage('⚠️ Something went wrong')
250
+ })
251
+ }
252
+ sendBtn.onclick = sendMessage
253
+ input.addEventListener('keydown', e => {
254
+ if (e.key === 'Enter') sendMessage()
255
+ })
256
+
257
+ function addBotMessage (text) {
258
+ const div = document.createElement('div')
259
+ div.style.cssText = `
260
+ background:${colors.botBubble};
261
+ color:${colors.text};
262
+ padding:8px 12px;
263
+ border-radius:12px;
264
+ max-width:80%;
265
+ margin-bottom:8px`
266
+ div.textContent = text
267
+ messages.appendChild(div)
268
+ messages.scrollTop = messages.scrollHeight
269
+ }
270
+ }
271
+
272
+ /* =========================================================
273
+ LOADER
274
+ ========================================================= */
275
+ function showLoader (container, sendBtn) {
276
+ loaderEl = document.createElement('div')
277
+ loaderEl.className = 'vc-loader'
278
+ loaderEl.innerHTML = '<span></span><span></span><span></span>'
279
+ container.appendChild(loaderEl)
280
+
281
+ if (sendBtn) {
282
+ sendBtn.disabled = true
283
+ sendBtn.style.opacity = '0.6'
284
+ sendBtn.style.cursor = 'not-allowed'
285
+ }
286
+ }
287
+
288
+ function hideLoader (sendBtn) {
289
+ loaderEl?.remove()
290
+ loaderEl = null
291
+
292
+ if (sendBtn) {
293
+ sendBtn.disabled = false
294
+ sendBtn.style.opacity = '1'
295
+ sendBtn.style.cursor = 'pointer'
296
+ }
297
+ }
298
+
299
+ /* =========================================================
300
+ PUBLIC API
301
+ ========================================================= */
302
+ const VaultChat = {
303
+ init (config = {}) {
304
+ injectStylesOnce()
305
+ updateButton(config)
306
+ updateCard(config)
307
+
308
+ const button = getButton()
309
+ button.onclick = () => {
310
+ isOpen = !isOpen
311
+ vcCard.style.display = isOpen ? 'flex' : 'none'
312
+ }
230
313
  }
231
314
  }
232
315
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mobil80-dev/chatbot-widget",
3
- "version": "1.0.9",
3
+ "version": "2.0.1",
4
4
  "description": "Drop-in JavaScript chat widget for websites (no iframe, no framework)",
5
5
  "main": "index.js",
6
6
  "type": "module",