diff --git a/litegraph b/litegraph index 2d963a6..90cbc0f 160000 --- a/litegraph +++ b/litegraph @@ -1 +1 @@ -Subproject commit 2d963a609f5bdc6f01ca142ec8d07bd7519ac178 +Subproject commit 90cbc0fa9550eda7f82c3e52004997353316d0fc diff --git a/src/lib/ComfyGraph.ts b/src/lib/ComfyGraph.ts index f95931e..578a6fd 100644 --- a/src/lib/ComfyGraph.ts +++ b/src/lib/ComfyGraph.ts @@ -9,6 +9,7 @@ import type ComfyGraphNode from "./nodes/ComfyGraphNode"; import type IComfyInputSlot from "./IComfyInputSlot"; import type { ComfyBackendNode } from "./nodes/ComfyBackendNode"; import type { ComfyComboNode, ComfyWidgetNode } from "./nodes/widgets"; +import selectionState from "./stores/selectionState"; type ComfyGraphEvents = { configured: (graph: LGraph) => void @@ -115,7 +116,7 @@ export default class ComfyGraph extends LGraph { if (comfyInput.defaultWidgetNode) { const widgetNode = LiteGraph.createNode(comfyInput.defaultWidgetNode) const inputPos = comfyNode.getConnectionPos(true, index); - this.add(widgetNode) + node.graph.add(widgetNode) widgetNode.connect(0, comfyNode, index); widgetNode.collapse(); widgetNode.pos = [inputPos[0] - 140, inputPos[1] + LiteGraph.NODE_SLOT_HEIGHT / 2]; @@ -150,6 +151,7 @@ export default class ComfyGraph extends LGraph { } override onNodeRemoved(node: LGraphNode, options: LGraphRemoveNodeOptions) { + selectionState.clear(); // safest option layoutState.nodeRemoved(node, options); // Handle subgraphs being removed diff --git a/src/lib/ComfyNodeDef.ts b/src/lib/ComfyNodeDef.ts index 584b673..de281ab 100644 --- a/src/lib/ComfyNodeDef.ts +++ b/src/lib/ComfyNodeDef.ts @@ -17,12 +17,20 @@ export type ComfyNodeDefInputs = { optional?: Record } export type ComfyNodeDefInput = [ComfyNodeDefInputType, ComfyNodeDefInputOptions | null] + + +/** + * - Array: Combo widget. Usually the values are strings but they can also be other stuff like booleans. + * - "INT"/"FLOAT"/etc.: Non-combo type widgets. See ComfyWidgets type. + * - other string: Must be an input type, usually something lke "IMAGE" or "LATENT". + */ export type ComfyNodeDefInputType = any[] | keyof typeof ComfyWidgets | string + export type ComfyNodeDefInputOptions = { forceInput?: boolean } -// TODO when comfy refactors +// TODO if/when comfy refactors export type ComfyNodeDefOutput = { type: string, name: string, diff --git a/src/lib/components/AccordionContainer.svelte b/src/lib/components/AccordionContainer.svelte index e0cf0c1..ca86d8f 100644 --- a/src/lib/components/AccordionContainer.svelte +++ b/src/lib/components/AccordionContainer.svelte @@ -61,7 +61,7 @@ } -{#if container && children} +{#if container && Array.isArray(children)} {@const selected = $uiState.uiUnlocked && $selectionState.currentSelection.includes(container.id)}
-{#if container && children} +{#if container && Array.isArray(children)} {@const selected = $uiState.uiUnlocked && $selectionState.currentSelection.includes(container.id)}
number)) { + const dragItemID = $selectionState.currentSelection[0]; + const entry = $layoutState.allItems[dragItemID]; + if (!entry) { + return + } + + const dragItem = entry.dragItem; + const containing = entry.parent + if (containing == null || containing.type !== "container") { + return + } + + const containingEntry = $layoutState.allItems[containing.id]; + const oldIndex = containingEntry.children.findIndex(c => c.id === dragItem.id) + if (oldIndex === -1) { + return; + } + + let newIndex: number; + if (typeof delta === "number") + newIndex = oldIndex + delta; + else + newIndex = delta(oldIndex, containingEntry.children.length); + + layoutState.moveItem(dragItem, containing as ContainerLayout, newIndex) + $layoutState = $layoutState + } + + function moveUp() { + moveTo(-1) + } + + function moveDown() { + moveTo(1) + } + + function sendToTop() { + moveTo(() => 0) + } + + function sendToBottom() { + moveTo((cur: number, total: number) => total - 1) + } + function groupWidgets(horizontal: boolean) { const items = $selectionState.currentSelection $selectionState.currentSelection = [] @@ -110,6 +155,23 @@ {#if showMenu} + moveUp()} + text="Move Up" /> + moveDown()} + text="Move Down" /> + sendToTop()} + text="Send to Top" /> + sendToBottom()} + text="Send to Bottom" /> + groupWidgets(false)} diff --git a/src/lib/components/Container.svelte b/src/lib/components/Container.svelte index 2f5e61e..40dda45 100644 --- a/src/lib/components/Container.svelte +++ b/src/lib/components/Container.svelte @@ -1,20 +1,12 @@ -{#if container && children} +{#if container && Array.isArray(children)} {@const selected = $uiState.uiUnlocked && $selectionState.currentSelection.includes(container.id)}
, widgetChanges: Record) { - for (const node of this.graph._nodes) { + 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; @@ -482,9 +482,6 @@ export class ComfySetNodeModeAdvancedAction extends ComfyGraphNode { newMode = NodeMode.NEVER; } nodeChanges[node.id] = newMode - node.changeMode(newMode); - if ("notifyPropsChanged" in node) - (node as ComfyWidgetNode).notifyPropsChanged(); } } } @@ -530,8 +527,7 @@ export class ComfySetNodeModeAdvancedAction extends ComfyGraphNode { } for (const [nodeId, newMode] of Object.entries(nodeChanges)) { - // NOTE: Only applies to this subgraph, not parent/child graphs. - this.graph.getNodeById(nodeId).changeMode(newMode); + this.graph.getNodeByIdRecursive(nodeId).changeMode(newMode); } const layout = get(layoutState); diff --git a/src/lib/nodes/ComfyPickImageNode.ts b/src/lib/nodes/ComfyPickImageNode.ts index 24f140a..4e8de23 100644 --- a/src/lib/nodes/ComfyPickImageNode.ts +++ b/src/lib/nodes/ComfyPickImageNode.ts @@ -5,7 +5,7 @@ import { comfyFileToAnnotatedFilepath, type ComfyBoxImageMetadata } from "$lib/u export default class ComfyPickImageNode extends ComfyGraphNode { static slotLayout: SlotLayout = { inputs: [ - { name: "images", type: "COMFYBOX_IMAGES" }, + { name: "images", type: "COMFYBOX_IMAGES,COMFYBOX_IMAGE" }, ], outputs: [ { name: "image", type: "COMFYBOX_IMAGE" }, @@ -35,9 +35,13 @@ export default class ComfyPickImageNode extends ComfyGraphNode { _path: string | null = null; _index: number = 0; - private setValue(value: ComfyBoxImageMetadata[] | null) { + private setValue(value: ComfyBoxImageMetadata[] | ComfyBoxImageMetadata | null) { + if (value != null && !Array.isArray(value)) { + value = [value] + this._index = 0; + } const changed = this._value != value; - this._value = value; + this._value = value as ComfyBoxImageMetadata[]; if (changed) { if (value && value[this._index] != null) { this._image = value[this._index] @@ -55,6 +59,7 @@ export default class ComfyPickImageNode extends ComfyGraphNode { this.widthWidget.value = 0 this.heightWidget.value = 0 } + console.log("SET", value, this._image, this._path) } } diff --git a/src/lib/nodes/ComfyValueControl.ts b/src/lib/nodes/ComfyValueControl.ts index b699ee7..95e2fc5 100644 --- a/src/lib/nodes/ComfyValueControl.ts +++ b/src/lib/nodes/ComfyValueControl.ts @@ -9,7 +9,8 @@ export interface ComfyValueControlProperties extends ComfyGraphNodeProperties { action: "fixed" | "increment" | "decrement" | "randomize", min: number, max: number, - step: number + step: number, + ignoreStepWhenRandom: boolean } const INT_MAX = 1125899906842624; @@ -21,7 +22,8 @@ export default class ComfyValueControl extends ComfyGraphNode { action: "fixed", min: -INT_MAX, max: INT_MAX, - step: 1 + step: 1, + ignoreStepWhenRandom: false } static slotLayout: SlotLayout = { @@ -61,15 +63,11 @@ export default class ComfyValueControl extends ComfyGraphNode { } override onExecute() { - this.setProperty("action", this.getInputData(2) || "fixed") - this.setProperty("min", this.getInputData(3)) - this.setProperty("max", this.getInputData(4)) - this.setProperty("step", this.getInputData(5) || 1) - if (this._aboutToChange > 0) { this._aboutToChange -= 1; if (this._aboutToChange <= 0) { const value = this._aboutToChangeValue; + console.warn("ABOUTTOCHANGE", value) this._aboutToChange = 0; this._aboutToChangeValue = null; this.triggerSlot(1, value) @@ -82,8 +80,26 @@ export default class ComfyValueControl extends ComfyGraphNode { if (typeof v !== "number") return - let min = this.properties.min - let max = this.properties.max + let action_ = this.getInputData(2); + if (action_ == null) + action_ = "fixed" + let min = this.getInputData(3); + if (min == null) + min = -INT_MAX + let max = this.getInputData(4); + if (max == null) + max = INT_MAX + let step = this.getInputData(5); + if (step == null) + step = 1 + + this.setProperty("action", action_) + this.setProperty("min", min) + this.setProperty("max", max) + this.setProperty("step", step) + + min = this.properties.min + max = this.properties.max if (min == null) min = -INT_MAX if (max == null) max = INT_MAX @@ -103,7 +119,8 @@ export default class ComfyValueControl extends ComfyGraphNode { v -= this.properties.step; break; case "randomize": - v = Math.floor(Math.random() * range) * (this.properties.step) + min; + const step = this.properties.ignoreStepWhenRandom ? 1 : this.properties.step + v = Math.floor(Math.random() * range) * step + min; default: break; } diff --git a/src/lib/nodes/widgets/ComfyComboNode.ts b/src/lib/nodes/widgets/ComfyComboNode.ts index fd21274..a1ddbce 100644 --- a/src/lib/nodes/widgets/ComfyComboNode.ts +++ b/src/lib/nodes/widgets/ComfyComboNode.ts @@ -113,12 +113,16 @@ export default class ComfyComboNode extends ComfyWidgetNode { const comfyInput = input as IComfyInputSlot; const otherProps = comfyInput.config; + console.warn("CHECK COMBO CONNECTION", otherProps, thisProps) + // Ensure combo options match if (!(otherProps.values instanceof Array)) return false; if (thisProps.values.find((v, i) => otherProps.values.indexOf(v) === -1)) return false; + console.warn("PASSED") + return true; } diff --git a/src/lib/nodes/widgets/ComfyGalleryNode.ts b/src/lib/nodes/widgets/ComfyGalleryNode.ts index d0b1f5b..632b4f9 100644 --- a/src/lib/nodes/widgets/ComfyGalleryNode.ts +++ b/src/lib/nodes/widgets/ComfyGalleryNode.ts @@ -72,6 +72,9 @@ export default class ComfyGalleryNode extends ComfyWidgetNode { static slotLayout: SlotLayout = { inputs: [ + { name: "value", type: "string" }, { name: "store", type: BuiltInSlotType.ACTION } ], outputs: [ @@ -24,6 +25,7 @@ export default class ComfyTextNode extends ComfyWidgetNode { ] } + override inputSlotName = "value"; override svelteComponentType = TextWidget override defaultValue = ""; diff --git a/src/lib/nodes/widgets/ComfyWidgetNode.ts b/src/lib/nodes/widgets/ComfyWidgetNode.ts index 7b8b71b..3b447d9 100644 --- a/src/lib/nodes/widgets/ComfyWidgetNode.ts +++ b/src/lib/nodes/widgets/ComfyWidgetNode.ts @@ -73,12 +73,14 @@ export default abstract class ComfyWidgetNode extends ComfyGraphNode { // shownInputProperties: string[] = [] /** Names of properties to add as outputs */ - private shownOutputProperties: Record = {} + private shownOutputProperties: Record = {} outputProperties: { name: string, type: string }[] = [] override isBackendNode = false; override serialize_widgets = true; + // input slots + inputSlotName: string | null = "value"; storeActionName: string | null = "store"; // output slots @@ -105,15 +107,16 @@ export default abstract class ComfyWidgetNode extends ComfyGraphNode { } addPropertyAsOutput(propertyName: string, type: string) { - if (this.shownOutputProperties["@" + propertyName]) + if (this.shownOutputProperties[propertyName]) return; if (!(propertyName in this.properties)) { throw `No property named ${propertyName} found!` } - this.shownOutputProperties["@" + propertyName] = { type, index: this.outputs.length } - this.addOutput("@" + propertyName, type) + const outputName = "@" + propertyName; + this.shownOutputProperties[propertyName] = { type, outputName } + this.addOutput(outputName, type) } formatValue(value: any): string { @@ -174,8 +177,11 @@ export default abstract class ComfyWidgetNode extends ComfyGraphNode { override onPropertyChanged(property: string, value: any, prevValue?: any) { if (this.shownOutputProperties != null) { const data = this.shownOutputProperties[property] - if (data) - this.setOutputData(data.index, value) + if (data) { + const index = this.findOutputSlotIndexByName(data.outputName) + if (index !== -1) + this.setOutputData(index, value) + } } } @@ -183,6 +189,15 @@ export default abstract class ComfyWidgetNode extends ComfyGraphNode { * Logic to run if this widget can be treated as output (slider, combo, text) */ override onExecute(param: any, options: object) { + if (this.inputSlotName != null) { + const inputIndex = this.findInputSlotIndexByName(this.inputSlotName) + if (inputIndex !== -1) { + const data = this.getInputData(inputIndex) + if (data != null) { // TODO can "null" be a legitimate value here? + this.setValue(data) + } + } + } if (this.outputSlotName != null) { const outputIndex = this.findOutputSlotIndexByName(this.outputSlotName) if (outputIndex !== -1) @@ -190,7 +205,9 @@ export default abstract class ComfyWidgetNode extends ComfyGraphNode { } for (const propName in this.shownOutputProperties) { const data = this.shownOutputProperties[propName] - this.setOutputData(data.index, this.properties[propName]) + const index = this.findOutputSlotIndexByName(data.outputName) + if (index !== -1) + this.setOutputData(index, this.properties[propName]) } // Fire a pending change event after one full step of the graph has diff --git a/src/lib/stores/layoutState.ts b/src/lib/stores/layoutState.ts index 3bd4778..e3568c6 100644 --- a/src/lib/stores/layoutState.ts +++ b/src/lib/stores/layoutState.ts @@ -314,7 +314,7 @@ const ALL_ATTRIBUTES: AttributesSpecList = [ location: "widget", defaultValue: "", editable: true, - onChanged: setNodeTitle + // onChanged: setNodeTitle }, { name: "hidden", @@ -679,6 +679,7 @@ type LayoutStateOps = { updateChildren: (parent: IDragItem, children: IDragItem[]) => IDragItem[], nodeAdded: (node: LGraphNode, options: LGraphAddNodeOptions) => void, nodeRemoved: (node: LGraphNode, options: LGraphRemoveNodeOptions) => void, + moveItem: (target: IDragItem, to: ContainerLayout, index?: number) => void, groupItems: (dragItemIDs: DragItemID[], attrs?: Partial) => ContainerLayout, ungroup: (container: ContainerLayout) => void, findLayoutEntryForNode: (nodeId: ComfyNodeID) => DragItemEntry | null, @@ -922,7 +923,7 @@ function nodeRemoved(node: LGraphNode, options: LGraphRemoveNodeOptions) { function moveItem(target: IDragItem, to: ContainerLayout, index?: number) { const state = get(store) const entry = state.allItems[target.id] - if (entry.parent && entry.parent.id === to.id) + if (!entry || (entry.parent && entry.parent.id === to.id && entry.children.indexOf(target) === index)) return; if (entry.parent) { @@ -1175,6 +1176,7 @@ const layoutStateStore: WritableLayoutStateStore = updateChildren, nodeAdded, nodeRemoved, + moveItem, groupItems, findLayoutEntryForNode, findLayoutForNode, diff --git a/src/lib/utils.ts b/src/lib/utils.ts index a38ae88..dc53262 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -206,9 +206,10 @@ export function getNodeInfo(nodeId: ComfyNodeID): string { if (!app || !app.lGraph) return String(nodeId); - // TODO subgraph support + const displayNodeID = nodeId ? (nodeId.split("-")[0]) : String(nodeId); + const title = app.lGraph.getNodeByIdRecursive(nodeId)?.title || String(nodeId); - return title + " (" + nodeId + ")" + return title + " (" + displayNodeID + ")" } export const debounce = (callback: Function, wait = 250) => { diff --git a/src/lib/widgets/ImageUploadWidget.svelte b/src/lib/widgets/ImageUploadWidget.svelte index fa474e5..8c71e30 100644 --- a/src/lib/widgets/ImageUploadWidget.svelte +++ b/src/lib/widgets/ImageUploadWidget.svelte @@ -3,7 +3,7 @@ import { Block } from "@gradio/atoms"; import { TextBox } from "@gradio/form"; import Row from "$lib/components/gradio/app/Row.svelte"; - import { get, type Writable } from "svelte/store"; + import { get, writable, type Writable } from "svelte/store"; import Modal from "$lib/components/Modal.svelte"; import { Button } from "@gradio/button"; import { Embed as Klecks } from "klecks"; @@ -21,16 +21,18 @@ let nodeValue: Writable | null = null; let attrsChanged: Writable | null = null; - let imgWidth: number = 0; - let imgHeight: number = 0; + let imgWidth: Writable = writable(0); + let imgHeight: Writable = writable(0); $: widget && setNodeValue(widget); + $: console.warn("IMGSIZE2!!!", $imgWidth, $imgHeight) + $: if ($nodeValue && $nodeValue.length > 0) { // TODO improve - if (imgWidth > 0 && imgHeight > 0) { - $nodeValue[0].width = imgWidth - $nodeValue[0].height = imgHeight + if ($imgWidth > 0 && $imgHeight > 0) { + $nodeValue[0].width = $imgWidth + $nodeValue[0].height = $imgHeight } else { $nodeValue[0].width = 0 @@ -232,8 +234,8 @@
{#if widget.attrs.variant === "fileUpload" || isMobile}