neural-os / static /index.html
da03
.
e733937
raw
history blame
50.9 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>NeuralOS Demo</title>
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
body {
padding: 20px;
background-color: #f8f9fa;
}
.title-container {
text-align: center;
margin-bottom: 30px;
}
.canvas-container {
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 20px;
}
.canvas-wrapper {
position: relative;
border: 3px solid #007bff;
border-radius: 8px;
background-color: #f8f9fa;
margin-bottom: 15px;
}
.instruction-box {
position: absolute;
top: -40px;
left: 50%;
transform: translateX(-50%);
background-color: #007bff;
color: white;
padding: 5px 15px;
border-radius: 5px;
font-weight: bold;
white-space: nowrap;
}
#displayCanvas {
cursor: none;
display: block;
}
.controls {
margin-top: 20px;
display: flex;
gap: 15px;
align-items: center;
justify-content: center;
flex-wrap: wrap;
}
.control-button {
cursor: pointer;
}
.step-control {
display: flex;
align-items: center;
gap: 10px;
}
#samplingSteps {
width: 70px;
}
.footer {
margin-top: 30px;
text-align: center;
}
</style>
</head>
<body>
<div class="container">
<div class="title-container">
<h1 class="mb-2">NeuralOS: Towards Simulating Operating Systems<br>via Neural Generative Models</h1>
<p class="mb-3">
<a href="https://anonymous.4open.science/r/neural-os" target="_blank" class="text-decoration-none">
Project Code: anonymous.4open.science/r/neural-os
</a>
</p>
</div>
<div class="canvas-container">
<div id="timeoutWarning" class="alert alert-warning" style="display: none; margin-bottom: 15px;">
<strong>Connection Timeout Warning:</strong>
<span id="timeoutMessage">No user activity detected. Connection will be dropped in <span id="timeoutCountdown">10</span> seconds and page will refresh automatically.</span>
<button type="button" class="btn btn-sm btn-primary ms-2" onclick="resetTimeout()">Stay Connected</button>
</div>
<div class="canvas-wrapper">
<div class="instruction-box">Move your mouse inside to interact</div>
<canvas id="displayCanvas" width="512" height="384"></canvas>
</div>
<div class="controls">
<button id="resetButton" class="btn btn-primary control-button">Reset Simulation</button>
<div class="step-control">
<label for="samplingSteps" class="form-label mb-0">Sampling Steps:</label>
<input type="number" id="samplingSteps" class="form-control" min="1" max="100" value="32">
<button id="updateStepsButton" class="btn btn-secondary control-button">Update</button>
</div>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="useRnnToggle">
<label class="form-check-label" for="useRnnToggle">Use RNN</label>
</div>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="autoInputToggle" checked>
<label class="form-check-label" for="autoInputToggle" id="autoInputLabel">Auto Input</label>
</div>
</div>
</div>
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-body">
<h5 class="card-title">Instructions:</h5>
<ul class="mb-0">
<li>Move your mouse inside the blue box to interact with NeuralOS</li>
<li>Click (left-click or right-click) to perform click actions</li>
<li>Use your keyboard to type within the simulated environment</li>
<li>Adjust sampling steps to control quality/speed tradeoff</li>
<li>Toggle "Use RNN" to switch between RNN and diffusion mode</li>
<li>Toggle "Auto Input" to enable automatic frame generation (starts after 2s idle once you move mouse inside canvas, then runs every 0.5s)</li>
</ul>
</div>
</div>
</div>
</div>
</div>
<script>
const canvas = document.getElementById('displayCanvas');
const ctx = canvas.getContext('2d');
let socket;
let isConnected = false;
let reconnectAttempts = 0;
const MAX_RECONNECT_DELAY = 30000; // Maximum delay between reconnection attempts (30 seconds)
let connectionAttempted = false; // Flag to prevent multiple connection attempts
let isProcessing = false;
// Add flag to control trace visibility, default to false (hidden)
let showTrace = false;
// Function to draw the initial canvas state
function drawInitialCanvas() {
ctx.fillStyle = "#ffffff";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.font = "18px Arial";
ctx.fillStyle = "#666666";
ctx.textAlign = "center";
ctx.fillText("Move your mouse here to interact with NeuralOS", canvas.width/2, canvas.height/2 - 10);
ctx.fillText("The neural model will render in this area", canvas.width/2, canvas.height/2 + 20);
}
// Function to show connection status
function showConnectionStatus(message, subtitle = null) {
ctx.fillStyle = "#ffffff";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.font = "18px Arial";
ctx.fillStyle = "#666666";
ctx.textAlign = "center";
//ctx.fillText(message, canvas.width/2, canvas.height/2 - 10);
ctx.fillText(message, canvas.width/2, canvas.height/2);
if (subtitle) {
ctx.font = "14px Arial";
ctx.fillStyle = "#888888";
ctx.fillText(subtitle, canvas.width/2, canvas.height/2 + 15);
}
}
function connect() {
console.log("connect() function called - creating new WebSocket connection");
//const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const protocol = 'ws:';
socket = new WebSocket(`${protocol}//${window.location.host}/ws`);
socket.onopen = function(event) {
console.log("WebSocket connection established");
isConnected = true;
//connectionAttempted = false; // Reset flag for potential reconnections
//reconnectAttempts = 0;
// Show brief success message
//showConnectionStatus("Connected! Processing input...");
//setTimeout(() => {
// // Clear the message after 0.5 second
// if (isConnected) {
// ctx.clearRect(0, 0, canvas.width, canvas.height);
// }
//}, 500);
// Start auto-input mechanism
startAutoInput();
// Request current settings from server to sync UI
socket.send(JSON.stringify({
type: "get_settings"
}));
//startHeartbeat();
};
socket.onclose = function(event) {
console.log("WebSocket connection closed. Attempting to reconnect...");
console.log("Close event code:", event.code, "reason:", event.reason);
// Check if this was a timeout closure
if (event.code === 1000 && event.reason === "User inactivity timeout") {
console.log("Connection closed due to timeout - refreshing page");
stopAutoInput(); // Stop auto-input when connection is lost
stopTimeoutCountdown(); // Stop timeout countdown when connection is lost
//clearInterval(heartbeatInterval);
// Refresh the page to reconnect
window.location.reload();
return;
}
isConnected = false;
//connectionAttempted = false; // Reset flag to allow reconnection attempts
stopAutoInput(); // Stop auto-input when connection is lost
stopTimeoutCountdown(); // Stop timeout countdown when connection is lost
//clearInterval(heartbeatInterval);
//scheduleReconnection();
};
socket.onerror = function(error) {
console.error("WebSocket error:", error);
};
socket.onmessage = function (event) {
const data = JSON.parse(event.data);
if (data.type === "heartbeat_response") {
console.log("Heartbeat response received");
} else if (data.image) {
let img = new Image();
img.onload = function() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(img, 0, 0);
//isProcessing = false; // Reset the processing flag when we get a response
};
img.src = 'data:image/png;base64,' + data.image;
} else if (data.type === "reset_confirmed") {
console.log("Simulation reset confirmed by server");
// Clear the canvas and reset to initial state
drawInitialCanvas();
// Reset user interaction state
userHasInteracted = false;
lastSentPosition = null;
console.log("Reset user interaction state - waiting for user to move mouse inside canvas");
} else if (data.type === "rnn_updated") {
console.log(`USE_RNN setting updated to: ${data.use_rnn}`);
// Update the toggle to match the server state
document.getElementById('useRnnToggle').checked = data.use_rnn;
} else if (data.type === "settings") {
// Update UI elements to match server settings
console.log(`Received settings from server: SAMPLING_STEPS=${data.sampling_steps}, USE_RNN=${data.use_rnn}`);
document.getElementById('samplingSteps').value = data.sampling_steps;
document.getElementById('useRnnToggle').checked = data.use_rnn;
} else if (data.type === "timeout_warning") {
console.log(`Received timeout warning: ${data.timeout_in} seconds remaining`);
setTimeoutMessage(`No user activity detected. Connection will be dropped in <span id="timeoutCountdown">${data.timeout_in}</span> seconds and page will refresh automatically.`);
startTimeoutCountdown(data.timeout_in, false, 'idle'); // false = show stay connected button
} else if (data.type === "activity_reset") {
console.log("Server detected user activity, resetting timeout");
stopTimeoutCountdown('idle'); // Only clear idle timeout
// If there's still a session time limit active, update it
if (data.session_time_remaining && data.session_time_remaining > 0) {
console.log(`Updating session countdown: ${data.session_time_remaining} seconds remaining`);
setTimeoutMessage(`⏰ Other users waiting. Time remaining: <span id="timeoutCountdown">${Math.ceil(data.session_time_remaining)}</span> seconds.`);
startTimeoutCountdown(Math.ceil(data.session_time_remaining), true, 'session'); // true = hide stay connected button
}
} else if (data.type === "queue_update") {
console.log(`Queue update: Position ${data.position}/${data.total_waiting}, wait: ${data.maximum_wait_seconds.toFixed(1)} seconds`);
const waitSeconds = Math.ceil(data.maximum_wait_seconds);
// Disable canvas interaction while in queue
disableCanvasInteraction();
if (waitSeconds === 0) {
showConnectionStatus("Starting soon...");
stopQueueCountdown();
} else {
// Only restart countdown if not already running or if time changed significantly
if (!queueCountdownActive) {
console.log(`Starting queue countdown: ${waitSeconds} seconds`);
startQueueCountdown(waitSeconds);
} else if (waitSeconds < queueWaitTime - 10 || waitSeconds > queueWaitTime + 15) {
// Be responsive to decreases (users dropping out) and significant increases
console.log(`Updating queue countdown: ${queueWaitTime} -> ${waitSeconds} (significant change)`);
startQueueCountdown(waitSeconds);
} else if (Math.abs(queueWaitTime - waitSeconds) <= 3) {
// Small differences (1-3 seconds) - keep local countdown running
console.log(`Ignoring small change: ${queueWaitTime} vs ${waitSeconds} (keeping local countdown)`);
} else {
console.log(`Moderate change ignored: ${queueWaitTime} -> ${waitSeconds} (${Math.abs(queueWaitTime - waitSeconds)}s difference)`);
}
}
} else if (data.type === "session_start") {
console.log("Session started, clearing queue display");
// Stop queue countdown and clear the display
stopQueueCountdown();
// Enable canvas interaction when session starts
enableCanvasInteraction();
//ctx.clearRect(0, 0, canvas.width, canvas.height);
} else if (data.type === "session_warning") {
console.log(`Session time warning: ${data.time_remaining} seconds remaining`);
setTimeoutMessage(`Other users waiting. Time remaining: <span id="timeoutCountdown">${Math.ceil(data.time_remaining)}</span> seconds.`);
startTimeoutCountdown(Math.ceil(data.time_remaining), true, 'session'); // true = hide stay connected button
} else if (data.type === "idle_warning") {
console.log(`Idle warning: ${data.time_remaining} seconds until timeout`);
setTimeoutMessage(`No user activity detected. Connection will be dropped in <span id="timeoutCountdown">${Math.ceil(data.time_remaining)}</span> seconds and page will refresh automatically.`);
startTimeoutCountdown(Math.ceil(data.time_remaining), false, 'idle'); // false = show stay connected button
} else if (data.type === "grace_period") {
console.log(`Grace period: ${data.time_remaining} seconds remaining`);
setTimeoutMessage(`Other users waiting. Time remaining: <span id="timeoutCountdown">${Math.ceil(data.time_remaining)}</span> seconds.`);
startTimeoutCountdown(Math.ceil(data.time_remaining), true, 'grace'); // true = hide stay connected button
} else if (data.type === "time_limit_removed") {
console.log("Time limit removed - queue became empty");
stopTimeoutCountdown('session'); // Only clear session timeout
stopTimeoutCountdown('grace'); // Also clear grace period if active
} else if (data.type === "queue_limit_applied") {
console.log(`Queue limit applied, ${data.time_remaining} seconds remaining`);
setTimeoutMessage(`⏰ Other users waiting. Time remaining: <span id="timeoutCountdown">${Math.ceil(data.time_remaining)}</span> seconds.`);
startTimeoutCountdown(Math.ceil(data.time_remaining), true, 'session'); // true = hide stay connected button
} else if (data.type === "worker_failure") {
console.log("Worker failure detected, reconnecting...");
showConnectionStatus("🔄 GPU worker failed. Reconnecting to healthy worker...");
// Clear the canvas to show we're reconnecting
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#f0f0f0';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#666';
ctx.font = '20px Arial';
ctx.textAlign = 'center';
ctx.fillText('🔄 Reconnecting to healthy GPU...', canvas.width/2, canvas.height/2);
}
};
}
function scheduleReconnection() {
const delay = Math.min(1000 * Math.pow(2, reconnectAttempts), MAX_RECONNECT_DELAY);
console.log(`Scheduling reconnection in ${delay}ms`);
setTimeout(connect, delay);
reconnectAttempts++;
}
let heartbeatInterval;
function startHeartbeat() {
heartbeatInterval = setInterval(() => {
if (isConnected) {
try {
socket.send(JSON.stringify({ type: "heartbeat" }));
console.error("finished sending heartbeat");
} catch (error) {
console.error("Error sending heartbeat:", error);
}
}
}, 1000); // Send heartbeat every 15 seconds
}
// Don't connect immediately - wait for user interaction
// connect();
// Draw initial state on the canvas
drawInitialCanvas();
let lastSentPosition = null;
let lastSentTime = 0;
const SEND_INTERVAL = 10; // Send updates every 50ms
// Auto-input mechanism
let lastUserInputTime = 0;
let lastAutoInputTime = 0;
let autoInputInterval = null;
let autoInputActive = false; // Track if auto-input mode is active
const INITIAL_AUTO_INPUT_DELAY = 2000; // Start auto-input after 2 seconds of no user input
const AUTO_INPUT_INTERVAL = 500; // Send auto-input every 0.5 second once active
let autoInputEnabled = true; // Default to enabled
let userHasInteracted = false; // Track if user has moved mouse inside canvas
// Session state tracking
let sessionState = 'queued'; // 'queued', 'active', 'disconnected'
let canvasInteractionEnabled = false;
// Timeout countdown mechanism - support concurrent timeouts
let timeoutCountdownInterval = null;
let timeoutCountdown = 10;
let timeoutWarningActive = false;
// Track concurrent timeout systems
let currentTimeouts = {
idle: null, // { remaining: 10, hideButton: false }
session: null, // { remaining: 60, hideButton: true }
grace: null // { remaining: 5, hideButton: true }
};
// Queue waiting countdown mechanism
let queueCountdownInterval = null;
let queueWaitTime = 0;
let queueCountdownActive = false;
function startAutoInput() {
if (autoInputInterval) {
clearInterval(autoInputInterval);
}
autoInputInterval = setInterval(() => {
const currentTime = Date.now();
if (!autoInputEnabled || !lastSentPosition || !isConnected || socket.readyState !== WebSocket.OPEN || !userHasInteracted) {
return;
}
// Check if we should start auto-input mode
if (!autoInputActive && currentTime - lastUserInputTime >= INITIAL_AUTO_INPUT_DELAY) {
console.log("Starting auto-input mode (no user activity for 2 seconds)");
autoInputActive = true;
lastAutoInputTime = currentTime;
// Update UI to show auto-input is active
try {
const label = document.getElementById('autoInputLabel');
if (label) {
label.textContent = "Auto Input (Active)";
label.style.color = "#28a745";
}
} catch (error) {
console.error("Error updating auto-input UI:", error);
}
}
// Send auto-input if mode is active and enough time has passed
if (autoInputActive && currentTime - lastAutoInputTime >= AUTO_INPUT_INTERVAL) {
console.log("Sending auto-input (auto-input mode active)");
try {
socket.send(JSON.stringify({
"x": lastSentPosition.x,
"y": lastSentPosition.y,
"is_left_click": false,
"is_right_click": false,
"keys_down": [],
"keys_up": [],
"wheel_delta_x": 0,
"wheel_delta_y": 0,
"is_auto_input": true // Flag to identify auto-generated inputs
}));
lastAutoInputTime = currentTime;
} catch (error) {
console.error("Error sending auto-input:", error);
// Stop auto-input if there's an error (connection likely closed)
stopAutoInput();
}
}
}, 100); // Check every 100ms
}
function stopAutoInput() {
if (autoInputInterval) {
clearInterval(autoInputInterval);
autoInputInterval = null;
}
autoInputActive = false; // Reset auto-input mode
}
function updateLastUserInputTime() {
lastUserInputTime = Date.now();
// Reset auto-input mode when user provides input
if (autoInputActive) {
console.log("User activity detected, stopping auto-input mode");
autoInputActive = false;
// Reset UI indicator
try {
const label = document.getElementById('autoInputLabel');
if (label) {
label.textContent = "Auto Input";
label.style.color = "";
}
} catch (error) {
console.error("Error updating auto-input UI:", error);
}
}
}
function startTimeoutCountdown(initialTime = 10, hideStayConnectedButton = false, timeoutType = 'idle') {
// Update the specific timeout type
currentTimeouts[timeoutType] = {
remaining: initialTime,
hideButton: hideStayConnectedButton,
type: timeoutType
};
// Find the timeout that will expire first
let earliestTimeout = null;
let earliestTime = Infinity;
for (const [type, timeout] of Object.entries(currentTimeouts)) {
if (timeout && timeout.remaining < earliestTime) {
earliestTime = timeout.remaining;
earliestTimeout = timeout;
}
}
if (!earliestTimeout) {
stopTimeoutCountdown();
return;
}
// Stop existing countdown
if (timeoutCountdownInterval) {
clearInterval(timeoutCountdownInterval);
}
timeoutCountdown = earliestTimeout.remaining;
timeoutWarningActive = true;
// Show warning
const warning = document.getElementById('timeoutWarning');
if (warning) {
warning.style.display = 'block';
}
// Show/hide Stay Connected button based on the earliest timeout
const stayConnectedButton = warning.querySelector('button');
if (stayConnectedButton) {
stayConnectedButton.style.display = earliestTimeout.hideButton ? 'none' : 'inline-block';
}
// Update initial display
const countdownElement = document.getElementById('timeoutCountdown');
if (countdownElement) {
countdownElement.textContent = timeoutCountdown;
}
console.log(`Starting ${earliestTimeout.type} timeout countdown: ${timeoutCountdown} seconds`);
// Start countdown
timeoutCountdownInterval = setInterval(() => {
// Update all active timeouts
for (const [type, timeout] of Object.entries(currentTimeouts)) {
if (timeout) {
timeout.remaining--;
}
}
// Check if ANY timeout has reached 0 (expired)
let expiredTimeout = null;
for (const [type, timeout] of Object.entries(currentTimeouts)) {
if (timeout && timeout.remaining <= 0) {
expiredTimeout = { type, ...timeout };
break;
}
}
// If any timeout expired, trigger the appropriate action
if (expiredTimeout) {
console.log(`${expiredTimeout.type} timeout expired - refreshing page`);
// Remove the expired timeout from tracking
currentTimeouts[expiredTimeout.type] = null;
stopAutoInput();
stopTimeoutCountdown();
// All timeout types result in page refresh for now
window.location.reload();
return;
}
// Find the timeout that will expire first among remaining timeouts
let newEarliestTimeout = null;
let newEarliestTime = Infinity;
for (const [type, timeout] of Object.entries(currentTimeouts)) {
if (timeout && timeout.remaining > 0 && timeout.remaining < newEarliestTime) {
newEarliestTime = timeout.remaining;
newEarliestTimeout = timeout;
}
}
// If no active timeouts remain, hide the warning
if (!newEarliestTimeout) {
stopTimeoutCountdown();
return;
}
// Update display with the earliest timeout
timeoutCountdown = newEarliestTimeout.remaining;
const countdownElement = document.getElementById('timeoutCountdown');
if (countdownElement) {
countdownElement.textContent = timeoutCountdown;
}
// Update Stay Connected button visibility if the timeout type changed
if (newEarliestTimeout.type !== earliestTimeout.type) {
console.log(`Timeout type changed from ${earliestTimeout.type} to ${newEarliestTimeout.type}`);
const stayConnectedButton = warning.querySelector('button');
if (stayConnectedButton) {
stayConnectedButton.style.display = newEarliestTimeout.hideButton ? 'none' : 'inline-block';
}
// Update timeout message based on new timeout type
if (newEarliestTimeout.type === 'idle') {
setTimeoutMessage(`No user activity detected. Connection will be dropped in <span id="timeoutCountdown">${newEarliestTimeout.remaining}</span> seconds and page will refresh automatically.`);
} else if (newEarliestTimeout.type === 'session' || newEarliestTimeout.type === 'grace') {
setTimeoutMessage(`⏰ Other users waiting. Time remaining: <span id="timeoutCountdown">${newEarliestTimeout.remaining}</span> seconds.`);
}
earliestTimeout = newEarliestTimeout;
}
}, 1000);
}
function stopTimeoutCountdown(timeoutType = null) {
if (timeoutType) {
// Clear specific timeout type
currentTimeouts[timeoutType] = null;
console.log(`Cleared ${timeoutType} timeout`);
// Check if any other timeouts are still active
let hasActiveTimeouts = false;
for (const [type, timeout] of Object.entries(currentTimeouts)) {
if (timeout && timeout.remaining > 0) {
hasActiveTimeouts = true;
break;
}
}
if (hasActiveTimeouts) {
// Restart countdown with remaining timeouts
// Find the earliest remaining timeout and restart with it
let earliestTimeout = null;
let earliestTime = Infinity;
for (const [type, timeout] of Object.entries(currentTimeouts)) {
if (timeout && timeout.remaining < earliestTime) {
earliestTime = timeout.remaining;
earliestTimeout = timeout;
}
}
if (earliestTimeout) {
startTimeoutCountdown(earliestTimeout.remaining, earliestTimeout.hideButton, earliestTimeout.type);
}
return;
}
} else {
// Clear all timeouts
currentTimeouts = {
idle: null,
session: null,
grace: null
};
console.log("Cleared all timeouts");
}
if (timeoutCountdownInterval) {
clearInterval(timeoutCountdownInterval);
timeoutCountdownInterval = null;
}
timeoutWarningActive = false;
timeoutCountdown = 10;
// Reset the countdown display to 10 seconds
const countdownElement = document.getElementById('timeoutCountdown');
if (countdownElement) {
countdownElement.textContent = timeoutCountdown;
}
// Hide warning
const warning = document.getElementById('timeoutWarning');
if (warning) {
warning.style.display = 'none';
}
}
function startQueueCountdown(initialWaitTime) {
if (queueCountdownInterval) {
clearInterval(queueCountdownInterval);
}
queueWaitTime = initialWaitTime;
queueCountdownActive = true;
// Show initial countdown
updateQueueCountdownDisplay();
// Start countdown
queueCountdownInterval = setInterval(() => {
queueWaitTime--;
updateQueueCountdownDisplay();
if (queueWaitTime <= 0) {
stopQueueCountdown();
showConnectionStatus("Starting soon...");
}
}, 1000);
}
function stopQueueCountdown() {
if (queueCountdownInterval) {
clearInterval(queueCountdownInterval);
queueCountdownInterval = null;
}
queueCountdownActive = false;
queueWaitTime = 0;
}
function enableCanvasInteraction() {
canvasInteractionEnabled = true;
sessionState = 'active';
// Remove visual queue indicator
if (canvas) {
canvas.style.opacity = '1';
canvas.style.cursor = 'crosshair';
canvas.style.pointerEvents = 'auto';
}
// Update status
const statusElement = document.getElementById('connectionStatus');
if (statusElement) {
statusElement.textContent = 'Active';
statusElement.className = 'connected';
}
}
function disableCanvasInteraction() {
canvasInteractionEnabled = false;
sessionState = 'queued';
// Add visual queue indicator
if (canvas) {
canvas.style.opacity = '0.5';
canvas.style.cursor = 'not-allowed';
canvas.style.pointerEvents = 'none';
}
// Update status
const statusElement = document.getElementById('connectionStatus');
if (statusElement) {
statusElement.textContent = 'Queued';
statusElement.className = 'connecting';
}
}
function updateQueueCountdownDisplay() {
if (queueWaitTime <= 0) {
showConnectionStatus("Starting soon...");
} else if (queueWaitTime === 1) {
showConnectionStatus("Max wait: 1 second");
} else {
showConnectionStatus(`Max wait: ${queueWaitTime} seconds`);
}
}
function setTimeoutMessage(message) {
const messageElement = document.getElementById('timeoutMessage');
if (messageElement) {
messageElement.innerHTML = message;
}
}
function resetTimeout() {
// Send a stay-connected ping to reset the server's timeout
if (socket && socket.readyState === WebSocket.OPEN && lastSentPosition) {
try {
socket.send(JSON.stringify({
"x": lastSentPosition.x,
"y": lastSentPosition.y,
"is_left_click": false,
"is_right_click": false,
"keys_down": [],
"keys_up": [],
"wheel_delta_x": 0,
"wheel_delta_y": 0,
"is_stay_connected": true // Mark this as a stay-connected ping
}));
updateLastUserInputTime(); // Update for auto-input mechanism
console.log("Stay connected ping sent to server");
} catch (error) {
console.error("Error sending stay connected ping:", error);
}
}
stopTimeoutCountdown('idle'); // Only clear idle timeout, not session timeout
}
function sendInputState(x, y, isLeftClick = false, isRightClick = false, keysDownArr = [], keysUpArr = [], wheelDeltaX = 0, wheelDeltaY = 0) {
const currentTime = Date.now();
if (isConnected && socket.readyState === WebSocket.OPEN && (isLeftClick || isRightClick || keysDownArr.length > 0 || keysUpArr.length > 0 || wheelDeltaX !== 0 || wheelDeltaY !== 0 || !lastSentPosition || currentTime - lastSentTime >= SEND_INTERVAL)) {
try {
socket.send(JSON.stringify({
"x": x,
"y": y,
"is_left_click": isLeftClick,
"is_right_click": isRightClick,
"keys_down": keysDownArr,
"keys_up": keysUpArr,
"wheel_delta_x": wheelDeltaX,
"wheel_delta_y": wheelDeltaY,
}));
lastSentPosition = { x, y };
lastSentTime = currentTime;
// Mark that user has interacted with the canvas
if (!userHasInteracted) {
userHasInteracted = true;
console.log("User has interacted with canvas for the first time");
}
// Update last user input time for auto-input mechanism
updateLastUserInputTime();
//if (isLeftClick || isRightClick) {
// isProcessing = true; // Block further inputs until response
//}
} catch (error) {
console.error("Error sending input state:", error);
}
}
}
// Capture mouse movements and clicks
canvas.addEventListener("mousemove", async function (event) {
// Establish connection on first mouse movement if not already connected
if (!isConnected && !connectionAttempted) {
console.log("First mouse movement detected - establishing WebSocket connection");
connectionAttempted = true;
showConnectionStatus("Connecting to NeuralOS...");
connect();
// Wait for connection to be established
let attempts = 0;
const maxAttempts = 50; // 5 seconds max wait
while (!isConnected && attempts < maxAttempts) {
await new Promise(resolve => setTimeout(resolve, 100)); // Wait 100ms
attempts++;
}
if (!isConnected) {
console.error("Failed to establish connection after 5 seconds");
connectionAttempted = false; // Reset flag to allow retry
showConnectionStatus("Connection failed. Move mouse to retry.");
return;
}
console.log("Connection established, processing first mouse movement");
}
if (!isConnected || isProcessing) return;
// Check if canvas interaction is enabled (not queued)
if (!canvasInteractionEnabled) {
console.log("Canvas interaction disabled - user is queued");
return;
}
let rect = canvas.getBoundingClientRect();
let x = event.clientX - rect.left;
let y = event.clientY - rect.top;
// Only draw the trace if showTrace is true
if (showTrace && lastSentPosition) {
ctx.beginPath();
ctx.moveTo(lastSentPosition.x, lastSentPosition.y);
ctx.lineTo(x, y);
ctx.stroke();
}
sendInputState(x, y);
});
canvas.addEventListener("click", function (event) {
if (!isConnected || isProcessing) return;
// Check if canvas interaction is enabled (not queued)
if (!canvasInteractionEnabled) {
console.log("Canvas interaction disabled - user is queued");
return;
}
let rect = canvas.getBoundingClientRect();
let x = event.clientX - rect.left;
let y = event.clientY - rect.top;
sendInputState(x, y, true, false);
});
// Handle right clicks
canvas.addEventListener("contextmenu", function (event) {
event.preventDefault(); // Prevent default context menu
if (!isConnected || isProcessing) return;
// Check if canvas interaction is enabled (not queued)
if (!canvasInteractionEnabled) {
console.log("Canvas interaction disabled - user is queued");
return;
}
let rect = canvas.getBoundingClientRect();
let x = event.clientX - rect.left;
let y = event.clientY - rect.top;
sendInputState(x, y, false, true);
});
// Handle mouse wheel events
canvas.addEventListener("wheel", function (event) {
event.preventDefault(); // Prevent page scrolling
if (!isConnected || isProcessing) return;
let rect = canvas.getBoundingClientRect();
let x = event.clientX - rect.left;
let y = event.clientY - rect.top;
// Normalize wheel delta values (different browsers handle this differently)
let deltaX = event.deltaX;
let deltaY = event.deltaY;
// Clamp values to reasonable range
//deltaX = Math.max(-10, Math.min(10, deltaX));
//deltaY = Math.max(-10, Math.min(10, deltaY));
console.log(`Wheel event: deltaX=${deltaX}, deltaY=${deltaY} at (${x}, ${y})`);
sendInputState(x, y, false, false, [], [], deltaX, deltaY);
});
// Track keyboard events
const TROUBLESOME = new Set([
"Tab", // focus change
" ", // Space - scrolls page
"Escape", // Esc
"ArrowUp", // scroll up
"ArrowDown", // scroll down
"ArrowLeft", // horiz. scroll or browser Back with Alt/⌥
"ArrowRight", // horiz. scroll or browser Forward with Alt/⌥
"Backspace", // navigate back in some browsers
"PageUp", // scroll
"PageDown", // scroll
"Home", // scroll to top
"End", // scroll to bottom
"Enter",
"Delete",
"/"
]);
function isForbiddenCombo(e) {
// Reload
if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "r") return true;
// Close tab
if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "w") return true;
return false;
}
document.addEventListener("keydown", function (event) {
// Only prevent default if user has interacted with the canvas
if (userHasInteracted && (TROUBLESOME.has(event.key) || isForbiddenCombo(event))) {
event.preventDefault();
event.stopPropagation();
}
if (!isConnected || isProcessing || !userHasInteracted) return;
// Check if canvas interaction is enabled (not queued)
if (!canvasInteractionEnabled) {
console.log("Canvas interaction disabled - user is queued");
return;
}
// Get the current mouse position
let rect = canvas.getBoundingClientRect();
let x = lastSentPosition ? lastSentPosition.x : canvas.width / 2;
let y = lastSentPosition ? lastSentPosition.y : canvas.height / 2;
sendInputState(x, y, false, false, [event.key], []);
});
document.addEventListener("keyup", function (event) {
// Only prevent default if user has interacted with the canvas
if (userHasInteracted && TROUBLESOME.has(event.key)) {
event.preventDefault();
event.stopPropagation();
}
if (!isConnected || socket.readyState !== WebSocket.OPEN || !userHasInteracted) return;
// Check if canvas interaction is enabled (not queued)
if (!canvasInteractionEnabled) {
console.log("Canvas interaction disabled - user is queued");
return;
}
// Get the current mouse position
let rect = canvas.getBoundingClientRect();
let x = lastSentPosition ? lastSentPosition.x : canvas.width / 2;
let y = lastSentPosition ? lastSentPosition.y : canvas.height / 2;
// For key up events, we send the key in the keys_up array
sendInputState(x, y, false, false, [], [event.key]);
});
// Graceful disconnection
window.addEventListener('beforeunload', function (e) {
stopAutoInput(); // Clean up auto-input interval
stopTimeoutCountdown(); // Clean up timeout countdown
stopQueueCountdown(); // Clean up queue countdown
if (isConnected) {
try {
//socket.send(JSON.stringify({ type: "disconnect" }));
//socket.close();
} catch (error) {
console.error("Error during disconnection:", error);
}
}
});
// Add event listener for the reset button
document.getElementById('resetButton').addEventListener('click', function() {
if (socket && socket.readyState === WebSocket.OPEN) {
console.log("Sending reset command to server");
socket.send(JSON.stringify({
type: "reset"
}));
} else {
console.error("WebSocket not connected, cannot reset");
}
});
// Add event listener for updating sampling steps
document.getElementById('updateStepsButton').addEventListener('click', function() {
const stepsInput = document.getElementById('samplingSteps');
const newSteps = parseInt(stepsInput.value, 10);
if (isNaN(newSteps) || newSteps < 1) {
alert("Please enter a valid number of steps (minimum 1)");
return;
}
if (socket && socket.readyState === WebSocket.OPEN) {
console.log(`Sending update to set sampling steps to ${newSteps}`);
socket.send(JSON.stringify({
type: "update_sampling_steps",
steps: newSteps
}));
} else {
console.error("WebSocket not connected, cannot update steps");
}
});
// Add event listener for the USE_RNN toggle
document.getElementById('useRnnToggle').addEventListener('change', function() {
const useRnn = this.checked;
if (socket && socket.readyState === WebSocket.OPEN) {
console.log(`Sending update to set USE_RNN to ${useRnn}`);
socket.send(JSON.stringify({
type: "update_use_rnn",
use_rnn: useRnn
}));
} else {
console.error("WebSocket not connected, cannot update USE_RNN");
}
});
// Add event listener for the auto-input toggle
document.getElementById('autoInputToggle').addEventListener('change', function() {
autoInputEnabled = this.checked;
console.log(`Auto-input ${autoInputEnabled ? 'enabled' : 'disabled'}`);
if (autoInputEnabled) {
// Reset the timers when enabling to start fresh
updateLastUserInputTime();
lastAutoInputTime = 0;
autoInputActive = false;
// Reset UI indicator
try {
const label = document.getElementById('autoInputLabel');
if (label) {
label.textContent = "Auto Input";
label.style.color = "";
}
} catch (error) {
console.error("Error updating auto-input UI:", error);
}
} else {
// Stop auto-input mode when disabling
autoInputActive = false;
// Reset UI indicator
try {
const label = document.getElementById('autoInputLabel');
if (label) {
label.textContent = "Auto Input";
label.style.color = "";
}
} catch (error) {
console.error("Error updating auto-input UI:", error);
}
}
});
// Initialize canvas in disabled state (user starts queued)
disableCanvasInteraction();
</script>
<!-- Bootstrap JS (optional) -->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>