/* bridge.js — glue between htmx fragments and the Gradio-queued GPU endpoint. * * htmx handles the plain routes (/ui/spaces, /ui/check). The LLM review runs * behind Gradio's queue (ZeroGPU token handshake), which htmx can't speak — * so when a fresh #step-review block lands in the DOM we open a @gradio/client * stream and swap each yielded HTML chunk into #review-body. Pattern borrowed * from vivamais. */ import { Client } from "/static/vendor/gradio-client.min.js"; let clientPromise = null; function getClient() { if (!clientPromise) clientPromise = Client.connect(window.location.origin); return clientPromise; } async function streamReview(block) { const space = block.dataset.space; const model = block.dataset.model; block.dataset.state = "streaming"; try { const client = await getClient(); const job = client.submit("/review", [space, model]); for await (const msg of job) { if (msg.type === "data" && typeof msg.data?.[0] === "string") { htmx.swap("#review-body", msg.data[0], { swapStyle: "outerHTML" }); } } block.dataset.state = "done"; const title = document.getElementById("review-title"); if (title) title.textContent = title.textContent.replace(/ is reading.*/, "'s review"); } catch (err) { block.dataset.state = "error"; htmx.swap( "#review-body", `

` + `The reviewer model napped through this one (${String(err).slice(0, 120)}). ` + `The rule check above is still solid — try the review again in a moment.

`, { swapStyle: "outerHTML" }, ); } } /* Floating arrow that walks the user from step 1 down to step 2; it leaves * once the review block scrolls into view. */ function armNextStepCue() { const cue = document.getElementById("next-step-cue"); const review = document.getElementById("step-review"); if (!cue || !review) return; cue.addEventListener("click", () => review.scrollIntoView({ behavior: "smooth", block: "start" }), ); new IntersectionObserver( (entries, obs) => { if (entries.some((e) => e.isIntersecting)) { cue.remove(); obs.disconnect(); } }, { threshold: 0.35 }, ).observe(review); } /* Whenever htmx swaps in new content, look for a waiting review block. */ document.body.addEventListener("htmx:afterSwap", () => { const block = document.querySelector('#step-review[data-state="waiting"]'); if (block) { streamReview(block); armNextStepCue(); } }); /* Free-text space input wins over the dropdown. */ document.body.addEventListener("htmx:configRequest", (evt) => { if (evt.detail.elt?.id !== "check-form") return; const typed = (evt.detail.parameters.space_text || "").trim(); if (typed) evt.detail.parameters.space = typed; delete evt.detail.parameters.space_text; });