document.addEventListener('DOMContentLoaded', () => { const connectBtn = document.getElementById('connectBtn'); if (!connectBtn) return; // Exit if not logged in 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'); const aiPersonality = document.getElementById('aiPersonality'); let isConnected = false; let commentCount = 0; let synth = window.speechSynthesis; let voices = []; let pollInterval = null; let lastEventId = 0; // Load voices function populateVoiceList() { if (!synth) return; 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; }); if (voiceSelect) { 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; } if (rateRange) { rateRange.addEventListener('input', () => { if (rateValue) rateValue.textContent = rateRange.value; }); } if (aiPersonality) { aiPersonality.addEventListener('change', async () => { const personality = aiPersonality.value; try { const resp = await fetch('api/update_personality.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ personality }) }); const data = await resp.json(); if (data.success) { showToast('Settings Saved', 'AI personality updated successfully.', 'success'); } else { showToast('Error', data.error || 'Failed to update personality', 'danger'); } } catch (err) { showToast('Network Error', 'Could not reach settings API.', 'danger'); } }); } 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'); if (disconnectBtn) disconnectBtn.classList.remove('d-none'); if (connectionStatus) connectionStatus.innerHTML = ' Live connection active: @' + username; if (emptyFeed) 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'); if (disconnectBtn) disconnectBtn.classList.add('d-none'); if (connectionStatus) 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++; if (commentCountBadge) 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 = `
${event.author}: ${event.comment}
${new Date(event.timestamp).toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'})}
AI: ${event.reply}
`; if (commentFeed) commentFeed.prepend(commentItem); // TTS speak(event.reply); // Update history table updateHistoryTable(event); } connectBtn.addEventListener('click', () => { const username = tiktokUsernameInput.value.trim(); if (!username) { showToast('Error', 'Please enter a TikTok username.', 'danger'); return; } startBridge(username); }); if (disconnectBtn) { disconnectBtn.addEventListener('click', () => { const username = tiktokUsernameInput.value.trim(); stopBridge(username); }); } if (simulateBtn) { simulateBtn.addEventListener('click', async () => { const commentText = manualCommentInput.value.trim(); const username = tiktokUsernameInput.value.trim(); if (!commentText || !username) return; try { // Manually insert into DB to test await fetch('api/process_comment.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ author: 'TestUser', comment: commentText, username }) }); manualCommentInput.value = ''; // Polling will pick it up } catch (err) { console.error(err); } }); } function speak(text) { if (!text || !synth) return; const utterThis = new SpeechSynthesisUtterance(text); const selectedOption = voiceSelect?.selectedOptions[0]?.getAttribute('data-name'); if (selectedOption) { for (let i = 0; i < voices.length; i++) { if (voices[i].name === selectedOption) { utterThis.voice = voices[i]; } } } utterThis.pitch = 1; utterThis.rate = rateRange?.value || 1.0; synth.speak(utterThis); } function updateHistoryTable(event) { if (!historyTableBody) return; const row = document.createElement('tr'); row.className = 'border-secondary'; const time = new Date(event.timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' }); row.innerHTML = ` ${time} ${event.author} ${event.comment} ${event.reply} `; if (historyTableBody.firstChild && historyTableBody.firstChild.tagName === 'TR' && historyTableBody.firstChild.innerText.includes('No history yet')) { historyTableBody.innerHTML = ''; } historyTableBody.prepend(row); if (historyTableBody.children.length > 20) { historyTableBody.removeChild(historyTableBody.lastChild); } } function showToast(title, message, type = 'info') { if (!toastContainer) return; const toastId = 'toast-' + Date.now(); const toastHtml = ` `; toastContainer.insertAdjacentHTML('beforeend', toastHtml); const toastElement = document.getElementById(toastId); if (toastElement) { const toast = new bootstrap.Toast(toastElement); toast.show(); toastElement.addEventListener('hidden.bs.toast', () => { toastElement.remove(); }); } } });