// RMScript App - Frontend Application // Connects to the settings_app backend for compilation and server-side execution // API is served from same origin (settings_app) const API_BASE = ''; // Global state const state = { currentIR: null, isExecuting: false }; // DOM elements const elements = { editor: document.getElementById('editor'), compileStatus: document.getElementById('compileStatus'), robotStatus: document.getElementById('robotStatus'), console: document.getElementById('console'), irDisplay: document.getElementById('irDisplay'), executionInfo: document.getElementById('executionInfo'), executeBtn: document.getElementById('executeBtn') }; // Example scripts const examples = { basic: `look left wait 1s look right wait 1s look center`, complex: `look left antenna both up wait 1s look right antenna both down wait 0.5s look center`, repeat: `repeat 3 look left wait 0.5s look right wait 0.5s look center` }; // Update compile status UI function updateCompileStatus(status, message) { const dot = status === 'success' ? 'green' : status === 'error' ? 'red' : 'gray'; elements.compileStatus.className = `status ${status}`; elements.compileStatus.innerHTML = `${message}`; } // Log to console function log(message, type = 'info') { const line = document.createElement('div'); line.className = `console-line ${type}`; line.textContent = `[${new Date().toLocaleTimeString()}] ${message}`; elements.console.appendChild(line); elements.console.scrollTop = elements.console.scrollHeight; } // Clear console function clearConsole() { elements.console.innerHTML = ''; } // Load example script function loadExample(exampleName) { if (examples[exampleName]) { elements.editor.value = examples[exampleName]; log(`Loaded ${exampleName} example`, 'info'); } } // Clear editor function clearEditor() { elements.editor.value = ''; state.currentIR = null; elements.irDisplay.innerHTML = '
No IR yet. Verify a script first!
'; updateCompileStatus('idle', 'Ready'); } // Verify script (syntax and semantics only) async function verifyScript() { const source = elements.editor.value.trim(); if (!source) { log('Editor is empty', 'warning'); return; } clearConsole(); log('Verifying script...', 'info'); updateCompileStatus('idle', 'Verifying...'); try { const response = await fetch(`${API_BASE}/api/verify`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ source }) }); const result = await response.json(); if (result.success) { log('Verification successful!', 'success'); if (result.name) log(` Name: ${result.name}`, 'info'); if (result.description) log(` Description: ${result.description}`, 'info'); if (result.warnings.length > 0) { result.warnings.forEach(w => { log(` Warning (line ${w.line}): ${w.message}`, 'warning'); }); } updateCompileStatus('success', 'Valid script'); } else { log('Verification failed', 'error'); result.errors.forEach(e => { log(` Error (line ${e.line}): ${e.message}`, 'error'); }); updateCompileStatus('error', 'Invalid script'); } } catch (error) { log(`Backend error: ${error.message}`, 'error'); updateCompileStatus('error', 'Backend error'); } } // Compile script to IR async function compileScript() { const source = elements.editor.value.trim(); if (!source) { log('Editor is empty', 'warning'); return null; } log('Compiling script to IR...', 'info'); updateCompileStatus('idle', 'Compiling...'); try { const response = await fetch(`${API_BASE}/api/compile`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ source }) }); const result = await response.json(); if (result.success) { log(`Compiled ${result.ir.length} actions`, 'success'); if (result.warnings.length > 0) { result.warnings.forEach(w => { log(` Warning (line ${w.line}): ${w.message}`, 'warning'); }); } state.currentIR = result.ir; displayIR(result.ir); updateCompileStatus('success', `${result.ir.length} actions ready`); return result.ir; } else { log('Compilation failed', 'error'); result.errors.forEach(e => { log(` Error (line ${e.line}): ${e.message}`, 'error'); }); updateCompileStatus('error', 'Compilation failed'); return null; } } catch (error) { log(`Backend error: ${error.message}`, 'error'); updateCompileStatus('error', 'Backend error'); return null; } } // Display IR in the UI function displayIR(ir) { if (!ir || ir.length === 0) { elements.irDisplay.innerHTML = '
No actions
'; return; } const html = ir.map((action, idx) => { let details = ''; if (action.type === 'action') { details = `Duration: ${action.duration}s`; if (action.head_pose) details += ', Head movement'; if (action.antennas) details += `, Antennas: [${action.antennas.map(a => a.toFixed(2)).join(', ')}]`; if (action.body_yaw !== null) details += `, Body yaw: ${action.body_yaw.toFixed(2)}`; } else if (action.type === 'wait') { details = `Wait ${action.duration}s`; } else if (action.type === 'picture') { details = 'Take picture'; } else if (action.type === 'sound') { details = `Play "${action.sound_name}"`; if (action.blocking) details += ' (blocking)'; if (action.loop) details += ' (loop)'; } return `
${idx + 1}. ${action.type.toUpperCase()}
${details}
`; }).join(''); elements.irDisplay.innerHTML = html; } // Execute script on robot (server-side via SDK) async function executeScript() { if (state.isExecuting) { log('Already executing a script', 'warning'); return; } // Compile the current editor content const ir = await compileScript(); if (!ir) { log('Cannot execute - compilation failed', 'error'); return; } // Execute via server-side SDK state.isExecuting = true; elements.executeBtn.disabled = true; clearExecutionInfo(); logExecution('Sending to robot...', 'info'); try { const response = await fetch(`${API_BASE}/api/execute`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ir }) }); const result = await response.json(); if (result.success) { logExecution(`Execution complete! ${result.actions_executed} actions executed.`, 'success'); } else { logExecution(`Execution failed: ${result.message}`, 'error'); } } catch (error) { logExecution(`Execution error: ${error.message}`, 'error'); } finally { state.isExecuting = false; elements.executeBtn.disabled = false; } } // Log execution info function logExecution(message, type = 'info') { const line = document.createElement('div'); line.className = `console-line ${type}`; line.textContent = `[${new Date().toLocaleTimeString()}] ${message}`; elements.executionInfo.appendChild(line); elements.executionInfo.scrollTop = elements.executionInfo.scrollHeight; } // Clear execution info function clearExecutionInfo() { elements.executionInfo.innerHTML = ''; } // Initialize application function init() { console.log('Initializing RMScript App'); loadExample('basic'); console.log('RMScript App ready'); } // Start when DOM is loaded if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); }