Enhanced notification & configure default queue action

This commit is contained in:
space-nuko
2023-05-09 16:01:41 -05:00
parent 30198c3808
commit e107b65db7
19 changed files with 412 additions and 103 deletions

View File

@@ -6,24 +6,27 @@ import { BuiltInSlotType, LiteGraph, NodeMode, type ITextWidget, type IToggleWid
import { get } from "svelte/store";
import ComfyGraphNode, { type ComfyGraphNodeProperties } from "./ComfyGraphNode";
import type { ComfyWidgetNode, GalleryOutput } from "./ComfyWidgetNodes";
import type { NotifyOptions } from "$lib/notify";
import { convertComfyOutputToGradio } from "$lib/utils";
export class ComfyQueueEvents extends ComfyGraphNode {
static slotLayout: SlotLayout = {
outputs: [
{ name: "beforeQueued", type: BuiltInSlotType.EVENT },
{ name: "afterQueued", type: BuiltInSlotType.EVENT }
{ name: "afterQueued", type: BuiltInSlotType.EVENT },
{ name: "onDefaultQueueAction", type: BuiltInSlotType.EVENT },
],
}
private getActionParams(subgraph: string | null): any {
let queue = get(queueState)
let remaining = 0;
let queueRemaining = 0;
if (typeof queue.queueRemaining === "number")
remaining = queue.queueRemaining
queueRemaining = queue.queueRemaining
return {
queueRemaining: remaining,
queueRemaining,
subgraph
}
}
@@ -36,6 +39,16 @@ export class ComfyQueueEvents extends ComfyGraphNode {
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)
}
@@ -54,6 +67,7 @@ export interface ComfyStoreImagesActionProperties extends ComfyGraphNodeProperti
export class ComfyStoreImagesAction extends ComfyGraphNode {
override properties: ComfyStoreImagesActionProperties = {
tags: [],
images: null
}
@@ -175,13 +189,15 @@ LiteGraph.registerNodeType({
})
export interface ComfyNotifyActionProperties extends ComfyGraphNodeProperties {
message: string
message: string,
type: string
}
export class ComfyNotifyAction extends ComfyGraphNode {
override properties: ComfyNotifyActionProperties = {
tags: [],
message: "Nya.",
tags: []
type: "info"
}
static slotLayout: SlotLayout = {
@@ -192,10 +208,27 @@ export class ComfyNotifyAction extends ComfyGraphNode {
}
override onAction(action: any, param: any) {
const message = this.getInputData(0);
if (message) {
notify(message);
const message = this.getInputData(0) || this.properties.message;
if (!message)
return;
const options: NotifyOptions = {
type: this.properties.type
}
// 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 GalleryOutput;
const converted = convertComfyOutputToGradio(output);
if (converted.length > 0)
options.imageUrl = converted[0].data;
}
}
notify(message, options);
};
}
@@ -206,6 +239,40 @@ LiteGraph.registerNodeType({
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
}

View File

@@ -0,0 +1,47 @@
import layoutState from "$lib/stores/layoutState"
import { BuiltInSlotType, LGraphNode, LiteGraph, type ITextWidget, type OptionalSlots, type PropertyLayout, type SlotLayout, type Vector2 } from "@litegraph-ts/core"
export interface ComfyConfigureQueuePromptButtonProperties extends Record<string, any> {
}
export default class ComfyConfigureQueuePromptButton extends LGraphNode {
override properties: ComfyConfigureQueuePromptButtonProperties = {
}
static slotLayout: SlotLayout = {
inputs: [
{ name: "config", type: BuiltInSlotType.ACTION },
],
}
static propertyLayout: PropertyLayout = [
]
static optionalSlots: OptionalSlots = {
}
override size: Vector2 = [60, 30];
constructor(title?: string) {
super(title)
}
override onAction(action: any, param: any, options: { action_call?: string }) {
if (action === "config" && param != null) {
layoutState.update(state => {
if (typeof param === "string")
state.attrs.queuePromptButtonName = param || ""
else if (typeof param === "object" && "buttonName" in param)
state.attrs.queuePromptButtonName = param.buttonName || ""
return state
})
}
}
}
LiteGraph.registerNodeType({
class: ComfyConfigureQueuePromptButton,
title: "Comfy.ConfigureQueuePromptButton",
desc: "Sets the properties of the global queue prompt button",
type: "workflow/configure_queue_prompt_button"
})

View File

@@ -28,10 +28,36 @@ export default class ComfyGraphNode extends LGraphNode {
isBackendNode?: boolean;
/*
* Triggered when the user presses the global "Queue Prompt" button in the fixed toolbar.
*/
onDefaultQueueAction?(): void;
/*
* Triggered before a prompt containing this node is passed to the backend.
*/
beforeQueued?(subgraph: string | null): void;
/*
* Triggered after a prompt containing this node is passed to the backend.
*/
afterQueued?(prompt: SerializedPrompt, subgraph: string | null): void;
/*
* Triggered when the backend sends a finished output back with this node's ID.
* Valid for output nodes like SaveImage and PreviewImage.
*/
onExecuted?(output: any): void;
/*
* Allows you to manually specify an auto-config for certain input slot
* indices, so that when a ComfyWidgetNode is connected to the input slot it
* receives the specified min/max/values/etc.
* Otherwise the config passed from the backend is used.
*
* Use this if you're creating a frontend-only node and want some input
* slots to have auto-configs, like for connected combo box widgets.
*/
defaultWidgets?: DefaultWidgetLayout
/*
@@ -71,6 +97,12 @@ export default class ComfyGraphNode extends LGraphNode {
this.addProperty("tags", [], "array")
}
/*
* Adjusts output slot types to have the same type as the first connected
* input. Used for frontend-only nodes with inputs and outputs that act as
* wildcards, so that they can be connected to ComfyBackendNodes without
* rejection.
*/
private inheritSlotTypes(type: LConnectionKind, isConnected: boolean) {
// Prevent multiple connections to different types when we have no input
if (isConnected && type === LConnectionKind.OUTPUT) {
@@ -229,6 +261,7 @@ export default class ComfyGraphNode extends LGraphNode {
}
override onResize(size: Vector2) {
// Snap to grid if shift is held down.
if ((window as any)?.app?.shiftDown) {
const w = LiteGraph.CANVAS_GRID_SIZE * Math.round(this.size[0] / LiteGraph.CANVAS_GRID_SIZE);
const h = LiteGraph.CANVAS_GRID_SIZE * Math.round(this.size[1] / LiteGraph.CANVAS_GRID_SIZE);
@@ -241,6 +274,8 @@ export default class ComfyGraphNode extends LGraphNode {
}
override onSerialize(o: SerializedLGraphNode) {
// Resync the widget node types for each input.
// This is so combo widget nodes will be correctly detected by ComfyApp.refreshComboInNodes().
for (let index = 0; index < this.inputs.length; index++) {
const input = this.inputs[index]
const serInput = o.inputs[index]
@@ -254,6 +289,7 @@ export default class ComfyGraphNode extends LGraphNode {
(serInput as any).defaultWidgetNode = null
}
}
(o as any).saveUserState = this.saveUserState
if (!this.saveUserState) {
this.stripUserState(o)
@@ -262,6 +298,7 @@ export default class ComfyGraphNode extends LGraphNode {
}
override onConfigure(o: SerializedLGraphNode) {
// Save the litegraph type of the default ComfyWidgetNode for each input.
for (let index = 0; index < this.inputs.length; index++) {
const input = this.inputs[index]
const serInput = o.inputs[index]

View File

@@ -18,6 +18,12 @@ import CheckboxWidget from "$lib/widgets/CheckboxWidget.svelte";
import RadioWidget from "$lib/widgets/RadioWidget.svelte";
import ImageUploadWidget from "$lib/widgets/ImageUploadWidget.svelte";
export type AutoConfigOptions = {
includeProperties?: Set<string> | null,
setDefaultValue?: boolean
setWidgetTitle?: boolean
}
/*
* NOTE: If you want to add a new widget but it has the same input/output type
* as another one of the existing widgets, best to create a new "variant" of
@@ -165,7 +171,9 @@ export abstract class ComfyWidgetNode<T = any> extends ComfyGraphNode {
setValue(value: any, noChangedEvent: boolean = false) {
if (noChangedEvent)
this._noChangedEvent = true;
this.value.set(this.parseValue(value))
const parsed = this.parseValue(value)
this.value.set(parsed)
// In case value.set() does not trigger onValueUpdated, we need to reset
// the counter here also.
@@ -222,8 +230,6 @@ export abstract class ComfyWidgetNode<T = any> extends ComfyGraphNode {
if ("noChangedEvent" in param)
noChangedEvent = Boolean(param.noChangedEvent)
}
value = this.parseValue(value);
console.warn("[Widget] Store!", param, "=>", value, noChangedEvent)
this.setValue(value, noChangedEvent)
}
}
@@ -244,18 +250,24 @@ export abstract class ComfyWidgetNode<T = any> extends ComfyGraphNode {
return true;
}
doAutoConfig(input: IComfyInputSlot) {
doAutoConfig(input: IComfyInputSlot, options: AutoConfigOptions = { setDefaultValue: true, setWidgetTitle: true }) {
// Copy properties from default config in input slot
const comfyInput = input as IComfyInputSlot;
for (const key in comfyInput.config)
this.setProperty(key, comfyInput.config[key])
for (const key in comfyInput.config) {
if (options.includeProperties == null || options.includeProperties.has(key))
this.setProperty(key, comfyInput.config[key])
}
if ("defaultValue" in this.properties)
this.setValue(this.properties.defaultValue)
if (options.setDefaultValue) {
if ("defaultValue" in this.properties)
this.setValue(this.properties.defaultValue)
}
const widget = layoutState.findLayoutForNode(this.id)
if (widget && input.name !== "") {
widget.attrs.title = input.name;
if (options.setWidgetTitle) {
const widget = layoutState.findLayoutForNode(this.id)
if (widget && input.name !== "") {
widget.attrs.title = input.name;
}
}
console.debug("Property copy", input, this.properties)

View File

@@ -1,8 +1,19 @@
export { default as ComfyReroute } from "./ComfyReroute"
export { ComfyWidgetNode, ComfySliderNode, ComfyComboNode, ComfyTextNode } from "./ComfyWidgetNodes"
export { ComfyQueueEvents, ComfyCopyAction, ComfySwapAction, ComfyNotifyAction, ComfyStoreImagesAction, ComfyExecuteSubgraphAction } from "./ComfyActionNodes"
export {
ComfyQueueEvents,
ComfyCopyAction,
ComfySwapAction,
ComfyNotifyAction,
ComfyPlaySoundAction,
ComfyStoreImagesAction,
ComfyExecuteSubgraphAction,
ComfySetNodeModeAction,
ComfySetNodeModeAdvancedAction
} from "./ComfyActionNodes"
export { default as ComfyPickFirstNode } from "./ComfyPickFirstNode"
export { default as ComfyValueControl } from "./ComfyValueControl"
export { default as ComfySelector } from "./ComfySelector"
export { default as ComfyImageCacheNode } from "./ComfyImageCacheNode"
export { default as ComfyTriggerNewEventNode } from "./ComfyTriggerNewEventNode"
export { default as ComfyConfigureQueuePromptButton } from "./ComfyConfigureQueuePromptButton"