document.addEventListener('DOMContentLoaded', () => { const connectBtn = document.getElementById('connectBtn'); const disconnectBtn = document.getElementById('disconnectBtn'); const tiktokUsernameInput = document.getElementById('tiktokUsername'); const connectionStatus = document.getElementById('connectionStatus'); const commentFeed = document.getElementById('commentFeed'); const emptyFeed = document.getElementById('emptyFeed'); const voiceSelect = document.getElementById('voiceSelect'); const rateRange = document.getElementById('rateRange'); const rateValue = document.getElementById('rateValue'); const autoReplyToggle = document.getElementById('autoReplyToggle'); const simulateBtn = document.getElementById('simulateBtn'); const manualCommentInput = document.getElementById('manualComment'); const historyTableBody = document.getElementById('historyTableBody'); const commentCountBadge = document.getElementById('commentCount'); const toastContainer = document.getElementById('toastContainer'); let isConnected = false; let commentCount = 0; let synth = window.speechSynthesis; let voices = []; let pollInterval = null; let lastEventId = 0; // Load voices function populateVoiceList() { voices = synth.getVoices().sort(function (a, b) { const aname = a.name.toUpperCase(); const bname = b.name.toUpperCase(); if (aname < bname) return -1; else if (aname > bname) return 1; return 0; }); voiceSelect.innerHTML = ''; voices.forEach((voice, i) => { const option = document.createElement('option'); option.textContent = `${voice.name} (${voice.lang})`; if (voice.default) option.textContent += ' -- DEFAULT'; option.setAttribute('data-lang', voice.lang); option.setAttribute('data-name', voice.name); voiceSelect.appendChild(option); }); } populateVoiceList(); if (speechSynthesis.onvoiceschanged !== undefined) { speechSynthesis.onvoiceschanged = populateVoiceList; } rateRange.addEventListener('input', () => { rateValue.textContent = rateRange.value; }); async function startBridge(username) { try { const resp = await fetch(`api/bridge_control.php?action=start&username=${username}`); const data = await resp.json(); if (data.success) { isConnected = true; connectBtn.classList.add('d-none'); disconnectBtn.classList.remove('d-none'); connectionStatus.innerHTML = ' Live connection active: @' + username; emptyFeed.classList.add('d-none'); showToast('Connected', `Started listening to @${username}. Looking for comments...`, 'success'); startPolling(username); } else { showToast('Bridge Error', data.error || 'Failed to start bridge', 'danger'); } } catch (err) { showToast('Network Error', 'Could not reach bridge control.', 'danger'); } } async function stopBridge(username) { try { await fetch(`api/bridge_control.php?action=stop&username=${username}`); isConnected = false; connectBtn.classList.remove('d-none'); disconnectBtn.classList.add('d-none'); connectionStatus.innerHTML = ' Disconnected'; showToast('Disconnected', 'Stopped bridge.', 'warning'); if (pollInterval) clearInterval(pollInterval); } catch (err) { console.error(err); } } function startPolling(username) { if (pollInterval) clearInterval(pollInterval); pollInterval = setInterval(async () => { if (!isConnected) return; try { const resp = await fetch(`api/get_updates.php?username=${username}`); const data = await resp.json(); if (data.events && data.events.length > 0) { data.events.forEach(event => { // Only add if not already seen if (event.id > lastEventId) { addEventToFeed(event); lastEventId = event.id; } }); } } catch (err) { console.error('Polling error:', err); } }, 3000); // Poll every 3 seconds } function addEventToFeed(event) { commentCount++; commentCountBadge.textContent = `${commentCount} events`; const isSystem = event.comment.includes('sent') && event.comment.includes('x'); const commentItem = document.createElement('div'); commentItem.className = isSystem ? 'comment-item border-start border-4 border-tiktok-red' : 'comment-item'; commentItem.innerHTML = `