feat: add LoraDemoNode and LoraRandomizerNode with documentation

- Import and register two new nodes: LoraDemoNode and LoraRandomizerNode
- Update import exception handling for better readability with multi-line formatting
- Add comprehensive documentation file `docs/custom-node-ui-output.md` for UI output usage in custom nodes
- Ensure proper node registration in NODE_CLASS_MAPPINGS for ComfyUI integration
- Maintain backward compatibility with existing node structure and import fallbacks
This commit is contained in:
Will Miao
2026-01-12 15:06:38 +08:00
parent 65cede7335
commit 177b20263d
18 changed files with 2404 additions and 242 deletions

View File

@@ -0,0 +1,40 @@
import { app } from "../../../scripts/app.js";
app.registerExtension({
name: "LoraManager.LoraDemo",
// Hook into node creation
async nodeCreated(node) {
if (node.comfyClass !== "Lora Demo (LoraManager)") {
return;
}
// Store original onExecuted
const originalOnExecuted = node.onExecuted?.bind(node);
// Override onExecuted to handle UI updates
node.onExecuted = function(output) {
// Check if output has loras data
if (output?.loras && Array.isArray(output.loras)) {
console.log("[LoraDemoNode] Received loras data from backend:", output.loras);
// Find the loras widget on this node
const lorasWidget = node.widgets.find(w => w.name === 'loras');
if (lorasWidget) {
// Update widget value with backend data
lorasWidget.value = output.loras;
console.log(`[LoraDemoNode] Updated widget with ${output.loras.length} loras`);
} else {
console.warn("[LoraDemoNode] loras widget not found on node");
}
}
// Call original onExecuted if it exists
if (originalOnExecuted) {
return originalOnExecuted(output);
}
};
}
});

View File

@@ -0,0 +1,44 @@
import { app } from "../../../scripts/app.js";
app.registerExtension({
name: "LoraManager.LoraRandomizer",
// Hook into node creation
async nodeCreated(node) {
if (node.comfyClass !== "Lora Randomizer (LoraManager)") {
return;
}
console.log("[LoraRandomizerWidget] Node created:", node.id);
// Store original onExecuted
const originalOnExecuted = node.onExecuted?.bind(node);
// Override onExecuted to handle UI updates
node.onExecuted = function(output) {
console.log("[LoraRandomizerWidget] Node executed with output:", output);
// Check if output has loras data
if (output?.loras && Array.isArray(output.loras)) {
console.log("[LoraRandomizerWidget] Received loras data from backend:", output.loras);
// Find the loras widget on this node
const lorasWidget = node.widgets.find(w => w.name === 'loras');
if (lorasWidget) {
// Update widget value with backend data
lorasWidget.value = output.loras;
console.log(`[LoraRandomizerWidget] Updated widget with ${output.loras.length} loras`);
} else {
console.warn("[LoraRandomizerWidget] loras widget not found on node");
}
}
// Call original onExecuted if it exists
if (originalOnExecuted) {
return originalOnExecuted(output);
}
};
}
});

View File

@@ -97,27 +97,27 @@ app.registerExtension({
if (isUpdating) return;
isUpdating = true;
try {
// Update this stacker's direct trigger toggles with its own active loras
// Only if the stacker node itself is active (mode 0 for Always, mode 3 for On Trigger)
const isNodeActive = this.mode === undefined || this.mode === 0 || this.mode === 3;
const activeLoraNames = new Set();
if (isNodeActive) {
value.forEach((lora) => {
if (lora.active) {
activeLoraNames.add(lora.name);
}
});
try {
// Update this stacker's direct trigger toggles with its own active loras
// Only if the stacker node itself is active (mode 0 for Always, mode 3 for On Trigger)
const isNodeActive = this.mode === undefined || this.mode === 0 || this.mode === 3;
const activeLoraNames = new Set();
if (isNodeActive) {
value.forEach((lora) => {
if (lora.active) {
activeLoraNames.add(lora.name);
}
});
}
updateConnectedTriggerWords(this, activeLoraNames);
// Find all Lora Loader nodes in the chain that might need updates
updateDownstreamLoaders(this);
} finally {
isUpdating = false;
}
updateConnectedTriggerWords(this, activeLoraNames);
// Find all Lora Loader nodes in the chain that might need updates
updateDownstreamLoaders(this);
} finally {
isUpdating = false;
}
scheduleInputSync(value);
scheduleInputSync(value);
});
this.lorasWidget = result.widget;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long