fix(scan): read sampler-less graphs from the one wired text, retire the heuristic (#252) (#397)

* fix(scan): read sampler-less graphs from the one wired text, retire the heuristic (#252)

A graph where no node takes a positive or negative input fell back to the
pre-redesign _extract_readable_prompt heuristic: the last node with any string
text input won, and with none it read stale UI workflow widgets. It stored LoRA
syntax, display-node reports, install notes, model file names and numbers.

The scan now asks sampler_less_prompt: only the executed API graph counts; a
candidate is a node with a text input that another node consumes; blank and
numeric texts are never prompts; this repository's own positive nodes are
authoritative; one distinct text links, several (or an unreadable wired text
that no candidate builds) stay unlinked and try the recorded-run attribution.
The heuristic and its helpers are removed; A1111 files read the parsed
positive prompt directly.

* fix(scan): refuse loader side outputs, seeded templates and keep A1111 text (#252)

Review findings: an unreadable text link counts as built from a candidate
only through that node's first output, so a LoRA loader's trigger-word output
is unknown, not its text. A node with a seed input holds a template, never the
prompt that ran. A sampler-less graph the rule cannot decide still links the
A1111 positive a metadata saver wrote beside it, as the old scan did. Only
plain numbers are numeric; words such as nan or inf are prompts.

* fix(scan): linear plain-number check for sampler-less texts (#252)

The numeric pattern had overlapping quantifiers that backtrack quadratically
on a long digit run ending in a non-digit; it now has none.
This commit is contained in:
Vito
2026-10-28 00:32:30 +00:00
committed by GitHub
parent d11b354ed6
commit 44177974cb
7 changed files with 414 additions and 315 deletions
+13 -21
View File
@@ -846,6 +846,14 @@ class TestSettingsReadMonitorThroughModule(AdminAPITestCase):
self.assertEqual(data["settings"]["gallery_root_paths"], ["output"])
def _wired_prompt(text):
"""A sampler-less graph whose one encoder feeds a guider (#252)."""
return {
"1": {"class_type": "CLIPTextEncode", "inputs": {"text": text}},
"2": {"class_type": "BasicGuider", "inputs": {"conditioning": ["1", 0]}},
}
def _raise(*args, **kwargs):
raise RuntimeError("boom")
@@ -2186,11 +2194,7 @@ class TestOutputScanInternals(AdminAPITestCase):
def test_ingest_outcomes(self):
f = Path("gen.png")
meta = {
"prompt": json.dumps(
{"1": {"class_type": "CLIPTextEncode", "inputs": {"text": "a cat"}}}
)
}
meta = {"prompt": json.dumps(_wired_prompt("a cat"))}
self.assertIsNone(self.api._ingest_scanned_file(f, {}))
self.assertIsNone(self.api._ingest_scanned_file(f, {"prompt": "{}"}))
self.assertEqual(
@@ -2202,31 +2206,19 @@ class TestOutputScanInternals(AdminAPITestCase):
def test_ingest_link_failures_are_tolerated(self):
f = Path("gen.png")
meta = {
"prompt": json.dumps(
{"1": {"class_type": "CLIPTextEncode", "inputs": {"text": "a dog"}}}
)
}
meta = {"prompt": json.dumps(_wired_prompt("a dog"))}
self.api.db.link_image_to_prompt = _raise
self.assertEqual(self.api._ingest_scanned_file(f, meta), "added")
self.assertEqual(self.api._ingest_scanned_file(f, meta), "found")
def test_ingest_save_failure_counts_as_found(self):
f = Path("gen.png")
meta = {
"prompt": json.dumps(
{"1": {"class_type": "CLIPTextEncode", "inputs": {"text": "a bird"}}}
)
}
meta = {"prompt": json.dumps(_wired_prompt("a bird"))}
self.api.db.save_prompt = lambda *a, **k: None
self.assertEqual(self.api._ingest_scanned_file(f, meta), "found")
def test_batch_counts_and_per_file_errors(self):
meta = {
"prompt": json.dumps(
{"1": {"class_type": "CLIPTextEncode", "inputs": {"text": "a fish"}}}
)
}
meta = {"prompt": json.dumps(_wired_prompt("a fish"))}
self.api.db.get_prompt_by_hash = _raise
counts = self.api._ingest_scan_batch_sync(
[(Path("a.png"), meta), (Path("b.png"), {})]
@@ -2915,7 +2907,7 @@ class TestScanReadsVideos(AdminAPITestCase):
shutil.which("ffmpeg") and shutil.which("ffprobe"), "ffmpeg not installed"
)
async def test_scan_adds_prompts_found_in_videos(self):
graph = {"1": {"class_type": "CLIPTextEncode", "inputs": {"text": "a cat"}}}
graph = _wired_prompt("a cat")
_write_mp4(
self.output_dir / "clip.mp4", json.dumps({"prompt": json.dumps(graph)})
)