diff --git a/ComfyUI-FString/__init__.py b/ComfyUI-FString/__init__.py new file mode 100644 index 0000000..7aee1c5 --- /dev/null +++ b/ComfyUI-FString/__init__.py @@ -0,0 +1,13 @@ +from .fstring_node import SimpleFStringNode + +NODE_CLASS_MAPPINGS = { + "SimpleFStringNode": SimpleFStringNode +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "SimpleFStringNode": "Simple F-String" +} + +WEB_DIRECTORY = "./js" + +__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS", "WEB_DIRECTORY"] \ No newline at end of file diff --git a/ComfyUI-FString/fstring_node.py b/ComfyUI-FString/fstring_node.py new file mode 100644 index 0000000..901f611 --- /dev/null +++ b/ComfyUI-FString/fstring_node.py @@ -0,0 +1,42 @@ +# AI Disclosure: Code comments were generated with AI but manually checked for correctness. + +class SimpleFStringNode: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "template": ("STRING", { + "multiline": True, + "default": "The value of x is {x}" + }), + }, + # We leave "optional" empty. + # The JS will force new inputs into the node, and ComfyUI + # will pass them to our function automatically. + "optional": {} + } + + RETURN_TYPES = ("STRING",) + RETURN_NAMES = ("formatted_string",) + FUNCTION = "format_inputs" + CATEGORY = "Learning/Text" + + # We use **kwargs to catch all the dynamic inputs created in the UI + def format_inputs(self, template, **kwargs): + clean_values = {} + for key, val in kwargs.items(): + if val is None: + clean_values[key] = "None" + elif hasattr(val, 'shape'): + clean_values[key] = f"Tensor {val.shape}" + else: + clean_values[key] = str(val) + + try: + result = template.format(**clean_values) + except KeyError as e: + result = f"Error: Template uses {{{e.args[0]}}} but that input doesn't exist!" + except Exception as e: + result = f"Error: {str(e)}" + + return (result,) \ No newline at end of file diff --git a/ComfyUI-FString/js/fstring_dynamic.js b/ComfyUI-FString/js/fstring_dynamic.js new file mode 100644 index 0000000..d83d8b7 --- /dev/null +++ b/ComfyUI-FString/js/fstring_dynamic.js @@ -0,0 +1,58 @@ +// AI Disclosure: Code comments were generated with AI + +import { app } from "../../scripts/app.js"; + +app.registerExtension({ + name: "ComfyUI.SimpleFString", + async beforeRegisterNodeDef(nodeType, nodeData, app) { + if (nodeData.name === "SimpleFStringNode") { + + // This function adds the "Add Input" and "Remove Input" options + // to the right-click context menu of the node. + const getExtraMenuOptions = nodeType.prototype.getExtraMenuOptions; + nodeType.prototype.getExtraMenuOptions = function(_, options) { + + // Run the original menu logic first (if any) + if (getExtraMenuOptions) { + getExtraMenuOptions.apply(this, arguments); + } + + // Add "Add Input" option + options.unshift({ + content: "Add Input (Variable)", + callback: () => { + // Ask the user for the variable name (e.g., "my_image") + const name = prompt("Enter input name (used in f-string as {name}):"); + if (name) { + // Verify duplicates + const exists = this.findInputSlot(name); + if (exists !== -1) { + alert("Input with that name already exists!"); + return; + } + + // 1st arg: name, 2nd arg: type ("*" = wildcard), 3rd arg: shape + this.addInput(name, "*"); + + // Tell ComfyUI the graph changed so it saves the new slot + this.setDirtyCanvas(true, true); + } + } + }); + + // Add "Remove Input" option + options.unshift({ + content: "Remove Last Input", + callback: () => { + // Check if we have optional inputs to remove + // (We don't want to remove the required "template" input) + if (this.inputs && this.inputs.length > 1) { + // Remove the last input in the array + this.removeInput(this.inputs.length - 1); + } + } + }); + }; + } + }, +}); \ No newline at end of file