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 = `
${data.description}
`; 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 = `🛋️ ${id} [${pi.x},${pi.y}] R:${pi.rotation}°`; invListEl.appendChild(li); } // Unplaced obs.unplaced_items.forEach(u => { const li = document.createElement('li'); li.className = 'unplaced'; li.innerHTML = `📦 ${u.id} Unplaced (${u.width}x${u.height})`; 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