test(nodes): require static tooltips

This commit is contained in:
silveroxides
2026-08-13 07:27:41 +02:00
parent c84ff102cb
commit e2c11f2290
2 changed files with 126 additions and 64 deletions
+84 -64
View File
@@ -936,34 +936,52 @@ class LoRAMultiMerge(io.ComfyNode):
tooltip="Number of LoRAs to merge"),
# LoRA 1
io.Combo.Input("lora_1", options=folder_paths.get_filename_list("loras"), tooltip="First LoRA"),
io.Float.Input("weight_1", default=1.0, min=-10.0, max=10.0, step=0.01),
io.Float.Input("weight_1", default=1.0, min=-10.0, max=10.0, step=0.01,
tooltip="Scale LoRA 1's represented delta before merging; negative values subtract it."),
# LoRA 2
io.Combo.Input("lora_2", options=["None"] + folder_paths.get_filename_list("loras"), default="None"),
io.Float.Input("weight_2", default=1.0, min=-10.0, max=10.0, step=0.01),
io.Combo.Input("lora_2", options=["None"] + folder_paths.get_filename_list("loras"), default="None",
tooltip="Optional LoRA 2; used only when included by LoRA Count."),
io.Float.Input("weight_2", default=1.0, min=-10.0, max=10.0, step=0.01,
tooltip="Scale LoRA 2's represented delta before merging."),
# LoRA 3
io.Combo.Input("lora_3", options=["None"] + folder_paths.get_filename_list("loras"), default="None"),
io.Float.Input("weight_3", default=1.0, min=-10.0, max=10.0, step=0.01),
io.Combo.Input("lora_3", options=["None"] + folder_paths.get_filename_list("loras"), default="None",
tooltip="Optional LoRA 3; used only when included by LoRA Count."),
io.Float.Input("weight_3", default=1.0, min=-10.0, max=10.0, step=0.01,
tooltip="Scale LoRA 3's represented delta before merging."),
# LoRA 4
io.Combo.Input("lora_4", options=["None"] + folder_paths.get_filename_list("loras"), default="None"),
io.Float.Input("weight_4", default=1.0, min=-10.0, max=10.0, step=0.01),
io.Combo.Input("lora_4", options=["None"] + folder_paths.get_filename_list("loras"), default="None",
tooltip="Optional LoRA 4; used only when included by LoRA Count."),
io.Float.Input("weight_4", default=1.0, min=-10.0, max=10.0, step=0.01,
tooltip="Scale LoRA 4's represented delta before merging."),
# LoRA 5
io.Combo.Input("lora_5", options=["None"] + folder_paths.get_filename_list("loras"), default="None"),
io.Float.Input("weight_5", default=1.0, min=-10.0, max=10.0, step=0.01),
io.Combo.Input("lora_5", options=["None"] + folder_paths.get_filename_list("loras"), default="None",
tooltip="Optional LoRA 5; used only when included by LoRA Count."),
io.Float.Input("weight_5", default=1.0, min=-10.0, max=10.0, step=0.01,
tooltip="Scale LoRA 5's represented delta before merging."),
# LoRA 6
io.Combo.Input("lora_6", options=["None"] + folder_paths.get_filename_list("loras"), default="None"),
io.Float.Input("weight_6", default=1.0, min=-10.0, max=10.0, step=0.01),
io.Combo.Input("lora_6", options=["None"] + folder_paths.get_filename_list("loras"), default="None",
tooltip="Optional LoRA 6; used only when included by LoRA Count."),
io.Float.Input("weight_6", default=1.0, min=-10.0, max=10.0, step=0.01,
tooltip="Scale LoRA 6's represented delta before merging."),
# LoRA 7
io.Combo.Input("lora_7", options=["None"] + folder_paths.get_filename_list("loras"), default="None"),
io.Float.Input("weight_7", default=1.0, min=-10.0, max=10.0, step=0.01),
io.Combo.Input("lora_7", options=["None"] + folder_paths.get_filename_list("loras"), default="None",
tooltip="Optional LoRA 7; used only when included by LoRA Count."),
io.Float.Input("weight_7", default=1.0, min=-10.0, max=10.0, step=0.01,
tooltip="Scale LoRA 7's represented delta before merging."),
# LoRA 8
io.Combo.Input("lora_8", options=["None"] + folder_paths.get_filename_list("loras"), default="None"),
io.Float.Input("weight_8", default=1.0, min=-10.0, max=10.0, step=0.01),
io.Combo.Input("lora_8", options=["None"] + folder_paths.get_filename_list("loras"), default="None",
tooltip="Optional LoRA 8; used only when included by LoRA Count."),
io.Float.Input("weight_8", default=1.0, min=-10.0, max=10.0, step=0.01,
tooltip="Scale LoRA 8's represented delta before merging."),
io.Combo.Input("merge_mode", options=["concatenate", "weighted_sum"], default="concatenate",
tooltip="concatenate: safe, increases rank. weighted_sum: fixed rank (to max input rank), mathematically lossy but standard in Kohya."),
io.String.Input("output_filename", default="merged_lora"),
io.Combo.Input("save_dtype", options=["fp16", "bf16", "fp32"], default="fp16"),
io.Combo.Input("device", options=["cuda", "cpu"], default="cuda"),
io.String.Input("output_filename", default="merged_lora",
tooltip="Output filename without extension, written under ComfyUI's LoRA directory."),
io.Combo.Input("save_dtype", options=["fp16", "bf16", "fp32"], default="fp16",
tooltip="Requested dtype for generated factors; each logical layer's source factor precision is considered independently of scalar alpha."),
io.Combo.Input("device", options=["cuda", "cpu"], default="cuda",
tooltip="Device for per-target arithmetic; a CUDA OOM retries only the affected target on CPU."),
io.Boolean.Input("include_1d_diffs", default=False,
tooltip="Include and merge 1D direct-diff tensors as FP32."),
],
@@ -1025,32 +1043,34 @@ class LoRAMultiMergeDARE(io.ComfyNode):
inputs=[
io.Combo.Input("base_model", options=["None"] + folder_paths.get_filename_list("diffusion_models"), default="None",
tooltip="Optional reference model to resolve key naming issues across formats. If None, input keys are preserved verbatim."),
io.Combo.Input("lora_count", options=[str(i) for i in range(1, 9)], default="2"),
io.Combo.Input("lora_count", options=[str(i) for i in range(1, 9)], default="2",
tooltip="Use the first N LoRA selectors; selectors beyond this count are ignored."),
# LoRA 1
io.Combo.Input("lora_1", options=folder_paths.get_filename_list("loras"), tooltip="First LoRA"),
io.Float.Input("weight_1", default=1.0, min=-10.0, max=10.0, step=0.01),
io.Float.Input("weight_1", default=1.0, min=-10.0, max=10.0, step=0.01,
tooltip="Scale LoRA 1's represented delta before DARE-Ties merging; negative values subtract it."),
# ...
io.Combo.Input("lora_2", options=["None"] + folder_paths.get_filename_list("loras"), default="None"),
io.Float.Input("weight_2", default=1.0, min=-10.0, max=10.0, step=0.01),
io.Combo.Input("lora_3", options=["None"] + folder_paths.get_filename_list("loras"), default="None"),
io.Float.Input("weight_3", default=1.0, min=-10.0, max=10.0, step=0.01),
io.Combo.Input("lora_4", options=["None"] + folder_paths.get_filename_list("loras"), default="None"),
io.Float.Input("weight_4", default=1.0, min=-10.0, max=10.0, step=0.01),
io.Combo.Input("lora_5", options=["None"] + folder_paths.get_filename_list("loras"), default="None"),
io.Float.Input("weight_5", default=1.0, min=-10.0, max=10.0, step=0.01),
io.Combo.Input("lora_6", options=["None"] + folder_paths.get_filename_list("loras"), default="None"),
io.Float.Input("weight_6", default=1.0, min=-10.0, max=10.0, step=0.01),
io.Combo.Input("lora_7", options=["None"] + folder_paths.get_filename_list("loras"), default="None"),
io.Float.Input("weight_7", default=1.0, min=-10.0, max=10.0, step=0.01),
io.Combo.Input("lora_8", options=["None"] + folder_paths.get_filename_list("loras"), default="None"),
io.Float.Input("weight_8", default=1.0, min=-10.0, max=10.0, step=0.01),
io.Combo.Input("lora_2", options=["None"] + folder_paths.get_filename_list("loras"), default="None", tooltip="Optional LoRA 2; used only when included by LoRA Count."),
io.Float.Input("weight_2", default=1.0, min=-10.0, max=10.0, step=0.01, tooltip="Scale LoRA 2's represented delta before DARE-Ties merging."),
io.Combo.Input("lora_3", options=["None"] + folder_paths.get_filename_list("loras"), default="None", tooltip="Optional LoRA 3; used only when included by LoRA Count."),
io.Float.Input("weight_3", default=1.0, min=-10.0, max=10.0, step=0.01, tooltip="Scale LoRA 3's represented delta before DARE-Ties merging."),
io.Combo.Input("lora_4", options=["None"] + folder_paths.get_filename_list("loras"), default="None", tooltip="Optional LoRA 4; used only when included by LoRA Count."),
io.Float.Input("weight_4", default=1.0, min=-10.0, max=10.0, step=0.01, tooltip="Scale LoRA 4's represented delta before DARE-Ties merging."),
io.Combo.Input("lora_5", options=["None"] + folder_paths.get_filename_list("loras"), default="None", tooltip="Optional LoRA 5; used only when included by LoRA Count."),
io.Float.Input("weight_5", default=1.0, min=-10.0, max=10.0, step=0.01, tooltip="Scale LoRA 5's represented delta before DARE-Ties merging."),
io.Combo.Input("lora_6", options=["None"] + folder_paths.get_filename_list("loras"), default="None", tooltip="Optional LoRA 6; used only when included by LoRA Count."),
io.Float.Input("weight_6", default=1.0, min=-10.0, max=10.0, step=0.01, tooltip="Scale LoRA 6's represented delta before DARE-Ties merging."),
io.Combo.Input("lora_7", options=["None"] + folder_paths.get_filename_list("loras"), default="None", tooltip="Optional LoRA 7; used only when included by LoRA Count."),
io.Float.Input("weight_7", default=1.0, min=-10.0, max=10.0, step=0.01, tooltip="Scale LoRA 7's represented delta before DARE-Ties merging."),
io.Combo.Input("lora_8", options=["None"] + folder_paths.get_filename_list("loras"), default="None", tooltip="Optional LoRA 8; used only when included by LoRA Count."),
io.Float.Input("weight_8", default=1.0, min=-10.0, max=10.0, step=0.01, tooltip="Scale LoRA 8's represented delta before DARE-Ties merging."),
io.Float.Input("drop_rate", default=0.1, min=0.0, max=1.0, step=0.01, tooltip="DARE drop rate"),
io.Float.Input("trim_quantile", default=0.2, min=0.0, max=1.0, step=0.01, tooltip="TIES trim quantile (drops smallest values)"),
io.Int.Input("seed", default=42, min=0, max=0xffffffffffffffff),
io.String.Input("output_filename", default="merged_lora_dare"),
io.Combo.Input("save_dtype", options=["fp16", "bf16", "fp32"], default="fp16"),
io.Combo.Input("device", options=["cuda", "cpu"], default="cuda"),
io.Float.Input("drop_rate", default=0.1, min=0.0, max=1.0, step=0.01, tooltip="Fraction of factor values randomly dropped before rescaling; higher values increase sparsification."),
io.Float.Input("trim_quantile", default=0.2, min=0.0, max=1.0, step=0.01, tooltip="Remove the lowest-magnitude fraction before resolving sign conflicts with TIES."),
io.Int.Input("seed", default=42, min=0, max=0xffffffffffffffff, tooltip="Random seed for reproducible DARE masks."),
io.String.Input("output_filename", default="merged_lora_dare", tooltip="Output filename without extension, written under ComfyUI's LoRA directory."),
io.Combo.Input("save_dtype", options=["fp16", "bf16", "fp32"], default="fp16", tooltip="Requested dtype for generated factors; scalar alpha tensors do not determine factor precision."),
io.Combo.Input("device", options=["cuda", "cpu"], default="cuda", tooltip="Device for per-target arithmetic; a CUDA OOM retries only the affected target on CPU."),
io.Boolean.Input("include_1d_diffs", default=False,
tooltip="Include and merge 1D direct-diff tensors as FP32."),
],
@@ -1143,34 +1163,34 @@ class LoRAMultiMergeDAREEnhanced(io.ComfyNode):
inputs=[
io.Combo.Input("base_model", options=["None"] + folder_paths.get_filename_list("diffusion_models"), default="None",
tooltip="Optional reference model to resolve key naming issues across formats. If None, input keys are preserved verbatim."),
io.Combo.Input("lora_count", options=[str(i) for i in range(1, 9)], default="2"),
io.Combo.Input("lora_count", options=[str(i) for i in range(1, 9)], default="2", tooltip="Use the first N LoRA selectors; selectors beyond this count are ignored."),
# LoRA 1
io.Combo.Input("lora_1", options=folder_paths.get_filename_list("loras"), tooltip="First LoRA"),
io.Float.Input("weight_1", default=1.0, min=-10.0, max=10.0, step=0.01),
io.Float.Input("weight_1", default=1.0, min=-10.0, max=10.0, step=0.01, tooltip="Scale LoRA 1's represented delta before enhanced DARE-Ties merging; negative values subtract it."),
# ...
io.Combo.Input("lora_2", options=["None"] + folder_paths.get_filename_list("loras"), default="None"),
io.Float.Input("weight_2", default=1.0, min=-10.0, max=10.0, step=0.01),
io.Combo.Input("lora_3", options=["None"] + folder_paths.get_filename_list("loras"), default="None"),
io.Float.Input("weight_3", default=1.0, min=-10.0, max=10.0, step=0.01),
io.Combo.Input("lora_4", options=["None"] + folder_paths.get_filename_list("loras"), default="None"),
io.Float.Input("weight_4", default=1.0, min=-10.0, max=10.0, step=0.01),
io.Combo.Input("lora_5", options=["None"] + folder_paths.get_filename_list("loras"), default="None"),
io.Float.Input("weight_5", default=1.0, min=-10.0, max=10.0, step=0.01),
io.Combo.Input("lora_6", options=["None"] + folder_paths.get_filename_list("loras"), default="None"),
io.Float.Input("weight_6", default=1.0, min=-10.0, max=10.0, step=0.01),
io.Combo.Input("lora_7", options=["None"] + folder_paths.get_filename_list("loras"), default="None"),
io.Float.Input("weight_7", default=1.0, min=-10.0, max=10.0, step=0.01),
io.Combo.Input("lora_8", options=["None"] + folder_paths.get_filename_list("loras"), default="None"),
io.Float.Input("weight_8", default=1.0, min=-10.0, max=10.0, step=0.01),
io.Combo.Input("lora_2", options=["None"] + folder_paths.get_filename_list("loras"), default="None", tooltip="Optional LoRA 2; used only when included by LoRA Count."),
io.Float.Input("weight_2", default=1.0, min=-10.0, max=10.0, step=0.01, tooltip="Scale LoRA 2's represented delta before enhanced DARE-Ties merging."),
io.Combo.Input("lora_3", options=["None"] + folder_paths.get_filename_list("loras"), default="None", tooltip="Optional LoRA 3; used only when included by LoRA Count."),
io.Float.Input("weight_3", default=1.0, min=-10.0, max=10.0, step=0.01, tooltip="Scale LoRA 3's represented delta before enhanced DARE-Ties merging."),
io.Combo.Input("lora_4", options=["None"] + folder_paths.get_filename_list("loras"), default="None", tooltip="Optional LoRA 4; used only when included by LoRA Count."),
io.Float.Input("weight_4", default=1.0, min=-10.0, max=10.0, step=0.01, tooltip="Scale LoRA 4's represented delta before enhanced DARE-Ties merging."),
io.Combo.Input("lora_5", options=["None"] + folder_paths.get_filename_list("loras"), default="None", tooltip="Optional LoRA 5; used only when included by LoRA Count."),
io.Float.Input("weight_5", default=1.0, min=-10.0, max=10.0, step=0.01, tooltip="Scale LoRA 5's represented delta before enhanced DARE-Ties merging."),
io.Combo.Input("lora_6", options=["None"] + folder_paths.get_filename_list("loras"), default="None", tooltip="Optional LoRA 6; used only when included by LoRA Count."),
io.Float.Input("weight_6", default=1.0, min=-10.0, max=10.0, step=0.01, tooltip="Scale LoRA 6's represented delta before enhanced DARE-Ties merging."),
io.Combo.Input("lora_7", options=["None"] + folder_paths.get_filename_list("loras"), default="None", tooltip="Optional LoRA 7; used only when included by LoRA Count."),
io.Float.Input("weight_7", default=1.0, min=-10.0, max=10.0, step=0.01, tooltip="Scale LoRA 7's represented delta before enhanced DARE-Ties merging."),
io.Combo.Input("lora_8", options=["None"] + folder_paths.get_filename_list("loras"), default="None", tooltip="Optional LoRA 8; used only when included by LoRA Count."),
io.Float.Input("weight_8", default=1.0, min=-10.0, max=10.0, step=0.01, tooltip="Scale LoRA 8's represented delta before enhanced DARE-Ties merging."),
io.Float.Input("mask_power", default=2.0, min=0.001, max=10.0, step=0.01, tooltip="Mask power (Curve. 2.0 = quadratic)"),
io.Float.Input("min_keep_prob", default=0.01, min=0.0, max=1.0, step=0.01, tooltip="Minimum keep probability (Floor to prevent explosion)"),
io.Float.Input("mask_smooth", default=0.0, min=0.0, max=1.0, step=0.01, tooltip="Mask smoothness factor (0.0 = pure dropout, 1.0 = soft scale)"),
io.Float.Input("trim_quantile", default=0.2, min=0.0, max=1.0, step=0.01, tooltip="TIES trim quantile (drops smallest values)"),
io.Int.Input("seed", default=42, min=0, max=0xffffffffffffffff),
io.String.Input("output_filename", default="merged_lora_dare_enhanced"),
io.Combo.Input("save_dtype", options=["fp16", "bf16", "fp32"], default="fp16"),
io.Combo.Input("device", options=["cuda", "cpu"], default="cuda"),
io.Float.Input("mask_power", default=2.0, min=0.001, max=10.0, step=0.01, tooltip="Exponent shaping magnitude-based keep probabilities; larger values favor high-magnitude values more strongly."),
io.Float.Input("min_keep_prob", default=0.01, min=0.0, max=1.0, step=0.01, tooltip="Lower bound for each value's keep probability, limiting inverse-probability rescaling."),
io.Float.Input("mask_smooth", default=0.0, min=0.0, max=1.0, step=0.01, tooltip="Blend between stochastic masking at 0 and smooth probability scaling at 1."),
io.Float.Input("trim_quantile", default=0.2, min=0.0, max=1.0, step=0.01, tooltip="Remove the lowest-magnitude fraction before resolving sign conflicts with TIES."),
io.Int.Input("seed", default=42, min=0, max=0xffffffffffffffff, tooltip="Random seed for reproducible enhanced DARE masks."),
io.String.Input("output_filename", default="merged_lora_dare_enhanced", tooltip="Output filename without extension, written under ComfyUI's LoRA directory."),
io.Combo.Input("save_dtype", options=["fp16", "bf16", "fp32"], default="fp16", tooltip="Requested dtype for generated factors; scalar alpha tensors do not determine factor precision."),
io.Combo.Input("device", options=["cuda", "cpu"], default="cuda", tooltip="Device for per-target arithmetic; a CUDA OOM retries only the affected target on CPU."),
io.Boolean.Input("include_1d_diffs", default=False,
tooltip="Include and merge 1D direct-diff tensors as FP32."),
],
+42
View File
@@ -0,0 +1,42 @@
import ast
from pathlib import Path
NODE_DIR = Path(__file__).resolve().parents[1] / "nodes"
def test_every_schema_input_has_a_direct_static_tooltip():
missing = []
non_static = []
for path in sorted(NODE_DIR.glob("*.py")):
tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path))
for call in ast.walk(tree):
if not (
isinstance(call, ast.Call)
and isinstance(call.func, ast.Attribute)
and call.func.attr == "Input"
):
continue
name = (
call.args[0].value
if call.args and isinstance(call.args[0], ast.Constant)
else "<dynamic input name>"
)
tooltip = next(
(keyword.value for keyword in call.keywords if keyword.arg == "tooltip"),
None,
)
location = f"{path.name}:{call.lineno}:{name}"
if tooltip is None:
missing.append(location)
elif not (
isinstance(tooltip, ast.Constant)
and isinstance(tooltip.value, str)
and tooltip.value.strip()
):
non_static.append(location)
assert not missing, "Inputs without tooltips:\n" + "\n".join(missing)
assert not non_static, "Tooltips must be direct non-empty string literals:\n" + "\n".join(non_static)