/** * BRAIN Alpha Simulator - Frontend JavaScript * Handles the user interface for the simulator with parameter input and log monitoring */ let currentLogFile = null; let logPollingInterval = null; let isSimulationRunning = false; let simulationAbortController = null; let userSelectedLogFile = false; // Track if user manually selected a log file // Initialize page when DOM is loaded document.addEventListener('DOMContentLoaded', function() { refreshLogFiles(); setupFormValidation(); loadSimulatorDefaults(); }); /** * Setup form validation and change handlers */ function setupFormValidation() { const startPosition = document.getElementById('startPosition'); const randomShuffle = document.getElementById('randomShuffle'); const jsonFile = document.getElementById('jsonFile'); // Show warning when file might be overwritten function checkOverwriteWarning() { const warning = document.getElementById('overwriteWarning'); const showWarning = parseInt(startPosition.value) > 0 || randomShuffle.checked; warning.style.display = showWarning ? 'block' : 'none'; } startPosition.addEventListener('input', checkOverwriteWarning); randomShuffle.addEventListener('change', checkOverwriteWarning); // Handle JSON file selection jsonFile.addEventListener('change', function(e) { const file = e.target.files[0]; const info = document.getElementById('jsonFileInfo'); if (file) { info.innerHTML = ` Selected: ${file.name}
Size: ${(file.size / 1024).toFixed(1)} KB
Modified: ${new Date(file.lastModified).toLocaleString()} `; info.style.display = 'block'; // Try to read and validate JSON const reader = new FileReader(); reader.onload = function(e) { try { const data = JSON.parse(e.target.result); if (Array.isArray(data)) { const maxStart = Math.max(0, data.length - 1); startPosition.max = maxStart; info.innerHTML += `
Expressions: ${data.length} found`; } else { info.innerHTML += '
⚠️ Warning: Not an array format'; } } catch (err) { info.innerHTML += '
❌ Invalid JSON format'; } }; reader.readAsText(file); } else { info.style.display = 'none'; } }); } /** * Load default values from localStorage if available */ function loadSimulatorDefaults() { const username = localStorage.getItem('simulator_username'); if (username) { document.getElementById('username').value = username; } const concurrentCount = localStorage.getItem('simulator_concurrent'); if (concurrentCount) { document.getElementById('concurrentCount').value = concurrentCount; } } /** * Save current form values to localStorage */ function saveSimulatorDefaults() { localStorage.setItem('simulator_username', document.getElementById('username').value); localStorage.setItem('simulator_concurrent', document.getElementById('concurrentCount').value); } /** * Toggle password visibility */ function togglePassword() { const passwordInput = document.getElementById('password'); const isPassword = passwordInput.type === 'password'; passwordInput.type = isPassword ? 'text' : 'password'; const toggleBtn = document.querySelector('.password-toggle'); toggleBtn.textContent = isPassword ? 'πŸ™ˆ' : 'πŸ‘οΈ'; } /** * Toggle multi-simulation options */ function toggleMultiSimOptions() { const checkbox = document.getElementById('useMultiSim'); const options = document.getElementById('multiSimOptions'); options.style.display = checkbox.checked ? 'block' : 'none'; } /** * Refresh available log files */ async function refreshLogFiles() { try { const response = await fetch('/api/simulator/logs'); const data = await response.json(); const selector = document.getElementById('logSelector'); selector.innerHTML = ''; if (data.logs && data.logs.length > 0) { data.logs.forEach(log => { const option = document.createElement('option'); option.value = log.filename; option.textContent = `${log.filename} (${log.size}, ${log.modified})`; selector.appendChild(option); }); // Auto-select the latest log file only if user hasn't manually selected one if (data.latest && !userSelectedLogFile) { selector.value = data.latest; currentLogFile = data.latest; // Update UI to show auto-monitoring document.getElementById('currentLogFile').innerHTML = ` πŸ”„ Auto-monitoring: ${data.latest}
Latest log file will be automatically selected when new ones appear. `; loadSelectedLog(); // Ensure polling is active for auto-selected files too ensureLogPollingActive(); } } } catch (error) { console.error('Error refreshing log files:', error); updateStatus('Error loading log files', 'error'); } // Ensure polling continues after refresh ensureLogPollingActive(); } /** * Load selected log file content */ async function loadSelectedLog() { const selector = document.getElementById('logSelector'); const selectedLog = selector.value; if (!selectedLog) { // Reset if user deselects userSelectedLogFile = false; currentLogFile = null; document.getElementById('currentLogFile').innerHTML = ` πŸ”„ Auto-mode enabled: Will monitor latest log when available
System will automatically select and monitor the newest log file. `; // Try to auto-select latest again refreshLogFiles(); return; } // Mark that user has manually selected a log file userSelectedLogFile = true; currentLogFile = selectedLog; // Start polling if not already running to monitor the selected file ensureLogPollingActive(); try { const response = await fetch(`/api/simulator/logs/${encodeURIComponent(selectedLog)}`); const data = await response.json(); if (data.content !== undefined) { const logViewer = document.getElementById('logViewer'); // Use innerHTML to properly handle character encoding for Chinese text const content = data.content || 'Log file is empty.'; logViewer.textContent = content; logViewer.scrollTop = logViewer.scrollHeight; // Only update status if user manually selected (not auto-selected) if (userSelectedLogFile) { document.getElementById('currentLogFile').innerHTML = ` πŸ“Œ Manually selected: ${selectedLog}
Auto-switching to latest log disabled. Select "Select a log file..." to re-enable. `; } } } catch (error) { console.error('Error loading log file:', error); updateStatus('Error loading log content', 'error'); } } /** * Test connection to BRAIN API */ async function testConnection() { const username = document.getElementById('username').value; const password = document.getElementById('password').value; if (!username || !password) { updateStatus('Please enter username and password first', 'error'); return; } const testBtn = document.getElementById('testBtn'); testBtn.disabled = true; testBtn.textContent = 'πŸ”„ Testing...'; updateStatus('Testing BRAIN API connection...', 'running'); try { const response = await fetch('/api/simulator/test-connection', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username: username, password: password }) }); const data = await response.json(); if (data.success) { updateStatus('βœ… Connection successful! Ready to run simulation.', 'success'); saveSimulatorDefaults(); } else { updateStatus(`❌ Connection failed: ${data.error}`, 'error'); } } catch (error) { updateStatus(`❌ Connection error: ${error.message}`, 'error'); } finally { testBtn.disabled = false; testBtn.textContent = 'πŸ”— Test Connection'; } } /** * Run the simulator with user parameters */ async function runSimulator() { if (isSimulationRunning) { updateStatus('Simulation is already running', 'error'); return; } // Validate form const form = document.getElementById('simulatorForm'); if (!form.checkValidity()) { form.reportValidity(); return; } const jsonFile = document.getElementById('jsonFile').files[0]; if (!jsonFile) { updateStatus('Please select a JSON file', 'error'); return; } // Prepare form data const formData = new FormData(); formData.append('jsonFile', jsonFile); formData.append('username', document.getElementById('username').value); formData.append('password', document.getElementById('password').value); formData.append('startPosition', document.getElementById('startPosition').value); formData.append('concurrentCount', document.getElementById('concurrentCount').value); formData.append('randomShuffle', document.getElementById('randomShuffle').checked); formData.append('useMultiSim', document.getElementById('useMultiSim').checked); formData.append('alphaCountPerSlot', document.getElementById('alphaCountPerSlot').value); // UI updates isSimulationRunning = true; const runBtn = document.getElementById('runSimulator'); const stopBtn = document.getElementById('stopBtn'); runBtn.disabled = true; runBtn.textContent = 'πŸ”„ Running...'; stopBtn.style.display = 'inline-block'; updateStatus('Starting simulation...', 'running'); showProgress(true); // Create abort controller for stopping simulation simulationAbortController = new AbortController(); try { saveSimulatorDefaults(); const response = await fetch('/api/simulator/run', { method: 'POST', body: formData, signal: simulationAbortController.signal }); const data = await response.json(); if (data.success) { updateStatus('βœ… Simulator launched in new terminalε›žζ΅‹ε™¨ε―εŠ¨ζˆεŠŸ! Check the terminal window for progress.θ―·ζ£€ζŸ₯ζ–°ε”€θ΅·ηš„python程序', 'success'); // Show launch information if (data.parameters) { showLaunchInfo(data.parameters); } // Start monitoring log files more frequently since simulation is running startLogPolling(); // Refresh log files to get the latest simulation log setTimeout(() => { refreshLogFiles(); }, 3000); } else { updateStatus(`❌ Failed to launch simulator: ${data.error}`, 'error'); } } catch (error) { if (error.name === 'AbortError') { updateStatus('⏹️ Simulation stopped by user', 'idle'); } else { updateStatus(`❌ Simulation error: ${error.message}`, 'error'); } } finally { isSimulationRunning = false; runBtn.disabled = false; runBtn.textContent = 'πŸš€ Start Simulation'; stopBtn.style.display = 'none'; simulationAbortController = null; showProgress(false); } } /** * Stop running simulation */ async function stopSimulation() { if (simulationAbortController) { simulationAbortController.abort(); } try { await fetch('/api/simulator/stop', { method: 'POST' }); } catch (error) { console.error('Error stopping simulation:', error); } updateStatus('Stopping simulation...', 'idle'); } /** * Ensure log polling is active if we have a log file to monitor */ function ensureLogPollingActive() { if (currentLogFile && !logPollingInterval) { console.log('Starting log polling for file:', currentLogFile); startLogPolling(); // Add visual indicator that polling is active const currentLogFileDiv = document.getElementById('currentLogFile'); if (userSelectedLogFile) { currentLogFileDiv.innerHTML = ` πŸ“Œ Manually selected: ${currentLogFile} ●
Auto-switching to latest log disabled. Select "Select a log file..." to re-enable. `; } else { currentLogFileDiv.innerHTML = ` πŸ”„ Auto-monitoring: ${currentLogFile} ●
Latest log file will be automatically selected when new ones appear. `; } } else if (currentLogFile && logPollingInterval) { console.log('Log polling already active for:', currentLogFile); } } /** * Start polling for log updates */ function startLogPolling() { if (logPollingInterval) { clearInterval(logPollingInterval); } // Start more frequent polling when simulation is running in terminal logPollingInterval = setInterval(async () => { try { // Only refresh log file list if user hasn't manually selected a file // This allows the system to detect new log files but won't interfere with user's choice if (!userSelectedLogFile) { await refreshLogFiles(); } // Always refresh the content of the currently monitored log file if (currentLogFile) { console.log('Polling log file:', currentLogFile, 'User selected:', userSelectedLogFile); const response = await fetch(`/api/simulator/logs/${encodeURIComponent(currentLogFile)}`); const data = await response.json(); if (data.content !== undefined) { const logViewer = document.getElementById('logViewer'); logViewer.textContent = data.content; logViewer.scrollTop = logViewer.scrollHeight; } } } catch (error) { console.error('Error polling log:', error); } }, 3000); // Poll every 3 seconds when running in terminal // Auto-stop polling after 15 minutes to prevent excessive server load setTimeout(() => { if (logPollingInterval) { clearInterval(logPollingInterval); logPollingInterval = null; console.log('Auto-stopped log polling after 15 minutes'); } }, 900000); // 15 minutes } /** * Stop log polling */ function stopLogPolling() { if (logPollingInterval) { clearInterval(logPollingInterval); logPollingInterval = null; } } /** * Update status indicator */ function updateStatus(message, type = 'idle') { const statusEl = document.getElementById('simulatorStatus'); statusEl.textContent = message; statusEl.className = `status-indicator status-${type}`; } /** * Show/hide progress bar */ function showProgress(show) { const progressDiv = document.getElementById('simulationProgress'); progressDiv.style.display = show ? 'block' : 'none'; if (!show) { updateProgress(0, 0); } } /** * Update progress bar */ function updateProgress(current, total) { const progressText = document.getElementById('progressText'); const progressBar = document.getElementById('progressBar'); progressText.textContent = `${current}/${total}`; if (total > 0) { const percentage = (current / total) * 100; progressBar.style.width = `${percentage}%`; } else { progressBar.style.width = '0%'; } } /** * Show launch information when simulator starts in terminal */ function showLaunchInfo(parameters) { const resultsPanel = document.getElementById('resultsPanel'); const resultsDiv = document.getElementById('simulationResults'); let html = '
'; html += '

πŸš€ Simulator Launched Successfully

'; html += '

The simulator is running in a separate terminal window. You can monitor the progress there.

'; html += '
'; html += '
πŸ“‹ Configuration Summary:
'; html += `

