Spaces:
Running
Running
File size: 6,722 Bytes
d6bfc8b | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 | // 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 => `<option value="${e.db_id}">${e.label}</option>`).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 => `<span class="chip" title="${esc(q)}">${esc(q)}</span>`).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(`<span class="badge">${r.elapsed_s}s</span>`);
if (r.self_corrected) b.push(`<span class="badge fix">🛠️ self-corrected (${r.attempts} tries)</span>`);
if (r.executed && !r.error) b.push(`<span class="badge good">✓ ran · ${r.row_count} row${r.row_count === 1 ? "" : "s"}</span>`);
if (r.executed && r.error) b.push(`<span class="badge warn">✕ execution error</span>`);
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 `<div class="trace-step">
<div class="tlabel">Attempt ${step.attempt}
<span class="tag ${ok ? "ok" : "fail"}">${ok ? "ran clean" : "crashed"}</span></div>
<pre class="code sql mono">${highlight(step.sql)}</pre>
${step.error ? `<div class="terr">↳ ${esc(step.error)}</div>` : ""}
</div>`;
}).join("");
traceBlock.classList.remove("hidden");
} else { traceBlock.classList.add("hidden"); }
// results / error
errorBlock.classList.add("hidden"); resultsBlock.classList.add("hidden");
if (r.executed && r.error) { showError("Execution error", r.error); }
else if (r.executed) { renderTable(r.columns, r.rows, r.row_count); }
}
function renderTable(cols, rows, n) {
if (!cols || !cols.length) { resultsBlock.classList.add("hidden"); return; }
resultsHead.textContent = `Query results — ${n} row${n === 1 ? "" : "s"}`;
const head = `<thead><tr>${cols.map(c => `<th>${esc(c)}</th>`).join("")}</tr></thead>`;
const body = `<tbody>${rows.map(row =>
`<tr>${row.map(c => `<td>${esc(c === null ? "NULL" : String(c))}</td>`).join("")}</tr>`).join("")}</tbody>`;
resultsTable.innerHTML = head + (rows.length ? body : `<tbody><tr><td colspan="${cols.length}" class="muted">(no rows returned)</td></tr></tbody>`);
resultsBlock.classList.remove("hidden");
}
function showError(title, msg) {
document.querySelector("#errorBlock .block-head span").textContent = title;
errorOut.textContent = msg; errorBlock.classList.remove("hidden");
}
// ---------- tiny SQL highlighter ----------
const KW = /\b(SELECT|FROM|WHERE|JOIN|LEFT|RIGHT|INNER|OUTER|ON|AS|AND|OR|NOT|IN|IS|NULL|GROUP|BY|ORDER|HAVING|LIMIT|OFFSET|DISTINCT|UNION|ALL|INTERSECT|EXCEPT|INSERT|INTO|VALUES|UPDATE|SET|DELETE|CREATE|TABLE|LIKE|BETWEEN|ASC|DESC|CASE|WHEN|THEN|ELSE|END|EXISTS)\b/gi;
const FN = /\b(COUNT|SUM|AVG|MIN|MAX|ROUND|ABS|LENGTH|LOWER|UPPER|SUBSTR|COALESCE|CAST)\b/gi;
function highlight(sql) {
let h = esc(sql || "");
h = h.replace(/'([^']*)'/g, '<span class="str">\'$1\'</span>');
h = h.replace(/\b(\d+(\.\d+)?)\b/g, '<span class="num">$1</span>');
h = h.replace(FN, (m) => `<span class="fn">${m}</span>`);
h = h.replace(KW, (m) => `<span class="kw">${m}</span>`);
return h;
}
function esc(s) { return String(s).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">"); }
// ---------- wire up ----------
dbSelect.onchange = onDbChange;
runBtn.onclick = run;
questionEl.addEventListener("keydown", (e) => { if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) run(); });
$("copyBtn").onclick = () => {
navigator.clipboard.writeText(sqlOut.textContent).then(() => {
const b = $("copyBtn"); b.textContent = "copied!"; setTimeout(() => b.textContent = "copy", 1200);
});
};
loadExamples();
pollHealth();
setInterval(pollHealth, 4000);
|