/**
* 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 = '
The simulator is running in a separate terminal window. You can monitor the progress there.
'; 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 += 'π‘ Monitoring Tips:
'; 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 += `