@ihoomanai/chat-widget 2.2.0 → 2.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cdn/SRI_HASHES.md +8 -8
- package/cdn/health.json +2 -2
- package/cdn/latest/chat.js +367 -7
- package/cdn/latest/chat.js.map +1 -1
- package/cdn/latest/chat.min.js +1 -1
- package/cdn/latest/chat.min.js.map +1 -1
- package/cdn/manifest.json +9 -9
- package/cdn/v2/chat.js +367 -7
- package/cdn/v2/chat.js.map +1 -1
- package/cdn/v2/chat.min.js +1 -1
- package/cdn/v2/chat.min.js.map +1 -1
- package/cdn/{v2.0.2 → v2.2.1}/chat.js +367 -7
- package/cdn/v2.2.1/chat.js.map +1 -0
- package/cdn/v2.2.1/chat.min.js +2 -0
- package/cdn/v2.2.1/chat.min.js.map +1 -0
- package/dist/index.cjs.js +13 -37
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.esm.js +13 -37
- 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 +13 -37
- 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/widget.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/widget.ts +11 -39
- package/cdn/v2.0.2/chat.js.map +0 -1
- package/cdn/v2.0.2/chat.min.js +0 -2
- package/cdn/v2.0.2/chat.min.js.map +0 -1
package/cdn/SRI_HASHES.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# SRI Hashes for @ihooman/chat-widget
|
|
2
2
|
|
|
3
|
-
## Version: 2.
|
|
3
|
+
## Version: 2.2.1
|
|
4
4
|
|
|
5
|
-
Build Date: 2026-
|
|
5
|
+
Build Date: 2026-02-01T13:15:35.563Z
|
|
6
6
|
|
|
7
7
|
## Subresource Integrity (SRI) Hashes
|
|
8
8
|
|
|
@@ -12,16 +12,16 @@ Use these hashes to ensure the integrity of the widget script when loading from
|
|
|
12
12
|
|
|
13
13
|
| Version | URL | SRI Hash |
|
|
14
14
|
|---------|-----|----------|
|
|
15
|
-
| Major (v2) | `https://cdn.ihooman.ai/widget/v2/chat.min.js` | `sha384-
|
|
16
|
-
| Full (v2.
|
|
15
|
+
| Major (v2) | `https://cdn.ihooman.ai/widget/v2/chat.min.js` | `sha384-1TV7Gv8ewoNYzwRYWcYawDbU+vZ1q/cdsNPV1K8xrEf84hOpCzsgd76EhE5471rV` |
|
|
16
|
+
| Full (v2.2.1) | `https://cdn.ihooman.ai/widget/v2.2.1/chat.min.js` | `sha384-1TV7Gv8ewoNYzwRYWcYawDbU+vZ1q/cdsNPV1K8xrEf84hOpCzsgd76EhE5471rV` |
|
|
17
17
|
| Latest | `https://cdn.ihooman.ai/widget/latest/chat.min.js` | *(SRI not recommended for latest)* |
|
|
18
18
|
|
|
19
19
|
### Unminified Bundle (For Development/Debugging)
|
|
20
20
|
|
|
21
21
|
| Version | URL | SRI Hash |
|
|
22
22
|
|---------|-----|----------|
|
|
23
|
-
| Major (v2) | `https://cdn.ihooman.ai/widget/v2/chat.js` | `sha384-
|
|
24
|
-
| Full (v2.
|
|
23
|
+
| Major (v2) | `https://cdn.ihooman.ai/widget/v2/chat.js` | `sha384-ATTcz+Ml80Gdw0rEz9R+yB5mcTtya6wbjCeyqvmKDeN15oarj3+VE63tQ7GNh6Vr` |
|
|
24
|
+
| Full (v2.2.1) | `https://cdn.ihooman.ai/widget/v2.2.1/chat.js` | `sha384-ATTcz+Ml80Gdw0rEz9R+yB5mcTtya6wbjCeyqvmKDeN15oarj3+VE63tQ7GNh6Vr` |
|
|
25
25
|
| Latest | `https://cdn.ihooman.ai/widget/latest/chat.js` | *(SRI not recommended for latest)* |
|
|
26
26
|
|
|
27
27
|
## Usage Examples
|
|
@@ -29,8 +29,8 @@ Use these hashes to ensure the integrity of the widget script when loading from
|
|
|
29
29
|
### With SRI (Recommended for versioned URLs)
|
|
30
30
|
|
|
31
31
|
```html
|
|
32
|
-
<script src="https://cdn.ihooman.ai/widget/v2.
|
|
33
|
-
integrity="sha384-
|
|
32
|
+
<script src="https://cdn.ihooman.ai/widget/v2.2.1/chat.min.js"
|
|
33
|
+
integrity="sha384-1TV7Gv8ewoNYzwRYWcYawDbU+vZ1q/cdsNPV1K8xrEf84hOpCzsgd76EhE5471rV"
|
|
34
34
|
crossorigin="anonymous"></script>
|
|
35
35
|
<script>
|
|
36
36
|
IhoomanChat.init({
|
package/cdn/health.json
CHANGED
package/cdn/latest/chat.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @ihoomanai/chat-widget v2.
|
|
2
|
+
* @ihoomanai/chat-widget v2.2.1
|
|
3
3
|
* Universal chat support widget for any website - secure Widget ID based initialization
|
|
4
4
|
*
|
|
5
5
|
* @license MIT
|
|
@@ -69,6 +69,8 @@
|
|
|
69
69
|
minimize: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="5" y1="12" x2="19" y2="12"></line></svg>`,
|
|
70
70
|
refresh: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="23 4 23 10 17 10"></polyline><polyline points="1 20 1 14 7 14"></polyline><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"></path></svg>`,
|
|
71
71
|
bot: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="11" width="18" height="10" rx="2"></rect><circle cx="12" cy="5" r="2"></circle><path d="M12 7v4"></path></svg>`,
|
|
72
|
+
agent: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path><circle cx="12" cy="7" r="4"></circle></svg>`,
|
|
73
|
+
ticket: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line><line x1="10" y1="9" x2="8" y2="9"></line></svg>`,
|
|
72
74
|
};
|
|
73
75
|
/**
|
|
74
76
|
* Internal widget state
|
|
@@ -82,6 +84,7 @@
|
|
|
82
84
|
visitorId: null,
|
|
83
85
|
unreadCount: 0,
|
|
84
86
|
};
|
|
87
|
+
let isLiveAgentMode = false;
|
|
85
88
|
let elements = {};
|
|
86
89
|
const eventListeners = {};
|
|
87
90
|
/**
|
|
@@ -260,6 +263,41 @@
|
|
|
260
263
|
.ihooman-powered { text-align: center; padding: 8px; font-size: 11px; color: ${mutedColor}; background: ${bgColor}; }
|
|
261
264
|
.ihooman-powered a { color: ${primaryColor}; text-decoration: none; }
|
|
262
265
|
.ihooman-error { padding: 16px; text-align: center; color: #ef4444; background: ${bgColor}; }
|
|
266
|
+
.ihooman-escalation-actions { display: flex; gap: 8px; margin-top: 12px; flex-wrap: wrap; }
|
|
267
|
+
.ihooman-escalation-btn { display: inline-flex; align-items: center; gap: 6px; padding: 10px 16px; border-radius: 8px; border: none; cursor: pointer; font-family: inherit; font-size: 13px; font-weight: 500; transition: all 0.2s ease; }
|
|
268
|
+
.ihooman-escalation-btn svg { width: 16px; height: 16px; }
|
|
269
|
+
.ihooman-escalation-btn.primary { background: linear-gradient(135deg, ${gradientFrom}, ${gradientTo}); color: white; }
|
|
270
|
+
.ihooman-escalation-btn.primary:hover { transform: translateY(-1px); box-shadow: 0 4px 12px rgba(0, 174, 255, 0.3); }
|
|
271
|
+
.ihooman-escalation-btn.secondary { background: ${isDark ? 'rgba(255,255,255,0.1)' : 'rgba(0,0,0,0.05)'}; color: ${textColor}; border: 1px solid ${borderColor}; }
|
|
272
|
+
.ihooman-escalation-btn.secondary:hover { background: ${isDark ? 'rgba(255,255,255,0.15)' : 'rgba(0,0,0,0.08)'}; }
|
|
273
|
+
.ihooman-escalation-btn:disabled { opacity: 0.5; cursor: not-allowed; transform: none; }
|
|
274
|
+
.ihooman-status-bar { padding: 10px 16px; text-align: center; font-size: 13px; display: none; }
|
|
275
|
+
.ihooman-status-bar.show { display: block; }
|
|
276
|
+
.ihooman-status-bar.waiting { background: #fef3c7; color: #92400e; }
|
|
277
|
+
.ihooman-status-bar.connected { background: #dcfce7; color: #166534; }
|
|
278
|
+
.ihooman-chat-view { display: flex; flex-direction: column; flex: 1; overflow: hidden; }
|
|
279
|
+
.ihooman-chat-view.hidden { display: none; }
|
|
280
|
+
.ihooman-ticket-view { display: none; flex-direction: column; padding: 20px; gap: 16px; background: ${bgColor}; flex: 1; overflow-y: auto; }
|
|
281
|
+
.ihooman-ticket-view.show { display: flex; }
|
|
282
|
+
.ihooman-ticket-title { font-size: 18px; font-weight: 600; color: ${textColor}; margin: 0; display: flex; align-items: center; gap: 8px; }
|
|
283
|
+
.ihooman-ticket-subtitle { font-size: 13px; color: ${mutedColor}; margin: 0; }
|
|
284
|
+
.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; }
|
|
285
|
+
.ihooman-ticket-input:focus { border-color: ${primaryColor}; }
|
|
286
|
+
.ihooman-ticket-input::placeholder { color: ${mutedColor}; }
|
|
287
|
+
.ihooman-ticket-textarea { min-height: 100px; resize: vertical; }
|
|
288
|
+
.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; }
|
|
289
|
+
.ihooman-ticket-submit:hover { transform: translateY(-1px); box-shadow: 0 4px 12px rgba(0, 174, 255, 0.3); }
|
|
290
|
+
.ihooman-ticket-submit:disabled { opacity: 0.5; cursor: not-allowed; transform: none; }
|
|
291
|
+
.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; }
|
|
292
|
+
.ihooman-ticket-back:hover { background: ${isDark ? 'rgba(255,255,255,0.05)' : 'rgba(0,0,0,0.03)'}; }
|
|
293
|
+
.ihooman-action-buttons { display: flex; gap: 8px; padding: 0 16px 12px; }
|
|
294
|
+
.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; }
|
|
295
|
+
.ihooman-action-btn svg { width: 16px; height: 16px; }
|
|
296
|
+
.ihooman-action-btn.ticket { background: #6366f1; color: white; }
|
|
297
|
+
.ihooman-action-btn.ticket:hover { background: #4f46e5; }
|
|
298
|
+
.ihooman-action-btn.live { background: #10b981; color: white; }
|
|
299
|
+
.ihooman-action-btn.live:hover { background: #059669; }
|
|
300
|
+
.ihooman-action-btn:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
263
301
|
@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; } }
|
|
264
302
|
`;
|
|
265
303
|
}
|
|
@@ -298,14 +336,41 @@
|
|
|
298
336
|
<button class="ihooman-header-btn" data-action="minimize" title="Minimize">${icons.minimize}</button>
|
|
299
337
|
</div>
|
|
300
338
|
</div>
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
339
|
+
|
|
340
|
+
<!-- Chat View -->
|
|
341
|
+
<div class="ihooman-chat-view">
|
|
342
|
+
<div class="ihooman-status-bar"></div>
|
|
343
|
+
<div class="ihooman-messages" role="log" aria-live="polite"></div>
|
|
344
|
+
<div class="ihooman-action-buttons">
|
|
345
|
+
<button class="ihooman-action-btn ticket" data-action="show-ticket">
|
|
346
|
+
<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>
|
|
347
|
+
Submit Ticket
|
|
348
|
+
</button>
|
|
349
|
+
<button class="ihooman-action-btn live" data-action="live-agent">
|
|
350
|
+
<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>
|
|
351
|
+
Live Agent
|
|
352
|
+
</button>
|
|
307
353
|
</div>
|
|
354
|
+
<div class="ihooman-input-area">
|
|
355
|
+
<div class="ihooman-input-wrapper">
|
|
356
|
+
${config.enableFileUpload ? `<button class="ihooman-input-btn attach" title="Attach file">${icons.attach}</button><input type="file" class="ihooman-file-input">` : ''}
|
|
357
|
+
<textarea class="ihooman-input" placeholder="${escapeHtml(config.placeholder || 'Type a message...')}" rows="1" aria-label="Message input"></textarea>
|
|
358
|
+
<button class="ihooman-input-btn send" title="Send message" disabled>${icons.send}</button>
|
|
359
|
+
</div>
|
|
360
|
+
</div>
|
|
361
|
+
</div>
|
|
362
|
+
|
|
363
|
+
<!-- Ticket Form View -->
|
|
364
|
+
<div class="ihooman-ticket-view">
|
|
365
|
+
<h4 class="ihooman-ticket-title">📝 Submit a Ticket</h4>
|
|
366
|
+
<p class="ihooman-ticket-subtitle">We'll get back to you via email</p>
|
|
367
|
+
<input class="ihooman-ticket-input" id="ihooman-ticket-name" placeholder="Your name" required>
|
|
368
|
+
<input class="ihooman-ticket-input" id="ihooman-ticket-email" type="email" placeholder="Your email" required>
|
|
369
|
+
<textarea class="ihooman-ticket-input ihooman-ticket-textarea" id="ihooman-ticket-issue" placeholder="Describe your issue..."></textarea>
|
|
370
|
+
<button class="ihooman-ticket-submit" id="ihooman-ticket-submit">Submit Ticket</button>
|
|
371
|
+
<button class="ihooman-ticket-back" id="ihooman-ticket-back">← Back to Chat</button>
|
|
308
372
|
</div>
|
|
373
|
+
|
|
309
374
|
${config.poweredBy ? `<div class="ihooman-powered">Powered by <a href="https://ihooman.ai" target="_blank" rel="noopener">Ihooman AI</a></div>` : ''}
|
|
310
375
|
</div>
|
|
311
376
|
</div>
|
|
@@ -317,6 +382,8 @@
|
|
|
317
382
|
toggle: widget.querySelector('.ihooman-toggle'),
|
|
318
383
|
badge: widget.querySelector('.ihooman-badge'),
|
|
319
384
|
window: widget.querySelector('.ihooman-window'),
|
|
385
|
+
chatView: widget.querySelector('.ihooman-chat-view'),
|
|
386
|
+
ticketView: widget.querySelector('.ihooman-ticket-view'),
|
|
320
387
|
messages: widget.querySelector('.ihooman-messages'),
|
|
321
388
|
input: widget.querySelector('.ihooman-input'),
|
|
322
389
|
sendBtn: widget.querySelector('.ihooman-input-btn.send'),
|
|
@@ -324,6 +391,12 @@
|
|
|
324
391
|
fileInput: widget.querySelector('.ihooman-file-input'),
|
|
325
392
|
statusDot: widget.querySelector('.ihooman-status-dot'),
|
|
326
393
|
statusText: widget.querySelector('.ihooman-status-text'),
|
|
394
|
+
statusBar: widget.querySelector('.ihooman-status-bar'),
|
|
395
|
+
ticketName: widget.querySelector('#ihooman-ticket-name'),
|
|
396
|
+
ticketEmail: widget.querySelector('#ihooman-ticket-email'),
|
|
397
|
+
ticketIssue: widget.querySelector('#ihooman-ticket-issue'),
|
|
398
|
+
ticketSubmitBtn: widget.querySelector('#ihooman-ticket-submit'),
|
|
399
|
+
ticketBackBtn: widget.querySelector('#ihooman-ticket-back'),
|
|
327
400
|
};
|
|
328
401
|
// Set up event listeners
|
|
329
402
|
setupEventListeners();
|
|
@@ -358,6 +431,16 @@
|
|
|
358
431
|
}
|
|
359
432
|
elements.widget?.querySelector('[data-action="refresh"]')?.addEventListener('click', startNewConversation);
|
|
360
433
|
elements.widget?.querySelector('[data-action="minimize"]')?.addEventListener('click', close);
|
|
434
|
+
// Action buttons - Submit Ticket and Live Agent
|
|
435
|
+
elements.widget?.querySelector('[data-action="show-ticket"]')?.addEventListener('click', handleShowTicketForm);
|
|
436
|
+
elements.widget?.querySelector('[data-action="live-agent"]')?.addEventListener('click', handleRequestLiveAgent);
|
|
437
|
+
// Ticket form buttons
|
|
438
|
+
if (elements.ticketSubmitBtn) {
|
|
439
|
+
elements.ticketSubmitBtn.addEventListener('click', handleSubmitTicket);
|
|
440
|
+
}
|
|
441
|
+
if (elements.ticketBackBtn) {
|
|
442
|
+
elements.ticketBackBtn.addEventListener('click', () => showView('chat'));
|
|
443
|
+
}
|
|
361
444
|
}
|
|
362
445
|
// ============================================================================
|
|
363
446
|
// MESSAGING
|
|
@@ -378,10 +461,39 @@
|
|
|
378
461
|
return message;
|
|
379
462
|
const el = document.createElement('div');
|
|
380
463
|
el.className = `ihooman-message ${sender}`;
|
|
464
|
+
// Check if this message should show escalation buttons
|
|
465
|
+
const showEscalationButtons = sender === 'bot' && metadata?.escalation_offered === true;
|
|
466
|
+
let escalationButtonsHtml = '';
|
|
467
|
+
if (showEscalationButtons) {
|
|
468
|
+
escalationButtonsHtml = `
|
|
469
|
+
<div class="ihooman-escalation-actions">
|
|
470
|
+
<button class="ihooman-escalation-btn primary" data-action="live-agent">
|
|
471
|
+
${icons.agent}
|
|
472
|
+
<span>Talk to Agent</span>
|
|
473
|
+
</button>
|
|
474
|
+
<button class="ihooman-escalation-btn secondary" data-action="create-ticket">
|
|
475
|
+
${icons.ticket}
|
|
476
|
+
<span>Create Ticket</span>
|
|
477
|
+
</button>
|
|
478
|
+
</div>
|
|
479
|
+
`;
|
|
480
|
+
}
|
|
381
481
|
el.innerHTML = `
|
|
382
482
|
<div class="ihooman-message-content">${parseMarkdown(content)}</div>
|
|
483
|
+
${escalationButtonsHtml}
|
|
383
484
|
${config.showTimestamps ? `<div class="ihooman-message-time">${formatTime(message.timestamp)}</div>` : ''}
|
|
384
485
|
`;
|
|
486
|
+
// Add event listeners for escalation buttons
|
|
487
|
+
if (showEscalationButtons) {
|
|
488
|
+
const liveAgentBtn = el.querySelector('[data-action="live-agent"]');
|
|
489
|
+
const ticketBtn = el.querySelector('[data-action="create-ticket"]');
|
|
490
|
+
if (liveAgentBtn) {
|
|
491
|
+
liveAgentBtn.addEventListener('click', () => handleEscalationAction('live-agent'));
|
|
492
|
+
}
|
|
493
|
+
if (ticketBtn) {
|
|
494
|
+
ticketBtn.addEventListener('click', () => handleEscalationAction('create-ticket'));
|
|
495
|
+
}
|
|
496
|
+
}
|
|
385
497
|
// Remove typing indicator if present
|
|
386
498
|
const typing = elements.messages.querySelector('.ihooman-typing');
|
|
387
499
|
if (typing)
|
|
@@ -400,6 +512,250 @@
|
|
|
400
512
|
emit('message', message);
|
|
401
513
|
return message;
|
|
402
514
|
}
|
|
515
|
+
/**
|
|
516
|
+
* Handle escalation action button clicks
|
|
517
|
+
*/
|
|
518
|
+
function handleEscalationAction(action) {
|
|
519
|
+
// Disable all escalation buttons to prevent double-clicks
|
|
520
|
+
const buttons = document.querySelectorAll('.ihooman-escalation-btn');
|
|
521
|
+
buttons.forEach(btn => btn.disabled = true);
|
|
522
|
+
if (action === 'live-agent') {
|
|
523
|
+
handleRequestLiveAgent();
|
|
524
|
+
}
|
|
525
|
+
else if (action === 'create-ticket') {
|
|
526
|
+
handleShowTicketForm();
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
/**
|
|
530
|
+
* Switch between chat and ticket views
|
|
531
|
+
*/
|
|
532
|
+
function showView(view) {
|
|
533
|
+
if (elements.chatView) {
|
|
534
|
+
elements.chatView.classList.toggle('hidden', view !== 'chat');
|
|
535
|
+
}
|
|
536
|
+
if (elements.ticketView) {
|
|
537
|
+
elements.ticketView.classList.toggle('show', view === 'ticket');
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
/**
|
|
541
|
+
* Show the ticket form
|
|
542
|
+
*/
|
|
543
|
+
function handleShowTicketForm() {
|
|
544
|
+
showView('ticket');
|
|
545
|
+
// Focus on name input
|
|
546
|
+
setTimeout(() => elements.ticketName?.focus(), 100);
|
|
547
|
+
}
|
|
548
|
+
/**
|
|
549
|
+
* Update the status bar display
|
|
550
|
+
*/
|
|
551
|
+
function updateStatusBar(status, message) {
|
|
552
|
+
if (!elements.statusBar)
|
|
553
|
+
return;
|
|
554
|
+
if (status === 'hidden') {
|
|
555
|
+
elements.statusBar.classList.remove('show');
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
elements.statusBar.classList.add('show');
|
|
559
|
+
elements.statusBar.classList.remove('waiting', 'connected');
|
|
560
|
+
elements.statusBar.classList.add(status);
|
|
561
|
+
if (message) {
|
|
562
|
+
elements.statusBar.textContent = message;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
/**
|
|
566
|
+
* Submit a ticket via the API
|
|
567
|
+
*/
|
|
568
|
+
async function handleSubmitTicket() {
|
|
569
|
+
const name = elements.ticketName?.value.trim();
|
|
570
|
+
const email = elements.ticketEmail?.value.trim();
|
|
571
|
+
const issue = elements.ticketIssue?.value.trim();
|
|
572
|
+
if (!name || !email) {
|
|
573
|
+
alert('Please fill in your name and email');
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
576
|
+
if (elements.ticketSubmitBtn) {
|
|
577
|
+
elements.ticketSubmitBtn.disabled = true;
|
|
578
|
+
elements.ticketSubmitBtn.textContent = 'Submitting...';
|
|
579
|
+
}
|
|
580
|
+
try {
|
|
581
|
+
const response = await fetch(`${config.serverUrl}/api/v1/public/submit-ticket`, {
|
|
582
|
+
method: 'POST',
|
|
583
|
+
headers: { 'Content-Type': 'application/json' },
|
|
584
|
+
body: JSON.stringify({
|
|
585
|
+
session_id: state.sessionId,
|
|
586
|
+
user_name: name,
|
|
587
|
+
user_email: email,
|
|
588
|
+
issue: issue || 'No description provided',
|
|
589
|
+
escalation_type: 'ticket',
|
|
590
|
+
widget_id: config.widgetId,
|
|
591
|
+
}),
|
|
592
|
+
});
|
|
593
|
+
const data = await response.json();
|
|
594
|
+
// Clear form
|
|
595
|
+
if (elements.ticketName)
|
|
596
|
+
elements.ticketName.value = '';
|
|
597
|
+
if (elements.ticketEmail)
|
|
598
|
+
elements.ticketEmail.value = '';
|
|
599
|
+
if (elements.ticketIssue)
|
|
600
|
+
elements.ticketIssue.value = '';
|
|
601
|
+
// Switch back to chat view
|
|
602
|
+
showView('chat');
|
|
603
|
+
// Show success message
|
|
604
|
+
const ticketRef = data.ticket_id ? data.ticket_id.slice(0, 8) : 'submitted';
|
|
605
|
+
addMessage(`✅ Ticket submitted! We'll contact you at ${email}. Reference: #${ticketRef}`, 'bot', { is_system_message: true });
|
|
606
|
+
}
|
|
607
|
+
catch (error) {
|
|
608
|
+
console.error('Error submitting ticket:', error);
|
|
609
|
+
addMessage('❌ Error submitting ticket. Please try again.', 'bot', { is_system_message: true });
|
|
610
|
+
}
|
|
611
|
+
if (elements.ticketSubmitBtn) {
|
|
612
|
+
elements.ticketSubmitBtn.disabled = false;
|
|
613
|
+
elements.ticketSubmitBtn.textContent = 'Submit Ticket';
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
/**
|
|
617
|
+
* Request a live agent via the API
|
|
618
|
+
*/
|
|
619
|
+
async function handleRequestLiveAgent() {
|
|
620
|
+
if (!state.sessionId) {
|
|
621
|
+
addMessage('Please send a message first to start a conversation.', 'bot', { is_system_message: true });
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
624
|
+
// Disable live agent button
|
|
625
|
+
const liveBtn = elements.widget?.querySelector('[data-action="live-agent"]');
|
|
626
|
+
if (liveBtn) {
|
|
627
|
+
liveBtn.disabled = true;
|
|
628
|
+
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...';
|
|
629
|
+
}
|
|
630
|
+
try {
|
|
631
|
+
const response = await fetch(`${config.serverUrl}/api/v1/public/live-agent`, {
|
|
632
|
+
method: 'POST',
|
|
633
|
+
headers: { 'Content-Type': 'application/json' },
|
|
634
|
+
body: JSON.stringify({
|
|
635
|
+
session_id: state.sessionId,
|
|
636
|
+
visitor_id: state.visitorId,
|
|
637
|
+
widget_id: config.widgetId,
|
|
638
|
+
}),
|
|
639
|
+
});
|
|
640
|
+
const data = await response.json();
|
|
641
|
+
isLiveAgentMode = true;
|
|
642
|
+
// Show status bar - always start with waiting since agent hasn't accepted yet
|
|
643
|
+
if (data.position_in_queue && data.position_in_queue > 1) {
|
|
644
|
+
updateStatusBar('waiting', `⏳ Waiting for agent (Position: #${data.position_in_queue})`);
|
|
645
|
+
}
|
|
646
|
+
else {
|
|
647
|
+
updateStatusBar('waiting', '⏳ Connecting to live support...');
|
|
648
|
+
}
|
|
649
|
+
// Hide action buttons when in live agent mode
|
|
650
|
+
const actionsDiv = elements.widget?.querySelector('.ihooman-action-buttons');
|
|
651
|
+
if (actionsDiv) {
|
|
652
|
+
actionsDiv.style.display = 'none';
|
|
653
|
+
}
|
|
654
|
+
// Start polling for agent messages
|
|
655
|
+
startLiveAgentPolling();
|
|
656
|
+
}
|
|
657
|
+
catch (error) {
|
|
658
|
+
console.error('Error requesting live agent:', error);
|
|
659
|
+
addMessage('❌ Unable to connect to live support. Please try again.', 'bot', { is_system_message: true });
|
|
660
|
+
}
|
|
661
|
+
// Re-enable button
|
|
662
|
+
if (liveBtn) {
|
|
663
|
+
liveBtn.disabled = false;
|
|
664
|
+
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';
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
/**
|
|
668
|
+
* Live agent polling interval
|
|
669
|
+
*/
|
|
670
|
+
let liveAgentPollInterval = null;
|
|
671
|
+
let lastMessageCount = 0;
|
|
672
|
+
/**
|
|
673
|
+
* Start polling for live agent messages
|
|
674
|
+
*/
|
|
675
|
+
function startLiveAgentPolling() {
|
|
676
|
+
if (liveAgentPollInterval)
|
|
677
|
+
return;
|
|
678
|
+
liveAgentPollInterval = setInterval(async () => {
|
|
679
|
+
if (!state.sessionId || !isLiveAgentMode) {
|
|
680
|
+
stopLiveAgentPolling();
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
683
|
+
try {
|
|
684
|
+
const response = await fetch(`${config.serverUrl}/api/v1/public/transcript/${state.sessionId}`, {
|
|
685
|
+
headers: { 'Content-Type': 'application/json' },
|
|
686
|
+
});
|
|
687
|
+
if (!response.ok)
|
|
688
|
+
return;
|
|
689
|
+
const data = await response.json();
|
|
690
|
+
const msgCount = data.messages?.length || 0;
|
|
691
|
+
// Check for new messages
|
|
692
|
+
if (msgCount > lastMessageCount) {
|
|
693
|
+
// Get only new messages
|
|
694
|
+
const newMessages = data.messages.slice(lastMessageCount);
|
|
695
|
+
newMessages.forEach((msg) => {
|
|
696
|
+
// Only add agent messages (user messages are already shown)
|
|
697
|
+
if (msg.sender_type === 'agent') {
|
|
698
|
+
addMessage(msg.content, 'bot', { agent_name: 'Agent' });
|
|
699
|
+
}
|
|
700
|
+
else if (msg.sender_type === 'ai' && msg.content.includes('connected to live support')) {
|
|
701
|
+
// System message about connection
|
|
702
|
+
addMessage(msg.content, 'bot', { is_system_message: true });
|
|
703
|
+
}
|
|
704
|
+
});
|
|
705
|
+
lastMessageCount = msgCount;
|
|
706
|
+
}
|
|
707
|
+
// Check if conversation was closed
|
|
708
|
+
if (data.status === 'closed') {
|
|
709
|
+
isLiveAgentMode = false;
|
|
710
|
+
stopLiveAgentPolling();
|
|
711
|
+
updateStatusBar('hidden');
|
|
712
|
+
addMessage('This conversation has been closed. Thank you for contacting us!', 'bot', { is_system_message: true });
|
|
713
|
+
// Show action buttons again
|
|
714
|
+
const actionsDiv = elements.widget?.querySelector('.ihooman-action-buttons');
|
|
715
|
+
if (actionsDiv) {
|
|
716
|
+
actionsDiv.style.display = 'flex';
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
// Update queue position and connection status
|
|
720
|
+
try {
|
|
721
|
+
const escResponse = await fetch(`${config.serverUrl}/api/v1/public/escalation-status/${state.sessionId}`);
|
|
722
|
+
if (escResponse.ok) {
|
|
723
|
+
const escData = await escResponse.json();
|
|
724
|
+
if (escData.escalated) {
|
|
725
|
+
if (escData.ticket_status === 'in_progress') {
|
|
726
|
+
// Agent has accepted - show connected status
|
|
727
|
+
updateStatusBar('connected', '🟢 Connected to live agent');
|
|
728
|
+
}
|
|
729
|
+
else if (escData.ticket_status === 'open') {
|
|
730
|
+
// Still waiting in queue
|
|
731
|
+
if (escData.position_in_queue && escData.position_in_queue > 1) {
|
|
732
|
+
updateStatusBar('waiting', `⏳ Waiting for agent (Position: #${escData.position_in_queue})`);
|
|
733
|
+
}
|
|
734
|
+
else {
|
|
735
|
+
updateStatusBar('waiting', '⏳ Waiting for agent...');
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
catch {
|
|
742
|
+
// Ignore escalation status errors
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
catch (error) {
|
|
746
|
+
console.error('Error polling for messages:', error);
|
|
747
|
+
}
|
|
748
|
+
}, 3000);
|
|
749
|
+
}
|
|
750
|
+
/**
|
|
751
|
+
* Stop live agent polling
|
|
752
|
+
*/
|
|
753
|
+
function stopLiveAgentPolling() {
|
|
754
|
+
if (liveAgentPollInterval) {
|
|
755
|
+
clearInterval(liveAgentPollInterval);
|
|
756
|
+
liveAgentPollInterval = null;
|
|
757
|
+
}
|
|
758
|
+
}
|
|
403
759
|
/**
|
|
404
760
|
* Show typing indicator
|
|
405
761
|
*/
|
|
@@ -610,6 +966,8 @@
|
|
|
610
966
|
addMessage(data.content, sender, {
|
|
611
967
|
confidence: data.confidence,
|
|
612
968
|
agent_name: data.agent_name,
|
|
969
|
+
escalation_offered: data.escalation_offered,
|
|
970
|
+
escalated: data.escalated,
|
|
613
971
|
});
|
|
614
972
|
}
|
|
615
973
|
else if (data.type === 'typing') {
|
|
@@ -761,6 +1119,8 @@
|
|
|
761
1119
|
function destroy() {
|
|
762
1120
|
// Stop heartbeat
|
|
763
1121
|
stopHeartbeat();
|
|
1122
|
+
// Stop live agent polling
|
|
1123
|
+
stopLiveAgentPolling();
|
|
764
1124
|
// Close WebSocket
|
|
765
1125
|
if (ws) {
|
|
766
1126
|
ws.close();
|