@mobil80-dev/chatbot-widget 2.0.3 → 2.0.5
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 +301 -0
- package/dist/index.umd.js +309 -0
- package/index.js +62 -20
- package/package.json +17 -6
- package/rollup.config.js +14 -0
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/index.js
CHANGED
|
@@ -10,7 +10,7 @@ let isOpen = false
|
|
|
10
10
|
/* =========================================================
|
|
11
11
|
STYLE INJECTION (ONCE)
|
|
12
12
|
========================================================= */
|
|
13
|
-
function injectStylesOnce() {
|
|
13
|
+
function injectStylesOnce () {
|
|
14
14
|
if (stylesInjected) return
|
|
15
15
|
stylesInjected = true
|
|
16
16
|
|
|
@@ -51,7 +51,7 @@ function injectStylesOnce() {
|
|
|
51
51
|
/* =========================================================
|
|
52
52
|
HELPER TO RESOLVE CONTAINER
|
|
53
53
|
========================================================= */
|
|
54
|
-
function getAttachContainer(selector) {
|
|
54
|
+
function getAttachContainer (selector) {
|
|
55
55
|
if (!selector) return document.body
|
|
56
56
|
if (typeof selector === 'string') {
|
|
57
57
|
const el = document.querySelector(selector)
|
|
@@ -63,18 +63,23 @@ function getAttachContainer(selector) {
|
|
|
63
63
|
/* =========================================================
|
|
64
64
|
BUTTON
|
|
65
65
|
========================================================= */
|
|
66
|
-
function getButton() {
|
|
66
|
+
function getButton () {
|
|
67
67
|
if (vcButton) return vcButton
|
|
68
68
|
vcButton = document.createElement('div')
|
|
69
69
|
vcButton.className = 'vc-fab'
|
|
70
70
|
return vcButton
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
function updateButton(config) {
|
|
73
|
+
function updateButton (config) {
|
|
74
74
|
const button = getButton()
|
|
75
75
|
button.innerHTML = ''
|
|
76
76
|
|
|
77
|
-
const {
|
|
77
|
+
const {
|
|
78
|
+
buttonType = 'text',
|
|
79
|
+
buttonContent = '🤖',
|
|
80
|
+
buttonShape = 'circle',
|
|
81
|
+
primaryColor = '#2563eb'
|
|
82
|
+
} = config
|
|
78
83
|
const isText = buttonType === 'text'
|
|
79
84
|
|
|
80
85
|
if (buttonType === 'image') {
|
|
@@ -88,7 +93,8 @@ function updateButton(config) {
|
|
|
88
93
|
button.textContent = buttonContent
|
|
89
94
|
}
|
|
90
95
|
|
|
91
|
-
const borderRadius =
|
|
96
|
+
const borderRadius =
|
|
97
|
+
buttonShape === 'pill' ? '999px' : buttonShape === 'square' ? '8px' : '50%'
|
|
92
98
|
|
|
93
99
|
Object.assign(button.style, {
|
|
94
100
|
position: 'fixed',
|
|
@@ -116,22 +122,48 @@ function updateButton(config) {
|
|
|
116
122
|
/* =========================================================
|
|
117
123
|
CARD
|
|
118
124
|
========================================================= */
|
|
119
|
-
function getCard() {
|
|
125
|
+
function getCard () {
|
|
120
126
|
if (vcCard) return vcCard
|
|
121
127
|
vcCard = document.createElement('div')
|
|
122
128
|
return vcCard
|
|
123
129
|
}
|
|
124
130
|
|
|
125
|
-
function updateCard(config) {
|
|
131
|
+
function updateCard (config) {
|
|
126
132
|
const card = getCard()
|
|
127
133
|
|
|
128
|
-
const theme =
|
|
129
|
-
|
|
134
|
+
const theme =
|
|
135
|
+
config.theme === 'dark'
|
|
136
|
+
? 'dark'
|
|
137
|
+
: config.theme === 'light'
|
|
138
|
+
? 'light'
|
|
139
|
+
: window.matchMedia('(prefers-color-scheme: dark)').matches
|
|
140
|
+
? 'dark'
|
|
141
|
+
: 'light'
|
|
130
142
|
const isDark = theme === 'dark'
|
|
131
143
|
|
|
132
144
|
const colors = isDark
|
|
133
|
-
? {
|
|
134
|
-
|
|
145
|
+
? {
|
|
146
|
+
cardBg: '#0f172a',
|
|
147
|
+
headerBorder: '#1e293b',
|
|
148
|
+
text: '#e5e7eb',
|
|
149
|
+
mutedText: '#94a3b8',
|
|
150
|
+
inputBg: '#020617',
|
|
151
|
+
inputBorder: '#334155',
|
|
152
|
+
botBubble: '#1e293b',
|
|
153
|
+
userText: '#ffffff',
|
|
154
|
+
messagesBg: '#020617'
|
|
155
|
+
}
|
|
156
|
+
: {
|
|
157
|
+
cardBg: '#ffffff',
|
|
158
|
+
headerBorder: '#e5e7eb',
|
|
159
|
+
text: '#0f172a',
|
|
160
|
+
mutedText: '#64748b',
|
|
161
|
+
inputBg: '#ffffff',
|
|
162
|
+
inputBorder: '#cbd5f5',
|
|
163
|
+
botBubble: '#e5e7eb',
|
|
164
|
+
userText: '#ffffff',
|
|
165
|
+
messagesBg: '#f8fafc'
|
|
166
|
+
}
|
|
135
167
|
|
|
136
168
|
const primaryColor = config.primaryColor || '#2563eb'
|
|
137
169
|
|
|
@@ -162,8 +194,10 @@ function updateCard(config) {
|
|
|
162
194
|
position: 'fixed',
|
|
163
195
|
bottom: '90px',
|
|
164
196
|
right: '20px',
|
|
197
|
+
maxWidth: '90vw',
|
|
165
198
|
width: '400px',
|
|
166
199
|
height: '420px',
|
|
200
|
+
maxHeight: '80vh',
|
|
167
201
|
background: colors.cardBg,
|
|
168
202
|
borderRadius: '12px',
|
|
169
203
|
boxShadow: '0 10px 30px rgba(0,0,0,.3)',
|
|
@@ -171,6 +205,10 @@ function updateCard(config) {
|
|
|
171
205
|
display: isOpen ? 'flex' : 'none',
|
|
172
206
|
flexDirection: 'column'
|
|
173
207
|
})
|
|
208
|
+
if (window.innerWidth < 450) {
|
|
209
|
+
card.style.bottom = '10px'
|
|
210
|
+
card.style.right = '5px'
|
|
211
|
+
}
|
|
174
212
|
|
|
175
213
|
card.style.setProperty('--vc-text-muted', colors.mutedText)
|
|
176
214
|
card.style.setProperty('--vc-bot-bg', colors.botBubble)
|
|
@@ -180,7 +218,7 @@ function updateCard(config) {
|
|
|
180
218
|
const sendBtn = card.querySelector('#vc-send')
|
|
181
219
|
|
|
182
220
|
// send message
|
|
183
|
-
async function sendMessage() {
|
|
221
|
+
async function sendMessage () {
|
|
184
222
|
const text = input.value.trim()
|
|
185
223
|
if (!text) return
|
|
186
224
|
|
|
@@ -213,14 +251,16 @@ function updateCard(config) {
|
|
|
213
251
|
})
|
|
214
252
|
const data = await res.json()
|
|
215
253
|
hideLoader(sendBtn)
|
|
216
|
-
addBotMessage(
|
|
254
|
+
addBotMessage(
|
|
255
|
+
data?.data?.blocks?.map(b => b.text).join('\n') || 'No response'
|
|
256
|
+
)
|
|
217
257
|
} catch {
|
|
218
258
|
hideLoader(sendBtn)
|
|
219
259
|
addBotMessage('⚠️ Something went wrong')
|
|
220
260
|
}
|
|
221
261
|
}
|
|
222
262
|
|
|
223
|
-
function addBotMessage(text) {
|
|
263
|
+
function addBotMessage (text) {
|
|
224
264
|
const div = document.createElement('div')
|
|
225
265
|
div.style.cssText = `
|
|
226
266
|
background:${colors.botBubble};
|
|
@@ -236,7 +276,9 @@ function updateCard(config) {
|
|
|
236
276
|
|
|
237
277
|
// events
|
|
238
278
|
sendBtn.onclick = sendMessage
|
|
239
|
-
input.addEventListener('keydown', e => {
|
|
279
|
+
input.addEventListener('keydown', e => {
|
|
280
|
+
if (e.key === 'Enter') sendMessage()
|
|
281
|
+
})
|
|
240
282
|
|
|
241
283
|
return { messages, input, sendBtn }
|
|
242
284
|
}
|
|
@@ -244,7 +286,7 @@ function updateCard(config) {
|
|
|
244
286
|
/* =========================================================
|
|
245
287
|
LOADER
|
|
246
288
|
========================================================= */
|
|
247
|
-
function showLoader(container, sendBtn) {
|
|
289
|
+
function showLoader (container, sendBtn) {
|
|
248
290
|
loaderEl = document.createElement('div')
|
|
249
291
|
loaderEl.className = 'vc-loader'
|
|
250
292
|
loaderEl.innerHTML = '<span></span><span></span><span></span>'
|
|
@@ -257,7 +299,7 @@ function showLoader(container, sendBtn) {
|
|
|
257
299
|
}
|
|
258
300
|
}
|
|
259
301
|
|
|
260
|
-
function hideLoader(sendBtn) {
|
|
302
|
+
function hideLoader (sendBtn) {
|
|
261
303
|
loaderEl?.remove()
|
|
262
304
|
loaderEl = null
|
|
263
305
|
|
|
@@ -272,7 +314,7 @@ function hideLoader(sendBtn) {
|
|
|
272
314
|
PUBLIC API
|
|
273
315
|
========================================================= */
|
|
274
316
|
const VaultChat = {
|
|
275
|
-
init(config = {}) {
|
|
317
|
+
init (config = {}) {
|
|
276
318
|
injectStylesOnce()
|
|
277
319
|
updateButton(config)
|
|
278
320
|
|
|
@@ -288,7 +330,7 @@ const VaultChat = {
|
|
|
288
330
|
}
|
|
289
331
|
},
|
|
290
332
|
|
|
291
|
-
destroy() {
|
|
333
|
+
destroy () {
|
|
292
334
|
vcButton?.remove()
|
|
293
335
|
vcCard?.remove()
|
|
294
336
|
vcButton = null
|
package/package.json
CHANGED
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mobil80-dev/chatbot-widget",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.5",
|
|
4
4
|
"description": "Drop-in JavaScript chat widget for websites (no iframe, no framework)",
|
|
5
|
-
"
|
|
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
|
-
|
|
15
|
-
"
|
|
16
|
-
|
|
17
|
-
|
|
22
|
+
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "rollup -c"
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"rollup": "^4.54.0"
|
|
18
29
|
}
|
|
19
30
|
}
|