From fa3d4bd71ad06d8bc95b135bee05a4125734b3a0 Mon Sep 17 00:00:00 2001 From: digital-garbage <133010426+digital-garbage@users.noreply.github.com> Date: Thu, 5 Nov 2026 21:46:44 +0200 Subject: [PATCH] Veda predictor download row sits right under the predictor selector It was drawn at the bottom of the model page, far from the field it fills, and the selector's hint pointed at 'the row below'. The row now follows the selector directly and the hint no longer points anywhere. --- app/ui/features/settings/models.js | 7 +++---- app/ui/features/settings/models.test.js | 6 +++++- modules/loaders/diffusion_model/nodes.py | 3 +-- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/app/ui/features/settings/models.js b/app/ui/features/settings/models.js index 1ed71a86..4fb7f6b2 100644 --- a/app/ui/features/settings/models.js +++ b/app/ui/features/settings/models.js @@ -168,11 +168,10 @@ export const models = (app) => function mount() { : c.settingsRow.default({ label: w.name, hint: w.tooltip || "", control: widgetControl(w, value, setInput(slot, w.name)) }); const appOwned = (slot.roles || []).some((r) => r.input === w.name && r.at !== "project.video"); // the app writes it: wiring it elsewhere would make every Generate refuse const canTake = !appOwned && (w.type !== "COMBO" || (w.choices || []).length) && sourcesFor(slot, w.type, slotsNow(), specs).length; - return canTake ? [row, c.field.default({ label: "↳ or take it from", hint: "Another node's output can drive this value; one Primitive can drive several inputs.", control: wiring(slot, w.name, w.type, null, "A value (typed here)") })] : [row]; + const under = w.name === "veda_predictor" ? [vedaRow()] : []; // the download goes right under the file it fills + return canTake ? [row, ...under, c.field.default({ label: "↳ or take it from", hint: "Another node's output can drive this value; one Primitive can drive several inputs.", control: wiring(slot, w.name, w.type, null, "A value (typed here)") })] : [row, ...under]; }); - const veda = (spec.widgets || []).find((w) => w.name === "veda_predictor"); - const vedaOn = veda && !gone.has(veda.name) && showing(veda, slot, spec); - return [head, structure(slot), ...sockets, ...rows, ...(vedaOn ? [vedaRow()] : []), ...(!rows.length && !sockets.length ? [c.hint.default({ text: "Nothing to configure on this node." })] : [])]; + return [head, structure(slot), ...sockets, ...rows, ...(!rows.length && !sockets.length ? [c.hint.default({ text: "Nothing to configure on this node." })] : [])]; }; // One node as a card: what it is, the one or two values that matter, and whether it needs attention. diff --git a/app/ui/features/settings/models.test.js b/app/ui/features/settings/models.test.js index 17c24625..046afa15 100644 --- a/app/ui/features/settings/models.test.js +++ b/app/ui/features/settings/models.test.js @@ -145,7 +145,8 @@ test("a field that belongs to a module is drawn only while that module is here a test("the Veda predictor row appears only for method veda with no file, names the licence beside the buttons, and downloads only on a click", async () => { const widgets = [{ name: "sla", type: "BOOLEAN" }, { name: "sla_method", type: "COMBO", choices: ["sla", "veda"], shows: [{ input: "sla", when: [true] }] }, - { name: "veda_predictor", type: "COMBO", choices: ["auto"], shows: [{ input: "sla", when: [true] }, { input: "sla_method", when: ["veda"] }] }]; + { name: "veda_predictor", type: "COMBO", choices: ["auto"], shows: [{ input: "sla", when: [true] }, { input: "sla_method", when: ["veda"] }] }, + { name: "after_it", type: "INT", min: 0, max: 9, default: 1 }]; const state = { files: [], license: { name: "MiniMax H3 Community License", url: "https://huggingface.co/x/blob/main/LICENSE" }, models: [{ id: "t2va", title: "T2VA / FL2VA", hint: "text", file: "a.safetensors", size: 275415648, state: "absent", done: 0, error: "" }, { id: "r2va", title: "R2VA", hint: "refs", file: "b.safetensors", size: 275415752, state: "absent", done: 0, error: "" }] }; @@ -169,6 +170,9 @@ test("the Veda predictor row appears only for method veda with no file, names th page.node.remove(); page = await open({ sla: true, sla_method: "veda" }); assert.match(page.node.textContent, /none is there/); + const text = page.node.textContent; + assert.ok(text.indexOf("veda_predictor") < text.indexOf("none is there") && text.indexOf("none is there") < text.indexOf("after_it"), + "the download sits right under the predictor selector, before the next field, not at the bottom of the page"); assert.match(page.node.textContent, /By downloading you accept the MiniMax H3 Community License/); assert.match(page.node.textContent, /275 MB|263 MB/); assert.deepEqual(fetched, [], "looking never downloads"); diff --git a/modules/loaders/diffusion_model/nodes.py b/modules/loaders/diffusion_model/nodes.py index f4bdfadd..8d5443ce 100644 --- a/modules/loaders/diffusion_model/nodes.py +++ b/modules/loaders/diffusion_model/nodes.py @@ -197,8 +197,7 @@ class FunUIDiffusionModelLoader(io.ComfyNode): io.Combo.Input("veda_predictor", extra_dict=shows(("sla", [True]), ("sla_method", ["veda"])), options=veda_attention.predictor_choices(), default="auto", optional=True, tooltip="veda: the predictor file in ComfyUI/models/veda. auto takes the text-to-video one " - "when it is there, else the first. Use the R2VA file for reference-to-video runs. " - "Not there yet? The row below offers the download."), + "when it is there, else the first. Use the R2VA file for reference-to-video runs."), ], outputs=[ io.Model.Output(display_name="model"),