// SQLForge demo — frontend logic const $ = (id) => document.getElementById(id); const dbSelect = $("dbSelect"), questionEl = $("question"), examplesEl = $("examples"), schemaView = $("schemaView"), runBtn = $("runBtn"), placeholder = $("placeholder"), result = $("result"), sqlOut = $("sqlOut"), badges = $("badges"), traceBlock = $("traceBlock"), traceList = $("traceList"), resultsBlock = $("resultsBlock"), resultsHead = $("resultsHead"), resultsTable = $("resultsTable"), errorBlock = $("errorBlock"), errorOut = $("errorOut"); let EXAMPLES = [], modelReady = false; // ---------- model status ---------- async function pollHealth() { const el = $("status"), label = $("statusLabel"); try { const h = await (await fetch("/api/health")).json(); if (h.status === "online") { el.dataset.state = "online"; label.textContent = "model online"; modelReady = true; } else if (h.status === "loading") { el.dataset.state = "loading"; label.textContent = "loading model…"; modelReady = false; } else { el.dataset.state = "error"; label.textContent = "model error"; modelReady = false; } } catch { el.dataset.state = "offline"; label.textContent = "offline"; modelReady = false; } updateRunBtn(); } function updateRunBtn() { runBtn.disabled = !modelReady || runBtn.classList.contains("loading"); } // ---------- examples + schema ---------- async function loadExamples() { EXAMPLES = await (await fetch("/api/examples")).json(); dbSelect.innerHTML = EXAMPLES.map(e => ``).join(""); if (EXAMPLES.length) { await onDbChange(); } } async function onDbChange() { const ex = EXAMPLES.find(e => e.db_id === dbSelect.value); examplesEl.innerHTML = (ex?.questions || []) .map(q => `${esc(q)}`).join(""); examplesEl.querySelectorAll(".chip").forEach((c, i) => { c.onclick = () => { questionEl.value = ex.questions[i]; questionEl.focus(); }; }); try { const s = await (await fetch(`/api/schema?db_id=${encodeURIComponent(dbSelect.value)}`)).json(); schemaView.textContent = s.schema; } catch { schemaView.textContent = "—"; } } // ---------- generate ---------- async function run() { const question = questionEl.value.trim(); if (!question || !modelReady) return; runBtn.classList.add("loading"); updateRunBtn(); runBtn.querySelector(".btn-label").textContent = "Generating…"; try { const resp = await fetch("/api/generate", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ question, db_id: dbSelect.value, self_correct: true }), }); if (!resp.ok) { const e = await resp.json().catch(() => ({})); throw new Error(e.detail || resp.statusText); } render(await resp.json()); } catch (err) { placeholder.classList.add("hidden"); result.classList.remove("hidden"); showError("Request failed", err.message); } finally { runBtn.classList.remove("loading"); updateRunBtn(); runBtn.querySelector(".btn-label").textContent = "Generate SQL"; } } function render(r) { placeholder.classList.add("hidden"); result.classList.remove("hidden"); sqlOut.innerHTML = highlight(r.sql); // badges const b = []; b.push(`${r.elapsed_s}s`); if (r.self_corrected) b.push(`🛠️ self-corrected (${r.attempts} tries)`); if (r.executed && !r.error) b.push(`✓ ran · ${r.row_count} row${r.row_count === 1 ? "" : "s"}`); if (r.executed && r.error) b.push(`✕ execution error`); badges.innerHTML = b.join(""); // self-correction trace (only show if there was more than one attempt) if (r.trace && r.trace.length > 1) { traceList.innerHTML = r.trace.map(step => { const ok = !step.error; return `
${highlight(step.sql)}
${step.error ? `