@mobil80-dev/chatbot-widget 2.0.3 → 2.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,301 @@
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
+ }
26
+ .vc-loader {
27
+ display: inline-flex;
28
+ gap: 6px;
29
+ padding: 8px 12px;
30
+ border-radius: 12px;
31
+ max-width: 80%;
32
+ margin-bottom: 8px;
33
+ }
34
+ .vc-loader span {
35
+ width: 6px;
36
+ height: 6px;
37
+ background: var(--vc-text-muted);
38
+ border-radius: 50%;
39
+ animation: vc-bounce 1.4s infinite ease-in-out both;
40
+ }
41
+ .vc-loader span:nth-child(1) { animation-delay: -0.32s; }
42
+ .vc-loader span:nth-child(2) { animation-delay: -0.16s; }
43
+ @keyframes vc-bounce {
44
+ 0%, 80%, 100% { transform: scale(0); }
45
+ 40% { transform: scale(1); }
46
+ }
47
+ `;
48
+ document.head.appendChild(style);
49
+ }
50
+
51
+ /* =========================================================
52
+ HELPER TO RESOLVE CONTAINER
53
+ ========================================================= */
54
+ function getAttachContainer(selector) {
55
+ if (!selector) return document.body
56
+ if (typeof selector === 'string') {
57
+ const el = document.querySelector(selector);
58
+ return el || document.body
59
+ }
60
+ return selector // accept DOM element directly
61
+ }
62
+
63
+ /* =========================================================
64
+ BUTTON
65
+ ========================================================= */
66
+ function getButton() {
67
+ if (vcButton) return vcButton
68
+ vcButton = document.createElement('div');
69
+ vcButton.className = 'vc-fab';
70
+ return vcButton
71
+ }
72
+
73
+ function updateButton(config) {
74
+ const button = getButton();
75
+ button.innerHTML = '';
76
+
77
+ const { buttonType = 'text', buttonContent = '🤖', buttonShape = 'circle', primaryColor = '#2563eb' } = config;
78
+ const isText = buttonType === 'text';
79
+
80
+ if (buttonType === 'image') {
81
+ const img = document.createElement('img');
82
+ img.src = buttonContent;
83
+ img.style.width = '26px';
84
+ img.style.height = '26px';
85
+ img.style.objectFit = 'contain';
86
+ button.appendChild(img);
87
+ } else {
88
+ button.textContent = buttonContent;
89
+ }
90
+
91
+ const borderRadius = 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
+ vcCard = document.createElement('div');
122
+ return vcCard
123
+ }
124
+
125
+ function updateCard(config) {
126
+ const card = getCard();
127
+
128
+ const theme = config.theme === 'dark' ? 'dark' : config.theme === 'light' ? 'light' :
129
+ window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
130
+ const isDark = theme === 'dark';
131
+
132
+ const colors = isDark
133
+ ? { cardBg: '#0f172a', headerBorder: '#1e293b', text: '#e5e7eb', mutedText: '#94a3b8', inputBg: '#020617', inputBorder: '#334155', botBubble: '#1e293b', userText: '#ffffff', messagesBg: '#020617' }
134
+ : { cardBg: '#ffffff', headerBorder: '#e5e7eb', text: '#0f172a', mutedText: '#64748b', inputBg: '#ffffff', inputBorder: '#cbd5f5', botBubble: '#e5e7eb', userText: '#ffffff', messagesBg: '#f8fafc' };
135
+
136
+ const primaryColor = config.primaryColor || '#2563eb';
137
+
138
+ card.innerHTML = `
139
+ <div style="padding:12px;border-bottom:1px solid ${colors.headerBorder};color:${colors.mutedText}">
140
+ Powered by <strong>VaultChat</strong>
141
+ </div>
142
+
143
+ <div id="vc-messages" style="
144
+ flex:1;
145
+ padding:12px;
146
+ overflow-y:auto;
147
+ background:${colors.messagesBg};
148
+ color:${colors.text}">
149
+ </div>
150
+
151
+ <div style="display:flex;padding:10px;border-top:1px solid ${colors.headerBorder};background:${colors.cardBg}">
152
+ <input id="vc-input" placeholder="Type your message..."
153
+ style="flex:1;padding:10px;border-radius:8px;border:1px solid ${colors.inputBorder};
154
+ background:${colors.inputBg};color:${colors.text}" />
155
+ <button id="vc-send" style="
156
+ margin-left:8px;padding:0 14px;border:none;border-radius:8px;
157
+ background:${primaryColor};color:white;cursor:pointer">➤</button>
158
+ </div>
159
+ `;
160
+
161
+ Object.assign(card.style, {
162
+ position: 'fixed',
163
+ bottom: '90px',
164
+ right: '20px',
165
+ width: '400px',
166
+ height: '420px',
167
+ background: colors.cardBg,
168
+ borderRadius: '12px',
169
+ boxShadow: '0 10px 30px rgba(0,0,0,.3)',
170
+ zIndex: 999999,
171
+ display: isOpen ? 'flex' : 'none',
172
+ flexDirection: 'column'
173
+ });
174
+
175
+ card.style.setProperty('--vc-text-muted', colors.mutedText);
176
+ card.style.setProperty('--vc-bot-bg', colors.botBubble);
177
+
178
+ const messages = card.querySelector('#vc-messages');
179
+ const input = card.querySelector('#vc-input');
180
+ const sendBtn = card.querySelector('#vc-send');
181
+
182
+ // send message
183
+ async function sendMessage() {
184
+ const text = input.value.trim();
185
+ if (!text) return
186
+
187
+ const userMsg = document.createElement('div');
188
+ userMsg.style.cssText = `
189
+ background:${primaryColor};
190
+ color:${colors.userText};
191
+ padding:8px 12px;
192
+ border-radius:12px;
193
+ max-width:80%;
194
+ margin:8px 0 8px auto`;
195
+ userMsg.textContent = text;
196
+ messages.appendChild(userMsg);
197
+
198
+ input.value = '';
199
+ messages.scrollTop = messages.scrollHeight;
200
+
201
+ if (!config.apiKey) {
202
+ addBotMessage('⚠️ API key not configured');
203
+ return
204
+ }
205
+
206
+ showLoader(messages, sendBtn);
207
+
208
+ try {
209
+ const res = await fetch('https://api.vaultchat.io/askChatbot', {
210
+ method: 'POST',
211
+ headers: { 'Content-Type': 'application/json' },
212
+ body: JSON.stringify({ api_key: config.apiKey, question: text })
213
+ });
214
+ const data = await res.json();
215
+ hideLoader(sendBtn);
216
+ addBotMessage(data?.data?.blocks?.map(b => b.text).join('\n') || 'No response');
217
+ } catch {
218
+ hideLoader(sendBtn);
219
+ addBotMessage('⚠️ Something went wrong');
220
+ }
221
+ }
222
+
223
+ function addBotMessage(text) {
224
+ const div = document.createElement('div');
225
+ div.style.cssText = `
226
+ background:${colors.botBubble};
227
+ color:${colors.text};
228
+ padding:8px 12px;
229
+ border-radius:12px;
230
+ max-width:80%;
231
+ margin-bottom:8px`;
232
+ div.textContent = text;
233
+ messages.appendChild(div);
234
+ messages.scrollTop = messages.scrollHeight;
235
+ }
236
+
237
+ // events
238
+ sendBtn.onclick = sendMessage;
239
+ input.addEventListener('keydown', e => { if (e.key === 'Enter') sendMessage(); });
240
+
241
+ return { messages, input, sendBtn }
242
+ }
243
+
244
+ /* =========================================================
245
+ LOADER
246
+ ========================================================= */
247
+ function showLoader(container, sendBtn) {
248
+ loaderEl = document.createElement('div');
249
+ loaderEl.className = 'vc-loader';
250
+ loaderEl.innerHTML = '<span></span><span></span><span></span>';
251
+ container.appendChild(loaderEl);
252
+
253
+ if (sendBtn) {
254
+ sendBtn.disabled = true;
255
+ sendBtn.style.opacity = '0.6';
256
+ sendBtn.style.cursor = 'not-allowed';
257
+ }
258
+ }
259
+
260
+ function hideLoader(sendBtn) {
261
+ loaderEl?.remove();
262
+ loaderEl = null;
263
+
264
+ if (sendBtn) {
265
+ sendBtn.disabled = false;
266
+ sendBtn.style.opacity = '1';
267
+ sendBtn.style.cursor = 'pointer';
268
+ }
269
+ }
270
+
271
+ /* =========================================================
272
+ PUBLIC API
273
+ ========================================================= */
274
+ const VaultChat = {
275
+ init(config = {}) {
276
+ injectStylesOnce();
277
+ updateButton(config);
278
+
279
+ const mountEl = getAttachContainer(config.attachToElement);
280
+ mountEl.appendChild(vcButton);
281
+
282
+ updateCard(config);
283
+ mountEl.appendChild(vcCard);
284
+
285
+ vcButton.onclick = () => {
286
+ isOpen = !isOpen;
287
+ vcCard.style.display = isOpen ? 'flex' : 'none';
288
+ };
289
+ },
290
+
291
+ destroy() {
292
+ vcButton?.remove();
293
+ vcCard?.remove();
294
+ vcButton = null;
295
+ vcCard = null;
296
+ loaderEl = null;
297
+ isOpen = false;
298
+ }
299
+ };
300
+
301
+ export { VaultChat as default };
@@ -0,0 +1,309 @@
1
+ (function (global, factory) {
2
+ typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
3
+ typeof define === 'function' && define.amd ? define(factory) :
4
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.VaultChat = factory());
5
+ })(this, (function () { 'use strict';
6
+
7
+ /* =========================================================
8
+ GLOBAL SINGLETON REFERENCES
9
+ ========================================================= */
10
+ let vcButton = null;
11
+ let vcCard = null;
12
+ let loaderEl = null;
13
+ let stylesInjected = false;
14
+ let isOpen = false;
15
+
16
+ /* =========================================================
17
+ STYLE INJECTION (ONCE)
18
+ ========================================================= */
19
+ function injectStylesOnce() {
20
+ if (stylesInjected) return
21
+ stylesInjected = true;
22
+
23
+ const style = document.createElement('style');
24
+ style.innerHTML = `
25
+ .vc-fab {
26
+ box-shadow: 0 8px 20px rgba(0,0,0,.25);
27
+ transition: box-shadow .2s ease, transform .2s ease;
28
+ }
29
+ .vc-fab:hover {
30
+ box-shadow: 0 12px 28px rgba(0,0,0,.3);
31
+ }
32
+ .vc-loader {
33
+ display: inline-flex;
34
+ gap: 6px;
35
+ padding: 8px 12px;
36
+ border-radius: 12px;
37
+ max-width: 80%;
38
+ margin-bottom: 8px;
39
+ }
40
+ .vc-loader span {
41
+ width: 6px;
42
+ height: 6px;
43
+ background: var(--vc-text-muted);
44
+ border-radius: 50%;
45
+ animation: vc-bounce 1.4s infinite ease-in-out both;
46
+ }
47
+ .vc-loader span:nth-child(1) { animation-delay: -0.32s; }
48
+ .vc-loader span:nth-child(2) { animation-delay: -0.16s; }
49
+ @keyframes vc-bounce {
50
+ 0%, 80%, 100% { transform: scale(0); }
51
+ 40% { transform: scale(1); }
52
+ }
53
+ `;
54
+ document.head.appendChild(style);
55
+ }
56
+
57
+ /* =========================================================
58
+ HELPER TO RESOLVE CONTAINER
59
+ ========================================================= */
60
+ function getAttachContainer(selector) {
61
+ if (!selector) return document.body
62
+ if (typeof selector === 'string') {
63
+ const el = document.querySelector(selector);
64
+ return el || document.body
65
+ }
66
+ return selector // accept DOM element directly
67
+ }
68
+
69
+ /* =========================================================
70
+ BUTTON
71
+ ========================================================= */
72
+ function getButton() {
73
+ if (vcButton) return vcButton
74
+ vcButton = document.createElement('div');
75
+ vcButton.className = 'vc-fab';
76
+ return vcButton
77
+ }
78
+
79
+ function updateButton(config) {
80
+ const button = getButton();
81
+ button.innerHTML = '';
82
+
83
+ const { buttonType = 'text', buttonContent = '🤖', buttonShape = 'circle', primaryColor = '#2563eb' } = config;
84
+ const isText = buttonType === 'text';
85
+
86
+ if (buttonType === 'image') {
87
+ const img = document.createElement('img');
88
+ img.src = buttonContent;
89
+ img.style.width = '26px';
90
+ img.style.height = '26px';
91
+ img.style.objectFit = 'contain';
92
+ button.appendChild(img);
93
+ } else {
94
+ button.textContent = buttonContent;
95
+ }
96
+
97
+ const borderRadius = buttonShape === 'pill' ? '999px' : buttonShape === 'square' ? '8px' : '50%';
98
+
99
+ Object.assign(button.style, {
100
+ position: 'fixed',
101
+ bottom: '20px',
102
+ right: '20px',
103
+ width: isText ? 'auto' : '56px',
104
+ height: isText ? 'auto' : '56px',
105
+ minWidth: '56px',
106
+ minHeight: '56px',
107
+ padding: isText ? '10px 16px' : '0',
108
+ borderRadius,
109
+ background: primaryColor,
110
+ color: '#fff',
111
+ display: 'flex',
112
+ alignItems: 'center',
113
+ justifyContent: 'center',
114
+ cursor: 'pointer',
115
+ fontSize: '18px',
116
+ fontWeight: '500',
117
+ userSelect: 'none',
118
+ zIndex: 999999
119
+ });
120
+ }
121
+
122
+ /* =========================================================
123
+ CARD
124
+ ========================================================= */
125
+ function getCard() {
126
+ if (vcCard) return vcCard
127
+ vcCard = document.createElement('div');
128
+ return vcCard
129
+ }
130
+
131
+ function updateCard(config) {
132
+ const card = getCard();
133
+
134
+ const theme = config.theme === 'dark' ? 'dark' : config.theme === 'light' ? 'light' :
135
+ window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
136
+ const isDark = theme === 'dark';
137
+
138
+ const colors = isDark
139
+ ? { cardBg: '#0f172a', headerBorder: '#1e293b', text: '#e5e7eb', mutedText: '#94a3b8', inputBg: '#020617', inputBorder: '#334155', botBubble: '#1e293b', userText: '#ffffff', messagesBg: '#020617' }
140
+ : { cardBg: '#ffffff', headerBorder: '#e5e7eb', text: '#0f172a', mutedText: '#64748b', inputBg: '#ffffff', inputBorder: '#cbd5f5', botBubble: '#e5e7eb', userText: '#ffffff', messagesBg: '#f8fafc' };
141
+
142
+ const primaryColor = config.primaryColor || '#2563eb';
143
+
144
+ card.innerHTML = `
145
+ <div style="padding:12px;border-bottom:1px solid ${colors.headerBorder};color:${colors.mutedText}">
146
+ Powered by <strong>VaultChat</strong>
147
+ </div>
148
+
149
+ <div id="vc-messages" style="
150
+ flex:1;
151
+ padding:12px;
152
+ overflow-y:auto;
153
+ background:${colors.messagesBg};
154
+ color:${colors.text}">
155
+ </div>
156
+
157
+ <div style="display:flex;padding:10px;border-top:1px solid ${colors.headerBorder};background:${colors.cardBg}">
158
+ <input id="vc-input" placeholder="Type your message..."
159
+ style="flex:1;padding:10px;border-radius:8px;border:1px solid ${colors.inputBorder};
160
+ background:${colors.inputBg};color:${colors.text}" />
161
+ <button id="vc-send" style="
162
+ margin-left:8px;padding:0 14px;border:none;border-radius:8px;
163
+ background:${primaryColor};color:white;cursor:pointer">➤</button>
164
+ </div>
165
+ `;
166
+
167
+ Object.assign(card.style, {
168
+ position: 'fixed',
169
+ bottom: '90px',
170
+ right: '20px',
171
+ width: '400px',
172
+ height: '420px',
173
+ background: colors.cardBg,
174
+ borderRadius: '12px',
175
+ boxShadow: '0 10px 30px rgba(0,0,0,.3)',
176
+ zIndex: 999999,
177
+ display: isOpen ? 'flex' : 'none',
178
+ flexDirection: 'column'
179
+ });
180
+
181
+ card.style.setProperty('--vc-text-muted', colors.mutedText);
182
+ card.style.setProperty('--vc-bot-bg', colors.botBubble);
183
+
184
+ const messages = card.querySelector('#vc-messages');
185
+ const input = card.querySelector('#vc-input');
186
+ const sendBtn = card.querySelector('#vc-send');
187
+
188
+ // send message
189
+ async function sendMessage() {
190
+ const text = input.value.trim();
191
+ if (!text) return
192
+
193
+ const userMsg = document.createElement('div');
194
+ userMsg.style.cssText = `
195
+ background:${primaryColor};
196
+ color:${colors.userText};
197
+ padding:8px 12px;
198
+ border-radius:12px;
199
+ max-width:80%;
200
+ margin:8px 0 8px auto`;
201
+ userMsg.textContent = text;
202
+ messages.appendChild(userMsg);
203
+
204
+ input.value = '';
205
+ messages.scrollTop = messages.scrollHeight;
206
+
207
+ if (!config.apiKey) {
208
+ addBotMessage('⚠️ API key not configured');
209
+ return
210
+ }
211
+
212
+ showLoader(messages, sendBtn);
213
+
214
+ try {
215
+ const res = await fetch('https://api.vaultchat.io/askChatbot', {
216
+ method: 'POST',
217
+ headers: { 'Content-Type': 'application/json' },
218
+ body: JSON.stringify({ api_key: config.apiKey, question: text })
219
+ });
220
+ const data = await res.json();
221
+ hideLoader(sendBtn);
222
+ addBotMessage(data?.data?.blocks?.map(b => b.text).join('\n') || 'No response');
223
+ } catch {
224
+ hideLoader(sendBtn);
225
+ addBotMessage('⚠️ Something went wrong');
226
+ }
227
+ }
228
+
229
+ function addBotMessage(text) {
230
+ const div = document.createElement('div');
231
+ div.style.cssText = `
232
+ background:${colors.botBubble};
233
+ color:${colors.text};
234
+ padding:8px 12px;
235
+ border-radius:12px;
236
+ max-width:80%;
237
+ margin-bottom:8px`;
238
+ div.textContent = text;
239
+ messages.appendChild(div);
240
+ messages.scrollTop = messages.scrollHeight;
241
+ }
242
+
243
+ // events
244
+ sendBtn.onclick = sendMessage;
245
+ input.addEventListener('keydown', e => { if (e.key === 'Enter') sendMessage(); });
246
+
247
+ return { messages, input, sendBtn }
248
+ }
249
+
250
+ /* =========================================================
251
+ LOADER
252
+ ========================================================= */
253
+ function showLoader(container, sendBtn) {
254
+ loaderEl = document.createElement('div');
255
+ loaderEl.className = 'vc-loader';
256
+ loaderEl.innerHTML = '<span></span><span></span><span></span>';
257
+ container.appendChild(loaderEl);
258
+
259
+ if (sendBtn) {
260
+ sendBtn.disabled = true;
261
+ sendBtn.style.opacity = '0.6';
262
+ sendBtn.style.cursor = 'not-allowed';
263
+ }
264
+ }
265
+
266
+ function hideLoader(sendBtn) {
267
+ loaderEl?.remove();
268
+ loaderEl = null;
269
+
270
+ if (sendBtn) {
271
+ sendBtn.disabled = false;
272
+ sendBtn.style.opacity = '1';
273
+ sendBtn.style.cursor = 'pointer';
274
+ }
275
+ }
276
+
277
+ /* =========================================================
278
+ PUBLIC API
279
+ ========================================================= */
280
+ const VaultChat = {
281
+ init(config = {}) {
282
+ injectStylesOnce();
283
+ updateButton(config);
284
+
285
+ const mountEl = getAttachContainer(config.attachToElement);
286
+ mountEl.appendChild(vcButton);
287
+
288
+ updateCard(config);
289
+ mountEl.appendChild(vcCard);
290
+
291
+ vcButton.onclick = () => {
292
+ isOpen = !isOpen;
293
+ vcCard.style.display = isOpen ? 'flex' : 'none';
294
+ };
295
+ },
296
+
297
+ destroy() {
298
+ vcButton?.remove();
299
+ vcCard?.remove();
300
+ vcButton = null;
301
+ vcCard = null;
302
+ loaderEl = null;
303
+ isOpen = false;
304
+ }
305
+ };
306
+
307
+ return VaultChat;
308
+
309
+ }));
package/package.json CHANGED
@@ -1,9 +1,17 @@
1
1
  {
2
2
  "name": "@mobil80-dev/chatbot-widget",
3
- "version": "2.0.3",
3
+ "version": "2.0.4",
4
4
  "description": "Drop-in JavaScript chat widget for websites (no iframe, no framework)",
5
- "main": "index.js",
5
+ "author": "NanthaGopal",
6
+ "license": "MIT",
7
+
8
+ "main": "dist/index.js",
9
+ "module": "dist/index.js",
10
+ "unpkg": "dist/index.umd.js",
11
+ "jsdelivr": "dist/index.umd.js",
12
+
6
13
  "type": "module",
14
+
7
15
  "keywords": [
8
16
  "chat",
9
17
  "chatbot",
@@ -11,9 +19,12 @@
11
19
  "javascript",
12
20
  "vaultchat"
13
21
  ],
14
- "author": "NanthaGopal",
15
- "license": "MIT",
16
- "dependencies": {
17
- "@mobil80-dev/chatbot-widget": "^1.0.8"
22
+
23
+ "scripts": {
24
+ "build": "rollup -c"
25
+ },
26
+
27
+ "devDependencies": {
28
+ "rollup": "^4.54.0"
18
29
  }
19
30
  }
@@ -0,0 +1,14 @@
1
+ export default {
2
+ input: 'index.js',
3
+ output: [
4
+ {
5
+ file: 'dist/index.js',
6
+ format: 'es'
7
+ },
8
+ {
9
+ file: 'dist/index.umd.js',
10
+ format: 'umd',
11
+ name: 'VaultChat'
12
+ }
13
+ ]
14
+ }