diff --git a/src/lib/IComfyInputSlot.ts b/src/lib/IComfyInputSlot.ts index 23dc382..612723b 100644 --- a/src/lib/IComfyInputSlot.ts +++ b/src/lib/IComfyInputSlot.ts @@ -14,6 +14,7 @@ export type ComfyInputConfig = { export default interface IComfyInputSlot extends INodeInputSlot { serialize: boolean, - defaultWidgetNode?: new (name?: string) => ComfyWidgetNode, + defaultWidgetNode: new (name?: string) => ComfyWidgetNode + widgetNodeType?: string, config: ComfyInputConfig, // stores range min/max/step, etc. } diff --git a/src/lib/components/ComfyApp.svelte b/src/lib/components/ComfyApp.svelte index e785f7b..db027f5 100644 --- a/src/lib/components/ComfyApp.svelte +++ b/src/lib/components/ComfyApp.svelte @@ -135,6 +135,10 @@ refreshView(); }) + + async function doRefreshCombos() { + await app.refreshComboInNodes() + }
@@ -179,6 +183,9 @@ + diff --git a/src/lib/components/ComfyApp.ts b/src/lib/components/ComfyApp.ts index 2d149a8..56667d7 100644 --- a/src/lib/components/ComfyApp.ts +++ b/src/lib/components/ComfyApp.ts @@ -23,6 +23,7 @@ import layoutState from "$lib/stores/layoutState"; import { toast } from '@zerodevx/svelte-toast' import ComfyGraph from "$lib/ComfyGraph"; import { ComfyBackendNode } from "$lib/nodes/ComfyBackendNode"; +import { get } from "svelte/store"; export const COMFYBOX_SERIAL_VERSION = 1; @@ -616,18 +617,29 @@ export default class ComfyApp { const def = defs[node.type]; - throw "refreshComboInNodes unimplemented!" - // for (const widgetNum in node.widgets) { - // const widget = node.widgets[widgetNum] + for (let index = 0; index < node.inputs.length; index++) { + const input = node.inputs[index]; + if ("config" in input) { + const comfyInput = input as IComfyInputSlot; - // if (widget.type == "combo" && def["input"]["required"][widget.name] !== undefined) { - // widget.options.values = def["input"]["required"][widget.name][0]; + console.warn("RefreshCombo", comfyInput.defaultWidgetNode, comfyInput) - // if (!widget.options.values.includes(widget.value)) { - // widget.value = widget.options.values[0]; - // } - // } - // } + if (comfyInput.defaultWidgetNode == nodes.ComfyComboNode && def["input"]["required"][comfyInput.name] !== undefined) { + comfyInput.config.values = def["input"]["required"][comfyInput.name][0]; + + console.warn("RefreshCombo", comfyInput.config.values, def["input"]["required"][comfyInput.name]) + const inputNode = node.getInputNode(index) + + if (inputNode && "doAutoConfig" in inputNode) { + const comfyInputNode = inputNode as nodes.ComfyWidgetNode; + comfyInputNode.doAutoConfig(comfyInput) + if (!comfyInput.config.values.includes(get(comfyInputNode.value))) { + comfyInputNode.setValue(comfyInput.config.defaultValue || comfyInput.config.values[0]) + } + } + } + } + } } } diff --git a/src/lib/nodes/ComfyActionNodes.ts b/src/lib/nodes/ComfyActionNodes.ts index e58a2ba..e7c7427 100644 --- a/src/lib/nodes/ComfyActionNodes.ts +++ b/src/lib/nodes/ComfyActionNodes.ts @@ -91,3 +91,37 @@ LiteGraph.registerNodeType({ desc: "Copies its input to its output when an event is received", type: "actions/copy" }) + +export interface ComfySwapActionProperties extends Record { +} + +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: "*" }, + { name: "A", type: "*" } + ], + } + + override onAction(action: any, param: any) { + const a = this.getInputData(0) + const b = this.getInputData(1) + this.setOutputData(0, a) + this.setOutputData(1, b) + }; +} + +LiteGraph.registerNodeType({ + class: ComfySwapAction, + title: "Comfy.SwapAction", + desc: "Swaps two inputs when triggered", + type: "actions/swap" +}) diff --git a/src/lib/nodes/ComfyGraphNode.ts b/src/lib/nodes/ComfyGraphNode.ts index 38cc9a5..be3d1d3 100644 --- a/src/lib/nodes/ComfyGraphNode.ts +++ b/src/lib/nodes/ComfyGraphNode.ts @@ -1,9 +1,10 @@ import type { ComfyInputConfig } from "$lib/IComfyInputSlot"; import type { SerializedPrompt } from "$lib/components/ComfyApp"; import type ComfyWidget from "$lib/components/widgets/ComfyWidget"; -import { LGraph, LGraphNode } from "@litegraph-ts/core"; +import { LGraph, LGraphNode, LiteGraph, type SerializedLGraphNode } from "@litegraph-ts/core"; import type { SvelteComponentDev } from "svelte/internal"; import type { ComfyWidgetNode } from "./ComfyWidgetNodes"; +import type IComfyInputSlot from "$lib/IComfyInputSlot"; export type DefaultWidgetSpec = { defaultWidgetNode: new (name?: string) => ComfyWidgetNode, @@ -21,4 +22,34 @@ export default class ComfyGraphNode extends LGraphNode { onExecuted?(output: any): void; defaultWidgets?: DefaultWidgetLayout + + override onSerialize(o: SerializedLGraphNode) { + for (let index = 0; index < this.inputs.length; index++) { + const input = this.inputs[index] + const serInput = o.inputs[index] + if ("defaultWidgetNode" in input) { + const comfyInput = input as IComfyInputSlot + const widgetNode = comfyInput.defaultWidgetNode + const ty = Object.values(LiteGraph.registered_node_types) + .find(v => v.class === widgetNode) + if (ty) + (serInput as any).widgetNodeType = ty.type + } + } + } + + override onConfigure(o: SerializedLGraphNode) { + for (let index = 0; index < this.inputs.length; index++) { + const input = this.inputs[index] + const serInput = o.inputs[index] + if ("widgetNodeType" in serInput) { + const comfyInput = input as IComfyInputSlot + const ty: string = serInput.widgetNodeType as any + const widgetNode = Object.values(LiteGraph.registered_node_types) + .find(v => v.type === ty) + if (widgetNode) + comfyInput.defaultWidgetNode = widgetNode.class as any + } + } + } } diff --git a/src/lib/nodes/ComfyWidgetNodes.ts b/src/lib/nodes/ComfyWidgetNodes.ts index fce009f..7c127a7 100644 --- a/src/lib/nodes/ComfyWidgetNodes.ts +++ b/src/lib/nodes/ComfyWidgetNodes.ts @@ -27,7 +27,7 @@ export abstract class ComfyWidgetNode extends ComfyGraphNode { abstract properties: ComfyWidgetProperties; value: Writable - propsChanged: Writable = writable(true) // dummy to indicate if props changed + propsChanged: Writable = writable(0) // dummy to indicate if props changed unsubscribe: Unsubscriber; /** Svelte class for the frontend logic */ @@ -119,6 +119,7 @@ export abstract class ComfyWidgetNode extends ComfyGraphNode { if (this.inputs.length >= this.inputIndex) { const data = this.getInputData(this.inputIndex) if (data) { // TODO can "null" be a legitimate value here? + console.log(data) this.setValue(data) const input = this.getInputLink(this.inputIndex) input.data = null; @@ -145,30 +146,33 @@ export abstract class ComfyWidgetNode extends ComfyGraphNode { inputNode: LGraphNode, inputIndex: number ): boolean { - if (this.autoConfig) { - // Copy properties from default config in input slot - if ("config" in input) { - const comfyInput = input as IComfyInputSlot; - for (const key in comfyInput.config) - this.setProperty(key, comfyInput.config[key]) - - 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; - } - - console.debug("Property copy", input, this.properties) - - this.setValue(get(this.value)) - } + if (this.autoConfig && "config" in input) { + this.doAutoConfig(input as IComfyInputSlot) } return true; } + doAutoConfig(input: IComfyInputSlot) { + // 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]) + + 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; + } + + console.debug("Property copy", input, this.properties) + + this.setValue(get(this.value)) + this.propsChanged.set(get(this.propsChanged) + 1) + } + onConnectionsChange( type: LConnectionKind, slotIndex: number, @@ -195,12 +199,13 @@ export abstract class ComfyWidgetNode extends ComfyGraphNode { } // Force reactivity change so the frontend can be updated with the new props - this.propsChanged.set(!get(this.propsChanged)) + this.propsChanged.set(get(this.propsChanged) + 1) } clampOneConfig(input: IComfyInputSlot) { } override onSerialize(o: SerializedLGraphNode) { + super.onSerialize(o); (o as any).comfyValue = get(this.value); (o as any).shownOutputProperties = this.shownOutputProperties } diff --git a/src/lib/nodes/index.ts b/src/lib/nodes/index.ts index 8c22c73..c2777d1 100644 --- a/src/lib/nodes/index.ts +++ b/src/lib/nodes/index.ts @@ -1,5 +1,5 @@ export { default as ComfyReroute } from "./ComfyReroute" export { ComfyWidgetNode, ComfySliderNode, ComfyComboNode, ComfyTextNode } from "./ComfyWidgetNodes" -export { ComfyCopyAction } from "./ComfyActionNodes" +export { ComfyCopyAction, ComfySwapAction } from "./ComfyActionNodes" export { default as ComfyValueControl } from "./ComfyValueControl" export { default as ComfySelector } from "./ComfySelector" diff --git a/src/lib/widgets.ts b/src/lib/widgets.ts index 93e8a98..e3083aa 100644 --- a/src/lib/widgets.ts +++ b/src/lib/widgets.ts @@ -2,7 +2,7 @@ import type { IWidget, LGraphNode } from "@litegraph-js/core"; import ComfyValueControlWidget from "./widgets/ComfyValueControlWidget"; import type { ComfyInputConfig } from "./IComfyInputSlot"; import type IComfyInputSlot from "./IComfyInputSlot"; -import { BuiltInSlotShape } from "@litegraph-ts/core"; +import { BuiltInSlotShape, LiteGraph } from "@litegraph-ts/core"; import { ComfyComboNode, ComfySliderNode, ComfyTextNode } from "./nodes"; type WidgetFactory = (node: LGraphNode, inputName: string, inputData: any) => IComfyInputSlot; @@ -23,6 +23,14 @@ function addComfyInput(node: LGraphNode, inputName: string, extraInfo: Partial v.class === input.defaultWidgetNode) + if (ty) + input.widgetNodeType = ty.type + } + input.serialize = true; return input; } diff --git a/src/lib/widgets/ButtonWidget.svelte b/src/lib/widgets/ButtonWidget.svelte index de3c972..b2b8b6e 100644 --- a/src/lib/widgets/ButtonWidget.svelte +++ b/src/lib/widgets/ButtonWidget.svelte @@ -7,7 +7,7 @@ export let widget: WidgetLayout | null = null; let node: ComfyButtonNode | null = null; let nodeValue: Writable | null = null; - let propsChanged: Writable | null = null; + let propsChanged: Writable | null = null; $: widget && setNodeValue(widget); diff --git a/src/lib/widgets/ComboWidget.svelte b/src/lib/widgets/ComboWidget.svelte index da637dc..6d1cedf 100644 --- a/src/lib/widgets/ComboWidget.svelte +++ b/src/lib/widgets/ComboWidget.svelte @@ -7,7 +7,7 @@ export let widget: WidgetLayout | null = null; let node: ComfyComboNode | null = null; let nodeValue: Writable | null = null; - let propsChanged: Writable | null = null; + let propsChanged: Writable | null = null; let option: any export let debug: boolean = false; @@ -15,8 +15,14 @@ $: widget && setNodeValue(widget); $: if (nodeValue !== null && (!$propsChanged || $propsChanged)) { - $nodeValue = option - setOption($nodeValue) + if (node.properties.values.indexOf(option.value) === -1) { + setOption($nodeValue) + $nodeValue = option + } + else { + $nodeValue = option + setOption($nodeValue) + } setNodeValue(widget) node.properties = node.properties } @@ -46,37 +52,65 @@ return "???"; return links[0].data } + + let lastPropsChanged: number = 0; + let werePropsChanged: boolean = false; + + $: if ($propsChanged !== lastPropsChanged) { + werePropsChanged = true; + lastPropsChanged = $propsChanged; + setTimeout(() => (werePropsChanged = false), 2000); + } -
- {#if node !== null && nodeValue !== null} - + {/if} + {/key}
-