Use ComfyPool to manage a collection of ComfyApi instances. This is useful for load balancing or distributing jobs across multiple ComfyUI servers.
- Initialization: Pass an array of
ComfyApi instances and an EQueueMode (e.g., EQueueMode.PICK_ZERO) to the constructor. - Events: The pool emits events like
init, add_job, and added. - Batching: Use
ApiPool.batch(tasks, weight) to run multiple asynchronous tasks across the pool. This returns a promise that resolves when the batch is complete.
import {
ComfyApi,
CallWrapper,
ComfyPool,
EQueueMode,
PromptBuilder,
seed,
TSamplerName,
TSchedulerName
} from "@saintno/comfyui-sdk";
import ExampleTxt2ImgWorkflow from "./example-txt2img-workflow.json";
const ApiPool = new ComfyPool(
[new ComfyApi("http://localhost:8188"), new ComfyApi("http://localhost:8189")],
EQueueMode.PICK_ZERO
)
.on("init", () => console.log("Pool in initializing"))
.on("add_job", (ev) => console.log("Job added at index", ev.detail.jobIdx, "weight:", ev.detail.weight))
.on("added", (ev) => console.log("Client added", ev.detail.clientIdx));
const generateFn = async (api: ComfyApi, clientIdx?: number) => {
const workflow = new PromptBuilder(
ExampleTxt2ImgWorkflow,
["positive", "negative", "checkpoint", "seed", "batch", "step", "cfg", "sampler", "sheduler", "width", "height"],
["images"]
)
.setInputNode("checkpoint", "4.inputs.ckpt_name")
.setInputNode("seed", "3.inputs.seed")
.setInputNode("batch", "5.inputs.batch_size")
.setInputNode("negative", "7.inputs.text")
.setInputNode("positive", "6.inputs.text")
.setInputNode("step", "3.inputs.steps")
.setInputNode("width", "5.inputs.width")
.setInputNode("height", "5.inputs.height")
.setInputNode("cfg", "3.inputs.cfg")
.setInputNode("sampler", "3.inputs.sampler_name")
.setInputNode("sheduler", "3.inputs.scheduler")
.setOutputNode("images", "9")
.input("checkpoint", "SDXL/realvisxlV40_v40LightningBakedvae.safetensors", api.osType)
.input("seed", seed())
.input("step", 6)
.input("width", 512)
.input("height", 512)
.input("batch", 2)
.input("cfg", 1)
.input<TSamplerName>("sampler", "dpmpp_2m_sde_gpu")
.input<TSchedulerName>("scheduler", "sgm_uniform")
.input("positive", "A close up picture of cute Cat")
.input("negative", "text, blurry, bad picture, nsfw");
return new Promise<string[]>((resolve) => {
new CallWrapper(api, workflow)
.onFinished((data) => {
const url = data.images?.images.map((img: any) => api.getPathImage(img));
resolve(url as string[]);
})
.run();
});
};
const jobA = ApiPool.batch(Array(5).fill(generateFn), 10).then((res) => {
console.log("Batch A done");
return res.flat();
});
const jobB = ApiPool.batch(Array(5).fill(generateFn), 0).then((res) => {
console.log("Batch B done");
return res.flat();
});
console.log(await Promise.all([jobA, jobB]).then((res) => res.flat()));