@ihoomanai/chat-widget 2.1.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs.js +307 -15
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.esm.js +307 -15
- package/dist/index.esm.js.map +1 -1
- package/dist/index.esm.min.js +1 -1
- package/dist/index.esm.min.js.map +1 -1
- package/dist/index.umd.js +307 -15
- package/dist/index.umd.js.map +1 -1
- package/dist/index.umd.min.js +1 -1
- package/dist/index.umd.min.js.map +1 -1
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/widget.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/types.ts +2 -0
- package/src/widget.ts +350 -14
package/src/widget.ts
CHANGED
|
@@ -87,6 +87,13 @@ let state: WidgetState = {
|
|
|
87
87
|
unreadCount: 0,
|
|
88
88
|
};
|
|
89
89
|
|
|
90
|
+
/**
|
|
91
|
+
* Current view state
|
|
92
|
+
*/
|
|
93
|
+
type WidgetView = 'chat' | 'ticket';
|
|
94
|
+
let currentView: WidgetView = 'chat';
|
|
95
|
+
let isLiveAgentMode = false;
|
|
96
|
+
|
|
90
97
|
/**
|
|
91
98
|
* DOM element references
|
|
92
99
|
*/
|
|
@@ -95,6 +102,8 @@ interface WidgetElements {
|
|
|
95
102
|
toggle?: HTMLButtonElement;
|
|
96
103
|
badge?: HTMLElement;
|
|
97
104
|
window?: HTMLElement;
|
|
105
|
+
chatView?: HTMLElement;
|
|
106
|
+
ticketView?: HTMLElement;
|
|
98
107
|
messages?: HTMLElement;
|
|
99
108
|
input?: HTMLTextAreaElement;
|
|
100
109
|
sendBtn?: HTMLButtonElement;
|
|
@@ -102,6 +111,12 @@ interface WidgetElements {
|
|
|
102
111
|
fileInput?: HTMLInputElement | null;
|
|
103
112
|
statusDot?: HTMLElement;
|
|
104
113
|
statusText?: HTMLElement;
|
|
114
|
+
statusBar?: HTMLElement;
|
|
115
|
+
ticketName?: HTMLInputElement;
|
|
116
|
+
ticketEmail?: HTMLInputElement;
|
|
117
|
+
ticketIssue?: HTMLTextAreaElement;
|
|
118
|
+
ticketSubmitBtn?: HTMLButtonElement;
|
|
119
|
+
ticketBackBtn?: HTMLButtonElement;
|
|
105
120
|
}
|
|
106
121
|
|
|
107
122
|
let elements: WidgetElements = {};
|
|
@@ -310,6 +325,33 @@ function generateStyles(): string {
|
|
|
310
325
|
.ihooman-escalation-btn.secondary { background: ${isDark ? 'rgba(255,255,255,0.1)' : 'rgba(0,0,0,0.05)'}; color: ${textColor}; border: 1px solid ${borderColor}; }
|
|
311
326
|
.ihooman-escalation-btn.secondary:hover { background: ${isDark ? 'rgba(255,255,255,0.15)' : 'rgba(0,0,0,0.08)'}; }
|
|
312
327
|
.ihooman-escalation-btn:disabled { opacity: 0.5; cursor: not-allowed; transform: none; }
|
|
328
|
+
.ihooman-status-bar { padding: 10px 16px; text-align: center; font-size: 13px; display: none; }
|
|
329
|
+
.ihooman-status-bar.show { display: block; }
|
|
330
|
+
.ihooman-status-bar.waiting { background: #fef3c7; color: #92400e; }
|
|
331
|
+
.ihooman-status-bar.connected { background: #dcfce7; color: #166534; }
|
|
332
|
+
.ihooman-chat-view { display: flex; flex-direction: column; flex: 1; overflow: hidden; }
|
|
333
|
+
.ihooman-chat-view.hidden { display: none; }
|
|
334
|
+
.ihooman-ticket-view { display: none; flex-direction: column; padding: 20px; gap: 16px; background: ${bgColor}; flex: 1; overflow-y: auto; }
|
|
335
|
+
.ihooman-ticket-view.show { display: flex; }
|
|
336
|
+
.ihooman-ticket-title { font-size: 18px; font-weight: 600; color: ${textColor}; margin: 0; display: flex; align-items: center; gap: 8px; }
|
|
337
|
+
.ihooman-ticket-subtitle { font-size: 13px; color: ${mutedColor}; margin: 0; }
|
|
338
|
+
.ihooman-ticket-input { padding: 12px 14px; border: 1px solid ${borderColor}; border-radius: 10px; font-size: 14px; font-family: inherit; background: ${inputBg}; color: ${textColor}; outline: none; transition: border-color 0.2s; }
|
|
339
|
+
.ihooman-ticket-input:focus { border-color: ${primaryColor}; }
|
|
340
|
+
.ihooman-ticket-input::placeholder { color: ${mutedColor}; }
|
|
341
|
+
.ihooman-ticket-textarea { min-height: 100px; resize: vertical; }
|
|
342
|
+
.ihooman-ticket-submit { padding: 14px; background: linear-gradient(135deg, ${gradientFrom}, ${gradientTo}); color: white; border: none; border-radius: 10px; font-size: 14px; font-weight: 500; cursor: pointer; transition: all 0.2s; }
|
|
343
|
+
.ihooman-ticket-submit:hover { transform: translateY(-1px); box-shadow: 0 4px 12px rgba(0, 174, 255, 0.3); }
|
|
344
|
+
.ihooman-ticket-submit:disabled { opacity: 0.5; cursor: not-allowed; transform: none; }
|
|
345
|
+
.ihooman-ticket-back { padding: 10px; background: transparent; color: ${mutedColor}; border: 1px solid ${borderColor}; border-radius: 10px; font-size: 13px; cursor: pointer; transition: all 0.2s; }
|
|
346
|
+
.ihooman-ticket-back:hover { background: ${isDark ? 'rgba(255,255,255,0.05)' : 'rgba(0,0,0,0.03)'}; }
|
|
347
|
+
.ihooman-action-buttons { display: flex; gap: 8px; padding: 0 16px 12px; }
|
|
348
|
+
.ihooman-action-btn { flex: 1; padding: 10px; border: none; border-radius: 8px; font-size: 13px; font-weight: 500; cursor: pointer; display: flex; align-items: center; justify-content: center; gap: 6px; transition: all 0.2s; }
|
|
349
|
+
.ihooman-action-btn svg { width: 16px; height: 16px; }
|
|
350
|
+
.ihooman-action-btn.ticket { background: #6366f1; color: white; }
|
|
351
|
+
.ihooman-action-btn.ticket:hover { background: #4f46e5; }
|
|
352
|
+
.ihooman-action-btn.live { background: #10b981; color: white; }
|
|
353
|
+
.ihooman-action-btn.live:hover { background: #059669; }
|
|
354
|
+
.ihooman-action-btn:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
313
355
|
@media (max-width: 480px) { .ihooman-window { width: calc(100vw - 20px); height: calc(100vh - 100px); left: 10px; right: 10px; bottom: 80px; max-height: none; } .ihooman-toggle { ${positionRight ? 'right: 16px' : 'left: 16px'}; bottom: 16px; } }
|
|
314
356
|
`;
|
|
315
357
|
}
|
|
@@ -352,14 +394,41 @@ function createWidget(): void {
|
|
|
352
394
|
<button class="ihooman-header-btn" data-action="minimize" title="Minimize">${icons.minimize}</button>
|
|
353
395
|
</div>
|
|
354
396
|
</div>
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
397
|
+
|
|
398
|
+
<!-- Chat View -->
|
|
399
|
+
<div class="ihooman-chat-view">
|
|
400
|
+
<div class="ihooman-status-bar"></div>
|
|
401
|
+
<div class="ihooman-messages" role="log" aria-live="polite"></div>
|
|
402
|
+
<div class="ihooman-action-buttons">
|
|
403
|
+
<button class="ihooman-action-btn ticket" data-action="show-ticket">
|
|
404
|
+
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-5 14H7v-2h7v2zm3-4H7v-2h10v2zm0-4H7V7h10v2z"/></svg>
|
|
405
|
+
Submit Ticket
|
|
406
|
+
</button>
|
|
407
|
+
<button class="ihooman-action-btn live" data-action="live-agent">
|
|
408
|
+
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"/></svg>
|
|
409
|
+
Live Agent
|
|
410
|
+
</button>
|
|
411
|
+
</div>
|
|
412
|
+
<div class="ihooman-input-area">
|
|
413
|
+
<div class="ihooman-input-wrapper">
|
|
414
|
+
${config.enableFileUpload ? `<button class="ihooman-input-btn attach" title="Attach file">${icons.attach}</button><input type="file" class="ihooman-file-input">` : ''}
|
|
415
|
+
<textarea class="ihooman-input" placeholder="${escapeHtml(config.placeholder || 'Type a message...')}" rows="1" aria-label="Message input"></textarea>
|
|
416
|
+
<button class="ihooman-input-btn send" title="Send message" disabled>${icons.send}</button>
|
|
417
|
+
</div>
|
|
361
418
|
</div>
|
|
362
419
|
</div>
|
|
420
|
+
|
|
421
|
+
<!-- Ticket Form View -->
|
|
422
|
+
<div class="ihooman-ticket-view">
|
|
423
|
+
<h4 class="ihooman-ticket-title">📝 Submit a Ticket</h4>
|
|
424
|
+
<p class="ihooman-ticket-subtitle">We'll get back to you via email</p>
|
|
425
|
+
<input class="ihooman-ticket-input" id="ihooman-ticket-name" placeholder="Your name" required>
|
|
426
|
+
<input class="ihooman-ticket-input" id="ihooman-ticket-email" type="email" placeholder="Your email" required>
|
|
427
|
+
<textarea class="ihooman-ticket-input ihooman-ticket-textarea" id="ihooman-ticket-issue" placeholder="Describe your issue..."></textarea>
|
|
428
|
+
<button class="ihooman-ticket-submit" id="ihooman-ticket-submit">Submit Ticket</button>
|
|
429
|
+
<button class="ihooman-ticket-back" id="ihooman-ticket-back">← Back to Chat</button>
|
|
430
|
+
</div>
|
|
431
|
+
|
|
363
432
|
${config.poweredBy ? `<div class="ihooman-powered">Powered by <a href="https://ihooman.ai" target="_blank" rel="noopener">Ihooman AI</a></div>` : ''}
|
|
364
433
|
</div>
|
|
365
434
|
</div>
|
|
@@ -373,6 +442,8 @@ function createWidget(): void {
|
|
|
373
442
|
toggle: widget.querySelector('.ihooman-toggle') as HTMLButtonElement,
|
|
374
443
|
badge: widget.querySelector('.ihooman-badge') as HTMLElement,
|
|
375
444
|
window: widget.querySelector('.ihooman-window') as HTMLElement,
|
|
445
|
+
chatView: widget.querySelector('.ihooman-chat-view') as HTMLElement,
|
|
446
|
+
ticketView: widget.querySelector('.ihooman-ticket-view') as HTMLElement,
|
|
376
447
|
messages: widget.querySelector('.ihooman-messages') as HTMLElement,
|
|
377
448
|
input: widget.querySelector('.ihooman-input') as HTMLTextAreaElement,
|
|
378
449
|
sendBtn: widget.querySelector('.ihooman-input-btn.send') as HTMLButtonElement,
|
|
@@ -380,6 +451,12 @@ function createWidget(): void {
|
|
|
380
451
|
fileInput: widget.querySelector('.ihooman-file-input') as HTMLInputElement | null,
|
|
381
452
|
statusDot: widget.querySelector('.ihooman-status-dot') as HTMLElement,
|
|
382
453
|
statusText: widget.querySelector('.ihooman-status-text') as HTMLElement,
|
|
454
|
+
statusBar: widget.querySelector('.ihooman-status-bar') as HTMLElement,
|
|
455
|
+
ticketName: widget.querySelector('#ihooman-ticket-name') as HTMLInputElement,
|
|
456
|
+
ticketEmail: widget.querySelector('#ihooman-ticket-email') as HTMLInputElement,
|
|
457
|
+
ticketIssue: widget.querySelector('#ihooman-ticket-issue') as HTMLTextAreaElement,
|
|
458
|
+
ticketSubmitBtn: widget.querySelector('#ihooman-ticket-submit') as HTMLButtonElement,
|
|
459
|
+
ticketBackBtn: widget.querySelector('#ihooman-ticket-back') as HTMLButtonElement,
|
|
383
460
|
};
|
|
384
461
|
|
|
385
462
|
// Set up event listeners
|
|
@@ -419,6 +496,18 @@ function setupEventListeners(): void {
|
|
|
419
496
|
|
|
420
497
|
elements.widget?.querySelector('[data-action="refresh"]')?.addEventListener('click', startNewConversation);
|
|
421
498
|
elements.widget?.querySelector('[data-action="minimize"]')?.addEventListener('click', close);
|
|
499
|
+
|
|
500
|
+
// Action buttons - Submit Ticket and Live Agent
|
|
501
|
+
elements.widget?.querySelector('[data-action="show-ticket"]')?.addEventListener('click', handleShowTicketForm);
|
|
502
|
+
elements.widget?.querySelector('[data-action="live-agent"]')?.addEventListener('click', handleRequestLiveAgent);
|
|
503
|
+
|
|
504
|
+
// Ticket form buttons
|
|
505
|
+
if (elements.ticketSubmitBtn) {
|
|
506
|
+
elements.ticketSubmitBtn.addEventListener('click', handleSubmitTicket);
|
|
507
|
+
}
|
|
508
|
+
if (elements.ticketBackBtn) {
|
|
509
|
+
elements.ticketBackBtn.addEventListener('click', () => showView('chat'));
|
|
510
|
+
}
|
|
422
511
|
}
|
|
423
512
|
|
|
424
513
|
|
|
@@ -511,15 +600,259 @@ function handleEscalationAction(action: 'live-agent' | 'create-ticket'): void {
|
|
|
511
600
|
buttons.forEach(btn => (btn as HTMLButtonElement).disabled = true);
|
|
512
601
|
|
|
513
602
|
if (action === 'live-agent') {
|
|
514
|
-
|
|
515
|
-
addMessage('Connect me with a live agent', 'user');
|
|
516
|
-
showTyping();
|
|
517
|
-
sendMessageToServer('Connect me with a live agent');
|
|
603
|
+
handleRequestLiveAgent();
|
|
518
604
|
} else if (action === 'create-ticket') {
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
605
|
+
handleShowTicketForm();
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
/**
|
|
610
|
+
* Switch between chat and ticket views
|
|
611
|
+
*/
|
|
612
|
+
function showView(view: 'chat' | 'ticket'): void {
|
|
613
|
+
currentView = view;
|
|
614
|
+
|
|
615
|
+
if (elements.chatView) {
|
|
616
|
+
elements.chatView.classList.toggle('hidden', view !== 'chat');
|
|
617
|
+
}
|
|
618
|
+
if (elements.ticketView) {
|
|
619
|
+
elements.ticketView.classList.toggle('show', view === 'ticket');
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
/**
|
|
624
|
+
* Show the ticket form
|
|
625
|
+
*/
|
|
626
|
+
function handleShowTicketForm(): void {
|
|
627
|
+
showView('ticket');
|
|
628
|
+
// Focus on name input
|
|
629
|
+
setTimeout(() => elements.ticketName?.focus(), 100);
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
/**
|
|
633
|
+
* Update the status bar display
|
|
634
|
+
*/
|
|
635
|
+
function updateStatusBar(status: 'waiting' | 'connected' | 'hidden', message?: string): void {
|
|
636
|
+
if (!elements.statusBar) return;
|
|
637
|
+
|
|
638
|
+
if (status === 'hidden') {
|
|
639
|
+
elements.statusBar.classList.remove('show');
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
elements.statusBar.classList.add('show');
|
|
644
|
+
elements.statusBar.classList.remove('waiting', 'connected');
|
|
645
|
+
elements.statusBar.classList.add(status);
|
|
646
|
+
|
|
647
|
+
if (message) {
|
|
648
|
+
elements.statusBar.textContent = message;
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
/**
|
|
653
|
+
* Submit a ticket via the API
|
|
654
|
+
*/
|
|
655
|
+
async function handleSubmitTicket(): Promise<void> {
|
|
656
|
+
const name = elements.ticketName?.value.trim();
|
|
657
|
+
const email = elements.ticketEmail?.value.trim();
|
|
658
|
+
const issue = elements.ticketIssue?.value.trim();
|
|
659
|
+
|
|
660
|
+
if (!name || !email) {
|
|
661
|
+
alert('Please fill in your name and email');
|
|
662
|
+
return;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
if (elements.ticketSubmitBtn) {
|
|
666
|
+
elements.ticketSubmitBtn.disabled = true;
|
|
667
|
+
elements.ticketSubmitBtn.textContent = 'Submitting...';
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
try {
|
|
671
|
+
const response = await fetch(`${config.serverUrl}/api/v1/public/submit-ticket`, {
|
|
672
|
+
method: 'POST',
|
|
673
|
+
headers: { 'Content-Type': 'application/json' },
|
|
674
|
+
body: JSON.stringify({
|
|
675
|
+
session_id: state.sessionId,
|
|
676
|
+
user_name: name,
|
|
677
|
+
user_email: email,
|
|
678
|
+
issue: issue || 'No description provided',
|
|
679
|
+
escalation_type: 'ticket',
|
|
680
|
+
widget_id: config.widgetId,
|
|
681
|
+
}),
|
|
682
|
+
});
|
|
683
|
+
|
|
684
|
+
const data = await response.json();
|
|
685
|
+
|
|
686
|
+
// Clear form
|
|
687
|
+
if (elements.ticketName) elements.ticketName.value = '';
|
|
688
|
+
if (elements.ticketEmail) elements.ticketEmail.value = '';
|
|
689
|
+
if (elements.ticketIssue) elements.ticketIssue.value = '';
|
|
690
|
+
|
|
691
|
+
// Switch back to chat view
|
|
692
|
+
showView('chat');
|
|
693
|
+
|
|
694
|
+
// Show success message
|
|
695
|
+
const ticketRef = data.ticket_id ? data.ticket_id.slice(0, 8) : 'submitted';
|
|
696
|
+
addMessage(`✅ Ticket submitted! We'll contact you at ${email}. Reference: #${ticketRef}`, 'bot', { is_system_message: true });
|
|
697
|
+
|
|
698
|
+
} catch (error) {
|
|
699
|
+
console.error('Error submitting ticket:', error);
|
|
700
|
+
addMessage('❌ Error submitting ticket. Please try again.', 'bot', { is_system_message: true });
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
if (elements.ticketSubmitBtn) {
|
|
704
|
+
elements.ticketSubmitBtn.disabled = false;
|
|
705
|
+
elements.ticketSubmitBtn.textContent = 'Submit Ticket';
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
/**
|
|
710
|
+
* Request a live agent via the API
|
|
711
|
+
*/
|
|
712
|
+
async function handleRequestLiveAgent(): Promise<void> {
|
|
713
|
+
if (!state.sessionId) {
|
|
714
|
+
addMessage('Please send a message first to start a conversation.', 'bot', { is_system_message: true });
|
|
715
|
+
return;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
// Disable live agent button
|
|
719
|
+
const liveBtn = elements.widget?.querySelector('[data-action="live-agent"]') as HTMLButtonElement;
|
|
720
|
+
if (liveBtn) {
|
|
721
|
+
liveBtn.disabled = true;
|
|
722
|
+
liveBtn.innerHTML = '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"/></svg> Connecting...';
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
try {
|
|
726
|
+
const response = await fetch(`${config.serverUrl}/api/v1/public/live-agent`, {
|
|
727
|
+
method: 'POST',
|
|
728
|
+
headers: { 'Content-Type': 'application/json' },
|
|
729
|
+
body: JSON.stringify({
|
|
730
|
+
session_id: state.sessionId,
|
|
731
|
+
visitor_id: state.visitorId,
|
|
732
|
+
widget_id: config.widgetId,
|
|
733
|
+
}),
|
|
734
|
+
});
|
|
735
|
+
|
|
736
|
+
const data = await response.json();
|
|
737
|
+
|
|
738
|
+
isLiveAgentMode = true;
|
|
739
|
+
|
|
740
|
+
// Show status bar
|
|
741
|
+
if (data.position_in_queue && data.position_in_queue > 1) {
|
|
742
|
+
updateStatusBar('waiting', `⏳ Waiting for agent (Position: #${data.position_in_queue})`);
|
|
743
|
+
} else {
|
|
744
|
+
updateStatusBar('connected', '🟢 Connected to live support');
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
// Hide action buttons when in live agent mode
|
|
748
|
+
const actionsDiv = elements.widget?.querySelector('.ihooman-action-buttons') as HTMLElement;
|
|
749
|
+
if (actionsDiv) {
|
|
750
|
+
actionsDiv.style.display = 'none';
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
// Start polling for agent messages
|
|
754
|
+
startLiveAgentPolling();
|
|
755
|
+
|
|
756
|
+
} catch (error) {
|
|
757
|
+
console.error('Error requesting live agent:', error);
|
|
758
|
+
addMessage('❌ Unable to connect to live support. Please try again.', 'bot', { is_system_message: true });
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
// Re-enable button
|
|
762
|
+
if (liveBtn) {
|
|
763
|
+
liveBtn.disabled = false;
|
|
764
|
+
liveBtn.innerHTML = '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"/></svg> Live Agent';
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
/**
|
|
769
|
+
* Live agent polling interval
|
|
770
|
+
*/
|
|
771
|
+
let liveAgentPollInterval: ReturnType<typeof setInterval> | null = null;
|
|
772
|
+
let lastMessageCount = 0;
|
|
773
|
+
|
|
774
|
+
/**
|
|
775
|
+
* Start polling for live agent messages
|
|
776
|
+
*/
|
|
777
|
+
function startLiveAgentPolling(): void {
|
|
778
|
+
if (liveAgentPollInterval) return;
|
|
779
|
+
|
|
780
|
+
liveAgentPollInterval = setInterval(async () => {
|
|
781
|
+
if (!state.sessionId || !isLiveAgentMode) {
|
|
782
|
+
stopLiveAgentPolling();
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
try {
|
|
787
|
+
const response = await fetch(`${config.serverUrl}/api/v1/public/transcript/${state.sessionId}`, {
|
|
788
|
+
headers: { 'Content-Type': 'application/json' },
|
|
789
|
+
});
|
|
790
|
+
|
|
791
|
+
if (!response.ok) return;
|
|
792
|
+
|
|
793
|
+
const data = await response.json();
|
|
794
|
+
const msgCount = data.messages?.length || 0;
|
|
795
|
+
|
|
796
|
+
// Check for new messages
|
|
797
|
+
if (msgCount > lastMessageCount) {
|
|
798
|
+
// Get only new messages
|
|
799
|
+
const newMessages = data.messages.slice(lastMessageCount);
|
|
800
|
+
newMessages.forEach((msg: { sender_type: string; content: string }) => {
|
|
801
|
+
// Only add agent messages (user messages are already shown)
|
|
802
|
+
if (msg.sender_type === 'agent') {
|
|
803
|
+
addMessage(msg.content, 'bot', { agent_name: 'Agent' });
|
|
804
|
+
} else if (msg.sender_type === 'ai' && msg.content.includes('connected to live support')) {
|
|
805
|
+
// System message about connection
|
|
806
|
+
addMessage(msg.content, 'bot', { is_system_message: true });
|
|
807
|
+
}
|
|
808
|
+
});
|
|
809
|
+
lastMessageCount = msgCount;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
// Check if conversation was closed
|
|
813
|
+
if (data.status === 'closed') {
|
|
814
|
+
isLiveAgentMode = false;
|
|
815
|
+
stopLiveAgentPolling();
|
|
816
|
+
updateStatusBar('hidden');
|
|
817
|
+
addMessage('This conversation has been closed. Thank you for contacting us!', 'bot', { is_system_message: true });
|
|
818
|
+
|
|
819
|
+
// Show action buttons again
|
|
820
|
+
const actionsDiv = elements.widget?.querySelector('.ihooman-action-buttons') as HTMLElement;
|
|
821
|
+
if (actionsDiv) {
|
|
822
|
+
actionsDiv.style.display = 'flex';
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
// Update queue position
|
|
827
|
+
try {
|
|
828
|
+
const escResponse = await fetch(`${config.serverUrl}/api/v1/public/escalation-status/${state.sessionId}`);
|
|
829
|
+
if (escResponse.ok) {
|
|
830
|
+
const escData = await escResponse.json();
|
|
831
|
+
if (escData.escalated) {
|
|
832
|
+
if (escData.ticket_status === 'in_progress') {
|
|
833
|
+
updateStatusBar('connected', '🟢 Connected to live agent');
|
|
834
|
+
} else if (escData.ticket_status === 'open' && escData.position_in_queue > 1) {
|
|
835
|
+
updateStatusBar('waiting', `⏳ Waiting for agent (Position: #${escData.position_in_queue})`);
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
} catch {
|
|
840
|
+
// Ignore escalation status errors
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
} catch (error) {
|
|
844
|
+
console.error('Error polling for messages:', error);
|
|
845
|
+
}
|
|
846
|
+
}, 3000);
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
/**
|
|
850
|
+
* Stop live agent polling
|
|
851
|
+
*/
|
|
852
|
+
function stopLiveAgentPolling(): void {
|
|
853
|
+
if (liveAgentPollInterval) {
|
|
854
|
+
clearInterval(liveAgentPollInterval);
|
|
855
|
+
liveAgentPollInterval = null;
|
|
523
856
|
}
|
|
524
857
|
}
|
|
525
858
|
|
|
@@ -917,6 +1250,9 @@ function destroy(): void {
|
|
|
917
1250
|
// Stop heartbeat
|
|
918
1251
|
stopHeartbeat();
|
|
919
1252
|
|
|
1253
|
+
// Stop live agent polling
|
|
1254
|
+
stopLiveAgentPolling();
|
|
1255
|
+
|
|
920
1256
|
// Close WebSocket
|
|
921
1257
|
if (ws) {
|
|
922
1258
|
ws.close();
|