Enhanced notification & configure default queue action
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
47
src/lib/nodes/ComfyConfigureQueuePromptButton.ts
Normal file
47
src/lib/nodes/ComfyConfigureQueuePromptButton.ts
Normal 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"
|
||||
})
|
||||
@@ -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]
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user