|
import { app } from "scripts/app.js";
|
|
import { ComfyWidgets } from "scripts/widgets.js";
|
|
import type {
|
|
ContextMenuItem,
|
|
IContextMenuOptions,
|
|
ContextMenu,
|
|
LGraphNode as TLGraphNode,
|
|
IWidget,
|
|
LGraphCanvas,
|
|
SerializedLGraphNode,
|
|
} from "typings/litegraph.js";
|
|
import type {
|
|
ComfyObjectInfo,
|
|
ComfyWidget,
|
|
ComfyNodeConstructor,
|
|
ComfyApiPrompt,
|
|
} from "typings/comfy.js";
|
|
import { RgthreeBaseServerNode } from "./base_node.js";
|
|
import { rgthree } from "./rgthree.js";
|
|
import { addConnectionLayoutSupport } from "./utils.js";
|
|
import { NodeTypesString } from "./constants.js";
|
|
import { SerializedNode } from "typings/index.js";
|
|
|
|
const LAST_SEED_BUTTON_LABEL = "♻️ (Use Last Queued Seed)";
|
|
|
|
const SPECIAL_SEED_RANDOM = -1;
|
|
const SPECIAL_SEED_INCREMENT = -2;
|
|
const SPECIAL_SEED_DECREMENT = -3;
|
|
const SPECIAL_SEEDS = [SPECIAL_SEED_RANDOM, SPECIAL_SEED_INCREMENT, SPECIAL_SEED_DECREMENT];
|
|
|
|
interface SeedSerializedCtx {
|
|
inputSeed?: number;
|
|
seedUsed?: number;
|
|
}
|
|
|
|
class RgthreeSeed extends RgthreeBaseServerNode {
|
|
static override title = NodeTypesString.SEED;
|
|
static override type = NodeTypesString.SEED;
|
|
static comfyClass = NodeTypesString.SEED;
|
|
|
|
override serialize_widgets = true;
|
|
|
|
private logger = rgthree.newLogSession(`[Seed]`);
|
|
|
|
static override exposedActions = ["Randomize Each Time", "Use Last Queued Seed"];
|
|
|
|
lastSeed?: number = undefined;
|
|
serializedCtx: SeedSerializedCtx = {};
|
|
seedWidget!: IWidget;
|
|
lastSeedButton!: IWidget;
|
|
lastSeedValue: ComfyWidget | null = null;
|
|
|
|
randMax = 1125899906842624;
|
|
|
|
|
|
|
|
randMin = 0;
|
|
randomRange = 1125899906842624;
|
|
|
|
private handleApiHijackingBound = this.handleApiHijacking.bind(this);
|
|
|
|
constructor(title = RgthreeSeed.title) {
|
|
super(title);
|
|
|
|
rgthree.addEventListener(
|
|
"comfy-api-queue-prompt-before",
|
|
this.handleApiHijackingBound as EventListener,
|
|
);
|
|
}
|
|
|
|
override onRemoved() {
|
|
rgthree.addEventListener(
|
|
"comfy-api-queue-prompt-before",
|
|
this.handleApiHijackingBound as EventListener,
|
|
);
|
|
}
|
|
|
|
override configure(info: SerializedLGraphNode<TLGraphNode>): void {
|
|
super.configure(info);
|
|
if (this.properties?.["showLastSeed"]) {
|
|
this.addLastSeedValue();
|
|
}
|
|
}
|
|
|
|
override async handleAction(action: string) {
|
|
if (action === "Randomize Each Time") {
|
|
this.seedWidget.value = SPECIAL_SEED_RANDOM;
|
|
} else if (action === "Use Last Queued Seed") {
|
|
this.seedWidget.value = this.lastSeed != null ? this.lastSeed : this.seedWidget.value;
|
|
this.lastSeedButton.name = LAST_SEED_BUTTON_LABEL;
|
|
this.lastSeedButton.disabled = true;
|
|
}
|
|
}
|
|
|
|
override onNodeCreated() {
|
|
super.onNodeCreated?.();
|
|
|
|
for (const [i, w] of this.widgets.entries()) {
|
|
if (w.name === "seed") {
|
|
this.seedWidget = w;
|
|
this.seedWidget.value = SPECIAL_SEED_RANDOM;
|
|
} else if (w.name === "control_after_generate") {
|
|
this.widgets.splice(i, 1);
|
|
}
|
|
}
|
|
|
|
|
|
let step = this.seedWidget.options.step || 1;
|
|
this.randMax = Math.min(1125899906842624, this.seedWidget.options.max);
|
|
|
|
|
|
this.randMin = Math.max(0, this.seedWidget.options.min);
|
|
this.randomRange = (this.randMax - Math.max(0, this.randMin)) / (step / 10);
|
|
|
|
this.addWidget(
|
|
"button",
|
|
"🎲 Randomize Each Time",
|
|
null,
|
|
() => {
|
|
this.seedWidget.value = SPECIAL_SEED_RANDOM;
|
|
},
|
|
{ serialize: false },
|
|
) as ComfyWidget;
|
|
|
|
this.addWidget(
|
|
"button",
|
|
"🎲 New Fixed Random",
|
|
null,
|
|
() => {
|
|
this.seedWidget.value =
|
|
Math.floor(Math.random() * this.randomRange) * (step / 10) + this.randMin;
|
|
},
|
|
{ serialize: false },
|
|
);
|
|
|
|
this.lastSeedButton = this.addWidget(
|
|
"button",
|
|
LAST_SEED_BUTTON_LABEL,
|
|
null,
|
|
() => {
|
|
this.seedWidget.value = this.lastSeed != null ? this.lastSeed : this.seedWidget.value;
|
|
this.lastSeedButton.name = LAST_SEED_BUTTON_LABEL;
|
|
this.lastSeedButton.disabled = true;
|
|
},
|
|
{ width: 50, serialize: false },
|
|
);
|
|
this.lastSeedButton.disabled = true;
|
|
}
|
|
|
|
override getExtraMenuOptions(canvas: LGraphCanvas, options: ContextMenuItem[]): void {
|
|
super.getExtraMenuOptions?.apply(this, [...arguments] as any);
|
|
options.splice(options.length - 1, 0, {
|
|
content: "Show/Hide Last Seed Value",
|
|
callback: (
|
|
_value: ContextMenuItem,
|
|
_options: IContextMenuOptions,
|
|
_event: MouseEvent,
|
|
_parentMenu: ContextMenu | undefined,
|
|
_node: TLGraphNode,
|
|
) => {
|
|
this.properties["showLastSeed"] = !this.properties["showLastSeed"];
|
|
if (this.properties["showLastSeed"]) {
|
|
this.addLastSeedValue();
|
|
} else {
|
|
this.removeLastSeedValue();
|
|
}
|
|
},
|
|
});
|
|
}
|
|
|
|
addLastSeedValue() {
|
|
if (this.lastSeedValue) return;
|
|
this.lastSeedValue = ComfyWidgets["STRING"](
|
|
this,
|
|
"last_seed",
|
|
["STRING", { multiline: true }],
|
|
app,
|
|
).widget;
|
|
this.lastSeedValue!.inputEl!.readOnly = true;
|
|
this.lastSeedValue!.inputEl!.style.fontSize = "0.75rem";
|
|
this.lastSeedValue!.inputEl!.style.textAlign = "center";
|
|
this.computeSize();
|
|
}
|
|
|
|
removeLastSeedValue() {
|
|
if (!this.lastSeedValue) return;
|
|
this.lastSeedValue!.inputEl!.remove();
|
|
this.widgets.splice(this.widgets.indexOf(this.lastSeedValue as IWidget), 1);
|
|
this.lastSeedValue = null;
|
|
this.computeSize();
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
handleApiHijacking(e: CustomEvent<ComfyApiPrompt>) {
|
|
|
|
if (this.mode === LiteGraph.NEVER || this.mode === 4) {
|
|
return;
|
|
}
|
|
|
|
const workflow = e.detail.workflow;
|
|
const output = e.detail.output;
|
|
|
|
let workflowNode = workflow?.nodes?.find((n: SerializedNode) => n.id === this.id) ?? null;
|
|
let outputInputs = output?.[this.id]?.inputs;
|
|
|
|
if (
|
|
!workflowNode ||
|
|
!outputInputs ||
|
|
outputInputs[this.seedWidget.name || "seed"] === undefined
|
|
) {
|
|
const [n, v] = this.logger.warnParts(
|
|
`Node ${this.id} not found in prompt data sent to server. This may be fine if only ` +
|
|
`queuing part of the workflow. If not, then this could be a bug.`,
|
|
);
|
|
console[n]?.(...v);
|
|
return;
|
|
}
|
|
|
|
const seedToUse = this.getSeedToUse();
|
|
const seedWidgetndex = this.widgets.indexOf(this.seedWidget);
|
|
|
|
workflowNode.widgets_values![seedWidgetndex] = seedToUse;
|
|
outputInputs[this.seedWidget.name || "seed"] = seedToUse;
|
|
|
|
this.lastSeed = seedToUse;
|
|
if (seedToUse != this.seedWidget.value) {
|
|
this.lastSeedButton.name = `♻️ ${this.lastSeed}`;
|
|
this.lastSeedButton.disabled = false;
|
|
} else {
|
|
this.lastSeedButton.name = LAST_SEED_BUTTON_LABEL;
|
|
this.lastSeedButton.disabled = true;
|
|
}
|
|
if (this.lastSeedValue) {
|
|
this.lastSeedValue.value = `Last Seed: ${this.lastSeed}`;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private getSeedToUse() {
|
|
const inputSeed: number = this.seedWidget.value;
|
|
let seedToUse: number | null = null;
|
|
|
|
|
|
if (SPECIAL_SEEDS.includes(inputSeed)) {
|
|
|
|
|
|
if (typeof this.lastSeed === "number" && !SPECIAL_SEEDS.includes(this.lastSeed)) {
|
|
if (inputSeed === SPECIAL_SEED_INCREMENT) {
|
|
seedToUse = this.lastSeed + 1;
|
|
} else if (inputSeed === SPECIAL_SEED_DECREMENT) {
|
|
seedToUse = this.lastSeed - 1;
|
|
}
|
|
}
|
|
|
|
|
|
if (seedToUse == null || SPECIAL_SEEDS.includes(seedToUse)) {
|
|
seedToUse =
|
|
Math.floor(Math.random() * this.randomRange) *
|
|
((this.seedWidget.options.step || 1) / 10) +
|
|
this.randMin;
|
|
}
|
|
}
|
|
|
|
return seedToUse ?? inputSeed;
|
|
}
|
|
|
|
static override setUp(comfyClass: ComfyNodeConstructor, nodeData: ComfyObjectInfo) {
|
|
RgthreeBaseServerNode.registerForOverride(comfyClass, nodeData, RgthreeSeed);
|
|
}
|
|
|
|
static override onRegisteredForOverride(comfyClass: any, ctxClass: any) {
|
|
addConnectionLayoutSupport(RgthreeSeed, app, [
|
|
["Left", "Right"],
|
|
["Right", "Left"],
|
|
]);
|
|
setTimeout(() => {
|
|
RgthreeSeed.category = comfyClass.category;
|
|
});
|
|
}
|
|
}
|
|
|
|
app.registerExtension({
|
|
name: "rgthree.Seed",
|
|
async beforeRegisterNodeDef(nodeType: ComfyNodeConstructor, nodeData: ComfyObjectInfo) {
|
|
if (nodeData.name === RgthreeSeed.type) {
|
|
RgthreeSeed.setUp(nodeType, nodeData);
|
|
}
|
|
},
|
|
});
|
|
|