Spaces:
Running
Running
| // 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: `"Simple head movement" | |
| look left | |
| wait 1s | |
| look right | |
| wait 1s | |
| look center`, | |
| complex: `"Wave hello" | |
| look left | |
| antenna both up | |
| wait 1s | |
| look right | |
| antenna both down | |
| wait 0.5s | |
| look center`, | |
| repeat: `"Repeat demo" | |
| REPEAT 3 | |
| look left | |
| wait 0.5s | |
| look right | |
| wait 0.5s | |
| END | |
| 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 = `<span><span class="status-dot ${dot}"></span>${message}</span>`; | |
| } | |
| // 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 = '<div style="color: #999; text-align: center; padding: 20px;">No IR yet. Verify a script first!</div>'; | |
| 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 = '<div style="color: #999; text-align: center; padding: 20px;">No actions</div>'; | |
| 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 ` | |
| <div class="ir-action"> | |
| <div class="ir-action-type">${idx + 1}. ${action.type.toUpperCase()}</div> | |
| <div class="ir-action-details">${details}</div> | |
| </div> | |
| `; | |
| }).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(); | |
| } | |