var activeSessionId = null; var supporterWs = null; var lastTypingSessionId = null; var supporterTypingActive = false; function setAppHeight() { var height = window.visualViewport ? window.visualViewport.height : window.innerHeight; document.documentElement.style.setProperty('--app-height', height + 'px'); } setAppHeight(); window.addEventListener('resize', setAppHeight); if (window.visualViewport) { window.visualViewport.addEventListener('resize', setAppHeight); window.visualViewport.addEventListener('scroll', setAppHeight); } function openSidebar() { document.getElementById('sidebar').classList.add('open'); document.getElementById('drawer-backdrop').classList.add('visible'); } function closeSidebar() { document.getElementById('sidebar').classList.remove('open'); document.getElementById('drawer-backdrop').classList.remove('visible'); } function selectSession(button) { document.querySelectorAll('.session-item').forEach(function (b) { b.classList.remove('selected'); }); button.classList.add('selected'); setActiveSession(button.dataset.sessionId, button.dataset.sessionName); closeSidebar(); } function setActiveSession(sessionId, name) { stopSupporterTyping(lastTypingSessionId); activeSessionId = sessionId; document.getElementById('active-session-name').textContent = name; document.getElementById('active-session-name').style.color = ''; document.getElementById('active-session-meta').textContent = 'Session #' + sessionId.slice(0, 8); document.getElementById('reply-area').classList.add('visible'); document.getElementById('reply-input').value = ''; hideUserTyping(); scrollSupporterMessages(); } function resetSupporterDashboard() { stopSupporterTyping(lastTypingSessionId); activeSessionId = null; document.getElementById('active-session-name').textContent = 'Select a conversation'; document.getElementById('active-session-name').style.color = 'var(--ink-dim)'; document.getElementById('active-session-meta').textContent = ''; document.getElementById('reply-area').classList.remove('visible'); hideUserTyping(); closeSidebar(); document.getElementById('supporter-messages').innerHTML = '

Chat history was cleared.

New conversations will appear in the chat list.

'; htmx.trigger('#session-list', 'refresh'); } function scrollSupporterMessages() { var el = document.getElementById('supporter-messages'); if (el) el.scrollTop = el.scrollHeight; } function showUserTyping() { var indicator = document.getElementById('supporter-peer-typing'); if (!indicator) return; indicator.classList.add('visible'); scrollSupporterMessages(); } function hideUserTyping() { var indicator = document.getElementById('supporter-peer-typing'); if (indicator) indicator.classList.remove('visible'); } function sendSupporterTypingState(state, sessionId) { sessionId = sessionId || activeSessionId; if (!sessionId || !supporterWs || supporterWs.readyState !== WebSocket.OPEN) return; supporterWs.send('typing:supporter:' + state + ':' + sessionId); } function startSupporterTyping() { var input = document.getElementById('reply-input'); if (!activeSessionId || !input || !input.value.trim()) { stopSupporterTyping(lastTypingSessionId || activeSessionId); return; } if (supporterTypingActive && lastTypingSessionId === activeSessionId) return; supporterTypingActive = true; lastTypingSessionId = activeSessionId; sendSupporterTypingState('start', activeSessionId); } function stopSupporterTyping(sessionId) { sessionId = sessionId || activeSessionId; if (!supporterTypingActive && !sessionId) return; var targetSession = sessionId || lastTypingSessionId; if (targetSession) sendSupporterTypingState('stop', targetSession); supporterTypingActive = false; lastTypingSessionId = null; } function handleReplyInput() { var input = document.getElementById('reply-input'); if (input && input.value.trim()) startSupporterTyping(); else stopSupporterTyping(lastTypingSessionId || activeSessionId); } document.addEventListener('htmx:afterSwap', function (e) { if (e.detail.target.id === 'supporter-messages') scrollSupporterMessages(); }); document.getElementById('reply-btn').addEventListener('click', function () { if (!activeSessionId) return; var input = document.getElementById('reply-input'); var content = input.value.trim(); if (!content) return; stopSupporterTyping(activeSessionId); fetch('/supporter/send/' + activeSessionId, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: 'content=' + encodeURIComponent(content) }) .then(function (r) { return r.text(); }) .then(function (html) { var msgs = document.getElementById('supporter-messages'); msgs.insertAdjacentHTML('beforeend', html); input.value = ''; scrollSupporterMessages(); setAppHeight(); }); }); document.getElementById('reply-input').addEventListener('keydown', function (e) { if (e.key === 'Enter') document.getElementById('reply-btn').click(); }); document.getElementById('reply-input').addEventListener('input', handleReplyInput); document.getElementById('reply-input').addEventListener('focus', function () { setTimeout(setAppHeight, 80); }); document.getElementById('reply-input').addEventListener('blur', function () { setTimeout(setAppHeight, 80); }); function connectSupporterWS() { var wsProtocol = location.protocol === 'https:' ? 'wss' : 'ws'; supporterWs = new WebSocket(wsProtocol + '://' + location.host + '/ws/supporter/dashboard'); supporterWs.onmessage = function (e) { var data = e.data; if (data === '__reset__') { resetSupporterDashboard(); return; } if (data.indexOf('typing:user:start:') === 0) { var sessionId = data.replace('typing:user:start:', ''); if (sessionId === activeSessionId) showUserTyping(); return; } if (data.indexOf('typing:user:stop:') === 0) { var sessionId2 = data.replace('typing:user:stop:', ''); if (sessionId2 === activeSessionId) hideUserTyping(); return; } if (data.indexOf('typing:supporter:') === 0) return; htmx.trigger('#session-list', 'refresh'); if (data === activeSessionId) { hideUserTyping(); fetch('/supporter/messages/' + activeSessionId) .then(function (r) { return r.text(); }) .then(function (html) { document.getElementById('supporter-messages').innerHTML = html; scrollSupporterMessages(); }); } }; supporterWs.onopen = function () { if (supporterTypingActive && lastTypingSessionId) sendSupporterTypingState('start', lastTypingSessionId); }; supporterWs.onclose = function () { setTimeout(connectSupporterWS, 2000); }; } connectSupporterWS();