import { addWidgets, convertToInput, getDefaultOptions } from "./widgets.js"; import { string_widget } from "./constants.js"; export function createColor(type, bg = false) { // Generate a consistent color based on the type string const hash = type.split("").reduce((acc, char) => { return char.charCodeAt(0) + ((acc << 5) - acc); }, 0); // Convert hash to RGB values let r = (hash & 0xff0000) >> 16; let g = (hash & 0x00ff00) >> 8; let b = hash & 0x0000ff; // For background colors, make them lighter // For background colors, make them darker by reducing RGB values if (bg) { const darkenFactor = 0.7; // Adjust this value to control darkness r = Math.floor(r * darkenFactor); g = Math.floor(g * darkenFactor); b = Math.floor(b * darkenFactor); return `#${r.toString(16).padStart(2, "0")}${g .toString(16) .padStart(2, "0")}${b.toString(16).padStart(2, "0")}`; } else { return `#${r.toString(16).padStart(2, "0")}${g .toString(16) .padStart(2, "0")}${b.toString(16).padStart(2, "0")}`; } } export function cleanInputs(root_obj, start_index = 2, reset_value = true) { for (let i = 0; i < root_obj.outputs.length; i++) { const output = root_obj.outputs[i]; if (output.links && output.links.length) { // Make a copy of the links array because it will be modified during disconnection const links = output.links.slice(); for (const linkId of links) { root_obj.graph.removeLink(linkId); } } } // Same for inputs for (let i = 0; i < root_obj.inputs.length; i++) { const input = root_obj.inputs[i]; if (input.link) { root_obj.graph.removeLink(input.link); } } root_obj.widgets = root_obj.widgets.splice(0, start_index); if (reset_value) { root_obj.widgets_values = []; const max_node_output = root_obj.outputs.length; for (let i = 0; i < max_node_output; i++) root_obj.removeOutput(0); } const max_node_input = root_obj.inputs.length; for (let i = 0; i < max_node_input; i++) root_obj.removeInput(0); } export function clearInputs(root_obj, reset_value = true, splice = 2) { //nodeData.input.required = {}; if (!root_obj.widgets_values) root_obj.widgets_values = []; // Déconnecter tous les liens d'abord pour les entrées for (let i = 0; i < root_obj.outputs.length; i++) { const output = root_obj.outputs[i]; if (output.links && output.links.length) { const links = output.links.slice(); for (const linkId of links) { root_obj.graph.removeLink(linkId); } } } // Parcourir les entrées en sens inverse pour éviter les problèmes d'indice for (let i = root_obj.inputs.length - 1; i >= 0; i--) { const input = root_obj.inputs[i]; if ((input.name === "default" || input.name === "input") && input.link) { root_obj.graph.removeLink(input.link); } } for (let i = 0; i < root_obj.inputs.length; i++) { if ( root_obj.inputs[i].name == "default" || root_obj.inputs[i].name == "input" ) { root_obj.removeInput(i); } } root_obj.widgets = root_obj.widgets.splice(0, splice); if (reset_value) { for (let key in root_obj.widgets_values) { if (key != "Name" && key != "type") { delete root_obj.widgets_values[key]; } } } const max_node_input = root_obj.outputs.length; for (let i = root_obj.outputs.length - 1; i >= 0; i--) root_obj.removeOutput(i); /* if (root_obj.graph) { root_obj.graph.setDirtyCanvas(true); root_obj.graph.change(); }*/ } export function addOutputs(root_obj, workflow_name) { const outputs = app.lipsync_studio[workflow_name].outputs; for (let [key, value] of Object.entries(outputs)) { let name = ""; let type = ""; if (value.inputs.Name) { name = value.inputs.Name.value; type = value.inputs.type.value; } else { name = value.inputs[0]; type = value.inputs[1]; } const isoutput = root_obj.outputs.find((i) => i.name == name); if (!isoutput) { root_obj.addOutput(name, type); } } // remove out not in outputs const all_outputs = Object.entries(outputs).map(([key, value]) => value); for (let i = 0; i < root_obj.outputs.length; i++) { if (!all_outputs.find((out) => out.inputs[0] == root_obj.outputs[i].name)) { root_obj.removeOutput(i); } } organizeOutputs(root_obj, workflow_name); } export function addInputs(node, inputs, widgets_values) { // Phase 1: Préparer les données triées et simplifiées const mapped_input = Object.entries(inputs) .sort(([, a], [, b]) => a.position - b.position) .map(([, input], index) => { let val = ""; let type = input.inputs[1]; if (input.inputs[2]?.values?.length > 0) { type = "COMBO"; val = input.inputs[1]; } else { val = input.inputs.length > 2 ? input.inputs[2] : undefined; } return { name: input.inputs[0], type: type, value: val, orderIndex: index, // Ajouter un index d'ordre basé sur la position options: type == "COMBO" ? input.inputs[2] : undefined, }; }); // Séparer les inputs pour un traitement différencié const widget_inputs = mapped_input.filter( (input) => input.value !== undefined && input.value !== null ); // replace value in widget_inputs with the value in widgets_values if (widgets_values) { for (let i = 0; i < widget_inputs.length; i++) { const widget_input = widget_inputs[i]; const widget_value = widgets_values[2 + i]; //find name in mapped_input and replace value if (widget_value) for (let j = 0; j < mapped_input.length; j++) if (mapped_input[j].name == widget_input.name) { mapped_input[j].value = widget_value; break; } } } const pure_inputs = mapped_input.filter( (input) => input.value === undefined || input.value === null ); // S'assurer que le nœud a un objet local_input_defs if (!node.local_input_defs) { node.local_input_defs = { required: {}, optional: {}, }; } // Phase 2: Ajouter les nouveaux widgets/inputs avec leur index d'ordre for (const input of mapped_input) { const { name, type, value, orderIndex, options } = input; let isWidget = node.widgets.find((w) => w.name === name); let isinput = node.inputs.filter((i) => i.name === name); const seen = {}; const indexesToRemove = []; node.inputs.forEach((input, idx) => { if (seen[input.name] !== undefined) { indexesToRemove.push(idx); } else { seen[input.name] = idx; } }); if (node.graph._nodes){ for (let i = indexesToRemove.length - 1; i >= 0; i--) { node.removeInput(indexesToRemove[i]); } } isinput = node.inputs.find((i) => i.name === name); if (!isWidget && (!isinput || isinput.widget)) { if (value !== undefined) { // Ajouter un widget avec sa valeur et son index d'ordre let widget_param = { value, type, options }; if (string_widget.includes(type)) widget_param = { value, type: "STRING" }; addWidgets(node, name, widget_param, app); isWidget = node.widgets.find((w) => w.name === name); if (isWidget) { if (orderIndex !== undefined) isWidget.orderIndex = orderIndex; // Stocker l'index d'ordre// Stocker l'index d'ordre } if (isinput && !isinput.widget) { convertToInput(node, isWidget); // Préserver l'index d'ordre sur l'input converti const newInput = node.inputs.find((i) => i.name === name); if (newInput && orderIndex !== undefined) newInput.orderIndex = orderIndex; newInput.pos = isWidget.pos; } } else { // C'est un input pur if (!isinput) { node.addInput(name, type); const newInput = node.inputs[node.inputs.length - 1]; newInput.orderIndex = orderIndex; // Stocker l'index d'ordre } } } else { // Mettre à jour l'index d'ordre des éléments existants if (isWidget) isWidget.orderIndex = orderIndex; if (isinput) isinput.orderIndex = orderIndex; } } // Phase 3: Mettre à jour les types si nécessaires for (const input of mapped_input) { const { name, type, value } = input; let isWidget = node.widgets.find((w) => w.name === name); let isinput = node.inputs.find((i) => i.name === name); if (isWidget || isinput) { const localType = node.local_input_defs?.required[name] || null; const typeToUse = string_widget.includes(type) ? "STRING" : type; // Vérifier si le type est différent et le mettre à jour if (localType && localType[0] !== typeToUse) { if (isinput) { if (isinput.link) { node.graph.removeLink(isinput.link); } isinput.type = typeToUse; } if (isWidget) { isWidget.type = typeToUse; const options = getDefaultOptions(typeToUse, value); node.local_input_defs.required[name] = [typeToUse, options]; isWidget.options = options; isWidget.value = value !== undefined ? value : isWidget.value; } } } } // Phase 4: Réorganiser les widgets selon leur index d'ordre // Séparer les widgets système (les 2 premiers) des widgets à trier const systemWidgets = node.widgets.slice(0, 2); const sortableWidgets = node.widgets.slice(2); // Trier les widgets par index d'ordre sortableWidgets.sort((a, b) => { if (a.orderIndex === undefined) return 1; if (b.orderIndex === undefined) return -1; return a.orderIndex - b.orderIndex; }); // Reconstruire le tableau des widgets node.widgets = [...systemWidgets, ...sortableWidgets]; // Maintenant ajuster les positions visuelles des widgets for (let i = 2; i < node.widgets.length; i++) { node.widgets[i].y = node.widgets[i - 1].y + (node.widgets[i - 1].computedHeight || LiteGraph.NODE_WIDGET_HEIGHT); node.widgets[i].last_y = node.widgets[i].y; } // Réorganiser les inputs purs for (let i = 0; i < pure_inputs.length; i++) { const targetInput = pure_inputs[i]; const actualIndex = node.inputs.findIndex( (input) => input.name === targetInput.name ); if (actualIndex !== -1) { // const actualIndex = node.inputs.indexOf(pureNodeInputs[inputIndex]); const expected_position = i; if (actualIndex !== expected_position) { // Déconnecter temporairement le lien s'il existe let link = null; if (node.inputs[actualIndex].link) { link = node.graph.links[node.inputs[actualIndex].link]; node.graph.removeLink(node.inputs[actualIndex].link); } // Sauvegarder les propriétés importantes const inputToMove = node.inputs[actualIndex]; const inputName = inputToMove.name; const inputType = inputToMove.type; const inputWidget = inputToMove.widget; const inputPos = inputToMove.pos; // Supprimer l'input node.removeInput(actualIndex); // Ajouter à la position attendue node.addInput( inputName, inputType, { pos: inputPos, widget: inputWidget }, expected_position ); // Reconnecter le lien si nécessaire if (link && link.origin_id !== null) { const newIndex = node.inputs.findIndex( (input) => input.name === targetInput.name ); link.target_slot = newIndex; node.graph.links[link.id] = link; node.inputs[newIndex].link = link.id; } } } } // Phase 6: S'assurer que les inputs avec widget sont toujours à la fin de la liste const inputsWithWidgets = []; const inputsWithoutWidgets = []; // 1. Trier les inputs en deux groupes for (let i = 0; i < node.inputs.length; i++) { if (node.inputs[i].widget) { inputsWithWidgets.push(i); } else { inputsWithoutWidgets.push(i); } } // 2. Si des inputs avec widgets ne sont pas déjà à la fin, les réorganiser if ( inputsWithWidgets.length > 0 && inputsWithWidgets[0] < inputsWithoutWidgets[inputsWithoutWidgets.length - 1] ) { // Déplacer tous les inputs avec widgets à la fin for (let i = 0; i < inputsWithWidgets.length; i++) { const currentIndex = inputsWithWidgets[i] - i; // Ajuster l'index car la liste change à chaque suppression const currentWidget = node.inputs[currentIndex]; // Sauvegarder les informations de l'input et son lien let link = null; if (currentWidget.link) { let link = null; if (currentWidget.link) { link = node.graph.links[currentWidget.link]; node.graph.removeLink(currentWidget.link); } } // Sauvegarder les propriétés importantes const inputToMove = currentWidget; const inputName = inputToMove.name; const inputType = inputToMove.type; const inputWidget = inputToMove.widget; const inputPos = inputToMove.pos; node.removeInput(currentIndex); node.addInput(inputName, inputType, { pos: inputPos, widget: inputWidget, }); // Reconnecter le lien si nécessaire if (link && link.origin_id !== null) { const newIndex = node.inputs.findIndex( (input) => input.name === inputName ); node.graph.links[link.id].target_slot = newIndex; node.inputs[newIndex].link = link.id; } /*if (link && link.origin_id !== null) { const newIndex = node.inputs.findIndex(input => input.name === inputName); link.target_slot = newIndex; node.graph.links[link.id] = link; node.inputs[newIndex].link = link.id; }*/ } // Rafraîchir le canvas /* if (node.graph) { node.graph.setDirtyCanvas(true); }*/ } // Rafraîchir le canvas si nécessaire if (node.graph) { node.graph.setDirtyCanvas(false, true); //node.graph.afterChange(); //node.graph.connectionChange(node); } } function organizeOutputs(node, workflow_name) { const outputs = app.lipsync_studio[workflow_name].outputs; // Extraire et trier les outputs selon leur position let sortedOutputs; sortedOutputs = Object.entries(outputs) .sort(([, a], [, b]) => a.position - b.position) .map(([, output]) => { let name = ""; let type = ""; if (output.inputs.length === undefined) { name = output.inputs.Name.value; type = output.inputs.type.value; } else { name = output.inputs[0]; type = output.inputs[1]; } return { name, type }; }); // Réorganiser les outputs selon l'ordre déterminé for (let i = 0; i < sortedOutputs.length; i++) { const targetOutput = sortedOutputs[i]; const actualIndex = node.outputs.findIndex( (output) => output.name === targetOutput.name ); if (actualIndex !== -1 && actualIndex !== i) { // Sauvegarder les liens existants let links = []; if ( node.outputs[actualIndex].links && node.outputs[actualIndex].links.length > 0 ) { for (const linkId of node.outputs[actualIndex].links) { const linkInfo = node.graph.links[linkId]; if (linkInfo) { links.push({ id: linkId, origin_id: linkInfo.origin_id, origin_slot: linkInfo.origin_slot, target_id: linkInfo.target_id, target_slot: linkInfo.target_slot, }); // Déconnecter temporairement node.graph.removeLink(linkId); } } } // Sauvegarder les propriétés importantes const outputToMove = node.outputs[actualIndex]; const outputName = outputToMove.name; const outputType = outputToMove.type; // Supprimer l'output node.removeOutput(actualIndex); // Ajouter à la position attendue node.addOutput(outputName, outputType, i); const newOutput = node.outputs.findIndex( (output) => output.name === outputName ); // Reconnecter les liens sauvegardés for (const link of links) { const targetNode = node.graph.getNodeById(link.target_id); node.connect(newOutput, targetNode, link.target_slot); } } } // Rafraîchir le canvas if (node.graph) { node.graph.setDirtyCanvas(true); } } export function removeInputs(node, inputs, widgets_values) { // Ensemble des noms d'entrées valides à partir de inputs const validInputNames = new Set(); for (let [key, value] of Object.entries(inputs)) { if (value.inputs && value.inputs[0]) { validInputNames.add(value.inputs[0]); } } // Noms à préserver quoi qu'il arrive const preserveNames = new Set(["workflows", "workflow"]); // Collecter les noms d'entrées actuelles const currentInputNames = new Set(); for (let input of node.inputs) if (!preserveNames.has(input.name)) currentInputNames.add(input.name); // Collecter les noms de widgets actuels (à partir de l'index 2) const currentWidgetNames = new Set(); for (let i = 2; i < node.widgets.length; i++) if (!preserveNames.has(node.widgets[i].name)) currentWidgetNames.add(node.widgets[i].name); // Identifier les éléments à supprimer (ceux qui sont dans current mais pas dans valid) const inputsToRemove = [...currentInputNames].filter( (name) => !validInputNames.has(name) ); const widgetsToRemove = [...currentWidgetNames].filter( (name) => !validInputNames.has(name) ); // Supprimer les entrées identifiées (en parcourant en sens inverse) if (inputsToRemove.length > 0) for (let i = node.inputs.length - 1; i >= 0; i--) if (inputsToRemove.includes(node.inputs[i].name)) { // Déconnecter le lien s'il existe if (node.inputs[i].link != null) node.graph.removeLink(node.inputs[i].link); node.removeInput(i); } // Supprimer les widgets identifiés (en parcourant en sens inverse) if (widgetsToRemove.length > 0) for (let i = node.widgets.length - 1; i >= 2; i--) if (widgetsToRemove.includes(node.widgets[i].name)) { widgetName = node.widgets[i].name; node.widgets.splice(i, 1); widgets_values.splice(i, 1); if ( node.local_input_defs && node.local_input_defs.required[widgetName] ) { delete node.local_input_defs.required[widgetName]; } // change y and last_y of widgets for (let j = i; j < node.widgets.length; j++) { node.widgets[j].y -= node.widgets[j].computedHeight; node.widgets[j].last_y -= node.widgets[j].computedHeight; } } }