Spaces:
Sleeping
Sleeping
Interior Env
Exposed live Physics engine logic to Frontend Visualizer and upgraded LLM agent prompt
a373fb7 Download static/app.js from 1akshith/interior-env: direct link, hf CLI and curl.
- Browser
- Download file 8.3 kB
-
https://huggingface.co/spaces/1akshith/interior-env/resolve/main/static/app.js
- Command line
-
hf download hf://spaces/1akshith/interior-env/static/app.js
-
curl -L -o app.js https://huggingface.co/spaces/1akshith/interior-env/resolve/main/static/app.js
8.3 kB
| const API_BASE = "/api/v1"; | |
| let currentSessionId = null; | |
| let currentTaskConfig = null; | |
| // DOM Elements | |
| const screenSelect = document.getElementById('selection-screen'); | |
| const screenSim = document.getElementById('simulation-screen'); | |
| const taskListEl = document.getElementById('task-list'); | |
| const invListEl = document.getElementById('inventory-list'); | |
| const logsEl = document.getElementById('logs'); | |
| const lblTask = document.getElementById('lbl-task'); | |
| const lblStep = document.getElementById('lbl-step'); | |
| const lblMax = document.getElementById('lbl-max'); | |
| const lblStatus = document.getElementById('lbl-status'); | |
| const btnSubmit = document.getElementById('btn-submit'); | |
| const modalSuccess = document.getElementById('modal-success'); | |
| const modalScore = document.getElementById('modal-score'); | |
| // Canvas | |
| const canvas = document.getElementById('blueprint'); | |
| const ctx = canvas.getContext('2d'); | |
| async function init() { | |
| const res = await fetch(`${API_BASE}/tasks`); | |
| const tasks = await res.json(); | |
| for (const [taskId, data] of Object.entries(tasks)) { | |
| const card = document.createElement('div'); | |
| card.className = 'task-card glass'; | |
| card.innerHTML = ` | |
| <h4>${taskId}</h4> | |
| <span class="badge">${data.difficulty} | ${data.room_size[0]}x${data.room_size[1]} Room</span> | |
| <p style="margin-top:0.8rem; color:var(--text-dim)">${data.description}</p> | |
| `; | |
| card.onclick = () => startSession(taskId, data); | |
| taskListEl.appendChild(card); | |
| } | |
| } | |
| async function startSession(taskId, taskData) { | |
| const res = await fetch(`${API_BASE}/session/start`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ task_id: taskId }) | |
| }); | |
| if (!res.ok) return alert("Failed to start server session."); | |
| const data = await res.json(); | |
| currentSessionId = data.session_id; | |
| currentTaskConfig = taskData; | |
| lblTask.textContent = taskId.toUpperCase(); | |
| screenSelect.classList.add('hidden'); | |
| screenSim.classList.remove('hidden'); | |
| renderState(data.observation); | |
| } | |
| async function executeStep() { | |
| if (!currentSessionId) return; | |
| const actionType = document.getElementById('action-type').value; | |
| const item = document.getElementById('action-item').value; | |
| const x = parseInt(document.getElementById('action-x').value) || 0; | |
| const y = parseInt(document.getElementById('action-y').value) || 0; | |
| const rot = parseInt(document.getElementById('action-rot').value) || 0; | |
| const res = await fetch(`${API_BASE}/session/step`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ | |
| session_id: currentSessionId, | |
| action_type: actionType, | |
| item: item, | |
| x: x, | |
| y: y, | |
| rotation: rot | |
| }) | |
| }); | |
| if (!res.ok) return alert("API failed."); | |
| const data = await res.json(); | |
| renderState(data.observation); | |
| if (data.done) { | |
| if (data.observation.placed_items && Object.keys(data.observation.unplaced_items).length === 0 && data.observation.feedback_logs.some(l => l.includes("SUCCESS"))) { | |
| setTimeout(() => showSuccess(data.result), 600); | |
| } | |
| } | |
| } | |
| let currentObs = null; | |
| function renderState(obs) { | |
| currentObs = obs; | |
| lblStep.textContent = obs.step_count; | |
| lblMax.textContent = obs.max_steps; | |
| const isSuccess = obs.feedback_logs.some(l => l.includes("SUCCESS")); | |
| if (isSuccess) { | |
| lblStatus.textContent = "VALIDATED"; | |
| lblStatus.className = "status pass"; | |
| } else { | |
| lblStatus.textContent = "UNSTABLE"; | |
| lblStatus.className = "status fail"; | |
| } | |
| // Render Inventory list | |
| invListEl.innerHTML = ''; | |
| // Placed | |
| for (const [id, pi] of Object.entries(obs.placed_items)) { | |
| const li = document.createElement('li'); | |
| li.innerHTML = `<span>🛋️ ${id}</span> <span class="text-green">[${pi.x},${pi.y}] R:${pi.rotation}°</span>`; | |
| invListEl.appendChild(li); | |
| } | |
| // Unplaced | |
| obs.unplaced_items.forEach(u => { | |
| const li = document.createElement('li'); | |
| li.className = 'unplaced'; | |
| li.innerHTML = `<span>📦 ${u.id}</span> <span class="hl-red">Unplaced (${u.width}x${u.height})</span>`; | |
| invListEl.appendChild(li); | |
| }); | |
| // Console | |
| logsEl.innerHTML = ''; | |
| obs.feedback_logs.forEach(msg => { | |
| const div = document.createElement('div'); | |
| div.textContent = `> ${msg}`; | |
| if (msg.includes("SUCCESS")) div.className = "succ"; | |
| else if (msg.includes("ERROR") || msg.includes("CRITICAL") || msg.includes("Collision")) div.className = "err"; | |
| logsEl.appendChild(div); | |
| }); | |
| logsEl.scrollTop = logsEl.scrollHeight; | |
| drawCanvas(obs); | |
| } | |
| function drawCanvas(obs) { | |
| const w = obs.room_size[0]; | |
| const h = obs.room_size[1]; | |
| // Compute cell size | |
| const maxDim = Math.max(w, h); | |
| const cellSize = Math.floor(600 / maxDim); | |
| // Clear | |
| ctx.fillStyle = '#1e2233'; | |
| ctx.fillRect(0, 0, 600, 600); | |
| // Offset to center | |
| const ox = (600 - (w * cellSize)) / 2; | |
| const oy = (600 - (h * cellSize)) / 2; | |
| // Check toggles | |
| const showSolar = document.getElementById("toggleSolar")?.checked; | |
| const showThermal = document.getElementById("toggleThermal")?.checked; | |
| // Draw grid & Physics | |
| ctx.strokeStyle = 'rgba(255, 255, 255, 0.1)'; | |
| ctx.lineWidth = 1; | |
| for(let y=0; y<h; y++) { | |
| for(let x=0; x<w; x++) { | |
| // Draw Thermal Background | |
| if (showThermal && obs.thermal_grid) { | |
| const heat = obs.thermal_grid[y][x]; | |
| if (heat > 0) { | |
| ctx.fillStyle = `rgba(255, 50, 50, ${Math.min(0.6, heat / 5)})`; | |
| ctx.fillRect(ox + x*cellSize, oy + y*cellSize, cellSize, cellSize); | |
| } else if (heat < 0) { | |
| ctx.fillStyle = `rgba(50, 150, 255, ${Math.min(0.6, -heat / 3)})`; | |
| ctx.fillRect(ox + x*cellSize, oy + y*cellSize, cellSize, cellSize); | |
| } | |
| } | |
| // Draw Solar Background | |
| else if (showSolar && obs.solar_grid && obs.solar_grid[y][x]) { | |
| ctx.fillStyle = 'rgba(255, 235, 59, 0.15)'; | |
| ctx.fillRect(ox + x*cellSize, oy + y*cellSize, cellSize, cellSize); | |
| } | |
| // Draw Grid Lines | |
| ctx.strokeRect(ox + x*cellSize, oy + y*cellSize, cellSize, cellSize); | |
| } | |
| } | |
| // Draw Door | |
| const dx = obs.door_position[0]; | |
| const dy = obs.door_position[1]; | |
| ctx.fillStyle = 'var(--door)'; | |
| ctx.fillRect(ox + dx*cellSize, oy + dy*cellSize, cellSize, cellSize); | |
| ctx.fillStyle = '#000'; | |
| ctx.font = '14px sans-serif'; | |
| ctx.fillText("EXIT", ox + dx*cellSize + 5, oy + dy*cellSize + 20); | |
| // Filter placed items to get original dimensions | |
| const itemDefs = {}; | |
| if(currentTaskConfig) { | |
| currentTaskConfig.items.forEach(i => itemDefs[i.id] = i); | |
| } | |
| // Draw Furniture | |
| for (const [id, pi] of Object.entries(obs.placed_items)) { | |
| if(!itemDefs[id]) continue; | |
| const origW = itemDefs[id].width; | |
| const origH = itemDefs[id].height; | |
| const drawW = pi.rotation === 0 ? origW : origH; | |
| const drawH = pi.rotation === 0 ? origH : origW; | |
| ctx.fillStyle = 'rgba(59, 130, 246, 0.8)'; | |
| ctx.strokeStyle = '#2563eb'; | |
| ctx.lineWidth = 2; | |
| const rectX = ox + pi.x * cellSize; | |
| const rectY = oy + pi.y * cellSize; | |
| // Draw with slight padding | |
| ctx.fillRect(rectX + 2, rectY + 2, drawW * cellSize - 4, drawH * cellSize - 4); | |
| ctx.strokeRect(rectX + 2, rectY + 2, drawW * cellSize - 4, drawH * cellSize - 4); | |
| // Text | |
| ctx.fillStyle = '#fff'; | |
| ctx.font = 'bold 12px var(--mono)'; | |
| ctx.fillText(id, rectX + 10, rectY + 20); | |
| } | |
| } | |
| function showSuccess(result) { | |
| modalScore.textContent = `Score: ${result.score.toFixed(4)}`; | |
| modalSuccess.classList.remove('hidden'); | |
| } | |
| btnSubmit.onclick = executeStep; | |
| window.onload = init; | |