Inserting took a round per LoRA. The picker now gathers them: click to add, click again to drop, the number on a card is the order they go in. They go in as a chain of ComfyUI's loaders, or through one Power Lora Loader or Easy-Use stack where installed, all or nothing and as one undo step. Third-party nodes change height just after they appear, so they are placed, and the step taken, once they have settled. New nodes stand in one column and the view goes to them when a crowded canvas leaves them out of sight. Fixes the CLIP line going missing for a LoRA inserted before a sampler with a LoRA already there: that LoRA counted as a prompt node, so there seemed to be two CLIP sources. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
58 lines
2.3 KiB
JavaScript
58 lines
2.3 KiB
JavaScript
/**
|
|
* Several LoRAs into a workflow at once (the model picker's Insert LoRA), as one of the LoRA nodes
|
|
* of lora_nodes.js: ComfyUI's own in a chain, or the one node a third-party stack goes through.
|
|
* All or nothing. No imports of ComfyUI, so it is tested on its own (LiteGraph is read from globalThis).
|
|
*/
|
|
|
|
import { placeNodes, planLoraInsertion, removeSpliced, spliceLora } from './graph_splice.js';
|
|
import { fillLoraHead } from './lora_nodes.js';
|
|
|
|
function make(type) {
|
|
const node = globalThis.LiteGraph.createNode(type);
|
|
if (!node) throw new Error(`ComfyUI cannot make ${type}.`);
|
|
return node;
|
|
}
|
|
|
|
/**
|
|
* Puts `loras` (paths in order) before or after `anchorNode` and returns the new nodes.
|
|
* `textEncoder`: whether any of them trains it. It holds for them all, so the CLIP line runs
|
|
* unbroken through a chain. `setPath(node, path)` fills a ComfyUI LoRA loader on the canvas.
|
|
*/
|
|
export function insertLoras({ graph, anchorNode, direction, kind, loras, textEncoder, setPath }) {
|
|
const spliced = [];
|
|
const beside = [];
|
|
const splice = (anchor, type) => {
|
|
const plan = planLoraInsertion(graph, anchor, direction, { textEncoder });
|
|
if (!plan.supported) throw new Error(plan.code);
|
|
const insertedNode = make(type ?? (plan.clip ? 'LoraLoader' : 'LoraLoaderModelOnly'));
|
|
spliceLora({ graph, plan, anchorNode: anchor, insertedNode });
|
|
spliced.push({ graph, plan, insertedNode });
|
|
return insertedNode;
|
|
};
|
|
const add = (type) => {
|
|
const node = make(type);
|
|
graph.add(node);
|
|
beside.push(node);
|
|
return node;
|
|
};
|
|
try {
|
|
if (kind.head) {
|
|
fillLoraHead(kind, splice(anchorNode, kind.head), loras, add);
|
|
} else {
|
|
let anchor = anchorNode;
|
|
for (const path of loras) {
|
|
const node = splice(anchor);
|
|
setPath(node, path);
|
|
if (direction === 'after') anchor = node; // before: the next goes between this one and the anchor
|
|
}
|
|
}
|
|
} catch (error) {
|
|
beside.forEach(node => graph.remove(node));
|
|
spliced.reverse().forEach(removeSpliced);
|
|
throw error;
|
|
}
|
|
const nodes = [...beside, ...spliced.map(item => item.insertedNode)];
|
|
placeNodes(graph, anchorNode, nodes, direction);
|
|
return nodes;
|
|
}
|