From dcb6d5cd49ac29b7ec8741d744c8bd7d4d482115 Mon Sep 17 00:00:00 2001 From: digital-garbage <133010426+digital-garbage@users.noreply.github.com> Date: Sat, 10 Oct 2026 15:58:31 +0300 Subject: [PATCH] feat: a warning beside Generate when SLA and ComfyUI's Model Sparse Attention node are both in the pipeline Co-Authored-By: Claude Haiku 5.5 --- app/ui/features/generate/warn.js | 15 +++++++++++++-- app/ui/features/generate/warn.test.js | 14 ++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/app/ui/features/generate/warn.js b/app/ui/features/generate/warn.js index 26bc05c8..c525cf5e 100644 --- a/app/ui/features/generate/warn.js +++ b/app/ui/features/generate/warn.js @@ -15,8 +15,18 @@ export default { const keyless = c.button.sm({ label: "⚠ No Taste key", tone: "ghost", title: "A feature that learns from ratings is on, but no Taste key is set: ratings teach nothing. Name one in Settings ▸ Engine ▸ System.", onClick: () => app.openSettings?.("engine") }).node; keyless.hidden = true; - host.append(chip, plain, keyless); + // Two sparse attentions at once: the pipeline's own SLA and a native Model Sparse Attention node. Both run on + // every block; the native one's dense fall-throughs print their own log lines, and nothing else says it is there. + const twice = c.button.sm({ label: "⚠ Two sparse attentions", tone: "ghost", disabled: true, + title: "SLA and ComfyUI's Model Sparse Attention node are both in this pipeline. Both sparsify attention; remove one." }).node; + twice.hidden = true; + host.append(chip, plain, keyless, twice); const draw = (slots) => { chip.hidden = !(slots && !slots.some((s) => (s.roles || []).some((r) => r.at === "generation.prompt"))); }; + const drawTwice = (slots) => { + const native = (slots || []).some((s) => /SparseAttention/.test(String(s.node || ""))); + const sla = (slots || []).some((s) => (s.inputs || {}).sla === true); + twice.hidden = !(native && sla); + }; const drawPlain = () => { const ps = app.pipeline, taste = ps.modulesById?.().taste; plain.hidden = !ps.allOff?.(); @@ -24,6 +34,7 @@ export default { }; drawPlain(); draw(app.pipeline.slots()); - return app.pipeline.subscribe ? app.pipeline.subscribe(() => { draw(app.pipeline.slots()); drawPlain(); }) : undefined; + drawTwice(app.pipeline.slots()); + return app.pipeline.subscribe ? app.pipeline.subscribe(() => { draw(app.pipeline.slots()); drawTwice(app.pipeline.slots()); drawPlain(); }) : undefined; }, }; diff --git a/app/ui/features/generate/warn.test.js b/app/ui/features/generate/warn.test.js index 7c949251..be70ee33 100644 --- a/app/ui/features/generate/warn.test.js +++ b/app/ui/features/generate/warn.test.js @@ -21,3 +21,17 @@ test("the No Taste key chip shows only while a learning feature is on and the ke values = { taste: { key: "" } }; learning = false; listener(); assert.equal(chip(host).hidden, true, "nothing learns, so a missing key changes nothing"); }); + +test("the two-sparse-attentions chip shows only when SLA and a native sparse node are both in the pipeline", () => { + let slots = [{ id: "loader", node: "FunPackLoader", inputs: { sla: true } }, { id: "sa", node: "BlockSparseAttention", inputs: {} }]; + let listener; + const pipeline = { slots: () => slots, allOff: () => false, modulesById: () => ({}), activeModules: () => [], useful: () => false, + currentValues: () => ({}), subscribe: (fn) => { listener = fn; return () => {}; } }; + const host = document.createElement("div"); + warn.setup({ host, app: { pipeline } }); + const twice = () => [...host.querySelectorAll("button")].find((b) => /Two sparse attentions/.test(b.textContent)); + assert.equal(twice().hidden, false, "both present: said"); + slots = [{ id: "loader", node: "FunPackLoader", inputs: { sla: false } }, { id: "sa", node: "BlockSparseAttention", inputs: {} }]; + listener(); + assert.equal(twice().hidden, true, "SLA off: the native node alone is not a clash"); +});