// DesignGym 2.0 — minimal demo client. // Talks only to the backend /demo/* endpoints; all policy logic lives server-side. const $ = (id) => document.getElementById(id); const taskSel = $("task"); const runBtn = $("run"); const stepBtn = $("step"); const resetBtn = $("reset"); const statusBox = $("status"); const scrollDetailsBtn = $("scroll-details"); const canvas = $("canvas"); const scoreFinal = $("score-final"); const scoreInstr = $("score-instr"); const scoreSteps = $("score-steps"); const scoreReward = $("score-reward"); const scoreValid = $("score-valid"); const logBody = $("log-body"); const logCount = $("log-count"); const rawSummary = $("raw-summary"); let llmActive = false; let lastTrajectory = []; window.addEventListener("error", (e) => setStatus(`JS error: ${e.message}`, "error")); window.addEventListener("unhandledrejection", (e) => setStatus(`Promise: ${e.reason}`, "error")); function setStatus(text, kind) { statusBox.textContent = text; statusBox.className = "status " + (kind || "muted"); } function selectedPolicy() { const r = document.querySelector('input[name="policy"]:checked'); return r ? r.value : "heuristic"; } function policyLabel(id) { return id === "sft" ? "SFT-LLM Picker" : "Heuristic Planner"; } function rectColor(role, type) { const key = role || type || "default"; const colors = { title: "#bfdbfe", subtitle: "#dbeafe", image: "#bbf7d0", cta: "#fecaca", logo: "#fde68a", badge: "#ddd6fe", body: "#e2e8f0", caption: "#fef3c7", shape: "#ddd6fe", masthead: "#fed7aa", headline: "#a7f3d0", default: "#e5e7eb" }; return colors[key] || colors.default; } function drawState(state) { canvas.innerHTML = ""; const bg = document.createElementNS("http://www.w3.org/2000/svg", "rect"); bg.setAttribute("x", 0); bg.setAttribute("y", 0); bg.setAttribute("width", 800); bg.setAttribute("height", 1000); bg.setAttribute("fill", "#f8fafc"); canvas.appendChild(bg); const elements = state?.elements || []; if (!elements.length) { const t = document.createElementNS("http://www.w3.org/2000/svg", "text"); t.setAttribute("x", 40); t.setAttribute("y", 60); t.setAttribute("fill", "#0f172a"); t.setAttribute("font-size", "22"); t.textContent = "Reset to load a layout."; canvas.appendChild(t); return; } for (const el of elements) { const b = el.bbox || el.box; if (!b || b.length < 4) continue; const x = Number(b[0]) * 800; const y = Number(b[1]) * 1000; const w = Number(b[2]) * 800; const h = Number(b[3]) * 1000; const r = document.createElementNS("http://www.w3.org/2000/svg", "rect"); r.setAttribute("x", x); r.setAttribute("y", y); r.setAttribute("width", w); r.setAttribute("height", h); r.setAttribute("rx", 8); r.setAttribute("fill", rectColor(el.role, el.type)); r.setAttribute("stroke", "#0f172a"); r.setAttribute("stroke-width", 2); canvas.appendChild(r); const label = document.createElementNS("http://www.w3.org/2000/svg", "text"); label.setAttribute("x", x + 8); label.setAttribute("y", y + 22); label.setAttribute("fill", "#0f172a"); label.setAttribute("font-size", "15"); label.setAttribute("font-family", "ui-monospace, monospace"); label.textContent = el.id || "element"; canvas.appendChild(label); const sub = document.createElementNS("http://www.w3.org/2000/svg", "text"); sub.setAttribute("x", x + 8); sub.setAttribute("y", y + 42); sub.setAttribute("fill", "#334155"); sub.setAttribute("font-size", "12"); sub.setAttribute("font-family", "ui-monospace, monospace"); sub.textContent = el.role || el.type || ""; canvas.appendChild(sub); } } function fmt(n, digits = 3) { if (n === null || n === undefined || Number.isNaN(Number(n))) return "—"; return Number(n).toFixed(digits); } function fmtPct(n) { if (n === null || n === undefined) return "—"; return `${(Number(n) * 100).toFixed(0)}%`; } function renderScores(summary, state) { const finalScore = summary?.final_score ?? state?.current_score; const instrScore = summary?.instruction_score ?? state?.instruction_score; scoreFinal.textContent = fmt(finalScore, 3); scoreInstr.textContent = fmt(instrScore, 3); scoreSteps.textContent = (summary?.steps_taken ?? state?.step_count ?? 0).toString(); scoreReward.textContent = fmt(summary?.total_reward ?? 0, 2); scoreValid.textContent = summary?.valid_action_rate !== undefined ? fmtPct(summary.valid_action_rate) : "—"; } function renderLog(trajectory) { logBody.innerHTML = ""; for (const t of trajectory) { const tr = document.createElement("tr"); if (t.error) tr.className = "error-row"; const deltaCls = t.delta_score > 0 ? "delta-pos" : (t.delta_score < 0 ? "delta-neg" : ""); const status = t.error ? `❌ ${t.error}` : (t.action_type === "finalize" ? "🏁 finalize" : "✓"); tr.innerHTML = `