Add custom nodes, Civitai loras (LFS), and vast.ai setup script
Some checks failed
Python Linting / Run Ruff (push) Has been cancelled
Python Linting / Run Pylint (push) Has been cancelled
Full Comfy CI Workflow Runs / test-stable (12.1, , linux, 3.10, [self-hosted Linux], stable) (push) Has been cancelled
Full Comfy CI Workflow Runs / test-stable (12.1, , linux, 3.11, [self-hosted Linux], stable) (push) Has been cancelled
Full Comfy CI Workflow Runs / test-stable (12.1, , linux, 3.12, [self-hosted Linux], stable) (push) Has been cancelled
Full Comfy CI Workflow Runs / test-unix-nightly (12.1, , linux, 3.11, [self-hosted Linux], nightly) (push) Has been cancelled
Execution Tests / test (macos-latest) (push) Has been cancelled
Execution Tests / test (ubuntu-latest) (push) Has been cancelled
Execution Tests / test (windows-latest) (push) Has been cancelled
Test server launches without errors / test (push) Has been cancelled
Unit Tests / test (macos-latest) (push) Has been cancelled
Unit Tests / test (ubuntu-latest) (push) Has been cancelled
Unit Tests / test (windows-2022) (push) Has been cancelled

Includes 30 custom nodes committed directly, 7 Civitai-exclusive
loras stored via Git LFS, and a setup script that installs all
dependencies and downloads HuggingFace-hosted models on vast.ai.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-09 00:55:26 +00:00
parent 2b70ab9ad0
commit f09734b0ee
2274 changed files with 748556 additions and 3 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

View File

