@mobil80-dev/chatbot-widget 2.0.0 → 2.0.2
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/README.md +36 -14
- package/index.js +287 -284
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -11,6 +11,7 @@ A lightweight JavaScript chatbot widget powered by VaultChat, designed to be emb
|
|
|
11
11
|
- No iframe
|
|
12
12
|
- No dependencies
|
|
13
13
|
- Easy setup
|
|
14
|
+
- Optional page/container restriction via `attachToElement`
|
|
14
15
|
|
|
15
16
|
## 📦 Installation
|
|
16
17
|
|
|
@@ -29,7 +30,7 @@ theme: 'light' | 'dark'
|
|
|
29
30
|
|
|
30
31
|
## ✅ Supported Button Types
|
|
31
32
|
|
|
32
|
-
You can use
|
|
33
|
+
You can use text, image, or custom HTML as the floating button.
|
|
33
34
|
|
|
34
35
|
## ✅ Supported Button Shapes
|
|
35
36
|
|
|
@@ -48,7 +49,7 @@ You can use circle, square, pill as the floating button shape.
|
|
|
48
49
|
primaryColor: '#7c3aed',
|
|
49
50
|
theme: 'light' | 'dark'
|
|
50
51
|
buttonContent: '💬', // content | image Url | emote
|
|
51
|
-
buttonType: '
|
|
52
|
+
buttonType: 'text', // text | image
|
|
52
53
|
buttonShape: 'circle' // circle | square | pill
|
|
53
54
|
})
|
|
54
55
|
</script>
|
|
@@ -68,7 +69,7 @@ onMounted(() => {
|
|
|
68
69
|
primaryColor: '#7c3aed',
|
|
69
70
|
theme: 'light' | 'dark'
|
|
70
71
|
buttonContent: '💬', // content | image Url | emote
|
|
71
|
-
buttonType: '
|
|
72
|
+
buttonType: 'test', // text | image
|
|
72
73
|
buttonShape: 'circle' // circle | square | pill
|
|
73
74
|
})
|
|
74
75
|
})
|
|
@@ -88,7 +89,7 @@ useEffect(() => {
|
|
|
88
89
|
primaryColor: '#7c3aed',
|
|
89
90
|
theme: 'light' | 'dark'
|
|
90
91
|
buttonContent: '💬', // content | image Url | emote
|
|
91
|
-
buttonType: '
|
|
92
|
+
buttonType: 'text', // text | image
|
|
92
93
|
buttonShape: 'circle' // circle | square | pill
|
|
93
94
|
})
|
|
94
95
|
}, [])
|
|
@@ -106,26 +107,47 @@ VaultChat.init({
|
|
|
106
107
|
primaryColor: '#7c3aed',
|
|
107
108
|
theme: 'light' | 'dark'
|
|
108
109
|
buttonContent: '💬', // content | image Url | emote
|
|
109
|
-
buttonType: '
|
|
110
|
+
buttonType: 'text', // text | image
|
|
110
111
|
buttonShape: 'circle' // circle | square | pill
|
|
111
112
|
})
|
|
112
113
|
|
|
113
114
|
```
|
|
114
115
|
|
|
116
|
+
## 📌 attachToElement (Optional)
|
|
117
|
+
|
|
118
|
+
VaultChat allows you to specify a container where the chat widget should be rendered. By default, the chat button and card are appended to document.body.
|
|
119
|
+
|
|
120
|
+
You can restrict the widget to a specific element by passing a CSS selector or a DOM element using the attachToElement option:
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
// Using a CSS selector
|
|
124
|
+
VaultChat.init({
|
|
125
|
+
apiKey: 'YOUR_API_KEY',
|
|
126
|
+
attachToElement: '#dashboard-container'
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
// Using a DOM element
|
|
130
|
+
const container = document.getElementById('dashboard-container')
|
|
131
|
+
VaultChat.init({
|
|
132
|
+
apiKey: 'YOUR_API_KEY',
|
|
133
|
+
attachToElement: container
|
|
134
|
+
})
|
|
135
|
+
```
|
|
136
|
+
|
|
115
137
|
📌 Call this inside ngOnInit() of your root or layout component.
|
|
116
138
|
|
|
117
139
|
## ⚙️ Configuration Reference
|
|
118
140
|
|
|
119
141
|
```
|
|
120
|
-
| Option
|
|
121
|
-
|
|
|
122
|
-
| apiKey
|
|
123
|
-
| primaryColor
|
|
124
|
-
| theme
|
|
125
|
-
| buttonContent
|
|
126
|
-
| buttonType
|
|
127
|
-
| buttonShape
|
|
128
|
-
|
|
142
|
+
| Option | Type | Description |
|
|
143
|
+
| --------------- | ---------- | ----------------------------------------------- |
|
|
144
|
+
| apiKey | string | Your VaultChat API key |
|
|
145
|
+
| primaryColor | string | Primary UI color |
|
|
146
|
+
| theme | string | `light` or `dark` |
|
|
147
|
+
| buttonContent | string | Text, image URL, or emoji |
|
|
148
|
+
| buttonType | string | `text` or `image` |
|
|
149
|
+
| buttonShape | string | `circle`, `square`, or `pill` |
|
|
150
|
+
| attachToElement | string/DOM | CSS selector or DOM element to mount the widget |
|
|
129
151
|
|
|
130
152
|
```
|
|
131
153
|
|
package/index.js
CHANGED
|
@@ -1,297 +1,300 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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; }
|
|
67
|
-
|
|
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
|
|
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;
|
|
94
22
|
}
|
|
95
|
-
|
|
96
|
-
|
|
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
|
-
})
|
|
124
|
-
|
|
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
|
-
})
|
|
190
|
-
|
|
191
|
-
card.style.setProperty('--vc-bot-bg', colors.botBubble)
|
|
192
|
-
card.style.setProperty('--vc-text-muted', colors.mutedText)
|
|
193
|
-
|
|
194
|
-
let open = false
|
|
195
|
-
button.onclick = () => {
|
|
196
|
-
open = !open
|
|
197
|
-
card.style.display = open ? 'flex' : 'none'
|
|
23
|
+
.vc-fab:hover {
|
|
24
|
+
box-shadow: 0 12px 28px rgba(0,0,0,.3);
|
|
198
25
|
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
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
|
|
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;
|
|
222
33
|
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
messages.scrollTop = messages.scrollHeight
|
|
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;
|
|
230
40
|
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
}
|
|
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); }
|
|
237
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
|
+
}
|
|
238
62
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
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 = 'Chat', 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
|
|
251
204
|
}
|
|
252
205
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
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
|
-
})
|
|
276
|
-
|
|
277
|
-
const data = await res.json()
|
|
278
|
-
hideLoader()
|
|
279
|
-
|
|
280
|
-
const reply =
|
|
281
|
-
data?.data?.blocks?.map(b => b.text).join('\n') ||
|
|
282
|
-
'No response received'
|
|
283
|
-
|
|
284
|
-
addBotMessage(reply)
|
|
285
|
-
} catch {
|
|
286
|
-
hideLoader()
|
|
287
|
-
addBotMessage('⚠️ Something went wrong')
|
|
288
|
-
}
|
|
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')
|
|
289
220
|
}
|
|
221
|
+
}
|
|
290
222
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
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
|
|
295
298
|
}
|
|
296
299
|
}
|
|
297
300
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mobil80-dev/chatbot-widget",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.2",
|
|
4
4
|
"description": "Drop-in JavaScript chat widget for websites (no iframe, no framework)",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"javascript",
|
|
12
12
|
"vaultchat"
|
|
13
13
|
],
|
|
14
|
-
"author": "
|
|
14
|
+
"author": "NanthaGopal",
|
|
15
15
|
"license": "MIT",
|
|
16
16
|
"dependencies": {
|
|
17
17
|
"@mobil80-dev/chatbot-widget": "^1.0.8"
|