@helllo-ai/agent-chat-widget 0.1.21 → 0.1.23

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.
Files changed (2) hide show
  1. package/agent-chat.staging.js +233 -49
  2. package/package.json +1 -1
@@ -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
- launcher.innerHTML = '<span>Chat</span>'
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(titleEl)
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
- statusEl.appendChild(disconnectBtn)
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
- messages.className = 'acw-messages'
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
- formActions.appendChild(skipBtn)
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 // Reset when connecting
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('Connection error', false)
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
- // Session ID will be received from connection_established message
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
- // Extract session_id from connection message
426
- if (data.session_id) {
427
- sessionId = data.session_id
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
- customerForm.style.display = 'flex'
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
- customerForm.style.display = 'flex'
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
- return // Don't process as regular message
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('Connection error', false)
480
- appendMessage('Connection error. Check WS service and URL.', 'bot')
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(`Connection closed (${reason || 'unclean'})`, 'bot')
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
- connect()
525
- setTimeout(sendMessage, 200)
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
- connect()
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
- connect()
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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@helllo-ai/agent-chat-widget",
3
- "version": "0.1.21",
3
+ "version": "0.1.23",
4
4
  "description": "Bot Swarm Agent Chat Widget - Embeddable chat widget for AI agents",
5
5
  "main": "agent-chat.latest.js",
6
6
  "files": [