Files
robertvoy-ComfyUI-Distributed/web/collectorOutputs.js
T
speedyrulzandClaude Fable 5.1 339719ece7 fix: harden per-GPU preview outputs after review
- Typed serverless pools validate the unpruned workflow, so a preview on
  any new slot failed the whole run; both serverless compilers now accept
  every Collector output slot (COLLECTOR_OUTPUT_COUNT).
- Serverless pools are config workers: their serverless:<pool>:<job>
  participants now share the pool's worker_N slot instead of overflowing.
- A worker returning only empty frames no longer raises in the preview
  split (the combined batch already skipped them).
- Worker previews are views into the combined batch, not a second copy,
  and a frame-count guard skips them rather than misattribute frames.
- Frontend: one shared config fetch per load; unnamed slots drop their
  saved label instead of storing a redundant one.
- Tooltip notes that the per-GPU outputs are unavailable downstream of
  Ultimate SD Upscale Distributed.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-31 16:42:40 -04:00

95 lines
3.0 KiB
JavaScript

import { app } from "/scripts/app.js";
import { ENDPOINTS, NODE_CLASSES } from "./constants.js";
// worker_N previews the Nth configured worker (see nodes/collector.py).
const WORKER_OUTPUT_PREFIX = "worker_";
const WORKERS_CHANGED_EVENT = "distributed:workers-changed";
const trackedNodes = new Set();
let workers = [];
let workersPromise = null;
let listenerAttached = false;
async function fetchWorkers() {
try {
const resp = await fetch(ENDPOINTS.CONFIG);
if (!resp.ok) return [];
const config = await resp.json();
return Array.isArray(config.workers) ? config.workers : [];
} catch {
return [];
}
}
// Collectors created together (a workflow load) share one config fetch.
function loadWorkers() {
if (!workersPromise) {
workersPromise = fetchWorkers().then((list) => {
workers = list;
if (list.length === 0) workersPromise = null;
return list;
});
}
return workersPromise;
}
// Only the displayed label changes; slot names stay fixed so links survive.
// Unnamed slots drop the label so saved workflows carry no redundant copies.
function labelWorkerOutputs(node, configWorkers) {
for (const output of node.outputs || []) {
if (!output?.name?.startsWith(WORKER_OUTPUT_PREFIX)) continue;
const index = parseInt(output.name.slice(WORKER_OUTPUT_PREFIX.length), 10) - 1;
const worker = configWorkers[index];
if (worker?.name) {
output.label = `${output.name} (${worker.name})`;
} else {
delete output.label;
}
}
node.setDirtyCanvas?.(true, true);
}
function relabelAll() {
for (const node of trackedNodes) labelWorkerOutputs(node, workers);
}
function attachListener() {
if (listenerAttached || typeof window === "undefined") return;
window.addEventListener(WORKERS_CHANGED_EVENT, (event) => {
if (Array.isArray(event?.detail?.workers)) {
workers = event.detail.workers;
relabelAll();
}
});
listenerAttached = true;
}
app.registerExtension({
name: "Distributed.CollectorOutputs",
async nodeCreated(node) {
if (node.comfyClass !== NODE_CLASSES.DISTRIBUTED_COLLECTOR) return;
try {
attachListener();
trackedNodes.add(node);
const originalConfigure = node.configure;
node.configure = function (data) {
const result = originalConfigure ? originalConfigure.call(this, data) : undefined;
labelWorkerOutputs(this, workers);
return result;
};
const originalOnRemoved = node.onRemoved;
node.onRemoved = function () {
trackedNodes.delete(this);
return originalOnRemoved ? originalOnRemoved.call(this) : undefined;
};
if (workers.length === 0) await loadWorkers();
labelWorkerOutputs(node, workers);
} catch (error) {
console.error("Error in DistributedCollector outputs extension:", error);
}
},
});