@@ -0,0 +1,140 @@
<!doctype html>
<html>
<head>
<title>rgthree's comfy: Workflow Link Fixer</title>
<style>
html, body {
}
html {
font-size: 100%;
overflow-y: scroll;
-webkit-text-size-adjust: 100%;
-ms-text-size-adjust: 100%;
box-sizing: border-box
}
*, *:before, *:after {
box-sizing: inherit
}
body {
background: #222;
font-family: Arial, sans-serif;
font-size: calc(16 * 0.0625rem);
font-weight: 400;
margin: 0;
color: #ffffff;
padding-bottom: 64px;
display: flex;
}
.box, figure, picture {
display: flex;
flex-direction: column;
align-items: center;
}
.box {
margin: 32px auto;
max-width: 720px;
padding: 16px 16px 32px;
background: rgba(125,125,125,0.33);
box-shadow: 0px 8px 10px rgba(0,0,0,0.85);
border-radius: 8px;
}
h1 {
margin-top: 0;
}
.box > * {
text-align: center;
}
.box > p {
margin: 0 0 .6em;
text-align: left;
line-height: 1.25;
}
.box > small {
margin: 0 0 .6em;
text-align: left;
line-height: 1.25;
opacity: 0.75;
}
picture > img {
display: block;
height: 256px;
min-width: 256px;
border: 10px dashed rgba(125,125,125,0.66);
border-radius: 32px;
object-fit: contain;
pointer-events: none;
}
figcaption {
opacity: 0.66;
padding: 4px;
font-size: calc(11 * 0.0625rem);
}
.-has-file picture > img {
border-color: transparent;
border-radius: 32px;
}
.output {
margin: 8px 8px 32px;
border-radius: 4px;
border: 1px solid rgba(125,125,125,0.66);
padding: 4px 8px;
min-width: 80%;
}
.output:empty {
display: none;
}
.btn-fix {
display: none;
cursor: pointer;
font-size: calc(24 * 0.0625rem);
}
.-has-fixable-results .btn-fix {
display: inline-block;
}
</style>
</head>
<body>
<div class="box">
<h1>rgthree's Workflow Link Fixer</h1>
<p>
Drag and drop a comfy-generated image or workflow json into this window to check its
serialized links and attempt to fix.
</p>
<small>
Sometimes as you have complex workflows the internal data can become corrupt, and the
ComfyUI doesn't always understand or display correctly. Maybe links disappear, or reconnect
to another node when changing something. Load it here to detect and, if possible, attempt
to fix it (sometimes, however, fixing it just isn't feasible, so fingers crossed).
</small>
<figure>
<picture>
<img class="output-image" />
</picture>
<figcaption></figcaption>
</figure>
<div class="output"></div>
<button class="btn-fix">Fix & Save new workflow</button>
</div>
<script type="module">
import {LinkPage} from './link_page.js';
new LinkPage();
</script>
</body>
</html>

View File

@@ -0,0 +1,225 @@
import type {ISerialisedGraph} from "@comfyorg/frontend";
import type {BadLinksData} from "../common/link_fixer.js";
import {WorkflowLinkFixer} from "../common/link_fixer.js";
import {getPngMetadata} from "../common/comfyui_shim.js";
function wait(ms = 16, value?: any) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(value);
}, ms);
});
}
const logger = {
logTo: console as Console | HTMLElement,
log: (...args: any[]) => {
logger.logTo === console
? console.log(...args)
: ((logger.logTo as HTMLElement).innerText += args.join(",") + "\n");
},
};
export class LinkPage {
private containerEl: HTMLDivElement;
private figcaptionEl: HTMLElement;
private btnFix: HTMLButtonElement;
private outputeMessageEl: HTMLDivElement;
private outputImageEl: HTMLImageElement;
private file?: File | Blob;
private graph?: ISerialisedGraph;
private graphResults?: BadLinksData;
private graphFinalResults?: BadLinksData;
private fixer: WorkflowLinkFixer<any, any> | null = null;
constructor() {
// const consoleEl = document.getElementById("console")!;
this.containerEl = document.querySelector(".box")!;
this.figcaptionEl = document.querySelector("figcaption")!;
this.outputeMessageEl = document.querySelector(".output")!;
this.outputImageEl = document.querySelector(".output-image")!;
this.btnFix = document.querySelector(".btn-fix")!;
// Need to prevent on dragover to allow drop...
document.addEventListener(
"dragover",
(e) => {
e.preventDefault();
},
false,
);
document.addEventListener("drop", (e) => {
this.onDrop(e);
});
this.btnFix.addEventListener("click", (e) => {
this.onFixClick(e);
});
}
private async onFixClick(e: MouseEvent) {
if (!this.fixer?.checkedData || !this.graph) {
this.updateUi("⛔ Fix button click without results.");
return;
}
this.graphFinalResults = this.fixer.fix();
if (this.graphFinalResults.hasBadLinks) {
this.updateUi(
"⛔ Hmm... Still detecting bad links. Can you file an issue at https://github.com/rgthree/rgthree-comfy/issues with your image/workflow.",
);
} else {
this.updateUi(
"✅ Workflow fixed.<br><br><small>Please load new saved workflow json and double check linking and execution.</small>",
);
}
await wait(16);
await this.saveFixedWorkflow();
}
private async onDrop(event: DragEvent) {
if (!event.dataTransfer) {
return;
}
this.reset();
event.preventDefault();
event.stopPropagation();
// Dragging from Chrome->Firefox there is a file but its a bmp, so ignore that
if (event.dataTransfer.files.length && event.dataTransfer.files?.[0]?.type !== "image/bmp") {
await this.handleFile(event.dataTransfer.files[0]!);
return;
}
// Try loading the first URI in the transfer list
const validTypes = ["text/uri-list", "text/x-moz-url"];
const match = [...event.dataTransfer.types].find((t) => validTypes.find((v) => t === v));
if (match) {
const uri = event.dataTransfer.getData(match)?.split("\n")?.[0];
if (uri) {
await this.handleFile(await (await fetch(uri)).blob());
}
}
}
reset() {
this.file = undefined;
this.graph = undefined;
this.graphResults = undefined;
this.graphFinalResults = undefined;
this.updateUi();
}
private updateUi(msg?: string) {
this.outputeMessageEl.innerHTML = "";
if (this.file && !this.containerEl.classList.contains("-has-file")) {
this.containerEl.classList.add("-has-file");
this.figcaptionEl.innerHTML = (this.file as File).name || this.file.type;
if (this.file.type === "application/json") {
this.outputImageEl.src = "icon_file_json.png";
} else {
const reader = new FileReader();
reader.onload = () => (this.outputImageEl.src = reader.result as string);
reader.readAsDataURL(this.file);
}
} else if (!this.file && this.containerEl.classList.contains("-has-file")) {
this.containerEl.classList.remove("-has-file");
this.outputImageEl.src = "";
this.outputImageEl.removeAttribute("src");
}
if (this.graphResults) {
this.containerEl.classList.add("-has-results");
if (!this.graphResults.patches && !this.graphResults.deletes) {
this.outputeMessageEl.innerHTML = "✅ No bad links detected in the workflow.";
} else {
this.containerEl.classList.add("-has-fixable-results");
this.outputeMessageEl.innerHTML = `⚠️ Found ${this.graphResults.patches} links to fix, and ${this.graphResults.deletes} to be removed.`;
}
} else {
this.containerEl.classList.remove("-has-results");
this.containerEl.classList.remove("-has-fixable-results");
}
if (msg) {
this.outputeMessageEl.innerHTML = msg;
}
}
private async handleFile(file: File | Blob) {
this.file = file;
this.updateUi();
let workflow: string | undefined | null = null;
if (file.type.startsWith("image/")) {
const pngInfo = await getPngMetadata(file);
workflow = pngInfo?.workflow;
} else if (
file.type === "application/json" ||
(file instanceof File && file.name.endsWith(".json"))
) {
workflow = await new Promise((resolve) => {
const reader = new FileReader();
reader.onload = () => {
resolve(reader.result as string);
};
reader.readAsText(file);
});
}
if (!workflow) {
this.updateUi("⛔ No workflow found in dropped item.");
} else {
try {
this.graph = JSON.parse(workflow);
} catch (e) {
this.graph = undefined;
}
if (!this.graph) {
this.updateUi("⛔ Invalid workflow found in dropped item.");
} else {
this.loadGraphData(this.graph);
}
}
}
private async loadGraphData(graphData: ISerialisedGraph) {
this.fixer = WorkflowLinkFixer.create(graphData);
this.graphResults = this.fixer.check();
this.updateUi();
}
private async saveFixedWorkflow() {
if (!this.graphFinalResults) {
this.updateUi("⛔ Save w/o final graph patched.");
return false;
}
let filename: string | null = (this.file as File).name || "workflow.json";
let filenames = filename.split(".");
filenames.pop();
filename = filenames.join(".");
filename += "_fixed.json";
filename = prompt("Save workflow as:", filename);
if (!filename) return false;
if (!filename.toLowerCase().endsWith(".json")) {
filename += ".json";
}
const json = JSON.stringify(this.graphFinalResults.graph, null, 2);
const blob = new Blob([json], {type: "application/json"});
const url = URL.createObjectURL(blob);
const anchor = document.createElement("a");
anchor.download = filename;
anchor.href = url;
anchor.style.display = "none";
document.body.appendChild(anchor);
await wait();
anchor.click();
await wait();
anchor.remove();
window.URL.revokeObjectURL(url);
return true;
}
}