DesignGym / web /app.js
yashvyasop's picture
Upload folder using huggingface_hub
322a982 verified
// 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 = `
<td>${t.step}</td>
<td>${t.action}</td>
<td>${fmt(t.reward, 3)}</td>
<td class="${deltaCls}">${(t.delta_score >= 0 ? "+" : "") + fmt(t.delta_score, 3)}</td>
<td>${fmt(t.score, 3)}</td>
<td>${(t.worst_metrics || []).slice(0, 2).join(", ") || "—"}</td>
<td>${status}</td>
`;
logBody.appendChild(tr);
}
logCount.textContent = trajectory.length
? ${trajectory.length} step${trajectory.length === 1 ? "" : "s"}`
: "— no steps yet";
}
function setRawSummary(payload) {
rawSummary.textContent = JSON.stringify(payload, null, 2);
}
async function fetchPolicies() {
try {
const res = await fetch("/demo/policies");
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();
llmActive = !!data.llm_active;
} catch (err) {
llmActive = false;
}
}
async function resetEnv() {
setStatus("Resetting environment…", "running");
try {
const res = await fetch("/demo/reset", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ task_id: taskSel.value, seed: 0 }),
});
if (!res.ok) throw new Error(`reset failed (${res.status})`);
const payload = await res.json();
drawState(payload.state);
lastTrajectory = [];
renderLog([]);
renderScores({ steps_taken: 0, total_reward: 0, valid_action_rate: 0 }, payload.state);
setStatus(`Ready: ${taskSel.value}. Click Run to start.`, "muted");
setRawSummary({ task_id: taskSel.value, brief: payload.observation?.brief });
} catch (err) {
setStatus(`Reset error: ${err.message}`, "error");
}
}
async function stepOnce() {
setStatus(`Stepping (policy=${selectedPolicy()})…`, "running");
try {
const res = await fetch("/demo/policy_step", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ policy: selectedPolicy() }),
});
if (res.status === 409) {
await resetEnv();
return stepOnce();
}
if (!res.ok) throw new Error(`step failed (${res.status})`);
const data = await res.json();
drawState(data.state);
lastTrajectory.push(data.step_record);
renderLog(lastTrajectory);
const liveSummary = {
final_score: data.state.current_score,
instruction_score: data.state.instruction_score,
steps_taken: lastTrajectory.length,
total_reward: lastTrajectory.reduce((a, r) => a + (r.reward || 0), 0),
valid_action_rate:
lastTrajectory.filter((r) => !r.error).length / lastTrajectory.length,
};
renderScores(liveSummary, data.state);
setRawSummary({ step_record: data.step_record, summary: liveSummary });
if (data.done) {
setStatus("Episode complete.", "success");
} else {
setStatus(`Step ${data.step_record.step} done. Click Step or Run.`, "muted");
}
} catch (err) {
setStatus(`Step error: ${err.message}`, "error");
}
}
async function runEpisode() {
const policy = selectedPolicy();
setStatus(`Running episode with ${policyLabel(policy)}… (this may take a few seconds for SFT)`, "running");
runBtn.disabled = true;
stepBtn.disabled = true;
resetBtn.disabled = true;
try {
const res = await fetch("/demo/run_episode", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ policy, task_id: taskSel.value, seed: 0 }),
});
if (!res.ok) {
const txt = await res.text();
throw new Error(`HTTP ${res.status}${txt.slice(0, 240)}`);
}
const data = await res.json();
if (data.error) throw new Error(data.error);
drawState(data.final_state);
lastTrajectory = data.trajectory || [];
renderLog(lastTrajectory);
renderScores(data.summary, data.final_state);
setRawSummary(data.summary);
const tag = data.summary.llm_available ? "live LLM" : "local-best fallback";
setStatus(
`Done in ${data.summary.wall_time_sec}s — ${data.summary.steps_taken} steps · final score ${fmt(data.summary.final_score, 3)} (${tag})`,
"success",
);
} catch (err) {
setStatus(`Run error: ${err.message}`, "error");
} finally {
runBtn.disabled = false;
stepBtn.disabled = false;
resetBtn.disabled = false;
}
}
runBtn.addEventListener("click", runEpisode);
stepBtn.addEventListener("click", stepOnce);
resetBtn.addEventListener("click", resetEnv);
taskSel.addEventListener("change", resetEnv);
if (scrollDetailsBtn) {
scrollDetailsBtn.addEventListener("click", () => {
const target = document.querySelector(".log-panel");
if (target) target.scrollIntoView({ behavior: "smooth", block: "start" });
});
}
(async function init() {
await fetchPolicies();
await resetEnv();
})();