class PythonWebInterface { constructor() { this.ws = null this.currentSessionId = null this.isConnected = false this.isRunning = false this.initializeElements() this.setupEventListeners() this.connect() } initializeElements() { this.statusIndicator = document.getElementById('status-indicator') this.statusText = document.getElementById('status-text') this.output = document.getElementById('output') this.commandInput = document.getElementById('command-input') this.userInput = document.getElementById('user-input') this.userInputContainer = document.getElementById('user-input-container') this.runBtn = document.getElementById('run-btn') this.killBtn = document.getElementById('kill-btn') this.sendBtn = document.getElementById('send-btn') this.themeToggle = document.getElementById('theme-toggle') // Initialize theme this.initializeTheme() } setupEventListeners() { this.runBtn.addEventListener('click', () => this.runCommand()) this.killBtn.addEventListener('click', () => this.killSession()) this.sendBtn.addEventListener('click', () => this.sendInput()) this.themeToggle.addEventListener('click', () => this.toggleTheme()) this.commandInput.addEventListener('keypress', (e) => { if (e.key === 'Enter' && !this.isRunning) { this.runCommand() } }) this.userInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') { this.sendInput() } }) // Listen for system theme changes if (window.matchMedia) { const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)') mediaQuery.addEventListener('change', () => { if (!localStorage.getItem('theme-preference')) { this.updateTheme(mediaQuery.matches ? 'dark' : 'light') } }) } } connect() { const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:' const wsUrl = `${protocol}//${window.location.host}` this.ws = new WebSocket(wsUrl) this.ws.onopen = () => { this.isConnected = true this.updateStatus('Connected', true) this.addSystemMessage('Connected to server') } this.ws.onclose = () => { this.isConnected = false this.updateStatus('Disconnected', false) this.addSystemMessage('Disconnected from server. Attempting to reconnect...') // Attempt to reconnect after 3 seconds setTimeout(() => { if (!this.isConnected) { this.connect() } }, 3000) } this.ws.onerror = (error) => { this.addErrorMessage('WebSocket error occurred') } this.ws.onmessage = (event) => { this.handleMessage(JSON.parse(event.data)) } } handleMessage(data) { switch (data.type) { case 'command_started': this.isRunning = true this.currentSessionId = data.sessionId this.updateUIState() this.showUserInput() this.addSystemMessage(`Started: ${data.command}`) break case 'output': this.addOutput(data.data, data.stream === 'stderr') break case 'command_finished': this.isRunning = false this.currentSessionId = null this.updateUIState() this.hideUserInput() this.addSystemMessage(`Process finished with exit code: ${data.exitCode}`) break case 'error': this.addErrorMessage(data.message) this.isRunning = false this.currentSessionId = null this.updateUIState() this.hideUserInput() break } } runCommand() { const command = this.commandInput.value.trim() if (!command || !this.isConnected || this.isRunning) return this.addCommandMessage(command) const sessionId = 'session_' + Date.now() this.ws.send( JSON.stringify({ type: 'run_command', command: command, sessionId: sessionId }) ) this.commandInput.value = '' } sendInput() { const input = this.userInput.value if (!input || !this.currentSessionId) return this.addInputMessage(input) this.ws.send( JSON.stringify({ type: 'send_input', input: input, sessionId: this.currentSessionId }) ) this.userInput.value = '' } killSession() { if (this.currentSessionId) { this.ws.send( JSON.stringify({ type: 'kill_session', sessionId: this.currentSessionId }) ) } } updateStatus(text, connected) { this.statusText.textContent = text this.statusIndicator.className = connected ? 'status-connected' : 'status-disconnected' } updateUIState() { this.runBtn.disabled = !this.isConnected || this.isRunning this.killBtn.disabled = !this.isRunning this.commandInput.disabled = !this.isConnected || this.isRunning } showUserInput() { this.userInputContainer.style.display = 'flex' this.userInput.focus() } hideUserInput() { this.userInputContainer.style.display = 'none' } addMessage(content, className) { const messageDiv = document.createElement('div') messageDiv.className = `message ${className}` const timestamp = document.createElement('div') timestamp.className = 'timestamp' timestamp.textContent = new Date().toLocaleTimeString() const contentDiv = document.createElement('pre') contentDiv.textContent = content messageDiv.appendChild(timestamp) messageDiv.appendChild(contentDiv) this.output.appendChild(messageDiv) this.scrollToBottom() } addCommandMessage(command) { this.addMessage(`$ ${command}`, 'command') } addInputMessage(input) { this.addMessage(`> ${input}`, 'command') } addOutput(data, isError = false) { this.addMessage(data, isError ? 'error' : 'output') } addSystemMessage(message) { this.addMessage(message, 'system') } addErrorMessage(message) { this.addMessage(`Error: ${message}`, 'error') } scrollToBottom() { this.output.scrollTop = this.output.scrollHeight } // Theme Management - Cherry Studio Style initializeTheme() { const savedTheme = localStorage.getItem('theme-preference') const systemPrefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches let theme if (savedTheme) { theme = savedTheme } else { theme = systemPrefersDark ? 'dark' : 'light' } this.updateTheme(theme) } toggleTheme() { const currentTheme = document.documentElement.getAttribute('theme-mode') || 'dark' const newTheme = currentTheme === 'dark' ? 'light' : 'dark' localStorage.setItem('theme-preference', newTheme) this.updateTheme(newTheme) // Add feedback animation this.themeToggle.style.transform = 'scale(0.9)' setTimeout(() => { this.themeToggle.style.transform = 'scale(1)' }, 150) } updateTheme(theme) { document.documentElement.setAttribute('theme-mode', theme) // Update theme toggle icon if (this.themeToggle) { this.themeToggle.textContent = theme === 'dark' ? '☀️' : '🌙' this.themeToggle.setAttribute('aria-label', `Switch to ${theme === 'dark' ? 'light' : 'dark'} theme`) this.themeToggle.title = `Switch to ${theme === 'dark' ? 'light' : 'dark'} theme` } // Detect OS for header styling if (navigator.platform.includes('Mac')) { document.body.setAttribute('os', 'darwin') } } } // Initialize the interface when the page loads document.addEventListener('DOMContentLoaded', () => { new PythonWebInterface() })