Fix issues with adding input and workflow widgets and also fix the issue with the logger on the main workflow.py
This commit is contained in:
+70
-29
@@ -1,47 +1,58 @@
|
||||
import { chainCallback } from "./utils.js";
|
||||
import { ComfyWidgets } from '../../../scripts/widgets.js'
|
||||
import { addInputs, cleanInputs, clearInputs } from "./inputs.js";
|
||||
import { ComfyWidgets } from '../../../scripts/widgets.js';
|
||||
import { addInputs, cleanInputs, clearInputs } from "./inputs.js"; // Ensure clearInputs is imported
|
||||
import { colors, bg_colors, node_type_list } from "./constants.js";
|
||||
|
||||
function initialisation(node) {
|
||||
node.widgets[1].callback = ( value ) => {
|
||||
cleanInputs(node);
|
||||
// Graph-independent part of initialization
|
||||
function initialisation_preGraph(node) {
|
||||
if (!node.widgets || node.widgets.length < 2) {
|
||||
console.error("Node widgets not properly initialized for callback setup.", node.type, node.id);
|
||||
return;
|
||||
}
|
||||
node.widgets[1].callback = (value) => {
|
||||
if (node.graph) { // Still check for graph here for safety, though cleanInputs might be called from onAdded too
|
||||
clearInputs(node);
|
||||
}
|
||||
|
||||
switch(value){
|
||||
switch (value) {
|
||||
case "STRING":
|
||||
node.addOutput("output", "STRING");
|
||||
ComfyWidgets.STRING(
|
||||
node,
|
||||
"default",
|
||||
["STRING",{default: "",},],
|
||||
app,
|
||||
)
|
||||
|
||||
["STRING", { default: "" }],
|
||||
app
|
||||
);
|
||||
break;
|
||||
case "INT":
|
||||
node.addOutput("output", "INT");
|
||||
ComfyWidgets.INT(
|
||||
node,
|
||||
"default",
|
||||
['INT',{default: 0, "min": 0, "max": 18446744073709551616, "step": 1},],
|
||||
//['INT',{default: 0},],
|
||||
app,
|
||||
)
|
||||
['INT', { default: 0, min: 0, max: 18446744073709551616, step: 1 }],
|
||||
app
|
||||
);
|
||||
break;
|
||||
case "FLOAT":
|
||||
node.addOutput("output", "FLOAT");
|
||||
ComfyWidgets.FLOAT(
|
||||
node,
|
||||
"default",
|
||||
['FLOAT',{default: 0, "min": 0.00, "max": 2048.00, "step": 0.01},],
|
||||
app,
|
||||
)
|
||||
['FLOAT', { default: 0, min: 0.00, max: 2048.00, step: 0.01 }],
|
||||
app
|
||||
);
|
||||
break;
|
||||
case "BOOLEAN":
|
||||
node.addOutput("output", "BOOLEAN");
|
||||
node.addWidget("toggle", "default", false, ()=>{});
|
||||
node.addWidget("toggle", "default", false, () => {});
|
||||
break;
|
||||
case "none":
|
||||
// If type is 'none', outputs might have been cleared by clearInputs.
|
||||
// Ensure no "output" is present if it's truly "none".
|
||||
if (node.outputs && node.outputs.find(o => o.name === "output")) {
|
||||
const outputIndex = node.outputs.findIndex(o => o.name === "output");
|
||||
if (outputIndex !== -1) node.removeOutput(outputIndex);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
node.addOutput("output", value);
|
||||
@@ -51,35 +62,65 @@ function initialisation(node) {
|
||||
node.color = colors[node_type_list.indexOf(value)];
|
||||
node.bgcolor = bg_colors[node_type_list.indexOf(value)];
|
||||
};
|
||||
if (node.widgets[1].value == "none")
|
||||
clearInputs(node);
|
||||
|
||||
node.color = colors[node_type_list.indexOf("none")];
|
||||
node.bgcolor = bg_colors[node_type_list.indexOf("none")];
|
||||
}
|
||||
|
||||
// Graph-dependent part of initialization
|
||||
function initialisation_onAdded(node) {
|
||||
if (!node.widgets || node.widgets.length < 2) {
|
||||
return;
|
||||
}
|
||||
if (node.widgets[1].value === "none") {
|
||||
clearInputs(node); // This needs node.graph, which is available in onAdded
|
||||
}
|
||||
}
|
||||
|
||||
function configure(info) {
|
||||
const inputs = {};
|
||||
inputs["default"] = {inputs: ["default", info.widgets_values[1], info.widgets_values[2]]};
|
||||
inputs["default"] = { inputs: ["default", info.widgets_values[1], info.widgets_values[2]] };
|
||||
addInputs(this, inputs);
|
||||
}
|
||||
|
||||
function serialize(info) {
|
||||
for (let inp of this.inputs){
|
||||
if (inp.widget){
|
||||
if (inp.type != this.local_input_defs.required[inp.name][0]){
|
||||
// Add check for this.local_input_defs
|
||||
if (!this.inputs || !this.local_input_defs || !this.local_input_defs.required) {
|
||||
return; // Exit early if the required structures don't exist
|
||||
}
|
||||
|
||||
for (let inp of this.inputs) {
|
||||
if (inp.widget) {
|
||||
// Check that the required path exists before accessing it
|
||||
if (this.local_input_defs.required[inp.name] &&
|
||||
this.local_input_defs.required[inp.name][0] !== undefined &&
|
||||
inp.type !== this.local_input_defs.required[inp.name][0]) {
|
||||
|
||||
inp.type = this.local_input_defs.required[inp.name][0];
|
||||
const wid = this.widgets.find(w => w.name == inp.name);
|
||||
if (wid && wid.origType != this.local_input_defs.required[inp.name][0])
|
||||
const wid = this.widgets.find(w => w.name === inp.name);
|
||||
if (wid && wid.origType !== this.local_input_defs.required[inp.name][0]) {
|
||||
wid.origType = this.local_input_defs.required[inp.name][0];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function setupInputNode(nodeType) {
|
||||
nodeType.prototype.onNodeCreated = function() {
|
||||
const originalOnAdded = nodeType.prototype.onAdded;
|
||||
nodeType.prototype.onAdded = function(graph) {
|
||||
if (originalOnAdded) {
|
||||
originalOnAdded.apply(this, arguments);
|
||||
}
|
||||
initialisation_onAdded(this);
|
||||
};
|
||||
|
||||
nodeType.prototype.onNodeCreated = function () {
|
||||
this.local_input_defs = this.local_input_defs || { required: {} };
|
||||
|
||||
initialisation_preGraph(this);
|
||||
|
||||
chainCallback(this, "onConfigure", configure);
|
||||
chainCallback(this, "onSerialize", serialize);
|
||||
initialisation(this);
|
||||
}
|
||||
};
|
||||
}
|
||||
+209
-52
@@ -1,76 +1,233 @@
|
||||
import { chainCallback, fitHeight } from "./utils.js";
|
||||
import { addInputs, cleanInputs, removeInputs } from "./inputs.js";
|
||||
import { addInputs, cleanInputs, removeInputs, addOutputs } from "./inputs.js"; // Consolidated imports
|
||||
import { importWorkflow } from "./workflows.js";
|
||||
import { addOutputs } from "./inputs.js";
|
||||
import { hideWidget } from "./widgets.js";
|
||||
|
||||
function initialisation(node) {
|
||||
const workflow_reload = node.title.startsWith("Workflow: ")?true:false;
|
||||
node.widgets[0].options.values = ["None", ...Object.keys(app.lipsync_studio)]
|
||||
node.widgets[0].callback = ( value ) => {
|
||||
cleanInputs(node);
|
||||
if (value == "None"){
|
||||
node.title = "Workflow (FlowChain ⛓️)";
|
||||
}else{
|
||||
node.widgets[1].value = importWorkflow(node, value, app);
|
||||
const inputs = app.lipsync_studio[value].inputs;
|
||||
addInputs(node, inputs, []);
|
||||
addOutputs(node, value);
|
||||
fitHeight(node);
|
||||
}
|
||||
};
|
||||
if (!workflow_reload){
|
||||
node.widgets[0].value = "None";
|
||||
node.widgets[1].value = "";
|
||||
}
|
||||
hideWidget(node, node.widgets[1], { holdSpace: false })
|
||||
cleanInputs(node);
|
||||
// Graph-independent part of initialization (called onNodeCreated)
|
||||
function initialisation_preGraph(node) {
|
||||
node.color = "#004670";
|
||||
node.bgcolor = "#002942";
|
||||
|
||||
// Basic widget setup that doesn't depend on the graph
|
||||
if (node.widgets && node.widgets[0] && node.widgets[0].options) {
|
||||
if (app && app.lipsync_studio) {
|
||||
node.widgets[0].options.values = ["None", ...Object.keys(app.lipsync_studio)];
|
||||
} else {
|
||||
console.warn("App or lipsync_studio not available during preGraph setup for workflow node.");
|
||||
node.widgets[0].options.values = ["None"];
|
||||
}
|
||||
}
|
||||
|
||||
const isReloading = node.title && node.title.startsWith("Workflow: ");
|
||||
if (!isReloading && node.widgets && node.widgets[0]) {
|
||||
node.widgets[0].value = "None"; // Default to "None"
|
||||
if (node.widgets[1]) {
|
||||
node.widgets[1].value = ""; // Clear workflow JSON
|
||||
}
|
||||
}
|
||||
|
||||
if (node.widgets && node.widgets[1]) {
|
||||
hideWidget(node, node.widgets[1], { holdSpace: false });
|
||||
}
|
||||
|
||||
// Setup the primary widget callback.
|
||||
// Operations inside this callback that need the graph (like cleanInputs, addInputs, addOutputs)
|
||||
// will be guarded by checking node.graph.
|
||||
if (node.widgets && node.widgets[0]) {
|
||||
node.widgets[0].callback = async (value) => {
|
||||
if (!node.graph) {
|
||||
console.warn("Workflow widget callback triggered, but node.graph is not yet available. Action might be deferred or skipped.");
|
||||
// If critical, you might need a flag to re-process this in onAdded
|
||||
return;
|
||||
}
|
||||
|
||||
cleanInputs(node); // Requires node.graph
|
||||
|
||||
if (value === "None") {
|
||||
node.title = "Workflow (FlowChain ⛓️)";
|
||||
// Further cleanup of dynamic inputs/outputs might be needed here
|
||||
// For example, explicitly removing all but the essential widgets/inputs/outputs.
|
||||
} else if (app && app.lipsync_studio && app.lipsync_studio[value]) {
|
||||
try {
|
||||
const workflowJSON = await importWorkflow(node, value, app); // importWorkflow updates node.title
|
||||
if (node.widgets && node.widgets[1]) {
|
||||
node.widgets[1].value = workflowJSON;
|
||||
}
|
||||
|
||||
// Ensure app.lipsync_studio[value] (and its .inputs) is still valid after await
|
||||
if (app.lipsync_studio[value] && app.lipsync_studio[value].inputs) {
|
||||
const inputs = app.lipsync_studio[value].inputs;
|
||||
addInputs(node, inputs, []); // Requires node.graph
|
||||
addOutputs(node, value); // Requires node.graph
|
||||
fitHeight(node);
|
||||
} else {
|
||||
console.error("Workflow data or inputs became unavailable after import for:", value);
|
||||
node.title = "Workflow (FlowChain ⛓️)"; // Reset title on error
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error processing workflow selection in callback:", error);
|
||||
node.title = "Workflow (FlowChain ⛓️)"; // Reset title on error
|
||||
}
|
||||
} else {
|
||||
node.title = "Workflow (FlowChain ⛓️)";
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Graph-dependent part of initialization (called onAdded)
|
||||
function initialisation_onAdded(node) {
|
||||
if (!node.graph) {
|
||||
console.error("CRITICAL: initialisation_onAdded called for workflow node, but node.graph is not set.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Perform an initial cleanInputs now that we are sure the graph exists.
|
||||
// This is important if the node was configured (e.g. from graph load) before being added.
|
||||
cleanInputs(node);
|
||||
|
||||
// If a workflow was already selected (e.g. loading a saved graph),
|
||||
// ensure its inputs/outputs are correctly set up.
|
||||
// The widget callback might have already run if `configure` set its value.
|
||||
// We re-evaluate or trigger necessary setup steps.
|
||||
if (node.widgets && node.widgets[0]) {
|
||||
const selectedWorkflow = node.widgets[0].value;
|
||||
if (selectedWorkflow && selectedWorkflow !== "None") {
|
||||
if (app && app.lipsync_studio && app.lipsync_studio[selectedWorkflow] && app.lipsync_studio[selectedWorkflow].inputs) {
|
||||
// If workflow JSON is loaded and inputs are known, ensure UI is consistent
|
||||
if (node.widgets[1] && (node.widgets[1].value === "" || typeof node.widgets[1].value !== 'string' || !node.widgets[1].value.startsWith('{'))) {
|
||||
// Workflow selected, but JSON not loaded in widget[1] or inputs/outputs not added by callback yet.
|
||||
// Trigger the callback logic.
|
||||
console.log(`Workflow node ${node.id}: Re-evaluating selected workflow '${selectedWorkflow}' onAdded.`);
|
||||
node.widgets[0].callback(selectedWorkflow);
|
||||
} else {
|
||||
// Workflow JSON likely loaded, ensure inputs/outputs are present
|
||||
// This can be a fallback if the callback didn't fully setup due to timing
|
||||
const inputs = app.lipsync_studio[selectedWorkflow].inputs;
|
||||
const currentWidgetValues = node.widgets_values || []; // from node.configure
|
||||
addInputs(node, inputs, currentWidgetValues);
|
||||
addOutputs(node, selectedWorkflow);
|
||||
removeInputs(node, inputs, currentWidgetValues); // Ensure this logic is sound for onAdded
|
||||
fitHeight(node);
|
||||
}
|
||||
} else if (selectedWorkflow !== "None") {
|
||||
// Workflow selected, but its definition isn't in app.lipsync_studio. Try to load it.
|
||||
console.warn(`Workflow node ${node.id}: '${selectedWorkflow}' selected but not in lipsync_studio. Attempting import via callback.`);
|
||||
node.widgets[0].callback(selectedWorkflow); // This will attempt importWorkflow
|
||||
}
|
||||
} else {
|
||||
node.title = "Workflow (FlowChain ⛓️)";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function configure(info) {
|
||||
if (info.widgets_values[0] != "None"){
|
||||
const inputs = app.lipsync_studio[info.widgets_values[0]].inputs;
|
||||
// `this` is the node. Called when loading graph data.
|
||||
// `info.widgets_values` contains the saved values for widgets.
|
||||
if (!app || !app.lipsync_studio) {
|
||||
console.error("App or lipsync_studio not available during workflow node configure.");
|
||||
return;
|
||||
}
|
||||
|
||||
addInputs(this, inputs, info.widgets_values);
|
||||
addOutputs(this, info.widgets_values[0]);
|
||||
removeInputs(this, inputs, info.widgets_values);
|
||||
fitHeight(this);
|
||||
importWorkflow(this, info.widgets_values[0], app)
|
||||
.then(data => {
|
||||
if (data){
|
||||
this.widgets[1].value = data;
|
||||
const inputs = app.lipsync_studio[info.widgets_values[0]].inputs;
|
||||
// Update widget options first, in case lipsync_studio has changed since last save
|
||||
if (this.widgets && this.widgets[0] && this.widgets[0].options) {
|
||||
this.widgets[0].options.values = ["None", ...Object.keys(app.lipsync_studio)];
|
||||
}
|
||||
|
||||
addInputs(this, inputs, info.widgets_values);
|
||||
addOutputs(this, info.widgets_values[0]);
|
||||
removeInputs(this, inputs, info.widgets_values);
|
||||
fitHeight(this);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Erreur lors de l\'importation:', error);
|
||||
});
|
||||
const selectedWorkflowName = info.widgets_values ? info.widgets_values[0] : "None";
|
||||
|
||||
if (this.widgets && this.widgets[0]) {
|
||||
this.widgets[0].value = selectedWorkflowName; // Set the widget value from saved data
|
||||
}
|
||||
if (this.widgets && this.widgets[1] && info.widgets_values && info.widgets_values[1]) {
|
||||
this.widgets[1].value = info.widgets_values[1]; // Set the hidden workflow JSON
|
||||
}
|
||||
|
||||
|
||||
if (selectedWorkflowName === "None") {
|
||||
this.title = "Workflow (FlowChain ⛓️)";
|
||||
if (this.graph) { // Only clean if graph is available
|
||||
cleanInputs(this);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// If the node is already on a graph, we can proceed with fuller setup.
|
||||
// If not, onAdded will handle the rest.
|
||||
if (this.graph) {
|
||||
if (app.lipsync_studio[selectedWorkflowName] && app.lipsync_studio[selectedWorkflowName].inputs) {
|
||||
this.title = app.lipsync_studio[selectedWorkflowName].title || `Workflow: ${selectedWorkflowName}`;
|
||||
const inputs = app.lipsync_studio[selectedWorkflowName].inputs;
|
||||
addInputs(this, inputs, info.widgets_values || []);
|
||||
addOutputs(this, selectedWorkflowName);
|
||||
removeInputs(this, inputs, info.widgets_values || []);
|
||||
fitHeight(this);
|
||||
} else {
|
||||
// Data not yet in lipsync_studio, try to import.
|
||||
// The callback of widget[0] will handle this if triggered by value change,
|
||||
// or onAdded will pick it up.
|
||||
// For configure, we might just set the title and let onAdded handle full setup.
|
||||
this.title = `Workflow: ${selectedWorkflowName} (loading...)`;
|
||||
// Avoid calling importWorkflow directly here if onAdded will robustly handle it,
|
||||
// to prevent multiple calls.
|
||||
}
|
||||
} else {
|
||||
// Graph not yet available, title will be set, onAdded will do the heavy lifting.
|
||||
this.title = `Workflow: ${selectedWorkflowName}`;
|
||||
}
|
||||
}
|
||||
|
||||
function serialize(info) {
|
||||
for (let inp of this.inputs){
|
||||
if (inp.widget){
|
||||
if (inp.type != this.local_input_defs.required[inp.name][0])
|
||||
// Standard serialization of widget values is usually handled by LiteGraph.
|
||||
// This custom serialize can ensure specific data is captured if needed.
|
||||
// info.widgets_values will be populated by LiteGraph based on current widget values.
|
||||
|
||||
// If local_input_defs logic is still needed for type adjustments:
|
||||
if (this.inputs && this.local_input_defs && this.local_input_defs.required) {
|
||||
for (let inp of this.inputs) {
|
||||
if (inp.widget &&
|
||||
this.local_input_defs.required[inp.name] &&
|
||||
this.local_input_defs.required[inp.name][0] !== undefined &&
|
||||
inp.type !== this.local_input_defs.required[inp.name][0]) {
|
||||
inp.type = this.local_input_defs.required[inp.name][0];
|
||||
}
|
||||
}
|
||||
}
|
||||
if(this.widgets[0].options.values == "COMBO"){
|
||||
this.widgets[0].options.values = ["None", ...Object.keys(app.lipsync_studio)];
|
||||
}
|
||||
}
|
||||
|
||||
export function setupWorkflowNode(nodeType) {
|
||||
nodeType.prototype.onNodeCreated = function() {
|
||||
const originalOnAdded = nodeType.prototype.onAdded;
|
||||
nodeType.prototype.onAdded = function(graph) {
|
||||
if (originalOnAdded) {
|
||||
originalOnAdded.apply(this, arguments);
|
||||
}
|
||||
initialisation_onAdded(this); // Our graph-dependent setup
|
||||
};
|
||||
|
||||
const originalOnRemoved = nodeType.prototype.onRemoved;
|
||||
nodeType.prototype.onRemoved = function() {
|
||||
// Perform any cleanup specific to this node when removed from graph
|
||||
// For example, disconnecting callbacks or releasing resources
|
||||
if (originalOnRemoved) {
|
||||
originalOnRemoved.apply(this, arguments);
|
||||
}
|
||||
};
|
||||
|
||||
nodeType.prototype.onNodeCreated = function() {
|
||||
// This is one of the first lifecycle hooks.
|
||||
// Initialize properties that don't depend on the graph.
|
||||
this.local_input_defs = this.local_input_defs || { required: {} };
|
||||
|
||||
// Add essential widgets if not already present (LiteGraph usually handles this from type definition)
|
||||
// For a "COMBO" and a "STRING" (hidden)
|
||||
if (!this.widgets || this.widgets.length < 2) {
|
||||
this.addWidget("combo", "Workflow", "None", () => {}, { values: ["None"] });
|
||||
this.addWidget("string", "workflow_json", "", () => {}, { multiline: true });
|
||||
}
|
||||
|
||||
initialisation_preGraph(this); // Our graph-independent setup
|
||||
|
||||
chainCallback(this, "onConfigure", configure);
|
||||
chainCallback(this, "onSerialize", serialize);
|
||||
initialisation(this);
|
||||
}
|
||||
};
|
||||
}
|
||||
+28
@@ -14,6 +14,27 @@ import gc
|
||||
import folder_paths
|
||||
from server import PromptServer
|
||||
from execution import PromptExecutor
|
||||
from comfy.model_patcher import ModelPatcher # Added import
|
||||
from comfy.sd import CLIP, VAE # Added imports
|
||||
|
||||
|
||||
# Add this custom JSON Encoder class
|
||||
class ComfyUIObjectEncoder(json.JSONEncoder):
|
||||
def default(self, obj):
|
||||
if isinstance(obj, torch.Tensor):
|
||||
return f"<Tensor shape={obj.shape} dtype={obj.dtype} device={obj.device}>"
|
||||
elif isinstance(obj, ModelPatcher):
|
||||
# Represent ModelPatcher as a descriptive string
|
||||
# You could add more details if needed, e.g., obj.model.__class__.__name__
|
||||
return f"<ModelPatcher instance: {type(obj.model).__name__ if hasattr(obj, 'model') else 'Generic'}>"
|
||||
elif isinstance(obj, CLIP):
|
||||
# Represent CLIP as a descriptive string
|
||||
return f"<CLIP instance: {type(obj.cond_stage_model).__name__ if hasattr(obj, 'cond_stage_model') else 'Generic'}>"
|
||||
elif isinstance(obj, VAE):
|
||||
# Represent VAE as a descriptive string
|
||||
return f"<VAE instance: {type(obj.first_stage_model).__name__ if hasattr(obj, 'first_stage_model') else 'Generic'}>"
|
||||
# Let the base class default method raise the TypeError for other types
|
||||
return json.JSONEncoder.default(self, obj)
|
||||
|
||||
|
||||
class ExecutionResult(Enum):
|
||||
@@ -428,6 +449,13 @@ class Workflow(SaveImage):
|
||||
|
||||
simple_server = SimpleServer()
|
||||
executor = PromptExecutor(simple_server)
|
||||
# Before executing, you can print the workflow structure for debugging
|
||||
print(json.dumps(workflow, indent=2, cls=ComfyUIObjectEncoder)) # MODIFIED: Use the new encoder
|
||||
# Or specifically find the ShowText node if you know its ID or can iterate
|
||||
for node_id, node_data in workflow.items():
|
||||
if node_data.get("class_type") == "ShowText": # Or whatever the exact class_type is
|
||||
print(f"ShowText Node {node_id} inputs: {node_data.get('inputs')}")
|
||||
|
||||
executor.execute(workflow, prompt_id, {"client_id": client_id}, workflow_outputs_id)
|
||||
|
||||
history_result = executor.history_result
|
||||
|
||||
Reference in New Issue
Block a user