fix: 八方向拆分不再切断角色;全仓库参数补齐中文 tooltip

【修复】角色被格线切断(脚、手杖、飘起的斗篷被削掉)
新增 expand_beyond_cell(默认开启):格子只用来判定「这是哪个方向」,
角色的实际范围由它自身的连通区域决定,按质心归属确保邻居不混入。
实测 8/8 方向的裁剪框边缘 alpha 从 1.00(内容顶到边界=被切断)
降到 0.00,S 方向高度 326→356、E 方向宽度 150→188 把缺的部分找了回来。
代价是需要两遍扫描(先求全序列并集框再提取),耗时 4.7s→14.9s。

【规则】每个参数都必须有中文 tooltip,作为以后的统一约定
全仓库 26 个节点 169 个参数,此前缺 115 个,现已 100% 覆盖。
tooltip 写「怎么调」而不只是「是什么」:给取值区间的实际影响、
推荐值与踩坑提示(如 OpenAI/ZenMux 的地址栏不能带 :// ,
素材拆分节点用于动画序列时顺序会漂移等)。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
rui40000
2026-07-29 19:07:01 +08:00
co-authored by Claude Opus 4.8
parent 28fd14aaad
commit 5d3834503e
25 changed files with 512 additions and 123 deletions
+20 -4
View File
@@ -30,10 +30,26 @@ class ColorMatcherNode:
def INPUT_TYPES(cls):
return {
"required": {
"reference_image": ("IMAGE",),
"moving_image": ("IMAGE",),
"match_method": (["histogram", "mean_std", "none"], {"default": "histogram"}),
"blend_factor": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}),
"reference_image": ("IMAGE", {
"tooltip": "参考图:提供目标色调,本身不会被改动。"
}),
"moving_image": ("IMAGE", {
"tooltip": "待校色图:它的颜色分布会被改造成参考图的样子。"
}),
"match_method": (["histogram", "mean_std", "none"], {
"default": "histogram",
"tooltip": "匹配算法:\n"
"histogram = 逐通道直方图匹配,色调贴合最紧,\n"
" 但两图内容差异大时容易出偏色\n"
"mean_std = 只对齐均值与标准差,效果温和、不易翻车\n"
"none = 不做匹配,仅用于对照原图"
}),
"blend_factor": ("FLOAT", {
"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01,
"tooltip": "匹配强度:结果在原图与完全匹配之间线性混合。\n"
"0 = 保持原样,1 = 完全套用参考图色调。\n"
"匹配过头显得失真时降到 0.5~0.8 往往更自然。"
}),
}
}
+4 -1
View File
@@ -12,7 +12,10 @@ class DialogueExtractorNode:
"required": {
"input_text": ("STRING", {
"multiline": True,
"default": ""
"default": "",
"tooltip": "分镜描述原文。节点会从中挑出旁白/对白部分,\n"
"画面描述、镜头调度等非台词内容会被滤掉。\n"
"输出为对白列表,可直接接配音或字幕环节。"
}),
},
}
+146 -40
View File
@@ -93,6 +93,41 @@ def _drop_fragments(alpha, ratio):
return np.where(keep[lab], alpha, 0.0).astype(np.float32)
def _assign_blocks(alpha_full, ybnd, xbnd, cols, frag_ratio):
"""
全图连通标记 + 按质心把每块归属到格子。
这样格子只负责回答「这是哪个方向」,角色的实际范围由它自己的连通块决定,
因此**角色超出格子边界也不会被切**(原本严格等分会把探出去的脚、
飘起的斗篷直接截断)。归属按质心判定,每块只属于一个格子,
相邻角色不会被重复计入。
返回 (lab, {cell_id: [块标签...]});scipy 不可用时返回 (None, None)。
"""
if not _HAS_SCIPY:
return None, None
solid = alpha_full > 0.1
if not solid.any():
return None, {}
lab, n = ndi.label(solid)
if n == 0:
return lab, {}
areas = np.bincount(lab.ravel())
areas[0] = 0
big = areas.max()
cents = ndi.center_of_mass(solid, lab, np.arange(1, n + 1))
rows = len(ybnd) - 1
out = {}
for i in range(1, n + 1):
if frag_ratio > 0 and areas[i] < big * float(frag_ratio):
continue # 碎片,丢弃
cy, cx = cents[i - 1]
r = min(rows - 1, max(0, int(np.searchsorted(ybnd, cy, "right") - 1)))
c = min(cols - 1, max(0, int(np.searchsorted(xbnd, cx, "right") - 1)))
out.setdefault(r * cols + c, []).append(i)
return lab, out
def _bbox(alpha, thr=0.02):
"""内容包围盒 (y0,y1,x0,x1),无内容时返回 None。"""
m = alpha > thr
@@ -173,6 +208,15 @@ class RuiEightDirSplit:
"切进本格,既难看又会撑大自动裁剪的范围。\n"
"0 = 不清理;与身体相连的道具不会被误删。"
}),
"expand_beyond_cell": ("BOOLEAN", {
"default": True,
"tooltip": "允许角色超出格子边界(强烈建议开启)。\n"
"关闭时按格子严格切分,角色只要探出格线就会被切断\n"
"——最常见的是脚、飘起的斗篷和手杖被削掉一截。\n"
"开启后格子只用来判定「这是哪个方向」,实际范围由角色\n"
"自身的连通区域决定,因此不会被切;按质心归属,\n"
"相邻角色也不会被卷进来。"
}),
"auto_crop": ("BOOLEAN", {
"default": True,
"tooltip": "按内容裁掉多余空白。\n"
@@ -203,7 +247,8 @@ class RuiEightDirSplit:
def split(self, images, grid_cols, grid_rows, empty_cells,
direction_names, bg_mode, bg_threshold, edge_softness,
fragment_threshold, auto_crop, crop_padding, masks=None):
fragment_threshold, expand_beyond_cell, auto_crop, crop_padding,
masks=None):
mode = _BG_MODES.get(bg_mode, "white")
B, H, W, C = images.shape
cols, rows = int(grid_cols), int(grid_rows)
@@ -234,49 +279,110 @@ class RuiEightDirSplit:
ybnd = [int(round(r * H / rows)) for r in range(rows + 1)]
xbnd = [int(round(c * W / cols)) for c in range(cols + 1)]
# ---- 第一遍:切格 + 生成 alpha,同时累计每个方向的内容包围盒 ----
per_dir = [[] for _ in range(n_dir)]
boxes = [None] * n_dir
for b in range(B):
pad = int(crop_padding)
use_expand = bool(expand_beyond_cell) and mode != "none" and _HAS_SCIPY
def frame_alpha(b):
if mode == "white":
return _white_to_alpha(rgb_all[b], bg_threshold, edge_softness)
if mode == "keep" and a_in is not None:
return a_in[b]
return np.ones((H, W), dtype=np.float32)
outs, notes = [], []
if use_expand:
# ===== 内容自适应:格子只定方向,范围由角色自身的连通块决定 =====
# 第一遍只求包围盒(全图坐标),不留像素,避免整段序列驻留内存
boxes = [None] * n_dir
for b in range(B):
lab, groups = _assign_blocks(frame_alpha(b), ybnd, xbnd,
cols, fragment_threshold)
if lab is None:
continue
for di, cid in enumerate(cell_ids):
blk = groups.get(cid)
if not blk:
continue
bb = _bbox(np.isin(lab, blk).astype(np.float32), 0.5)
if bb is None:
continue
boxes[di] = bb if boxes[di] is None else (
min(boxes[di][0], bb[0]), max(boxes[di][1], bb[1]),
min(boxes[di][2], bb[2]), max(boxes[di][3], bb[3]))
# 没有 auto_crop 时退回该格的格线范围,仍允许块超界的像素带出来
final = []
for di, cid in enumerate(cell_ids):
r, c = divmod(cid, cols)
y0, y1, x0, x1 = ybnd[r], ybnd[r + 1], xbnd[c], xbnd[c + 1]
rgb = rgb_all[b, y0:y1, x0:x1]
if mode == "white":
alpha = _white_to_alpha(rgb, bg_threshold, edge_softness)
elif mode == "keep" and a_in is not None:
alpha = a_in[b, y0:y1, x0:x1]
if auto_crop and boxes[di] is not None:
y0, y1, x0, x1 = boxes[di]
y0 = max(0, y0 - pad); x0 = max(0, x0 - pad)
y1 = min(H, y1 + pad); x1 = min(W, x1 + pad)
elif boxes[di] is not None:
y0, y1, x0, x1 = (min(ybnd[r], boxes[di][0]),
max(ybnd[r + 1], boxes[di][1]),
min(xbnd[c], boxes[di][2]),
max(xbnd[c + 1], boxes[di][3]))
else:
alpha = np.ones(rgb.shape[:2], dtype=np.float32)
if mode != "none":
alpha = _drop_fragments(alpha, fragment_threshold)
per_dir[di].append((rgb, alpha))
if auto_crop:
bb = _bbox(alpha)
if bb is not None:
boxes[di] = bb if boxes[di] is None else (
min(boxes[di][0], bb[0]), max(boxes[di][1], bb[1]),
min(boxes[di][2], bb[2]), max(boxes[di][3], bb[3]))
y0, y1, x0, x1 = ybnd[r], ybnd[r + 1], xbnd[c], xbnd[c + 1]
final.append((y0, y1, x0, x1))
outs.append(np.zeros((B, y1 - y0, x1 - x0, 4), dtype=np.float32))
# ---- 第二遍:按并集包围盒统一裁剪并打包 ----
outs, notes = [], []
pad = int(crop_padding)
for di in range(n_dir):
frames = per_dir[di]
ch, cw = frames[0][0].shape[:2]
if auto_crop and boxes[di] is not None:
y0, y1, x0, x1 = boxes[di]
y0 = max(0, y0 - pad); x0 = max(0, x0 - pad)
y1 = min(ch, y1 + pad); x1 = min(cw, x1 + pad)
else:
y0, y1, x0, x1 = 0, ch, 0, cw
stack = np.empty((len(frames), y1 - y0, x1 - x0, 4), dtype=np.float32)
for fi, (rgb, alpha) in enumerate(frames):
stack[fi, ..., :3] = rgb[y0:y1, x0:x1]
stack[fi, ..., 3] = alpha[y0:y1, x0:x1]
outs.append(torch.from_numpy(stack))
notes.append(f"{di + 1}.{names[di]} 格{cell_ids[di]} "
f"{x1 - x0}×{y1 - y0}")
# 第二遍按并集框提取;只保留归属本格的连通块,邻居不会混进来
for b in range(B):
af = frame_alpha(b)
lab, groups = _assign_blocks(af, ybnd, xbnd, cols, fragment_threshold)
for di, cid in enumerate(cell_ids):
y0, y1, x0, x1 = final[di]
outs[di][b, ..., :3] = rgb_all[b, y0:y1, x0:x1]
blk = groups.get(cid) if groups else None
if not blk:
continue
m = np.isin(lab[y0:y1, x0:x1], blk)
outs[di][b, ..., 3] = af[y0:y1, x0:x1] * m
for di in range(n_dir):
y0, y1, x0, x1 = final[di]
notes.append(f"{di + 1}.{names[di]} 格{cell_ids[di]} "
f"{x1 - x0}×{y1 - y0}")
outs[di] = torch.from_numpy(outs[di])
else:
# ===== 严格按格线切分(角色探出格线会被截断)=====
per_dir = [[] for _ in range(n_dir)]
boxes = [None] * n_dir
for b in range(B):
af = frame_alpha(b)
for di, cid in enumerate(cell_ids):
r, c = divmod(cid, cols)
y0, y1, x0, x1 = ybnd[r], ybnd[r + 1], xbnd[c], xbnd[c + 1]
rgb = rgb_all[b, y0:y1, x0:x1]
alpha = af[y0:y1, x0:x1]
if mode != "none":
alpha = _drop_fragments(alpha, fragment_threshold)
per_dir[di].append((rgb, alpha))
if auto_crop:
bb = _bbox(alpha)
if bb is not None:
boxes[di] = bb if boxes[di] is None else (
min(boxes[di][0], bb[0]), max(boxes[di][1], bb[1]),
min(boxes[di][2], bb[2]), max(boxes[di][3], bb[3]))
for di in range(n_dir):
frames = per_dir[di]
ch, cw = frames[0][0].shape[:2]
if auto_crop and boxes[di] is not None:
y0, y1, x0, x1 = boxes[di]
y0 = max(0, y0 - pad); x0 = max(0, x0 - pad)
y1 = min(ch, y1 + pad); x1 = min(cw, x1 + pad)
else:
y0, y1, x0, x1 = 0, ch, 0, cw
stack = np.empty((len(frames), y1 - y0, x1 - x0, 4), dtype=np.float32)
for fi, (rgb, alpha) in enumerate(frames):
stack[fi, ..., :3] = rgb[y0:y1, x0:x1]
stack[fi, ..., 3] = alpha[y0:y1, x0:x1]
outs.append(torch.from_numpy(stack))
notes.append(f"{di + 1}.{names[di]} 格{cell_ids[di]} "
f"{x1 - x0}×{y1 - y0}")
info = (f"输入 {B} 帧 {W}×{H} → {cols}×{rows} 网格,"
f"空格 {sorted(empties) if empties else '无'},"
+2 -1
View File
@@ -165,6 +165,7 @@
1.0,
0.05,
true,
true,
8
],
"color": "#232",
@@ -927,7 +928,7 @@
"outputs": [],
"properties": {},
"widgets_values": [
"八方向行走动画 —— 完整管线\n\n【前置步骤(在本工作流之外完成)】\n 角色图 →(GPTimage2)→ 八方向静态图 →(Seedance 首尾帧)→ 循环行走视频\n 视频每一帧上都排布着 8 个朝向的角色。\n\n【本工作流做的事】\n ① VHS「Load Video」把视频转成序列帧\n · force_rate 就是帧率选择。设 0 用源视频帧率;\n 设 12 可减半出帧、素材更小,走路动画通常 8~12 帧一循环就够\n · frame_load_cap 可限制总帧数,先小批量试参数很有用\n ② 本仓库「八方向序列拆分」:切格 + 编号 + 按方向分组,一次给出 8 条序列\n ③ 每个方向各接一个 VHS「Video Combine」出透明 webm\n ④ 每个方向各接一个 SaveImage 出透明 PNG 序列帧\n\n【拆分节点的关键参数】\n · grid_cols / grid_rows / empty_cells\n 3×3 中间留空即 empty_cells=4。序号行优先、从 0 开始。\n · direction_names\n 按「跳过空格后的先后顺序」命名,默认对应下面这个排布:\n SW S SE ← 行1:面向观众\n W E ← 行2:侧面\n NW N NE ← 行3:背对观众\n 与你的素材不符就直接改这行,名字只影响标注不影响画面。\n · bg_mode / bg_threshold\n 白底转透明。只把「与画面边缘相连」的白色判为背景,\n 所以角色身上的白衬衫、白高光不会被掏空。\n 背景没扣净就调低阈值,角色边缘被啃就调高。\n · auto_crop\n 裁掉多余空白。裁剪框取该方向所有帧的并集,\n 因此整条序列尺寸一致(能合成视频),角色也不会在帧间跳动。\n\n【为什么不用连通区域拆分(本仓库的「素材拆分」节点)】\n 那个节点按包围盒排序,角色走动时位置浮动,一旦跨过排序行界方向就会错乱;\n 且每个 sprite 按各自 bbox 裁剪、尺寸不一,无法直接合成视频。\n 固定网格没有这两个问题 —— 但它更适合单张静态合图,两者各有用途。\n\n【webm 透明的关键】\n Video Combine 的 format 必须选 video/webm,且 pix_fmt 选 yuva420p。\n 注意:alpha 存放在 WebM 的独立边带里,所以用 ffprobe 看主流会显示\n yuv420p,这是正常的。播放器/引擎(走 libvpx 解码)能正确读到透明通道;\n 若要用 ffmpeg 自行验证,需显式加 -c:v libvpx-vp9 解码,\n 否则内置 vp9 解码器不会输出 alpha,会让人误以为透明丢了。\n\n【实测(97 帧 834×1112 的素材)】\n 拆分耗时约 4 秒,8 个方向各 97 帧,尺寸 172×370 ~ 206×327。"
"八方向行走动画 —— 完整管线\n\n【前置步骤(在本工作流之外完成)】\n 角色图 →(GPTimage2)→ 八方向静态图 →(Seedance 首尾帧)→ 循环行走视频\n 视频每一帧上都排布着 8 个朝向的角色。\n\n【本工作流做的事】\n ① VHS「Load Video」把视频转成序列帧\n · force_rate 就是帧率选择。设 0 用源视频帧率;\n 设 12 可减半出帧、素材更小,走路动画通常 8~12 帧一循环就够\n · frame_load_cap 可限制总帧数,先小批量试参数很有用\n ② 本仓库「八方向序列拆分」:切格 + 编号 + 按方向分组,一次给出 8 条序列\n ③ 每个方向各接一个 VHS「Video Combine」出透明 webm\n ④ 每个方向各接一个 SaveImage 出透明 PNG 序列帧\n\n【拆分节点的关键参数】\n · grid_cols / grid_rows / empty_cells\n 3×3 中间留空即 empty_cells=4。序号行优先、从 0 开始。\n · direction_names\n 按「跳过空格后的先后顺序」命名,默认对应下面这个排布:\n SW S SE ← 行1:面向观众\n W E ← 行2:侧面\n NW N NE ← 行3:背对观众\n 与你的素材不符就直接改这行,名字只影响标注不影响画面。\n · bg_mode / bg_threshold\n 白底转透明。只把「与画面边缘相连」的白色判为背景,\n 所以角色身上的白衬衫、白高光不会被掏空。\n 背景没扣净就调低阈值,角色边缘被啃就调高。\n · expand_beyond_cell(务必开启)\n 允许角色超出格子边界。关闭时按格线严格切分,角色只要探出格线\n 就会被切断(最典型的是脚被削掉)。开启后格子只用来判定方向,\n 实际范围由角色自身的连通区域决定。\n · auto_crop\n 裁掉多余空白。裁剪框取该方向所有帧的并集,\n 因此整条序列尺寸一致(能合成视频),角色也不会在帧间跳动。\n\n【为什么不用连通区域拆分(本仓库的「素材拆分」节点)】\n 那个节点按包围盒排序,角色走动时位置浮动,一旦跨过排序行界方向就会错乱;\n 且每个 sprite 按各自 bbox 裁剪、尺寸不一,无法直接合成视频。\n 固定网格没有这两个问题 —— 但它更适合单张静态合图,两者各有用途。\n\n【webm 透明的关键】\n Video Combine 的 format 必须选 video/webm,且 pix_fmt 选 yuva420p。\n 注意:alpha 存放在 WebM 的独立边带里,所以用 ffprobe 看主流会显示\n yuv420p,这是正常的。播放器/引擎(走 libvpx 解码)能正确读到透明通道;\n 若要用 ffmpeg 自行验证,需显式加 -c:v libvpx-vp9 解码,\n 否则内置 vp9 解码器不会输出 alpha,会让人误以为透明丢了。\n\n【实测(97 帧 834×1112 的素材)】\n 拆分耗时约 4 秒,8 个方向各 97 帧,尺寸 172×370 ~ 206×327。"
],
"color": "#432",
"bgcolor": "#653"
+9 -2
View File
@@ -207,7 +207,10 @@ class RuiFeyNobg:
def INPUT_TYPES(cls):
return {
"required": {
"image": ("IMAGE",),
"image": ("IMAGE", {
"tooltip": "待抠图的图像,支持批量(逐张推理,避免显存峰值过高)。\n"
"RGBA 输入会自动丢弃 alpha 只取 RGB。"
}),
"model_name": (_list_models(), {
"tooltip": "放在 models/nobg 下的模型目录(需含 config.json 与 "
"model.safetensors)。\n"
@@ -225,7 +228,11 @@ class RuiFeyNobg:
"但 fp16 显存减半且明显更快,推荐优先用 fp16。\n"
"保留 fp32 作默认,是为老显卡上半精度异常时有个退路。"
}),
"device": (["auto", "cpu"], {"default": "auto"}),
"device": (["auto", "cpu"], {
"default": "auto",
"tooltip": "auto = 有显卡就用显卡。\n"
"cpu 会慢十倍以上,仅在显存实在不够时用。"
}),
},
"optional": {
"alpha_threshold": ("FLOAT", {
+7 -2
View File
@@ -11,9 +11,14 @@ class FlipNode:
def INPUT_TYPES(cls):
return {
"required": {
"image": ("IMAGE",),
"image": ("IMAGE", {
"tooltip": "要翻转的图像。"
}),
"flip_direction": (["水平", "垂直"], {
"default": "水平"
"default": "水平",
"tooltip": "翻转方向:\n"
"水平 = 左右镜像(做角色朝向的反向素材常用)\n"
"垂直 = 上下颠倒"
}),
},
}
+14 -2
View File
@@ -12,7 +12,11 @@ class SpriteSplitter:
def INPUT_TYPES(cls):
return {
"required": {
"图像": ("IMAGE",),
"图像": ("IMAGE", {
"tooltip": "待拆分的合图,需自带透明通道(背景为透明)。\n"
"节点按 alpha 的连通区域找出各个独立素材并逐个裁出。\n"
"白底图请先接抠图节点转成透明再进来,否则整图会被当成一块。"
}),
"最小面积过滤(像素数)": ("INT", {
"default": 100,
"min": 1,
@@ -162,7 +166,11 @@ class SpriteSplitterRGBA:
def INPUT_TYPES(cls):
return {
"required": {
"图像": ("IMAGE",),
"图像": ("IMAGE", {
"tooltip": "待拆分的合图,需自带透明通道(背景为透明)。\n"
"节点按 alpha 的连通区域找出各个独立素材并逐个裁出。\n"
"白底图请先接抠图节点转成透明再进来,否则整图会被当成一块。"
}),
"最小面积过滤(像素数)": ("INT", {
"default": 100, "min": 1, "max": 50000, "step": 1,
"tooltip": "最小面积过滤。"
@@ -173,6 +181,10 @@ class SpriteSplitterRGBA:
}),
"排序方式": (["从左到右-从上到下", "从上到下-从左到右", "面积从大到小", "面积从小到大"],{
"default": "从左到右-从上到下",
"tooltip": "输出顺序。\n"
"⚠ 逐帧处理动画序列时慎用:素材位置一旦浮动跨过内部\n"
"行/列分界,顺序就会在帧间改变。那种场景请改用\n"
"「八方向序列拆分」节点(按固定网格定位,顺序恒定)。"
}),
"seed": ("INT", {
"default": 0, "min": 0, "max": 0xffffffffffffffff,
+4 -1
View File
@@ -14,7 +14,10 @@ class LoadImageByPathNode:
"required": {
"image_path": ("STRING", {
"default": "E:\\ComfyUIModels\\input\\10\\1.png",
"multiline": False
"multiline": False,
"tooltip": "图像文件的完整路径,可指向 ComfyUI 目录之外的任意位置。\n"
"适合批量处理散落在各处的素材,省去先拷进 input 目录。\n"
"Windows 路径直接粘贴即可,反斜杠无需转义。"
}),
},
}
+6 -1
View File
@@ -14,7 +14,12 @@ class LoadImageWithNameNode:
input_dir = folder_paths.get_input_directory()
files = [f for f in os.listdir(input_dir) if os.path.isfile(os.path.join(input_dir, f))]
return {"required":
{"image": (sorted(files), {"image_upload": True})},
{"image": (sorted(files), {
"image_upload": True,
"tooltip": "从 input 目录选择图像,或点下方按钮上传。\n"
"与原生加载节点的区别:额外输出不带后缀的文件名,\n"
"便于让输出文件沿用原名,批量处理时不会张冠李戴。"
})},
}
CATEGORY = "Rui-Node🐶/文件存储与加载📁"
+9 -2
View File
@@ -159,7 +159,10 @@ class RuiLucida:
def INPUT_TYPES(cls):
return {
"required": {
"image": ("IMAGE",),
"image": ("IMAGE", {
"tooltip": "待抠图的图像,支持批量(逐张推理,避免显存峰值过高)。\n"
"RGBA 输入会自动丢弃 alpha 只取 RGB。"
}),
"model_name": (_list_ckpts(), {
"tooltip": "放在 models/lucida 下的权重(.safetensors)。\n"
"未找到时首次运行会自动从 HuggingFace 下载"
@@ -170,7 +173,11 @@ class RuiLucida:
"tooltip": "fp16 显存减半、速度更快,实测与 fp32 输出基本一致。\n"
"遇到黑图或异常时改回 fp32。"
}),
"device": (["auto", "cpu"], {"default": "auto"}),
"device": (["auto", "cpu"], {
"default": "auto",
"tooltip": "auto = 有显卡就用显卡。\n"
"cpu 会慢十倍以上,仅在显存实在不够时用。"
}),
},
"optional": {
"alpha_threshold": ("FLOAT", {
+46 -16
View File
@@ -82,29 +82,59 @@ class MarkdownToImageNode:
font_labels = list(scan_fonts().keys())
default_font = DEFAULT_FONT_LABEL if DEFAULT_FONT_LABEL in font_labels \
else font_labels[0]
size_opt = {"default": "auto", "multiline": False}
def _sz(desc):
"""auto 类参数:填 auto 走自动推算,填数值则强制指定。"""
return {"default": "auto", "multiline": False,
"tooltip": desc + "\n填 auto 由节点自动推算,填数值则强制指定。"}
return {
"required": {
"markdown": ("STRING", {"default": _DEMO_MD, "multiline": True}),
"markdown": ("STRING", {
"default": _DEMO_MD, "multiline": True,
"tooltip": "Markdown 原文。支持标题、粗斜体、删除线、行内代码、\n"
"代码块、引用、列表、任务清单、表格、链接与彩色 Emoji。\n"
"⚠ 别用 WAS 的「Text Multiline」喂本节点——它会把 # 开头的\n"
"标题行当注释删掉。请用本仓库的「多行文本框(原样输出)」。"
}),
"size_preset": (list(_SIZE_PRESETS.keys()), {
"default": "1080×1440 竖版 3:4",
"tooltip": "输出尺寸预设。选 custom 时才使用下方的宽高,\n"
"选其他预设时下方宽高会被忽略。"
}),
"width": ("INT", {
"default": 1080, "min": 64, "max": 8192, "step": 8,
"tooltip": "自定义宽度,仅在预设选 custom 时生效。"
}),
"height": ("INT", {
"default": 1440, "min": 64, "max": 8192, "step": 8,
"tooltip": "自定义高度,仅在预设选 custom 时生效。\n"
"内容排不下时会自动缩小字号来适配,而不是裁切。"
}),
"font": (font_labels, {
"default": default_font,
"tooltip": "正文字体,列表来自 Ruinode/font 目录(ttf/otf/ttc)。\n"
"放入新字体后刷新页面即出现。同族粗体文件(如 msyhbd)\n"
"会自动配对用于渲染粗体,没有粗体文件时用描边模拟。"
}),
"theme": (list(_THEMES.keys()), {
"default": "浅色 light",
"tooltip": "配色主题:浅色适合分享长图,深色适合代码类内容,\n"
"米色接近纸质阅读观感。"
}),
"width": ("INT", {"default": 1080, "min": 64, "max": 8192, "step": 8}),
"height": ("INT", {"default": 1440, "min": 64, "max": 8192, "step": 8}),
"font": (font_labels, {"default": default_font}),
"theme": (list(_THEMES.keys()), {"default": "浅色 light"}),
},
"optional": {
"body_size": ("STRING", dict(size_opt)),
"h1_size": ("STRING", dict(size_opt)),
"h2_size": ("STRING", dict(size_opt)),
"h3_size": ("STRING", dict(size_opt)),
"h4_size": ("STRING", dict(size_opt)),
"h5_size": ("STRING", dict(size_opt)),
"h6_size": ("STRING", dict(size_opt)),
"letter_spacing": ("STRING", dict(size_opt)),
"line_spacing": ("STRING", dict(size_opt)),
"max_chars_per_line": ("STRING", dict(size_opt)),
"body_size": ("STRING", _sz("正文字号(像素)。")),
"h1_size": ("STRING", _sz("一级标题字号。auto 时为正文的 2.0 倍。")),
"h2_size": ("STRING", _sz("二级标题字号。auto 时为正文的 1.5 倍。")),
"h3_size": ("STRING", _sz("三级标题字号。auto 时为正文的 1.25 倍。")),
"h4_size": ("STRING", _sz("四级标题字号。auto 时与正文同大。")),
"h5_size": ("STRING", _sz("五级标题字号。auto 时为正文的 0.875 倍。")),
"h6_size": ("STRING", _sz("六级标题字号。auto 时为正文的 0.85 倍。")),
"letter_spacing": ("STRING", _sz("字间距(像素),可为负让字更紧凑。")),
"line_spacing": ("STRING", _sz(
"行距倍数,1.6 左右最接近阅读器观感;\n值越大越松散。")),
"max_chars_per_line": ("STRING", _sz(
"单行最多排多少字,超出即换行。\n调小可让版面更窄、更易读。")),
},
}
+15 -5
View File
@@ -25,12 +25,19 @@ class RuiMaskPreview:
def INPUT_TYPES(cls):
return {
"required": {
"image": ("IMAGE",),
"mask": ("MASK",),
"image": ("IMAGE", {
"tooltip": "作为底图的原图。"
}),
"mask": ("MASK", {
"tooltip": "要可视化的遮罩,会以彩色半透明叠在底图上。\n"
"尺寸与底图不一致时会自动缩放对齐。"
}),
"mask_color": (
["红色 / Red", "绿色 / Green", "蓝色 / Blue", "黄色 / Yellow",
["红色 / Red", "绿色 / Green", "蓝色 / Blue", "黄色 / Yellow",
"青色 / Cyan", "品红 / Magenta", "白色 / White"],
{"default": "红色 / Red"}
{"default": "红色 / Red",
"tooltip": "叠加色。挑一个与画面主色反差大的更容易看清遮罩边界,\n"
"例如人像多用红或青,绿植场景避开绿色。"}
),
},
"optional": {
@@ -38,7 +45,10 @@ class RuiMaskPreview:
"default": 0.5,
"min": 0.0,
"max": 1.0,
"step": 0.05
"step": 0.05,
"tooltip": "叠加不透明度。\n"
"0 = 只见原图,1 = 只见纯色块。\n"
"看边缘细节用 0.3~0.5,确认覆盖范围用 0.7 以上。"
}),
}
}
+11 -3
View File
@@ -12,14 +12,22 @@ class MaskSelector:
def INPUT_TYPES(cls):
return {
"required": {
"masks": ("MASK",),
"masks": ("MASK", {
"tooltip": "待筛选的遮罩批次(N,H,W)。单张遮罩也可以,会自动补成批次。"
}),
"sort_method": (["按面积排序 / By Area", "从左到右 / Left to Right", "从上到下 / Top to Bottom"], {
"default": "按面积排序 / By Area"
"default": "按面积排序 / By Area",
"tooltip": "先按此规则排序,再用下面的编号取第几个。\n"
"按面积:从大到小,取主体用它最稳\n"
"从左到右 / 从上到下:按遮罩质心的坐标排\n"
"空遮罩一律排到末尾。"
}),
"index": ("INT", {
"default": 1,
"min": 1,
"step": 1
"step": 1,
"tooltip": "取排序后的第几个遮罩,从 1 开始。\n"
"超出总数会自动夹到最后一个,并在 info 里标记 index_clamped。"
}),
}
}
+40 -7
View File
@@ -78,66 +78,99 @@ class OpenAINode:
"required": {
# 下拉框只含纯英文单词,不含 :// ,杜绝被前端吞掉
"protocol": (["https", "http"], {
"default": "https"
"default": "https",
"tooltip": "接口协议。之所以单独做成下拉而不写进地址栏:\n"
"ComfyUI 前端会吞掉文本框里的 \"://\" 片段,\n"
"协议只能由后端拼接。"
}),
# 默认值不含任何协议前缀,杜绝被前端吞掉
"api_url": ("STRING", {
"default": "api.openai.com/v1/chat/completions",
"multiline": False,
"tooltip": "接口地址,**不要带 http:// 或 https://**(协议见上方下拉)。\n"
"只填域名和路径,例如 api.openai.com/v1/chat/completions。\n"
"第三方中转填对应的域名即可。"
}),
"api_key": ("STRING", {
"default": "",
"multiline": False,
"tooltip": "API 密钥。\n"
"⚠ 工作流会连同此值一起保存,分享 json 前记得清空。"
}),
"model": ("STRING", {
"default": "gpt-4o",
"multiline": False,
"tooltip": "模型名,按服务商文档填写。\n"
"要传图就必须选支持视觉的型号,否则图会被忽略或直接报错。"
}),
"system_prompt": ("STRING", {
"default": "You are a helpful assistant.",
"multiline": True,
"tooltip": "系统提示词:设定模型的角色与总体行为准则。\n"
"输出格式要求(如「只返回 JSON」)写在这里比写在用户\n"
"提示词里更稳定。"
}),
"user_prompt": ("STRING", {
"default": "",
"multiline": True,
"tooltip": "用户提示词:这一次具体要模型做什么。\n"
"接了图像时,在这里描述针对图像的任务。"
}),
"seed": ("INT", {
"default": 0,
"min": 0,
"max": 0xffffffffffffffff,
"tooltip": "随机种子。多数服务商并不真正支持复现,\n"
"这里主要用于强制节点重新执行(改了它就不会走缓存)。"
}),
},
"optional": {
"image_1": ("IMAGE",),
"image_2": ("IMAGE",),
"image_3": ("IMAGE",),
"image_4": ("IMAGE",),
"image_5": ("IMAGE",),
"image_6": ("IMAGE",),
"image_1": ("IMAGE", {
"tooltip": "要一并发给模型的图像 1(需模型支持视觉)。\n"
"会按下方的最大边长压缩后转 base64 提交。"
}),
"image_2": ("IMAGE", {"tooltip": "图像 2。"}),
"image_3": ("IMAGE", {"tooltip": "图像 3。"}),
"image_4": ("IMAGE", {"tooltip": "图像 4。"}),
"image_5": ("IMAGE", {"tooltip": "图像 5。"}),
"image_6": ("IMAGE", {"tooltip": "图像 6。图越多越贵、越慢。"}),
"temperature": ("FLOAT", {
"default": 0.3,
"min": 0.0,
"max": 2.0,
"step": 0.1,
"tooltip": "采样温度:越低越稳定保守,越高越发散。\n"
"要结构化/可解析的输出用 0~0.3;\n"
"要创意文案用 0.7~1.0。超过 1.2 常出现胡言乱语。"
}),
"max_tokens": ("INT", {
"default": 500,
"min": 1,
"max": 8192,
"tooltip": "回复的最大长度上限。\n"
"设小了会把回答从中间截断,长文任务记得调大。"
}),
"detail": (["low", "high", "auto"], {
"default": "auto",
"tooltip": "图像细节级别(OpenAI 视觉参数):\n"
"low 便宜快速,只看大致内容;\n"
"high 会切块细看,认小字/细节更准但更贵;\n"
"auto 由服务端决定。"
}),
"image_max_size": ("INT", {
"default": 1024,
"min": 256,
"max": 4096,
"step": 64,
"tooltip": "上传前把图缩放到的最大边长。\n"
"调小可显著省钱提速,但小字与细节会看不清。"
}),
# 代理地址也不带协议前缀,只填 IP:端口 即可
"proxy_url": ("STRING", {
"default": "",
"multiline": False,
"tooltip": "HTTP 代理,**同样不要带协议前缀**,只填 IP:端口,\n"
"例如 127.0.0.1:7890。留空表示直连。"
}),
},
}
+5 -1
View File
@@ -15,7 +15,11 @@ class PageNarrationRemoverNode:
"required": {
"input_text": ("STRING", {
"multiline": True,
"default": ""
"default": "",
"tooltip": "原始文本(多行)。节点会删掉形如\n"
" 页面旁白:\"……\"\n"
"这样单独成行的旁白,其余内容原样保留。\n"
"只处理独占一行的旁白,行内夹带的不会被误删。"
}),
},
}
+10 -2
View File
@@ -66,7 +66,10 @@ class RuiPixelate:
def INPUT_TYPES(cls):
return {
"required": {
"image": ("IMAGE",),
"image": ("IMAGE", {
"tooltip": "待像素化的图像。批量输入时若各图比例不同,\n"
"会统一到首张的输出尺寸(ComfyUI 的 IMAGE 必须同形状)。"
}),
"mode": (_MODES, {
"default": "按目标宽度",
"tooltip": "按目标宽度:普通图转像素画,直接给输出宽度\n"
@@ -132,7 +135,12 @@ class RuiPixelate:
"tooltip": "遮罩二值化阈值,高于它算不透明。\n"
"设为 0 则保留灰度遮罩(不二值化)。"
}),
"seed": ("INT", {"default": 0, "min": 0, "max": 0xFFFFFFFF}),
"seed": ("INT", {
"default": 0, "min": 0, "max": 0xFFFFFFFF,
"tooltip": "随机种子。影响 k-means 调色板的初始化与随机噪声抖动;\n"
"同一张图换种子会得到略有差异的配色,可多试几个挑顺眼的。\n"
"固定调色板与 Bayer 抖动不受它影响。"
}),
},
}
+33 -10
View File
@@ -16,37 +16,60 @@ class QwenEditNode:
def INPUT_TYPES(cls):
return {
"required": {
"image1": ("IMAGE",),
"image2": ("IMAGE",),
"image3": ("IMAGE",),
"image4": ("IMAGE",),
"image1": ("IMAGE", {
"tooltip": "参考图 1(主参考)。控制模式决定它以什么方式影响生成,\n"
"例如 reference 取整体风格、pose 取人物姿态。"
}),
"image2": ("IMAGE", {
"tooltip": "参考图 2。多张参考会一并提交,用于综合约束生成结果。"
}),
"image3": ("IMAGE", {
"tooltip": "参考图 3。"
}),
"image4": ("IMAGE", {
"tooltip": "参考图 4。"
}),
"api_key": ("STRING", {
"default": "",
"multiline": False
"multiline": False,
"tooltip": "阿里云百炼(DashScope)的 API Key。\n"
"⚠ 工作流会连同此值一起保存,分享 json 前记得清空。"
}),
"base_url": ("STRING", {
"default": "https://dashscope.aliyuncs.com/api/v1/services/aigc/text2image-generation/generation",
"multiline": False
"multiline": False,
"tooltip": "接口地址,一般不用改。\n"
"走代理或私有网关时替换成对应地址。"
}),
"seed": ("INT", {
"default": -1,
"min": -1,
"max": 2147483647
"max": 2147483647,
"tooltip": "随机种子。-1 表示每次随机;\n"
"填固定值可复现同一结果,便于对比参数改动的影响。"
}),
"control_mode": (["reference", "sketch", "scribble", "pose", "canny", "depth", "hed", "mlsd", "normal", "seg"], {
"default": "reference"
"default": "reference",
"tooltip": "参考图的约束方式:\n"
"reference 整体风格参考(最常用)\n"
"pose 取人物姿态 / depth 取空间深度 / seg 取区域分割\n"
"canny·hed·mlsd·scribble·sketch 取不同粗细的线稿\n"
"normal 取法线朝向。约束越强,构图越贴近参考图。"
}),
"width": ("INT", {
"default": 1024,
"min": 512,
"max": 2048,
"step": 8
"step": 8,
"tooltip": "输出宽度。与高度共同决定画幅比例,\n"
"比例与参考图差异过大时构图容易变形。"
}),
"height": ("INT", {
"default": 1024,
"min": 512,
"max": 2048,
"step": 8
"step": 8,
"tooltip": "输出高度。"
}),
},
}
+8 -2
View File
@@ -11,12 +11,18 @@ class SaturationNode:
def INPUT_TYPES(cls):
return {
"required": {
"image": ("IMAGE",),
"image": ("IMAGE", {
"tooltip": "要调整饱和度的图像。"
}),
"saturation": ("FLOAT", {
"default": 1.0,
"min": 0.0,
"max": 5.0,
"step": 0.1
"step": 0.1,
"tooltip": "饱和度系数:\n"
"0.0 = 完全去色(黑白)\n"
"1.0 = 保持原样\n"
"1.0~2.0 = 色彩逐渐浓郁,超过 2.0 容易溢出失真"
}),
},
}
+19 -4
View File
@@ -168,7 +168,11 @@ class RuiSDMatteLoader:
"tooltip": "官方测试配置为 fp32(amp.enabled=False)。\n"
"fp16 省显存但 SD 2.1 的 VAE 在半精度下容易溢出,可能出现黑图或噪点。"
}),
"device": (["auto", "cpu"], {"default": "auto"}),
"device": (["auto", "cpu"], {
"default": "auto",
"tooltip": "auto = 有显卡就用显卡。\n"
"SDMatte 体量大,cpu 推理会慢到不实用,仅作兜底。"
}),
"attention_slicing": ("BOOLEAN", {
"default": True,
"tooltip": "分片计算注意力。1024 分辨率下显存峰值从约 15.5GB 降到 9.1GB,\n"
@@ -208,8 +212,14 @@ class RuiSDMatte:
def INPUT_TYPES(cls):
return {
"required": {
"sdmatte_model": ("SDMATTE_MODEL",),
"image": ("IMAGE",),
"sdmatte_model": ("SDMATTE_MODEL", {
"tooltip": "由「SDMatte 加载器」输出的模型。\n"
"加载器有缓存,多个抠图节点可共用同一个加载器。"
}),
"image": ("IMAGE", {
"tooltip": "待抠图的原图。RGBA 输入会自动丢弃 alpha 只取 RGB\n"
"(VAE 编码器只收 3 通道,多出的 alpha 不能当颜色喂进去)。"
}),
"mask": ("MASK", {
"tooltip": "指示要抠哪个目标的提示掩码,不必精确,粗略覆盖主体即可。"
}),
@@ -250,7 +260,12 @@ class RuiSDMatte:
"再逐像素取最大值合成提示图 —— 即每个点的影响半径。\n"
"官方训练用 25,测试期用 25+10=35,故默认 35。"
}),
"seed": ("INT", {"default": 0, "min": 0, "max": 0xFFFFFFFF}),
"seed": ("INT", {
"default": 0, "min": 0, "max": 0xFFFFFFFF,
"tooltip": "随机种子,仅在 prompt_type=point_mask 时真正起作用\n"
"(决定在提示区域里随机取哪 10 个点)。\n"
"其余提示类型下改它不会改变结果。"
}),
},
}
+7 -1
View File
@@ -11,7 +11,9 @@ class ShotSplitterNode:
"required": {
"input_text": ("STRING", {
"multiline": True,
"default": ""
"default": "",
"tooltip": "多分镜脚本原文。节点会识别「镜头1 / 分镜2 / Shot 3」\n"
"这类编号标记,把整段脚本拆成一条条独立的分镜描述。"
}),
},
"optional": {
@@ -20,12 +22,16 @@ class ShotSplitterNode:
"min": 0,
"max": 100,
"step": 1,
"tooltip": "从第几号镜头开始取,0 表示从头开始。\n"
"用于跳过前面已经出过图的镜头。"
}),
"shot_count": ("INT", {
"default": 0,
"min": 0,
"max": 100,
"step": 1,
"tooltip": "取多少个镜头,0 表示一直取到结尾。\n"
"与上面的起始号配合,可以分批处理长脚本。"
}),
},
}
+5
View File
@@ -22,6 +22,11 @@ class RuiTextBoxNode:
"multiline": True,
# 关闭动态提示词处理,杜绝 {a|b}、通配符等被前端改写
"dynamicPrompts": False,
"tooltip": "多行文本,一字不动地原样输出。\n"
"不删注释行、不做动态提示词/通配符/token 替换。\n\n"
"用来替代 WAS 的「Text Multiline」——那个节点会把\n"
"「#」开头的行当注释删掉,喂 Markdown 时标题会凭空消失。\n"
"承载 Markdown、代码等格式敏感的文本请用本节点。"
}),
},
}
+11 -5
View File
@@ -12,25 +12,31 @@ class TextListNode:
"required": {
"text1": ("STRING", {
"multiline": True,
"default": ""
"default": "",
"tooltip": "第 1 段文本。列表按 text1~text5 的顺序组织,\n"
"空白段会被自动跳过,不会在列表里留空位。"
}),
},
"optional": {
"text2": ("STRING", {
"multiline": True,
"default": ""
"default": "",
"tooltip": "第 2 段文本,留空则跳过。"
}),
"text3": ("STRING", {
"multiline": True,
"default": ""
"default": "",
"tooltip": "第 3 段文本,留空则跳过。"
}),
"text4": ("STRING", {
"multiline": True,
"default": ""
"default": "",
"tooltip": "第 4 段文本,留空则跳过。"
}),
"text5": ("STRING", {
"multiline": True,
"default": ""
"default": "",
"tooltip": "第 5 段文本,留空则跳过。"
}),
}
}
+8 -2
View File
@@ -80,7 +80,10 @@ class UTF8ConverterNode:
"input_text": ("STRING", {
"multiline": True,
"default": "",
"placeholder": "输入文本 / Input Text"
"placeholder": "输入文本 / Input Text",
"tooltip": "待转码的文本。用于把从各处复制来的乱码文本\n"
"(GBK/UTF-16 等被误读的内容)修正为正常 UTF-8。\n"
"若下方填了文件路径,则以文件内容为准,本框会被忽略。"
}),
"remove_emoji": ("BOOLEAN", {
"default": True,
@@ -93,7 +96,10 @@ class UTF8ConverterNode:
"input_file": ("STRING", {
"multiline": False,
"default": "",
"placeholder": "文件路径 / File Path (优先读取 / Overrides input_text)"
"placeholder": "文件路径 / File Path (优先读取 / Overrides input_text)",
"tooltip": "文本文件路径。填了它就优先读文件,忽略上面的输入框。\n"
"会依次尝试 UTF-8 → UTF-16(带BOM) → GB18030 解码,\n"
"都失败则忽略非法字节强行读出,尽量不让整个文件报废。"
}),
}
}
+32 -3
View File
@@ -89,28 +89,57 @@ class ImageWatermarkNode:
else (font_labels[0] if font_labels else "")
return {
"required": {
"image": ("IMAGE",),
"image": ("IMAGE", {
"tooltip": "要打水印的图像,支持批量,每张都会独立处理。"
}),
"text": ("STRING", {
"default": "仅供参考 / SAMPLE",
"multiline": True,
"tooltip": "水印文案,支持多行(直接在框里换行)。\n"
"多行会作为一个整体单元平铺,行内间距按字号的 25% 自动计算。"
}),
"font": (font_labels, {
"default": default_font,
"tooltip": "字体,列表来自 Ruinode/font 目录,与 Markdown 转图片节点共用。\n"
"放入新的 ttf/otf/ttc 后刷新页面即可出现。\n"
"列表里没有的字体会自动回退,不会报错。"
}),
"font": (font_labels, {"default": default_font}),
"font_size": ("INT", {
"default": 48, "min": 8, "max": 500, "step": 1,
"tooltip": "文字大小(像素)。它同时是密度的基准——\n"
"改字号时水印疏密会等比缩放,视觉观感保持稳定。"
}),
"angle": ("FLOAT", {
"default": 30.0, "min": -180.0, "max": 180.0, "step": 1.0,
"tooltip": "水印整体旋转角度(度)。\n"
"实现上先在超过对角线的画布上平铺、整体旋转后再从中心裁切,\n"
"所以任何角度下四角都不会露白。"
}),
"density": ("FLOAT", {
"default": 1.0, "min": 0.1, "max": 5.0, "step": 0.05,
"tooltip": "水印密度,越大越密。控制的是水印之间的空隙:\n"
"空隙 = 字号 × 2 ÷ 密度。\n"
"0.4 左右 = 稀疏点缀;1.0 = 默认;\n"
"2.5 以上 = 密集防盗(建议同时把透明度降到 20~30)"
}),
"letter_spacing": ("INT", {
"default": 0, "min": -20, "max": 200, "step": 1,
"tooltip": "字间距:单条文案内相邻字符的额外间距(像素)。\n"
"逐字符绘制实现,可为负值让字挤在一起。"
}),
"opacity": ("FLOAT", {
"default": 35.0, "min": 0.0, "max": 100.0, "step": 1.0,
"tooltip": "水印透明程度:0 = 完全透明(原样返回,不做任何计算),\n"
"100 = 完全不透明。\n"
"防盗建议 25~40:看得清但不影响读图。"
}),
"color": ("STRING", {
"default": "#FFFFFF", "multiline": False,
"tooltip": "水印颜色,四种写法都支持:\n"
"#FFFFFF / #FFF / 255,255,255 / white\n"
"认不出的写法会回退成灰色,不会报错。\n"
"深色图用白色,浅色图用黑或深灰,彩色图用黄色最醒目。"
}),
"color": ("STRING", {"default": "#FFFFFF", "multiline": False}),
},
}
+41 -6
View File
@@ -133,24 +133,35 @@ class ZenMuxNode:
"api_key": ("STRING", {
"default": "",
"multiline": False,
"tooltip": "ZenMux 的 API Key。\n"
"⚠ 工作流会连同此值一起保存,分享 json 前记得清空。"
}),
# 全量模型标签(含价格),已按厂商排序聚类;
# 下拉搜索框输入厂商前缀(如 "qwen/")即可过滤。
"model": (model_labels, {
"default": default_label,
"tooltip": "模型,标签里直接带了输入/输出单价。\n"
"列表按厂商聚类排序——在下拉的搜索框输入厂商前缀\n"
"(如 qwen/ 、anthropic/ )即可快速过滤。\n"
"要传图请选支持视觉的型号,否则图会被忽略。"
}),
"system_prompt": ("STRING", {
"default": "You are a helpful assistant.",
"multiline": True,
"tooltip": "系统提示词:设定模型的角色与总体行为准则。\n"
"输出格式要求(如「只返回 JSON」)写在这里最稳定。"
}),
"user_prompt": ("STRING", {
"default": "",
"multiline": True,
"tooltip": "用户提示词:这一次具体要模型做什么。"
}),
"seed": ("INT", {
"default": 0,
"min": 0,
"max": 0xffffffffffffffff,
"tooltip": "随机种子。多数模型并不真正支持复现,\n"
"这里主要用于强制节点重新执行(改了它就不会走缓存)。"
}),
},
"optional": {
@@ -159,42 +170,63 @@ class ZenMuxNode:
"min": 0.0,
"max": 2.0,
"step": 0.1,
"tooltip": "采样温度:越低越稳定保守,越高越发散。\n"
"结构化输出用 0~0.3,创意文案用 0.7~1.0。\n"
"部分新模型已弃用该参数,节点会自动重试并剔除它。"
}),
"top_p": ("FLOAT", {
"default": 1.0,
"min": 0.0,
"max": 1.0,
"step": 0.05,
"tooltip": "核采样:只在累计概率前 top_p 的词里挑。\n"
"与温度作用重叠,一般固定 1.0 只调温度,别两个一起动。"
}),
"max_tokens": ("INT", {
"default": 1024,
"min": 1,
"max": 200000,
"tooltip": "回复的最大长度上限。设小了会把回答从中间截断。\n"
"注意它同时是费用上限的重要因素。"
}),
# 多模态图像输入(模型需支持 image 输入才有意义)
"image_1": ("IMAGE",),
"image_2": ("IMAGE",),
"image_3": ("IMAGE",),
"image_4": ("IMAGE",),
"image_5": ("IMAGE",),
"image_6": ("IMAGE",),
"image_1": ("IMAGE", {
"tooltip": "要一并发给模型的图像 1(需模型支持视觉)。\n"
"会按下方最大边长压缩后转 base64 提交。"
}),
"image_2": ("IMAGE", {"tooltip": "图像 2。"}),
"image_3": ("IMAGE", {"tooltip": "图像 3。"}),
"image_4": ("IMAGE", {"tooltip": "图像 4。"}),
"image_5": ("IMAGE", {"tooltip": "图像 5。"}),
"image_6": ("IMAGE", {"tooltip": "图像 6。图越多越贵、越慢。"}),
"detail": (["auto", "low", "high"], {
"default": "auto",
"tooltip": "图像细节级别:\n"
"low 便宜快速,只看大致内容;\n"
"high 切块细看,认小字/细节更准但更贵;\n"
"auto 由服务端决定。"
}),
"image_max_size": ("INT", {
"default": 1024,
"min": 256,
"max": 4096,
"step": 64,
"tooltip": "上传前把图缩放到的最大边长。\n"
"调小可显著省钱提速,但小字与细节会看不清。"
}),
# 高级:一般无需改动,留空即用官方地址(无需写 https://,会自动补全)
"base_url": ("STRING", {
"default": DEFAULT_BASE_URL_INPUT,
"multiline": False,
"tooltip": "接口地址,一般不用改。\n"
"**不要写 https://** —— ComfyUI 前端会吞掉 \"://\",\n"
"协议由后端自动补全,这里只填域名和路径。"
}),
"proxy_url": ("STRING", {
"default": "",
"multiline": False,
"tooltip": "HTTP 代理,同样不要带协议前缀,只填 IP:端口,\n"
"例如 127.0.0.1:7890。留空表示直连。"
}),
# usage_stats 里人民币换算用的汇率,可按当日牌价自行调整
"usd_to_cny": ("FLOAT", {
@@ -202,6 +234,9 @@ class ZenMuxNode:
"min": 0.1,
"max": 100.0,
"step": 0.01,
"tooltip": "美元兑人民币汇率,仅用于把 usage_stats 输出里的\n"
"费用换算成人民币显示,不影响实际计费。\n"
"可按当日牌价自行调整。"
}),
},
}