Ported the ShowText.js bug fix from ComfyUI-Data-Analysis

This commit is contained in:
HowToSD
2025-06-10 21:45:28 -07:00
parent 2168836d13
commit 09ceb115b8
2 changed files with 90 additions and 26 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
[project]
name = "pt-wrapper"
description = "PyTorch extension for ComfyUI featuring extensive PyTorch wrapper nodes for seamless tensor operations and PyTorch model training."
version = "4.1.0"
version = "4.2.0"
license = {file = "LICENSE"}
[project.urls]
+89 -25
View File
@@ -8,59 +8,123 @@ See credit/credit.md for the full license.
import { app } from "../../../scripts/app.js";
import { ComfyWidgets } from "../../../scripts/widgets.js";
// Displays input text on a node
app.registerExtension({
name: "HowToSD.PtWrapperShowTextGeneric",
async beforeRegisterNodeDef(nodeType, nodeData, app) {
if (["PtShowText", "PtShowSize"].includes(nodeData.name)) { // This needs to match NODE_CLASS_MAPPINGS in Python
// Each node needs to match NODE_CLASS_MAPPINGS in Python
const targetNodes = ["PtShowText", "PtShowSize"]
const noConvertedFieldNodes = ["PtShowText", "PtShowSize"]
if (targetNodes.includes(nodeData.name)) {
function populate(text) {
// 'this' is not bound automatically — it's explicitly set via .call() in onExecuted.
if (this.widgets) {
// Clear all widgets on the node
for (let i = 0; i < this.widgets.length; i++) {
// if onRemove method exists, call it.
// On older frontend versions there is a hidden converted-widget
// Legacy version: const isConvertedWidget = +!!this.inputs?.[0].widget;
const hasInputWidget = !!(this.inputs?.[0]?.widget); // Coerce to boolean: true if widget exists
let isConvertedWidget = hasInputWidget ? 1 : 0; // Convert boolean to integer (1 or 0)
if (noConvertedFieldNodes.includes(nodeData.name)){ // Remove all widgets
isConvertedWidget = 0;
}
// Remove all dynamically added widgets, preserving the auto-converted input widget if present
for (let i = isConvertedWidget; i < this.widgets.length; i++) {
this.widgets[i].onRemove?.();
}
this.widgets.length = 0;
this.widgets.length = isConvertedWidget;
}
const v = [...text]; // Copy the text list (e.g. text.copy())
if (!v[0]) {
const v = [...text]; // Shallow copy of text array into v
// If the first element is empty or undefined, remove it from the array
if (!v[0]) {
v.shift();
}
for (const list of v) {
const w = ComfyWidgets["STRING"](this, "text0", ["STRING", { multiline: true }], app).widget;
w.inputEl.readOnly = true;
w.inputEl.style.opacity = 0.6;
w.value = list;
for (let row of v) { // Iterate over v. v can be 1d or 2d.
if (!(row instanceof Array)) row = [row];
for (const elem of row) {
// Create a new STRING widget
// Use field name like "text_0", "text_1", accounting for existing widgets
const fieldName = "text_" + (this.widgets?.length ?? 0);
const w = ComfyWidgets["STRING"](
this,
fieldName,
["STRING", { multiline: true }],
app).widget;
w.inputEl.readOnly = true;
w.inputEl.style.opacity = 0.6;
w.value = elem;
}
}
/*
Defer until the next animation frame so all widgets are rendered and layout is stable.
Then compute the node size needed to fit new content.
If computeSize underestimates it, use the current node size as the floor to preserve layout.
*/
requestAnimationFrame(() => {
const sz = this.computeSize();
if (sz[0] < this.size[0]) {
sz[0] = this.size[0];
}
if (sz[1] < this.size[1]) {
sz[1] = this.size[1];
}
this.onResize?.(sz);
app.graph.setDirtyCanvas(true, false);
const sz = this.computeSize(); // Compute size based on current widgets
// Adjust to ensure the node fits its current visual width/height
if (sz[0] < this.size[0]) sz[0] = this.size[0]; // Fit existing node width
if (sz[1] < this.size[1]) sz[1] = this.size[1]; // Fit existing node height
this.onResize?.(sz); // Apply the size adjustment
app.graph.setDirtyCanvas(true, false); // Trigger a redraw of the canvas
});
}
// **onExecute** setup
// When the node is executed we will be sent the input text, display this in the widget
const onExecuted = nodeType.prototype.onExecuted;
nodeType.prototype.onExecuted = function (message) {
// Call the original onExecuted handler (if one exists), forwarding all arguments
onExecuted?.apply(this, arguments);
// Call our populate function, binding 'this' to the current node
populate.call(this, message.text);
};
// **configure** setup
const VALUES = Symbol(); // Unique internal key to store widget values
const configure = nodeType.prototype.configure;
nodeType.prototype.configure = function () {
// Store unmodified widget values as they get removed on configure by new frontend
// This value is applied in onConfigure
this[VALUES] = arguments[0]?.widgets_values;
// Call the original configure handler, if it exists
return configure?.apply(this, arguments);
};
// **onConfigure** setup
// Populate node's widgets using the saved values in Configure
const onConfigure = nodeType.prototype.onConfigure;
nodeType.prototype.onConfigure = function () {
onConfigure?.apply(this, arguments);
if (this.widgets_values?.length) {
populate.call(this, this.widgets_values.slice(+this.widgets_values.length > 1));
// Read saved widget values
const widgets_values = this[VALUES];
if (widgets_values?.length) {
// In newer frontend there seems to be a delay in creating the initial widget
requestAnimationFrame(() => {
// Legacy code
// populate.call(this,
// widgets_values.slice(+(widgets_values.length > 1 && this.inputs?.[0].widget)));
const hasAutoWidget = widgets_values.length > 1 && this.inputs?.[0]?.widget;
let startIndex = hasAutoWidget ? 1 : 0;
if (noConvertedFieldNodes.includes(nodeData.name)){ // Remove all widgets
startIndex = 0;
}
populate.call(this, widgets_values.slice(startIndex));
});
}
};
}
},
});
});