Another test
This commit is contained in:
@@ -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 widgetNodeType = null;
|
||||||
let widgetInputType = null;
|
let widgetInputType = null;
|
||||||
|
let addedWidgetCount = 1;
|
||||||
|
|
||||||
if (Array.isArray(inputType)) {
|
if (Array.isArray(inputType)) {
|
||||||
// Combo options of string[]
|
// Combo options of string[]
|
||||||
widgetNodeType = "ui/combo";
|
widgetNodeType = "ui/combo";
|
||||||
widgetInputType = "string"
|
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) {
|
else if (inputType in ComfyWidgets) {
|
||||||
// Widget type
|
// Widget type
|
||||||
const widgetFactory = ComfyWidgets[inputType]
|
const widgetFactory = ComfyWidgets[inputType]
|
||||||
widgetNodeType = widgetFactory.nodeType;
|
widgetNodeType = widgetFactory.nodeType;
|
||||||
widgetInputType = widgetFactory.inputType
|
widgetInputType = widgetFactory.inputType
|
||||||
}
|
addedWidgetCount = widgetFactory.addedWidgetCount
|
||||||
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
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Backend type, we can safely ignore this
|
// Backend type, we can safely ignore this
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return [widgetNodeType, widgetInputType]
|
return [widgetNodeType, widgetInputType, addedWidgetCount]
|
||||||
}
|
}
|
||||||
|
|
||||||
function configureWidgetNodeProperties(serWidgetNode: SerializedComfyWidgetNode, inputOpts?: ComfyNodeDefInputOptions) {
|
function configureWidgetNodeProperties(serWidgetNode: SerializedComfyWidgetNode, inputOpts?: ComfyNodeDefInputOptions) {
|
||||||
inputOpts ||= {}
|
inputOpts ||= {}
|
||||||
switch (serWidgetNode.type) {
|
switch (serWidgetNode.type) {
|
||||||
case "ui/number":
|
case `ui/number`:
|
||||||
serWidgetNode.properties.min = inputOpts.min || 0;
|
serWidgetNode.properties.min = inputOpts.min || 0;
|
||||||
serWidgetNode.properties.max = inputOpts.max || 100;
|
serWidgetNode.properties.max = inputOpts.max || 100;
|
||||||
serWidgetNode.properties.step = inputOpts.step || 1;
|
serWidgetNode.properties.step = inputOpts.step || 1;
|
||||||
@@ -240,17 +245,17 @@ function convertPrimitiveNode(vanillaWorkflow: ComfyVanillaWorkflow, node: Seria
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let pair = getWidgetTypesFromConfig(widgetType);
|
let pair = getWidgetTypesFromConfig(widget.name, widgetType);
|
||||||
if (pair == null) {
|
if (pair == null) {
|
||||||
// This should never happen! Primitive nodes only deal with frontend types!
|
// This should never happen! Primitive nodes only deal with frontend types!
|
||||||
console.error("PrimitiveNode had a backend type configured!", node)
|
console.error("PrimitiveNode had a backend type configured!", node)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let [widgetNodeType, widgetInputType] = pair
|
let [widgetNodeType, widgetInputType, addedWidgetCount] = pair
|
||||||
|
|
||||||
// PrimitiveNode will have a widget in the first slot with the actual value.
|
// 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 value = node.widgets_values[0];
|
||||||
|
|
||||||
const [comfyWidgetNode, serWidgetNode] = createSerializedWidgetNode(
|
const [comfyWidgetNode, serWidgetNode] = createSerializedWidgetNode(
|
||||||
@@ -384,23 +389,26 @@ export default function convertVanillaWorkflow(vanillaWorkflow: ComfyVanillaWork
|
|||||||
return i.widget?.name === inputName;
|
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) {
|
if (convertedWidget != null) {
|
||||||
// This input is an extra input slot on the node that should be
|
// This input is an extra input slot on the node that should be
|
||||||
// accounted for.
|
// accounted for.
|
||||||
const [value] = node.widgets_values.splice(0, 1);
|
const values = node.widgets_values.splice(0, widgetCount);
|
||||||
|
const value = values[0]
|
||||||
|
// TODO
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// This input is a widget, it should be converted to an input
|
// This input is a widget, it should be converted to an input
|
||||||
// connected to a ComfyWidgetNode.
|
// 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 = {
|
const newInput: IComfyInputSlot = {
|
||||||
name: inputName,
|
name: inputName,
|
||||||
link: null,
|
link: null,
|
||||||
@@ -417,7 +425,17 @@ export default function convertVanillaWorkflow(vanillaWorkflow: ComfyVanillaWork
|
|||||||
const connInputIndex = node.inputs.length - 1;
|
const connInputIndex = node.inputs.length - 1;
|
||||||
|
|
||||||
// Now get the widget value.
|
// 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(
|
const [comfyWidgetNode, serWidgetNode] = createSerializedWidgetNode(
|
||||||
vanillaWorkflow,
|
vanillaWorkflow,
|
||||||
|
|||||||
@@ -312,7 +312,7 @@ export default class ComfyGraphNode extends LGraphNode {
|
|||||||
(o as any).saveUserState = this.saveUserState
|
(o as any).saveUserState = this.saveUserState
|
||||||
if (!this.saveUserState && (!get(uiState).isSavingToLocalStorage || get(configState).alwaysStripUserState)) {
|
if (!this.saveUserState && (!get(uiState).isSavingToLocalStorage || get(configState).alwaysStripUserState)) {
|
||||||
this.stripUserState(o)
|
this.stripUserState(o)
|
||||||
console.warn("[ComfyGraphNode] stripUserState", this, o)
|
console.debug("[ComfyGraphNode] stripUserState", this, o)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,9 @@ type WidgetFactory = {
|
|||||||
/* Input type as used by litegraph */
|
/* Input type as used by litegraph */
|
||||||
inputType: string,
|
inputType: string,
|
||||||
/* Node type to instantiate */
|
/* Node type to instantiate */
|
||||||
nodeType: string
|
nodeType: string,
|
||||||
|
/* Number of widgets this factory instantiates. */
|
||||||
|
addedWidgetCount: number
|
||||||
}
|
}
|
||||||
|
|
||||||
function getNumberDefaults(inputData: ComfyNodeDefInput, defaultStep: number): ComfyInputConfig {
|
function getNumberDefaults(inputData: ComfyNodeDefInput, defaultStep: number): ComfyInputConfig {
|
||||||
@@ -49,7 +51,8 @@ const FLOAT: WidgetFactory = {
|
|||||||
return addComfyInput(node, inputName, { type: "number", config, defaultWidgetNode: ComfyNumberNode })
|
return addComfyInput(node, inputName, { type: "number", config, defaultWidgetNode: ComfyNumberNode })
|
||||||
},
|
},
|
||||||
inputType: "number",
|
inputType: "number",
|
||||||
nodeType: "ui/number"
|
nodeType: "ui/number",
|
||||||
|
addedWidgetCount: 1
|
||||||
}
|
}
|
||||||
|
|
||||||
const INT: WidgetFactory = {
|
const INT: WidgetFactory = {
|
||||||
@@ -58,7 +61,8 @@ const INT: WidgetFactory = {
|
|||||||
return addComfyInput(node, inputName, { type: "number", config, defaultWidgetNode: ComfyNumberNode })
|
return addComfyInput(node, inputName, { type: "number", config, defaultWidgetNode: ComfyNumberNode })
|
||||||
},
|
},
|
||||||
nodeType: "ui/number",
|
nodeType: "ui/number",
|
||||||
inputType: "number"
|
inputType: "number",
|
||||||
|
addedWidgetCount: 1
|
||||||
}
|
}
|
||||||
|
|
||||||
const STRING: WidgetFactory = {
|
const STRING: WidgetFactory = {
|
||||||
@@ -69,7 +73,8 @@ const STRING: WidgetFactory = {
|
|||||||
return addComfyInput(node, inputName, { type: "string", config: { defaultValue, multiline }, defaultWidgetNode: ComfyTextNode })
|
return addComfyInput(node, inputName, { type: "string", config: { defaultValue, multiline }, defaultWidgetNode: ComfyTextNode })
|
||||||
},
|
},
|
||||||
inputType: "number",
|
inputType: "number",
|
||||||
nodeType: "ui/text"
|
nodeType: "ui/text",
|
||||||
|
addedWidgetCount: 1
|
||||||
}
|
}
|
||||||
|
|
||||||
const COMBO: WidgetFactory = {
|
const COMBO: WidgetFactory = {
|
||||||
@@ -82,7 +87,8 @@ const COMBO: WidgetFactory = {
|
|||||||
return addComfyInput(node, inputName, { type: "string", config: { values: type, defaultValue }, defaultWidgetNode: ComfyComboNode })
|
return addComfyInput(node, inputName, { type: "string", config: { values: type, defaultValue }, defaultWidgetNode: ComfyComboNode })
|
||||||
},
|
},
|
||||||
inputType: "number",
|
inputType: "number",
|
||||||
nodeType: "ui/combo"
|
nodeType: "ui/combo",
|
||||||
|
addedWidgetCount: 1
|
||||||
}
|
}
|
||||||
|
|
||||||
const IMAGEUPLOAD: WidgetFactory = {
|
const IMAGEUPLOAD: WidgetFactory = {
|
||||||
@@ -90,14 +96,25 @@ const IMAGEUPLOAD: WidgetFactory = {
|
|||||||
return addComfyInput(node, inputName, { type: "number", config: {} })
|
return addComfyInput(node, inputName, { type: "number", config: {} })
|
||||||
},
|
},
|
||||||
inputType: "COMFY_IMAGES",
|
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<string, WidgetFactory>
|
export type WidgetRepository = Record<string, WidgetFactory>
|
||||||
|
|
||||||
const ComfyWidgets: WidgetRepository = {
|
const ComfyWidgets: WidgetRepository = {
|
||||||
"INT:seed": INT,
|
"INT:seed": INT_seed,
|
||||||
"INT:noise_seed": INT,
|
"INT:noise_seed": INT_seed,
|
||||||
FLOAT,
|
FLOAT,
|
||||||
INT,
|
INT,
|
||||||
STRING,
|
STRING,
|
||||||
|
|||||||
@@ -10,10 +10,11 @@ import { LiteGraph } from '@litegraph-ts/core';
|
|||||||
import type { ComfyNodeDef } from '$lib/ComfyNodeDef';
|
import type { ComfyNodeDef } from '$lib/ComfyNodeDef';
|
||||||
|
|
||||||
const objectInfo: Record<string, ComfyNodeDef> = await import("./data/objectInfo.json")
|
const objectInfo: Record<string, ComfyNodeDef> = 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 {
|
export default class convertVanillaWorkflowTests extends UnitTest {
|
||||||
test__convertsPrimitiveNodeAndConvertedInput() {
|
test__convertsWidget() {
|
||||||
const workflow = LiteGraph.cloneObject(json1)
|
const workflow = LiteGraph.cloneObject(json1)
|
||||||
const attrs: WorkflowAttributes = { ...defaultWorkflowAttributes }
|
const attrs: WorkflowAttributes = { ...defaultWorkflowAttributes }
|
||||||
|
|
||||||
@@ -31,6 +32,46 @@ export default class convertVanillaWorkflowTests extends UnitTest {
|
|||||||
|
|
||||||
expect(Object.keys(layout.allItems)).toHaveLength(10)
|
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);
|
const widgets = Object.values(layout.allItems).filter(di => di.dragItem.type === "widget").map(di => di.dragItem);
|
||||||
expect(widgets).toHaveLength(6);
|
expect(widgets).toHaveLength(6);
|
||||||
|
|
||||||
|
|||||||
68
src/tests/data/convertedWidget.json
Normal file
68
src/tests/data/convertedWidget.json
Normal file
@@ -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
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user