| <!DOCTYPE html>
|
| <html lang="en">
|
|
|
| <head>
|
| <meta charset="UTF-8">
|
| <meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| <title>Martechsol Assistant Widget</title>
|
| <style>
|
|
|
| @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap');
|
|
|
| #martech-chat-wrapper {
|
| font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
|
| }
|
|
|
|
|
| #martech-chat-fab {
|
| position: fixed;
|
| right: 10px;
|
| bottom: 25px;
|
| width: auto;
|
| height: auto;
|
| background: transparent;
|
| border-radius: 50%;
|
| display: flex;
|
| align-items: center;
|
| justify-content: center;
|
| cursor: pointer;
|
| z-index: 999999;
|
| transition: transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1), background 0.3s ease, box-shadow 0.3s ease;
|
| -webkit-tap-highlight-color: transparent;
|
| }
|
|
|
| #martech-chat-fab:hover {
|
| transform: scale(1.1);
|
| }
|
|
|
| #martech-chat-fab.is-active {
|
| background: #1e293b;
|
| box-shadow: 0 10px 25px rgba(30, 41, 59, 0.4);
|
| transform: scale(0.8);
|
| }
|
|
|
|
|
| #martech-chat-box {
|
| position: fixed;
|
| right: 25px;
|
| bottom: 100px;
|
| width: 400px;
|
| height: 550px;
|
| background: #ffffff;
|
| border-radius: 16px;
|
| overflow: hidden;
|
| display: none;
|
| flex-direction: column;
|
| box-shadow: 0 12px 50px rgba(0, 0, 0, 0.2);
|
| z-index: 999998;
|
| border: 1px solid #e2e8f0;
|
| opacity: 0;
|
| transform: translateY(20px);
|
| transition: opacity 0.3s ease, transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
|
| }
|
|
|
| #martech-chat-box.is-visible {
|
| display: flex;
|
| opacity: 1;
|
| transform: translateY(0);
|
| }
|
|
|
| #martech-chat-header {
|
| background: #ffffff;
|
| padding: 16px 20px;
|
| border-bottom: 1px solid #f1f5f9;
|
| display: flex;
|
| align-items: center;
|
| gap: 12px;
|
| }
|
|
|
| @keyframes pulse-dot {
|
| 0% {
|
| transform: scale(1);
|
| box-shadow: 0 0 0 0 rgba(16, 185, 129, 0.7);
|
| }
|
|
|
| 70% {
|
| transform: scale(1.1);
|
| box-shadow: 0 0 0 10px rgba(16, 185, 129, 0);
|
| }
|
|
|
| 100% {
|
| transform: scale(1);
|
| box-shadow: 0 0 0 0 rgba(16, 185, 129, 0);
|
| }
|
| }
|
|
|
| #martech-chat-header .dot {
|
| width: 10px;
|
| height: 10px;
|
| background: #10b981;
|
| border-radius: 50%;
|
| animation: pulse-dot 2s infinite;
|
| transition: background 0.3s;
|
| }
|
|
|
| #martech-chat-header span {
|
| font-weight: 600;
|
| color: #1e293b;
|
| font-size: 15px;
|
| padding-left: 20px;
|
| }
|
|
|
| .header-actions {
|
| margin-left: auto;
|
| display: flex;
|
| gap: 8px;
|
| }
|
|
|
| .btn-end-chat {
|
| background: #fee2e2;
|
| color: #ef4444;
|
| border: none;
|
| padding: 4px 10px;
|
| border-radius: 6px;
|
| font-size: 12px;
|
| font-weight: 600;
|
| cursor: pointer;
|
| transition: background 0.2s;
|
| }
|
|
|
| .btn-end-chat:hover {
|
| background: #fecaca;
|
| }
|
|
|
| #rating-overlay {
|
| position: absolute;
|
| inset: 0;
|
| background: rgba(255, 255, 255, 0.95);
|
| display: flex;
|
| flex-direction: column;
|
| align-items: center;
|
| justify-content: center;
|
| z-index: 100;
|
| opacity: 0;
|
| visibility: hidden;
|
| transition: all 0.3s;
|
| }
|
|
|
| #rating-overlay.show {
|
| opacity: 1;
|
| visibility: visible;
|
| }
|
|
|
| .stars {
|
| display: flex;
|
| gap: 8px;
|
| font-size: 32px;
|
| color: #cbd5e1;
|
| cursor: pointer;
|
| margin: 16px 0;
|
| }
|
|
|
| .stars span {
|
| transition: color 0.2s;
|
| }
|
|
|
| .stars span:hover,
|
| .stars span.active {
|
| color: #f59e0b;
|
| }
|
|
|
|
|
| #chat-messages {
|
| flex: 1;
|
| overflow-y: auto;
|
| padding: 16px;
|
| display: flex;
|
| flex-direction: column;
|
| gap: 12px;
|
| background: #f8fafc;
|
| scroll-behavior: smooth;
|
| }
|
|
|
|
|
| #chat-messages::-webkit-scrollbar {
|
| width: 6px;
|
| }
|
|
|
| #chat-messages::-webkit-scrollbar-track {
|
| background: transparent;
|
| }
|
|
|
| #chat-messages::-webkit-scrollbar-thumb {
|
| background: #cbd5e1;
|
| border-radius: 10px;
|
| }
|
|
|
| .chat-message {
|
| max-width: 85%;
|
| padding: 12px 16px;
|
| border-radius: 16px;
|
| font-size: 14px;
|
| line-height: 1.5;
|
| word-wrap: break-word;
|
| white-space: pre-wrap;
|
| }
|
|
|
| .chat-message.user {
|
| align-self: flex-end;
|
| background: #1e293b;
|
| color: white;
|
| border-bottom-right-radius: 4px;
|
| }
|
|
|
| .chat-message.bot {
|
| align-self: flex-start;
|
| background: #ffffff;
|
| color: #334155;
|
| border-bottom-left-radius: 4px;
|
| border: 1px solid #e2e8f0;
|
| box-shadow: 0 2px 4px rgba(0, 0, 0, 0.02);
|
| }
|
|
|
|
|
| .chat-message b {
|
| font-weight: 800;
|
| color: #0f172a;
|
| }
|
|
|
| .chat-message.user b {
|
| color: #ffffff;
|
| }
|
|
|
| .queue-actions {
|
| display: flex;
|
| gap: 8px;
|
| padding: 8px 16px;
|
| background: #f8fafc;
|
| border-top: 1px solid #e2e8f0;
|
| justify-content: center;
|
| }
|
|
|
| .btn-queue {
|
| padding: 8px 12px;
|
| border-radius: 8px;
|
| font-size: 13px;
|
| font-weight: 600;
|
| cursor: pointer;
|
| border: 1px solid transparent;
|
| transition: all 0.2s;
|
| display: flex;
|
| align-items: center;
|
| gap: 6px;
|
| }
|
|
|
| .btn-remind {
|
| background: #fff;
|
| border-color: #f59e0b;
|
| color: #d97706;
|
| }
|
|
|
| .btn-remind:hover:not(:disabled) {
|
| background: #fef3c7;
|
| }
|
|
|
| .btn-remind:disabled {
|
| opacity: 0.5;
|
| cursor: not-allowed;
|
| border-color: #cbd5e1;
|
| color: #94a3b8;
|
| }
|
|
|
| .btn-exit-queue {
|
| background: #fff;
|
| border-color: #e2e8f0;
|
| color: #475569;
|
| }
|
|
|
| .btn-exit-queue:hover {
|
| background: #f1f5f9;
|
| }
|
|
|
| #chat-input-area {
|
| padding: 16px;
|
| background: #ffffff;
|
| border-top: 1px solid #f1f5f9;
|
| display: flex;
|
| gap: 8px;
|
| align-items: center;
|
| }
|
|
|
| #chat-input {
|
| flex: 1;
|
| border: 1px solid #e2e8f0;
|
| border-radius: 20px;
|
| padding: 12px 16px;
|
| font-size: 14px;
|
| outline: none;
|
| transition: border-color 0.2s;
|
| font-family: inherit;
|
| box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.02);
|
| }
|
|
|
| #chat-input:focus {
|
| border-color: #10b981;
|
| }
|
|
|
| #chat-input:disabled {
|
| background: #f1f5f9;
|
| cursor: not-allowed;
|
| }
|
|
|
| #chat-send {
|
| background: #10b981;
|
| color: white;
|
| border: none;
|
| width: 44px;
|
| height: 44px;
|
| border-radius: 50%;
|
| display: flex;
|
| align-items: center;
|
| justify-content: center;
|
| cursor: pointer;
|
| transition: background 0.2s, transform 0.1s;
|
| }
|
|
|
| #chat-send:hover {
|
| background: #059669;
|
| }
|
|
|
| #chat-send:active {
|
| transform: scale(0.95);
|
| }
|
|
|
| #chat-send:disabled {
|
| background: #cbd5e1;
|
| cursor: not-allowed;
|
| transform: none;
|
| }
|
|
|
|
|
| .typing-indicator {
|
| display: flex;
|
| gap: 4px;
|
| padding: 4px 2px;
|
| align-items: center;
|
| height: 18px;
|
| }
|
|
|
| .typing-indicator span {
|
| width: 6px;
|
| height: 6px;
|
| background: #94a3b8;
|
| border-radius: 50%;
|
| animation: typing-dots 1.4s infinite ease-in-out both;
|
| }
|
|
|
| .typing-indicator span:nth-child(1) {
|
| animation-delay: -0.32s;
|
| }
|
|
|
| .typing-indicator span:nth-child(2) {
|
| animation-delay: -0.16s;
|
| }
|
|
|
| @keyframes typing-dots {
|
|
|
| 0%,
|
| 80%,
|
| 100% {
|
| transform: scale(0);
|
| }
|
|
|
| 40% {
|
| transform: scale(1);
|
| }
|
| }
|
|
|
| @media (max-width: 480px) {
|
| #martech-chat-box {
|
| right: 15px;
|
| bottom: 100px;
|
| width: calc(100vw - 30px);
|
| height: 70vh;
|
| }
|
| }
|
| </style>
|
| </head>
|
|
|
| <body>
|
|
|
|
|
| <script src="https://unpkg.com/@dotlottie/player-component@latest/dist/dotlottie-player.mjs" type="module"></script>
|
|
|
| <div id="martech-chat-wrapper">
|
| <div id="martech-chat-fab">
|
|
|
| <div id="chat-icon"
|
| style="width: 233px; height: 238px; display: flex; align-items: center; justify-content: center; pointer-events: none;">
|
| <dotlottie-player src="https://joedown11-chatbot21.hf.space/static/bot-icon.lottie"
|
| background="transparent" speed="1" style="width: 100%; height: 100%; will-change: transform;" loop
|
| autoplay>
|
| </dotlottie-player>
|
| </div>
|
|
|
| <svg id="close-icon" viewBox="0 0 24 24" style="display:none; width: 60px; height: 60px;">
|
| <path
|
| d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"
|
| fill="white" />
|
| </svg>
|
| </div>
|
|
|
| <div id="martech-chat-box">
|
| <div id="martech-chat-header" style="display:flex; justify-content:space-between; align-items:center;">
|
| <div style="display:flex; align-items:center;">
|
| <div class="dot" id="chat-status-dot"></div>
|
| <span id="chat-header-title">Martechsol Assistant</span>
|
| </div>
|
| </div>
|
|
|
|
|
| <div id="chat-messages">
|
| <div class="chat-message bot">Welcome! How can I help you today?</div>
|
| </div>
|
|
|
| <div id="queue-actions-area" class="queue-actions" style="display: none;">
|
| <button id="btn-remind" class="btn-queue btn-remind" disabled>
|
| 🔔 <span id="remind-text">Wait 3:00</span>
|
| </button>
|
| <button id="btn-exit-queue" class="btn-queue btn-exit-queue">
|
| ✖ Exit Queue
|
| </button>
|
| </div>
|
|
|
| <div id="chat-input-area">
|
| <input type="text" id="chat-input" placeholder="Type your question here..." autocomplete="off" />
|
| <button id="chat-send" title="Send">
|
|
|
| <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
|
| stroke-linecap="round" stroke-linejoin="round">
|
| <line x1="22" y1="2" x2="11" y2="13"></line>
|
| <polygon points="22 2 15 22 11 13 2 9 22 2"></polygon>
|
| </svg>
|
| </button>
|
| </div>
|
|
|
| <div id="rating-overlay">
|
| <h3 style="color:#1e293b; margin-bottom:8px">Rate Your Experience</h3>
|
| <p style="color:#64748b; font-size:14px">How was your chat?</p>
|
| <div class="stars" id="rating-stars">
|
| <span data-val="1">★</span><span data-val="2">★</span><span data-val="3">★</span><span
|
| data-val="4">★</span><span data-val="5">★</span>
|
| </div>
|
| <button id="btn-submit-rating"
|
| style="background:#10b981; color:#fff; border:none; padding:10px 24px; border-radius:8px; font-weight:600; cursor:pointer; margin-top:8px">Submit
|
| & Close</button>
|
| </div>
|
| </div>
|
| </div>
|
|
|
| <script>
|
| (function () {
|
|
|
| const fab = document.getElementById('martech-chat-fab');
|
| const box = document.getElementById('martech-chat-box');
|
| const chatIcon = document.getElementById('chat-icon');
|
| const closeIcon = document.getElementById('close-icon');
|
| const messagesContainer = document.getElementById('chat-messages');
|
| const inputField = document.getElementById('chat-input');
|
| const sendButton = document.getElementById('chat-send');
|
|
|
|
|
| fab.addEventListener('click', function () {
|
| const isVisible = box.classList.contains('is-visible');
|
| if (isVisible) {
|
| box.classList.remove('is-visible');
|
| setTimeout(() => { box.style.display = 'none'; }, 300);
|
| chatIcon.style.display = 'block';
|
| closeIcon.style.display = 'none';
|
| fab.classList.remove('is-active');
|
| } else {
|
| box.style.display = 'flex';
|
| setTimeout(() => { box.classList.add('is-visible'); inputField.focus(); }, 10);
|
| chatIcon.style.display = 'none';
|
| closeIcon.style.display = 'block';
|
| fab.classList.add('is-active');
|
| }
|
| });
|
|
|
|
|
| const API_URL = "https://joedown11-chatbot21.hf.space/api/chat";
|
| const BASE_URL = API_URL.replace("/api/chat", "");
|
| let chatHistory = [];
|
| let _pollInterval = null;
|
| let _lastPollId = 0;
|
| let _wasBlocked = false;
|
| let _wasQueued = false;
|
| let _humanTypingEl = null;
|
|
|
|
|
| let _queueEnteredTime = 0;
|
| let _reminderUses = 3;
|
| let _lastReminderTime = 0;
|
| let _queueInterval = null;
|
|
|
| function updateUIState(status, blocked) {
|
| if (blocked) {
|
| inputField.disabled = true;
|
| inputField.placeholder = "Session blocked.";
|
| sendButton.disabled = true;
|
| if (!_wasBlocked) {
|
| appendMessage('bot', "Your session has been restricted by the administrator. You cannot send further messages.");
|
| _wasBlocked = true;
|
| }
|
| return;
|
| }
|
|
|
| if (status === 'QUEUED') {
|
| inputField.disabled = true;
|
| inputField.placeholder = "Waiting for human agent...";
|
| sendButton.disabled = true;
|
| if (!_wasQueued) {
|
| _wasQueued = true;
|
| _queueEnteredTime = Date.now();
|
| _reminderUses = 3;
|
| _lastReminderTime = 0;
|
| startQueueTimer();
|
| }
|
| document.getElementById('queue-actions-area').style.display = 'flex';
|
| } else if (status === 'ENDED') {
|
| inputField.disabled = true;
|
| inputField.placeholder = "Chat ended.";
|
| sendButton.disabled = true;
|
|
|
| document.getElementById('queue-actions-area').style.display = 'none';
|
| stopQueueTimer();
|
| _wasQueued = false;
|
|
|
| if (!document.getElementById('btn-new-chat')) {
|
| const newChatBtn = document.createElement('button');
|
| newChatBtn.id = 'btn-new-chat';
|
| newChatBtn.innerHTML = 'Start New Chat';
|
| newChatBtn.style.cssText = 'width:calc(100% - 32px); margin:0 16px 16px 16px; padding:12px; background:#10b981; color:white; border:none; border-radius:8px; font-weight:600; cursor:pointer; font-family:inherit;';
|
| newChatBtn.onclick = () => { ratingOverlay.classList.add('show'); };
|
| document.getElementById('martech-chat-box').appendChild(newChatBtn);
|
| }
|
| } else {
|
|
|
| inputField.disabled = false;
|
| inputField.placeholder = "Type your question here...";
|
| sendButton.disabled = false;
|
| _wasQueued = false;
|
|
|
| document.getElementById('queue-actions-area').style.display = 'none';
|
| stopQueueTimer();
|
|
|
|
|
| const btnNewChat = document.getElementById('btn-new-chat');
|
| if (btnNewChat) btnNewChat.remove();
|
|
|
| document.getElementById('chat-input-area').style.display = 'flex';
|
| }
|
| }
|
|
|
| function startQueueTimer() {
|
| if (_queueInterval) clearInterval(_queueInterval);
|
| _queueInterval = setInterval(checkQueueTimer, 1000);
|
| checkQueueTimer();
|
| }
|
|
|
| function stopQueueTimer() {
|
| if (_queueInterval) clearInterval(_queueInterval);
|
| }
|
|
|
| function checkQueueTimer() {
|
| const now = Date.now();
|
| const btnRemind = document.getElementById('btn-remind');
|
| const remindText = document.getElementById('remind-text');
|
|
|
| if (_reminderUses <= 0) {
|
| btnRemind.disabled = true;
|
| remindText.textContent = "No reminders left";
|
| return;
|
| }
|
|
|
|
|
| const timeSinceEntered = now - _queueEnteredTime;
|
| const timeSinceLastReminder = now - _lastReminderTime;
|
|
|
| if (timeSinceEntered >= 180000 && (_lastReminderTime === 0 || timeSinceLastReminder >= 180000)) {
|
| btnRemind.disabled = false;
|
| remindText.textContent = `Remind Admin (${_reminderUses} left)`;
|
| } else {
|
| btnRemind.disabled = true;
|
|
|
| const targetTime = _lastReminderTime > 0 ? _lastReminderTime + 180000 : _queueEnteredTime + 180000;
|
| const remainingSec = Math.ceil((targetTime - now) / 1000);
|
| const min = Math.floor(remainingSec / 60);
|
| const sec = remainingSec % 60;
|
| remindText.textContent = `Wait ${min}:${sec.toString().padStart(2, '0')}`;
|
| }
|
| }
|
|
|
|
|
| let sessionId = localStorage.getItem('martech_session_id');
|
| if (!sessionId) {
|
|
|
| sessionId = 'user-' + Date.now() + '-' + Math.random().toString(36).slice(2, 9);
|
| localStorage.setItem('martech_session_id', sessionId);
|
| }
|
|
|
| const _isGenericId = (id) => id && id.startsWith('user-') && /^user-\d+-[a-z0-9]+$/.test(id);
|
|
|
| function formatMessage(text) {
|
| if (!text) return "";
|
|
|
| let formatted = text.replace(/\*\*(.*?)\*\*/g, '<b>$1</b>');
|
|
|
| formatted = formatted.replace(/^\* /gm, '• ');
|
|
|
| return formatted.replace(/\n/g, '<br>');
|
| }
|
|
|
| function appendMessage(role, content) {
|
| const div = document.createElement('div');
|
| div.className = `chat-message ${role}`;
|
| div.innerHTML = formatMessage(content);
|
| messagesContainer.appendChild(div);
|
| messagesContainer.scrollTop = messagesContainer.scrollHeight;
|
| return div;
|
| }
|
|
|
|
|
| async function _resolveUserName() {
|
|
|
|
|
| if (window.HR_USER_NAME && typeof window.HR_USER_NAME === 'string' && window.HR_USER_NAME.trim().length > 1) {
|
| return window.HR_USER_NAME.trim();
|
| }
|
|
|
|
|
| try {
|
| const storageKeys = ['userInfo', 'user', 'profile', 'auth', 'currentUser', 'account', 'userData', 'loginUser'];
|
| for (const key of storageKeys) {
|
| const stored = localStorage.getItem(key) || sessionStorage.getItem(key);
|
| if (stored) {
|
| try {
|
| const parsed = JSON.parse(stored);
|
|
|
| const found = parsed.name
|
| || parsed.fullName
|
| || (parsed.user && parsed.user.name)
|
| || parsed.display_name
|
| || (parsed.personalDetails && parsed.personalDetails.fullName);
|
| if (found && found.trim().length > 1) return found.trim();
|
| } catch (err) {
|
|
|
| if (typeof stored === 'string' && stored.length < 80 && !stored.includes('{')) {
|
| return stored.trim();
|
| }
|
| }
|
| }
|
| }
|
| } catch (e) { }
|
|
|
|
|
|
|
| try {
|
| const tokenKeys = ['token', 'accessToken', 'access_token', 'authToken', 'auth_token', 'jwt', 'jwtToken'];
|
| let authToken = null;
|
| for (const tk of tokenKeys) {
|
| const tv = localStorage.getItem(tk) || sessionStorage.getItem(tk);
|
| if (tv && tv.length > 20) { authToken = tv.replace(/^Bearer\s+/i, ''); break; }
|
| }
|
| const headers = { 'Content-Type': 'application/json' };
|
| if (authToken) headers['Authorization'] = 'Bearer ' + authToken;
|
|
|
| const pResp = await fetch("https://hrmmartechsol-ba71e8dc3fa2.herokuapp.com/api/users/profile", {
|
| headers: headers,
|
| credentials: 'include'
|
| });
|
| if (pResp.ok) {
|
| const pData = await pResp.json();
|
|
|
| const found = pData.name || (pData.personalDetails && pData.personalDetails.fullName);
|
| if (found && found.trim().length > 1) return found.trim();
|
| }
|
| } catch (e) { }
|
|
|
| return null;
|
| }
|
|
|
|
|
| async function initUserSession() {
|
|
|
|
|
|
|
| const needsResolution = _isGenericId(sessionId);
|
|
|
| let userName = await _resolveUserName();
|
|
|
|
|
| const lastKnownName = localStorage.getItem('martech_last_user_name');
|
| if (userName && lastKnownName && userName !== lastKnownName) {
|
| localStorage.removeItem('martech_session_id');
|
| sessionId = 'user-' + Date.now() + '-' + Math.random().toString(36).slice(2, 9);
|
| localStorage.setItem('martech_session_id', sessionId);
|
| messagesContainer.innerHTML = '<div class="chat-message bot">Welcome! How can I help you today?</div>';
|
| }
|
| if (userName) {
|
| localStorage.setItem('martech_last_user_name', userName);
|
| }
|
|
|
|
|
|
|
|
|
| try {
|
| const initData = await fetch(BASE_URL + "/api/session/init", {
|
| method: 'POST',
|
| headers: { 'Content-Type': 'application/json' },
|
| body: JSON.stringify({ session_id: sessionId, user_name: userName || null })
|
| }).then(r => r.json());
|
|
|
| if (initData.session_id && initData.session_id !== sessionId) {
|
| sessionId = initData.session_id;
|
| localStorage.setItem('martech_session_id', sessionId);
|
| }
|
| } catch (e) {
|
|
|
| console.warn('Martech: background session pre-creation failed', e);
|
| }
|
|
|
| try {
|
| const hResp = await fetch(BASE_URL + "/api/session/history/" + sessionId);
|
| if (hResp.ok) {
|
| const hData = await hResp.json();
|
|
|
| if (hData.status === 'HUMAN_TAKEOVER') {
|
| document.getElementById('chat-status-dot').style.background = '#8b5cf6';
|
| document.getElementById('chat-header-title').textContent = 'Live Agent (Admin)';
|
| } else if (hData.status === 'ENDED') {
|
| document.getElementById('chat-status-dot').style.background = '#94a3b8';
|
| document.getElementById('chat-header-title').textContent = 'Chat Ended';
|
| } else {
|
| document.getElementById('chat-status-dot').style.background = '#10b981';
|
| document.getElementById('chat-header-title').textContent = 'Martechsol Assistant';
|
| }
|
|
|
| updateUIState(hData.status, hData.blocked);
|
|
|
| if (hData.last_id) {
|
| _lastPollId = hData.last_id;
|
| }
|
|
|
| if (hData.history && hData.history.length > 0) {
|
| messagesContainer.innerHTML = '';
|
| chatHistory = hData.history;
|
| chatHistory.forEach(m => {
|
| appendMessage(m.role === 'assistant' ? 'bot' : 'user', m.content);
|
| });
|
| }
|
| }
|
| } catch (e) { }
|
|
|
|
|
|
|
| if (!userName) {
|
| let _isResolving = false;
|
|
|
| const _attemptSync = async () => {
|
|
|
| if (!_isGenericId(sessionId) || _isResolving) return;
|
|
|
| _isResolving = true;
|
| const resolved = await _resolveUserName();
|
| if (resolved) {
|
| localStorage.setItem('martech_last_user_name', resolved);
|
| try {
|
|
|
| const rData = await fetch(BASE_URL + "/api/session/init", {
|
| method: 'POST',
|
| headers: { 'Content-Type': 'application/json' },
|
| body: JSON.stringify({ session_id: sessionId, user_name: resolved })
|
| }).then(r => r.json());
|
| if (rData.session_id && rData.session_id !== sessionId) {
|
| sessionId = rData.session_id;
|
| localStorage.setItem('martech_session_id', sessionId);
|
| }
|
| } catch (e) { }
|
| }
|
| _isResolving = false;
|
| };
|
|
|
|
|
| window.addEventListener('storage', _attemptSync);
|
|
|
|
|
| window.addEventListener('focus', _attemptSync);
|
|
|
|
|
| setInterval(_attemptSync, 15000);
|
| }
|
|
|
|
|
| if (_pollInterval) clearInterval(_pollInterval);
|
| _pollInterval = setInterval(pollMessages, 3000);
|
| }
|
|
|
| async function pollMessages() {
|
| if (!sessionId) return;
|
| try {
|
| const r = await fetch(BASE_URL + "/api/chat/poll", {
|
| method: 'POST', headers: { 'Content-Type': 'application/json' },
|
| body: JSON.stringify({ session_id: sessionId, last_id: _lastPollId })
|
| });
|
| if (r.ok) {
|
| const data = await r.json();
|
| _lastPollId = data.last_id;
|
|
|
| if (data.status === 'HUMAN_TAKEOVER') {
|
| document.getElementById('chat-status-dot').style.background = '#8b5cf6';
|
| document.getElementById('chat-header-title').textContent = 'Live Agent (Admin)';
|
| } else if (data.status === 'ENDED') {
|
| document.getElementById('chat-status-dot').style.background = '#94a3b8';
|
| document.getElementById('chat-header-title').textContent = 'Chat Ended';
|
| } else {
|
| document.getElementById('chat-status-dot').style.background = '#10b981';
|
| document.getElementById('chat-header-title').textContent = 'Martechsol Assistant';
|
| }
|
|
|
| updateUIState(data.status, data.blocked);
|
|
|
| if (data.new_messages && data.new_messages.length > 0) {
|
|
|
| if (_humanTypingEl) {
|
| _humanTypingEl.remove();
|
| _humanTypingEl = null;
|
| }
|
| data.new_messages.forEach(m => {
|
| appendMessage('bot', m.content);
|
| chatHistory.push({ role: "assistant", content: m.content });
|
| });
|
| }
|
| }
|
| } catch (e) { }
|
| }
|
|
|
| initUserSession();
|
|
|
| function showTypingIndicator() {
|
| const div = document.createElement('div');
|
| div.className = 'chat-message bot';
|
| div.style.padding = '8px 16px';
|
| div.innerHTML = '<div class="typing-indicator"><span></span><span></span><span></span></div>';
|
| messagesContainer.appendChild(div);
|
| messagesContainer.scrollTop = messagesContainer.scrollHeight;
|
| return div;
|
| }
|
|
|
|
|
| const NEARBY_KEYS = {
|
| 'a': 'sqwz', 'b': 'vghn', 'c': 'xdfv', 'd': 'sfecx', 'e': 'wrsdf',
|
| 'f': 'dgrtcv', 'g': 'fhtybn', 'h': 'gjyunb', 'i': 'uojkl', 'j': 'hkunmi',
|
| 'k': 'jlomi', 'l': 'kop', 'm': 'njk', 'n': 'bhjm', 'o': 'ipkl',
|
| 'p': 'ol', 'q': 'wa', 'r': 'edft', 's': 'awedxz', 't': 'rfgy',
|
| 'u': 'yihj', 'v': 'cfgb', 'w': 'qase', 'x': 'zsdc', 'y': 'tghu',
|
| 'z': 'xas',
|
| };
|
| const TYPO_CHANCE = 0.07;
|
|
|
| function getNearbyChar(ch) {
|
| const lower = ch.toLowerCase();
|
| if (NEARBY_KEYS[lower]) {
|
| const options = NEARBY_KEYS[lower];
|
| const replacement = options.charAt(Math.floor(Math.random() * options.length));
|
| return ch === ch.toUpperCase() ? replacement.toUpperCase() : replacement;
|
| }
|
| const abc = "abcdefghijklmnopqrstuvwxyz";
|
| return abc.charAt(Math.floor(Math.random() * abc.length));
|
| }
|
|
|
| const sleep = ms => new Promise(r => setTimeout(r, ms));
|
|
|
| async function typeMessageWithTypos(element, text) {
|
| const fullHtml = formatMessage(text);
|
|
|
|
|
| const tokens = fullHtml.split(/(<[^>]*>)/);
|
| let currentHtml = "";
|
|
|
| for (const token of tokens) {
|
| if (token.startsWith("<")) {
|
|
|
| currentHtml += token;
|
| element.innerHTML = currentHtml;
|
| } else {
|
|
|
| for (let char of token) {
|
| currentHtml += char;
|
| element.innerHTML = currentHtml;
|
| await sleep(Math.random() * 15 + 5);
|
| }
|
| }
|
| messagesContainer.scrollTop = messagesContainer.scrollHeight;
|
| }
|
| }
|
|
|
| async function handleSend() {
|
| const text = inputField.value.trim();
|
| if (!text) return;
|
|
|
|
|
| if (_humanTypingEl) {
|
| _humanTypingEl.remove();
|
| _humanTypingEl = null;
|
| }
|
|
|
|
|
| inputField.value = '';
|
| inputField.disabled = true;
|
| sendButton.disabled = true;
|
|
|
| appendMessage('user', text);
|
| const typingEl = showTypingIndicator();
|
|
|
| try {
|
|
|
| const response = await fetch(API_URL, {
|
| method: 'POST',
|
| headers: { 'Content-Type': 'application/json' },
|
| body: JSON.stringify({
|
| message: text,
|
| history: chatHistory,
|
| session_id: sessionId
|
| })
|
| });
|
|
|
| if (!response.ok) {
|
| if (response.status === 429) throw new Error("Rate limit exceeded.");
|
| throw new Error("Server error.");
|
| }
|
|
|
| const data = await response.json();
|
| const reply = data.reply;
|
|
|
| if (reply && reply.trim()) {
|
|
|
| typingEl.remove();
|
| const botEl = appendMessage('bot', '');
|
| chatHistory.push({ role: "user", content: text });
|
| chatHistory.push({ role: "assistant", content: reply });
|
| await typeMessageWithTypos(botEl, reply);
|
| } else {
|
|
|
|
|
| typingEl.innerHTML = `
|
| <div style="display:flex; align-items:center; justify-content:flex-start; gap:6px;">
|
| <span style="font-size:13px; color:#8b5cf6; font-weight:600; line-height:1; margin-top:2px;">typing</span>
|
| <div class="typing-indicator" style="height:18px; padding:0; margin:0;"><span></span><span></span><span></span></div>
|
| </div>`;
|
| typingEl.style.padding = '8px 16px';
|
| typingEl.style.lineHeight = '0.2';
|
| typingEl.style.borderColor = 'rgba(139,92,246,0.25)';
|
| typingEl.style.background = 'rgba(139,92,246,0.05)';
|
| _humanTypingEl = typingEl;
|
| chatHistory.push({ role: "user", content: text });
|
| }
|
|
|
| } catch (error) {
|
| console.error(error);
|
| typingEl.remove();
|
| if (_humanTypingEl === typingEl) _humanTypingEl = null;
|
| appendMessage('bot', "⚠️ I'm sorry, I encountered an error. Please try again.");
|
| } finally {
|
| inputField.disabled = false;
|
| sendButton.disabled = false;
|
| inputField.focus();
|
| }
|
| }
|
|
|
|
|
| sendButton.addEventListener('click', handleSend);
|
| inputField.addEventListener('keypress', function (e) {
|
| if (e.key === 'Enter') handleSend();
|
| });
|
|
|
| document.getElementById('btn-remind').addEventListener('click', async () => {
|
| if (_reminderUses <= 0) return;
|
|
|
| try {
|
| await fetch(BASE_URL + "/api/chat/remind", {
|
| method: 'POST', headers: { 'Content-Type': 'application/json' },
|
| body: JSON.stringify({ session_id: sessionId })
|
| });
|
|
|
| _reminderUses--;
|
| _lastReminderTime = Date.now();
|
| checkQueueTimer();
|
|
|
| const remindText = document.getElementById('remind-text');
|
| remindText.textContent = "Reminder Sent!";
|
| setTimeout(() => checkQueueTimer(), 2000);
|
| } catch (e) {}
|
| });
|
|
|
| document.getElementById('btn-exit-queue').addEventListener('click', async () => {
|
| try {
|
| document.getElementById('queue-actions-area').style.display = 'none';
|
| appendMessage('bot', "You have left the queue and are back to chatting with the AI.");
|
|
|
| await fetch(BASE_URL + "/api/chat/exit_queue", {
|
| method: 'POST', headers: { 'Content-Type': 'application/json' },
|
| body: JSON.stringify({ session_id: sessionId })
|
| });
|
|
|
|
|
| pollMessages();
|
| } catch (e) {}
|
| });
|
|
|
|
|
| const ratingOverlay = document.getElementById('rating-overlay');
|
| const stars = document.querySelectorAll('#rating-stars span');
|
| let _selectedRating = 0;
|
|
|
| stars.forEach(s => {
|
| s.addEventListener('click', () => {
|
| _selectedRating = parseInt(s.dataset.val);
|
| stars.forEach(st => {
|
| st.classList.toggle('active', parseInt(st.dataset.val) <= _selectedRating);
|
| });
|
| });
|
| });
|
|
|
| document.getElementById('btn-submit-rating').addEventListener('click', async () => {
|
| if (_selectedRating > 0) {
|
| try {
|
| await fetch(BASE_URL + "/api/chat/rate", {
|
| method: 'POST', headers: { 'Content-Type': 'application/json' },
|
| body: JSON.stringify({ session_id: sessionId, rating: _selectedRating })
|
| });
|
| } catch (e) { }
|
| }
|
|
|
|
|
| try {
|
| await fetch(BASE_URL + "/api/chat/reset", {
|
| method: 'POST', headers: { 'Content-Type': 'application/json' },
|
| body: JSON.stringify({ session_id: sessionId })
|
| });
|
| } catch (e) { }
|
|
|
| chatHistory = [];
|
| _lastPollId = 0;
|
| _wasBlocked = false;
|
| messagesContainer.innerHTML = '<div class="chat-message bot">Welcome! How can I help you today?</div>';
|
| ratingOverlay.classList.remove('show');
|
|
|
| document.getElementById('chat-status-dot').style.background = '#10b981';
|
| const titleEl = document.getElementById('chat-header-title');
|
| titleEl.textContent = 'Martechsol Assistant';
|
| document.getElementById('chat-input-area').style.display = 'flex';
|
| inputField.disabled = false;
|
| sendButton.disabled = false;
|
| inputField.placeholder = "Type your question here...";
|
|
|
| const newChatBtn = document.getElementById('btn-new-chat');
|
| if (newChatBtn) newChatBtn.remove();
|
|
|
| fab.click();
|
|
|
|
|
| setTimeout(() => {
|
| initUserSession();
|
| }, 500);
|
| });
|
|
|
| })();
|
| </script>
|
|
|
| </body>
|
|
|
| </html> |