Spaces:
Running
Running
Download static/app.js from codelion/data-inventor: direct link, hf CLI and curl.
- Browser
- Download file 25.7 kB
-
https://huggingface.co/spaces/codelion/data-inventor/resolve/main/static/app.js
- Command line
-
hf download hf://spaces/codelion/data-inventor/static/app.js
-
curl -L -o app.js https://huggingface.co/spaces/codelion/data-inventor/resolve/main/static/app.js
25.7 kB
| ; | |
| const STEPS = ["Format", "Domains", "Subdomains", "Languages", "Summary", "Review", "Run"]; | |
| const LANGS = ["English", "Spanish", "French", "German", "Portuguese", "Italian", "Dutch", "Hindi", "Bengali", "Arabic", "Turkish", "Russian", "Polish", "Ukrainian", | |
| "Chinese", "Japanese", "Korean", "Vietnamese", "Indonesian", "Thai", "Swahili", "Serbian", "Greek", "Hebrew"]; | |
| const FORMATS = { | |
| instruction: { name: "Instruction pairs", blurb: "A prompt and the reply you want a model to learn. Use it for supervised fine-tuning.", | |
| eg: [["Prompt", "My hardcover arrived with a bent corner. Can I swap it?"], ["Completion", "Sorry about that. Yes, send a photo of the corner and we will ship a replacement today, no need to return the damaged copy."]] }, | |
| preference: { name: "Preference pairs", blurb: "A prompt with a chosen and a rejected reply, ranked by the model. Use it for preference tuning.", | |
| eg: [["Prompt", "Where is order 8824? It was due Monday."], ["Chosen", "It left our warehouse Saturday and is with the courier. Tracking shows delivery Thursday."], ["Rejected", "Orders usually take a while. Please be patient."]] }, | |
| }; | |
| const S = { | |
| step: 0, maxStep: 0, format: "instruction", domains: [], subs: {}, languages: ["English"], request: "", constraints: "", rows: 50, | |
| blueprint: null, bpBusy: false, bpError: "", model: "", job: null, me: null, pub: { name: "", priv: true, license: "apache-2.0", ok: false, busy: false, err: "", url: "" }, jobRows: [], jobState: null, drawn: 0, printed: 0, pubShown: false, maxRows: 200, domainData: [], runError: "", | |
| }; | |
| const $ = (s, el = document) => el.querySelector(s); | |
| const esc = (t) => String(t ?? "").replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[c])); | |
| const sentence = (t) => (t ? t.charAt(0).toUpperCase() + t.slice(1) : t); | |
| async function api(path, body, method) { | |
| const r = await fetch(path, { method: method || (body ? "POST" : "GET"), headers: body ? { "Content-Type": "application/json" } : {}, body: body ? JSON.stringify(body) : undefined }); | |
| let data = null; try { data = await r.json(); } catch (e) { /* not json */ } | |
| if (!r.ok) throw new Error((data && (data.detail || data.message)) || `Request failed (${r.status})`); | |
| return data; | |
| } | |
| const spec = () => ({ format: S.format, request: S.request.trim(), domains: S.domains, subdomains: Object.values(S.subs).flat(), | |
| languages: S.languages, constraints: S.constraints, rows: S.rows, blueprint: S.blueprint }); | |
| /* ------------------------------------------------------------ chrome */ | |
| function renderLedger() { | |
| $("#steps").innerHTML = STEPS.map((n, i) => { | |
| const cls = i === S.step ? "on" : i < S.step ? "done" : ""; | |
| return `<li class="${cls}"><button data-go="${i}" ${i > S.maxStep ? "disabled" : ""} ${i === S.step ? 'aria-current="step"' : ""}><span class="n">${i + 1}</span><span>${n}</span></button></li>`; | |
| }).join(""); | |
| } | |
| function estimate() { | |
| const per = S.format === "preference" ? 4.4 : 2.5, lat = 5500, workers = 4; | |
| const calls = Math.round(S.rows * per + 2); | |
| const secs = Math.max(10, Math.round((calls * lat) / 1000 / workers)); | |
| const t = secs >= 3600 ? `${(secs / 3600).toFixed(1)} hours` : secs >= 90 ? `${Math.round(secs / 60)} minutes` : `${secs} seconds`; | |
| return { calls, time: t }; | |
| } | |
| function renderSheet() { | |
| const e = estimate(), subs = Object.values(S.subs).flat(); | |
| const line = (k, v) => `<div class="line"><dt>${k}</dt><dd>${v}</dd></div>`; | |
| $("#sheet").innerHTML = `<h2>Your dataset</h2><dl> | |
| ${line("Format", esc(FORMATS[S.format].name))} | |
| ${line("Domains", S.domains.length ? esc(S.domains.join(", ")) : '<span class="hint">None chosen. The description will guide it.</span>')} | |
| ${line("Subdomains", subs.length ? esc(subs.slice(0, 6).join(", ")) + (subs.length > 6 ? ` and ${subs.length - 6} more` : "") : '<span class="hint">All</span>')} | |
| ${line("Languages", esc(S.languages.join(", ")))} | |
| ${line("Rows", S.rows.toLocaleString())} | |
| ${line("Model", esc(S.model || "Free model on Hugging Face"))} | |
| </dl><p class="est">About <b>${e.calls.toLocaleString()}</b> model calls and <b>${e.time}</b> on this setup.</p>`; | |
| } | |
| function go(i) { S.step = i; S.maxStep = Math.max(S.maxStep, i); render(); $("#panel").focus({ preventScroll: true }); window.scrollTo({ top: 0 }); } | |
| function actions(back, nextLabel, nextOk = true, extra = "") { | |
| return `<div class="actions">${back ? `<button class="btn" data-act="back">Back</button>` : ""}${nextLabel ? `<button class="btn go" data-act="next" ${nextOk ? "" : "disabled"}>${nextLabel}</button>` : ""}${extra}</div>`; | |
| } | |
| /* ------------------------------------------------------------ steps */ | |
| function stepFormat() { | |
| return `<h1>What kind of rows do you need?</h1> | |
| <p class="lede">Pick the shape of each example. You can describe the content in the steps that follow.</p> | |
| <div class="specimens">${Object.entries(FORMATS).map(([k, f]) => ` | |
| <button class="spec" data-format="${k}" aria-pressed="${S.format === k}"><b>${f.name}</b><span class="hint">${f.blurb}</span> | |
| <div class="eg">${f.eg.map(([l, t]) => `<p><u>${l}</u>${esc(t)}</p>`).join("")}</div></button>`).join("")}</div> | |
| ${actions(false, "Continue")}`; | |
| } | |
| function stepDomains() { | |
| return `<h2>Which areas should the data cover?</h2> | |
| <p class="lede">Choose any number, or skip this and describe the data yourself in the summary.</p> | |
| <ul class="choices">${S.domainData.map((d) => `<li><button class="choice" data-domain="${esc(d.name)}" aria-pressed="${S.domains.includes(d.name)}"><span class="box"></span><span>${esc(d.name)}</span></button></li>`).join("")}</ul> | |
| ${actions(true, "Continue")}`; | |
| } | |
| function stepSubs() { | |
| if (!S.domains.length) return `<h2>Subdomains</h2><p class="lede">No domains chosen, so there is nothing to narrow. Continue to the languages, or go back and pick a domain.</p>${actions(true, "Continue")}`; | |
| return `<h2>Narrow it down</h2><p class="lede">Pick the subdomains you care about. Leave a domain empty to cover all of it.</p> | |
| ${S.domains.map((name) => { const d = S.domainData.find((x) => x.name === name); const sel = S.subs[name] || []; | |
| return `<div class="sub-block"><h3>${esc(name)}</h3><ul class="choices">${d.subdomains.map((s) => `<li><button class="choice" data-sub="${esc(name)}|${esc(s)}" aria-pressed="${sel.includes(s)}"><span class="box"></span><span>${esc(sentence(s))}</span></button></li>`).join("")}</ul></div>`; }).join("")} | |
| ${actions(true, "Continue")}`; | |
| } | |
| function stepLangs() { | |
| return `<h2>Which languages?</h2><p class="lede">Rows are written directly in each language you choose, split evenly. English is the default.</p> | |
| <ul class="choices">${LANGS.map((l) => `<li><button class="choice" data-lang="${l}" aria-pressed="${S.languages.includes(l)}"><span class="box"></span><span>${l}</span></button></li>`).join("")}</ul> | |
| ${actions(true, "Continue", S.languages.length > 0)}`; | |
| } | |
| function bpView() { | |
| const bp = S.blueprint; if (!bp) return ""; | |
| return `<div class="bp"> | |
| <div class="bp-row"><b>Who answers</b><span>${esc(bp.assistant_role)}</span></div> | |
| <div class="bp-row"><b>Topics</b><div class="tree">${bp.topics.map((a) => `<div><strong>${esc(a.area)}</strong> <small>${a.subtopics.map((s) => esc(s.subtopic)).join(", ")}</small></div>`).join("")}</div></div> | |
| <div class="bp-row"><b>Who asks</b><ul>${bp.personas.map((p) => `<li>${esc(p)}</li>`).join("")}</ul></div> | |
| <div class="bp-row"><b>Question shapes</b><ul>${bp.formats.map((p) => `<li>${esc(p)}</li>`).join("")}</ul></div> | |
| </div>`; | |
| } | |
| function stepSummary() { | |
| const suggest = () => { const d = S.domains.length ? ` covering ${S.domains.join(", ").toLowerCase()}` : ""; return `A dataset of ${S.format === "preference" ? "prompts with a better and a worse reply" : "prompts and ideal replies"}${d}.`; }; | |
| return `<h2>Describe the dataset</h2> | |
| <p class="lede">Say what you want in your own words. The more specific you are about who asks and who answers, the better the rows.</p> | |
| <label class="f"><span>What should the dataset contain?</span> | |
| <textarea class="in" id="request" placeholder="Customer support conversations for a small online bookshop: order status, returns, damaged books and gift wrapping.">${esc(S.request)}</textarea></label> | |
| <p style="margin-top:8px"><button class="linkish" data-act="suggest">Draft a description from my choices</button> <span class="hint" data-suggest="${esc(suggest())}"></span></p> | |
| <label class="f"><span>Rules every row must follow <span class="hint">(optional)</span></span> | |
| <textarea class="in" id="constraints" style="min-height:76px" placeholder="Replies must be under 120 words. Use plain text, no markdown.">${esc(S.constraints)}</textarea></label> | |
| <div class="group"><h3>How many rows?</h3> | |
| <div class="seg" role="group" aria-label="Rows">${[25, 50, 100, 200].filter((n) => n <= S.maxRows).map((n) => `<button data-rows="${n}" aria-pressed="${S.rows === n}">${n.toLocaleString()}</button>`).join("")}</div> | |
| <p class="hint" style="margin-top:8px">Hosted runs are capped at ${S.maxRows.toLocaleString()} rows. Run it yourself for more.</p></div> | |
| <div class="group"><h3>Blueprint</h3> | |
| <p class="hint">A plan for coverage: topics, who asks, who answers, and the shapes questions take. Preview it before you spend model calls on rows.</p> | |
| <div class="actions" style="margin-top:12px"><button class="btn" data-act="blueprint" ${S.bpBusy || !S.request.trim() ? "disabled" : ""}>${S.bpBusy ? "Drafting the blueprint" : S.blueprint ? "Draft it again" : "Preview the blueprint"}</button></div> | |
| ${S.bpError ? `<p class="note bad">${esc(S.bpError)}</p>` : ""}${bpView()}</div> | |
| ${actions(true, "Continue", !!S.request.trim())}`; | |
| } | |
| function stepReview() { | |
| const e = estimate(), subs = Object.values(S.subs).flat(); | |
| const row = (k, v) => `<div class="bp-row"><b>${k}</b><span>${v}</span></div>`; | |
| return `<h2>Check it, then start writing</h2> | |
| <p class="lede">Rows are written by a free model on Hugging Face Inference Providers. You can leave this page open or come back to it while the run goes.</p> | |
| <div class="bp">${row("Format", esc(FORMATS[S.format].name))}${row("Description", esc(S.request))} | |
| ${row("Domains", S.domains.length ? esc(S.domains.join(", ")) : "Guided by the description")}${row("Subdomains", subs.length ? esc(subs.join(", ")) : "All")} | |
| ${row("Languages", esc(S.languages.join(", ")))}${S.constraints.trim() ? row("Rules", esc(S.constraints)) : ""}${row("Rows", S.rows.toLocaleString())} | |
| ${row("Blueprint", S.blueprint ? "Previewed. It will be used as shown." : "Drafted when the run starts.")}</div> | |
| <p class="note">About <b>${e.calls.toLocaleString()}</b> model calls and <b>${e.time}</b>. Rows that fail the quality checks are rewritten, so a run can take a little longer.</p> | |
| <div class="actions"><button class="btn" data-act="back">Back</button><button class="btn go" data-act="next">Start writing</button></div>`; | |
| } | |
| function publishHtml() { | |
| const j = S.jobState; if (!isFinal(j) || !j.count) return ""; | |
| const p = S.pub, me = S.me || {}; | |
| if (p.url) return `<div class="group"><h3>Published</h3><p class="note ok">Your dataset is on Hugging Face${p.priv ? " as a private dataset" : ""}. <a href="${esc(p.url)}" target="_blank" rel="noopener">Open it</a>.</p></div>`; | |
| const head = `<h3>Publish to your Hugging Face account</h3>`; | |
| if (!me.user) { | |
| if (!me.can_sign_in) return `<div class="group">${head}<p class="hint">Publishing works on the hosted app. Download the CSV or JSONL instead.</p></div>`; | |
| const embedded = window.top !== window.self, href = embedded && me.host ? `https://${me.host}/oauth/login?job=${j.id}` : `/oauth/login?job=${j.id}`; | |
| return `<div class="group">${head}<p class="hint" style="max-width:60ch">Sign in to save this dataset to your own account, with a card that says how it was made. DataInventor can only create datasets for you. It cannot see your other repos. It is private unless you choose public.</p> | |
| <div class="actions" style="margin-top:12px"><a class="btn go" href="${esc(href)}" ${embedded ? 'target="_blank" rel="noopener"' : ""} style="text-decoration:none">Sign in with Hugging Face</a></div>${embedded ? '<p class="hint" style="margin-top:8px">Sign-in opens in its own tab because browsers block it inside an embedded page.</p>' : ""}</div>`; | |
| } | |
| if (!p.name) p.name = "datainventor-" + (j.slug || "dataset"); | |
| return `<div class="group">${head}<p class="hint">Signed in as <b>${esc(me.user)}</b>. <button class="linkish" data-act="signout">Sign out</button></p> | |
| <div class="row2"><label class="f"><span>Dataset name</span><input class="in" id="pub-name" value="${esc(p.name)}" spellcheck="false"></label> | |
| <label class="f"><span>Who can see it</span><select class="in" id="pub-private"><option value="1" ${p.priv ? "selected" : ""}>Only me (private)</option><option value="0" ${p.priv ? "" : "selected"}>Everyone (public)</option></select></label></div> | |
| <label class="f" style="max-width:320px"><span>License</span><select class="in" id="pub-license">${["apache-2.0", "cc-by-4.0", "cc-by-sa-4.0", "mit", "cc0-1.0"].map((l) => `<option ${p.license === l ? "selected" : ""}>${l}</option>`).join("")}</select></label> | |
| <label class="check"><input type="checkbox" id="pub-ok" ${p.ok ? "checked" : ""}><span>I have the right to publish this data. I understand it was written by a model and has not been checked by a person.</span></label> | |
| ${p.err ? `<p class="note bad">${esc(p.err)}</p>` : ""} | |
| <div class="actions"><button class="btn go" data-act="publish" ${p.busy ? "disabled" : ""}>${p.busy ? "Publishing" : "Publish dataset"}</button></div></div>`; | |
| } | |
| async function loadMe() { try { S.me = await api("/api/me"); } catch (e) { S.me = { user: null, can_sign_in: false }; } const el = document.getElementById("run-publish"); if (el) el.innerHTML = publishHtml(); } | |
| async function doPublish() { | |
| S.pub.busy = true; S.pub.err = ""; refreshPublish(); | |
| try { const r = await api(`/api/jobs/${S.job}/publish`, { name: S.pub.name.trim(), private: S.pub.priv, license: S.pub.license, confirm: S.pub.ok }); S.pub.url = r.url; } catch (e) { S.pub.err = e.message; } | |
| S.pub.busy = false; refreshPublish(); | |
| } | |
| function refreshPublish() { const el = document.getElementById("run-publish"); if (el) el.innerHTML = publishHtml(); } | |
| async function restoreJob(id) { | |
| try { | |
| let since = 0, last = null; S.jobRows = []; | |
| for (let k = 0; k < 8; k++) { last = await api(`/api/jobs/${id}?since=${since}`); S.jobRows.push(...last.new_rows); since = S.jobRows.length; if (since >= last.count) break; } | |
| const c = last.config || {}; S.job = id; S.jobState = last; S.format = c.format || "instruction"; S.request = c.request || ""; S.domains = c.domains || []; S.languages = c.languages || ["English"]; S.constraints = c.constraints || ""; S.rows = c.rows || S.rows; | |
| S.maxStep = 6; S.step = 6; S.printed = S.jobRows.length ? S.jobRows[S.jobRows.length - 1].id : 0; render(); | |
| if (!isFinal(last)) poll(); | |
| } catch (e) { /* the job is gone; start fresh */ } | |
| } | |
| const isFinal = (j) => j && ["done", "stopped", "failed"].includes(j.state); | |
| function rowHtml(r) { | |
| const pref = S.format === "preference"; | |
| return `<div class="trow"><span class="id">${r.id}</span><div><div class="p">${esc(r.prompt)}</div><div class="meta">${esc(r.area)}, ${esc(r.persona)}${r.language && r.language !== "English" ? ", " + esc(r.language) : ""}</div> | |
| <details><summary>${pref ? "Read both replies" : "Read the reply"}</summary><div class="full">${pref ? "Chosen\n" + esc(r.chosen) + "\n\nRejected\n" + esc(r.rejected) : esc(r.completion)}</div></details></div></div>`; | |
| } | |
| function deckHtml() { | |
| const pref = S.format === "preference", latest = S.jobRows.slice(-3); | |
| const fresh = latest.length ? latest[latest.length - 1].id : 0; | |
| const out = latest.map((r, i) => `<div class="card ${i === latest.length - 1 && fresh > S.printed ? "print" : ""}" style="transform:translate(${i * 16}px, ${i * 14}px);z-index:${i}"><em>${esc(r.persona)}, ${esc(r.topic)}</em><div class="clip">${esc(r.prompt)}</div><div class="a clip">${esc(pref ? r.chosen : r.completion)}</div></div>`).join(""); | |
| S.printed = Math.max(S.printed, fresh); | |
| return out; | |
| } | |
| function headParts() { | |
| const j = S.jobState, n = j ? j.count : 0, t = j ? j.target || S.rows : S.rows, pct = t ? Math.min(100, Math.round((n / t) * 100)) : 0, fin = isFinal(j); | |
| const st = j && j.stats && j.stats.rows ? j.stats : null; | |
| return { | |
| h2: fin ? (j.state === "failed" ? "The run stopped" : "Your dataset is ready") : "Writing your dataset", | |
| count: `<span class="big">${n.toLocaleString()}</span><span class="of">of ${t.toLocaleString()} rows</span>`, pct, | |
| state: `${j ? esc(j.message) : "Starting"}${j && !fin ? ` Elapsed ${Math.round(j.elapsed)} s.` : ""}`, | |
| note: j && j.state === "failed" ? `<p class="note bad">${esc(j.message)}</p>` : j && j.state === "done" && j.count < j.target ? `<p class="note">${esc(j.message)}</p>` : "", | |
| actions: fin ? `${j.count ? `<a class="btn go" href="/api/jobs/${j.id}/download?format=csv" style="text-decoration:none">Download CSV</a><a class="btn" href="/api/jobs/${j.id}/download?format=jsonl" style="text-decoration:none">Download JSONL</a>` : ""}${j.count ? `<button class="btn" data-act="visualize">Visualize these rows</button>` : ""}<button class="btn" data-act="again">Start another</button>` : `<button class="btn" data-act="stop">Stop and keep what is written</button>`, | |
| stats: st ? `<div class="stats"><div><b>${st.rows.toLocaleString()}</b><span>rows</span></div><div><b>${st.distinct3_prompts}</b><span>distinct 3-grams in prompts</span></div><div><b>${st.avg_prompt_words}</b><span>words per prompt</span></div><div><b>${st.avg_answer_words}</b><span>words per reply</span></div></div>` : "", | |
| filtered: j && j.rejected && Object.keys(j.rejected).length ? `<p class="hint" style="margin-top:12px">Filtered out: ${Object.entries(j.rejected).map(([k, v]) => `${v} ${esc(k)}`).join(", ")}.</p>` : "", | |
| }; | |
| } | |
| function stepRun() { | |
| if (S.runError && !S.jobState) return `<h2>The run did not start</h2><p class="note bad">${esc(S.runError)}</p>${actions(true, "Try again")}`; | |
| const h = headParts(); S.drawn = S.jobRows.length; | |
| return `<div id="runroot"><h2 id="run-h2">${h.h2}</h2> | |
| <div class="count" id="run-count" aria-live="polite">${h.count}</div> | |
| <div class="bar" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="${h.pct}"><i id="run-bar" style="width:${h.pct}%"></i></div> | |
| <p class="state" id="run-state">${h.state}</p><div id="run-note">${h.note}</div> | |
| <div class="actions" id="run-actions">${h.actions}</div><div id="run-stats">${h.stats}</div><div id="run-filtered">${h.filtered}</div><div id="run-publish">${publishHtml()}</div> | |
| <div class="press" id="run-deck" aria-hidden="true">${deckHtml()}</div> | |
| <div class="table" id="run-rows">${S.jobRows.slice().reverse().map(rowHtml).join("")}</div></div>`; | |
| } | |
| /* patch the run view in place so open replies stay open and only a new card prints */ | |
| function updateRun() { | |
| const h = headParts(), set = (id, html) => { const el = document.getElementById(id); if (el && el.innerHTML !== html) el.innerHTML = html; }; | |
| set("run-h2", h.h2); set("run-count", h.count); set("run-state", h.state); set("run-note", h.note); set("run-actions", h.actions); set("run-stats", h.stats); set("run-filtered", h.filtered); | |
| if (isFinal(S.jobState) && !S.pubShown) { S.pubShown = true; loadMe(); } | |
| const bar = document.getElementById("run-bar"); if (bar) { bar.style.width = h.pct + "%"; bar.parentElement.setAttribute("aria-valuenow", h.pct); } | |
| const deck = document.getElementById("run-deck"); if (deck) deck.innerHTML = deckHtml(); | |
| const rows = document.getElementById("run-rows"); | |
| if (rows && S.jobRows.length > S.drawn) { rows.insertAdjacentHTML("afterbegin", S.jobRows.slice(S.drawn).reverse().map(rowHtml).join("")); S.drawn = S.jobRows.length; } | |
| } | |
| /* ------------------------------------------------------------ render + events */ | |
| function render() { | |
| if (window.DI_MODE !== "invent") return; | |
| renderLedger(); renderSheet(); | |
| const fn = [stepFormat, stepDomains, stepSubs, stepLangs, stepSummary, stepReview, stepRun][S.step]; | |
| const keep = document.activeElement && document.activeElement.id; | |
| $("#panel").innerHTML = fn(); | |
| if (keep) { const el = document.getElementById(keep); if (el) { el.focus(); if (el.setSelectionRange && el.type !== "number") { const l = el.value.length; try { el.setSelectionRange(l, l); } catch (e) { /* ok */ } } } } | |
| } | |
| document.addEventListener("click", async (ev) => { | |
| const t = ev.target.closest("button, a"); if (!t) return; | |
| if (t.dataset.go !== undefined) return go(+t.dataset.go); | |
| if (t.dataset.format) { S.format = t.dataset.format; S.blueprint = null; return render(); } | |
| if (t.dataset.domain) { const d = t.dataset.domain; S.domains = S.domains.includes(d) ? S.domains.filter((x) => x !== d) : [...S.domains, d]; if (!S.domains.includes(d)) delete S.subs[d]; S.blueprint = null; return render(); } | |
| if (t.dataset.sub) { const [d, s] = t.dataset.sub.split("|"); const cur = S.subs[d] || []; S.subs[d] = cur.includes(s) ? cur.filter((x) => x !== s) : [...cur, s]; S.blueprint = null; return render(); } | |
| if (t.dataset.lang) { const l = t.dataset.lang; S.languages = S.languages.includes(l) ? S.languages.filter((x) => x !== l) : [...S.languages, l]; S.blueprint = null; return render(); } | |
| if (t.dataset.rows) { S.rows = +t.dataset.rows; return render(); } | |
| const act = t.dataset.act; if (!act) return; | |
| if (act === "next") return void go(S.step + 1); | |
| if (act === "back") return go(Math.max(0, S.step - 1)); | |
| if (act === "suggest") { S.request = S.request.trim() ? S.request : $("[data-suggest]").dataset.suggest; return render(); } | |
| if (act === "blueprint") return doBlueprint(); | |
| if (act === "publish") return doPublish(); | |
| if (act === "signout") { await api("/api/signout", {}); S.me = { ...S.me, user: null }; return refreshPublish(); } | |
| if (act === "visualize") { if (S.job && window.DI_visualizeJob) window.DI_visualizeJob(S.job); return; } | |
| if (act === "stop") { if (S.job) await api(`/api/jobs/${S.job}/stop`, {}); return; } | |
| if (act === "again") { S.job = null; S.jobState = null; S.jobRows = []; S.drawn = 0; S.printed = 0; S.pubShown = false; S.pub = { name: "", priv: true, license: "apache-2.0", ok: false, busy: false, err: "", url: "" }; S.runError = ""; return go(4); } | |
| }); | |
| document.addEventListener("change", (ev) => { if (ev.target.id && ev.target.id.startsWith("pub-")) { const id = ev.target.id; if (id === "pub-private") S.pub.priv = ev.target.value === "1"; else if (id === "pub-license") S.pub.license = ev.target.value; else if (id === "pub-ok") S.pub.ok = ev.target.checked; } }); | |
| document.addEventListener("input", (ev) => { | |
| const id = ev.target.id; | |
| if (id === "pub-name") S.pub.name = ev.target.value; else if (id === "pub-private") S.pub.priv = ev.target.value === "1"; else if (id === "pub-license") S.pub.license = ev.target.value; else if (id === "pub-ok") S.pub.ok = ev.target.checked; | |
| if (id === "request") { S.request = ev.target.value; S.blueprint = null; const b = $('[data-act="next"]'); if (b) b.disabled = !S.request.trim(); const bp = $('[data-act="blueprint"]'); if (bp) bp.disabled = S.bpBusy || !S.request.trim(); } | |
| else if (id === "constraints") { S.constraints = ev.target.value; S.blueprint = null; } | |
| }); | |
| async function doBlueprint() { | |
| S.bpBusy = true; S.bpError = ""; render(); | |
| try { S.blueprint = await api("/api/blueprint", spec()); } catch (e) { S.bpError = e.message; } | |
| S.bpBusy = false; render(); | |
| } | |
| async function startRun() { | |
| S.runError = ""; S.jobRows = []; S.jobState = null; S.drawn = 0; S.printed = 0; S.pubShown = false; S.pub = { name: "", priv: true, license: "apache-2.0", ok: false, busy: false, err: "", url: "" }; | |
| try { const r = await api("/api/jobs", spec()); S.job = r.id; poll(); } catch (e) { S.runError = e.message; } | |
| render(); | |
| } | |
| async function poll() { | |
| const id = S.job; if (!id) return; | |
| try { | |
| const j = await api(`/api/jobs/${id}?since=${S.jobRows.length}`); | |
| if (id !== S.job) return; | |
| const wasFinal = isFinal(S.jobState); S.jobRows.push(...j.new_rows); S.jobState = j; | |
| if (S.step === 6) { if (document.getElementById("runroot") && !wasFinal) updateRun(); else render(); } | |
| if (!["done", "stopped", "failed"].includes(j.state) || j.count > S.jobRows.length) return void setTimeout(poll, j.count > S.jobRows.length ? 200 : 1500); | |
| if (j.stats && j.stats.rows) S.maxStep = 6; | |
| } catch (e) { console.error(e.stack); S.jobState = { state: "failed", message: e.message, count: S.jobRows.length, target: S.rows, elapsed: 0, id }; if (S.step === 6) render(); } | |
| } | |
| window.DI_renderInvent = () => render(); | |
| window.DI_lastJob = () => (S.job && isFinal(S.jobState) && S.jobState.count ? { id: S.job, n: S.jobState.count, request: S.request || "" } : null); | |
| /* "Start writing" on the review step starts the run */ | |
| const _go = go; | |
| go = function (i) { const wasReview = S.step === 5; _go(i); if (i === 6 && wasReview && !S.jobState) startRun(); return true; }; | |
| (async function init() { | |
| try { const d = await api("/api/defaults"); S.model = d.model; S.maxRows = d.max_rows; S.configured = d.configured; if (S.rows > S.maxRows) S.rows = S.maxRows; } catch (e) { /* offline */ } | |
| try { S.domainData = await api("/api/domains"); } catch (e) { S.domainData = []; } | |
| const q = new URLSearchParams(location.search), jid = q.get("job"); | |
| if (jid) { await loadMe(); await restoreJob(jid); history.replaceState(null, "", "/"); } else render(); | |
| })(); | |