From 93afb64d63ab2c0fb670c1e38d6d5c57e2df3891 Mon Sep 17 00:00:00 2001 From: space-nuko <24979496+space-nuko@users.noreply.github.com> Date: Sun, 21 May 2023 18:27:37 -0500 Subject: [PATCH] Another test --- src/lib/convertVanillaWorkflow.ts | 64 ++++++++++++++-------- src/lib/nodes/ComfyGraphNode.ts | 2 +- src/lib/widgets.ts | 33 +++++++++--- src/tests/convertVanillaWorkflowTests.ts | 45 +++++++++++++++- src/tests/data/convertedWidget.json | 68 ++++++++++++++++++++++++ 5 files changed, 178 insertions(+), 34 deletions(-) create mode 100644 src/tests/data/convertedWidget.json diff --git a/src/lib/convertVanillaWorkflow.ts b/src/lib/convertVanillaWorkflow.ts index 3388f81..1fbc021 100644 --- a/src/lib/convertVanillaWorkflow.ts +++ b/src/lib/convertVanillaWorkflow.ts @@ -169,41 +169,46 @@ function rewriteIDsInGraph(vanillaWorkflow: ComfyVanillaWorkflow) { } /* - * Returns [nodeType, inputType] for a config type, like "FLOAT" -> ["ui/number", "number"] + * Returns [nodeType, inputType, addedWidgetCount] for a config type, like "FLOAT" -> ["ui/number", "number", 1] + * For "INT:seed" it's ["ui/number", "number", 2] since that type adds a randomizer combo widget */ -function getWidgetTypesFromConfig(inputType: ComfyNodeDefInputType): [string, SlotType] | null { +function getWidgetTypesFromConfig(inputName: string, inputType: ComfyNodeDefInputType): [string, SlotType, number] | null { let widgetNodeType = null; let widgetInputType = null; + let addedWidgetCount = 1; if (Array.isArray(inputType)) { // Combo options of string[] widgetNodeType = "ui/combo"; widgetInputType = "string" + addedWidgetCount = 1; + } + else if (`${inputType}:${inputName}` in ComfyWidgets) { + // Widget type override for input of type with given name ("seed", "noise_seed") + const widgetFactory = ComfyWidgets[`${inputType}:${inputName}`] + widgetNodeType = widgetFactory.nodeType; + widgetInputType = widgetFactory.inputType + addedWidgetCount = widgetFactory.addedWidgetCount } else if (inputType in ComfyWidgets) { // Widget type const widgetFactory = ComfyWidgets[inputType] widgetNodeType = widgetFactory.nodeType; widgetInputType = widgetFactory.inputType - } - else if ("${inputType}:{inputName}" in ComfyWidgets) { - // Widget type override for input of type with given name ("seed", "noise_seed") - const widgetFactory = ComfyWidgets["${inputType}:{inputName}"] - widgetNodeType = widgetFactory.nodeType; - widgetInputType = widgetFactory.inputType + addedWidgetCount = widgetFactory.addedWidgetCount } else { // Backend type, we can safely ignore this return null; } - return [widgetNodeType, widgetInputType] + return [widgetNodeType, widgetInputType, addedWidgetCount] } function configureWidgetNodeProperties(serWidgetNode: SerializedComfyWidgetNode, inputOpts?: ComfyNodeDefInputOptions) { inputOpts ||= {} switch (serWidgetNode.type) { - case "ui/number": + case `ui/number`: serWidgetNode.properties.min = inputOpts.min || 0; serWidgetNode.properties.max = inputOpts.max || 100; serWidgetNode.properties.step = inputOpts.step || 1; @@ -240,17 +245,17 @@ function convertPrimitiveNode(vanillaWorkflow: ComfyVanillaWorkflow, node: Seria return false; } - let pair = getWidgetTypesFromConfig(widgetType); + let pair = getWidgetTypesFromConfig(widget.name, widgetType); if (pair == null) { // This should never happen! Primitive nodes only deal with frontend types! console.error("PrimitiveNode had a backend type configured!", node) return false; } - let [widgetNodeType, widgetInputType] = pair + let [widgetNodeType, widgetInputType, addedWidgetCount] = pair // PrimitiveNode will have a widget in the first slot with the actual value. - // The rest are configuration values for e.g. seed action onprompt queue. + // The rest are configuration values for e.g. seed action on prompt queue. const value = node.widgets_values[0]; const [comfyWidgetNode, serWidgetNode] = createSerializedWidgetNode( @@ -384,23 +389,26 @@ export default function convertVanillaWorkflow(vanillaWorkflow: ComfyVanillaWork return i.widget?.name === inputName; }) + + let pair = getWidgetTypesFromConfig(inputName, inputType); + if (pair == null) { + // Input type is backend-only, we can skip adding a UI node here + continue + } + + let [widgetNodeType, widgetInputType, widgetCount] = pair + if (convertedWidget != null) { // This input is an extra input slot on the node that should be // accounted for. - const [value] = node.widgets_values.splice(0, 1); + const values = node.widgets_values.splice(0, widgetCount); + const value = values[0] + // TODO } else { // This input is a widget, it should be converted to an input // connected to a ComfyWidgetNode. - let pair = getWidgetTypesFromConfig(inputType); - if (pair == null) { - // Input type is backend-only, we can skip adding a UI node here - continue - } - - let [widgetNodeType, widgetInputType] = pair - const newInput: IComfyInputSlot = { name: inputName, link: null, @@ -417,7 +425,17 @@ export default function convertVanillaWorkflow(vanillaWorkflow: ComfyVanillaWork const connInputIndex = node.inputs.length - 1; // Now get the widget value. - const [value] = node.widgets_values.splice(0, 1); + // + // Assumes the value is the first in the widget list for the + // case of e.g. the seed randomizer + // That input type adds a number widget and a combo widget so + // the widgets_values will have entries like + // + // [ 8, "randomize", ... ] + // + // Only care about 8 and want to skip "randomize", that's the purpose of `widgetCount` + const values = node.widgets_values.splice(0, widgetCount); + const value = values[0] const [comfyWidgetNode, serWidgetNode] = createSerializedWidgetNode( vanillaWorkflow, diff --git a/src/lib/nodes/ComfyGraphNode.ts b/src/lib/nodes/ComfyGraphNode.ts index 4e2d82f..31f8b73 100644 --- a/src/lib/nodes/ComfyGraphNode.ts +++ b/src/lib/nodes/ComfyGraphNode.ts @@ -312,7 +312,7 @@ export default class ComfyGraphNode extends LGraphNode { (o as any).saveUserState = this.saveUserState if (!this.saveUserState && (!get(uiState).isSavingToLocalStorage || get(configState).alwaysStripUserState)) { this.stripUserState(o) - console.warn("[ComfyGraphNode] stripUserState", this, o) + console.debug("[ComfyGraphNode] stripUserState", this, o) } } diff --git a/src/lib/widgets.ts b/src/lib/widgets.ts index a56e0b8..00d5399 100644 --- a/src/lib/widgets.ts +++ b/src/lib/widgets.ts @@ -12,7 +12,9 @@ type WidgetFactory = { /* Input type as used by litegraph */ inputType: string, /* Node type to instantiate */ - nodeType: string + nodeType: string, + /* Number of widgets this factory instantiates. */ + addedWidgetCount: number } function getNumberDefaults(inputData: ComfyNodeDefInput, defaultStep: number): ComfyInputConfig { @@ -49,7 +51,8 @@ const FLOAT: WidgetFactory = { return addComfyInput(node, inputName, { type: "number", config, defaultWidgetNode: ComfyNumberNode }) }, inputType: "number", - nodeType: "ui/number" + nodeType: "ui/number", + addedWidgetCount: 1 } const INT: WidgetFactory = { @@ -58,7 +61,8 @@ const INT: WidgetFactory = { return addComfyInput(node, inputName, { type: "number", config, defaultWidgetNode: ComfyNumberNode }) }, nodeType: "ui/number", - inputType: "number" + inputType: "number", + addedWidgetCount: 1 } const STRING: WidgetFactory = { @@ -69,7 +73,8 @@ const STRING: WidgetFactory = { return addComfyInput(node, inputName, { type: "string", config: { defaultValue, multiline }, defaultWidgetNode: ComfyTextNode }) }, inputType: "number", - nodeType: "ui/text" + nodeType: "ui/text", + addedWidgetCount: 1 } const COMBO: WidgetFactory = { @@ -82,7 +87,8 @@ const COMBO: WidgetFactory = { return addComfyInput(node, inputName, { type: "string", config: { values: type, defaultValue }, defaultWidgetNode: ComfyComboNode }) }, inputType: "number", - nodeType: "ui/combo" + nodeType: "ui/combo", + addedWidgetCount: 1 } const IMAGEUPLOAD: WidgetFactory = { @@ -90,14 +96,25 @@ const IMAGEUPLOAD: WidgetFactory = { return addComfyInput(node, inputName, { type: "number", config: {} }) }, inputType: "COMFY_IMAGES", - nodeType: "ui/image_upload" + nodeType: "ui/image_upload", + addedWidgetCount: 1 +} + +const INT_seed: WidgetFactory = { + ...INT, + + // Adds a "randomize" combo box + // When converting from vanilla it should be skipped in the widgets_values + // array, so indicate this here + // litegraph really ought to key these by name instead of array indices... + addedWidgetCount: 2 } export type WidgetRepository = Record const ComfyWidgets: WidgetRepository = { - "INT:seed": INT, - "INT:noise_seed": INT, + "INT:seed": INT_seed, + "INT:noise_seed": INT_seed, FLOAT, INT, STRING, diff --git a/src/tests/convertVanillaWorkflowTests.ts b/src/tests/convertVanillaWorkflowTests.ts index b904de0..1ca8a1e 100644 --- a/src/tests/convertVanillaWorkflowTests.ts +++ b/src/tests/convertVanillaWorkflowTests.ts @@ -10,10 +10,11 @@ import { LiteGraph } from '@litegraph-ts/core'; import type { ComfyNodeDef } from '$lib/ComfyNodeDef'; const objectInfo: Record = await import("./data/objectInfo.json") -const json1: ComfyVanillaWorkflow = await import("./data/convertedWidgetAndPrimitiveNode.json") +const json1: ComfyVanillaWorkflow = await import("./data/convertedWidget.json") +const json2: ComfyVanillaWorkflow = await import("./data/convertedWidgetAndPrimitiveNode.json") export default class convertVanillaWorkflowTests extends UnitTest { - test__convertsPrimitiveNodeAndConvertedInput() { + test__convertsWidget() { const workflow = LiteGraph.cloneObject(json1) const attrs: WorkflowAttributes = { ...defaultWorkflowAttributes } @@ -31,6 +32,46 @@ export default class convertVanillaWorkflowTests extends UnitTest { expect(Object.keys(layout.allItems)).toHaveLength(10) + const widgets = Object.values(layout.allItems).filter(di => di.dragItem.type === "widget").map(di => di.dragItem) as WidgetLayout[]; + expect(widgets).toHaveLength(6); + + const widgetsValues = widgets.map(w => { return [w.node.type, w.node.getValue(), w.attrs.title] }) + expect(widgetsValues).toEqual([ + ["ui/number", 0, 'seed'], + ["ui/number", 20, 'steps'], + ["ui/number", 8.5, 'cfg'], + ["ui/combo", 'euler', 'sampler_name'], + ["ui/combo", 'normal', 'scheduler'], + ["ui/number", 1, 'denoise'] + ]); + + const widget = widgets.find(w => w.attrs.title === "cfg") as WidgetLayout | null; + expect(widget).toBeDefined(); + expect(widget.node).toBeDefined(); + expect(widget.node.type).toEqual("ui/number") + expect(widget.node.getValue()).toEqual(8.5) + expect(convWorkflow.graph.getNodeById(widget.node.id)).toEqual(widget.node) + } + + + test__convertsPrimitiveNodeAndConvertedInput() { + const workflow = LiteGraph.cloneObject(json2) + const attrs: WorkflowAttributes = { ...defaultWorkflowAttributes } + + ComfyApp.knownBackendNodes["KSampler"] = { + nodeDef: objectInfo["KSampler"] + } + + const converted = convertVanillaWorkflow(workflow, attrs) + + expect(converted).toBeInstanceOf(Array) + + const [convWorkflow, convLayout] = converted; + + const layout = get(convLayout) + + expect(Object.keys(layout.allItems)).toHaveLength(10) + const widgets = Object.values(layout.allItems).filter(di => di.dragItem.type === "widget").map(di => di.dragItem); expect(widgets).toHaveLength(6); diff --git a/src/tests/data/convertedWidget.json b/src/tests/data/convertedWidget.json new file mode 100644 index 0000000..eb02439 --- /dev/null +++ b/src/tests/data/convertedWidget.json @@ -0,0 +1,68 @@ +{ + "last_node_id": 1, + "last_link_id": 0, + "nodes": [ + { + "id": 1, + "type": "KSampler", + "pos": [ + 707, + 502 + ], + "size": { + "0": 315, + "1": 262 + }, + "flags": {}, + "order": 0, + "mode": 0, + "inputs": [ + { + "name": "model", + "type": "MODEL", + "link": null + }, + { + "name": "positive", + "type": "CONDITIONING", + "link": null + }, + { + "name": "negative", + "type": "CONDITIONING", + "link": null + }, + { + "name": "latent_image", + "type": "LATENT", + "link": null + } + ], + "outputs": [ + { + "name": "LATENT", + "type": "LATENT", + "links": null, + "shape": 3 + } + ], + "properties": { + "Node name for S&R": "KSampler" + }, + "widgets_values": [ + 0, + "randomize", + 20, + 8.5, + "euler", + "normal", + 1 + ] + } + ], + "links": [], + "groups": [], + "config": {}, + "extra": {}, + "version": 0.4 +} \ No newline at end of file