mirror of
https://github.com/tritant/ComfyUI_SuperScaler.git
synced 2025-12-15 16:17:41 +01:00
232 lines
9.8 KiB
JavaScript
232 lines
9.8 KiB
JavaScript
import { app } from "/scripts/app.js";
|
|
|
|
// --- Définition des groupes ---
|
|
const widgetGroups = {
|
|
"enable_latent_pass": [
|
|
"latent_upscale_by", "latent_denoise", "latent_sampler_name",
|
|
"latent_scheduler", "latent_steps", "latent_cfg"
|
|
],
|
|
"enable_tiled_pass_2": [
|
|
"tiled_upscale_by_2", "tiled_denoise_2", "tile_size_2", "tile_overlap_2",
|
|
"tiled_sampler_name_2", "tiled_scheduler_2", "tiled_steps_2", "tiled_cfg_2"
|
|
],
|
|
"enable_tiled_pass_3": [
|
|
"tiled_upscale_by_3", "tiled_denoise_3", "tile_size_3", "tile_overlap_3",
|
|
"tiled_sampler_name_3", "tiled_scheduler_3", "tiled_steps_3", "tiled_cfg_3"
|
|
],
|
|
"enable_sharpen": [
|
|
"sharpen_amount", "sharpen_radius"
|
|
],
|
|
"enable_grain": [
|
|
"grain_intensity", "grain_type", "grain_size", "saturation_mix", "adaptive_grain"
|
|
]
|
|
};
|
|
|
|
// Liste de TOUS les widgets gérés (parents et enfants)
|
|
const allManagedWidgetNames = new Set();
|
|
for (const parentName in widgetGroups) {
|
|
allManagedWidgetNames.add(parentName);
|
|
for (const childName of widgetGroups[parentName]) {
|
|
allManagedWidgetNames.add(childName);
|
|
}
|
|
}
|
|
|
|
function getCleanTitle(toggleName) {
|
|
let title = toggleName.replace("enable_", "").replace(/_/g, " ");
|
|
return title.replace(/\b\w/g, l => l.toUpperCase());
|
|
}
|
|
|
|
/**
|
|
* Construit l'interface utilisateur (les widgets) en lisant l'état depuis node.properties.
|
|
* @param {LGraphNode} node - Le nœud ComfyUI
|
|
*/
|
|
function rebuildWidgets(node) {
|
|
// 1. On efface tous les widgets actuels
|
|
node.widgets.length = 0;
|
|
|
|
// 2. On reconstruit la liste en lisant nos propriétés
|
|
for (const toggleName of Object.keys(widgetGroups)) {
|
|
const parentWidget = node.allPythonWidgets.find(w => w.name === toggleName);
|
|
if (!parentWidget) continue;
|
|
|
|
// Applique la valeur depuis notre "source de vérité"
|
|
// (Vérifie si la propriété existe, sinon utilise la valeur par défaut du widget)
|
|
parentWidget.value = node.properties.hasOwnProperty(toggleName)
|
|
? node.properties[toggleName]
|
|
: parentWidget.defaultValue;
|
|
|
|
// --- AJOUT DU SÉPARATEUR ---
|
|
node.widgets.push({
|
|
name: "separator_spacer_" + parentWidget.name,
|
|
type: "CUSTOM_SPACER",
|
|
draw: (ctx, node, width, y) => {
|
|
const title = getCleanTitle(parentWidget.name);
|
|
const rectHeight = 20, marginY = 5, x_padding = 10;
|
|
ctx.fillStyle = "#272";
|
|
ctx.fillRect(x_padding, y + marginY, width - (x_padding * 2), rectHeight);
|
|
ctx.fillStyle = "#CCC";
|
|
ctx.font = "bold 12px Arial";
|
|
ctx.textAlign = "center";
|
|
const textY = y + marginY + rectHeight / 2 + 4;
|
|
ctx.fillText(title, width / 2, textY);
|
|
},
|
|
computeSize: () => [0, 30]
|
|
});
|
|
|
|
// A. On ajoute le "parent" (toggle)
|
|
node.widgets.push(parentWidget);
|
|
|
|
// B. On vérifie (en lisant nos propriétés) s'il faut ajouter ses enfants
|
|
const showChildren = parentWidget.value;
|
|
|
|
if (showChildren) {
|
|
const childrenNames = widgetGroups[toggleName];
|
|
for (const childName of childrenNames) {
|
|
const childWidget = node.allPythonWidgets.find(w => w.name === childName);
|
|
if (childWidget) {
|
|
// Applique la valeur depuis notre "source de vérité"
|
|
childWidget.value = node.properties.hasOwnProperty(childName)
|
|
? node.properties[childName]
|
|
: childWidget.defaultValue;
|
|
node.widgets.push(childWidget);
|
|
} else {
|
|
console.warn(`[SuperScaler] Enfant widget '${childName}' NON TROUVÉ.`);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// --- AJOUT : AFFICHER LES WIDGETS NON GÉRÉS (SEED, BLEND, ETC.) ---
|
|
|
|
// 1. Trouver tous les widgets non gérés
|
|
const unmanagedWidgets = [];
|
|
for (const widget of node.allPythonWidgets) {
|
|
if (!allManagedWidgetNames.has(widget.name)) {
|
|
unmanagedWidgets.push(widget);
|
|
}
|
|
}
|
|
|
|
// 2. S'il y en a, ajouter un titre et les widgets
|
|
if (unmanagedWidgets.length > 0) {
|
|
// Ajouter un spacer "Final Settings"
|
|
node.widgets.push({
|
|
name: "separator_spacer_final_settings",
|
|
type: "CUSTOM_SPACER",
|
|
draw: (ctx, node, width, y) => {
|
|
const title = "Final Settings"; // Nouveau titre
|
|
const rectHeight = 20, marginY = 5, x_padding = 10;
|
|
ctx.fillStyle = "#272";
|
|
ctx.fillRect(x_padding, y + marginY, width - (x_padding * 2), rectHeight);
|
|
ctx.fillStyle = "#CCC";
|
|
ctx.font = "bold 12px Arial";
|
|
ctx.textAlign = "center";
|
|
const textY = y + marginY + rectHeight / 2 + 4;
|
|
ctx.fillText(title, width / 2, textY);
|
|
},
|
|
computeSize: () => [0, 30]
|
|
});
|
|
|
|
// 3. Ajouter tous les widgets non gérés (seed, compagnon, et mask_blend_weight)
|
|
for (const widget of unmanagedWidgets) {
|
|
// Applique la valeur depuis notre "source de vérité"
|
|
if (node.properties.hasOwnProperty(widget.name)) {
|
|
widget.value = node.properties[widget.name];
|
|
}
|
|
node.widgets.push(widget);
|
|
}
|
|
}
|
|
// --- FIN DE L'AJOUT ---
|
|
|
|
// 3. Force le redessinage
|
|
const newComputedSize = node.computeSize();
|
|
node.size[1] = newComputedSize[1];
|
|
|
|
if (app.graph) {
|
|
app.graph.setDirtyCanvas(true, true);
|
|
}
|
|
}
|
|
|
|
|
|
app.registerExtension({
|
|
name: "SuperScaler.DynamicWidgets.vSafe",
|
|
|
|
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
|
|
|
if (nodeData.name === "SuperScaler_Pipeline") {
|
|
|
|
// --- C'est ici que la logique de ton exemple 'Orchestrator' est appliquée ---
|
|
|
|
const onNodeCreated = nodeType.prototype.onNodeCreated;
|
|
nodeType.prototype.onNodeCreated = function () {
|
|
onNodeCreated?.apply(this, arguments);
|
|
|
|
const node = this;
|
|
|
|
// 1. Initialise la "source de vérité" des propriétés (comme Orchestrator)
|
|
if (!node.properties) {
|
|
node.properties = {};
|
|
}
|
|
|
|
// 2. Sauvegarde les widgets "templates" (avec leurs valeurs par défaut)
|
|
node.allPythonWidgets = [...node.widgets];
|
|
|
|
// 3. Remplit la "source de vérité" avec les valeurs par défaut (par NOM)
|
|
// UNIQUEMENT si elles n'existent pas déjà (chargées par le workflow)
|
|
for (const widget of node.allPythonWidgets) {
|
|
if (widget.name && !node.properties.hasOwnProperty(widget.name)) {
|
|
node.properties[widget.name] = widget.value;
|
|
}
|
|
|
|
// Stocke la valeur par défaut pour la réinitialisation
|
|
widget.defaultValue = widget.value;
|
|
}
|
|
|
|
// 4. Attache les callbacks (une seule fois)
|
|
// Le callback met à jour 'properties' et reconstruit l'UI
|
|
if (!node.callbacksAttached) {
|
|
for (const widget of node.allPythonWidgets) {
|
|
if (widget.callback) {
|
|
const originalCallback = widget.callback;
|
|
widget.callback = (value, ...args) => {
|
|
// Exécute le callback original (s'il existe)
|
|
originalCallback?.call(widget, value, ...args);
|
|
|
|
// Met à jour la "source de vérité"
|
|
node.properties[widget.name] = value;
|
|
|
|
// Reconstruit l'interface
|
|
rebuildWidgets(node);
|
|
};
|
|
}
|
|
}
|
|
node.callbacksAttached = true; // On pose le drapeau
|
|
}
|
|
|
|
// Lance la construction initiale de l'UI
|
|
// (utilise un court délai, comme ton 'Orchestrator', pour s'assurer que tout est chargé)
|
|
setTimeout(() => rebuildWidgets(node), 10);
|
|
};
|
|
|
|
// Garde une trace de la fonction 'onConfigure' originale
|
|
// Nous l'utilisons SEULEMENT pour fusionner les valeurs chargées
|
|
const originalOnConfigure = nodeType.prototype.onConfigure;
|
|
|
|
nodeType.prototype.onConfigure = function(values) {
|
|
originalOnConfigure?.apply(this, arguments);
|
|
|
|
// Quand ComfyUI charge un workflow, il met les valeurs dans this.properties
|
|
// (remplaçant 'values' qui est déprécié)
|
|
// Nous devons juste nous assurer que notre 'rebuild' est appelé après.
|
|
if (this.properties) {
|
|
// 'values' (l'argument) est souvent l'ancien 'properties'
|
|
Object.assign(this.properties, values);
|
|
}
|
|
|
|
// Force une reconstruction de l'UI au cas où
|
|
if (this.allPythonWidgets) {
|
|
rebuildWidgets(this);
|
|
}
|
|
};
|
|
}
|
|
}
|
|
}); |