feat(enhance): pick LoRAs from ComfyUI per job (#220) #222

Open
cameron wants to merge 1 commits from feat/enhance-lora-catalog into master
pull from: feat/enhance-lora-catalog
Owner

Closes #220 on the ImageApi side. The app picker follows in FileViewer-React.

What changes

Slots in the workflow: LoRA loader nodes titled imageapi:lora-slot:<n>:high and :low, numbered 1.. without gaps, each slot with at least one high and one low node.

  • Picks fill the slots in order. Each pick sets the slot nodes' lora_name and scales their exported strength_model/strength_clip.
  • Unused slots run at 0 with their exported file, so the graph is never rewired.
  • A malformed slot (missing side, gap, bad role, no lora_name/strength) fails that template at startup.

Catalog (src/enhance/lora_catalog.rs):

  • Read live from ComfyUI's GET /object_info/LoraLoaderModelOnly (classic and COMBO shapes).
  • Cached 60 s. A submit naming an unknown id re-reads it once, so a new download is usable right away. An unreachable ComfyUI keeps the last list.
  • Files are paired by a High/Low name token: StyleHigh/StyleLow, x-HIGH/x-LOW, x_high_noise/x_low_noise.
    • A token counts at the start of the name, after a non-letter, or capitalised, so slow, Glowing and lower never pair.
    • Files in different folders never pair, and unpaired files aren't offered.
  • Excluded: every lora_name the workflow loads outside the slots (lightx2v and the switch LoRAs), plus [template.catalog] exclude substrings.
  • Ids and labels come from the file names, so they're stable, e.g. StyleHighWan2_2 becomes style-wan2-2 / "Style Wan2 2".

Config: [template.catalog] takes strength (default 1.0), min/max (0.0–1.5) and exclude, validated like the switches.

API:

  • GET /enhance/templates: each template gains slots: n and, when n > 0, catalog: {loras: [{id, label}], strength, min, max}.
  • POST /enhance: takes slots: [{lora, scale?}]. A missing scale means strength, <= 0 leaves the slot unused, other values are clamped. More picks than slots, duplicates, unknown ids, or picks on a template without slots are 400s.
  • Jobs: the resolved picks go in a new enhance_jobs.lora_slots column (JSON [{lora, label, scale}]; additive migration) and are returned as slots.

Docs: enhance.example.toml and the CLAUDE.md Enhance section.

Rolling out

  1. The 4090's Animate workflow needs the slot nodes, two pairs chained after the Bounce switch. The export's lora_name must be a real file.
  2. Restart ImageApi after the new export.

Until then nothing changes, since Animate has no slots. Once picks work, the Bounce switch is redundant: Bounce also shows up in the catalog.

Testing

  • Catalog: naming styles, unpaired and tokenless files, exclusions, folders, unique ids, both object_info shapes.
  • Templates: slot discovery and the fixed LoRA list; picks filled in order with an unused slot at 0; too many picks; malformed slots.
  • Config: [template.catalog] parsing, validation and resolution.
  • Integration (mock ComfyUI):
    • catalog listed only on the slot template, without lightx2v, the switch or unpaired files;
    • a pick filled into slot 1 with slot 2 at 0;
    • stored and returned on the job, with the default scale applied;
    • a newly added LoRA found by re-reading the list;
    • 400s for unknown, duplicate, too many and wrong template.
  • cargo test: 1093 lib tests pass, plus every integration binary. Clippy is clean.
  • Unrelated hang: twice today a full cargo test run hung in an integration binary, caught once in tests/otel_log_correlation. It passes alone and on master. I'll file it separately.

🤖 Generated with Claude Code

Closes #220 on the ImageApi side. The app picker follows in FileViewer-React. ## What changes **Slots in the workflow:** LoRA loader nodes titled `imageapi:lora-slot:<n>:high` and `:low`, numbered 1.. without gaps, each slot with at least one high and one low node. - Picks fill the slots in order. Each pick sets the slot nodes' `lora_name` and scales their exported `strength_model`/`strength_clip`. - Unused slots run at 0 with their exported file, so the graph is never rewired. - A malformed slot (missing side, gap, bad role, no `lora_name`/strength) fails that template at startup. **Catalog (`src/enhance/lora_catalog.rs`):** - Read live from ComfyUI's `GET /object_info/LoraLoaderModelOnly` (classic and COMBO shapes). - Cached 60 s. A submit naming an unknown id re-reads it once, so a new download is usable right away. An unreachable ComfyUI keeps the last list. - Files are paired by a High/Low name token: `StyleHigh`/`StyleLow`, `x-HIGH`/`x-LOW`, `x_high_noise`/`x_low_noise`. - A token counts at the start of the name, after a non-letter, or capitalised, so `slow`, `Glowing` and `lower` never pair. - Files in different folders never pair, and unpaired files aren't offered. - **Excluded:** every `lora_name` the workflow loads outside the slots (lightx2v and the switch LoRAs), plus `[template.catalog] exclude` substrings. - Ids and labels come from the file names, so they're stable, e.g. `StyleHighWan2_2` becomes `style-wan2-2` / "Style Wan2 2". **Config:** `[template.catalog]` takes `strength` (default 1.0), `min`/`max` (0.0–1.5) and `exclude`, validated like the switches. **API:** - `GET /enhance/templates`: each template gains `slots: n` and, when n > 0, `catalog: {loras: [{id, label}], strength, min, max}`. - `POST /enhance`: takes `slots: [{lora, scale?}]`. A missing scale means `strength`, <= 0 leaves the slot unused, other values are clamped. More picks than slots, duplicates, unknown ids, or picks on a template without slots are 400s. - **Jobs:** the resolved picks go in a new `enhance_jobs.lora_slots` column (JSON `[{lora, label, scale}]`; additive migration) and are returned as `slots`. **Docs:** `enhance.example.toml` and the CLAUDE.md Enhance section. ## Rolling out 1. The 4090's Animate workflow needs the slot nodes, two pairs chained after the Bounce switch. The export's `lora_name` must be a real file. 2. Restart ImageApi after the new export. Until then nothing changes, since Animate has no slots. Once picks work, the Bounce switch is redundant: Bounce also shows up in the catalog. ## Testing - **Catalog:** naming styles, unpaired and tokenless files, exclusions, folders, unique ids, both `object_info` shapes. - **Templates:** slot discovery and the fixed LoRA list; picks filled in order with an unused slot at 0; too many picks; malformed slots. - **Config:** `[template.catalog]` parsing, validation and resolution. - **Integration (mock ComfyUI):** - catalog listed only on the slot template, without lightx2v, the switch or unpaired files; - a pick filled into slot 1 with slot 2 at 0; - stored and returned on the job, with the default scale applied; - a newly added LoRA found by re-reading the list; - 400s for unknown, duplicate, too many and wrong template. - `cargo test`: 1093 lib tests pass, plus every integration binary. Clippy is clean. - **Unrelated hang:** twice today a full `cargo test` run hung in an integration binary, caught once in `tests/otel_log_correlation`. It passes alone and on master. I'll file it separately. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
cameron added 1 commit 2026-11-03 21:28:28 +00:00
Templates with `imageapi:lora-slot:<n>:high|low` nodes take LoRAs picked
per job from a catalog built live from ComfyUI.

- Catalog: ComfyUI's LoraLoaderModelOnly `lora_name` choices (cached
  60 s, re-read when a submit names an unknown id), paired by High/Low
  name tokens (StyleHigh/StyleLow, x-HIGH/x-LOW, x_high_noise/...). Files
  the workflow already loads outside the slots (lightx2v, switches) and
  `[template.catalog] exclude` substrings never show; ids derive from the
  names so they stay stable.
- POST /enhance `slots: [{lora, scale?}]` fills the slots in order: each
  pick sets the slot nodes' lora_name and scales their exported strength;
  unused slots run at 0, so the graph is never rewired. More picks than
  slots, duplicates or unknown ids are 400s.
- GET /enhance/templates gains `slots` and `catalog: {loras, strength,
  min, max}`; jobs store and return the picks (new enhance_jobs.lora_slots
  JSON column).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Some checks are pending
Core Repos/ImageApi/pipeline/pr-master
Required
Some required checks are missing.
You are not authorized to merge this pull request.
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin feat/enhance-lora-catalog:feat/enhance-lora-catalog
git checkout feat/enhance-lora-catalog
Sign in to join this conversation.