diff --git a/__init__.py b/__init__.py index d0685a6..da92634 100644 --- a/__init__.py +++ b/__init__.py @@ -72,6 +72,7 @@ try: XB_ROCmSamplerCustom, XB_ROCmSamplerCustomAdvanced, XB_ROCmVAEDecode, XB_ROCmVAEEncode, XB_ROCmVAEDecodeTemporal, XB_ROCmMemCleaner) + from .nodes_string_merge import XB_StringMerge NODE_CLASS_MAPPINGS = { "XB_VRAM_Calculator": XB_VRAM_Calculator, @@ -139,7 +140,8 @@ try: "XB_AudioSlicer": XB_AudioSlicer, "XB_AudioSlicerV1": XB_AudioSlicerV1, "XB_AudioSlicerV2": XB_AudioSlicerV2, - "XB_AudioSlicerV3": XB_AudioSlicerV3 + "XB_AudioSlicerV3": XB_AudioSlicerV3, + "XB_StringMerge": XB_StringMerge } NODE_DISPLAY_NAME_MAPPINGS = { @@ -196,6 +198,7 @@ try: "XB_WanInfiniteTalkToVideo": "XB-BOX - 🎵 语音转视频分块", "XB_WanInfiniteTalkToVideo_Single": "XB-BOX - 🎵 语音转视频分块(单人)", "XB_WanInfiniteTalkToVideo_Dual": "XB-BOX - 🎵 语音转视频分块(双人)", + "XB_StringMerge": "XB-BOX - 📝 字符串合并", } print_success("\n" + "="*50) diff --git a/js/xb_string_merge.js b/js/xb_string_merge.js new file mode 100644 index 0000000..f4ceb9c --- /dev/null +++ b/js/xb_string_merge.js @@ -0,0 +1,43 @@ +import { app } from "../../scripts/app.js"; + +// ─── 动态输入槽管理(源自 ComfyUI-KJNodes) ─── +// 添加 "Update inputs" 按钮,根据 inputcount 重建 string_1..string_N 插槽 +function setupDynamicInputs(node, { type, prefix, countWidget = "inputcount", slotOptions } = {}) { + const rebuild = () => { + if (!node.inputs) node.inputs = []; + const countW = node.widgets?.find(w => w.name === countWidget); + if (!countW) return; + const target = countW.value; + const current = node.inputs.filter(i => i.name?.startsWith(prefix)).length; + if (target === current) return; + if (target < current) { + for (let i = 0; i < current - target; i++) node.removeInput(node.inputs.length - 1); + } else { + for (let i = current + 1; i <= target; i++) node.addInput(`${prefix}${i}`, type, slotOptions); + } + }; + node.addWidget("button", "Update inputs", null, rebuild); + const countW = node.widgets?.find(w => w.name === countWidget); + if (countW) { + const origCb = countW.callback; + countW.callback = function (value, canvas) { + const r = origCb ? origCb.apply(this, arguments) : undefined; + if (!canvas) rebuild(); // bare = API reload; skip interactive scrub + return r; + }; + } + return rebuild; +} + +app.registerExtension({ + name: "XB_ToolBox.StringMerge", + async beforeRegisterNodeDef(nodeType, nodeData) { + if (nodeData.name !== "XB_StringMerge") return; + + const origOnNodeCreated = nodeType.prototype.onNodeCreated || function () { }; + nodeType.prototype.onNodeCreated = function () { + origOnNodeCreated.apply(this, arguments); + setupDynamicInputs(this, { type: "STRING", prefix: "string_", slotOptions: { shape: 7 } }); + }; + }, +}); diff --git a/locales/zh/main.json b/locales/zh/main.json index a4e1eca..79401d3 100644 --- a/locales/zh/main.json +++ b/locales/zh/main.json @@ -12,7 +12,10 @@ "Batch_Process": "批量处理", "ROCm": "ROCm加速", "Wan": "Wan视频", - "Audio": "音频工具" + "Audio": "音频工具", + "Display": "显示标签", + "Segment": "图像分割", + "Text": "文本工具" }, "Mode A (128x128x32)": "模式A (128x128x32)", "Mode B (128x64x96)": "模式 B (128x64x96)", diff --git a/locales/zh/nodeDefs.json b/locales/zh/nodeDefs.json index 2865caa..0ab0332 100644 --- a/locales/zh/nodeDefs.json +++ b/locales/zh/nodeDefs.json @@ -1152,5 +1152,18 @@ "outputs": { "0": { "name": "图像" } } + }, + "XB_StringMerge": { + "display_name": "XB-BOX - 📝 字符串合并", + "inputs": { + "inputcount": { "name": "输入数量" }, + "string_1": { "name": "字符串_1" }, + "delimiter": { "name": "分隔符" }, + "return_list": { "name": "输出为列表" }, + "newline_per_segment": { "name": "每段换行" } + }, + "outputs": { + "0": { "name": "字符串" } + } } } \ No newline at end of file diff --git a/nodes_string_merge.py b/nodes_string_merge.py new file mode 100644 index 0000000..63b2056 --- /dev/null +++ b/nodes_string_merge.py @@ -0,0 +1,61 @@ +""" +XB-BOX 字符串合并节点 +==================== +基于 ComfyUI-KJNodes 的 JoinStringMulti 节点进行重构, +增加「每段换行」选项。 +""" + + +# ============================================================ +# XB_StringMerge — 字符串合并(多重) +# ============================================================ +class XB_StringMerge: + """合并多个输入字符串为单个字符串,支持自定义分隔符和每段换行""" + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "inputcount": ("INT", {"default": 2, "min": 2, "max": 1000, "step": 1}), + "string_1": ("STRING", {"default": "", "forceInput": True}), + "delimiter": ("STRING", {"default": " ", "multiline": False}), + "return_list": ("BOOLEAN", {"default": False}), + "newline_per_segment": ("BOOLEAN", {"default": False, "tooltip": "每段换行:打开后,每段输入字符串将换行排列"}), + }, + "optional": { + "string_2": ("STRING", {"default": "", "forceInput": True}), + }, + } + + RETURN_TYPES = ("STRING",) + RETURN_NAMES = ("string",) + FUNCTION = "combine" + CATEGORY = "XB_ToolBox/Text" + DESCRIPTION = ( + "合并多个字符串输入。\n" + "可以设置输入数量,点击「Update inputs」来增加更多插槽。\n" + "分隔符:指定连接字符串的分隔符。\n" + "输出为列表:开启后返回字符串列表而非合并后的单一字符串。\n" + "每段换行:开启后忽略分隔符,每段字符串用换行符连接。" + ) + + def combine(self, inputcount, delimiter, return_list, newline_per_segment, **kwargs): + string = kwargs.get("string_1", "") + strings = [string] # Initialize a list with the first string + + # 如果开启了每段换行,使用换行符作为实际分隔符 + actual_delimiter = "\n" if newline_per_segment else delimiter + + for c in range(1, inputcount): + new_string = kwargs.get(f"string_{c + 1}", "") + if not new_string: + continue + if return_list: + strings.append(new_string) # Add new string to the list + else: + string = string + actual_delimiter + new_string + + if return_list: + return (strings,) # Return the list of strings + else: + return (string,) # Return the combined string diff --git a/pyproject.toml b/pyproject.toml index 96f1a4f..9e9edc6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "XB_ToolBox" description = "XB-BOX: ComfyUI custom node toolbox - Wan video generation, ROCm acceleration, pipeline tools, and more" -version = "0.6.18" +version = "0.6.20" license = { file = "LICENSE" } [project.urls]