Refactor actions
This commit is contained in:
@@ -18,6 +18,7 @@ import "@litegraph-ts/nodes-math"
|
|||||||
import "@litegraph-ts/nodes-strings"
|
import "@litegraph-ts/nodes-strings"
|
||||||
import "$lib/nodes/index"
|
import "$lib/nodes/index"
|
||||||
import "$lib/nodes/widgets/index"
|
import "$lib/nodes/widgets/index"
|
||||||
|
import "$lib/nodes/actions/index"
|
||||||
import * as nodes from "$lib/nodes/index"
|
import * as nodes from "$lib/nodes/index"
|
||||||
import * as widgets from "$lib/nodes/widgets/index"
|
import * as widgets from "$lib/nodes/widgets/index"
|
||||||
|
|
||||||
|
|||||||
@@ -1,612 +0,0 @@
|
|||||||
import type { SerializedPrompt } from "$lib/components/ComfyApp";
|
|
||||||
import notify from "$lib/notify";
|
|
||||||
import { type DragItemID } from "$lib/stores/layoutStates";
|
|
||||||
import queueState from "$lib/stores/queueState";
|
|
||||||
import { BuiltInSlotType, LiteGraph, NodeMode, type ITextWidget, type IToggleWidget, type SerializedLGraphNode, type SlotLayout, type PropertyLayout } from "@litegraph-ts/core";
|
|
||||||
import { get } from "svelte/store";
|
|
||||||
import ComfyGraphNode, { type ComfyGraphNodeProperties } from "./ComfyGraphNode";
|
|
||||||
import type { ComfyWidgetNode } from "$lib/nodes/widgets";
|
|
||||||
import type { NotifyOptions } from "$lib/notify";
|
|
||||||
import type { FileData as GradioFileData } from "@gradio/upload";
|
|
||||||
import { type SerializedPromptOutput, type ComfyImageLocation, convertComfyOutputToGradio, type ComfyUploadImageAPIResponse, parseWhateverIntoComfyImageLocations } from "$lib/utils";
|
|
||||||
|
|
||||||
export class ComfyQueueEvents extends ComfyGraphNode {
|
|
||||||
static slotLayout: SlotLayout = {
|
|
||||||
outputs: [
|
|
||||||
{ name: "beforeQueued", type: BuiltInSlotType.EVENT },
|
|
||||||
{ name: "afterQueued", type: BuiltInSlotType.EVENT },
|
|
||||||
{ name: "onDefaultQueueAction", type: BuiltInSlotType.EVENT },
|
|
||||||
],
|
|
||||||
}
|
|
||||||
|
|
||||||
private getActionParams(subgraph: string | null): any {
|
|
||||||
let queue = get(queueState)
|
|
||||||
let queueRemaining = 0;
|
|
||||||
|
|
||||||
if (typeof queue.queueRemaining === "number")
|
|
||||||
queueRemaining = queue.queueRemaining
|
|
||||||
|
|
||||||
return {
|
|
||||||
queueRemaining,
|
|
||||||
subgraph
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override beforeQueued(subgraph: string | null) {
|
|
||||||
this.triggerSlot(0, this.getActionParams(subgraph))
|
|
||||||
}
|
|
||||||
|
|
||||||
override afterQueued(p: SerializedPrompt, subgraph: string | null) {
|
|
||||||
this.triggerSlot(1, this.getActionParams(subgraph))
|
|
||||||
}
|
|
||||||
|
|
||||||
override onDefaultQueueAction() {
|
|
||||||
let queue = get(queueState)
|
|
||||||
let queueRemaining = 0;
|
|
||||||
|
|
||||||
if (typeof queue.queueRemaining === "number")
|
|
||||||
queueRemaining = queue.queueRemaining
|
|
||||||
|
|
||||||
this.triggerSlot(2, { queueRemaining })
|
|
||||||
}
|
|
||||||
|
|
||||||
override onSerialize(o: SerializedLGraphNode) {
|
|
||||||
super.onSerialize(o)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LiteGraph.registerNodeType({
|
|
||||||
class: ComfyQueueEvents,
|
|
||||||
title: "Comfy.QueueEvents",
|
|
||||||
desc: "Triggers a 'bang' event when a prompt is queued.",
|
|
||||||
type: "events/queue_events"
|
|
||||||
})
|
|
||||||
|
|
||||||
export interface ComfyStoreImagesActionProperties extends ComfyGraphNodeProperties {
|
|
||||||
images: SerializedPromptOutput | null
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ComfyStoreImagesAction extends ComfyGraphNode {
|
|
||||||
override properties: ComfyStoreImagesActionProperties = {
|
|
||||||
tags: [],
|
|
||||||
images: null
|
|
||||||
}
|
|
||||||
|
|
||||||
static slotLayout: SlotLayout = {
|
|
||||||
inputs: [
|
|
||||||
{ name: "output", type: BuiltInSlotType.ACTION, options: { color_off: "rebeccapurple", color_on: "rebeccapurple" } }
|
|
||||||
],
|
|
||||||
outputs: [
|
|
||||||
{ name: "images", type: "OUTPUT" },
|
|
||||||
],
|
|
||||||
}
|
|
||||||
|
|
||||||
override onExecute() {
|
|
||||||
if (this.properties.images !== null)
|
|
||||||
this.setOutputData(0, this.properties.images)
|
|
||||||
}
|
|
||||||
|
|
||||||
override onAction(action: any, param: any) {
|
|
||||||
if (action !== "store" || !param || !("images" in param))
|
|
||||||
return;
|
|
||||||
|
|
||||||
this.setProperty("images", param as SerializedPromptOutput)
|
|
||||||
this.setOutputData(0, this.properties.images)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LiteGraph.registerNodeType({
|
|
||||||
class: ComfyStoreImagesAction,
|
|
||||||
title: "Comfy.StoreImagesAction",
|
|
||||||
desc: "Stores images from an onExecuted callback",
|
|
||||||
type: "actions/store_images"
|
|
||||||
})
|
|
||||||
|
|
||||||
export interface ComfyCopyActionProperties extends ComfyGraphNodeProperties {
|
|
||||||
value: any
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ComfyCopyAction extends ComfyGraphNode {
|
|
||||||
override properties: ComfyCopyActionProperties = {
|
|
||||||
value: null,
|
|
||||||
tags: []
|
|
||||||
}
|
|
||||||
|
|
||||||
static slotLayout: SlotLayout = {
|
|
||||||
inputs: [
|
|
||||||
{ name: "in", type: "*" },
|
|
||||||
{ name: "copy", type: BuiltInSlotType.ACTION }
|
|
||||||
],
|
|
||||||
outputs: [
|
|
||||||
{ name: "out", type: BuiltInSlotType.EVENT }
|
|
||||||
],
|
|
||||||
}
|
|
||||||
|
|
||||||
displayWidget: ITextWidget;
|
|
||||||
|
|
||||||
constructor(title?: string) {
|
|
||||||
super(title);
|
|
||||||
this.displayWidget = this.addWidget<ITextWidget>(
|
|
||||||
"text",
|
|
||||||
"Value",
|
|
||||||
"",
|
|
||||||
"value"
|
|
||||||
);
|
|
||||||
this.displayWidget.disabled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
override onExecute() {
|
|
||||||
if (this.getInputLink(0))
|
|
||||||
this.setProperty("value", this.getInputData(0))
|
|
||||||
}
|
|
||||||
|
|
||||||
override onAction(action: any, param: any) {
|
|
||||||
if (action === "copy") {
|
|
||||||
this.setProperty("value", this.getInputData(0))
|
|
||||||
this.triggerSlot(0, this.properties.value)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
LiteGraph.registerNodeType({
|
|
||||||
class: ComfyCopyAction,
|
|
||||||
title: "Comfy.CopyAction",
|
|
||||||
desc: "Copies its input to its output when an event is received",
|
|
||||||
type: "actions/copy"
|
|
||||||
})
|
|
||||||
|
|
||||||
export interface ComfySwapActionProperties extends ComfyGraphNodeProperties {
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ComfySwapAction extends ComfyGraphNode {
|
|
||||||
override properties: ComfySwapActionProperties = {
|
|
||||||
}
|
|
||||||
|
|
||||||
static slotLayout: SlotLayout = {
|
|
||||||
inputs: [
|
|
||||||
{ name: "A", type: "*" },
|
|
||||||
{ name: "B", type: "*" },
|
|
||||||
{ name: "swap", type: BuiltInSlotType.ACTION }
|
|
||||||
],
|
|
||||||
outputs: [
|
|
||||||
{ name: "B", type: BuiltInSlotType.EVENT },
|
|
||||||
{ name: "A", type: BuiltInSlotType.EVENT }
|
|
||||||
],
|
|
||||||
}
|
|
||||||
|
|
||||||
override onAction(action: any, param: any) {
|
|
||||||
const a = this.getInputData(0)
|
|
||||||
const b = this.getInputData(1)
|
|
||||||
this.triggerSlot(0, b)
|
|
||||||
this.triggerSlot(1, a)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
LiteGraph.registerNodeType({
|
|
||||||
class: ComfySwapAction,
|
|
||||||
title: "Comfy.SwapAction",
|
|
||||||
desc: "Swaps two inputs when triggered",
|
|
||||||
type: "actions/swap"
|
|
||||||
})
|
|
||||||
|
|
||||||
export interface ComfyNotifyActionProperties extends ComfyGraphNodeProperties {
|
|
||||||
message: string,
|
|
||||||
type: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ComfyNotifyAction extends ComfyGraphNode {
|
|
||||||
override properties: ComfyNotifyActionProperties = {
|
|
||||||
tags: [],
|
|
||||||
message: "Nya.",
|
|
||||||
type: "info"
|
|
||||||
}
|
|
||||||
|
|
||||||
static slotLayout: SlotLayout = {
|
|
||||||
inputs: [
|
|
||||||
{ name: "message", type: "string" },
|
|
||||||
{ name: "trigger", type: BuiltInSlotType.ACTION }
|
|
||||||
],
|
|
||||||
}
|
|
||||||
|
|
||||||
override onAction(action: any, param: any) {
|
|
||||||
const message = this.getInputData(0) || this.properties.message;
|
|
||||||
if (!message)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const options: NotifyOptions = {
|
|
||||||
type: this.properties.type,
|
|
||||||
showOn: "all"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if this event was triggered from a backend node and has the
|
|
||||||
// onExecuted arguments. If so then use the first image as the icon for
|
|
||||||
// native notifications.
|
|
||||||
if (param != null && typeof param === "object") {
|
|
||||||
if ("images" in param) {
|
|
||||||
const output = param as SerializedPromptOutput;
|
|
||||||
const converted = convertComfyOutputToGradio(output);
|
|
||||||
if (converted.length > 0)
|
|
||||||
options.imageUrl = converted[0].data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
notify(message, options);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
LiteGraph.registerNodeType({
|
|
||||||
class: ComfyNotifyAction,
|
|
||||||
title: "Comfy.NotifyAction",
|
|
||||||
desc: "Displays a message.",
|
|
||||||
type: "actions/notify"
|
|
||||||
})
|
|
||||||
|
|
||||||
export interface ComfyPlaySoundActionProperties extends ComfyGraphNodeProperties {
|
|
||||||
sound: string,
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ComfyPlaySoundAction extends ComfyGraphNode {
|
|
||||||
override properties: ComfyPlaySoundActionProperties = {
|
|
||||||
tags: [],
|
|
||||||
sound: "notification.mp3"
|
|
||||||
}
|
|
||||||
|
|
||||||
static slotLayout: SlotLayout = {
|
|
||||||
inputs: [
|
|
||||||
{ name: "sound", type: "string" },
|
|
||||||
{ name: "trigger", type: BuiltInSlotType.ACTION }
|
|
||||||
],
|
|
||||||
}
|
|
||||||
|
|
||||||
override onAction(action: any, param: any) {
|
|
||||||
const sound = this.getInputData(0) || this.properties.sound;
|
|
||||||
if (sound) {
|
|
||||||
const url = `${location.origin}/sound/${sound}`;
|
|
||||||
const audio = new Audio(url);
|
|
||||||
audio.play();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
LiteGraph.registerNodeType({
|
|
||||||
class: ComfyPlaySoundAction,
|
|
||||||
title: "Comfy.PlaySoundAction",
|
|
||||||
desc: "Plays a sound located under the sound/ directory.",
|
|
||||||
type: "actions/play_sound"
|
|
||||||
})
|
|
||||||
|
|
||||||
export interface ComfyExecuteSubgraphActionProperties extends ComfyGraphNodeProperties {
|
|
||||||
targetTag: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ComfyExecuteSubgraphAction extends ComfyGraphNode {
|
|
||||||
override properties: ComfyExecuteSubgraphActionProperties = {
|
|
||||||
tags: [],
|
|
||||||
targetTag: ""
|
|
||||||
}
|
|
||||||
|
|
||||||
static slotLayout: SlotLayout = {
|
|
||||||
inputs: [
|
|
||||||
{ name: "execute", type: BuiltInSlotType.ACTION },
|
|
||||||
{ name: "targetTag", type: "string" }
|
|
||||||
],
|
|
||||||
}
|
|
||||||
|
|
||||||
displayWidget: ITextWidget;
|
|
||||||
|
|
||||||
constructor(title?: string) {
|
|
||||||
super(title)
|
|
||||||
this.displayWidget = this.addWidget("text", "targetTag", this.properties.targetTag, "targetTag")
|
|
||||||
}
|
|
||||||
|
|
||||||
override onExecute() {
|
|
||||||
const tag = this.getInputData(1)
|
|
||||||
if (tag)
|
|
||||||
this.setProperty("tag", tag)
|
|
||||||
}
|
|
||||||
|
|
||||||
override onAction(action: any, param: any) {
|
|
||||||
const tag = this.getInputData(1) || this.properties.targetTag;
|
|
||||||
|
|
||||||
const app = (window as any)?.app;
|
|
||||||
if (!app)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Hold control to queue at the front
|
|
||||||
const num = app.ctrlDown ? -1 : 0;
|
|
||||||
app.queuePrompt(num, 1, tag);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LiteGraph.registerNodeType({
|
|
||||||
class: ComfyExecuteSubgraphAction,
|
|
||||||
title: "Comfy.ExecuteSubgraphAction",
|
|
||||||
desc: "Runs a part of the graph based on a tag",
|
|
||||||
type: "actions/execute_subgraph"
|
|
||||||
})
|
|
||||||
|
|
||||||
export interface ComfySetNodeModeActionProperties extends ComfyGraphNodeProperties {
|
|
||||||
targetTags: string,
|
|
||||||
enable: boolean,
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ComfySetNodeModeAction extends ComfyGraphNode {
|
|
||||||
override properties: ComfySetNodeModeActionProperties = {
|
|
||||||
targetTags: "",
|
|
||||||
enable: false,
|
|
||||||
tags: []
|
|
||||||
}
|
|
||||||
|
|
||||||
static slotLayout: SlotLayout = {
|
|
||||||
inputs: [
|
|
||||||
{ name: "enabled", type: "boolean" },
|
|
||||||
{ name: "set", type: BuiltInSlotType.ACTION },
|
|
||||||
],
|
|
||||||
}
|
|
||||||
|
|
||||||
displayWidget: ITextWidget;
|
|
||||||
enableWidget: IToggleWidget;
|
|
||||||
|
|
||||||
constructor(title?: string) {
|
|
||||||
super(title)
|
|
||||||
this.displayWidget = this.addWidget("text", "Tags", this.properties.targetTags, "targetTags")
|
|
||||||
this.enableWidget = this.addWidget("toggle", "Enable", this.properties.enable, "enable");
|
|
||||||
}
|
|
||||||
|
|
||||||
override onPropertyChanged(property: any, value: any) {
|
|
||||||
if (property === "enable") {
|
|
||||||
this.enableWidget.value = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override onAction(action: any, param: any) {
|
|
||||||
let input = this.getInputData(0)
|
|
||||||
if (input == null)
|
|
||||||
input = this.properties.enable
|
|
||||||
|
|
||||||
let enabled = Boolean(input)
|
|
||||||
|
|
||||||
if (typeof param === "object" && "enabled" in param)
|
|
||||||
enabled = param["enabled"]
|
|
||||||
|
|
||||||
const tags = this.properties.targetTags.split(",").map(s => s.trim());
|
|
||||||
|
|
||||||
for (const node of this.graph._nodes) {
|
|
||||||
if ("tags" in node.properties) {
|
|
||||||
const comfyNode = node as ComfyGraphNode;
|
|
||||||
const hasTag = tags.some(t => comfyNode.properties.tags.indexOf(t) != -1);
|
|
||||||
if (hasTag) {
|
|
||||||
let newMode: NodeMode;
|
|
||||||
if (enabled) {
|
|
||||||
newMode = NodeMode.ALWAYS;
|
|
||||||
} else {
|
|
||||||
newMode = NodeMode.NEVER;
|
|
||||||
}
|
|
||||||
node.changeMode(newMode);
|
|
||||||
if ("notifyPropsChanged" in node)
|
|
||||||
(node as ComfyWidgetNode).notifyPropsChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const entry of Object.values(get(this.layoutState).allItems)) {
|
|
||||||
if (entry.dragItem.type === "container") {
|
|
||||||
const container = entry.dragItem;
|
|
||||||
const hasTag = tags.some(t => container.attrs.tags.indexOf(t) != -1);
|
|
||||||
if (hasTag) {
|
|
||||||
container.attrs.hidden = !enabled;
|
|
||||||
}
|
|
||||||
container.attrsChanged.set(get(container.attrsChanged) + 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LiteGraph.registerNodeType({
|
|
||||||
class: ComfySetNodeModeAction,
|
|
||||||
title: "Comfy.SetNodeModeAction",
|
|
||||||
desc: "Sets a group of nodes/UI containers as enabled/disabled based on their tags (comma-separated)",
|
|
||||||
type: "actions/set_node_mode"
|
|
||||||
})
|
|
||||||
|
|
||||||
export type TagAction = {
|
|
||||||
tag: string,
|
|
||||||
enable: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ComfySetNodeModeAdvancedActionProperties extends ComfyGraphNodeProperties {
|
|
||||||
targetTags: TagAction[],
|
|
||||||
enable: boolean,
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ComfySetNodeModeAdvancedAction extends ComfyGraphNode {
|
|
||||||
override properties: ComfySetNodeModeAdvancedActionProperties = {
|
|
||||||
targetTags: [{ tag: "myTag", enable: true }, { tag: "anotherTag", enable: false }],
|
|
||||||
enable: true,
|
|
||||||
tags: []
|
|
||||||
}
|
|
||||||
|
|
||||||
static slotLayout: SlotLayout = {
|
|
||||||
inputs: [
|
|
||||||
{ name: "enabled", type: "boolean" },
|
|
||||||
{ name: "set", type: BuiltInSlotType.ACTION },
|
|
||||||
],
|
|
||||||
}
|
|
||||||
|
|
||||||
static propertyLayout: PropertyLayout = [
|
|
||||||
{ name: "enable", defaultValue: true, type: "boolean", },
|
|
||||||
{ name: "targetTags", defaultValue: [{ tag: "myTag", enable: true }, { tag: "anotherTag", enable: false }], type: "array", options: { multiline: true, inputStyle: { fontFamily: "monospace" } } }
|
|
||||||
]
|
|
||||||
|
|
||||||
displayWidget: ITextWidget;
|
|
||||||
enableWidget: IToggleWidget;
|
|
||||||
|
|
||||||
constructor(title?: string) {
|
|
||||||
super(title)
|
|
||||||
this.displayWidget = this.addWidget("text", "Tags", this.formatTags(), null);
|
|
||||||
this.displayWidget.disabled = true;
|
|
||||||
this.enableWidget = this.addWidget("toggle", "Enable", this.properties.enable, "enable");
|
|
||||||
}
|
|
||||||
|
|
||||||
override onPropertyChanged(property: any, value: any) {
|
|
||||||
if (property === "enable") {
|
|
||||||
this.enableWidget.value = value
|
|
||||||
}
|
|
||||||
else if (property === "targetTags") {
|
|
||||||
this.displayWidget.value = this.formatTags()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private formatTags(): string {
|
|
||||||
if (!Array.isArray(this.properties.targetTags) || this.properties.targetTags.length === 0)
|
|
||||||
return "(No tags)";
|
|
||||||
return this.properties.targetTags.map(t => {
|
|
||||||
let s = t.tag
|
|
||||||
if (t.enable)
|
|
||||||
s = "+" + s
|
|
||||||
else
|
|
||||||
s = "!" + s
|
|
||||||
return s
|
|
||||||
}).join(", ")
|
|
||||||
}
|
|
||||||
|
|
||||||
private getModeChanges(action: TagAction, enable: boolean, nodeChanges: Record<string, NodeMode>, widgetChanges: Record<DragItemID, boolean>) {
|
|
||||||
for (const node of this.graph.iterateNodesInOrderRecursive()) {
|
|
||||||
if ("tags" in node.properties) {
|
|
||||||
const comfyNode = node as ComfyGraphNode;
|
|
||||||
const hasTag = comfyNode.properties.tags.indexOf(action.tag) != -1;
|
|
||||||
|
|
||||||
if (hasTag) {
|
|
||||||
let newMode: NodeMode;
|
|
||||||
if (enable && action.enable) {
|
|
||||||
newMode = NodeMode.ALWAYS;
|
|
||||||
} else {
|
|
||||||
newMode = NodeMode.NEVER;
|
|
||||||
}
|
|
||||||
nodeChanges[node.id] = newMode
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const entry of Object.values(get(this.layoutState).allItems)) {
|
|
||||||
if (entry.dragItem.type === "container") {
|
|
||||||
const container = entry.dragItem;
|
|
||||||
const hasTag = container.attrs.tags.indexOf(action.tag) != -1;
|
|
||||||
if (hasTag) {
|
|
||||||
const hidden = !(enable && action.enable)
|
|
||||||
widgetChanges[container.id] = hidden
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override onExecute() {
|
|
||||||
this.boxcolor = LiteGraph.NODE_DEFAULT_BOXCOLOR;
|
|
||||||
|
|
||||||
for (const action of this.properties.targetTags) {
|
|
||||||
if (typeof action !== "object" || !("tag" in action) || !("enable" in action)) {
|
|
||||||
this.boxcolor = "red";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override onAction(action: any, param: any) {
|
|
||||||
let input = this.getInputData(0)
|
|
||||||
if (input == null)
|
|
||||||
input = this.properties.enable
|
|
||||||
|
|
||||||
let enabled = Boolean(input)
|
|
||||||
|
|
||||||
if (typeof param === "object" && "enabled" in param)
|
|
||||||
enabled = param["enabled"]
|
|
||||||
|
|
||||||
const nodeChanges: Record<string, NodeMode> = {} // nodeID => newState
|
|
||||||
const widgetChanges: Record<string, boolean> = {} // dragItemID => isHidden
|
|
||||||
|
|
||||||
for (const action of this.properties.targetTags) {
|
|
||||||
this.getModeChanges(action, enabled, nodeChanges, widgetChanges)
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const [nodeId, newMode] of Object.entries(nodeChanges)) {
|
|
||||||
this.graph.getNodeByIdRecursive(nodeId).changeMode(newMode);
|
|
||||||
}
|
|
||||||
|
|
||||||
const layout = get(this.layoutState);
|
|
||||||
for (const [dragItemID, isHidden] of Object.entries(widgetChanges)) {
|
|
||||||
const container = layout.allItems[dragItemID].dragItem
|
|
||||||
container.attrs.hidden = isHidden;
|
|
||||||
container.attrsChanged.set(get(container.attrsChanged) + 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LiteGraph.registerNodeType({
|
|
||||||
class: ComfySetNodeModeAdvancedAction,
|
|
||||||
title: "Comfy.SetNodeModeAdvancedAction",
|
|
||||||
desc: "Turns multiple groups of nodes on/off at once based on an array of rules [{ tag: string, enable: boolean }, ...]",
|
|
||||||
type: "actions/set_node_mode_advanced"
|
|
||||||
})
|
|
||||||
|
|
||||||
export class ComfyNoChangeEvent extends ComfyGraphNode {
|
|
||||||
static slotLayout: SlotLayout = {
|
|
||||||
inputs: [
|
|
||||||
{ name: "in", type: BuiltInSlotType.ACTION },
|
|
||||||
],
|
|
||||||
outputs: [
|
|
||||||
{ name: "out", type: BuiltInSlotType.EVENT },
|
|
||||||
],
|
|
||||||
}
|
|
||||||
|
|
||||||
override onAction(action: any, param: any, options: { action_call?: string }) {
|
|
||||||
if (param && typeof param === "object" && "noChangedEvent" in param) {
|
|
||||||
param.noChangedEvent = true;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
param = {
|
|
||||||
value: param,
|
|
||||||
noChangedEvent: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.triggerSlot(0, param, null, options);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LiteGraph.registerNodeType({
|
|
||||||
class: ComfyNoChangeEvent,
|
|
||||||
title: "Comfy.NoChangeEvent",
|
|
||||||
desc: "Wraps an event's parameter such that passing it into a ComfyWidgetNode's 'store' action will not trigger its 'changed' event",
|
|
||||||
type: "events/no_change"
|
|
||||||
})
|
|
||||||
|
|
||||||
export interface ComfySetPromptThumbnailsActionProperties extends ComfyGraphNodeProperties {
|
|
||||||
defaultFolderType: string | null
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ComfySetPromptThumbnailsAction extends ComfyGraphNode {
|
|
||||||
override properties: ComfySetPromptThumbnailsActionProperties = {
|
|
||||||
tags: [],
|
|
||||||
defaultFolderType: "input",
|
|
||||||
}
|
|
||||||
|
|
||||||
static slotLayout: SlotLayout = {
|
|
||||||
inputs: [
|
|
||||||
{ name: "filenames", type: "*" },
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
_value: any = null;
|
|
||||||
|
|
||||||
override getPromptThumbnails(): ComfyImageLocation[] | null {
|
|
||||||
const data = this.getInputData(0)
|
|
||||||
return parseWhateverIntoComfyImageLocations(data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LiteGraph.registerNodeType({
|
|
||||||
class: ComfySetPromptThumbnailsAction,
|
|
||||||
title: "Comfy.SetPromptThumbnailsAction",
|
|
||||||
desc: "When a subgraph containing this node is executed, sets the thumbnails in the queue sidebar to the input filename(s).",
|
|
||||||
type: "actions/set_prompt_thumbnails"
|
|
||||||
})
|
|
||||||
34
src/lib/nodes/ComfyNoChangeEvent.ts
Normal file
34
src/lib/nodes/ComfyNoChangeEvent.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import { BuiltInSlotType, LiteGraph, type SlotLayout } from "@litegraph-ts/core";
|
||||||
|
import ComfyGraphNode from "./ComfyGraphNode";
|
||||||
|
|
||||||
|
export default class ComfyNoChangeEvent extends ComfyGraphNode {
|
||||||
|
static slotLayout: SlotLayout = {
|
||||||
|
inputs: [
|
||||||
|
{ name: "in", type: BuiltInSlotType.ACTION },
|
||||||
|
],
|
||||||
|
outputs: [
|
||||||
|
{ name: "out", type: BuiltInSlotType.EVENT },
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
override onAction(action: any, param: any, options: { action_call?: string }) {
|
||||||
|
if (param && typeof param === "object" && "noChangedEvent" in param) {
|
||||||
|
param.noChangedEvent = true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
param = {
|
||||||
|
value: param,
|
||||||
|
noChangedEvent: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.triggerSlot(0, param, null, options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LiteGraph.registerNodeType({
|
||||||
|
class: ComfyNoChangeEvent,
|
||||||
|
title: "Comfy.NoChangeEvent",
|
||||||
|
desc: "Wraps an event's parameter such that passing it into a ComfyWidgetNode's 'store' action will not trigger its 'changed' event",
|
||||||
|
type: "events/no_change"
|
||||||
|
})
|
||||||
55
src/lib/nodes/actions/ComfyCopyAction.ts
Normal file
55
src/lib/nodes/actions/ComfyCopyAction.ts
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import { BuiltInSlotType, LiteGraph, type ITextWidget, type SlotLayout } from "@litegraph-ts/core";
|
||||||
|
import ComfyGraphNode, { type ComfyGraphNodeProperties } from "../ComfyGraphNode";
|
||||||
|
|
||||||
|
export interface ComfyCopyActionProperties extends ComfyGraphNodeProperties {
|
||||||
|
value: any
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class ComfyCopyAction extends ComfyGraphNode {
|
||||||
|
override properties: ComfyCopyActionProperties = {
|
||||||
|
value: null,
|
||||||
|
tags: []
|
||||||
|
}
|
||||||
|
|
||||||
|
static slotLayout: SlotLayout = {
|
||||||
|
inputs: [
|
||||||
|
{ name: "in", type: "*" },
|
||||||
|
{ name: "copy", type: BuiltInSlotType.ACTION }
|
||||||
|
],
|
||||||
|
outputs: [
|
||||||
|
{ name: "out", type: BuiltInSlotType.EVENT }
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
displayWidget: ITextWidget;
|
||||||
|
|
||||||
|
constructor(title?: string) {
|
||||||
|
super(title);
|
||||||
|
this.displayWidget = this.addWidget<ITextWidget>(
|
||||||
|
"text",
|
||||||
|
"Value",
|
||||||
|
"",
|
||||||
|
"value"
|
||||||
|
);
|
||||||
|
this.displayWidget.disabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
override onExecute() {
|
||||||
|
if (this.getInputLink(0))
|
||||||
|
this.setProperty("value", this.getInputData(0))
|
||||||
|
}
|
||||||
|
|
||||||
|
override onAction(action: any, param: any) {
|
||||||
|
if (action === "copy") {
|
||||||
|
this.setProperty("value", this.getInputData(0))
|
||||||
|
this.triggerSlot(0, this.properties.value)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
LiteGraph.registerNodeType({
|
||||||
|
class: ComfyCopyAction,
|
||||||
|
title: "Comfy.CopyAction",
|
||||||
|
desc: "Copies its input to its output when an event is received",
|
||||||
|
type: "actions/copy"
|
||||||
|
})
|
||||||
52
src/lib/nodes/actions/ComfyExecuteSubgraphAction.ts
Normal file
52
src/lib/nodes/actions/ComfyExecuteSubgraphAction.ts
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import { BuiltInSlotType, LiteGraph, type ITextWidget, type SlotLayout } from "@litegraph-ts/core";
|
||||||
|
import ComfyGraphNode, { type ComfyGraphNodeProperties } from "../ComfyGraphNode";
|
||||||
|
|
||||||
|
export interface ComfyExecuteSubgraphActionProperties extends ComfyGraphNodeProperties {
|
||||||
|
targetTag: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class ComfyExecuteSubgraphAction extends ComfyGraphNode {
|
||||||
|
override properties: ComfyExecuteSubgraphActionProperties = {
|
||||||
|
tags: [],
|
||||||
|
targetTag: ""
|
||||||
|
}
|
||||||
|
|
||||||
|
static slotLayout: SlotLayout = {
|
||||||
|
inputs: [
|
||||||
|
{ name: "execute", type: BuiltInSlotType.ACTION },
|
||||||
|
{ name: "targetTag", type: "string" }
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
displayWidget: ITextWidget;
|
||||||
|
|
||||||
|
constructor(title?: string) {
|
||||||
|
super(title)
|
||||||
|
this.displayWidget = this.addWidget("text", "targetTag", this.properties.targetTag, "targetTag")
|
||||||
|
}
|
||||||
|
|
||||||
|
override onExecute() {
|
||||||
|
const tag = this.getInputData(1)
|
||||||
|
if (tag)
|
||||||
|
this.setProperty("tag", tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
override onAction(action: any, param: any) {
|
||||||
|
const tag = this.getInputData(1) || this.properties.targetTag;
|
||||||
|
|
||||||
|
const app = (window as any)?.app;
|
||||||
|
if (!app)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Hold control to queue at the front
|
||||||
|
const num = app.ctrlDown ? -1 : 0;
|
||||||
|
app.queuePrompt(num, 1, tag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LiteGraph.registerNodeType({
|
||||||
|
class: ComfyExecuteSubgraphAction,
|
||||||
|
title: "Comfy.ExecuteSubgraphAction",
|
||||||
|
desc: "Runs a part of the graph based on a tag",
|
||||||
|
type: "actions/execute_subgraph"
|
||||||
|
})
|
||||||
57
src/lib/nodes/actions/ComfyNotifyAction.ts
Normal file
57
src/lib/nodes/actions/ComfyNotifyAction.ts
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import type { NotifyOptions } from "$lib/notify";
|
||||||
|
import notify from "$lib/notify";
|
||||||
|
import { convertComfyOutputToGradio, type SerializedPromptOutput } from "$lib/utils";
|
||||||
|
import { BuiltInSlotType, LiteGraph, type SlotLayout } from "@litegraph-ts/core";
|
||||||
|
import ComfyGraphNode, { type ComfyGraphNodeProperties } from "../ComfyGraphNode";
|
||||||
|
|
||||||
|
export interface ComfyNotifyActionProperties extends ComfyGraphNodeProperties {
|
||||||
|
message: string,
|
||||||
|
type: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class ComfyNotifyAction extends ComfyGraphNode {
|
||||||
|
override properties: ComfyNotifyActionProperties = {
|
||||||
|
tags: [],
|
||||||
|
message: "Nya.",
|
||||||
|
type: "info"
|
||||||
|
}
|
||||||
|
|
||||||
|
static slotLayout: SlotLayout = {
|
||||||
|
inputs: [
|
||||||
|
{ name: "message", type: "string" },
|
||||||
|
{ name: "trigger", type: BuiltInSlotType.ACTION }
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
override onAction(action: any, param: any) {
|
||||||
|
const message = this.getInputData(0) || this.properties.message;
|
||||||
|
if (!message)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const options: NotifyOptions = {
|
||||||
|
type: this.properties.type,
|
||||||
|
showOn: "all"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if this event was triggered from a backend node and has the
|
||||||
|
// onExecuted arguments. If so then use the first image as the icon for
|
||||||
|
// native notifications.
|
||||||
|
if (param != null && typeof param === "object") {
|
||||||
|
if ("images" in param) {
|
||||||
|
const output = param as SerializedPromptOutput;
|
||||||
|
const converted = convertComfyOutputToGradio(output);
|
||||||
|
if (converted.length > 0)
|
||||||
|
options.imageUrl = converted[0].data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
notify(message, options);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
LiteGraph.registerNodeType({
|
||||||
|
class: ComfyNotifyAction,
|
||||||
|
title: "Comfy.NotifyAction",
|
||||||
|
desc: "Displays a message.",
|
||||||
|
type: "actions/notify"
|
||||||
|
})
|
||||||
36
src/lib/nodes/actions/ComfyPlaySoundAction.ts
Normal file
36
src/lib/nodes/actions/ComfyPlaySoundAction.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import { BuiltInSlotType, LiteGraph, type SlotLayout } from "@litegraph-ts/core";
|
||||||
|
import ComfyGraphNode, { type ComfyGraphNodeProperties } from "../ComfyGraphNode";
|
||||||
|
|
||||||
|
export interface ComfyPlaySoundActionProperties extends ComfyGraphNodeProperties {
|
||||||
|
sound: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class ComfyPlaySoundAction extends ComfyGraphNode {
|
||||||
|
override properties: ComfyPlaySoundActionProperties = {
|
||||||
|
tags: [],
|
||||||
|
sound: "notification.mp3"
|
||||||
|
}
|
||||||
|
|
||||||
|
static slotLayout: SlotLayout = {
|
||||||
|
inputs: [
|
||||||
|
{ name: "sound", type: "string" },
|
||||||
|
{ name: "trigger", type: BuiltInSlotType.ACTION }
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
override onAction(action: any, param: any) {
|
||||||
|
const sound = this.getInputData(0) || this.properties.sound;
|
||||||
|
if (sound) {
|
||||||
|
const url = `${location.origin}/sound/${sound}`;
|
||||||
|
const audio = new Audio(url);
|
||||||
|
audio.play();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
LiteGraph.registerNodeType({
|
||||||
|
class: ComfyPlaySoundAction,
|
||||||
|
title: "Comfy.PlaySoundAction",
|
||||||
|
desc: "Plays a sound located under the sound/ directory.",
|
||||||
|
type: "actions/play_sound"
|
||||||
|
})
|
||||||
57
src/lib/nodes/actions/ComfyQueueEvents.ts
Normal file
57
src/lib/nodes/actions/ComfyQueueEvents.ts
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import type { SerializedPrompt } from "$lib/components/ComfyApp";
|
||||||
|
import queueState from "$lib/stores/queueState";
|
||||||
|
import { BuiltInSlotType, LiteGraph, type SerializedLGraphNode, type SlotLayout } from "@litegraph-ts/core";
|
||||||
|
import { get } from "svelte/store";
|
||||||
|
import ComfyGraphNode from "../ComfyGraphNode";
|
||||||
|
|
||||||
|
export default class ComfyQueueEvents extends ComfyGraphNode {
|
||||||
|
static slotLayout: SlotLayout = {
|
||||||
|
outputs: [
|
||||||
|
{ name: "beforeQueued", type: BuiltInSlotType.EVENT },
|
||||||
|
{ name: "afterQueued", type: BuiltInSlotType.EVENT },
|
||||||
|
{ name: "onDefaultQueueAction", type: BuiltInSlotType.EVENT },
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
private getActionParams(subgraph: string | null): any {
|
||||||
|
let queue = get(queueState)
|
||||||
|
let queueRemaining = 0;
|
||||||
|
|
||||||
|
if (typeof queue.queueRemaining === "number")
|
||||||
|
queueRemaining = queue.queueRemaining
|
||||||
|
|
||||||
|
return {
|
||||||
|
queueRemaining,
|
||||||
|
subgraph
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override beforeQueued(subgraph: string | null) {
|
||||||
|
this.triggerSlot(0, this.getActionParams(subgraph))
|
||||||
|
}
|
||||||
|
|
||||||
|
override afterQueued(p: SerializedPrompt, subgraph: string | null) {
|
||||||
|
this.triggerSlot(1, this.getActionParams(subgraph))
|
||||||
|
}
|
||||||
|
|
||||||
|
override onDefaultQueueAction() {
|
||||||
|
let queue = get(queueState)
|
||||||
|
let queueRemaining = 0;
|
||||||
|
|
||||||
|
if (typeof queue.queueRemaining === "number")
|
||||||
|
queueRemaining = queue.queueRemaining
|
||||||
|
|
||||||
|
this.triggerSlot(2, { queueRemaining })
|
||||||
|
}
|
||||||
|
|
||||||
|
override onSerialize(o: SerializedLGraphNode) {
|
||||||
|
super.onSerialize(o)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LiteGraph.registerNodeType({
|
||||||
|
class: ComfyQueueEvents,
|
||||||
|
title: "Comfy.QueueEvents",
|
||||||
|
desc: "Triggers a 'bang' event when a prompt is queued.",
|
||||||
|
type: "events/queue_events"
|
||||||
|
})
|
||||||
88
src/lib/nodes/actions/ComfySetNodeModeAction.ts
Normal file
88
src/lib/nodes/actions/ComfySetNodeModeAction.ts
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
import type { ComfyWidgetNode } from "$lib/nodes/widgets";
|
||||||
|
import { BuiltInSlotType, LiteGraph, NodeMode, type ITextWidget, type IToggleWidget, type SlotLayout } from "@litegraph-ts/core";
|
||||||
|
import { get } from "svelte/store";
|
||||||
|
import ComfyGraphNode, { type ComfyGraphNodeProperties } from "../ComfyGraphNode";
|
||||||
|
|
||||||
|
export interface ComfySetNodeModeActionProperties extends ComfyGraphNodeProperties {
|
||||||
|
targetTags: string,
|
||||||
|
enable: boolean,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class ComfySetNodeModeAction extends ComfyGraphNode {
|
||||||
|
override properties: ComfySetNodeModeActionProperties = {
|
||||||
|
targetTags: "",
|
||||||
|
enable: false,
|
||||||
|
tags: []
|
||||||
|
}
|
||||||
|
|
||||||
|
static slotLayout: SlotLayout = {
|
||||||
|
inputs: [
|
||||||
|
{ name: "enabled", type: "boolean" },
|
||||||
|
{ name: "set", type: BuiltInSlotType.ACTION },
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
displayWidget: ITextWidget;
|
||||||
|
enableWidget: IToggleWidget;
|
||||||
|
|
||||||
|
constructor(title?: string) {
|
||||||
|
super(title)
|
||||||
|
this.displayWidget = this.addWidget("text", "Tags", this.properties.targetTags, "targetTags")
|
||||||
|
this.enableWidget = this.addWidget("toggle", "Enable", this.properties.enable, "enable");
|
||||||
|
}
|
||||||
|
|
||||||
|
override onPropertyChanged(property: any, value: any) {
|
||||||
|
if (property === "enable") {
|
||||||
|
this.enableWidget.value = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override onAction(action: any, param: any) {
|
||||||
|
let input = this.getInputData(0)
|
||||||
|
if (input == null)
|
||||||
|
input = this.properties.enable
|
||||||
|
|
||||||
|
let enabled = Boolean(input)
|
||||||
|
|
||||||
|
if (typeof param === "object" && "enabled" in param)
|
||||||
|
enabled = param["enabled"]
|
||||||
|
|
||||||
|
const tags = this.properties.targetTags.split(",").map(s => s.trim());
|
||||||
|
|
||||||
|
for (const node of this.graph._nodes) {
|
||||||
|
if ("tags" in node.properties) {
|
||||||
|
const comfyNode = node as ComfyGraphNode;
|
||||||
|
const hasTag = tags.some(t => comfyNode.properties.tags.indexOf(t) != -1);
|
||||||
|
if (hasTag) {
|
||||||
|
let newMode: NodeMode;
|
||||||
|
if (enabled) {
|
||||||
|
newMode = NodeMode.ALWAYS;
|
||||||
|
} else {
|
||||||
|
newMode = NodeMode.NEVER;
|
||||||
|
}
|
||||||
|
node.changeMode(newMode);
|
||||||
|
if ("notifyPropsChanged" in node)
|
||||||
|
(node as ComfyWidgetNode).notifyPropsChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const entry of Object.values(get(this.layoutState).allItems)) {
|
||||||
|
if (entry.dragItem.type === "container") {
|
||||||
|
const container = entry.dragItem;
|
||||||
|
const hasTag = tags.some(t => container.attrs.tags.indexOf(t) != -1);
|
||||||
|
if (hasTag) {
|
||||||
|
container.attrs.hidden = !enabled;
|
||||||
|
}
|
||||||
|
container.attrsChanged.set(get(container.attrsChanged) + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LiteGraph.registerNodeType({
|
||||||
|
class: ComfySetNodeModeAction,
|
||||||
|
title: "Comfy.SetNodeModeAction",
|
||||||
|
desc: "Sets a group of nodes/UI containers as enabled/disabled based on their tags (comma-separated)",
|
||||||
|
type: "actions/set_node_mode"
|
||||||
|
})
|
||||||
143
src/lib/nodes/actions/ComfySetNodeModeAdvancedAction.ts
Normal file
143
src/lib/nodes/actions/ComfySetNodeModeAdvancedAction.ts
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
import { type DragItemID } from "$lib/stores/layoutStates";
|
||||||
|
import { BuiltInSlotType, LiteGraph, NodeMode, type ITextWidget, type IToggleWidget, type PropertyLayout, type SlotLayout } from "@litegraph-ts/core";
|
||||||
|
import { get } from "svelte/store";
|
||||||
|
import ComfyGraphNode, { type ComfyGraphNodeProperties } from "../ComfyGraphNode";
|
||||||
|
|
||||||
|
export type TagAction = {
|
||||||
|
tag: string,
|
||||||
|
enable: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ComfySetNodeModeAdvancedActionProperties extends ComfyGraphNodeProperties {
|
||||||
|
targetTags: TagAction[],
|
||||||
|
enable: boolean,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class ComfySetNodeModeAdvancedAction extends ComfyGraphNode {
|
||||||
|
override properties: ComfySetNodeModeAdvancedActionProperties = {
|
||||||
|
targetTags: [{ tag: "myTag", enable: true }, { tag: "anotherTag", enable: false }],
|
||||||
|
enable: true,
|
||||||
|
tags: []
|
||||||
|
}
|
||||||
|
|
||||||
|
static slotLayout: SlotLayout = {
|
||||||
|
inputs: [
|
||||||
|
{ name: "enabled", type: "boolean" },
|
||||||
|
{ name: "set", type: BuiltInSlotType.ACTION },
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
static propertyLayout: PropertyLayout = [
|
||||||
|
{ name: "enable", defaultValue: true, type: "boolean", },
|
||||||
|
{ name: "targetTags", defaultValue: [{ tag: "myTag", enable: true }, { tag: "anotherTag", enable: false }], type: "array", options: { multiline: true, inputStyle: { fontFamily: "monospace" } } }
|
||||||
|
]
|
||||||
|
|
||||||
|
displayWidget: ITextWidget;
|
||||||
|
enableWidget: IToggleWidget;
|
||||||
|
|
||||||
|
constructor(title?: string) {
|
||||||
|
super(title)
|
||||||
|
this.displayWidget = this.addWidget("text", "Tags", this.formatTags(), null);
|
||||||
|
this.displayWidget.disabled = true;
|
||||||
|
this.enableWidget = this.addWidget("toggle", "Enable", this.properties.enable, "enable");
|
||||||
|
}
|
||||||
|
|
||||||
|
override onPropertyChanged(property: any, value: any) {
|
||||||
|
if (property === "enable") {
|
||||||
|
this.enableWidget.value = value
|
||||||
|
}
|
||||||
|
else if (property === "targetTags") {
|
||||||
|
this.displayWidget.value = this.formatTags()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private formatTags(): string {
|
||||||
|
if (!Array.isArray(this.properties.targetTags) || this.properties.targetTags.length === 0)
|
||||||
|
return "(No tags)";
|
||||||
|
return this.properties.targetTags.map(t => {
|
||||||
|
let s = t.tag
|
||||||
|
if (t.enable)
|
||||||
|
s = "+" + s
|
||||||
|
else
|
||||||
|
s = "!" + s
|
||||||
|
return s
|
||||||
|
}).join(", ")
|
||||||
|
}
|
||||||
|
|
||||||
|
private getModeChanges(action: TagAction, enable: boolean, nodeChanges: Record<string, NodeMode>, widgetChanges: Record<DragItemID, boolean>) {
|
||||||
|
for (const node of this.graph.iterateNodesInOrderRecursive()) {
|
||||||
|
if ("tags" in node.properties) {
|
||||||
|
const comfyNode = node as ComfyGraphNode;
|
||||||
|
const hasTag = comfyNode.properties.tags.indexOf(action.tag) != -1;
|
||||||
|
|
||||||
|
if (hasTag) {
|
||||||
|
let newMode: NodeMode;
|
||||||
|
if (enable && action.enable) {
|
||||||
|
newMode = NodeMode.ALWAYS;
|
||||||
|
} else {
|
||||||
|
newMode = NodeMode.NEVER;
|
||||||
|
}
|
||||||
|
nodeChanges[node.id] = newMode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const entry of Object.values(get(this.layoutState).allItems)) {
|
||||||
|
if (entry.dragItem.type === "container") {
|
||||||
|
const container = entry.dragItem;
|
||||||
|
const hasTag = container.attrs.tags.indexOf(action.tag) != -1;
|
||||||
|
if (hasTag) {
|
||||||
|
const hidden = !(enable && action.enable)
|
||||||
|
widgetChanges[container.id] = hidden
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override onExecute() {
|
||||||
|
this.boxcolor = LiteGraph.NODE_DEFAULT_BOXCOLOR;
|
||||||
|
|
||||||
|
for (const action of this.properties.targetTags) {
|
||||||
|
if (typeof action !== "object" || !("tag" in action) || !("enable" in action)) {
|
||||||
|
this.boxcolor = "red";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override onAction(action: any, param: any) {
|
||||||
|
let input = this.getInputData(0)
|
||||||
|
if (input == null)
|
||||||
|
input = this.properties.enable
|
||||||
|
|
||||||
|
let enabled = Boolean(input)
|
||||||
|
|
||||||
|
if (typeof param === "object" && "enabled" in param)
|
||||||
|
enabled = param["enabled"]
|
||||||
|
|
||||||
|
const nodeChanges: Record<string, NodeMode> = {} // nodeID => newState
|
||||||
|
const widgetChanges: Record<string, boolean> = {} // dragItemID => isHidden
|
||||||
|
|
||||||
|
for (const action of this.properties.targetTags) {
|
||||||
|
this.getModeChanges(action, enabled, nodeChanges, widgetChanges)
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [nodeId, newMode] of Object.entries(nodeChanges)) {
|
||||||
|
this.graph.getNodeByIdRecursive(nodeId).changeMode(newMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
const layout = get(this.layoutState);
|
||||||
|
for (const [dragItemID, isHidden] of Object.entries(widgetChanges)) {
|
||||||
|
const container = layout.allItems[dragItemID].dragItem
|
||||||
|
container.attrs.hidden = isHidden;
|
||||||
|
container.attrsChanged.set(get(container.attrsChanged) + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LiteGraph.registerNodeType({
|
||||||
|
class: ComfySetNodeModeAdvancedAction,
|
||||||
|
title: "Comfy.SetNodeModeAdvancedAction",
|
||||||
|
desc: "Turns multiple groups of nodes on/off at once based on an array of rules [{ tag: string, enable: boolean }, ...]",
|
||||||
|
type: "actions/set_node_mode_advanced"
|
||||||
|
})
|
||||||
34
src/lib/nodes/actions/ComfySetPromptThumbnailsAction.ts
Normal file
34
src/lib/nodes/actions/ComfySetPromptThumbnailsAction.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import { parseWhateverIntoComfyImageLocations, type ComfyImageLocation } from "$lib/utils";
|
||||||
|
import { LiteGraph, type SlotLayout } from "@litegraph-ts/core";
|
||||||
|
import ComfyGraphNode, { type ComfyGraphNodeProperties } from "../ComfyGraphNode";
|
||||||
|
|
||||||
|
export interface ComfySetPromptThumbnailsActionProperties extends ComfyGraphNodeProperties {
|
||||||
|
defaultFolderType: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class ComfySetPromptThumbnailsAction extends ComfyGraphNode {
|
||||||
|
override properties: ComfySetPromptThumbnailsActionProperties = {
|
||||||
|
tags: [],
|
||||||
|
defaultFolderType: "input",
|
||||||
|
}
|
||||||
|
|
||||||
|
static slotLayout: SlotLayout = {
|
||||||
|
inputs: [
|
||||||
|
{ name: "filenames", type: "*" },
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
_value: any = null;
|
||||||
|
|
||||||
|
override getPromptThumbnails(): ComfyImageLocation[] | null {
|
||||||
|
const data = this.getInputData(0)
|
||||||
|
return parseWhateverIntoComfyImageLocations(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LiteGraph.registerNodeType({
|
||||||
|
class: ComfySetPromptThumbnailsAction,
|
||||||
|
title: "Comfy.SetPromptThumbnailsAction",
|
||||||
|
desc: "When a subgraph containing this node is executed, sets the thumbnails in the queue sidebar to the input filename(s).",
|
||||||
|
type: "actions/set_prompt_thumbnails"
|
||||||
|
})
|
||||||
43
src/lib/nodes/actions/ComfyStoreImagesAction.ts
Normal file
43
src/lib/nodes/actions/ComfyStoreImagesAction.ts
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import { type SerializedPromptOutput } from "$lib/utils";
|
||||||
|
import { BuiltInSlotType, LiteGraph, type SlotLayout } from "@litegraph-ts/core";
|
||||||
|
import ComfyGraphNode, { type ComfyGraphNodeProperties } from "../ComfyGraphNode";
|
||||||
|
|
||||||
|
export interface ComfyStoreImagesActionProperties extends ComfyGraphNodeProperties {
|
||||||
|
images: SerializedPromptOutput | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class ComfyStoreImagesAction extends ComfyGraphNode {
|
||||||
|
override properties: ComfyStoreImagesActionProperties = {
|
||||||
|
tags: [],
|
||||||
|
images: null
|
||||||
|
}
|
||||||
|
|
||||||
|
static slotLayout: SlotLayout = {
|
||||||
|
inputs: [
|
||||||
|
{ name: "output", type: BuiltInSlotType.ACTION, options: { color_off: "rebeccapurple", color_on: "rebeccapurple" } }
|
||||||
|
],
|
||||||
|
outputs: [
|
||||||
|
{ name: "images", type: "OUTPUT" },
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
override onExecute() {
|
||||||
|
if (this.properties.images !== null)
|
||||||
|
this.setOutputData(0, this.properties.images)
|
||||||
|
}
|
||||||
|
|
||||||
|
override onAction(action: any, param: any) {
|
||||||
|
if (action !== "store" || !param || !("images" in param))
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.setProperty("images", param as SerializedPromptOutput)
|
||||||
|
this.setOutputData(0, this.properties.images)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LiteGraph.registerNodeType({
|
||||||
|
class: ComfyStoreImagesAction,
|
||||||
|
title: "Comfy.StoreImagesAction",
|
||||||
|
desc: "Stores images from an onExecuted callback",
|
||||||
|
type: "actions/store_images"
|
||||||
|
})
|
||||||
30
src/lib/nodes/actions/ComfySwapAction.ts
Normal file
30
src/lib/nodes/actions/ComfySwapAction.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import { BuiltInSlotType, LiteGraph, type SlotLayout } from "@litegraph-ts/core";
|
||||||
|
import ComfyGraphNode from "../ComfyGraphNode";
|
||||||
|
|
||||||
|
export default class ComfySwapAction extends ComfyGraphNode {
|
||||||
|
static slotLayout: SlotLayout = {
|
||||||
|
inputs: [
|
||||||
|
{ name: "A", type: "*" },
|
||||||
|
{ name: "B", type: "*" },
|
||||||
|
{ name: "swap", type: BuiltInSlotType.ACTION }
|
||||||
|
],
|
||||||
|
outputs: [
|
||||||
|
{ name: "B", type: BuiltInSlotType.EVENT },
|
||||||
|
{ name: "A", type: BuiltInSlotType.EVENT }
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
override onAction(action: any, param: any) {
|
||||||
|
const a = this.getInputData(0)
|
||||||
|
const b = this.getInputData(1)
|
||||||
|
this.triggerSlot(0, b)
|
||||||
|
this.triggerSlot(1, a)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
LiteGraph.registerNodeType({
|
||||||
|
class: ComfySwapAction,
|
||||||
|
title: "Comfy.SwapAction",
|
||||||
|
desc: "Swaps two inputs when triggered",
|
||||||
|
type: "actions/swap"
|
||||||
|
})
|
||||||
10
src/lib/nodes/actions/index.ts
Normal file
10
src/lib/nodes/actions/index.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
export { default as ComfyCopyAction } from "./ComfyCopyAction"
|
||||||
|
export { default as ComfyExecuteSubgraphAction } from "./ComfyExecuteSubgraphAction"
|
||||||
|
export { default as ComfyNotifyAction } from "./ComfyNotifyAction"
|
||||||
|
export { default as ComfyPlaySoundAction } from "./ComfyPlaySoundAction"
|
||||||
|
export { default as ComfyQueueEvents } from "./ComfyQueueEvents"
|
||||||
|
export { default as ComfySetNodeModeAction } from "./ComfySetNodeModeAction"
|
||||||
|
export { default as ComfySetNodeModeAdvancedAction } from "./ComfySetNodeModeAdvancedAction"
|
||||||
|
export { default as ComfySetPromptThumbnailsAction } from "./ComfySetPromptThumbnailsAction"
|
||||||
|
export { default as ComfyStoreImagesAction } from "./ComfyStoreImagesAction"
|
||||||
|
export { default as ComfySwapAction } from "./ComfySwapAction"
|
||||||
@@ -1,18 +1,8 @@
|
|||||||
export { default as ComfyReroute } from "./ComfyReroute"
|
export { default as ComfyReroute } from "./ComfyReroute"
|
||||||
export {
|
|
||||||
ComfyQueueEvents,
|
|
||||||
ComfyCopyAction,
|
|
||||||
ComfySwapAction,
|
|
||||||
ComfyNotifyAction,
|
|
||||||
ComfyPlaySoundAction,
|
|
||||||
ComfyStoreImagesAction,
|
|
||||||
ComfyExecuteSubgraphAction,
|
|
||||||
ComfySetNodeModeAction,
|
|
||||||
ComfySetNodeModeAdvancedAction
|
|
||||||
} from "./ComfyActionNodes"
|
|
||||||
export { default as ComfyPickFirstNode } from "./ComfyPickFirstNode"
|
export { default as ComfyPickFirstNode } from "./ComfyPickFirstNode"
|
||||||
export { default as ComfyValueControl } from "./ComfyValueControl"
|
export { default as ComfyValueControl } from "./ComfyValueControl"
|
||||||
export { default as ComfySelector } from "./ComfySelector"
|
export { default as ComfySelector } from "./ComfySelector"
|
||||||
export { default as ComfyTriggerNewEventNode } from "./ComfyTriggerNewEventNode"
|
export { default as ComfyTriggerNewEventNode } from "./ComfyTriggerNewEventNode"
|
||||||
export { default as ComfyConfigureQueuePromptButton } from "./ComfyConfigureQueuePromptButton"
|
export { default as ComfyConfigureQueuePromptButton } from "./ComfyConfigureQueuePromptButton"
|
||||||
export { default as ComfyPickImageNode } from "./ComfyPickImageNode"
|
export { default as ComfyPickImageNode } from "./ComfyPickImageNode"
|
||||||
|
export { default as ComfyNoChangeEvent } from "./ComfyNoChangeEvent"
|
||||||
|
|||||||
Reference in New Issue
Block a user