// ============================================================================ // Miku Control Panel â Core Module // Global variables, utility functions, tab switching, initialization, polling // ============================================================================ // Global variables let currentMood = 'neutral'; let voiceCallActive = false; let voiceCallHistory = []; let servers = []; let evilMode = false; let bipolarMode = false; let selectedGPU = 'nvidia'; let chatConversationHistory = []; let pfpCropper = null; let albumEntries = []; let albumSelectedId = null; let albumChecked = new Set(); let albumCropper = null; let albumOpen = false; let activitiesData = null; let activitiesOpen = false; let activitiesSections = { normal: false, evil: false }; let activitiesEditing = {}; let activitiesEditCache = {}; let currentEditMemory = null; let logsAutoScroll = true; let notificationTimer = null; let statusInterval = null; let logsInterval = null; let argsInterval = null; let promptInterval = null; // Mood emoji mapping const MOOD_EMOJIS = { "asleep": "ðĪ", "neutral": "", "bubbly": "ðŦ§", "sleepy": "ð", "curious": "ð", "shy": "ðð", "serious": "ð", "excited": "âĻ", "melancholy": "ð·", "flirty": "ðŦĶ", "romantic": "ð", "irritated": "ð", "angry": "ðĒ", "silly": "ðŠŋ" }; // Evil mood emoji mapping const EVIL_MOOD_EMOJIS = { "aggressive": "ðŋ", "cunning": "ð", "sarcastic": "ð", "evil_neutral": "", "bored": "ðĨą", "manic": "ðĪŠ", "jealous": "ð", "melancholic": "ð", "playful_cruel": "ð", "contemptuous": "ð" }; // ============================================================================ // Utility functions // ============================================================================ function showNotification(message, type = 'info') { const notification = document.getElementById('notification'); notification.textContent = message; notification.style.display = 'block'; notification.style.opacity = '0.95'; if (type === 'error') { notification.style.backgroundColor = '#d32f2f'; } else if (type === 'success') { notification.style.backgroundColor = '#2e7d32'; } else { notification.style.backgroundColor = '#222'; } if (notificationTimer) clearTimeout(notificationTimer); notificationTimer = setTimeout(() => { notification.style.opacity = '0'; setTimeout(() => { notification.style.display = 'none'; notificationTimer = null; }, 300); }, 3000); } async function apiCall(endpoint, method = 'GET', data = null) { try { const options = { method: method, headers: { 'Content-Type': 'application/json', } }; if (data) { options.body = JSON.stringify(data); } const response = await fetch(endpoint, options); const result = await response.json(); if (response.ok) { return result; } else { throw new Error(result.message || 'API call failed'); } } catch (error) { console.error('API call error:', error); showNotification(error.message, 'error'); throw error; } } function escapeHtml(text) { if (!text) return ''; const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } function escapeJsonForAttribute(obj) { return JSON.stringify(obj) .replace(/&/g, '&') .replace(/'/g, ''') .replace(/"/g, '"') .replace(//g, '>'); } // ============================================================================ // Tab switching // ============================================================================ function switchTab(tabId) { document.querySelectorAll('.tab-content').forEach(tab => { tab.classList.remove('active'); }); document.querySelectorAll('.tab-button').forEach(button => { button.classList.remove('active'); }); document.getElementById(tabId).classList.add('active'); const activeBtn = document.querySelector(`.tab-button[data-tab="${tabId}"]`); if (activeBtn) activeBtn.classList.add('active'); localStorage.setItem('miku-active-tab', tabId); console.log(`ð Switched to ${tabId}`); if (tabId === 'tab1') { console.log('ð Refreshing figurine subscribers for Server Management tab'); refreshFigurineSubscribers(); } if (tabId === 'tab3') { loadStatus(); loadLastPrompt(); } if (tabId === 'tab6') { showTabLoading('tab6'); loadAutonomousStats().finally(() => hideTabLoading('tab6')); } if (tabId === 'tab9') { console.log('ð§ Refreshing memory stats for Memories tab'); showTabLoading('tab9'); refreshMemoryStats().finally(() => hideTabLoading('tab9')); } if (tabId === 'tab10') { console.log('ðą Loading DM users for DM Management tab'); showTabLoading('tab10'); loadDMUsers().finally(() => hideTabLoading('tab10')); } if (tabId === 'tab11') { console.log('ðžïļ Loading Profile Picture tab'); loadPfpTab(); } } function showTabLoading(tabId) { const tab = document.getElementById(tabId); if (!tab) return; if (tab.querySelector('.tab-loading-overlay')) return; const sections = tab.querySelectorAll('.section'); const hasContent = Array.from(sections).some(s => s.querySelector('[id]')?.innerHTML?.trim()); if (hasContent) return; const overlay = document.createElement('div'); overlay.className = 'tab-loading-overlay'; overlay.innerHTML = '
Loading...'; tab.prepend(overlay); } function hideTabLoading(tabId) { const tab = document.getElementById(tabId); if (!tab) return; const overlay = tab.querySelector('.tab-loading-overlay'); if (overlay) overlay.remove(); } // ============================================================================ // Polling // ============================================================================ function startPolling() { if (!statusInterval) statusInterval = setInterval(loadStatus, 10000); if (!logsInterval) logsInterval = setInterval(loadLogs, 5000); if (!argsInterval) argsInterval = setInterval(loadActiveArguments, 5000); if (!promptInterval) promptInterval = setInterval(loadPromptHistory, 10000); } function stopPolling() { clearInterval(statusInterval); statusInterval = null; clearInterval(logsInterval); logsInterval = null; clearInterval(argsInterval); argsInterval = null; clearInterval(promptInterval); promptInterval = null; } // ============================================================================ // Initialization helpers // ============================================================================ function initTabState() { const savedTab = localStorage.getItem('miku-active-tab'); if (savedTab && document.getElementById(savedTab)) { switchTab(savedTab); } } function initTabWheelScroll() { const tabButtonsEl = document.querySelector('.tab-buttons'); if (tabButtonsEl) { tabButtonsEl.addEventListener('wheel', function(e) { if (e.deltaY !== 0) { e.preventDefault(); tabButtonsEl.scrollLeft += e.deltaY; } }, { passive: false }); } } function initVisibilityPolling() { document.addEventListener('visibilitychange', () => { if (document.hidden) { stopPolling(); console.log('âļ Tab hidden â polling paused'); } else { loadStatus(); loadLogs(); loadActiveArguments(); loadPromptHistory(); startPolling(); console.log('âķïļ Tab visible â polling resumed'); } }); } function initChatImagePreview() { const imageInput = document.getElementById('chat-image-file'); if (imageInput) { imageInput.addEventListener('change', function(e) { const file = e.target.files[0]; if (file) { const reader = new FileReader(); reader.onload = function(event) { const preview = document.getElementById('chat-image-preview'); const previewImg = document.getElementById('chat-image-preview-img'); previewImg.src = event.target.result; preview.style.display = 'block'; }; reader.readAsDataURL(file); } }); } } function initModalAccessibility() { const editModal = document.getElementById('edit-memory-modal'); const createModal = document.getElementById('create-memory-modal'); if (editModal) { editModal.setAttribute('role', 'dialog'); editModal.setAttribute('aria-modal', 'true'); editModal.setAttribute('aria-label', 'Edit Memory'); editModal.addEventListener('click', function(e) { if (e.target === this) closeEditMemoryModal(); }); } if (createModal) { createModal.setAttribute('role', 'dialog'); createModal.setAttribute('aria-modal', 'true'); createModal.setAttribute('aria-label', 'Create Memory'); createModal.addEventListener('click', function(e) { if (e.target === this) closeCreateMemoryModal(); }); } } function initPromptSourceToggle() { const saved = localStorage.getItem('miku-prompt-source') || 'all'; document.querySelectorAll('.prompt-source-btn').forEach(btn => btn.classList.remove('active')); const btnId = saved === 'all' ? 'prompt-src-all' : `prompt-src-${saved}`; const btn = document.getElementById(btnId); if (btn) btn.classList.add('active'); } function initLogsScrollDetection() { const logsPanel = document.getElementById('logs-panel'); if (!logsPanel) return; logsPanel.addEventListener('scroll', function() { const atBottom = logsPanel.scrollHeight - logsPanel.scrollTop - logsPanel.clientHeight < 50; logsAutoScroll = atBottom; const banner = document.getElementById('logs-paused-banner'); if (banner) banner.style.display = atBottom ? 'none' : 'block'; }); } function scrollLogsToBottom() { const logsPanel = document.getElementById('logs-panel'); if (logsPanel) { logsPanel.scrollTop = logsPanel.scrollHeight; logsAutoScroll = true; const banner = document.getElementById('logs-paused-banner'); if (banner) banner.style.display = 'none'; } } // ============================================================================ // Log functions // ============================================================================ function classifyLogLine(line) { const upper = line.toUpperCase(); if (upper.includes(' ERROR ') || upper.includes(' CRITICAL ') || upper.startsWith('ERROR') || upper.startsWith('CRITICAL') || upper.includes('TRACEBACK')) return 'log-error'; if (upper.includes(' WARNING ') || upper.startsWith('WARNING')) return 'log-warning'; if (upper.includes(' DEBUG ') || upper.startsWith('DEBUG')) return 'log-debug'; return 'log-info'; } async function loadLogs() { try { const result = await apiCall('/logs'); const logsContent = document.getElementById('logs-content'); const lines = (result || '').split('\n'); logsContent.innerHTML = lines.map(line => { if (!line.trim()) return ''; const cls = classifyLogLine(line); return `