review all process

This commit is contained in:
NumZ
2025-03-24 08:50:35 +01:00
parent 70750a3f27
commit 338672fd82
5 changed files with 333 additions and 578 deletions
+20 -11
View File
@@ -37,8 +37,22 @@ The idea came from the frustration users experienced with the many limitations a
By solving these problems, my tool makes node management more intuitive, stable, and efficient, allowing you to focus on what matters: creating.
## 🚀 Updates
**2025.03.23**
First of all, I apologize for not maintaining the code regularly. ComfyUI has evolved significantly since my last version, so I've completely revised the code to ensure better integration with ComfyUI and avoid future issues.
Features in this version:
- 💪 No need to convert your workflows to API format anymore - you can use original workflows directly.
- 💪 Better cache management
- 🗑️ remove "import workflow", no longer usefull since working on standart workflow.
-
Warning and know issue:
- 🛑 old version will not be compatible, sorry about that
- ⚠️ The word "seed" in input as "INT" will break the node so if you wan't use "seed" as input prefer to give a name like "seed_input" or whatever...
**2024.11.01 Initial version features :**
- 💪 Convert your workflows into nodes
1 Convert your workflows into nodes
- ⛓️ Chain your workflow
- 👄 Extra Node that use [LipSync Studio v0.6](https://www.patreon.com/Wav2LipStudio)
@@ -93,19 +107,14 @@ After this command be created folder Comfyui-FlowChain
## ⛓️ Workflow Node
![Illustration](docs/assets/workflow2.png)
- Load a workflow in **workflows** list. This field will show all workflows saved in the comfyui user folder: **ComfyUI\user\default\workflows\api**, if you add a new workflow in this folder you have to refresh UI (F5 to refresh web page) to see it in the **workflows** list.
- Workflows have to be saved as **API format** of comfyui, but save it also in normal format because "api formal file" can't be loaded in comfyui as usually.
Select a workflow from the **workflows** dropdown menu. This list displays all compatible workflows saved in your ComfyUI user directory:
<img src="docs/assets/save_as_api.png">
`ComfyUI\user\default\workflows`
If you don't see **"Export (API format)"** options in Comfyui do this :
- go to Settings
- Activate the **Dev Mode** options
![Illustration](docs/assets/devmode.png)
-You can also Import the file by "copy/paste" your workflow path in "workflow_api_path" and click import, that will add your workflow in the comfyui api path.
For a workflow to be considered **compatible**, it must contain at least one "Workflow Input" or "Workflow Output" node. This ensures the workflow can properly interface with the FlowChain system.
**Note:** After adding new workflows to this directory, you'll need to refresh the ComfyUI interface (press F5) to see them appear in the dropdown list. But once loaded, it will refresh the parent node automatically
## ⛓️ Input Node
![Illustration](docs/assets/input2.png)
Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 82 KiB

+33 -223
View File
@@ -83,7 +83,7 @@ async def workflows(request):
@server.PromptServer.instance.routes.get("/flowchain/workflow")
async def workflow(request):
user = UserManager().get_request_user_id(request)
result = {}
original_path = request.query.get("workflow_path")
unversal_path = original_path.replace("\\", "/")
json_path = unversal_path.split("/")
@@ -94,231 +94,41 @@ async def workflow(request):
if os.path.exists(json_path):
with open(json_path, "r", encoding="utf-8") as f:
json_content = json.load(f)
err = "none"
"""
nodes_input = {}
nodes_output = {}
# Vérifier le format du workflow (API ou standard)
if "nodes" in json_content:
try:
api_workflow = convert_standard_to_api_format(json_content)
if api_workflow:
json_content = api_workflow
# Conservons la référence au workflow original si nécessaire
# json_content["_original_standard_format"] = True
else:
err = "Échec de la conversion du workflow standard en format API"
except Exception as e:
err = f"Erreur lors de la conversion: {str(e)}"
"""
result = {"error": err, "workflow": json_content, "file_name": file_name}
# Extraire les nœuds WorkflowInput et WorkflowOutput
for node in json_content["nodes"]:
if node.get("type") == "WorkflowInput":
# Convertir au format compatible pour le client
node_id = str(node.get("id", "unknown"))
nodes_input[node_id] = {
"class_type": "WorkflowInput",
"inputs": node.get("widgets_values", {})
}
elif node.get("type") == "WorkflowOutput":
node_id = str(node.get("id", "unknown"))
nodes_output[node_id] = {
"class_type": "WorkflowOutput",
"inputs": node.get("widgets_values", {})
}
else:
# Format API
nodes_input = {k: v for k, v in json_content.items() if
v.get("class_type") == "WorkflowInput"}
nodes_output = {k: v for k, v in json_content.items() if
v.get("class_type") == "WorkflowOutput"}
# Ajouter au résultat seulement si le fichier contient des nœuds WorkflowInput ou WorkflowOutput
if nodes_input or nodes_output:
result = {"inputs": nodes_input,
"outputs": nodes_output,
'workflow': json_content}
else:
result = {"error": "File not found"}
return web.json_response(result, content_type='application/json')
def convert_standard_to_api_format(standard_workflow):
"""
Convertit un workflow au format standard en format API de manière similaire à graphToPrompt
"""
# Vérifier si c'est un workflow valide
if not standard_workflow or "nodes" not in standard_workflow or not isinstance(standard_workflow["nodes"], list):
print("Format de workflow invalide")
return None
# Créer une structure pour retrouver les nœuds par ID
nodes_by_id = {node["id"]: node for node in standard_workflow["nodes"]}
# Créer une map des liens
links_by_id = {}
if "links" in standard_workflow and isinstance(standard_workflow["links"], list):
for link in standard_workflow["links"]:
# Format du lien: [id, origin_node, origin_slot, target_node, target_slot, type]
if len(link) >= 5:
links_by_id[link[0]] = {
"id": link[0],
"origin_id": link[1],
"origin_slot": link[2],
"target_id": link[3],
"target_slot": link[4],
"type": link[5] if len(link) > 5 else None
}
# Map des liens par cible (nœud + slot)
links_by_target = {}
for link_id, link in links_by_id.items():
key = f"{link['target_id']}-{link['target_slot']}"
links_by_target[key] = link
# Résultat au format API
api_format = {}
# Obtenir un ordre d'exécution approximatif (simplifié)
# Remarque: c'est une simplification, le vrai ordre d'exécution est plus complexe
execution_order = simulate_execution_order(standard_workflow)
# Traiter les nœuds selon l'ordre d'exécution simulé
for node_id in execution_order:
node = nodes_by_id.get(node_id)
if not node:
continue
# Ignorer les nœuds désactivés (mode NEVER ou BYPASS) - similaire à LGraphEventMode
# Le mode 2 correspond généralement à NEVER, le mode 3 à BYPASS dans LiteGraph
if node.get("mode") in [2, 4]:
continue
# Initialiser le nœud au format API
api_node = {
"inputs": {},
"class_type": node.get("type", ""),
"_meta": {
"title": node.get("title", node.get("type", ""))
}
}
# Traiter les widgets (valeurs des paramètres)
if "widgets_values" in node:
# Si c'est un tableau (ancien format)
if isinstance(node["widgets_values"], list):
# Associer chaque valeur à son nom si disponible
if "widgets" in node and isinstance(node["widgets"], list):
for i, widget in enumerate(node["widgets"]):
if i < len(node["widgets_values"]) and "name" in widget:
api_node["inputs"][widget["name"]] = node["widgets_values"][i]
# Si c'est un objet (format plus récent)
elif isinstance(node["widgets_values"], dict):
for name, value in node["widgets_values"].items():
# Si la valeur a une structure spécifique avec une propriété "value"
if isinstance(value, dict) and "value" in value:
api_node["inputs"][name] = value["value"]
else:
api_node["inputs"][name] = value
# Traiter les connexions d'entrée
if "inputs" in node and isinstance(node["inputs"], list):
for slot_index, input_data in enumerate(node["inputs"]):
if not input_data:
continue
input_name = input_data.get("name", f"input_{slot_index}")
# Vérifier si cette entrée a un lien
if "link" in input_data and input_data["link"] is not None:
link = links_by_id.get(input_data["link"])
if link:
# Suivre les redirections (nœuds en bypass)
origin_id, origin_slot = trace_link_through_bypasses(
link["origin_id"],
link["origin_slot"],
nodes_by_id,
links_by_id
)
if origin_id is not None:
# Format API pour une connexion: [node_id, output_slot]
api_node["inputs"][input_name] = [str(origin_id), origin_slot]
# Ajouter le nœud au format API
api_format[str(node_id)] = api_node
# Nettoyer les liens qui pointent vers des nœuds inexistants
for node_id, node_data in api_format.items():
inputs_to_remove = []
for input_name, input_value in node_data["inputs"].items():
if isinstance(input_value, list) and len(input_value) == 2:
source_node_id = input_value[0]
if source_node_id not in api_format:
inputs_to_remove.append(input_name)
for input_name in inputs_to_remove:
del node_data["inputs"][input_name]
return api_format
def simulate_execution_order(workflow):
"""
Simule un ordre d'exécution approximatif des nœuds.
C'est une simplification de graph.computeExecutionOrder().
"""
# Fonction simplifiée - dans un vrai graphe, cela serait plus complexe
# en impliquant une analyse topologique du graphe de dépendances
# On construit un graphe de dépendances
nodes_by_id = {node["id"]: node for node in workflow["nodes"]}
dependencies = {node["id"]: set() for node in workflow["nodes"]}
# Trouver les dépendances de chaque nœud
if "links" in workflow and isinstance(workflow["links"], list):
for link in workflow["links"]:
if len(link) >= 5:
target_id = link[3] # Le nœud qui reçoit la connexion
source_id = link[1] # Le nœud d'origine
if target_id in dependencies:
dependencies[target_id].add(source_id)
# Tri topologique simplifié
visited = set()
result = []
def visit(node_id):
if node_id in visited:
return
visited.add(node_id)
for dep_id in dependencies[node_id]:
if dep_id in dependencies: # S'assurer que le nœud existe
visit(dep_id)
result.append(node_id)
# Visiter tous les nœuds
for node_id in dependencies:
if node_id not in visited:
visit(node_id)
return result
def trace_link_through_bypasses(origin_id, origin_slot, nodes_by_id, links_by_id):
"""
Suit une chaîne de nœuds en mode BYPASS pour trouver la source réelle.
Simule le comportement de la boucle while dans graphToPrompt.
"""
current_id = origin_id
current_slot = origin_slot
# Maximum d'itérations pour éviter les boucles infinies
max_iterations = 100
iterations = 0
while iterations < max_iterations:
iterations += 1
parent = nodes_by_id.get(current_id)
if not parent:
break
# Si le nœud parent n'est pas en mode BYPASS, on a trouvé la source
if parent.get("mode") != 3: # 3 est généralement le mode BYPASS
return current_id, current_slot
# Chercher le lien d'entrée correspondant pour continuer à remonter
if "inputs" in parent and isinstance(parent["inputs"], list):
for i, input_data in enumerate(parent["inputs"]):
if not input_data or "link" not in input_data:
continue
# Vérifier si le type correspond (simplifié)
# Dans le vrai code, il y a une vérification plus complexe des types
link = links_by_id.get(input_data["link"])
if link:
# Mise à jour pour la prochaine itération
current_id = link["origin_id"]
current_slot = link["origin_slot"]
break
else:
# Aucun lien compatible trouvé
break
# Si on arrive ici, soit on a atteint le maximum d'itérations,
# soit on n'a pas trouvé de chemin valide
return origin_id, origin_slot
+271 -337
View File
@@ -273,7 +273,7 @@ function addLoadVideoCommon(nodeType, nodeData) {
addVideoPreview(nodeType);
addPreviewOptions(nodeType);
}
function cleanInputs(root_obj, nodeData, reset_value=true) {
function cleanInputs(root_obj, start_index = 2, reset_value=true) {
//nodeData.input.required = {};
/*
if (!root_obj.inputs) root_obj.inputs = [];
@@ -302,13 +302,15 @@ function cleanInputs(root_obj, nodeData, reset_value=true) {
}
root_obj.widgets = root_obj.widgets.splice(0,2)
root_obj.widgets = root_obj.widgets.splice(0,start_index)
if(reset_value){
root_obj.widgets_values = [];
/*
for (let key in root_obj.widgets_values) {
if (key != "workflows"){
delete root_obj.widgets_values[key];
}
}
}*/
const max_node_output = root_obj.outputs.length;
for(let i = 0; i<max_node_output; i++)
root_obj.removeOutput(0)
@@ -326,9 +328,7 @@ function cleanInputs(root_obj, nodeData, reset_value=true) {
}
function clearInputs(root_obj, reset_value=true) {
//nodeData.input.required = {};
if (!root_obj.inputs) root_obj.inputs = [];
if (!root_obj.outputs) root_obj.outputs = [];
if (!root_obj.widgets) root_obj.widgets = [];
if (!root_obj.widgets_values) root_obj.widgets_values = [];
// Déconnecter tous les liens d'abord pour les entrées
@@ -375,6 +375,7 @@ function clearInputs(root_obj, reset_value=true) {
}*/
}
/*
function addWidgetType(root_obj, value, nodeData){
if (!root_obj.widgets) {
root_obj.widgets = [];
@@ -437,7 +438,7 @@ function addWidgetType(root_obj, value, nodeData){
if (type == "VAE") root_obj.addInput(field_name, "VAE");
}
*/
async function convertWorkflowToApiFormat(standardWorkflow) {
try {
return new Promise((resolve, reject) => {
@@ -528,117 +529,63 @@ async function importWorkflow(root_obj, workflow_path, app, nodeData, reset_valu
const filename = workflow_path.replace(/\\/g, '/').split("/");
root_obj.title = "Workflow: "+filename[filename.length-1].replace(".json", "").replace(/_/g, " ");
root_obj.local_input_defs = {
required: {},
optional: {}
};
/*
return api.fetchApi("/flowchain/workflow?workflow_path="+workflow_path)
.then(response => response.json())
.then(async data => {*/
cleanInputs(root_obj,nodeData, reset_values);
/*
if (data.error != "none"){
return false;
}*/
//let workflow = data.workflow;
let workflow = app.lipsync_studio[workflow_path].workflow;
// Si c'est un format standard, le convertir en format API
if ("nodes" in workflow) {
try {
//app.loading_bling = true;
workflow = await convertWorkflowToApiFormat(workflow);
//app.loading_bling = false;
} catch (error) {
console.error("Échec de la conversion du workflow:", error);
return false;
}
}
if (!workflow) {
console.error('Workflow invalide ou échec de conversion');
return false;
}
// Traiter le workflow API
const nodes_input = Object.fromEntries(
Object.entries(workflow).filter(([k, v]) => v.class_type == "WorkflowInput")
);
.then(async data => {
//cleanInputs(root_obj,nodeData, reset_values);
let workflow = data.workflow;
app.lipsync_studio[workflow_path] = data;
// Si c'est un format standard, le convertir en format API
if ("nodes" in workflow) {
try {
workflow = await convertWorkflowToApiFormat(workflow);
} catch (error) {
console.error("Échec de la conversion du workflow:", error);
return false;
}
}
if (!workflow) {
console.error('Workflow invalide ou échec de conversion');
return false;
}
/*
// Traiter le workflow API
const nodes_input = Object.fromEntries(
Object.entries(workflow).filter(([k, v]) => v.class_type == "WorkflowInput")
);
const nodes_output = Object.fromEntries(
Object.entries(workflow).filter(([k, v]) => v.class_type == "WorkflowOutput")
);
root_obj.widgets[1].value = JSON.stringify(workflow);
//ComfyWidgets.STRING(root_obj, "workflow", ['STRING',{default: JSON.stringify(workflow)},],app,)
//app.hideWidget(root_obj, root_obj.widgets[root_obj.widgets.length - 1], {holdSpace: false});
// root_obj.widgets[root_obj.widgets.length - 1].hidden = true;
// Ajouter les widgets d'entrée
Object.entries(nodes_input).forEach(node => {
addWidgetType(root_obj, node[1].inputs, nodeData);
});
const nodes_output = Object.fromEntries(
Object.entries(workflow).filter(([k, v]) => v.class_type == "WorkflowOutput")
);
root_obj.widgets[1].value = JSON.stringify(workflow);
Object.entries(nodes_input).forEach(node => {
addWidgetType(root_obj, node[1].inputs, nodeData);
});
// Ajouter les sorties
Object.entries(nodes_output).forEach(node => {
root_obj.addOutput(`${node[1].inputs.Name}`, node[1].inputs.type);
});
// Ajouter les sorties
Object.entries(nodes_output).forEach(node => {
root_obj.addOutput(`${node[1].inputs.Name}`, node[1].inputs.type);
});
root_obj.size[0] = 400;
return JSON.stringify(workflow);
/*
root_obj.size[0] = 400;*/
return JSON.stringify(workflow);
})
.catch(error => {
console.error('Erreur lors de l\'importation:', error);
return false;
});*/
}
/*
function importWorkflow(root_obj, workflow_path, app, reset_values=true){
const filename = workflow_path.replace(/\\/g, '/').split("/");
root_obj.title = "Workflow: "+filename[filename.length-1].replace(".json", "").replace(/_/g, " ");
api.fetchApi("/flowchain/workflow?workflow_path="+workflow_path)
.then(response => response.json())
.then(data =>{
cleanInputs(root_obj, reset_values);
if (data.error != "none"){
return false
}else{
let workflow = data.workflow;
console.log('Workflow:', workflow);
const nodes_input = Object.fromEntries(
Object.entries(workflow).filter(([k, v]) => v.class_type == "WorkflowInput")
);
const nodes_output = Object.fromEntries(
Object.entries(workflow).filter(([k, v]) => v.class_type == "WorkflowOutput")
);
Object.fromEntries(
Object.entries(nodes_input).filter((node, idx) => addWidgetType(root_obj, node[1].inputs))
);
Object.fromEntries(
Object.entries(nodes_output).filter((node, idx) =>root_obj.addOutput(`${node[1].inputs.Name}`, node[1].inputs.type))
);
console.log('Nodes:', nodes_input);
root_obj.size[0] = 400;
return true
}
})
.catch(error => {
console.error('Error:', error);
throw error; // Rilancia l'errore per consentire al chiamante di gestirlo
});
}
*/
function addWidgetInfo(root_obj, field_name, value, app, nodeData){
function addWidgets(root_obj, field_name, value, app){
let type = value.type;
if (type == "converted-widget"){
type = value.origType;
@@ -662,7 +609,7 @@ function addWidgetInfo(root_obj, field_name, value, app, nodeData){
ComfyWidgets.INT(
root_obj,
field_name,
['',{default: value.value, "min": 0, "max": 18446744073709551616, "step": 1},],
['INT',{default: value.value, "min": 0, "max": 18446744073709551616, "step": 1},],
app,
)
// Stocker la définition localement
@@ -673,7 +620,7 @@ function addWidgetInfo(root_obj, field_name, value, app, nodeData){
ComfyWidgets.FLOAT(
root_obj,
field_name,
['',{default: value.value, callback: (val) => console.log('VALUE', val), "min": 0.00, "max": 2048.00, "step": 0.01},],
['FLOAT',{default: value.value, "min": 0.00, "max": 2048.00, "step": 0.01},],
app,
)
// Stocker la définition localement
@@ -692,6 +639,22 @@ function addWidgetInfo(root_obj, field_name, value, app, nodeData){
// Stocker la définition localement
root_obj.local_input_defs.required[field_name] = ["STRING", {}];
}
if (type == "LATENT") root_obj.addInput(field_name, "LATENT");
if (type == "MODEL") root_obj.addInput(field_name, "MODEL");
if (type == "CLIP") root_obj.addInput(field_name, "CLIP");
if (type == "MASK") root_obj.addInput(field_name, "MASK");
if (type == "CONDITIONING") root_obj.addInput(field_name, "CONDITIONING");
if (type == "VAE") root_obj.addInput(field_name, "VAE");
}
function addOutputs(root_obj, workflow_name){
const outputs = app.lipsync_studio[workflow_name].outputs;
for (let [key, value] of Object.entries(outputs)){
const isoutput = root_obj.outputs.find(i => i.name == value.inputs.Name.value);
if (!isoutput){
root_obj.addOutput(value.inputs.Name.value, value.inputs.type.value);
}
}
}
// change
@@ -710,25 +673,32 @@ function hideWidget(
// @ts-expect-error custom widget type
widget.type = "converted-widget" + suffix
if (holdSpace) {
widget.computeSize = () => [0, LiteGraph.NODE_WIDGET_HEIGHT]
widget.computeSize = () => [0, LiteGraph.NODE_WIDGET_HEIGHT]
widget.serializeValue = (node, index) => {
// Prevent serializing the widget if we have no input linked
if (!node.inputs) {
return undefined
}
let node_input = node.inputs.find((i) => i.widget?.name === widget.name)
if (!node_input || !node_input.link) {
return undefined
}
return widget.origSerializeValue
? widget.origSerializeValue(node, index)
: widget.value
}
} else {
// -4 is due to the gap litegraph adds between widgets automatically
widget.computeSize = () => [0, -4]
}
widget.serializeValue = (node, index) => {
// Prevent serializing the widget if we have no input linked
if (!node.inputs) {
return undefined
}
let node_input = node.inputs.find((i) => i.widget?.name === widget.name)
if (!node_input || !node_input.link) {
return undefined
}
return widget.origSerializeValue
? widget.origSerializeValue(node, index)
: widget.value
// -4 is due to the gap litegraph adds between widgets automatically
widget.computeSize = () => [0, -4]
widget.serializeValue = (node, index) => {
return widget.origSerializeValue
? widget.origSerializeValue(node, index)
: widget.value
}
}
// Hide any linked widgets, e.g. seed+seedControl
if (widget.linkedWidgets) {
@@ -738,23 +708,6 @@ function hideWidget(
}
}
function showWidget(widget) {
// @ts-expect-error custom widget type
widget.type = widget.origType
widget.computeSize = widget.origComputeSize
widget.serializeValue = widget.origSerializeValue
delete widget.origType
delete widget.origComputeSize
delete widget.origSerializeValue
// Hide any linked widgets, e.g. seed+seedControl
if (widget.linkedWidgets) {
for (const w of widget.linkedWidgets) {
showWidget(w)
}
}
}
function getWidgetType(config) {
// Special handling for COMBO so we restrict links based on the entries
@@ -778,14 +731,7 @@ function hideWidget(
// Add input and store widget config for creating on primitive node
const [oldWidth, oldHeight] = node.size
/*const inputIsOptional = !!widget.options?.inputIsOptional
const input = node.addInput(widget.name, type, {
// @ts-expect-error [GET_CONFIG] is not a valid property of IWidget
widget: { name: widget.name, [GET_CONFIG]: () => config },
...(inputIsOptional ? { shape: LiteGraph.SlotShape.HollowCircle } : {})
})
*/
for (const widget of node.widgets) {
widget.last_y += LiteGraph.NODE_SLOT_HEIGHT
}
@@ -798,21 +744,34 @@ function hideWidget(
return node
}
function convertToWidget(node, widget) {
showWidget(widget)
const [oldWidth, oldHeight] = node.size
node.removeInput(node.inputs.findIndex((i) => i.widget?.name === widget.name))
for (const widget of node.widgets) {
widget.last_y -= LiteGraph.NODE_SLOT_HEIGHT
export function addInputs(root_obj, inputs, widgets_values){
let start_index = 2;
for (let [key, value] of Object.entries(inputs)){
const isinput = root_obj.inputs.find(i => i.name == value.inputs[0]);
const isWidget = root_obj.widgets.find(w => w.name == value.inputs[0]);
if (!isWidget && (!isinput||isinput.widget)){
if (value.inputs[2]){
const values = widgets_values[start_index] ?? value.inputs[2];
const widget_param = {value: values, type: value.inputs[1]}
//const widget_param = {value: widgets_values[start_index], type: value.inputs.default.type}
addWidgets(root_obj, value.inputs[0], widget_param, app);
if (isinput){
//node.removeInput(node.inputs.findIndex((i) => i.widget?.name === widget.name))
const config = [value.inputs[2]]
convertToInput(root_obj, root_obj.widgets[start_index], config)
}
start_index += 1;
}else{
const input_param = {type: value.inputs[1]};
addWidgets(root_obj, value.inputs[0], input_param, app);
}
}
}
// Restore original size but grow if needed
node.setSize([
Math.max(oldWidth, node.size[0]),
Math.max(oldHeight, node.size[1])
])
}
}
app.registerExtension({
name: "FlowChain.jsnodes",
@@ -823,19 +782,33 @@ app.registerExtension({
switch (nodeData.name) {
case "Workflow":
nodeType.prototype.onNodeCreated = function() {
nodeType.prototype.onNodeCreated = function() {
chainCallback(this, "onConfigure", function(info) {
//let widgetDict = info.widgets_values
if (!info.widgets_values[0] != "None" && !app.loading_bling){
if (this.widgets.length==2){
if (info.widgets_values[0] != "None"){
//this.widgets[1].value = importWorkflow(this, info.widgets_values[0], app, nodeData)
//if (this.widgets.length==2){
const inputs = app.lipsync_studio[info.widgets_values[0]].inputs;
addInputs(this, inputs, info.widgets_values);
addOutputs(this, info.widgets_values[0]);
importWorkflow(this, info.widgets_values[0], app, nodeData)
.then(data => {
if (data){
this.widgets[1].value = data;
const inputs = app.lipsync_studio[info.widgets_values[0]].inputs;
addInputs(this, inputs, info.widgets_values);
addOutputs(this, info.widgets_values[0]);
importWorkflow(this, info.widgets_values[0], app, nodeData)
}
})
.catch(error => {
console.error('Erreur lors de l\'importation:', error);
});
/*
if (info.widgets_values[1] != this.widgets[1].value){
this.widgets[1].value = info.widgets_values[1];
//hideWidget(this, this.widgets[1])
//ComfyWidgets.STRING(this, "workflow", ['STRING',{default: workflow},],app,);
//this.widgets[1].hidden = true;
}
//this.widgets[0].value = info.widgets_values[0];
const inputs = app.lipsync_studio[info.widgets_values[0]].inputs;
let start_index = 2;
@@ -844,7 +817,7 @@ app.registerExtension({
const isWidget = this.widgets.find(w => w.name == value.inputs.Name.value);
if (!isWidget && (!isinput||isinput.widget)){
const widget_param = {value: info.widgets_values[start_index], type: value.inputs.default.type}
addWidgetInfo(this, value.inputs.Name.value, widget_param, app, nodeData);
addWidgets(this, value.inputs.Name.value, widget_param, app, nodeData);
if (isinput){
//node.removeInput(node.inputs.findIndex((i) => i.widget?.name === widget.name))
const config = [value.inputs.default.type]
@@ -852,154 +825,105 @@ app.registerExtension({
}
start_index += 1;
}
}
}
}*/
//}
/*
if(info.widgets_values[0] != "None")
api.fetchApi("/flowchain/workflow?workflow_path="+info.widgets_values[0])
.then(response => response.json())
.then(async data => {
cleanInputs(this);
app.lipsync_studio[info.widgets_values[0]] = data;
addInputs(this, info.widgets_values[0], info.widgets_values);
})
.catch(error => {
console.error('Erreur lors de l\'importation:', error);
});*/
}
/*
if(info.widgets_values.workflows.value != "None"){
const workflow_name = info.widgets_values.workflows.value;
this.widgets[0].value = workflow_name;
console.log("workflow_name", workflow_name)
console.log(app.lipsync_studio[workflow_name])
const inputs = app.lipsync_studio[workflow_name].inputs;
for (let w of this.widgets) {
if (w.name in widgetDict) {
w.value = widgetDict[w.name].value;
}
}
for (let [key, value] of Object.entries(widgetDict)) {
let widget = this.widgets.find(w => w.name === key);
if(!widget){
addWidgetInfo(this, key, value, app, nodeData);
widget = this.widgets.find(w => w.name === key);
}
console.log(key)
if(widget){
console.log(info.widgets_values[key])
console.log(info.widgets_values[key].options)
widget.options = info.widgets_values[key].options;
widget.value = info.widgets_values[key].value;
}
}
}
}
if (this.id == -1){
for (let i = 0; i < this.outputs.length; i++) {
this.outputs[i] = {links: null, name: info.outputs_values[i].name, type: info.outputs_values[i].type};
}
}else{
if (info.outputs_values != undefined){
this.outputs = [...info.outputs_values];
}
}
this.setSize(info.size);*/
});
chainCallback(this, "onSerialize", function(info) {
for (let w of this.inputs){
// if w.name exists in info.widgets_values
if (w.widget){
if (w.type != this.local_input_defs.required[w.name][0])
w.type = this.local_input_defs.required[w.name][0];
}
}
// Créer la structure info.inputs si elle n'existe pas
/*
if (!info.inputs)
info.inputs = {};
// Ajouter les valeurs des widgets dans info.inputs
if (this.widgets) {
for (let w of this.widgets) {
// Stocker uniquement la valeur dans info.inputs
info.inputs[w.name] = w.value;
}
}
// Le reste de votre code existant pour info.widgets_values reste inchangé
info.widgets_values = {};
if (this.widgets) {
for (let w of this.widgets) {
info.widgets_values[w.name] = {
name: w.name,
options: w.options,
value: w.value,
type: w.type,
origType: w.origType,
last_y: w.last_y
};
}
}
*/
/*
let inps = {};
if (info.widgets_values[0] != "None"){
const workflow_name = info.widgets_values[0];
inps = app.lipsync_studio[workflow_name].inputs
}
info.widgets_values = {};
if (!this.widgets) {
return;
}
for (let w of this.widgets) {
info.widgets_values[w.name] = {name: w.name, options : w.options, value: w.value, type: w.type, origType: w.origType, last_y: w.last_y};
}
// info.outputs_values = [...this.outputs];
info.outputs_values = []
for (let w of this.outputs){
info.outputs_values.push({links: w.links, name: w.name, type: w.type});
}
for (let w of this.inputs){
for (let [key, value] of Object.entries(inps)){
if (value.inputs.Name == w.name){
w.type = value.inputs.type;
break;
}
}
}
this.setSize(info.size);
*/
});
const workflow_reload = this.title.startsWith("Workflow: ")?true:false;
if (!app.loading_bling){
this.widgets[0].options.values = ["None", ...Object.keys(app.lipsync_studio)]
this.widgets[0].callback = ( value ) => {
if (value == "None" || value == ""){
this.title = "Workflow (FlowChain ⛓️)";
cleanInputs(this, nodeData);
}else{
this.widgets[1].value = importWorkflow(this, value, app, nodeData)
}
};
if (!workflow_reload){
this.widgets[0].value = "None";
this.widgets[1].value = "";
this.widgets[0].options.values = ["None", ...Object.keys(app.lipsync_studio)]
this.widgets[0].callback = ( value ) => {
cleanInputs(this);
if (value == "None"){
this.title = "Workflow (FlowChain ⛓️)";
}else{
this.widgets[1].value = importWorkflow(this, value, app, nodeData);
const inputs = app.lipsync_studio[value].inputs;
addInputs(this, inputs, {});
addOutputs(this, value);
}
//hideWidget(this, this.widgets[1], { holdSpace: false })
cleanInputs(this, nodeData);
this.color = "#004670";
this.bgcolor = "#002942";
};
if (!workflow_reload){
this.widgets[0].value = "None";
this.widgets[1].value = "";
}
hideWidget(this, this.widgets[1], { holdSpace: false })
cleanInputs(this);
this.color = "#004670";
this.bgcolor = "#002942";
}
break;
case "WorkflowInput":
nodeType.prototype.onNodeCreated = function() {
chainCallback(this, "onConfigure", function(info) {
let widgetDict = info.widgets_values
if (info.widgets_values.length == undefined) {
let widgetDict = info.widgets_values;
const inputs = {};
inputs["default"] = {
inputs: ["default", info.widgets_values[1], info.widgets_values[2]]
/*
default:{value: info.widgets_values[2], type: info.widgets_values[1]},
Name: {value: "default", type: "STRING"},
type: {value: info.widgets_values[1], type: "COMBO"}*/
};
addInputs(this, inputs, info.widgets_values);
/*
const inputs = {};
for (let [k, value] of Object.entries(info.widgets_values)) {
if (k == "default"){
inputs[k] = {
inputs: {
default:{value: value.value, type: info.widgets_values.default.type},
Name: {value: k, type: "STRING"},
type: {value: info.widgets_values.default.type, type: "STRING"}
}
};
}else{
inputs[k] = {
inputs: {
default:{value: value.value, type: value.type},
Name: {value: k, type: value.type},
type: {value: value.type, type: value.type}
}
};
}
}
let widgetDict = {};
if(info.widgets_values.default)
widgetDict = [info.widgets_values.Name.value, info.widgets_values.default.type, info.widgets_values.default.value];
else
widgetDict = [info.widgets_values.Name.value, info.widgets_values.type.value];
addInputs(this, inputs, widgetDict);*/
/*if (info.widgets_values.length == undefined) {
for (let w of this.widgets) {
if (w.name in widgetDict) {
w.value = widgetDict[w.name].value;
@@ -1011,7 +935,14 @@ app.registerExtension({
let widget = this.widgets.find(w => w.name === key);
let type = this.widgets.find(w => w.name === "type");
if(!widget){
addWidgetInfo(this, key, value, app, nodeData);
let widget_param = {}
if (value.name == "default"){
widget_param = {value: value.value, type: type.value}
}else{
widget_param = value;
}
// addWidgets(this, key, widget_param, app);
addInputs(this, {inputs: {Name: {value: key}, type: {value: type.value}}}, widgetDict);
widget = this.widgets.find(w => w.name === key);
}
//this.widgets.push(value);
@@ -1046,25 +977,45 @@ app.registerExtension({
this.outputs[0] = {...info.outputs_values};
}
}
this.setSize(info.size);
/*for(let i = this.outputs.length - 1; i>0; i--){
if (this.outputs[i].name == "*"){
this.removeOutput(i);
}
}*/
this.setSize(info.size);*/
});
chainCallback(this, "onSerialize", function(info) {
let widgetDict = info.widgets_values;
for (let inp of this.inputs){
// if w.name exists in info.widgets_values
if (inp.widget){
if (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])
wid.origType = this.local_input_defs.required[inp.name][0];
}
}
}
/*
info.widgets_values = {};
if (!this.widgets) {
return;
}
for (let w of this.widgets) {
info.widgets_values[w.name] = {name: w.name, options : w.options, value: w.value, type: w.type, origType: w.origType, last_y: w.last_y};
}
if (w.name == "default"){
const default_type = this.inputs.find(i => i.name == "default");
if (default_type)
info.widgets_values[w.name] = {name: w.name, options : w.options, value: w.value, type: default_type.type};
else
info.widgets_values[w.name] = {name: w.name, options : w.options, value: w.value, type: info.widgets_values.type.value};
}else{
info.widgets_values[w.name] = {name: w.name, options : w.options, value: w.value, type: w.type};
}
}*/
/*
for (let w of this.inputs){
// if w.name exists in info.widgets_values
if (info.widgets_values[w.name]){
w.type = info.widgets_values[w.name].type;
if(info.widgets_values[w.name].type == "converted-widget"){
if(info.widgets_values[w.name].origType == "toggle"){
w.type = "BOOLEAN";
@@ -1082,12 +1033,13 @@ app.registerExtension({
info.outputs_values = {links: [...this.outputs[0].links], name: this.outputs[0].name, slot_index: this.outputs[0].slot_index, type: this.outputs[0].type};
}
}
this.setSize(info.size);
this.setSize(info.size);*/
});
this.widgets[1].callback = ( value ) => {
// D'abord, déconnecter tous les liens existants
for (let i = 0; i < this.outputs.length; i++) {
/*for (let i = 0; i < this.outputs.length; i++) {
const output = this.outputs[i];
if (output.links && output.links.length) {
const links = output.links.slice();
@@ -1103,14 +1055,16 @@ app.registerExtension({
this.graph.removeLink(input.link);
}
}
clearInputs(this);
clearInputs(this);*/
cleanInputs(this);
switch(value){
case "STRING":
this.addOutput("output", "STRING");
ComfyWidgets.STRING(
this,
"default",
["STRING",{default: "",callback: (val) => console.log('VALUE', val),},],
["STRING",{default: "",},],
app,
)
@@ -1130,7 +1084,7 @@ app.registerExtension({
ComfyWidgets.FLOAT(
this,
"default",
['',{default: 0,callback: (val) => console.log('VALUE', val), "min": 0.00, "max": 2048.00, "step": 0.01},],
['',{default: 0, "min": 0.00, "max": 2048.00, "step": 0.01},],
app,
)
break;
@@ -1170,7 +1124,7 @@ app.registerExtension({
let widget = this.widgets.find(w => w.name === key);
let type = this.widgets.find(w => w.name === "type");
if(!widget){
addWidgetInfo(this, key, value, app, nodeData);
addWidgets(this, key, value, app);
widget = this.widgets.find(w => w.name === key);
}
//this.widgets.push(value);
@@ -1183,13 +1137,6 @@ app.registerExtension({
//find if key exists in inputs array in inputs.Name
if (info.widgets_values[key].type != "converted-widget"){
this.removeInput(this.inputs.indexOf(input));
//input.type = info.widgets_values.type.value;
/*widget.type = "converted-widget"
widget.origType = info.widgets_values[key].origType;
widget.origComputeSize = undefined;
widget.last_y = info.widgets_values[key].last_y;
widget.origSerializeValue = nodeType.prototype.serializeValue;
widget.computeSize = () => [0, -4];*/
}
break;
}
@@ -1269,7 +1216,7 @@ app.registerExtension({
let widget = this.widgets.find(w => w.name === key);
let type = this.widgets.find(w => w.name === "type");
if(!widget){
addWidgetInfo(this, key, value, app, nodeData);
addWidgets(this, key, value, app);
widget = this.widgets.find(w => w.name === key);
}
//this.widgets.push(value);
@@ -1282,15 +1229,6 @@ app.registerExtension({
//find if key exists in inputs array in inputs.Name
if (info.widgets_values[key].type != "converted-widget"){
this.removeInput(this.inputs.indexOf(input));
//input.type = info.widgets_values.type.value;
/*
widget.type = "converted-widget"
widget.origType = info.widgets_values[key].origType;
widget.origComputeSize = undefined;
widget.last_y = info.widgets_values[key].last_y;
widget.origSerializeValue = nodeType.prototype.serializeValue;
widget.computeSize = () => [0, -4];
*/
}
break;
}
@@ -1312,11 +1250,7 @@ app.registerExtension({
this.outputs[0] = {...info.outputs_values};
}
}
/*for(let i = this.outputs.length - 1; i>0; i--){
if (this.outputs[i].name == "*"){
this.removeOutput(i);
}
}*/
});
chainCallback(this, "onSerialize", function(info) {
info.widgets_values = {};
+9 -7
View File
@@ -277,12 +277,13 @@ class Workflow(SaveImage):
# get max workflow id
# coinvert workflow_outputs to list
workflow_outputs = list(workflow_outputs.values())
workflow_node = {"node": {"id":k, **v} for k, v in workflow.items() if v["class_type"] == "Workflow" and v["inputs"]["workflows"] == workflow_name}
# prendre le premier workflow
workflow_node = [{"id":k, **v} for k, v in workflow.items() if v["class_type"] == "Workflow" and v["inputs"]["workflows"] == workflow_name][0]
sub_input_nodes = {k: v for k, v in subworkflow.items() if v["class_type"] == "WorkflowInput"}
do_not_delete = []
for sub_id, sub_node in sub_input_nodes.items():
if sub_node["inputs"]["Name"] in workflow_node["node"]["inputs"]:
value = workflow_node["node"]["inputs"][sub_node["inputs"]["Name"]]
if sub_node["inputs"]["Name"] in workflow_node["inputs"]:
value = workflow_node["inputs"][sub_node["inputs"]["Name"]]
if type(value) == list:
subworkflow = change_subnode(subworkflow, sub_id, value)
else:
@@ -298,7 +299,7 @@ class Workflow(SaveImage):
for input_name, input_value in node["inputs"].items():
if type(input_value) == list:
if len(input_value) > 0:
if input_value[0] == workflow_node["node"]["id"]:
if input_value[0] == workflow_node["id"]:
for sub_output_id, sub_output_node in sub_output_nodes.items():
if sub_output_node["inputs"]["Name"] == workflow_outputs[input_value[1]]["inputs"]["Name"]:
workflow[node_id]["inputs"][input_name] = sub_output_node["inputs"]["default"]
@@ -340,8 +341,7 @@ class Workflow(SaveImage):
workflow_outputs_sub = {k: v for k, v in subworkflow.items() if v["class_type"] == "WorkflowOutput"}
workflow, subworkflow = merge_inputs_outputs(workflow, workflow_name, subworkflow, workflow_outputs_sub)
workflow = {k: v for k, v in workflow.items() if
not (v["class_type"] == "Workflow" and v["inputs"]["workflows"] == workflow_name)}
workflow = {k: v for k, v in workflow.items() if k != key}
# add subworkflow to workflow
workflow.update(subworkflow)
return workflow, max_id
@@ -393,16 +393,18 @@ class Workflow(SaveImage):
}
# Now you can access the original inputs as before
queue_to_use = len(queue_info["queue_pending"]) > 0 and queue_info["queue_pending"] or queue_info["queue_running"]
queue_to_use = queue_info["queue_running"]
original_inputs = [v["inputs"] for k, v in queue_to_use[0][2].items() if
"workflows" in v["inputs"] and v["inputs"]["workflows"] == workflows][0]
# find "workflow" node in queue_info["queue_pending"][-1][3]["extra_png_info"]["workflow"]["nodes"]
"""
for node in queue_to_use[-1][3]["extra_pnginfo"]["workflow"]["nodes"]:
workflow_name = 'Workflow: ' + original_inputs["workflows"].replace(".json", "").replace("_", " ")
if node["type"] == "Workflow" and node["title"] == workflow_name:
workflow = node["widgets_values"][1]
break
"""
else:
# Fallback to empty inputs if server instance not available