Total Expressions: ${parameters.expressions_count}

`; html += `

Concurrent Simulations: ${parameters.concurrent_count}

`; if (parameters.use_multi_sim) { html += `

Multi-Simulation Mode: Yes (${parameters.alpha_count_per_slot} alphas per slot)

`; } else { html += `

Multi-Simulation Mode: No

`; } html += '
'; html += '
'; html += '

πŸ’‘ Monitoring Tips:

'; html += ''; html += '
'; html += '
'; resultsDiv.innerHTML = html; resultsPanel.style.display = 'block'; // Scroll to results resultsPanel.scrollIntoView({ behavior: 'smooth' }); } /** * Show simulation results */ function showResults(results) { const resultsPanel = document.getElementById('resultsPanel'); const resultsDiv = document.getElementById('simulationResults'); let html = '
'; html += `

Total Simulations: ${results.total || 0}

`; html += `

Successful: ${results.successful || 0}

`; html += `

Failed: ${results.failed || 0}

`; // Add multi-simulation information if applicable if (results.use_multi_sim && results.alpha_count_per_slot) { html += `
`; html += `πŸ“Œ Multi-Simulation Mode:
`; html += `Each simulation slot contains ${results.alpha_count_per_slot} alphas.
`; html += `Total individual alphas processed: ${results.successful * results.alpha_count_per_slot}`; html += `
`; } html += '
'; if (results.alphaIds && results.alphaIds.length > 0) { html += '

Generated Alpha IDs:

'; html += '
'; results.alphaIds.forEach((alphaId, index) => { html += `
${index + 1}. ${alphaId}
`; }); html += '
'; // Add copy button for alpha IDs html += '
'; html += ''; html += '
'; } resultsDiv.innerHTML = html; resultsPanel.style.display = 'block'; // Store results for copying window.lastSimulationResults = results; // Scroll to results resultsPanel.scrollIntoView({ behavior: 'smooth' }); } /** * Copy all alpha IDs to clipboard */ function copyAlphaIds() { if (window.lastSimulationResults && window.lastSimulationResults.alphaIds) { const alphaIds = window.lastSimulationResults.alphaIds.join('\n'); navigator.clipboard.writeText(alphaIds).then(() => { updateStatus('βœ… Alpha IDs copied to clipboard!', 'success'); }).catch(err => { console.error('Failed to copy: ', err); updateStatus('❌ Failed to copy Alpha IDs', 'error'); }); } } /** * Handle page unload - cleanup polling */ window.addEventListener('beforeunload', function() { stopLogPolling(); if (simulationAbortController) { simulationAbortController.abort(); } });