Part of #218 (ImageApi side; the app switches and sliders follow in FileViewer-React).
What changes
enhance.toml:[[template.lora]]{ id, label, default, strength, min, max } per template.
Each id drives every workflow node titled imageapi:lora:<id>, so Wan's high/low pair shares one switch.
strength is the scale when on (default 1.0). min/max are the slider's range (default 0.0–1.5). Validation requires 0 <= min < max with strength between them.
The scale is a factor on each node's exported strength.
It multiplies strength_model, and strength_clip when present (LoraLoader).
So 1.0 means as exported and 0 means off.
A pair exported at different strengths keeps its ratio: 1.0/0.8 at scale 0.5 gives 0.5/0.4.
The graph is never rewired.
GET /enhance/templates: each template gains loras: [{id, label, default, strength, min, max}].
POST /enhance: takes loras: {id: true | false | scale}.
true means strength.
false, or a scale <= 0, means off.
Other scales are clamped to min..max.
A missing id uses its default.
An id the template doesn't offer is a 400.
Jobs: the resolved scale per id (0 = off) goes in a new enhance_jobs.loras column (JSON; additive migration). It's returned as loras: {id: scale} in the job view, so a retry or adjust repeats it exactly.
Startup checks:
A lora-titled node with no declaration keeps its exported strength.
A declaration with no matching node isn't offered.
Both log a startup warning.
A lora-titled node without a numeric strength_model fails that template.
Docs:enhance.example.toml gains an annotated Animate entry, and the CLAUDE.md Enhance section is updated.
Several LoRAs, and ordering
Several at once: a template can declare any number of switches, and one request can turn on and scale any combination, e.g. {style: 0.8, detail: 0.5}.
Ordering isn't exposed: LoRA patches add to the weights, so their order in the graph doesn't change the result.
Not covered: choosing an arbitrary LoRA file at request time. That needs nodes inserted dynamically. It would be a separate field later, so this shape stays valid.
Using it
In the 4090's Animate workflow, add a LoraLoaderModelOnly after lightx2v on the high path, and another on the low path.
Title both imageapi:lora:<id> and re-export the workflow in API format.
Add a [[template.lora]] with that id to the prod enhance.toml.
Until then nothing changes: Animate offers no switches.
Testing
Unit tests:
config parsing, ranges and validation;
request resolution (on/off/scale, clamping, <= 0 as off);
one switch driving both nodes;
a scale multiplying a pair with different exported strengths;
default on with a configured strength;
an unknown id refused;
strength_clip scaled;
undeclared and missing switches reported as warnings;
a node with no strength rejected.
Integration test: switches listed with their ranges, then on, default-off and 0.5-scale requests sent to ComfyUI. The resolved scales are stored on the job and returned by GET /enhance/jobs/{id}, plus the 400s. It uses a new fixture: the Animate workflow plus a generic imageapi:lora:style pair.
Part of #218 (ImageApi side; the app switches and sliders follow in FileViewer-React).
## What changes
**`enhance.toml`:** `[[template.lora]]` `{ id, label, default, strength, min, max }` per template.
- Each id drives every workflow node titled `imageapi:lora:<id>`, so Wan's high/low pair shares one switch.
- `strength` is the scale when on (default 1.0). `min`/`max` are the slider's range (default 0.0–1.5). Validation requires `0 <= min < max` with `strength` between them.
**The scale is a factor on each node's exported strength.**
- It multiplies `strength_model`, and `strength_clip` when present (LoraLoader).
- So 1.0 means as exported and 0 means off.
- A pair exported at different strengths keeps its ratio: 1.0/0.8 at scale 0.5 gives 0.5/0.4.
- The graph is never rewired.
**`GET /enhance/templates`:** each template gains `loras: [{id, label, default, strength, min, max}]`.
**`POST /enhance`:** takes `loras: {id: true | false | scale}`.
- `true` means `strength`.
- `false`, or a scale <= 0, means off.
- Other scales are clamped to `min..max`.
- A missing id uses its default.
- An id the template doesn't offer is a 400.
**Jobs:** the resolved scale per id (0 = off) goes in a new `enhance_jobs.loras` column (JSON; additive migration). It's returned as `loras: {id: scale}` in the job view, so a retry or adjust repeats it exactly.
**Startup checks:**
- A lora-titled node with no declaration keeps its exported strength.
- A declaration with no matching node isn't offered.
- Both log a startup warning.
- A lora-titled node without a numeric `strength_model` fails that template.
**Docs:** `enhance.example.toml` gains an annotated Animate entry, and the CLAUDE.md Enhance section is updated.
## Several LoRAs, and ordering
- **Several at once:** a template can declare any number of switches, and one request can turn on and scale any combination, e.g. `{style: 0.8, detail: 0.5}`.
- **Ordering** isn't exposed: LoRA patches add to the weights, so their order in the graph doesn't change the result.
- **Not covered:** choosing an arbitrary LoRA file at request time. That needs nodes inserted dynamically. It would be a separate field later, so this shape stays valid.
## Using it
1. In the 4090's Animate workflow, add a `LoraLoaderModelOnly` after lightx2v on the high path, and another on the low path.
2. Title both `imageapi:lora:<id>` and re-export the workflow in API format.
3. Add a `[[template.lora]]` with that id to the prod `enhance.toml`.
Until then nothing changes: Animate offers no switches.
## Testing
- Unit tests:
- config parsing, ranges and validation;
- request resolution (on/off/scale, clamping, <= 0 as off);
- one switch driving both nodes;
- a scale multiplying a pair with different exported strengths;
- default on with a configured strength;
- an unknown id refused;
- `strength_clip` scaled;
- undeclared and missing switches reported as warnings;
- a node with no strength rejected.
- Integration test: switches listed with their ranges, then on, default-off and 0.5-scale requests sent to ComfyUI. The resolved scales are stored on the job and returned by `GET /enhance/jobs/{id}`, plus the 400s. It uses a new fixture: the Animate workflow plus a generic `imageapi:lora:style` pair.
- `cargo test`: 1083 pass. Clippy is clean.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
A template can offer optional LoRAs the app turns on or off.
- enhance.toml: [[template.lora]] { id, label, default } per template.
Each id drives every workflow node titled `imageapi:lora:<id>`, so
Wan's high- and low-noise pair share one switch.
- POST /enhance takes `loras: {id: bool}`; a missing id uses its default,
an id the template doesn't offer is a 400. Off sets strength_model
(and strength_clip when present) to 0, so the graph is never rewired.
- The resolved switches are stored on the job (new enhance_jobs.loras
JSON column) and returned in the job view, so a retry or adjust can
repeat them; GET /enhance/templates lists each template's switches.
- A lora-titled node without a declaration keeps its exported strength,
and a declaration without nodes isn't offered; both are startup
warnings. A lora-titled node without a numeric strength_model fails
the template.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
So the app builds against the final shape: each [[template.lora]] gains
`strength` (the scale when on, default 1.0) and `min` / `max` (the
slider's range, default 0.0-1.5).
- POST /enhance `loras` takes true / false or a scale: true = strength,
false or <= 0 = off, a scale is clamped to min..max.
- The scale multiplies each node's exported strength_model /
strength_clip, so 1.0 = as exported and a high/low pair keeps its
ratio.
- Jobs and the job view store the resolved scale per id (0 = off);
GET /enhance/templates lists strength, min and max.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Part of #218 (ImageApi side; the app switches and sliders follow in FileViewer-React).
What changes
enhance.toml:[[template.lora]]{ id, label, default, strength, min, max }per template.imageapi:lora:<id>, so Wan's high/low pair shares one switch.strengthis the scale when on (default 1.0).min/maxare the slider's range (default 0.0–1.5). Validation requires0 <= min < maxwithstrengthbetween them.The scale is a factor on each node's exported strength.
strength_model, andstrength_clipwhen present (LoraLoader).GET /enhance/templates: each template gainsloras: [{id, label, default, strength, min, max}].POST /enhance: takesloras: {id: true | false | scale}.truemeansstrength.false, or a scale <= 0, means off.min..max.Jobs: the resolved scale per id (0 = off) goes in a new
enhance_jobs.lorascolumn (JSON; additive migration). It's returned asloras: {id: scale}in the job view, so a retry or adjust repeats it exactly.Startup checks:
strength_modelfails that template.Docs:
enhance.example.tomlgains an annotated Animate entry, and the CLAUDE.md Enhance section is updated.Several LoRAs, and ordering
{style: 0.8, detail: 0.5}.Using it
LoraLoaderModelOnlyafter lightx2v on the high path, and another on the low path.imageapi:lora:<id>and re-export the workflow in API format.[[template.lora]]with that id to the prodenhance.toml.Until then nothing changes: Animate offers no switches.
Testing
strength_clipscaled;GET /enhance/jobs/{id}, plus the 400s. It uses a new fixture: the Animate workflow plus a genericimageapi:lora:stylepair.cargo test: 1083 pass. Clippy is clean.🤖 Generated with Claude Code
A template can offer optional LoRAs the app turns on or off. - enhance.toml: [[template.lora]] { id, label, default } per template. Each id drives every workflow node titled `imageapi:lora:<id>`, so Wan's high- and low-noise pair share one switch. - POST /enhance takes `loras: {id: bool}`; a missing id uses its default, an id the template doesn't offer is a 400. Off sets strength_model (and strength_clip when present) to 0, so the graph is never rewired. - The resolved switches are stored on the job (new enhance_jobs.loras JSON column) and returned in the job view, so a retry or adjust can repeat them; GET /enhance/templates lists each template's switches. - A lora-titled node without a declaration keeps its exported strength, and a declaration without nodes isn't offered; both are startup warnings. A lora-titled node without a numeric strength_model fails the template. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>