From 51899bb26650ca6cc5b1ce09511ca8242f60046c Mon Sep 17 00:00:00 2001 From: Eric Wilson <5845477+WaitWut@users.noreply.github.com> Date: Thu, 9 Jul 2026 13:58:08 -0400 Subject: [PATCH] Add missing-file detection with click-to-replace Rows pointing at a lora that's no longer on disk (moved, renamed, or deleted) now render red with a warning marker instead of only logging to the console at generation time. The missing row's name is click-to-replace: clicking it reopens the folder browser to swap in a working file in place. Detection reuses the existing /tree endpoint - its full paths are flattened into a set of valid loras, and a row is flagged when its lora isn't in that set. Fails safe (flags nothing until the list is known / if the fetch fails) and tolerates path-separator differences across OSes. Documents the feature in the README with a screenshot and promotes it out of the roadmap. Co-Authored-By: Claude Opus 4.8 --- README.md | 6 ++- docs/screenshot-missing-file.png | Bin 0 -> 24281 bytes js/ultimate_lora_loader.js | 90 +++++++++++++++++++++++++++++-- 3 files changed, 92 insertions(+), 4 deletions(-) create mode 100644 docs/screenshot-missing-file.png diff --git a/README.md b/README.md index 7300e71..d70944d 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ The node in its default state, right after adding it to the canvas: - **Native quick-edit dialogs** — click any strength or priority number to get ComfyUI's own auto-selected value-entry dialog (matches the interaction rgthree's nodes use) - **Toggle-all / delete-all** — bulk controls in the header row, column-aligned with the per-row toggle and trash icons - **Auto-growing node** — the node resizes itself to fit your lora list; shrink it manually below that and the list scrolls internally instead of spilling out +- **Missing-file detection** — if a row points at a lora that's no longer on disk (moved, renamed, or deleted since the workflow was saved), the row is flagged in red with a ⚠ marker, and its name becomes click-to-replace so you can swap in a working file without rebuilding the row A populated stack — drag handle, priority number, toggle, name, strength stepper, and trash icon, all column-aligned with the header row's toggle-all and delete-all controls: @@ -33,6 +34,10 @@ Connect `clip` and every row gains an independent second stepper — the header ![Clip connected, showing the header split into Model and Clip columns with a second stepper per row](docs/screenshot-clip-optional.png) +If a row's lora file goes missing — moved, renamed, or deleted since the workflow was saved — that row is flagged in red with a ⚠ marker instead of silently failing at generation time. Click the highlighted name to reopen the folder browser and pick a replacement in place: + +![A lora stack with one row flagged red because its lora file is missing from disk](docs/screenshot-missing-file.png) + ## Install **Via git clone (recommended):** @@ -95,7 +100,6 @@ Planned features, roughly in the order they're likely to land: **Self-contained (no external services needed):** - Search/filter box in the Add Lora popup — type to filter instead of scrolling through folders - Duplicate detection — block re-adding a lora that's already in the stack, with a clear message, since there's no real use case for having the same file twice -- Missing-file detection — if a saved workflow references a lora that's been moved or deleted, highlight that row (rather than only printing to the console), and offer a way to search for it **Trigger words, suggested strength, and CivitAI metadata (one combined feature):** - A clickable info icon after the lora name, showing trigger words and CivitAI metadata for that lora diff --git a/docs/screenshot-missing-file.png b/docs/screenshot-missing-file.png new file mode 100644 index 0000000000000000000000000000000000000000..2d754ae6c80c2bb53e9653057c462cd09ae6b2f2 GIT binary patch literal 24281 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 false, so we fail safe toward +// "not missing" rather than lighting up every row red on a transient hiccup. +function isLoraMissing(loraName) { + if (!validLoraPaths || !loraName) return false; + return !validLoraPaths.has(_normLora(loraName)); +} + async function fetchLoraTree(force = false) { const now = Date.now(); if (!force && _treeCache && now - _treeCacheTime < TREE_CACHE_MS) { @@ -407,6 +462,7 @@ async function fetchLoraTree(force = false) { const data = await res.json(); _treeCache = data; _treeCacheTime = now; + _rebuildValidLoraPaths(data); return data; } catch (e) { console.error("[UltimateLoraLoader] Failed to fetch lora tree", e); @@ -571,7 +627,7 @@ function openLoraBrowser(x, y, onSelect) { // Node widget // --------------------------------------------------------------------------- -function makeRow(entry, { onChange, onRemove, showClipStrength, onDragStart, onDragOver, onDrop, onDragEnd, onPriorityChange, currentPriority }) { +function makeRow(entry, { onChange, onRemove, onReplace, showClipStrength, onDragStart, onDragOver, onDrop, onDragEnd, onPriorityChange, currentPriority }) { const row = document.createElement("div"); row.className = rowClassName(entry); // Row itself isn't draggable - only the dedicated drag handle icon @@ -660,8 +716,23 @@ function makeRow(entry, { onChange, onRemove, showClipStrength, onDragStart, onD const name = document.createElement("div"); name.className = "fll-name"; const shortName = entry.lora.split("/").pop(); - name.textContent = shortName; - name.title = entry.lora; + const missing = isLoraMissing(entry.lora); + name.textContent = missing ? "⚠ " + shortName : shortName; + if (missing) { + name.classList.add("fll-name-missing"); + name.title = `File not found on disk:\n${entry.lora}\n\nClick to choose a replacement.`; + // Click-to-replace: reopen the same folder browser and swap this row's + // lora in place. Cheap stand-in for the roadmap's "click-to-search" + // until a real search box exists. + name.onclick = (e) => { + e.stopPropagation(); + if (!onReplace) return; + const rect = name.getBoundingClientRect(); + openLoraBrowser(rect.left, rect.bottom + 4, (loraPath) => onReplace(loraPath)); + }; + } else { + name.title = entry.lora; + } const STRENGTH_STEP = 0.05; @@ -855,6 +926,7 @@ function rowClassName(entry) { let cls = "fll-row"; if (!entry.enabled) cls += " disabled"; if (entry.enabled) cls += " enabled-highlight"; + if (isLoraMissing(entry.lora)) cls += " missing"; return cls; } @@ -1074,6 +1146,11 @@ app.registerExtension({ render(); resizeNode(); }, + onReplace: (loraPath) => { + entry.lora = loraPath; + persist(); + render(); + }, showClipStrength, currentPriority: idx + 1, onPriorityChange: (rawPriority) => { @@ -1373,6 +1450,13 @@ app.registerExtension({ render(); syncWidth(); + // Fetch the on-disk lora list so missing-file highlighting can light up + // once we know what actually exists. The paint above happens first and + // flags nothing (validLoraPaths is null until this resolves); this + // re-render applies red highlighting to any rows pointing at files that + // are no longer present. + fetchLoraTree().then(() => render()); + // restore size sanity on load (also re-syncs width once ComfyUI has // finished laying out the node on the canvas, in case initial // node.size wasn't final yet at widget-creation time). Also retry