@helllo-ai/agent-chat-widget 0.1.18 → 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 +233 -49
- 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,11 +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
|
|
202
275
|
let firstMessageReceived = false
|
|
276
|
+
let conversationStarted = false
|
|
277
|
+
let formShownBeforeConnect = false
|
|
278
|
+
let pendingCustomerName = null
|
|
279
|
+
let pendingCustomerPhone = null
|
|
280
|
+
let unreadCount = 0
|
|
203
281
|
|
|
204
282
|
if (captureCustomerInfo) {
|
|
205
283
|
customerForm = document.createElement('div')
|
|
@@ -244,7 +322,9 @@
|
|
|
244
322
|
skipBtn.className = 'skip-btn'
|
|
245
323
|
skipBtn.textContent = 'Skip'
|
|
246
324
|
formActions.appendChild(submitBtn)
|
|
247
|
-
|
|
325
|
+
if (allowSkipCustomerInfo) {
|
|
326
|
+
formActions.appendChild(skipBtn)
|
|
327
|
+
}
|
|
248
328
|
|
|
249
329
|
customerForm.appendChild(formTitle)
|
|
250
330
|
customerForm.appendChild(formDesc)
|
|
@@ -261,20 +341,29 @@
|
|
|
261
341
|
return
|
|
262
342
|
}
|
|
263
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
|
+
|
|
264
355
|
if (!sessionId) {
|
|
265
356
|
console.error('[AgentChatWidget] No session ID available')
|
|
266
357
|
alert('Session not established. Please wait for connection.')
|
|
267
358
|
return
|
|
268
359
|
}
|
|
269
360
|
|
|
270
|
-
// Ensure WebSocket is connected
|
|
271
361
|
if (!ws || ws.readyState !== WebSocket.OPEN) {
|
|
272
362
|
alert('WebSocket not connected. Please wait for connection.')
|
|
273
363
|
return
|
|
274
364
|
}
|
|
275
365
|
|
|
276
366
|
try {
|
|
277
|
-
// Send customer info via WebSocket
|
|
278
367
|
ws.send(JSON.stringify({
|
|
279
368
|
type: 'customer_info',
|
|
280
369
|
session_id: sessionId,
|
|
@@ -282,8 +371,6 @@
|
|
|
282
371
|
customer_name: name,
|
|
283
372
|
embed_key: embedKey
|
|
284
373
|
}))
|
|
285
|
-
|
|
286
|
-
// Mark as submitted and show chat interface
|
|
287
374
|
customerInfoSubmitted = true
|
|
288
375
|
customerForm.style.display = 'none'
|
|
289
376
|
messages.style.display = 'flex'
|
|
@@ -295,6 +382,13 @@
|
|
|
295
382
|
}
|
|
296
383
|
|
|
297
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
|
+
}
|
|
298
392
|
customerInfoSubmitted = true
|
|
299
393
|
customerForm.style.display = 'none'
|
|
300
394
|
messages.style.display = 'flex'
|
|
@@ -302,7 +396,7 @@
|
|
|
302
396
|
}
|
|
303
397
|
|
|
304
398
|
submitBtn.onclick = submitCustomerInfo
|
|
305
|
-
skipBtn.onclick = skipCustomerInfo
|
|
399
|
+
if (allowSkipCustomerInfo) skipBtn.onclick = skipCustomerInfo
|
|
306
400
|
|
|
307
401
|
// Allow Enter key to submit
|
|
308
402
|
nameInput.addEventListener('keydown', (e) => {
|
|
@@ -323,18 +417,59 @@
|
|
|
323
417
|
|
|
324
418
|
panel.appendChild(header)
|
|
325
419
|
panel.appendChild(statusEl)
|
|
420
|
+
panel.appendChild(startCtaEl)
|
|
326
421
|
panel.appendChild(messages)
|
|
327
422
|
panel.appendChild(inputWrap)
|
|
423
|
+
panel.appendChild(footerEl)
|
|
328
424
|
shadow.appendChild(panel)
|
|
329
425
|
shadow.appendChild(launcher)
|
|
330
426
|
document.body.appendChild(container)
|
|
331
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
|
+
|
|
332
463
|
function appendMessage(text, role) {
|
|
333
464
|
const msg = document.createElement('div')
|
|
334
465
|
msg.className = 'acw-msg ' + (role === 'user' ? 'acw-msg-user' : 'acw-msg-bot')
|
|
335
466
|
msg.textContent = text
|
|
336
467
|
messages.appendChild(msg)
|
|
337
468
|
messages.scrollTop = messages.scrollHeight
|
|
469
|
+
if (role === 'bot' && panel.style.display !== 'flex') {
|
|
470
|
+
unreadCount++
|
|
471
|
+
updateUnreadBadge()
|
|
472
|
+
}
|
|
338
473
|
}
|
|
339
474
|
|
|
340
475
|
// No default greeting; only show messages after connection
|
|
@@ -381,26 +516,34 @@
|
|
|
381
516
|
console.info('[AgentChatWidget] Resolved WS URL:', resolvedWsUrl)
|
|
382
517
|
} catch (_) {}
|
|
383
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
|
+
|
|
384
525
|
function updateStatus(text, isConnected) {
|
|
385
526
|
statusText.textContent = text
|
|
386
527
|
statusDot.classList.remove('connected', 'disconnected')
|
|
387
528
|
statusDot.classList.add(isConnected ? 'connected' : 'disconnected')
|
|
388
|
-
|
|
389
|
-
// Update button text and handler based on connection status
|
|
390
|
-
disconnectBtn.textContent = isConnected ? 'Disconnect' : 'Connect'
|
|
391
|
-
disconnectBtn.onclick = isConnected ? disconnect : connect
|
|
529
|
+
setInputEnabled(isConnected)
|
|
392
530
|
}
|
|
393
531
|
|
|
394
532
|
function connect() {
|
|
395
533
|
if (connected || connecting) return
|
|
396
534
|
connecting = true
|
|
397
|
-
firstMessageReceived = false
|
|
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)
|
|
398
540
|
const url = resolvedWsUrl
|
|
399
541
|
try {
|
|
400
542
|
ws = new WebSocket(url)
|
|
401
543
|
} catch (err) {
|
|
402
544
|
connecting = false
|
|
403
|
-
updateStatus('
|
|
545
|
+
updateStatus('Disconnected', false)
|
|
546
|
+
appendMessage('Failed to connect. Please try again or check your connection.', 'bot')
|
|
404
547
|
console.error('[AgentChatWidget] WS error', err)
|
|
405
548
|
return
|
|
406
549
|
}
|
|
@@ -409,11 +552,8 @@
|
|
|
409
552
|
connected = true
|
|
410
553
|
connecting = false
|
|
411
554
|
updateStatus('Connected', true)
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
// Don't show customer form yet - wait for first message
|
|
415
|
-
messages.style.display = 'flex'
|
|
416
|
-
inputWrap.style.display = 'flex'
|
|
555
|
+
// If captureCustomerInfo, connection_established will show form or chat
|
|
556
|
+
if (!captureCustomerInfo) showChat()
|
|
417
557
|
}
|
|
418
558
|
|
|
419
559
|
ws.onmessage = (event) => {
|
|
@@ -422,42 +562,55 @@
|
|
|
422
562
|
|
|
423
563
|
// Handle connection_established message
|
|
424
564
|
if (data.type === 'connection_established') {
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
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
|
|
428
588
|
}
|
|
429
589
|
|
|
430
590
|
// Display welcome message if provided
|
|
431
591
|
if (data.welcome_message) {
|
|
432
592
|
appendMessage(data.welcome_message, 'bot')
|
|
433
|
-
|
|
434
|
-
// Show customer info form after welcome message
|
|
435
593
|
if (!firstMessageReceived && captureCustomerInfo && customerForm && !customerInfoSubmitted) {
|
|
436
594
|
firstMessageReceived = true
|
|
437
|
-
// Small delay to let the message render first
|
|
438
595
|
setTimeout(() => {
|
|
439
|
-
|
|
440
|
-
messages.style.display = 'none'
|
|
441
|
-
inputWrap.style.display = 'none'
|
|
442
|
-
// Focus first input
|
|
596
|
+
showFormOnly()
|
|
443
597
|
const nameInput = customerForm.querySelector('#acw-customer-name')
|
|
444
598
|
if (nameInput) setTimeout(() => nameInput.focus(), 100)
|
|
445
599
|
}, 300)
|
|
446
600
|
}
|
|
447
601
|
} else {
|
|
448
|
-
// No welcome message, but still show form if needed
|
|
449
602
|
if (!firstMessageReceived && captureCustomerInfo && customerForm && !customerInfoSubmitted) {
|
|
450
603
|
firstMessageReceived = true
|
|
451
604
|
setTimeout(() => {
|
|
452
|
-
|
|
453
|
-
messages.style.display = 'none'
|
|
454
|
-
inputWrap.style.display = 'none'
|
|
605
|
+
showFormOnly()
|
|
455
606
|
const nameInput = customerForm.querySelector('#acw-customer-name')
|
|
456
607
|
if (nameInput) setTimeout(() => nameInput.focus(), 100)
|
|
457
608
|
}, 100)
|
|
458
609
|
}
|
|
459
610
|
}
|
|
460
|
-
|
|
611
|
+
if (captureCustomerInfo && !customerInfoSubmitted && customerForm) return
|
|
612
|
+
showChat()
|
|
613
|
+
return
|
|
461
614
|
}
|
|
462
615
|
|
|
463
616
|
// Handle regular chat messages
|
|
@@ -476,17 +629,16 @@
|
|
|
476
629
|
|
|
477
630
|
ws.onerror = (e) => {
|
|
478
631
|
console.error('[AgentChatWidget] WS error', e)
|
|
479
|
-
updateStatus('
|
|
480
|
-
appendMessage('
|
|
632
|
+
updateStatus('Disconnected', false)
|
|
633
|
+
appendMessage('Failed to connect. Please try again or check your connection.', 'bot')
|
|
481
634
|
}
|
|
482
635
|
|
|
483
636
|
ws.onclose = (ev) => {
|
|
484
637
|
connected = false
|
|
485
638
|
connecting = false
|
|
486
|
-
const reason = ev && (ev.reason || ev.code)
|
|
487
639
|
updateStatus('Disconnected', false)
|
|
488
640
|
if (!ev.wasClean) {
|
|
489
|
-
appendMessage(
|
|
641
|
+
appendMessage('Failed to connect. Please try again or check your connection.', 'bot')
|
|
490
642
|
}
|
|
491
643
|
}
|
|
492
644
|
}
|
|
@@ -502,6 +654,10 @@
|
|
|
502
654
|
sessionId = null
|
|
503
655
|
customerInfoSubmitted = false
|
|
504
656
|
firstMessageReceived = false
|
|
657
|
+
conversationStarted = false
|
|
658
|
+
formShownBeforeConnect = false
|
|
659
|
+
pendingCustomerName = null
|
|
660
|
+
pendingCustomerPhone = null
|
|
505
661
|
if (customerForm) {
|
|
506
662
|
customerForm.style.display = 'none'
|
|
507
663
|
const nameInput = customerForm.querySelector('#acw-customer-name')
|
|
@@ -509,6 +665,7 @@
|
|
|
509
665
|
if (nameInput) nameInput.value = ''
|
|
510
666
|
if (phoneInput) phoneInput.value = ''
|
|
511
667
|
}
|
|
668
|
+
showStartCta()
|
|
512
669
|
}
|
|
513
670
|
|
|
514
671
|
function sendMessage() {
|
|
@@ -521,8 +678,11 @@
|
|
|
521
678
|
}
|
|
522
679
|
|
|
523
680
|
if (!ws || ws.readyState !== WebSocket.OPEN) {
|
|
524
|
-
|
|
525
|
-
|
|
681
|
+
if (connecting) {
|
|
682
|
+
appendMessage('Connecting... Please wait.', 'bot')
|
|
683
|
+
} else {
|
|
684
|
+
appendMessage('Please click "Click to Start Conversation" to start chatting.', 'bot')
|
|
685
|
+
}
|
|
526
686
|
return
|
|
527
687
|
}
|
|
528
688
|
appendMessage(text, 'user')
|
|
@@ -530,6 +690,19 @@
|
|
|
530
690
|
input.value = ''
|
|
531
691
|
}
|
|
532
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
|
+
|
|
533
706
|
launcher.onclick = () => {
|
|
534
707
|
const isOpen = panel.style.display === 'flex'
|
|
535
708
|
if (isOpen) {
|
|
@@ -537,7 +710,9 @@
|
|
|
537
710
|
disconnect()
|
|
538
711
|
} else {
|
|
539
712
|
panel.style.display = 'flex'
|
|
540
|
-
|
|
713
|
+
unreadCount = 0
|
|
714
|
+
updateUnreadBadge()
|
|
715
|
+
if (!connected && !connecting && !conversationStarted) showStartCta()
|
|
541
716
|
}
|
|
542
717
|
}
|
|
543
718
|
|
|
@@ -566,7 +741,7 @@
|
|
|
566
741
|
},
|
|
567
742
|
open() {
|
|
568
743
|
panel.style.display = 'flex'
|
|
569
|
-
|
|
744
|
+
// Connection only via explicit Connect button
|
|
570
745
|
},
|
|
571
746
|
close() {
|
|
572
747
|
panel.style.display = 'none'
|
|
@@ -582,9 +757,14 @@
|
|
|
582
757
|
const captureCustomerInfo = userConfig.captureCustomerInfo !== undefined
|
|
583
758
|
? userConfig.captureCustomerInfo
|
|
584
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'
|
|
585
763
|
const position = userConfig.position || ds.position || ds.launcherPosition || 'bottom-right'
|
|
586
764
|
const validPositions = ['bottom-right', 'middle-right', 'top-right', 'bottom-left', 'middle-left', 'top-left']
|
|
587
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'
|
|
588
768
|
return {
|
|
589
769
|
agentId: userConfig.agentId || ds.agentId,
|
|
590
770
|
embedKey: userConfig.embedKey || ds.embedKey || ds.embed_key,
|
|
@@ -597,7 +777,11 @@
|
|
|
597
777
|
title: userConfig.title || ds.title,
|
|
598
778
|
greeting: userConfig.greeting || ds.greeting,
|
|
599
779
|
captureCustomerInfo: captureCustomerInfo,
|
|
780
|
+
allowSkipCustomerInfo: allowSkipCustomerInfo,
|
|
600
781
|
position: validPosition,
|
|
782
|
+
iconUrl: userConfig.iconUrl || userConfig.icon_url || ds.iconUrl || ds.icon_url,
|
|
783
|
+
showIcon: showIcon,
|
|
784
|
+
launcherText: launcherText,
|
|
601
785
|
}
|
|
602
786
|
}
|
|
603
787
|
|