Better mode switching action

This commit is contained in:
space-nuko
2023-05-09 11:36:02 -05:00
parent 2df43c6b04
commit 30198c3808
6 changed files with 1070 additions and 848 deletions

View File

@@ -1,8 +1,8 @@
import type { SerializedPrompt } from "$lib/components/ComfyApp"; import type { SerializedPrompt } from "$lib/components/ComfyApp";
import notify from "$lib/notify"; import notify from "$lib/notify";
import layoutState from "$lib/stores/layoutState"; import layoutState, { type DragItemID } from "$lib/stores/layoutState";
import queueState from "$lib/stores/queueState"; import queueState from "$lib/stores/queueState";
import { BuiltInSlotType, LiteGraph, NodeMode, type ITextWidget, type IToggleWidget, type SerializedLGraphNode, type SlotLayout } from "@litegraph-ts/core"; import { BuiltInSlotType, LiteGraph, NodeMode, type ITextWidget, type IToggleWidget, type SerializedLGraphNode, type SlotLayout, type PropertyLayout } from "@litegraph-ts/core";
import { get } from "svelte/store"; import { get } from "svelte/store";
import ComfyGraphNode, { type ComfyGraphNodeProperties } from "./ComfyGraphNode"; import ComfyGraphNode, { type ComfyGraphNodeProperties } from "./ComfyGraphNode";
import type { ComfyWidgetNode, GalleryOutput } from "./ComfyWidgetNodes"; import type { ComfyWidgetNode, GalleryOutput } from "./ComfyWidgetNodes";
@@ -279,16 +279,21 @@ export class ComfySetNodeModeAction extends ComfyGraphNode {
constructor(title?: string) { constructor(title?: string) {
super(title) super(title)
this.displayWidget = this.addWidget("text", "Tags", this.properties.targetTags, "targetTags") 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) { override onPropertyChanged(property: any, value: any) {
if (property === "enabled") { if (property === "enable") {
this.enableWidget.value = value this.enableWidget.value = value
} }
} }
override onAction(action: any, param: any) { override onAction(action: any, param: any) {
let enabled = this.getInputData(0) let input = this.getInputData(0)
if (input == null)
input = this.properties.enable
let enabled = Boolean(input)
if (typeof param === "object" && "enabled" in param) if (typeof param === "object" && "enabled" in param)
enabled = param["enabled"] enabled = param["enabled"]
@@ -332,3 +337,145 @@ LiteGraph.registerNodeType({
desc: "Sets a group of nodes/UI containers as enabled/disabled based on their tags (comma-separated)", desc: "Sets a group of nodes/UI containers as enabled/disabled based on their tags (comma-separated)",
type: "actions/set_node_mode" 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._nodes) {
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
node.changeMode(newMode);
if ("notifyPropsChanged" in node)
(node as ComfyWidgetNode).notifyPropsChanged();
}
}
}
for (const entry of Object.values(get(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.getNodeById(parseInt(nodeId)).changeMode(newMode);
}
const layout = get(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"
})

View File

@@ -0,0 +1,56 @@
import { BuiltInSlotType, LGraphNode, LiteGraph, type ITextWidget, type SlotLayout, type PropertyLayout } from "@litegraph-ts/core"
import type { ComfyGraphNodeProperties } from "./ComfyGraphNode";
import { Watch } from "@litegraph-ts/nodes-basic";
export interface ComfyTriggerNewEventNodeProperties extends ComfyGraphNodeProperties {
param: any,
}
export default class ComfyTriggerNewEventNode extends LGraphNode {
override properties: ComfyTriggerNewEventNodeProperties = {
param: true,
tags: []
}
static slotLayout: SlotLayout = {
inputs: [
{ name: "in", type: BuiltInSlotType.ACTION },
{ name: "param", type: "*" },
],
outputs: [
{ name: "out", type: BuiltInSlotType.EVENT },
],
}
paramWidget: ITextWidget;
constructor(title?: string) {
super(title)
this.paramWidget = this.addWidget("text", "Param", Watch.toString(this.properties.param), "param")
}
override onPropertyChanged(property: any, value: any) {
if (property === "param")
this.paramWidget.value = Watch.toString(value)
}
override onExecute() {
const newParam = this.getInputData(1);
if (newParam != null)
this.setProperty("param", newParam)
}
override onAction(action: any, param: any, options: { action_call?: string }) {
let newParam = this.getInputData(1);
if (newParam == null)
newParam = this.properties.param
this.triggerSlot(0, newParam, null, options);
}
}
LiteGraph.registerNodeType({
class: ComfyTriggerNewEventNode,
title: "Comfy.TriggerNewEvent",
desc: "Triggers a new event with the specified parameter when an event is received",
type: "events/trigger_new_event"
})

View File

@@ -67,6 +67,7 @@ export abstract class ComfyWidgetNode<T = any> extends ComfyGraphNode {
private _aboutToChange: number = 0; private _aboutToChange: number = 0;
private _aboutToChangeValue: any = null; private _aboutToChangeValue: any = null;
private _noChangedEvent: boolean = false;
abstract defaultValue: T; abstract defaultValue: T;
@@ -84,6 +85,7 @@ export abstract class ComfyWidgetNode<T = any> extends ComfyGraphNode {
// TODO these are bad, create override methods instead // TODO these are bad, create override methods instead
// input slots // input slots
inputIndex: number = 0; inputIndex: number = 0;
storeActionName: string | null = "store";
// output slots // output slots
outputIndex: number | null = 0; outputIndex: number | null = 0;
@@ -139,31 +141,44 @@ export abstract class ComfyWidgetNode<T = any> extends ComfyGraphNode {
this.setOutputData(this.outputIndex, get(this.value)) this.setOutputData(this.outputIndex, get(this.value))
} }
if (this.changedIndex !== null && this.outputs.length >= this.changedIndex) { if (this.changedIndex !== null && this.outputs.length >= this.changedIndex && !this._noChangedEvent) {
if (!this.delayChangedEvent) if (!this.delayChangedEvent)
this.triggerChangeEvent(get(this.value)) this.triggerChangeEvent(get(this.value))
else { else {
this._aboutToChange = 2; // wait 1.5-2 frames, in case we're already in the middle of one console.debug("[Widget] queueChangeEvent", this, value)
this._aboutToChange = 2; // wait 1.5-2 frames, in case we're already in the middle of executing the graph
this._aboutToChangeValue = get(this.value); this._aboutToChangeValue = get(this.value);
} }
} }
this._noChangedEvent = false;
} }
private triggerChangeEvent(value: any) { private triggerChangeEvent(value: any) {
console.debug("[Widget] trigger changed", this, value)
const changedOutput = this.outputs[this.changedIndex] const changedOutput = this.outputs[this.changedIndex]
if (changedOutput.type === BuiltInSlotType.EVENT) if (changedOutput.type === BuiltInSlotType.EVENT)
this.triggerSlot(this.changedIndex, value) this.triggerSlot(this.changedIndex, value)
} }
setValue(value: any) { parseValue(value: any): T { return value as T };
this.value.set(value)
setValue(value: any, noChangedEvent: boolean = false) {
if (noChangedEvent)
this._noChangedEvent = true;
this.value.set(this.parseValue(value))
// In case value.set() does not trigger onValueUpdated, we need to reset
// the counter here also.
this._noChangedEvent = false;
} }
override onPropertyChanged(property: string, value: any, prevValue?: any) { override onPropertyChanged(property: string, value: any, prevValue?: any) {
if (this.shownOutputProperties != null) {
const data = this.shownOutputProperties[property] const data = this.shownOutputProperties[property]
if (data) if (data)
this.setOutputData(data.index, value) this.setOutputData(data.index, value)
} }
}
/* /*
* Logic to run if this widget can be treated as output (slider, combo, text) * Logic to run if this widget can be treated as output (slider, combo, text)
@@ -198,6 +213,21 @@ export abstract class ComfyWidgetNode<T = any> extends ComfyGraphNode {
} }
} }
override onAction(action: any, param: any, options: { action_call?: string }) {
if (action === this.storeActionName) {
let noChangedEvent = false;
let value = param;
if (param != null && typeof param === "object" && "value" in param) {
value = param.value
if ("noChangedEvent" in param)
noChangedEvent = Boolean(param.noChangedEvent)
}
value = this.parseValue(value);
console.warn("[Widget] Store!", param, "=>", value, noChangedEvent)
this.setValue(value, noChangedEvent)
}
}
onConnectOutput( onConnectOutput(
outputIndex: number, outputIndex: number,
inputType: INodeInputSlot["type"], inputType: INodeInputSlot["type"],
@@ -337,15 +367,10 @@ export class ComfySliderNode extends ComfyWidgetNode<number> {
super(name, 0) super(name, 0)
} }
override onAction(action: any, param: any) { override parseValue(value: any): number {
if (action === "store" && typeof param === "number")
this.setValue(param)
}
override setValue(value: any) {
if (typeof value !== "number") if (typeof value !== "number")
return; return this.properties.min;
super.setValue(clamp(value, this.properties.min, this.properties.max)) return clamp(value, this.properties.min, this.properties.max)
} }
override clampOneConfig(input: IComfyInputSlot) { override clampOneConfig(input: IComfyInputSlot) {
@@ -422,15 +447,10 @@ export class ComfyComboNode extends ComfyWidgetNode<string> {
return true; return true;
} }
override onAction(action: any, param: any) { override parseValue(value: any): string {
if (action === "store" && typeof param === "string")
this.setValue(param)
}
override setValue(value: any) {
if (typeof value !== "string" || this.properties.values.indexOf(value) === -1) if (typeof value !== "string" || this.properties.values.indexOf(value) === -1)
return; return this.properties.values[0]
super.setValue(value) return value
} }
override clampOneConfig(input: IComfyInputSlot) { override clampOneConfig(input: IComfyInputSlot) {
@@ -486,13 +506,8 @@ export class ComfyTextNode extends ComfyWidgetNode<string> {
super(name, "") super(name, "")
} }
override parseValue(value: any): string {
override onAction(action: any, param: any) { return `${value}`
if (action === "store")
this.setValue(param)
}
override setValue(value: any) {
} }
} }
@@ -578,49 +593,42 @@ export class ComfyGalleryNode extends ComfyWidgetNode<GradioFileData[]> {
} }
override onAction(action: any, param: any, options: { action_call?: string }) { override onAction(action: any, param: any, options: { action_call?: string }) {
super.onAction(action, param, options)
if (action === "clear") { if (action === "clear") {
this.setValue([]) this.setValue([])
} }
}
else if (action === "store") {
if (param && "images" in param) {
const data = param as GalleryOutput
console.debug("[ComfyGalleryNode] Received output!", data)
const galleryItems: GradioFileData[] = convertComfyOutputToGradio(data)
if (this.properties.updateMode === "append") {
const currentValue = get(this.value)
this.setValue(currentValue.concat(galleryItems))
}
else {
this.setValue(galleryItems)
}
}
this.setProperty("index", 0)
this.anyImageSelected = false;
} }
override formatValue(value: GradioFileData[] | null): string { override formatValue(value: GradioFileData[] | null): string {
return `Images: ${value?.length || 0}` return `Images: ${value?.length || 0}`
} }
override parseValue(param: any): GradioFileData[] {
override setValue(value: any) { if (!(typeof param === "object" && "images" in param)) {
console.warn("SETVALUE", value) return []
if (Array.isArray(value)) { }
const data = param as GalleryOutput
console.debug("[ComfyGalleryNode] Received output!", data)
const galleryItems: GradioFileData[] = convertComfyOutputToGradio(data)
if (this.properties.updateMode === "append") {
const currentValue = get(this.value)
return currentValue.concat(galleryItems)
} }
else { else {
else { return galleryItems;
}
} }
override setValue(value: any, noChangedEvent: boolean = false) {
if (!get(this.value)) console.warn("SETVALUE", value)
super.setValue(value, noChangedEvent)
this.setProperty("index", 0)
const len = get(this.value).length this.anyImageSelected = false;
if (this.properties.index < 0 || this.properties.index >= len) {
this.setProperty("index", clamp(this.properties.index, 0, len))
} }
} }
@@ -657,8 +665,8 @@ export class ComfyButtonNode extends ComfyWidgetNode<boolean> {
super(name, false) super(name, false)
} }
override parseValue(param: any): boolean {
override setValue(value: any) { return Boolean(param);
} }
onClick() { onClick() {
@@ -697,22 +705,14 @@ export class ComfyCheckboxNode extends ComfyWidgetNode<boolean> {
override svelteComponentType = CheckboxWidget; override svelteComponentType = CheckboxWidget;
override defaultValue = false; override defaultValue = false;
override changedIndex = 1;
constructor(name?: string) { constructor(name?: string) {
super(name, false) super(name, false)
} }
override parseValue(param: any) {
override setValue(value: any) { return Boolean(param);
value = Boolean(value)
const changed = value != get(this.value);
super.setValue(Boolean(value))
if (changed)
this.triggerSlot(1, value)
}
override onAction(action: any, param: any) {
if (action === "store")
} }
} }
@@ -735,6 +735,10 @@ export class ComfyRadioNode extends ComfyWidgetNode<string> {
} }
static slotLayout: SlotLayout = { static slotLayout: SlotLayout = {
inputs: [
{ name: "value", type: "string,number" },
{ name: "store", type: BuiltInSlotType.ACTION }
],
outputs: [ outputs: [
{ name: "value", type: "string" }, { name: "value", type: "string" },
{ name: "index", type: "number" }, { name: "index", type: "number" },
@@ -761,16 +765,30 @@ export class ComfyRadioNode extends ComfyWidgetNode<string> {
this.setOutputData(1, this.index) this.setOutputData(1, this.index)
} }
override setValue(value: string, noChangedEvent: boolean = false) {
super.setValue(value, noChangedEvent)
value = get(this.value);
const index = this.properties.choices.indexOf(value) const index = this.properties.choices.indexOf(value)
const index = this.properties.choices.indexOf(value) if (index === -1)
return; return;
this.index = index; this.index = index;
this.indexWidget.value = index; this.indexWidget.value = index;
this.setOutputData(1, this.index) this.setOutputData(1, this.index)
}
override parseValue(param: any): string {
if (typeof param === "string") {
if (this.properties.choices.indexOf(param) === -1)
return this.properties.choices[0]
return param
}
else {
const index = clamp(parseInt(param), 0, this.properties.choices.length - 1)
return this.properties.choices[index] || this.properties.defaultValue
}
} }
} }

View File

@@ -5,3 +5,4 @@ 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 ComfyImageCacheNode } from "./ComfyImageCacheNode" export { default as ComfyImageCacheNode } from "./ComfyImageCacheNode"
export { default as ComfyTriggerNewEventNode } from "./ComfyTriggerNewEventNode"

View File

@@ -603,7 +603,7 @@ export interface WidgetLayout extends IDragItem {
node: ComfyWidgetNode node: ComfyWidgetNode
} }
type DragItemID = string; export type DragItemID = string;
type LayoutStateOps = { type LayoutStateOps = {
addContainer: (parent: ContainerLayout | null, attrs: Partial<Attributes>, index?: number) => ContainerLayout, addContainer: (parent: ContainerLayout | null, attrs: Partial<Attributes>, index?: number) => ContainerLayout,