Skip to content

Loading Workflow Data, forceInput and Multiline Text #9017

Description

@AlekPet

Custom Node Testing

Expected Behavior

Descriptions:
The problem is that when writing a custom extension that adds another widget to the node (for example, my Speech And Recognition extension: https://github.com/AlekPet/ComfyUI_Custom_Nodes_AlekPet/blob/master/ExtrasNode/js/extras_speech_and_recognition.js), which has a multi-text field and forceInput, it incorrectly loads data from the workflow.

Maybe I somehow incorrectly add the widget to the node, but with two multi-text fields, everything works fine 😄 .

I made a more simplified version of the verification extension, which uses one checkbox and stores its value.

force_input_mtext.mp4

Example python code nodes.

class TestTextNode:

    @ classmethod
    def INPUT_TYPES(cls):
        return {
            "required": {
                "text": ("STRING", {"default": "", "forceInput":True}),
                "words": ("STRING", {"multiline": True, "default": ""}),
                },
        }

    RETURN_TYPES = ("STRING",)
    RETURN_NAMES = ("STRING",)
    CATEGORY = "alekpet-test"
    DESCRIPTION = "This node outputs text."
    FUNCTION = "ret_text"

    def ret_text(self, text="", words=""):      
        result_text = text * 5
        return (result_text,) 

class TestTextTwoNode:

    @ classmethod
    def INPUT_TYPES(cls):
        return {
            "required": {
                "text": ("STRING", {"default": "", "forceInput":True}),
                "words": ("STRING", {"multiline": True, "default": ""}),
                "words2": ("STRING", {"multiline": True, "default": ""}),
                },
        }

    RETURN_TYPES = ("STRING",)
    RETURN_NAMES = ("STRING",)
    CATEGORY = "alekpet-test"
    DESCRIPTION = "This node outputs text."
    FUNCTION = "ret_text_two"

    def ret_text_two(self, text="", words="", words2=""):      
        result_text = text * 5
        return (result_text,) 

Example js code extension.

import { app } from "../../scripts/app.js";
import { $el } from "../../scripts/ui.js";

const idExt = "alekpet.hello_world";

// -- Extension: Speak text & Recognition speech --
app.registerExtension({
  name: idExt,

  async beforeRegisterNodeDef(nodeType, nodeData, app) {
    // Node Created
    const onNodeCreated = nodeType.prototype.onNodeCreated;
    nodeType.prototype.onNodeCreated = async function () {
      const ret = onNodeCreated
        ? onNodeCreated.apply(this, arguments)
        : undefined;

      let nodeIsMultiString = false;

      if (nodeData?.input && nodeData?.input?.required) {
        for (const inp of Object.keys(nodeData.input.required)) {
          if (
            nodeData.input.required[inp][1]?.multiline &&
            !nodeData.input.required[inp][1]?.forceInput
          ) {
            const type = nodeData.input.required[inp][0];

            if (["STRING"].includes(type)) {
              nodeIsMultiString = true;
              break;
            }
          }
        }
      }
	  

      if (nodeData?.output) {
        for (const out of nodeData.output) {
          const isElementTextArea = this?.widgets?.some(
            (w) =>
              w?.element?.tagName === "TEXTAREA" ||
              w?.inputEl?.tagName === "TEXTAREA"
          );

          if (isElementTextArea && ["STRING"].includes(out)) {
            nodeIsMultiString = true;
            break;
          }
        }
      }

      if (!nodeIsMultiString) return ret;

      // Find all widget type customtext
      const widgetsTextMulti = this?.widgets?.filter((w) =>
        ["customtext", "converted-widget"].includes(w.type)
      );

      const isIncludesSpeech = this?.widgets?.some(
        (w) => w.type === "hello_world_type"
      );

      if (!isIncludesSpeech && widgetsTextMulti?.length) {
        widgetsTextMulti.forEach(async (w) => {
          const el = $el("input.widget_hello_world_el", { type: "checkbox" });
          this.addDOMWidget("widget_hello_world", "hello_world_type", el, {
            setValue(v) {
              el.checked = v;
            },
            getValue() {
              return !!el.checked;
            },
          });
        });
      }

      return ret;
    };
  },
});

Video console.log:
https://github.com/user-attachments/assets/9866e660-82cb-4a87-9f45-4cf1d23f247c

The entire extension and test users can be found here: alek_test.zip

And the workflow for testing: load_data_workflow_forcedinput_one_mtext.json

Actual Behavior

It should load and save normally as with one multi-text field. As with input data that does not have a property in the forceInput field.

Steps to Reproduce

  1. Enable or install an extension that adds a new widget to a multi-text field
  2. Add a widget with forceInput and one multi-text field to the workflow
  3. Change the values in the fields
  4. Switch to another workflow or refresh the page.
  5. See the result of loading values.****

Debug Logs

In the onSerialize method if you track all the values are saved correctly.

this.onSerialize = (o)=>{
console.log(o.type, o.widgets_values)
}

Video console.log below in the Expected Behavior

Other

Perhaps it is necessary to somehow process the data when loading, but why does everything work with two or more multi-text fields, but with one the loading of values breaks, reducing the data in the widgets_values array.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Potential BugUser is reporting a bug. This should be tested.bug-cop:non-blockingThe issue should be ignored by a bug cop, but doesn't necessarily need to be closed.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions