var ws = null; var userTypingActive = 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 scrollMessages() { var el = document.getElementById('messages'); if (el) el.scrollTop = el.scrollHeight; } function showSupporterTyping() { var indicator = document.getElementById('peer-typing'); if (!indicator) return; indicator.classList.add('visible'); scrollMessages(); } function hideSupporterTyping() { var indicator = document.getElementById('peer-typing'); if (indicator) indicator.classList.remove('visible'); } function sendUserTypingState(state) { if (!ws || ws.readyState !== WebSocket.OPEN || !window.__sessionId) return; ws.send('typing:user:' + state + ':' + window.__sessionId); } function startUserTyping() { if (userTypingActive) return; userTypingActive = true; sendUserTypingState('start'); } function stopUserTyping() { if (!userTypingActive) return; sendUserTypingState('stop'); userTypingActive = false; } function handleUserTypingInput() { var input = document.getElementById('chat-input'); if (input && input.value.trim()) startUserTyping(); else stopUserTyping(); } document.addEventListener('htmx:afterSwap', function (e) { if (e.detail.target.id === 'messages') scrollMessages(); }); document.addEventListener('htmx:beforeRequest', function (e) { if (e.detail.elt && e.detail.elt.id === 'chat-form') stopUserTyping(); }); function resetChat() { stopUserTyping(); var oldWs = ws; ws = null; window.__sessionId = null; userTypingActive = false; if (oldWs) oldWs.close(); location.reload(); } function initChat(sessionId) { window.__sessionId = sessionId; document.getElementById('chat-session-id').textContent = 'Session #' + sessionId.slice(0, 8); var wsProtocol = location.protocol === 'https:' ? 'wss' : 'ws'; ws = new WebSocket(wsProtocol + '://' + location.host + '/ws/user/' + sessionId); ws.onmessage = function (message) { if (message.data === '__reset__') { resetChat(); return; } if (message.data.startsWith('typing:supporter:start:')) { showSupporterTyping(); return; } if (message.data.startsWith('typing:supporter:stop:')) { hideSupporterTyping(); return; } if (message.data.startsWith('typing:user:')) return; hideSupporterTyping(); htmx.trigger('#messages', 'refresh'); }; ws.onopen = function () { htmx.trigger('#messages', 'refresh'); }; ws.onerror = function () { console.warn('WebSocket error; polling will continue.'); }; var chatInput = document.getElementById('chat-input'); if (chatInput) { chatInput.addEventListener('focus', function () { setTimeout(setAppHeight, 80); }); chatInput.addEventListener('blur', function () { setTimeout(setAppHeight, 80); }); } setAppHeight(); }