@helllo-ai/agent-chat-widget 0.1.17 → 0.1.21
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/agent-chat.latest.js +446 -27
- package/agent-chat.prod.js +285 -74
- package/package.json +1 -1
package/agent-chat.latest.js
CHANGED
|
@@ -20,10 +20,18 @@
|
|
|
20
20
|
return `
|
|
21
21
|
:host, .acw * { box-sizing: border-box; }
|
|
22
22
|
.acw-container { position: fixed; z-index: 2147483000; font-family: Inter, system-ui, -apple-system, sans-serif; }
|
|
23
|
+
.acw-container.bottom-right { right: 16px; bottom: 16px; }
|
|
24
|
+
.acw-container.middle-right { right: 16px; top: 50%; transform: translateY(-50%); }
|
|
23
25
|
.acw-launcher { background: ${safePrimary}; color: #fff; border: none; border-radius: 999px; padding: 10px 14px; cursor: pointer; display: inline-flex; align-items: center; justify-content: center; gap: 8px; box-shadow: 0 8px 24px rgba(0,0,0,0.18); font-size: 14px; transition: transform 0.2s; }
|
|
24
26
|
.acw-launcher:hover { opacity: 0.95; }
|
|
27
|
+
.acw-launcher img { width: 24px; height: 24px; object-fit: contain; }
|
|
25
28
|
.acw-launcher.vertical { flex-direction: column; padding: 14px 10px; min-width: 48px; height: auto; }
|
|
26
29
|
.acw-launcher.vertical span { writing-mode: sideways-lr; text-orientation: mixed; letter-spacing: 0.5px; }
|
|
30
|
+
.acw-header-left { display: flex; align-items: center; gap: 10px; flex: 1; min-width: 0; }
|
|
31
|
+
.acw-header-icon { width: 28px; height: 28px; object-fit: contain; flex-shrink: 0; }
|
|
32
|
+
.acw-header-title-wrap { display: flex; align-items: center; gap: 8px; min-width: 0; }
|
|
33
|
+
.acw-unread-badge { background: rgba(255,255,255,0.9); color: ${safePrimary}; font-size: 11px; font-weight: 700; min-width: 18px; height: 18px; border-radius: 9px; display: inline-flex; align-items: center; justify-content: center; padding: 0 5px; }
|
|
34
|
+
.acw-unread-badge.hidden { display: none; }
|
|
27
35
|
.acw-panel { position: absolute; width: 360px; max-width: 90vw; height: 520px; max-height: 80vh; background: ${safeBg}; border: 1px solid rgba(0,0,0,0.08); border-radius: 12px; box-shadow: 0 16px 40px rgba(0,0,0,0.22); display: none; flex-direction: column; overflow: hidden; }
|
|
28
36
|
.acw-panel.bottom-right { right: 0; bottom: 56px; }
|
|
29
37
|
.acw-panel.middle-right { right: 56px; top: 50%; transform: translateY(-50%); }
|
|
@@ -40,13 +48,30 @@
|
|
|
40
48
|
.acw-msg-bot { margin-right: auto; background: rgba(0,0,0,0.05); color: #111; }
|
|
41
49
|
.acw-input { border-top: 1px solid rgba(0,0,0,0.08); padding: 10px; display: flex; gap: 8px; }
|
|
42
50
|
.acw-input input { flex: 1; padding: 10px 12px; border: 1px solid rgba(0,0,0,0.12); border-radius: 8px; font-size: 14px; }
|
|
51
|
+
.acw-input input:disabled, .acw-input button:disabled { opacity: 0.6; cursor: not-allowed; }
|
|
43
52
|
.acw-input button { background: ${safePrimary}; color: #fff; border: none; border-radius: 8px; padding: 0 14px; cursor: pointer; font-weight: 600; }
|
|
53
|
+
.acw-footer { font-size: 11px; color: #94a3b8; padding: 6px 12px 10px; text-align: center; border-top: 1px solid rgba(0,0,0,0.06); }
|
|
54
|
+
.acw-footer a { color: #64748b; text-decoration: none; }
|
|
55
|
+
.acw-footer a:hover { text-decoration: underline; }
|
|
44
56
|
.acw-status { font-size: 12px; color: #666; padding: 8px 12px; display: flex; align-items: center; gap: 10px; border-bottom: 1px solid rgba(0,0,0,0.06); }
|
|
45
57
|
.acw-dot { width: 8px; height: 8px; border-radius: 999px; box-shadow: 0 0 0 6px rgba(0,0,0,0.04); }
|
|
46
58
|
.acw-dot.connected { background: #22c55e; box-shadow: 0 0 0 6px rgba(34,197,94,0.18); }
|
|
47
59
|
.acw-dot.disconnected { background: #ef4444; box-shadow: 0 0 0 6px rgba(239,68,68,0.18); }
|
|
48
|
-
.acw-
|
|
49
|
-
.acw-
|
|
60
|
+
.acw-customer-form { position: absolute; inset: 0; background: ${safeBg}; z-index: 10; display: flex; flex-direction: column; padding: 20px; }
|
|
61
|
+
.acw-customer-form h3 { margin: 0 0 12px; font-size: 16px; font-weight: 600; color: #0f172a; }
|
|
62
|
+
.acw-customer-form p { margin: 0 0 16px; font-size: 13px; color: #64748b; }
|
|
63
|
+
.acw-customer-form .field { margin-bottom: 16px; }
|
|
64
|
+
.acw-customer-form label { display: block; font-size: 13px; font-weight: 500; margin-bottom: 6px; color: #475569; }
|
|
65
|
+
.acw-customer-form input { width: 100%; padding: 10px 12px; border: 1px solid rgba(0,0,0,0.12); border-radius: 8px; font-size: 14px; }
|
|
66
|
+
.acw-customer-form .actions { display: flex; gap: 8px; margin-top: auto; }
|
|
67
|
+
.acw-customer-form button { flex: 1; padding: 10px; border: none; border-radius: 8px; font-size: 14px; font-weight: 600; cursor: pointer; }
|
|
68
|
+
.acw-customer-form .submit-btn { background: ${safePrimary}; color: #fff; }
|
|
69
|
+
.acw-customer-form .submit-btn:hover { opacity: 0.95; }
|
|
70
|
+
.acw-customer-form .skip-btn { background: #f8fafc; color: #64748b; border: 1px solid rgba(0,0,0,0.08); }
|
|
71
|
+
.acw-customer-form .skip-btn:hover { background: #eef2f7; }
|
|
72
|
+
.acw-start-cta { flex: 1; display: flex; align-items: center; justify-content: center; padding: 24px; }
|
|
73
|
+
.acw-start-cta button { background: ${safePrimary}; color: #fff; border: none; border-radius: 10px; padding: 14px 24px; font-size: 15px; font-weight: 600; cursor: pointer; box-shadow: 0 4px 12px rgba(0,0,0,0.12); }
|
|
74
|
+
.acw-start-cta button:hover { opacity: 0.95; }
|
|
50
75
|
@media (max-width: 480px) {
|
|
51
76
|
.acw-panel { width: calc(100vw - 24px); height: 70vh; }
|
|
52
77
|
}
|
|
@@ -65,9 +90,30 @@
|
|
|
65
90
|
wsUrl,
|
|
66
91
|
title = 'Chat with us',
|
|
67
92
|
greeting = null,
|
|
93
|
+
captureCustomerInfo = false,
|
|
94
|
+
allowSkipCustomerInfo = true,
|
|
68
95
|
position = 'bottom-right',
|
|
96
|
+
iconUrl = null,
|
|
97
|
+
showIcon = true,
|
|
98
|
+
launcherText = 'Chat',
|
|
69
99
|
} = config
|
|
70
100
|
|
|
101
|
+
// Resolve icon: explicit iconUrl, then page favicon, then /favicon.ico (only used when showIcon is true)
|
|
102
|
+
const resolvedIconUrl = (() => {
|
|
103
|
+
if (!showIcon) return null
|
|
104
|
+
if (iconUrl && typeof iconUrl === 'string' && iconUrl.trim()) return iconUrl.trim()
|
|
105
|
+
try {
|
|
106
|
+
const link = document.querySelector('link[rel="icon"]') || document.querySelector('link[rel="shortcut icon"]')
|
|
107
|
+
if (link && link.href) return link.href
|
|
108
|
+
} catch (_) {}
|
|
109
|
+
try {
|
|
110
|
+
const origin = window.location.origin || ''
|
|
111
|
+
if (origin) return origin + '/favicon.ico'
|
|
112
|
+
} catch (_) {}
|
|
113
|
+
return null
|
|
114
|
+
})()
|
|
115
|
+
const launcherLabel = (typeof launcherText === 'string' && launcherText.trim()) ? launcherText.trim() : 'Chat'
|
|
116
|
+
|
|
71
117
|
// Valid positions
|
|
72
118
|
const validPositions = ['bottom-right', 'middle-right', 'top-right', 'bottom-left', 'middle-left', 'top-left']
|
|
73
119
|
const finalPosition = validPositions.includes(position) ? position : 'bottom-right'
|
|
@@ -130,13 +176,42 @@
|
|
|
130
176
|
|
|
131
177
|
const launcher = document.createElement('button')
|
|
132
178
|
launcher.className = isVertical ? 'acw-launcher vertical' : 'acw-launcher'
|
|
133
|
-
|
|
179
|
+
if (resolvedIconUrl) {
|
|
180
|
+
const launcherImg = document.createElement('img')
|
|
181
|
+
launcherImg.src = resolvedIconUrl
|
|
182
|
+
launcherImg.alt = launcherLabel
|
|
183
|
+
launcher.appendChild(launcherImg)
|
|
184
|
+
const launcherTextSpan = document.createElement('span')
|
|
185
|
+
launcherTextSpan.textContent = launcherLabel
|
|
186
|
+
launcher.appendChild(launcherTextSpan)
|
|
187
|
+
} else {
|
|
188
|
+
const span = document.createElement('span')
|
|
189
|
+
span.textContent = launcherLabel
|
|
190
|
+
launcher.appendChild(span)
|
|
191
|
+
}
|
|
134
192
|
|
|
135
193
|
const header = document.createElement('div')
|
|
136
194
|
header.className = 'acw-header'
|
|
195
|
+
const headerLeft = document.createElement('div')
|
|
196
|
+
headerLeft.className = 'acw-header-left'
|
|
197
|
+
if (resolvedIconUrl) {
|
|
198
|
+
const headerIcon = document.createElement('img')
|
|
199
|
+
headerIcon.className = 'acw-header-icon'
|
|
200
|
+
headerIcon.src = resolvedIconUrl
|
|
201
|
+
headerIcon.alt = ''
|
|
202
|
+
headerLeft.appendChild(headerIcon)
|
|
203
|
+
}
|
|
204
|
+
const titleWrap = document.createElement('div')
|
|
205
|
+
titleWrap.className = 'acw-header-title-wrap'
|
|
137
206
|
const titleEl = document.createElement('div')
|
|
138
207
|
titleEl.className = 'acw-title'
|
|
139
208
|
titleEl.textContent = title
|
|
209
|
+
const unreadBadge = document.createElement('span')
|
|
210
|
+
unreadBadge.className = 'acw-unread-badge hidden'
|
|
211
|
+
unreadBadge.textContent = '0'
|
|
212
|
+
titleWrap.appendChild(titleEl)
|
|
213
|
+
titleWrap.appendChild(unreadBadge)
|
|
214
|
+
headerLeft.appendChild(titleWrap)
|
|
140
215
|
const headerActions = document.createElement('div')
|
|
141
216
|
headerActions.style.display = 'flex'
|
|
142
217
|
headerActions.style.alignItems = 'center'
|
|
@@ -151,7 +226,7 @@
|
|
|
151
226
|
|
|
152
227
|
headerActions.appendChild(minimizeBtn)
|
|
153
228
|
headerActions.appendChild(closeBtn)
|
|
154
|
-
header.appendChild(
|
|
229
|
+
header.appendChild(headerLeft)
|
|
155
230
|
header.appendChild(headerActions)
|
|
156
231
|
|
|
157
232
|
const statusEl = document.createElement('div')
|
|
@@ -160,15 +235,17 @@
|
|
|
160
235
|
statusDot.className = 'acw-dot disconnected'
|
|
161
236
|
const statusText = document.createElement('span')
|
|
162
237
|
statusText.textContent = 'Disconnected'
|
|
163
|
-
const disconnectBtn = document.createElement('button')
|
|
164
|
-
disconnectBtn.className = 'acw-disconnect'
|
|
165
|
-
disconnectBtn.textContent = 'Connect'
|
|
166
238
|
statusEl.appendChild(statusDot)
|
|
167
239
|
statusEl.appendChild(statusText)
|
|
168
|
-
|
|
240
|
+
|
|
241
|
+
const startCtaEl = document.createElement('div')
|
|
242
|
+
startCtaEl.className = 'acw-start-cta'
|
|
243
|
+
const startConversationBtn = document.createElement('button')
|
|
244
|
+
startConversationBtn.textContent = 'Click to Start Conversation'
|
|
245
|
+
startCtaEl.appendChild(startConversationBtn)
|
|
169
246
|
|
|
170
247
|
const messages = document.createElement('div')
|
|
171
|
-
|
|
248
|
+
messages.className = 'acw-messages'
|
|
172
249
|
|
|
173
250
|
const inputWrap = document.createElement('div')
|
|
174
251
|
inputWrap.className = 'acw-input'
|
|
@@ -180,20 +257,219 @@
|
|
|
180
257
|
inputWrap.appendChild(input)
|
|
181
258
|
inputWrap.appendChild(sendBtn)
|
|
182
259
|
|
|
260
|
+
const footerEl = document.createElement('div')
|
|
261
|
+
footerEl.className = 'acw-footer'
|
|
262
|
+
footerEl.style.display = 'none'
|
|
263
|
+
const footerLink = document.createElement('a')
|
|
264
|
+
footerLink.href = 'https://helllo.ai'
|
|
265
|
+
footerLink.target = '_blank'
|
|
266
|
+
footerLink.rel = 'noopener noreferrer'
|
|
267
|
+
footerLink.textContent = 'helllo.ai'
|
|
268
|
+
footerEl.appendChild(document.createTextNode('Powered by '))
|
|
269
|
+
footerEl.appendChild(footerLink)
|
|
270
|
+
|
|
271
|
+
// Customer info form (if enabled)
|
|
272
|
+
let customerForm = null
|
|
273
|
+
let sessionId = null
|
|
274
|
+
let customerInfoSubmitted = false
|
|
275
|
+
let firstMessageReceived = false
|
|
276
|
+
let conversationStarted = false
|
|
277
|
+
let formShownBeforeConnect = false
|
|
278
|
+
let pendingCustomerName = null
|
|
279
|
+
let pendingCustomerPhone = null
|
|
280
|
+
let unreadCount = 0
|
|
281
|
+
|
|
282
|
+
if (captureCustomerInfo) {
|
|
283
|
+
customerForm = document.createElement('div')
|
|
284
|
+
customerForm.className = 'acw-customer-form'
|
|
285
|
+
customerForm.style.display = 'none'
|
|
286
|
+
|
|
287
|
+
const formTitle = document.createElement('h3')
|
|
288
|
+
formTitle.textContent = 'Welcome! Please share your details'
|
|
289
|
+
const formDesc = document.createElement('p')
|
|
290
|
+
formDesc.textContent = 'We need a few details to get started'
|
|
291
|
+
|
|
292
|
+
const nameField = document.createElement('div')
|
|
293
|
+
nameField.className = 'field'
|
|
294
|
+
const nameLabel = document.createElement('label')
|
|
295
|
+
nameLabel.textContent = 'Your Name'
|
|
296
|
+
nameLabel.setAttribute('for', 'acw-customer-name')
|
|
297
|
+
const nameInput = document.createElement('input')
|
|
298
|
+
nameInput.id = 'acw-customer-name'
|
|
299
|
+
nameInput.type = 'text'
|
|
300
|
+
nameInput.placeholder = 'Enter your name'
|
|
301
|
+
nameField.appendChild(nameLabel)
|
|
302
|
+
nameField.appendChild(nameInput)
|
|
303
|
+
|
|
304
|
+
const phoneField = document.createElement('div')
|
|
305
|
+
phoneField.className = 'field'
|
|
306
|
+
const phoneLabel = document.createElement('label')
|
|
307
|
+
phoneLabel.textContent = 'Phone Number'
|
|
308
|
+
phoneLabel.setAttribute('for', 'acw-customer-phone')
|
|
309
|
+
const phoneInput = document.createElement('input')
|
|
310
|
+
phoneInput.id = 'acw-customer-phone'
|
|
311
|
+
phoneInput.type = 'tel'
|
|
312
|
+
phoneInput.placeholder = 'Enter your phone number'
|
|
313
|
+
phoneField.appendChild(phoneLabel)
|
|
314
|
+
phoneField.appendChild(phoneInput)
|
|
315
|
+
|
|
316
|
+
const formActions = document.createElement('div')
|
|
317
|
+
formActions.className = 'actions'
|
|
318
|
+
const submitBtn = document.createElement('button')
|
|
319
|
+
submitBtn.className = 'submit-btn'
|
|
320
|
+
submitBtn.textContent = 'Start Chat'
|
|
321
|
+
const skipBtn = document.createElement('button')
|
|
322
|
+
skipBtn.className = 'skip-btn'
|
|
323
|
+
skipBtn.textContent = 'Skip'
|
|
324
|
+
formActions.appendChild(submitBtn)
|
|
325
|
+
if (allowSkipCustomerInfo) {
|
|
326
|
+
formActions.appendChild(skipBtn)
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
customerForm.appendChild(formTitle)
|
|
330
|
+
customerForm.appendChild(formDesc)
|
|
331
|
+
customerForm.appendChild(nameField)
|
|
332
|
+
customerForm.appendChild(phoneField)
|
|
333
|
+
customerForm.appendChild(formActions)
|
|
334
|
+
|
|
335
|
+
function submitCustomerInfo() {
|
|
336
|
+
const name = nameInput.value.trim()
|
|
337
|
+
const phone = phoneInput.value.trim()
|
|
338
|
+
|
|
339
|
+
if (!name || !phone) {
|
|
340
|
+
alert('Please fill in both name and phone number')
|
|
341
|
+
return
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// Form shown before connect: save and connect; customer_info sent in connection_established
|
|
345
|
+
if (formShownBeforeConnect) {
|
|
346
|
+
pendingCustomerName = name
|
|
347
|
+
pendingCustomerPhone = phone
|
|
348
|
+
customerForm.style.display = 'none'
|
|
349
|
+
messages.style.display = 'flex'
|
|
350
|
+
inputWrap.style.display = 'none'
|
|
351
|
+
connect()
|
|
352
|
+
return
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
if (!sessionId) {
|
|
356
|
+
console.error('[AgentChatWidget] No session ID available')
|
|
357
|
+
alert('Session not established. Please wait for connection.')
|
|
358
|
+
return
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
if (!ws || ws.readyState !== WebSocket.OPEN) {
|
|
362
|
+
alert('WebSocket not connected. Please wait for connection.')
|
|
363
|
+
return
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
try {
|
|
367
|
+
ws.send(JSON.stringify({
|
|
368
|
+
type: 'customer_info',
|
|
369
|
+
session_id: sessionId,
|
|
370
|
+
phone_number: phone,
|
|
371
|
+
customer_name: name,
|
|
372
|
+
embed_key: embedKey
|
|
373
|
+
}))
|
|
374
|
+
customerInfoSubmitted = true
|
|
375
|
+
customerForm.style.display = 'none'
|
|
376
|
+
messages.style.display = 'flex'
|
|
377
|
+
inputWrap.style.display = 'flex'
|
|
378
|
+
} catch (error) {
|
|
379
|
+
console.error('[AgentChatWidget] Error sending customer info:', error)
|
|
380
|
+
alert('Error submitting information. Please try again.')
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function skipCustomerInfo() {
|
|
385
|
+
if (formShownBeforeConnect) {
|
|
386
|
+
customerForm.style.display = 'none'
|
|
387
|
+
messages.style.display = 'flex'
|
|
388
|
+
inputWrap.style.display = 'none'
|
|
389
|
+
connect()
|
|
390
|
+
return
|
|
391
|
+
}
|
|
392
|
+
customerInfoSubmitted = true
|
|
393
|
+
customerForm.style.display = 'none'
|
|
394
|
+
messages.style.display = 'flex'
|
|
395
|
+
inputWrap.style.display = 'flex'
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
submitBtn.onclick = submitCustomerInfo
|
|
399
|
+
if (allowSkipCustomerInfo) skipBtn.onclick = skipCustomerInfo
|
|
400
|
+
|
|
401
|
+
// Allow Enter key to submit
|
|
402
|
+
nameInput.addEventListener('keydown', (e) => {
|
|
403
|
+
if (e.key === 'Enter') {
|
|
404
|
+
e.preventDefault()
|
|
405
|
+
phoneInput.focus()
|
|
406
|
+
}
|
|
407
|
+
})
|
|
408
|
+
phoneInput.addEventListener('keydown', (e) => {
|
|
409
|
+
if (e.key === 'Enter') {
|
|
410
|
+
e.preventDefault()
|
|
411
|
+
submitCustomerInfo()
|
|
412
|
+
}
|
|
413
|
+
})
|
|
414
|
+
|
|
415
|
+
panel.appendChild(customerForm)
|
|
416
|
+
}
|
|
417
|
+
|
|
183
418
|
panel.appendChild(header)
|
|
184
419
|
panel.appendChild(statusEl)
|
|
420
|
+
panel.appendChild(startCtaEl)
|
|
185
421
|
panel.appendChild(messages)
|
|
186
422
|
panel.appendChild(inputWrap)
|
|
423
|
+
panel.appendChild(footerEl)
|
|
187
424
|
shadow.appendChild(panel)
|
|
188
425
|
shadow.appendChild(launcher)
|
|
189
426
|
document.body.appendChild(container)
|
|
190
427
|
|
|
428
|
+
// Initial state: show start CTA, hide messages and input until user starts conversation
|
|
429
|
+
messages.style.display = 'none'
|
|
430
|
+
inputWrap.style.display = 'none'
|
|
431
|
+
|
|
432
|
+
function showStartCta() {
|
|
433
|
+
startCtaEl.style.display = 'flex'
|
|
434
|
+
messages.style.display = 'none'
|
|
435
|
+
inputWrap.style.display = 'none'
|
|
436
|
+
footerEl.style.display = 'none'
|
|
437
|
+
if (customerForm) customerForm.style.display = 'none'
|
|
438
|
+
}
|
|
439
|
+
function showFormOnly() {
|
|
440
|
+
startCtaEl.style.display = 'none'
|
|
441
|
+
messages.style.display = 'none'
|
|
442
|
+
inputWrap.style.display = 'none'
|
|
443
|
+
footerEl.style.display = 'none'
|
|
444
|
+
if (customerForm) customerForm.style.display = 'flex'
|
|
445
|
+
}
|
|
446
|
+
function showChat() {
|
|
447
|
+
startCtaEl.style.display = 'none'
|
|
448
|
+
if (customerForm) customerForm.style.display = 'none'
|
|
449
|
+
messages.style.display = 'flex'
|
|
450
|
+
inputWrap.style.display = 'flex'
|
|
451
|
+
footerEl.style.display = 'block'
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
function updateUnreadBadge() {
|
|
455
|
+
unreadBadge.textContent = String(unreadCount)
|
|
456
|
+
if (unreadCount > 0) {
|
|
457
|
+
unreadBadge.classList.remove('hidden')
|
|
458
|
+
} else {
|
|
459
|
+
unreadBadge.classList.add('hidden')
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
191
463
|
function appendMessage(text, role) {
|
|
192
464
|
const msg = document.createElement('div')
|
|
193
465
|
msg.className = 'acw-msg ' + (role === 'user' ? 'acw-msg-user' : 'acw-msg-bot')
|
|
194
466
|
msg.textContent = text
|
|
195
467
|
messages.appendChild(msg)
|
|
196
468
|
messages.scrollTop = messages.scrollHeight
|
|
469
|
+
if (role === 'bot' && panel.style.display !== 'flex') {
|
|
470
|
+
unreadCount++
|
|
471
|
+
updateUnreadBadge()
|
|
472
|
+
}
|
|
197
473
|
}
|
|
198
474
|
|
|
199
475
|
// No default greeting; only show messages after connection
|
|
@@ -203,10 +479,32 @@
|
|
|
203
479
|
let connecting = false
|
|
204
480
|
|
|
205
481
|
const resolvedWsUrl = (() => {
|
|
482
|
+
// Explicit wsUrl takes highest priority
|
|
206
483
|
if (wsUrl) return wsUrl
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
484
|
+
|
|
485
|
+
// Determine environment-specific WebSocket URL based on VERSION
|
|
486
|
+
let wsBase
|
|
487
|
+
if (VERSION.includes('-staging')) {
|
|
488
|
+
wsBase = 'wss://talk2ai-staging.helllo.ai'
|
|
489
|
+
} else if (VERSION.includes('-prod')) {
|
|
490
|
+
wsBase = 'wss://talk2ai-prod.helllo.ai'
|
|
491
|
+
} else {
|
|
492
|
+
// For dev/latest, use existing fallback logic
|
|
493
|
+
const baseCandidate = voiceServiceUrl || apiBaseUrl || new URL(document.currentScript?.src || window.location.href).origin
|
|
494
|
+
const voiceBase = (baseCandidate || '').replace(/\/$/, '')
|
|
495
|
+
// Handle both http/https and ws/wss URLs
|
|
496
|
+
if (voiceBase.startsWith('http://')) {
|
|
497
|
+
wsBase = voiceBase.replace(/^http/, 'ws')
|
|
498
|
+
} else if (voiceBase.startsWith('https://')) {
|
|
499
|
+
wsBase = voiceBase.replace(/^https/, 'wss')
|
|
500
|
+
} else if (voiceBase.startsWith('ws://') || voiceBase.startsWith('wss://')) {
|
|
501
|
+
wsBase = voiceBase // Already a WebSocket URL
|
|
502
|
+
} else {
|
|
503
|
+
// Default to ws if no protocol
|
|
504
|
+
wsBase = 'ws://' + voiceBase
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
210
508
|
const params = new URLSearchParams()
|
|
211
509
|
params.set('embed_key', embedKey)
|
|
212
510
|
params.set('host', window.location.hostname)
|
|
@@ -218,25 +516,34 @@
|
|
|
218
516
|
console.info('[AgentChatWidget] Resolved WS URL:', resolvedWsUrl)
|
|
219
517
|
} catch (_) {}
|
|
220
518
|
|
|
519
|
+
function setInputEnabled(enabled) {
|
|
520
|
+
input.disabled = !enabled
|
|
521
|
+
sendBtn.disabled = !enabled
|
|
522
|
+
input.placeholder = enabled ? 'Type a message...' : 'Connect to start chatting'
|
|
523
|
+
}
|
|
524
|
+
|
|
221
525
|
function updateStatus(text, isConnected) {
|
|
222
526
|
statusText.textContent = text
|
|
223
527
|
statusDot.classList.remove('connected', 'disconnected')
|
|
224
528
|
statusDot.classList.add(isConnected ? 'connected' : 'disconnected')
|
|
225
|
-
|
|
226
|
-
// Update button text and handler based on connection status
|
|
227
|
-
disconnectBtn.textContent = isConnected ? 'Disconnect' : 'Connect'
|
|
228
|
-
disconnectBtn.onclick = isConnected ? disconnect : connect
|
|
529
|
+
setInputEnabled(isConnected)
|
|
229
530
|
}
|
|
230
531
|
|
|
231
532
|
function connect() {
|
|
232
533
|
if (connected || connecting) return
|
|
233
534
|
connecting = true
|
|
535
|
+
firstMessageReceived = false
|
|
536
|
+
startCtaEl.style.display = 'none'
|
|
537
|
+
// Show chat UI immediately so the panel is not empty while connecting
|
|
538
|
+
showChat()
|
|
539
|
+
setInputEnabled(false)
|
|
234
540
|
const url = resolvedWsUrl
|
|
235
541
|
try {
|
|
236
542
|
ws = new WebSocket(url)
|
|
237
543
|
} catch (err) {
|
|
238
544
|
connecting = false
|
|
239
|
-
updateStatus('
|
|
545
|
+
updateStatus('Disconnected', false)
|
|
546
|
+
appendMessage('Failed to connect. Please try again or check your connection.', 'bot')
|
|
240
547
|
console.error('[AgentChatWidget] WS error', err)
|
|
241
548
|
return
|
|
242
549
|
}
|
|
@@ -245,33 +552,93 @@
|
|
|
245
552
|
connected = true
|
|
246
553
|
connecting = false
|
|
247
554
|
updateStatus('Connected', true)
|
|
555
|
+
// If captureCustomerInfo, connection_established will show form or chat
|
|
556
|
+
if (!captureCustomerInfo) showChat()
|
|
248
557
|
}
|
|
249
558
|
|
|
250
559
|
ws.onmessage = (event) => {
|
|
251
560
|
try {
|
|
252
561
|
const data = JSON.parse(event.data)
|
|
562
|
+
|
|
563
|
+
// Handle connection_established message
|
|
564
|
+
if (data.type === 'connection_established') {
|
|
565
|
+
if (data.session_id) sessionId = data.session_id
|
|
566
|
+
|
|
567
|
+
// Form was shown before connect (Start Conversation → form → submit/skip): send customer_info if pending, then show chat
|
|
568
|
+
if (formShownBeforeConnect) {
|
|
569
|
+
if (pendingCustomerName && pendingCustomerPhone && ws && ws.readyState === WebSocket.OPEN) {
|
|
570
|
+
try {
|
|
571
|
+
ws.send(JSON.stringify({
|
|
572
|
+
type: 'customer_info',
|
|
573
|
+
session_id: sessionId,
|
|
574
|
+
phone_number: pendingCustomerPhone,
|
|
575
|
+
customer_name: pendingCustomerName,
|
|
576
|
+
embed_key: embedKey
|
|
577
|
+
}))
|
|
578
|
+
} catch (e) {
|
|
579
|
+
console.error('[AgentChatWidget] Error sending customer info:', e)
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
pendingCustomerName = null
|
|
583
|
+
pendingCustomerPhone = null
|
|
584
|
+
customerInfoSubmitted = true
|
|
585
|
+
if (data.welcome_message) appendMessage(data.welcome_message, 'bot')
|
|
586
|
+
showChat()
|
|
587
|
+
return
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
// Display welcome message if provided
|
|
591
|
+
if (data.welcome_message) {
|
|
592
|
+
appendMessage(data.welcome_message, 'bot')
|
|
593
|
+
if (!firstMessageReceived && captureCustomerInfo && customerForm && !customerInfoSubmitted) {
|
|
594
|
+
firstMessageReceived = true
|
|
595
|
+
setTimeout(() => {
|
|
596
|
+
showFormOnly()
|
|
597
|
+
const nameInput = customerForm.querySelector('#acw-customer-name')
|
|
598
|
+
if (nameInput) setTimeout(() => nameInput.focus(), 100)
|
|
599
|
+
}, 300)
|
|
600
|
+
}
|
|
601
|
+
} else {
|
|
602
|
+
if (!firstMessageReceived && captureCustomerInfo && customerForm && !customerInfoSubmitted) {
|
|
603
|
+
firstMessageReceived = true
|
|
604
|
+
setTimeout(() => {
|
|
605
|
+
showFormOnly()
|
|
606
|
+
const nameInput = customerForm.querySelector('#acw-customer-name')
|
|
607
|
+
if (nameInput) setTimeout(() => nameInput.focus(), 100)
|
|
608
|
+
}, 100)
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
if (captureCustomerInfo && !customerInfoSubmitted && customerForm) return
|
|
612
|
+
showChat()
|
|
613
|
+
return
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
// Handle regular chat messages
|
|
253
617
|
const text = data.text || data.content || data.message || ''
|
|
254
618
|
const role = data.role || 'assistant'
|
|
255
|
-
if (text)
|
|
619
|
+
if (text) {
|
|
620
|
+
appendMessage(text, role === 'user' ? 'user' : 'bot')
|
|
621
|
+
}
|
|
256
622
|
} catch (e) {
|
|
257
623
|
// Fallback to raw text
|
|
258
|
-
if (event.data)
|
|
624
|
+
if (event.data) {
|
|
625
|
+
appendMessage(String(event.data), 'bot')
|
|
626
|
+
}
|
|
259
627
|
}
|
|
260
628
|
}
|
|
261
629
|
|
|
262
630
|
ws.onerror = (e) => {
|
|
263
631
|
console.error('[AgentChatWidget] WS error', e)
|
|
264
|
-
updateStatus('
|
|
265
|
-
appendMessage('
|
|
632
|
+
updateStatus('Disconnected', false)
|
|
633
|
+
appendMessage('Failed to connect. Please try again or check your connection.', 'bot')
|
|
266
634
|
}
|
|
267
635
|
|
|
268
636
|
ws.onclose = (ev) => {
|
|
269
637
|
connected = false
|
|
270
638
|
connecting = false
|
|
271
|
-
const reason = ev && (ev.reason || ev.code)
|
|
272
639
|
updateStatus('Disconnected', false)
|
|
273
640
|
if (!ev.wasClean) {
|
|
274
|
-
appendMessage(
|
|
641
|
+
appendMessage('Failed to connect. Please try again or check your connection.', 'bot')
|
|
275
642
|
}
|
|
276
643
|
}
|
|
277
644
|
}
|
|
@@ -284,14 +651,38 @@
|
|
|
284
651
|
connected = false
|
|
285
652
|
connecting = false
|
|
286
653
|
updateStatus('Disconnected', false)
|
|
654
|
+
sessionId = null
|
|
655
|
+
customerInfoSubmitted = false
|
|
656
|
+
firstMessageReceived = false
|
|
657
|
+
conversationStarted = false
|
|
658
|
+
formShownBeforeConnect = false
|
|
659
|
+
pendingCustomerName = null
|
|
660
|
+
pendingCustomerPhone = null
|
|
661
|
+
if (customerForm) {
|
|
662
|
+
customerForm.style.display = 'none'
|
|
663
|
+
const nameInput = customerForm.querySelector('#acw-customer-name')
|
|
664
|
+
const phoneInput = customerForm.querySelector('#acw-customer-phone')
|
|
665
|
+
if (nameInput) nameInput.value = ''
|
|
666
|
+
if (phoneInput) phoneInput.value = ''
|
|
667
|
+
}
|
|
668
|
+
showStartCta()
|
|
287
669
|
}
|
|
288
670
|
|
|
289
671
|
function sendMessage() {
|
|
290
672
|
const text = input.value.trim()
|
|
291
673
|
if (!text) return
|
|
674
|
+
|
|
675
|
+
// Don't allow sending messages if customer info form is required and not submitted
|
|
676
|
+
if (captureCustomerInfo && !customerInfoSubmitted) {
|
|
677
|
+
return
|
|
678
|
+
}
|
|
679
|
+
|
|
292
680
|
if (!ws || ws.readyState !== WebSocket.OPEN) {
|
|
293
|
-
|
|
294
|
-
|
|
681
|
+
if (connecting) {
|
|
682
|
+
appendMessage('Connecting... Please wait.', 'bot')
|
|
683
|
+
} else {
|
|
684
|
+
appendMessage('Please click "Click to Start Conversation" to start chatting.', 'bot')
|
|
685
|
+
}
|
|
295
686
|
return
|
|
296
687
|
}
|
|
297
688
|
appendMessage(text, 'user')
|
|
@@ -299,6 +690,19 @@
|
|
|
299
690
|
input.value = ''
|
|
300
691
|
}
|
|
301
692
|
|
|
693
|
+
startConversationBtn.onclick = () => {
|
|
694
|
+
conversationStarted = true
|
|
695
|
+
startCtaEl.style.display = 'none'
|
|
696
|
+
if (captureCustomerInfo && customerForm) {
|
|
697
|
+
formShownBeforeConnect = true
|
|
698
|
+
showFormOnly()
|
|
699
|
+
const nameInput = customerForm.querySelector('#acw-customer-name')
|
|
700
|
+
if (nameInput) setTimeout(() => nameInput.focus(), 100)
|
|
701
|
+
} else {
|
|
702
|
+
connect()
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
|
|
302
706
|
launcher.onclick = () => {
|
|
303
707
|
const isOpen = panel.style.display === 'flex'
|
|
304
708
|
if (isOpen) {
|
|
@@ -306,7 +710,9 @@
|
|
|
306
710
|
disconnect()
|
|
307
711
|
} else {
|
|
308
712
|
panel.style.display = 'flex'
|
|
309
|
-
|
|
713
|
+
unreadCount = 0
|
|
714
|
+
updateUnreadBadge()
|
|
715
|
+
if (!connected && !connecting && !conversationStarted) showStartCta()
|
|
310
716
|
}
|
|
311
717
|
}
|
|
312
718
|
|
|
@@ -335,7 +741,7 @@
|
|
|
335
741
|
},
|
|
336
742
|
open() {
|
|
337
743
|
panel.style.display = 'flex'
|
|
338
|
-
|
|
744
|
+
// Connection only via explicit Connect button
|
|
339
745
|
},
|
|
340
746
|
close() {
|
|
341
747
|
panel.style.display = 'none'
|
|
@@ -348,9 +754,17 @@
|
|
|
348
754
|
const scriptEl = document.currentScript || document.querySelector('script[data-agent-id]')
|
|
349
755
|
const ds = (scriptEl && scriptEl.dataset) || {}
|
|
350
756
|
const allowed = (ds.allowedDomains || ds.allowed_domains || '').split(',').map((d) => d.trim()).filter(Boolean)
|
|
757
|
+
const captureCustomerInfo = userConfig.captureCustomerInfo !== undefined
|
|
758
|
+
? userConfig.captureCustomerInfo
|
|
759
|
+
: ds.captureCustomerInfo === 'true' || ds.capture_customer_info === 'true'
|
|
760
|
+
const allowSkipCustomerInfo = userConfig.allowSkipCustomerInfo !== undefined
|
|
761
|
+
? userConfig.allowSkipCustomerInfo
|
|
762
|
+
: ds.allowSkipCustomerInfo !== 'false' && ds.allow_skip_customer_info !== 'false'
|
|
351
763
|
const position = userConfig.position || ds.position || ds.launcherPosition || 'bottom-right'
|
|
352
764
|
const validPositions = ['bottom-right', 'middle-right', 'top-right', 'bottom-left', 'middle-left', 'top-left']
|
|
353
765
|
const validPosition = validPositions.includes(position) ? position : 'bottom-right'
|
|
766
|
+
const showIcon = userConfig.showIcon !== undefined ? userConfig.showIcon : ds.showIcon !== 'false' && ds.show_icon !== 'false'
|
|
767
|
+
const launcherText = userConfig.launcherText ?? userConfig.launcher_text ?? ds.launcherText ?? ds.launcher_text ?? 'Chat'
|
|
354
768
|
return {
|
|
355
769
|
agentId: userConfig.agentId || ds.agentId,
|
|
356
770
|
embedKey: userConfig.embedKey || ds.embedKey || ds.embed_key,
|
|
@@ -362,7 +776,12 @@
|
|
|
362
776
|
wsUrl: userConfig.wsUrl || ds.wsUrl || ds.ws_url,
|
|
363
777
|
title: userConfig.title || ds.title,
|
|
364
778
|
greeting: userConfig.greeting || ds.greeting,
|
|
779
|
+
captureCustomerInfo: captureCustomerInfo,
|
|
780
|
+
allowSkipCustomerInfo: allowSkipCustomerInfo,
|
|
365
781
|
position: validPosition,
|
|
782
|
+
iconUrl: userConfig.iconUrl || userConfig.icon_url || ds.iconUrl || ds.icon_url,
|
|
783
|
+
showIcon: showIcon,
|
|
784
|
+
launcherText: launcherText,
|
|
366
785
|
}
|
|
367
786
|
}
|
|
368
787
|
|
package/agent-chat.prod.js
CHANGED
|
@@ -24,8 +24,14 @@
|
|
|
24
24
|
.acw-container.middle-right { right: 16px; top: 50%; transform: translateY(-50%); }
|
|
25
25
|
.acw-launcher { background: ${safePrimary}; color: #fff; border: none; border-radius: 999px; padding: 10px 14px; cursor: pointer; display: inline-flex; align-items: center; justify-content: center; gap: 8px; box-shadow: 0 8px 24px rgba(0,0,0,0.18); font-size: 14px; transition: transform 0.2s; }
|
|
26
26
|
.acw-launcher:hover { opacity: 0.95; }
|
|
27
|
+
.acw-launcher img { width: 24px; height: 24px; object-fit: contain; }
|
|
27
28
|
.acw-launcher.vertical { flex-direction: column; padding: 14px 10px; min-width: 48px; height: auto; }
|
|
28
29
|
.acw-launcher.vertical span { writing-mode: sideways-lr; text-orientation: mixed; letter-spacing: 0.5px; }
|
|
30
|
+
.acw-header-left { display: flex; align-items: center; gap: 10px; flex: 1; min-width: 0; }
|
|
31
|
+
.acw-header-icon { width: 28px; height: 28px; object-fit: contain; flex-shrink: 0; }
|
|
32
|
+
.acw-header-title-wrap { display: flex; align-items: center; gap: 8px; min-width: 0; }
|
|
33
|
+
.acw-unread-badge { background: rgba(255,255,255,0.9); color: ${safePrimary}; font-size: 11px; font-weight: 700; min-width: 18px; height: 18px; border-radius: 9px; display: inline-flex; align-items: center; justify-content: center; padding: 0 5px; }
|
|
34
|
+
.acw-unread-badge.hidden { display: none; }
|
|
29
35
|
.acw-panel { position: absolute; width: 360px; max-width: 90vw; height: 520px; max-height: 80vh; background: ${safeBg}; border: 1px solid rgba(0,0,0,0.08); border-radius: 12px; box-shadow: 0 16px 40px rgba(0,0,0,0.22); display: none; flex-direction: column; overflow: hidden; }
|
|
30
36
|
.acw-panel.bottom-right { right: 0; bottom: 56px; }
|
|
31
37
|
.acw-panel.middle-right { right: 56px; top: 50%; transform: translateY(-50%); }
|
|
@@ -42,13 +48,15 @@
|
|
|
42
48
|
.acw-msg-bot { margin-right: auto; background: rgba(0,0,0,0.05); color: #111; }
|
|
43
49
|
.acw-input { border-top: 1px solid rgba(0,0,0,0.08); padding: 10px; display: flex; gap: 8px; }
|
|
44
50
|
.acw-input input { flex: 1; padding: 10px 12px; border: 1px solid rgba(0,0,0,0.12); border-radius: 8px; font-size: 14px; }
|
|
51
|
+
.acw-input input:disabled, .acw-input button:disabled { opacity: 0.6; cursor: not-allowed; }
|
|
45
52
|
.acw-input button { background: ${safePrimary}; color: #fff; border: none; border-radius: 8px; padding: 0 14px; cursor: pointer; font-weight: 600; }
|
|
53
|
+
.acw-footer { font-size: 11px; color: #94a3b8; padding: 6px 12px 10px; text-align: center; border-top: 1px solid rgba(0,0,0,0.06); }
|
|
54
|
+
.acw-footer a { color: #64748b; text-decoration: none; }
|
|
55
|
+
.acw-footer a:hover { text-decoration: underline; }
|
|
46
56
|
.acw-status { font-size: 12px; color: #666; padding: 8px 12px; display: flex; align-items: center; gap: 10px; border-bottom: 1px solid rgba(0,0,0,0.06); }
|
|
47
57
|
.acw-dot { width: 8px; height: 8px; border-radius: 999px; box-shadow: 0 0 0 6px rgba(0,0,0,0.04); }
|
|
48
58
|
.acw-dot.connected { background: #22c55e; box-shadow: 0 0 0 6px rgba(34,197,94,0.18); }
|
|
49
59
|
.acw-dot.disconnected { background: #ef4444; box-shadow: 0 0 0 6px rgba(239,68,68,0.18); }
|
|
50
|
-
.acw-disconnect { margin-left: auto; background: #f8fafc; color: #0f172a; border: 1px solid rgba(0,0,0,0.08); border-radius: 6px; padding: 6px 8px; font-size: 12px; cursor: pointer; }
|
|
51
|
-
.acw-disconnect:hover { background: #eef2f7; }
|
|
52
60
|
.acw-customer-form { position: absolute; inset: 0; background: ${safeBg}; z-index: 10; display: flex; flex-direction: column; padding: 20px; }
|
|
53
61
|
.acw-customer-form h3 { margin: 0 0 12px; font-size: 16px; font-weight: 600; color: #0f172a; }
|
|
54
62
|
.acw-customer-form p { margin: 0 0 16px; font-size: 13px; color: #64748b; }
|
|
@@ -61,6 +69,9 @@
|
|
|
61
69
|
.acw-customer-form .submit-btn:hover { opacity: 0.95; }
|
|
62
70
|
.acw-customer-form .skip-btn { background: #f8fafc; color: #64748b; border: 1px solid rgba(0,0,0,0.08); }
|
|
63
71
|
.acw-customer-form .skip-btn:hover { background: #eef2f7; }
|
|
72
|
+
.acw-start-cta { flex: 1; display: flex; align-items: center; justify-content: center; padding: 24px; }
|
|
73
|
+
.acw-start-cta button { background: ${safePrimary}; color: #fff; border: none; border-radius: 10px; padding: 14px 24px; font-size: 15px; font-weight: 600; cursor: pointer; box-shadow: 0 4px 12px rgba(0,0,0,0.12); }
|
|
74
|
+
.acw-start-cta button:hover { opacity: 0.95; }
|
|
64
75
|
@media (max-width: 480px) {
|
|
65
76
|
.acw-panel { width: calc(100vw - 24px); height: 70vh; }
|
|
66
77
|
}
|
|
@@ -80,9 +91,29 @@
|
|
|
80
91
|
title = 'Chat with us',
|
|
81
92
|
greeting = null,
|
|
82
93
|
captureCustomerInfo = false,
|
|
94
|
+
allowSkipCustomerInfo = true,
|
|
83
95
|
position = 'bottom-right',
|
|
96
|
+
iconUrl = null,
|
|
97
|
+
showIcon = true,
|
|
98
|
+
launcherText = 'Chat',
|
|
84
99
|
} = config
|
|
85
100
|
|
|
101
|
+
// Resolve icon: explicit iconUrl, then page favicon, then /favicon.ico (only used when showIcon is true)
|
|
102
|
+
const resolvedIconUrl = (() => {
|
|
103
|
+
if (!showIcon) return null
|
|
104
|
+
if (iconUrl && typeof iconUrl === 'string' && iconUrl.trim()) return iconUrl.trim()
|
|
105
|
+
try {
|
|
106
|
+
const link = document.querySelector('link[rel="icon"]') || document.querySelector('link[rel="shortcut icon"]')
|
|
107
|
+
if (link && link.href) return link.href
|
|
108
|
+
} catch (_) {}
|
|
109
|
+
try {
|
|
110
|
+
const origin = window.location.origin || ''
|
|
111
|
+
if (origin) return origin + '/favicon.ico'
|
|
112
|
+
} catch (_) {}
|
|
113
|
+
return null
|
|
114
|
+
})()
|
|
115
|
+
const launcherLabel = (typeof launcherText === 'string' && launcherText.trim()) ? launcherText.trim() : 'Chat'
|
|
116
|
+
|
|
86
117
|
// Valid positions
|
|
87
118
|
const validPositions = ['bottom-right', 'middle-right', 'top-right', 'bottom-left', 'middle-left', 'top-left']
|
|
88
119
|
const finalPosition = validPositions.includes(position) ? position : 'bottom-right'
|
|
@@ -145,13 +176,42 @@
|
|
|
145
176
|
|
|
146
177
|
const launcher = document.createElement('button')
|
|
147
178
|
launcher.className = isVertical ? 'acw-launcher vertical' : 'acw-launcher'
|
|
148
|
-
|
|
179
|
+
if (resolvedIconUrl) {
|
|
180
|
+
const launcherImg = document.createElement('img')
|
|
181
|
+
launcherImg.src = resolvedIconUrl
|
|
182
|
+
launcherImg.alt = launcherLabel
|
|
183
|
+
launcher.appendChild(launcherImg)
|
|
184
|
+
const launcherTextSpan = document.createElement('span')
|
|
185
|
+
launcherTextSpan.textContent = launcherLabel
|
|
186
|
+
launcher.appendChild(launcherTextSpan)
|
|
187
|
+
} else {
|
|
188
|
+
const span = document.createElement('span')
|
|
189
|
+
span.textContent = launcherLabel
|
|
190
|
+
launcher.appendChild(span)
|
|
191
|
+
}
|
|
149
192
|
|
|
150
193
|
const header = document.createElement('div')
|
|
151
194
|
header.className = 'acw-header'
|
|
195
|
+
const headerLeft = document.createElement('div')
|
|
196
|
+
headerLeft.className = 'acw-header-left'
|
|
197
|
+
if (resolvedIconUrl) {
|
|
198
|
+
const headerIcon = document.createElement('img')
|
|
199
|
+
headerIcon.className = 'acw-header-icon'
|
|
200
|
+
headerIcon.src = resolvedIconUrl
|
|
201
|
+
headerIcon.alt = ''
|
|
202
|
+
headerLeft.appendChild(headerIcon)
|
|
203
|
+
}
|
|
204
|
+
const titleWrap = document.createElement('div')
|
|
205
|
+
titleWrap.className = 'acw-header-title-wrap'
|
|
152
206
|
const titleEl = document.createElement('div')
|
|
153
207
|
titleEl.className = 'acw-title'
|
|
154
208
|
titleEl.textContent = title
|
|
209
|
+
const unreadBadge = document.createElement('span')
|
|
210
|
+
unreadBadge.className = 'acw-unread-badge hidden'
|
|
211
|
+
unreadBadge.textContent = '0'
|
|
212
|
+
titleWrap.appendChild(titleEl)
|
|
213
|
+
titleWrap.appendChild(unreadBadge)
|
|
214
|
+
headerLeft.appendChild(titleWrap)
|
|
155
215
|
const headerActions = document.createElement('div')
|
|
156
216
|
headerActions.style.display = 'flex'
|
|
157
217
|
headerActions.style.alignItems = 'center'
|
|
@@ -166,7 +226,7 @@
|
|
|
166
226
|
|
|
167
227
|
headerActions.appendChild(minimizeBtn)
|
|
168
228
|
headerActions.appendChild(closeBtn)
|
|
169
|
-
header.appendChild(
|
|
229
|
+
header.appendChild(headerLeft)
|
|
170
230
|
header.appendChild(headerActions)
|
|
171
231
|
|
|
172
232
|
const statusEl = document.createElement('div')
|
|
@@ -175,15 +235,17 @@
|
|
|
175
235
|
statusDot.className = 'acw-dot disconnected'
|
|
176
236
|
const statusText = document.createElement('span')
|
|
177
237
|
statusText.textContent = 'Disconnected'
|
|
178
|
-
const disconnectBtn = document.createElement('button')
|
|
179
|
-
disconnectBtn.className = 'acw-disconnect'
|
|
180
|
-
disconnectBtn.textContent = 'Connect'
|
|
181
238
|
statusEl.appendChild(statusDot)
|
|
182
239
|
statusEl.appendChild(statusText)
|
|
183
|
-
|
|
240
|
+
|
|
241
|
+
const startCtaEl = document.createElement('div')
|
|
242
|
+
startCtaEl.className = 'acw-start-cta'
|
|
243
|
+
const startConversationBtn = document.createElement('button')
|
|
244
|
+
startConversationBtn.textContent = 'Click to Start Conversation'
|
|
245
|
+
startCtaEl.appendChild(startConversationBtn)
|
|
184
246
|
|
|
185
247
|
const messages = document.createElement('div')
|
|
186
|
-
|
|
248
|
+
messages.className = 'acw-messages'
|
|
187
249
|
|
|
188
250
|
const inputWrap = document.createElement('div')
|
|
189
251
|
inputWrap.className = 'acw-input'
|
|
@@ -195,10 +257,27 @@
|
|
|
195
257
|
inputWrap.appendChild(input)
|
|
196
258
|
inputWrap.appendChild(sendBtn)
|
|
197
259
|
|
|
260
|
+
const footerEl = document.createElement('div')
|
|
261
|
+
footerEl.className = 'acw-footer'
|
|
262
|
+
footerEl.style.display = 'none'
|
|
263
|
+
const footerLink = document.createElement('a')
|
|
264
|
+
footerLink.href = 'https://helllo.ai'
|
|
265
|
+
footerLink.target = '_blank'
|
|
266
|
+
footerLink.rel = 'noopener noreferrer'
|
|
267
|
+
footerLink.textContent = 'helllo.ai'
|
|
268
|
+
footerEl.appendChild(document.createTextNode('Powered by '))
|
|
269
|
+
footerEl.appendChild(footerLink)
|
|
270
|
+
|
|
198
271
|
// Customer info form (if enabled)
|
|
199
272
|
let customerForm = null
|
|
200
273
|
let sessionId = null
|
|
201
274
|
let customerInfoSubmitted = false
|
|
275
|
+
let firstMessageReceived = false
|
|
276
|
+
let conversationStarted = false
|
|
277
|
+
let formShownBeforeConnect = false
|
|
278
|
+
let pendingCustomerName = null
|
|
279
|
+
let pendingCustomerPhone = null
|
|
280
|
+
let unreadCount = 0
|
|
202
281
|
|
|
203
282
|
if (captureCustomerInfo) {
|
|
204
283
|
customerForm = document.createElement('div')
|
|
@@ -243,7 +322,9 @@
|
|
|
243
322
|
skipBtn.className = 'skip-btn'
|
|
244
323
|
skipBtn.textContent = 'Skip'
|
|
245
324
|
formActions.appendChild(submitBtn)
|
|
246
|
-
|
|
325
|
+
if (allowSkipCustomerInfo) {
|
|
326
|
+
formActions.appendChild(skipBtn)
|
|
327
|
+
}
|
|
247
328
|
|
|
248
329
|
customerForm.appendChild(formTitle)
|
|
249
330
|
customerForm.appendChild(formDesc)
|
|
@@ -251,7 +332,7 @@
|
|
|
251
332
|
customerForm.appendChild(phoneField)
|
|
252
333
|
customerForm.appendChild(formActions)
|
|
253
334
|
|
|
254
|
-
|
|
335
|
+
function submitCustomerInfo() {
|
|
255
336
|
const name = nameInput.value.trim()
|
|
256
337
|
const phone = phoneInput.value.trim()
|
|
257
338
|
|
|
@@ -260,51 +341,54 @@
|
|
|
260
341
|
return
|
|
261
342
|
}
|
|
262
343
|
|
|
344
|
+
// Form shown before connect: save and connect; customer_info sent in connection_established
|
|
345
|
+
if (formShownBeforeConnect) {
|
|
346
|
+
pendingCustomerName = name
|
|
347
|
+
pendingCustomerPhone = phone
|
|
348
|
+
customerForm.style.display = 'none'
|
|
349
|
+
messages.style.display = 'flex'
|
|
350
|
+
inputWrap.style.display = 'none'
|
|
351
|
+
connect()
|
|
352
|
+
return
|
|
353
|
+
}
|
|
354
|
+
|
|
263
355
|
if (!sessionId) {
|
|
264
356
|
console.error('[AgentChatWidget] No session ID available')
|
|
357
|
+
alert('Session not established. Please wait for connection.')
|
|
265
358
|
return
|
|
266
359
|
}
|
|
267
360
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
if (apiBase.startsWith('ws://')) {
|
|
272
|
-
apiBase = apiBase.replace(/^ws/, 'http')
|
|
273
|
-
} else if (apiBase.startsWith('wss://')) {
|
|
274
|
-
apiBase = apiBase.replace(/^wss/, 'https')
|
|
361
|
+
if (!ws || ws.readyState !== WebSocket.OPEN) {
|
|
362
|
+
alert('WebSocket not connected. Please wait for connection.')
|
|
363
|
+
return
|
|
275
364
|
}
|
|
276
|
-
const endpoint = `${apiBase}/api/v1/agents/text-chat/customer-info`
|
|
277
365
|
|
|
278
366
|
try {
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
if (response.ok) {
|
|
292
|
-
customerInfoSubmitted = true
|
|
293
|
-
customerForm.style.display = 'none'
|
|
294
|
-
messages.style.display = 'flex'
|
|
295
|
-
inputWrap.style.display = 'flex'
|
|
296
|
-
} else {
|
|
297
|
-
const errorText = await response.text()
|
|
298
|
-
console.error('[AgentChatWidget] Failed to submit customer info:', errorText)
|
|
299
|
-
alert('Failed to submit information. Please try again.')
|
|
300
|
-
}
|
|
367
|
+
ws.send(JSON.stringify({
|
|
368
|
+
type: 'customer_info',
|
|
369
|
+
session_id: sessionId,
|
|
370
|
+
phone_number: phone,
|
|
371
|
+
customer_name: name,
|
|
372
|
+
embed_key: embedKey
|
|
373
|
+
}))
|
|
374
|
+
customerInfoSubmitted = true
|
|
375
|
+
customerForm.style.display = 'none'
|
|
376
|
+
messages.style.display = 'flex'
|
|
377
|
+
inputWrap.style.display = 'flex'
|
|
301
378
|
} catch (error) {
|
|
302
|
-
console.error('[AgentChatWidget] Error
|
|
379
|
+
console.error('[AgentChatWidget] Error sending customer info:', error)
|
|
303
380
|
alert('Error submitting information. Please try again.')
|
|
304
381
|
}
|
|
305
382
|
}
|
|
306
383
|
|
|
307
384
|
function skipCustomerInfo() {
|
|
385
|
+
if (formShownBeforeConnect) {
|
|
386
|
+
customerForm.style.display = 'none'
|
|
387
|
+
messages.style.display = 'flex'
|
|
388
|
+
inputWrap.style.display = 'none'
|
|
389
|
+
connect()
|
|
390
|
+
return
|
|
391
|
+
}
|
|
308
392
|
customerInfoSubmitted = true
|
|
309
393
|
customerForm.style.display = 'none'
|
|
310
394
|
messages.style.display = 'flex'
|
|
@@ -312,7 +396,7 @@
|
|
|
312
396
|
}
|
|
313
397
|
|
|
314
398
|
submitBtn.onclick = submitCustomerInfo
|
|
315
|
-
skipBtn.onclick = skipCustomerInfo
|
|
399
|
+
if (allowSkipCustomerInfo) skipBtn.onclick = skipCustomerInfo
|
|
316
400
|
|
|
317
401
|
// Allow Enter key to submit
|
|
318
402
|
nameInput.addEventListener('keydown', (e) => {
|
|
@@ -333,18 +417,59 @@
|
|
|
333
417
|
|
|
334
418
|
panel.appendChild(header)
|
|
335
419
|
panel.appendChild(statusEl)
|
|
420
|
+
panel.appendChild(startCtaEl)
|
|
336
421
|
panel.appendChild(messages)
|
|
337
422
|
panel.appendChild(inputWrap)
|
|
423
|
+
panel.appendChild(footerEl)
|
|
338
424
|
shadow.appendChild(panel)
|
|
339
425
|
shadow.appendChild(launcher)
|
|
340
426
|
document.body.appendChild(container)
|
|
341
427
|
|
|
428
|
+
// Initial state: show start CTA, hide messages and input until user starts conversation
|
|
429
|
+
messages.style.display = 'none'
|
|
430
|
+
inputWrap.style.display = 'none'
|
|
431
|
+
|
|
432
|
+
function showStartCta() {
|
|
433
|
+
startCtaEl.style.display = 'flex'
|
|
434
|
+
messages.style.display = 'none'
|
|
435
|
+
inputWrap.style.display = 'none'
|
|
436
|
+
footerEl.style.display = 'none'
|
|
437
|
+
if (customerForm) customerForm.style.display = 'none'
|
|
438
|
+
}
|
|
439
|
+
function showFormOnly() {
|
|
440
|
+
startCtaEl.style.display = 'none'
|
|
441
|
+
messages.style.display = 'none'
|
|
442
|
+
inputWrap.style.display = 'none'
|
|
443
|
+
footerEl.style.display = 'none'
|
|
444
|
+
if (customerForm) customerForm.style.display = 'flex'
|
|
445
|
+
}
|
|
446
|
+
function showChat() {
|
|
447
|
+
startCtaEl.style.display = 'none'
|
|
448
|
+
if (customerForm) customerForm.style.display = 'none'
|
|
449
|
+
messages.style.display = 'flex'
|
|
450
|
+
inputWrap.style.display = 'flex'
|
|
451
|
+
footerEl.style.display = 'block'
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
function updateUnreadBadge() {
|
|
455
|
+
unreadBadge.textContent = String(unreadCount)
|
|
456
|
+
if (unreadCount > 0) {
|
|
457
|
+
unreadBadge.classList.remove('hidden')
|
|
458
|
+
} else {
|
|
459
|
+
unreadBadge.classList.add('hidden')
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
342
463
|
function appendMessage(text, role) {
|
|
343
464
|
const msg = document.createElement('div')
|
|
344
465
|
msg.className = 'acw-msg ' + (role === 'user' ? 'acw-msg-user' : 'acw-msg-bot')
|
|
345
466
|
msg.textContent = text
|
|
346
467
|
messages.appendChild(msg)
|
|
347
468
|
messages.scrollTop = messages.scrollHeight
|
|
469
|
+
if (role === 'bot' && panel.style.display !== 'flex') {
|
|
470
|
+
unreadCount++
|
|
471
|
+
updateUnreadBadge()
|
|
472
|
+
}
|
|
348
473
|
}
|
|
349
474
|
|
|
350
475
|
// No default greeting; only show messages after connection
|
|
@@ -391,25 +516,34 @@
|
|
|
391
516
|
console.info('[AgentChatWidget] Resolved WS URL:', resolvedWsUrl)
|
|
392
517
|
} catch (_) {}
|
|
393
518
|
|
|
519
|
+
function setInputEnabled(enabled) {
|
|
520
|
+
input.disabled = !enabled
|
|
521
|
+
sendBtn.disabled = !enabled
|
|
522
|
+
input.placeholder = enabled ? 'Type a message...' : 'Connect to start chatting'
|
|
523
|
+
}
|
|
524
|
+
|
|
394
525
|
function updateStatus(text, isConnected) {
|
|
395
526
|
statusText.textContent = text
|
|
396
527
|
statusDot.classList.remove('connected', 'disconnected')
|
|
397
528
|
statusDot.classList.add(isConnected ? 'connected' : 'disconnected')
|
|
398
|
-
|
|
399
|
-
// Update button text and handler based on connection status
|
|
400
|
-
disconnectBtn.textContent = isConnected ? 'Disconnect' : 'Connect'
|
|
401
|
-
disconnectBtn.onclick = isConnected ? disconnect : connect
|
|
529
|
+
setInputEnabled(isConnected)
|
|
402
530
|
}
|
|
403
531
|
|
|
404
532
|
function connect() {
|
|
405
533
|
if (connected || connecting) return
|
|
406
534
|
connecting = true
|
|
535
|
+
firstMessageReceived = false
|
|
536
|
+
startCtaEl.style.display = 'none'
|
|
537
|
+
// Show chat UI immediately so the panel is not empty while connecting
|
|
538
|
+
showChat()
|
|
539
|
+
setInputEnabled(false)
|
|
407
540
|
const url = resolvedWsUrl
|
|
408
541
|
try {
|
|
409
542
|
ws = new WebSocket(url)
|
|
410
543
|
} catch (err) {
|
|
411
544
|
connecting = false
|
|
412
|
-
updateStatus('
|
|
545
|
+
updateStatus('Disconnected', false)
|
|
546
|
+
appendMessage('Failed to connect. Please try again or check your connection.', 'bot')
|
|
413
547
|
console.error('[AgentChatWidget] WS error', err)
|
|
414
548
|
return
|
|
415
549
|
}
|
|
@@ -418,49 +552,93 @@
|
|
|
418
552
|
connected = true
|
|
419
553
|
connecting = false
|
|
420
554
|
updateStatus('Connected', true)
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
sessionId = 'session_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9)
|
|
424
|
-
|
|
425
|
-
// Show customer info form if enabled and not yet submitted
|
|
426
|
-
if (captureCustomerInfo && customerForm && !customerInfoSubmitted) {
|
|
427
|
-
customerForm.style.display = 'flex'
|
|
428
|
-
messages.style.display = 'none'
|
|
429
|
-
inputWrap.style.display = 'none'
|
|
430
|
-
// Focus first input
|
|
431
|
-
const nameInput = customerForm.querySelector('#acw-customer-name')
|
|
432
|
-
if (nameInput) setTimeout(() => nameInput.focus(), 100)
|
|
433
|
-
} else {
|
|
434
|
-
messages.style.display = 'flex'
|
|
435
|
-
inputWrap.style.display = 'flex'
|
|
436
|
-
}
|
|
555
|
+
// If captureCustomerInfo, connection_established will show form or chat
|
|
556
|
+
if (!captureCustomerInfo) showChat()
|
|
437
557
|
}
|
|
438
558
|
|
|
439
559
|
ws.onmessage = (event) => {
|
|
440
560
|
try {
|
|
441
561
|
const data = JSON.parse(event.data)
|
|
562
|
+
|
|
563
|
+
// Handle connection_established message
|
|
564
|
+
if (data.type === 'connection_established') {
|
|
565
|
+
if (data.session_id) sessionId = data.session_id
|
|
566
|
+
|
|
567
|
+
// Form was shown before connect (Start Conversation → form → submit/skip): send customer_info if pending, then show chat
|
|
568
|
+
if (formShownBeforeConnect) {
|
|
569
|
+
if (pendingCustomerName && pendingCustomerPhone && ws && ws.readyState === WebSocket.OPEN) {
|
|
570
|
+
try {
|
|
571
|
+
ws.send(JSON.stringify({
|
|
572
|
+
type: 'customer_info',
|
|
573
|
+
session_id: sessionId,
|
|
574
|
+
phone_number: pendingCustomerPhone,
|
|
575
|
+
customer_name: pendingCustomerName,
|
|
576
|
+
embed_key: embedKey
|
|
577
|
+
}))
|
|
578
|
+
} catch (e) {
|
|
579
|
+
console.error('[AgentChatWidget] Error sending customer info:', e)
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
pendingCustomerName = null
|
|
583
|
+
pendingCustomerPhone = null
|
|
584
|
+
customerInfoSubmitted = true
|
|
585
|
+
if (data.welcome_message) appendMessage(data.welcome_message, 'bot')
|
|
586
|
+
showChat()
|
|
587
|
+
return
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
// Display welcome message if provided
|
|
591
|
+
if (data.welcome_message) {
|
|
592
|
+
appendMessage(data.welcome_message, 'bot')
|
|
593
|
+
if (!firstMessageReceived && captureCustomerInfo && customerForm && !customerInfoSubmitted) {
|
|
594
|
+
firstMessageReceived = true
|
|
595
|
+
setTimeout(() => {
|
|
596
|
+
showFormOnly()
|
|
597
|
+
const nameInput = customerForm.querySelector('#acw-customer-name')
|
|
598
|
+
if (nameInput) setTimeout(() => nameInput.focus(), 100)
|
|
599
|
+
}, 300)
|
|
600
|
+
}
|
|
601
|
+
} else {
|
|
602
|
+
if (!firstMessageReceived && captureCustomerInfo && customerForm && !customerInfoSubmitted) {
|
|
603
|
+
firstMessageReceived = true
|
|
604
|
+
setTimeout(() => {
|
|
605
|
+
showFormOnly()
|
|
606
|
+
const nameInput = customerForm.querySelector('#acw-customer-name')
|
|
607
|
+
if (nameInput) setTimeout(() => nameInput.focus(), 100)
|
|
608
|
+
}, 100)
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
if (captureCustomerInfo && !customerInfoSubmitted && customerForm) return
|
|
612
|
+
showChat()
|
|
613
|
+
return
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
// Handle regular chat messages
|
|
442
617
|
const text = data.text || data.content || data.message || ''
|
|
443
618
|
const role = data.role || 'assistant'
|
|
444
|
-
if (text)
|
|
619
|
+
if (text) {
|
|
620
|
+
appendMessage(text, role === 'user' ? 'user' : 'bot')
|
|
621
|
+
}
|
|
445
622
|
} catch (e) {
|
|
446
623
|
// Fallback to raw text
|
|
447
|
-
if (event.data)
|
|
624
|
+
if (event.data) {
|
|
625
|
+
appendMessage(String(event.data), 'bot')
|
|
626
|
+
}
|
|
448
627
|
}
|
|
449
628
|
}
|
|
450
629
|
|
|
451
630
|
ws.onerror = (e) => {
|
|
452
631
|
console.error('[AgentChatWidget] WS error', e)
|
|
453
|
-
updateStatus('
|
|
454
|
-
appendMessage('
|
|
632
|
+
updateStatus('Disconnected', false)
|
|
633
|
+
appendMessage('Failed to connect. Please try again or check your connection.', 'bot')
|
|
455
634
|
}
|
|
456
635
|
|
|
457
636
|
ws.onclose = (ev) => {
|
|
458
637
|
connected = false
|
|
459
638
|
connecting = false
|
|
460
|
-
const reason = ev && (ev.reason || ev.code)
|
|
461
639
|
updateStatus('Disconnected', false)
|
|
462
640
|
if (!ev.wasClean) {
|
|
463
|
-
appendMessage(
|
|
641
|
+
appendMessage('Failed to connect. Please try again or check your connection.', 'bot')
|
|
464
642
|
}
|
|
465
643
|
}
|
|
466
644
|
}
|
|
@@ -475,6 +653,11 @@
|
|
|
475
653
|
updateStatus('Disconnected', false)
|
|
476
654
|
sessionId = null
|
|
477
655
|
customerInfoSubmitted = false
|
|
656
|
+
firstMessageReceived = false
|
|
657
|
+
conversationStarted = false
|
|
658
|
+
formShownBeforeConnect = false
|
|
659
|
+
pendingCustomerName = null
|
|
660
|
+
pendingCustomerPhone = null
|
|
478
661
|
if (customerForm) {
|
|
479
662
|
customerForm.style.display = 'none'
|
|
480
663
|
const nameInput = customerForm.querySelector('#acw-customer-name')
|
|
@@ -482,6 +665,7 @@
|
|
|
482
665
|
if (nameInput) nameInput.value = ''
|
|
483
666
|
if (phoneInput) phoneInput.value = ''
|
|
484
667
|
}
|
|
668
|
+
showStartCta()
|
|
485
669
|
}
|
|
486
670
|
|
|
487
671
|
function sendMessage() {
|
|
@@ -494,8 +678,11 @@
|
|
|
494
678
|
}
|
|
495
679
|
|
|
496
680
|
if (!ws || ws.readyState !== WebSocket.OPEN) {
|
|
497
|
-
|
|
498
|
-
|
|
681
|
+
if (connecting) {
|
|
682
|
+
appendMessage('Connecting... Please wait.', 'bot')
|
|
683
|
+
} else {
|
|
684
|
+
appendMessage('Please click "Click to Start Conversation" to start chatting.', 'bot')
|
|
685
|
+
}
|
|
499
686
|
return
|
|
500
687
|
}
|
|
501
688
|
appendMessage(text, 'user')
|
|
@@ -503,6 +690,19 @@
|
|
|
503
690
|
input.value = ''
|
|
504
691
|
}
|
|
505
692
|
|
|
693
|
+
startConversationBtn.onclick = () => {
|
|
694
|
+
conversationStarted = true
|
|
695
|
+
startCtaEl.style.display = 'none'
|
|
696
|
+
if (captureCustomerInfo && customerForm) {
|
|
697
|
+
formShownBeforeConnect = true
|
|
698
|
+
showFormOnly()
|
|
699
|
+
const nameInput = customerForm.querySelector('#acw-customer-name')
|
|
700
|
+
if (nameInput) setTimeout(() => nameInput.focus(), 100)
|
|
701
|
+
} else {
|
|
702
|
+
connect()
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
|
|
506
706
|
launcher.onclick = () => {
|
|
507
707
|
const isOpen = panel.style.display === 'flex'
|
|
508
708
|
if (isOpen) {
|
|
@@ -510,7 +710,9 @@
|
|
|
510
710
|
disconnect()
|
|
511
711
|
} else {
|
|
512
712
|
panel.style.display = 'flex'
|
|
513
|
-
|
|
713
|
+
unreadCount = 0
|
|
714
|
+
updateUnreadBadge()
|
|
715
|
+
if (!connected && !connecting && !conversationStarted) showStartCta()
|
|
514
716
|
}
|
|
515
717
|
}
|
|
516
718
|
|
|
@@ -539,7 +741,7 @@
|
|
|
539
741
|
},
|
|
540
742
|
open() {
|
|
541
743
|
panel.style.display = 'flex'
|
|
542
|
-
|
|
744
|
+
// Connection only via explicit Connect button
|
|
543
745
|
},
|
|
544
746
|
close() {
|
|
545
747
|
panel.style.display = 'none'
|
|
@@ -555,9 +757,14 @@
|
|
|
555
757
|
const captureCustomerInfo = userConfig.captureCustomerInfo !== undefined
|
|
556
758
|
? userConfig.captureCustomerInfo
|
|
557
759
|
: ds.captureCustomerInfo === 'true' || ds.capture_customer_info === 'true'
|
|
760
|
+
const allowSkipCustomerInfo = userConfig.allowSkipCustomerInfo !== undefined
|
|
761
|
+
? userConfig.allowSkipCustomerInfo
|
|
762
|
+
: ds.allowSkipCustomerInfo !== 'false' && ds.allow_skip_customer_info !== 'false'
|
|
558
763
|
const position = userConfig.position || ds.position || ds.launcherPosition || 'bottom-right'
|
|
559
764
|
const validPositions = ['bottom-right', 'middle-right', 'top-right', 'bottom-left', 'middle-left', 'top-left']
|
|
560
765
|
const validPosition = validPositions.includes(position) ? position : 'bottom-right'
|
|
766
|
+
const showIcon = userConfig.showIcon !== undefined ? userConfig.showIcon : ds.showIcon !== 'false' && ds.show_icon !== 'false'
|
|
767
|
+
const launcherText = userConfig.launcherText ?? userConfig.launcher_text ?? ds.launcherText ?? ds.launcher_text ?? 'Chat'
|
|
561
768
|
return {
|
|
562
769
|
agentId: userConfig.agentId || ds.agentId,
|
|
563
770
|
embedKey: userConfig.embedKey || ds.embedKey || ds.embed_key,
|
|
@@ -570,7 +777,11 @@
|
|
|
570
777
|
title: userConfig.title || ds.title,
|
|
571
778
|
greeting: userConfig.greeting || ds.greeting,
|
|
572
779
|
captureCustomerInfo: captureCustomerInfo,
|
|
780
|
+
allowSkipCustomerInfo: allowSkipCustomerInfo,
|
|
573
781
|
position: validPosition,
|
|
782
|
+
iconUrl: userConfig.iconUrl || userConfig.icon_url || ds.iconUrl || ds.icon_url,
|
|
783
|
+
showIcon: showIcon,
|
|
784
|
+
launcherText: launcherText,
|
|
574
785
|
}
|
|
575
786
|
}
|
|
576
787
|
|