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