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 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,
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<string, WidgetFactory>
|
||||
|
||||
const ComfyWidgets: WidgetRepository = {
|
||||
"INT:seed": INT,
|
||||
"INT:noise_seed": INT,
|
||||
"INT:seed": INT_seed,
|
||||
"INT:noise_seed": INT_seed,
|
||||
FLOAT,
|
||||
INT,
|
||||
STRING,
|
||||
|
||||
@@ -10,10 +10,11 @@ import { LiteGraph } from '@litegraph-ts/core';
|
||||
import type { ComfyNodeDef } from '$lib/ComfyNodeDef';
|
||||
|
||||
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 {
|
||||
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);
|
||||
|
||||
|
||||
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