简介:这是一款专为ComfyUI用户打造的中文提示词输入自定义插件,针对不熟悉英文术语的创作者设计,可直接通过中文关键词完成AI生图参数调整与创意表达,大幅降低上手门槛。资源包为ZIP压缩格式,共619个文件,包括224个JSON节点配置、101个JavaScript脚本、9个Python脚本,以及大量PNG预览图、CSS样式和示例文档,整体约16.96MB,解压到CustomNode目录即可生效。目前已有769人学习下载,适合零基础新手和希望提升AI绘画效率的中文用户使用。该插件内含完整的界面适配配置、节点样式与自定义字体、可视化示例和安装指引,读者既能直接安装使用,也能通过文件结构了解插件开发思路,为后续自研节点提供参考。借助这一工具,中文用户可以在ComfyUI中更流畅地表达创意,享受AI绘画带来的便利。
1. 为什么在 ComfyUI 里直接敲中文出图会翻车
在 ComfyUI 里输入中文提示词,很多人第一反应是“加个文本框不就行了”,其实真正的卡点不在输入框,而在背后那层文本编码。ComfyUI 内置的文本编码器是基于英文语料训练的,你硬塞一句中文进去,得到的是一个信息损失严重的条件向量,画面经常出现主体对了、风格崩了、细节丢了的情况。这个现象不是玄学,而是编码器的词表里根本没有中文的位置。于是“自定义插件 + 中文提示词输入”才成了一个值得认真做的方向:在输入和编码之间补一层可控的翻译与术语映射,让中文提示词真正变成可用的生产步骤,而不是每次手动查词典、翻完再改。下面这套方案会从插件骨架讲起,一直做到能稳定挂在 ComfyUI 里日常使用的程度。
2. 先搞懂自定义节点的加载骨架:从init.py 到节点面板
2.1 目录结构与两个注册字典
ComfyUI 的自定义节点并不是把 Python 文件丢进custom_nodes就能自动识别,它依赖一套固定的注册机制。一个最干净的插件目录长这样:
custom_nodes/ └── comfyui-zh-prompt/ ├── __init__.py ├── zh_prompt_node.py └── web/web/目录在没有自定义前端 JS 的时候可以留空,但目录本身建议保留,因为新版 ComfyUI 扫描插件时会检查WEB_DIRECTORY指向的路径是否存在。真正决定节点能不能出现在面板里的,是__init__.py里的两个字典。NODE_CLASS_MAPPINGS用于把字符串 ID 映射到 Python 类,NODE_DISPLAY_NAME_MAPPINGS用于定义显示名,两者缺一不可。如果显示名映射漏了,ComfyUI 会直接用类名当显示名,虽然不影响运行,但在节点列表里很难看。
另外一个实务点:字符串 ID 要保证全局唯一。不同作者写的插件如果撞了同一个 ID,ComfyUI 启动时会采用“后加载者覆盖先加载者”的策略,你辛苦写的节点可能直接被别人的同名节点顶掉。所以给节点起内部 ID 时,最好带项目前缀,比如zh_prompt_translate_node,而不是图省事写个translate。
2.2 最小插件骨架:注册一个空节点
先跑通一个最小骨架,确认加载链路没有断,再去填充翻译逻辑。下面是__init__.py的完整内容:
# __init__.py from .zh_prompt_node import ZhCnPromptNode NODE_CLASS_MAPPINGS = { "ZhCnPromptNode": ZhCnPromptNode, } NODE_DISPLAY_NAME_MAPPINGS = { "ZhCnPromptNode": "中文提示词输入", } WEB_DIRECTORY = "./web" __all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"]WEB_DIRECTORY = "./web"是近几版 ComfyUI 的硬性要求。如果你用的是较老版本,web 目录会自动识别,但新版没有这个声明就可能出现“Python 类明明注册了,前端面板就是找不到”的怪问题。这一点先记住,后面的避坑章节会再展开。
然后是zh_prompt_node.py,先写一个只做透传的占位节点,验证“能在面板里拖出来并且执行不报错”:
# zh_prompt_node.py class ZhCnPromptNode: @classmethod def INPUT_TYPES(cls): return { "required": { "zh_text": ("STRING", { "multiline": True, "default": "一只橘猫坐在窗台上", }), }, } RETURN_TYPES = ("STRING",) RETURN_NAMES = ("en_prompt",) FUNCTION = "run" CATEGORY = "中文提示词输入" def run(self, zh_text): return (zh_text,)INPUT_TYPES里的multiline: True让前端渲染出一个多行文本框,这是 ComfyUI 里标准的多行文本控件声明方式。RETURN_TYPES声明输出类型是字符串,配套的RETURN_NAMES给出端口名称。FUNCTION指定执行入口方法名,节点每次执行时都会调用run方法,并把输入参数按名字传进来。注意,返回值必须是元组,哪怕只有一个输出也要写成(zh_text,),写成普通字符串会直接报错。
把文件放好后重启 ComfyUI,打开节点搜索面板,输入“中文提示词输入”,能搜到并且能执行,就说明加载链路通了。
2.3 三种常用的输入控件声明:文本、下拉与多行
做提示词输入插件,最常用的输入控件有三种。第一种是上面那种多行文本,适合写完整的中文提示词;第二种是单行文本,适合做phrase这种短词组输入;第三种是下拉框,适合做工作模式切换。三种控件写法如下:
@classmethod def INPUT_TYPES(cls): return { "required": { "zh_text": ("STRING", {"multiline": True, "default": ""}), "mode": (["translate_only", "translate_and_return_list"], {"default": "translate_only"}), }, "optional": { "extra_note": ("STRING", {"default": "best quality"}), }, }下拉框的写法是给一个字符串列表作为可选值,ComfyUI 会自动渲染成下拉控件;optional区块里的参数不在默认端口里强制显示,但对提示词类插件来说,把“补充英文风格词”这种不常用的输入放到 optional 里,能让主界面保持干净。
这里有一个经常被忽略的细节:STRING控件若想设置“当前值不可为空”,需要把 default 留空并在节点端自行校验,ComfyUI 本身不会因为空字符串给你报错,它只是沉默地把空字符串传进来。所以翻译节点里遇到空输入时,要在代码里明确返回一个回落提示词,而不是继续调翻译接口。
3. 实现中文转英文提示词节点:翻译、缓存与术语保护
3.1 节点代码:带缓存和重试的翻译逻辑
空节点跑通之后,接着把翻译逻辑填进去。下面这版代码是我在实际使用时一直保留的结构,它做了三件事:缓存同名提示词的翻译结果、对专有名词做占位保护、请求超时后自动重试一次。
# zh_prompt_node.py import hashlib import json import time from pathlib import Path import requests BASE_DIR = Path(__file__).parent CACHE_FILE = BASE_DIR / "translation_cache.json" TRANSLATE_API = "替换成你申请到的翻译接口地址" TERM_DICT = { "杰作": "masterpiece", "高质量": "best quality", "高细节": "highly detailed", "电影光": "cinematic lighting", "赛璐璐": "cel shading", "全身像": "full body shot", } class ZhCnPromptNode: @classmethod def INPUT_TYPES(cls): return { "required": { "zh_text": ("STRING", { "multiline": True, "default": "一只橘猫坐在窗台上,午后阳光,杰作级", }), "timeout": ("INT", {"default": 10, "min": 3, "max": 60, "step": 1}), "use_cache": ("BOOLEAN", {"default": True}), }, "optional": { "term_map": ("STRING", { "multiline": True, "default": "", "placeholder": "每行一个: 中文:英文", }), }, } RETURN_TYPES = ("STRING",) RETURN_NAMES = ("en_prompt",) FUNCTION = "run" CATEGORY = "中文提示词输入" def run(self, zh_text, timeout, use_cache, term_map): cache_key = hashlib.md5( (zh_text + "||" + term_map).encode("utf-8") ).hexdigest() if use_cache: cache = self._load_cache() if cache_key in cache: return (cache[cache_key],) guarded_text, placeholder_map = self._protect_terms(zh_text, term_map) translated = self._request_translation(guarded_text, timeout) en_text = self._restore_terms(translated, placeholder_map) if use_cache: cache[cache_key] = en_text self._save_cache(cache) return (en_text,)cache_key用zh_text和term_map拼接后取 MD5,这样改了术语映射也会走新的缓存,不会互相污染。_protect_terms是这套节点的核心思路:把“杰作”“赛璐璐”这类词从中文句子里抽出来,换成大括号占位符,让翻译接口先翻译剩下的普通句子,翻译完成后再把英文术语填回去。这样做的原因很实际——机器翻译遇到“赛璐璐”这类动画术语时,经常翻出离谱的地名音译,占位保护能直接绕过这个问题。
3.2 关键方法与参数说明
继续补齐上面的三个私有方法。它们对应了三个容易翻车的环节:接口调用、术语表解析、占位还原。
def _load_cache(self): if not CACHE_FILE.exists(): return {} try: return json.loads(CACHE_FILE.read_text(encoding="utf-8")) except (json.JSONDecodeError, OSError): return {} def _save_cache(self, cache): try: CACHE_FILE.write_text( json.dumps(cache, ensure_ascii=False, indent=2), encoding="utf-8", ) except OSError: pass def _protect_terms(self, text, term_map): mapping = {} for zh, en in TERM_DICT.items(): if zh in text: mapping[zh] = en if term_map and term_map.strip(): for line in term_map.splitlines(): if ":" not in line: continue zh, en = line.split(":", 1) zh, en = zh.strip(), en.strip() if zh and en: mapping[zh] = en placeholder_map = {} guarded = text for i, (zh, en) in enumerate(mapping.items()): placeholder = f"{{{{term_{i}}}}}" guarded = guarded.replace(zh, placeholder) placeholder_map[placeholder] = en return guarded, placeholder_map def _restore_terms(self, translated, placeholder_map): for placeholder, en in placeholder_map.items(): if placeholder in translated: translated = translated.replace(placeholder, en) else: translated += ", " + en return translated def _request_translation(self, text, timeout): for attempt in range(2): try: resp = requests.post( TRANSLATE_API, json={"q": text}, timeout=timeout, ) if resp.status_code == 200: data = resp.json() if isinstance(data.get("translatedText"), str): return data["translatedText"] if isinstance(data.get("data"), dict): return data["data"]["translated"] except requests.Timeout: pass time.sleep(1) raise RuntimeError("翻译接口超时,请检查网络或调大 timeout 参数")TERM_DICT是内置的默认映射表,适合通用画风词;term_map是用户在节点面板上临时追加的映射,优先级更高,适合某个特定项目里的人名、地名或画风词。占位符用的是双层花括号{{term_0}}这种形式,绝大多数翻译接口会保留花括号内容不翻译,这是做过多个接口后才确认可行的做法。
如果翻译接口返回的内容里找不到占位符,说明接口把花括号吞掉了,这时直接拼到句子末尾,至少保证术语不丢。这个“兜底拼回”的行为会让句子结构不完美,但比术语丢失导致画错要强得多。
节点面板上的参数各有讲究。timeout默认给 10 秒,免费接口建议调到 15,商用接口 8 到 10 秒足够。use_cache默认开启,实际使用中这一项能省掉八成以上的重复请求。term_map默认留空,在需要时逐行填写,例如吉卜力:Studio Ghibli,每行一对映射。
对于不指定具体厂商的翻译接口,这里留了TRANSLATE_API常量,你申请到哪家就填哪家。选接口时重点看两个指标:免费额度里的请求频率限制,以及返回 JSON 的结构是否固定。不确定响应结构时,先用浏览器或命令行工具直接调用一次观察返回,再决定_extract_translation里解析哪个字段。
3.3 把节点接到现有工作流里:两种连接方式
这个节点最稳妥的用法是只输出字符串,不碰 CLIP 模型。把en_prompt端口连到 ComfyUI 原生的CLIPTextEncode节点上,正向提示词一套、反向提示词一套,完全复用官方维护的编码逻辑。这样做的最大优势是兼容性——不同时期的 ComfyUI 对CLIPTextEncode内部实现做过多轮调整,但节点本身的端口从来没有变过,你用字符串中转就永远不用追版本。
另一种做法是把 CLIP 编进节点内部,直接输出CONDITIONING,省掉一个连接线。这类实现需要在节点里拿到clip对象并调用文本编码接口,不同版本的方法名有差异,容易跟上版本升级。我一般只在做一次性实验时用,交付给别人用时始终走字符串输出方案。对绝大多数使用者来说,多拉一根线不是负担,少依赖一个内部接口才是真正的省心。
接好线的典型工作流长这样:中文提示词节点输出英文,接到正向CLIPTextEncode;反向提示词同样接一个中文节点,输入“模糊,低质量,丑陋,畸形”这类负面词。首次执行时能明显看到节点有网络请求耗时,第二次执行同一句提示词时,耗时降到几乎为零,因为缓存直接命中了。
4. 排查与避坑:翻译超时、术语崩坏与节点列表消失
4.1 现象一:节点列表里找不到刚写的插件
明明把整个文件夹丢进了custom_nodes,重启之后面板里就是搜不到,控制台也没有红色报错。最常见的元凶有三个。
第一个是__init__.py里忘记写NODE_CLASS_MAPPINGS,或者字典里的类名和实际类对不上。ComfyUI 扫描到没有映射的模块时不会报错,只会静默跳过。第二个是requests库没装,导致zh_prompt_node.py在 import 阶段就抛异常,整组节点加载失败。第三个是版本差异,新版 ComfyUI 要求WEB_DIRECTORY声明存在,缺失时前端不会加载该插件的节点定义。
排查顺序也按这个来:先看启动日志里有没有ImportError或ModuleNotFoundError,其次确认__init__.py的字典键名与类名一致,最后检查WEB_DIRECTORY声明。在__init__.py里临时加一行print("load zh prompt node")是最快的定位方式,启动时输出过这行说明模块本身没问题,问题一定出在注册字典。
4.2 现象二:翻译接口超时或频繁返回限流码
表现很直接:第一次用一切正常,连续生成几张图之后,节点开始报超时,偶尔过几分钟又自己好了。这不是网络断了,而是免费翻译接口对短时间内的请求数量做了限制。中文提示词节点的一个隐蔽特性是会随工作流里的采样步数变化被反复调用,如果同一句提示词没有命中缓存,每次采样都会触发一次新请求。
解决办法分三步:首先确认use_cache确实开着,多数情况开启后同一句提示词只会请求一次;其次把timeout调到 15 秒,给慢响应留出余量;最后在_request_translation的超时分支里补一个退避等待,等 1 秒再重试,不要立即连续请求。代码里现有实现是重试一次,实际需要更稳的队伍可以改成三次重试并把退避时间翻倍递增。
4.3 现象三:中文翻译成英文后画风全变
这是“中文提示词输入”最容易翻车的地方。输入“杰作级,高细节,电影光”,翻译接口给出的结果里这些词要么消失,要么被翻成非常普通的英文,比如“masterpiece”变成“master work”,看似意思相近,但稳定扩散模型对于风格质量词的响应存在惯性,生图效果直接差一截。
解决方式是先保护再翻译,也就是_protect_terms正在做的事。把“杰作”映射为固定的masterpiece,翻译过程不触碰它,生成的英文提示词永远保留目标模型的习惯用语。如果你经常做特定风格,比如古风、赛璐璐、写实摄影,把对应的英文风格词整理进TERM_DICT,一次性投入后面每张图都受益。
另外要注意:不要只依赖翻译接口的自动翻译来覆盖风格词,每个模型偏爱的风格词清单不完全一样,用一段时间后按实际输出效果回填映射表,比不断调接口更有效。
4.4 现象四:输出提示词里出现多余句号与分隔符
翻译结果是英文,但原始中文里的全角逗号、句号可能被直接保留,或者接口在句子末尾多补一个句号。ComfyUI 的提示词语法里逗号是有实际含义的,多余标点会被解析成空权重或异常分组,画面出现明明写“一只猫”却不出猫的情况。
处理办法是在翻译前统一做一次标点归一化,把全角逗号、句号转成半角,翻译完成后再把结果里的连续空格和多余逗号清理干净。这个逻辑如果你已经写完节点再补,也不算迟,在run方法入口加两行替换就行:
zh_text = zh_text.replace(",", ",").replace("。", ".").replace("、", ",")空格与逗号的清理放在翻译之后,使用简单的re.sub(r'\s*,\s*', ', ', translated)即可。这一步直接避免了大量“提示词写得没问题但画面无法复现”的玄学事故。
4.5 验证方法:三张图的控制变量对比
节点写完,不要只看一两次输出就宣布可用,用三张图的控制变量对比来验证质量。固定同一个种子和采样器,生成三组画面:第一组用翻译节点输出的英文;第二组用人手修正过的英文;第三组完全用原生英文提示词。
| 对比项 | 第一组 | 第二组 | 第三组 | 预期观察 |
|---|---|---|---|---|
| 主体描述 | 翻译输出 | 人工修正 | 原生英文 | 主体应该一致 |
| 风格词完整性 | 可能缺 | 已补充 | 完整 | 画风差异主要出在这里 |
| 细节密度 | 看映射表 | 看映射表 | 原生 | 缺失时细节明显减少 |
如果翻译输出的画面与人工修正后的差距集中在风格词上,说明术语表需要补条目;如果主体描述本身就漂移,说明接口的翻译质量不达标,需要换接口或者把节点改为走本地翻译模型。这套验证方式不需要额外工具,固定种子跑一次就能定位绝大多数问题。
5. 进阶:术语字典、本地翻译回退与批量提示词
把这套节点推给团队或自己长期使用时,有三个体验上的做法可以进一步优化。第一个是维护一份“项目专属术语映射表”,不要只依赖代码里的TERM_DICT。每次出图后凡是发现“为什么这里翻译歪了”的词,立刻记到term_map输入框里。一段时间后这些映射会沉淀成一份高价值资产,关键的是它跟工作流文件一起保存,换电脑不丢。
吉卜力:Studio Ghibli 新海诚风:Shinkai style 赛博朋克:cyberpunk, neon-lit第二个是考虑本地翻译回退。对免费接口的请求频率限制感到恼火时,可以在节点里加一个布尔参数use_local_first,优先调用本地部署的翻译模型,失败再走远程接口。本地翻译的好处是零延迟、无限流、数据不出机器,缺点是首次加载模型耗时较长,适合批量出图的场景,不适合随手玩几张的轻量使用。实现时只需在_request_translation前面插入一个本地模型分支即可,缓存逻辑完全复用。
第三个是批量提示词的缓存热身。如果你经常跑多个候选提示词做方案筛选,可以在正式出图前,先把这批中文提示词逐一过一遍节点,让缓存文件提前写满。之后工作流真正执行采样时,所有翻译请求全部命中本地缓存,整个执行过程不会卡在网络请求上。缓存文件本身可以直接复制到另一台机器,团队里分发工作流时顺手带上它,新成员连接口都不填就能跑通。
这个方向做到这里,基本就脱离了“会写一个节点”的阶段,变成了一套稳定且自带知识积累的中文提示词生产方案。不要指望某个免费翻译接口永远稳定,也别指望内置术语表覆盖所有风格,保持“随时往映射表里加一行”的习惯,比换更强壮的接口更能长期改善出图效果。希望这些踩过的坑能帮到你。
本文还有配套的精品资源,点击获取