我要提问
ARTICLE DETAIL

资讯详情

前沿编程新知与开发实战干货的深度解读。

ComfyUI中文提示词插件开发:从CLIP翻译节点到缓存兜底全攻略

ComfyUI中文提示词插件开发:从CLIP翻译节点到缓存兜底全攻略 简介这是专为中文用户设计的 ComfyUI 自定义插件将英文提示词门槛替换为直观的中文输入适用于 AI 绘画爱好者、设计师以及刚接触 ComfyUI 的非专业用户。资源以 zip 形式打包共 619 个文件含 json 配置、png 预览图与样式示意、js 交互脚本、py 插件主体代码以及 md 说明与 sample 示例文件压缩包约 16.96MB解压放入 CustomNode 目录即可使用。目前已有 769 人学习下载适合需要快速搭建中文工作流的用户。插件在工作流节点内加入中文关键词解析与语义理解无需记忆复杂英文术语即可完成风格控制、参数调整等常见操作同时附带预处理好的字体与样式文件可保持界面显示不乱码。对依赖可视化流程操作的用户而言这款插件显著降低了 ComfyUI 的上手成本也为中文 AI 绘画工作流提供了更友好的落地路径。1. 为什么 ComfyUI 需要「中文提示词输入」而不是硬啃英文把一套调好的工作流发给一位只会中文的同事他在正向提示词里填「一只猫在窗台上午后阳光」反向提示词填「低清、模糊、水印」出图结果和提示词完全对不上。这不是使用问题——ComfyUI 默认的 CLIPTextEncode 直接吃英文中文进到 tokenizer 里会被切成一批几乎没有语义的 token而且不报错这是最坑的地方。「ComfyUI 自定义插件 中文提示词输入」要解决的就是这件事把中文提示词转成英文后再喂给 CLIP或者在文本框层面做中文输入增强让不熟悉英文的人也能改工作流。它适合两类人一类是给工作流做交付和分发的人另一类是打算在 ComfyUI 生态里做工具插件的开发者。后面我会按插件骨架怎么搭、中文处理链路怎么选、节点和前端怎么写、坑在哪、怎么把稳定性做上去一层层拆开讲。2. ComfyUI 自定义插件先摸清节点注册与加载这套骨架2.1 插件目录结构与 node.py 最小骨架ComfyUI 的插件机制不复杂启动时扫描custom_nodes/目录下每个子目录先执行目录里的__init__.py再从里面读取节点类映射。目录名字不要求固定但结构基本都长这样custom_nodes/ └── my_zh_text/ ├── __init__.py ├── nodes.py ├── zh_dict.py # 离线词表后续章节会用到 ├── README.md └── web/ └── zh_input.js # 前端扩展nodes.py里定义节点类。先用一个只做「原样透传」的最小节点把链路跑通比一上来就写完整翻译逻辑靠谱得多# nodes.py class HelloZHText: classmethod def INPUT_TYPES(cls): return { required: { zh_text: (STRING, {multiline: True, default: }), } } RETURN_TYPES (STRING,) RETURN_NAMES (text_en,) FUNCTION convert CATEGORY text/zh def convert(self, zh_text): # 先原样返回确认节点能跑通后续再替换成真正的翻译逻辑 return (zh_text,) NODE_CLASS_MAPPINGS {HelloZHText: HelloZHText} NODE_DISPLAY_NAME_MAPPINGS {HelloZHText: 中文提示词透传 (Hello ZH)}代码逻辑说明INPUT_TYPES是节点配置的入口ComfyUI 在创建节点时靠它渲染输入控件RETURN_TYPES决定输出口的类型和颜色STRING是文本输出口RETURN_NAMES让输出口显示成text_en而不是默认的string工作流里一眼能看懂FUNCTION指向真正干活的方法名CATEGORY决定菜单分组这里的text/zh会在 Add Node 菜单生成 text 分类下的「中文提示词」子项。__init__.py只需要做一件小事# __init__.py from .nodes import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS __all__ [NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS]这里常见翻车点是用了绝对导入或者忘记__all__导致启动时自定义节点被静默跳过菜单里什么都没有。保存后重启 ComfyUI在 Add Node 里搜 HelloZH能找到就说明骨架没问题。2.2 INPUT_TYPES 里藏着的参数类型与 Combo 下拉INPUT_TYPES是整个插件的控制面板。写中文提示词节点时常用的参数类型是这几类类型写法渲染效果取值STRING(STRING, {multiline: True, default: 猫})单行/多行输入框字符串COMBO([zh2en, zh2ja], {default: zh2en})下拉菜单选中项字符串INT(INT, {default: 8, min: 1, max: 64})数字输入/步进整数FLOAT(FLOAT, {default: 1.0, step: 0.1})数字输入浮点数CLIP / MODEL(CLIP, {})类型钳制的连接口上游节点输出COMBO是中文提示词节点里很常用的一项——用来切换翻译目标语言。写法上注意它是二元组第一个元素是候选列表ComfyUI 会把选中的字符串作为参数传给FUNCTION。列表放[zh2en, zh2ja]拿到的参数值就是zh2en或zh2ja函数内再做分支。容易被忽略的是hidden参数。INPUT_TYPES的返回值里可以带hidden键把节点 ID 之类运行时信息传进函数classmethod def INPUT_TYPES(cls): return { required: {text: (STRING, {multiline: True})}, hidden: {unique_id: UNIQUE_ID}, }这个unique_id会在调函数时作为关键字参数传入做节点级缓存和日志定位特别方便。注意部分 ComfyUI 版本的 UNIQUE_ID 类型名做过调整函数侧建议用带默认值的 kwargs 兜底别默认所有版本都有。2.3 节点菜单名、分类命名与加载日志三个高频问题先排掉免得后面写了半天看不到节点。第一CATEGORY的层级由斜杠决定。写成text/zh菜单里就是 text → zh。两个插件若用了同一个 CATEGORY会挤在同一组里所以尽量用带项目特征的前缀比如text/zh/my_zh_text避免和别人撞车。第二NODE_DISPLAY_NAME_MAPPINGS决定菜单和节点标题上的显示名。中文名能写就直接写看工作流的人不用对着英文猜功能。第三加载失败时 ComfyUI 不一定弹窗。启动时用python main.py --show-errors能看到每个 custom node 的导入耗时和报错日志里找 Import times for custom nodes 和 Failed to load 字样。节点没出现先去翻启动日志比在菜单里反复刷新快得多。依赖方面我尽量只用 Python 标准库urllib 足够发翻译请求如果图省事想用 requests记得在插件目录放requirements.txt否则换一台机器加载直接失败。3. 中文提示词为什么不能直接塞给 CLIP处理链路的三种选型3.1 CLIP 词表与 Tokenizer 对中文的失效机制CLIP 的 tokenizer 用的是 Byte-Pair Encoding 词表这个词表以英文子词为主。中文句子进去之后会被切成单字或非常罕见的子词组合语义几乎丢光。更麻烦的是它不报错输出一张构图和提示词毫无关系的图你会怀疑是采样步数不够、模型版本不对。我一般会写个小脚本验证一下import clip # 仅示意ComfyUI 内部用自带的 tokenize tokens clip.tokenize(一只猫在窗台上午后阳光).squeeze(0) print(tokens)tensor([ 49406, 10065, 26145, 19016, 36769, ...])对照词表查一下这些 id几乎全是数字和罕见子词而不是 cat、sunlight 这类语义 token。这个现象决定了「中文提示词输入」插件的核心工作在文本进入 CLIP 之前先把中文翻成英文。这段链路里 CLIPTextEncode 本身不用改我们做的是在它前面插一个翻译节点。至于绕过英文、直接用能理解中文的多语言编码器那牵涉到模型底座替换放到 3.4 单独说。3.2 路线一在线翻译接口快但要看 QPS 与失败兜底最常见的做法是调公开翻译接口。延迟一般在 200ms 到 1s 之间对一个文本节点来说完全可以接受词组和长句质量相对有保障不需要自己维护词表。落地时注意三个点。一是 QPS 限制工作流里如果有多个中文节点正反向提示词、多分支一次出图可能并发十几个请求很容易触发频率限制。我一般会在节点里加一层节流每 N 秒最多调一次接口其余请求走缓存。二是鉴权字段不同服务的 appId、secret、签名规则不一样把调用封装成一个独立函数不要散落在节点逻辑里。三是失败回退网络抖动时不能直接让工作流中断至少要回退到本地词表或者原样返回并打日志。在线翻译适合自己用、网络稳定的场景开发成本最低覆盖最好。3.3 路线二本地词典/短语表可控但覆盖有限离线词表走的是「精确短语优先 分词查表」的路子。把工作流里最高频的词先收进去画质词杰作、最佳画质、8k、超详细、负面词模糊、低清、水印、变形、常见主体猫、狗、人像、建筑。这些词互相组合能覆盖提示词里 70% 以上的固定表达。词表文件我喜欢用 JSON 而不是 CSV因为短语里可能带逗号CSV 容易踩转义坑{ 杰作: masterpiece, 最佳画质: best quality, 超详细: ultra-detailed, 模糊: blur, 低清: low resolution, 水印: watermark }查的时候先做最长匹配再按标点把句子切成短语逐个查。离线方案的好处是快、零成本、不依赖外部服务坏处是自由组合的长句会漏比如「午后阳光透过纱窗洒在木地板上」这种句子词表里没有就拼不出来。所以它更适合做在线翻译的兜底而不是主力翻译。3.4 路线三换多语言文本编码器底座量级与权重的代价理论上还有一条更彻底的路用多语言版的文本编码器让中文直接 tokenize 成语义 token。但这条路在 ComfyUI 里落地不是换一个节点的事而是整条生成链路换底座——文本编码器、UNet 交叉注意力行为、权重文件格式都可能不一致踩一圈下来比写十个自定义节点还累。它适合什么场景团队自研底座、把 ComfyUI 当内核嵌进自有产品、对离线中文语义有硬性要求的场景。对单机插件用户我不建议第一优先选这条成本和不确定性远高于前两条。三条路线的取舍在线翻译胜在覆盖和质量本地词表胜在可控和零依赖换编码器胜在彻底但代价最大。我常用的默认组合是在线翻译为主力本地词表做失败兜底缓存做节流。这样单个节点就能应对绝大多数场景。4. 把中文转成英文提示词的自定义节点与前端输入增强可抄作业的实现4.1 核心节点代码翻译、缓存与错误回退下面这个节点是我平时直接能用的版本。为了在没预装 requests 的环境里也能跑我用 urllib 发请求翻译接口地址和鉴权字段按实际服务替换即可。# nodes.py import hashlib, json, os, random, sqlite3, urllib.parse, urllib.request # 本地词表兜底 _DICT {猫: cat, 窗台: windowsill, 阳光: sunlight, 模糊: blur, 水印: watermark, 低清: low resolution} _CACHE_DB os.path.join(os.path.dirname(__file__), zh_cache.sqlite3) def _cache_get(key): con sqlite3.connect(_CACHE_DB) row con.execute(select value from t where key?, (key,)).fetchone() con.close() return row[0] if row else None def _cache_set(key, value): con sqlite3.connect(_CACHE_DB) con.execute(create table if not exists t (key text primary key, value text)) con.execute(insert or replace into t (key, value) values (?, ?), (key, value)) con.commit(); con.close() def _translate_online(text, appid, secret): salt str(random.randint(10000, 99999)) sign hashlib.md5((appid text salt secret).encode(utf-8)).hexdigest() url https://your-api-endpoint/translate? urllib.parse.urlencode({ q: text, from: zh, to: en, appid: appid, salt: salt, sign: sign, }) with urllib.request.urlopen(url, timeout8) as resp: data json.loads(resp.read().decode(utf-8)) # 不同服务的返回字段不同这里要取得英文译文 return data[translated] def _to_en(text, appid, secret): key hashlib.md5((zh|en| text).encode(utf-8)).hexdigest() cached _cache_get(key) if cached: return cached try: en _translate_online(text, appid, secret) except Exception as e: # 网络失败按短语最长匹配回退词表 print([ZH] online translate failed, fallback to dict:, e) en .join(_DICT.get(seg, seg) for seg in text.replace(, ,).split()) _cache_set(key, en) return en class ZHTextEncode: classmethod def INPUT_TYPES(cls): return { required: { text: (STRING, {multiline: True, default: 一只猫在窗台上午后阳光}), clip: (CLIP, {}), target_lang: ([zh2en, zh2ja], {default: zh2en}), appid: (STRING, {default: your_appid}), secret: (STRING, {default: your_secret}), } } RETURN_TYPES (CONDITIONING,) FUNCTION encode CATEGORY text/zh def encode(self, text, clip, target_lang, appid, secret): if target_lang zh2ja: # 这里留分支接口实际可扩展日文翻译 pass prompt_en _to_en(text, appid, secret) tokens clip.tokenize(prompt_en) cond clip.encode_from_tokens(tokens, return_pooledFalse) return (cond,) classmethod def IS_CHANGED(cls, text, **kwargs): # 文本变化时才重跑避免 ComfyUI 缓存旧结果 return text NODE_CLASS_MAPPINGS {ZHTextEncode: ZHTextEncode} NODE_DISPLAY_NAME_MAPPINGS {ZHTextEncode: 中文提示词转英文 (ZH4EN)}这段代码有四个关键设计。翻译函数独立成_to_en先读 SQLite 缓存再请求在线接口网络失败走_DICT回退保证节点永远有返回IS_CHANGED是 ComfyUI 的缓存开关返回值变了才重新执行节点不写这个函数第二次改中文你会发现图根本没变appid和secret作为节点输入参数而不是写死在代码里方便把节点发给别人时各自填自己的target_lang目前只接了zh2enzh2ja留了坑位接入时把目标语言参数传进翻译函数即可。参数上注意三处timeout8是同步请求的底线太短容易误判失败太长会让整个出图流程干等SQLite 缓存库建在插件目录下分发插件时记得说明缓存文件可以删除改词表后也要清一次缓存词表回退的分割逻辑把中文逗号转成英文逗号再 split避免「」在分词后被当成半个字符。同步网络请求是这版代码最大的短板。一次出图串多个中文节点时耗时线性叠加更稳的做法是异步化但 ComfyUI 节点 FUNCTION 是同步入口异步化要走服务端 API 扩展复杂度上一个台阶。个人使用先同步够用。4.2 前端文本框扩展注册 extension 与中文输入法事件后端节点能出图了前端再补一块「中文输入体验」给文本框挂快捷短语面板或输入时机控制。ComfyUI 前端的扩展机制是app.registerExtension一个文件就能注册// web/zh_input.js import { app } from ../../scripts/app.js; const PHRASES [masterpiece, best quality, ultra-detailed, 8k]; app.registerExtension({ name: My.ZHText.Helper, async beforeRegisterNodeDef(nodeType, nodeData) { if (nodeData.name ! ZHTextEncode) return; const onNodeCreated nodeType.prototype.onNodeCreated; nodeType.prototype.onNodeCreated function () { const ret onNodeCreated ? onNodeCreated.apply(this, arguments) : undefined; const w this.widgets.find((x) x.name text); if (w) { // 选词结束后再同步 widget 值避免拼音中间态 w.inputEl.addEventListener(compositionend, () { w.callback?.(w.value); }); } return ret; }; }, });beforeRegisterNodeDef在节点类型注册前被调用nodeData.name是后端节点的类名ZHTextEncode这里做白名单过滤onNodeCreated是节点实例创建的生命周期钩子在里面找到textwidget 并挂事件compositionend是中文输入法选词结束才触发的事件不用它的话监听 keyup 会在拼音还没确认时就把半截字符串写进 widget。w.callback?.()显式触发 widget 回调让下游及时感知文本变化。前端扩展文件放在插件根目录的web/下。../../scripts/app.js是相对前端入口的常见路径前端版本差异可能导致路径失效5.4 会专门说排查方法。4.3 接入既有工作流替换 CLIPTextEncode 的三种具体路径拿到新节点后接入老工作流有三条路按改造量从小到大排。路径 A最省事把正向和反向两个 CLIPTextEncode 整体替换成 ZHTextEncode。CLIP 输入从原来的 Checkpoint Loader 或 CLIP Loader 接过来text 填中文其他连线不动。缺点是反向提示词也走在线翻译每次出图多一次请求反向词一般就几个固定短语直接走词表更合适。路径 B保留原节点先用 STRING 输出节点做中文转英文把输出的text_en连到原 CLIPTextEncode 的 text 输入。改动最小但文本链路多一跳工作流图看起来冗余。路径 C正向走节点、反向走词表正向提示词用 ZHTextEncode反向提示词的输入直接连「负面词表查询」的简单文本节点或者把反向词表写进工作流文本里。这套组合出图稳定性最高翻译接口挂掉时反向词还在出图不会因为负面词缺失而翻车。我自己的默认做法是路径 C。判断标准很简单正向提示词是开放文本需要在线翻译的质量反向提示词是固定集合适合零延迟的词表查询。哪条线出问题日志里也分得更清楚。5. 中文提示词插件的 5 个高频坑现象、原因与排查步骤插件写出来能跑只是第一步实际用起来真正消耗时间的是下面这五类问题。5.1 现象菜单里找不到节点原因导入失败被静默吞掉重启后 Add Node 里搜不到 ZHTextEncode。原因一般是__init__.py导入路径写错或 nodes.py 里语法错误ComfyUI 会把异常打进启动日志而不是弹窗提示。解决用python main.py --show-errors启动翻日志里 Failed to load 那一段异常堆栈会打出来修完后不用重启整个程序CtrlC 再启动即可。这里还有个小坑多个插件目录里同名文件会造成导入混淆给节点和目录起名时加个前缀别叫 my_node 这种通用名。5.2 现象改了中文提示词出的图完全没变化原因节点缓存没有失效ComfyUI 对相同输入的节点结果会做缓存。文本 widget 的变化若没触发IS_CHANGED节点直接返回历史结果翻译根本没执行。解决实现IS_CHANGED返回值带上原始文本和目标语言像 4.1 那样让返回值随 text 变化即可。注意别把翻译后的英文塞进IS_CHANGED——翻译结果变化应该走缓存逻辑而不是触发重跑两者关注点不同。检查方法右键节点看有没有「重新执行」选项有但没有重跑基本就是IS_CHANGED没生效。5.3 现象翻译接口一超时整条工作流直接红色报错中断原因同步网络请求没有兜底FUNCTION 是同步入口urllib 抛异常会一路冒泡到工作流执行中断。解决网络请求必须包 try/except失败时回退到本地词表并打一行日志标记 fallback否则分不清这轮图是用在线翻译还是词表出的。超时参数从 5 秒起调局域网内翻译服务可以压到 3 秒公网服务给 8 秒比较稳。另外在做回退时把原始中文也拼进日志方便回头补词表条目。5.4 现象前端扩展不生效浏览器报 undefined原因前端路径与版本差异浏览器控制台报Cannot read properties of undefined (reading registerExtension)多半是 web 目录路径不对或前端版本太老。解决先确认web/zh_input.js在插件根目录下再用浏览器开发者工具的 Network 面板看脚本有没有被加载404 就说明路径不对。较老的前端版本里registerExtension挂在全局上新版本更推荐用 app 对象兼容写法是(window.app ?? window).registerExtension二选一。5.5 现象中文输入法选词时翻译提前触发出图用了半截拼音原因监听事件选错监听 keyup 时输入「猫」的拼音 mao还没选字m/a/o 三次 keyup 都会触发widget 里被写入 mao 或 ma。解决只监听compositionend并在 keydown 里判断e.isComposing为真时跳过处理中文输入法在部分浏览器里 compositionend 后还跟一个 keydown这个事件要用防抖吞掉。这个坑只影响中文用户英文用户永远复现不出来排查时容易误以为是玄学。6. 进阶离线词表与缓存让中文提示词插件的成本与稳定性更可控6.1 词表优先级与 SQLite 缓存的配合细化下来节点内部按「精确短语 分词查表 在线翻译 原样返回」四级处理。精确短语指「最佳画质」这种完整词条直接命中分词查表把长句拆成子句做最长匹配在线翻译负责剩下的开放文本原样返回是最后底线至少保证不中断。缓存键用source_lang|target_lang|text的 MD5命中时零网络延迟也能把接口的每日配额省下来。6.2 验证清单与我的使用习惯交付前按这张清单过一遍校验项做法通过标准翻译正确性输入 10 条中英对照样本正向词无明显错译缓存生效连续两次跑同一节点看翻译接口调用日志第二次无网络请求断网兜底关掉接口服务跑一次工作流出图成功且走词表日志负向词稳定反向词固定短语禁用在线翻译每次出图零外部请求输入法体验用中文输入法输入长句观察触发时机选词结束后才触发翻译这几条不是形式是真翻过车的。我现在的习惯是凡是分发给别人用的工作流正向词走在线翻译加缓存反向词固定在本地词表翻译接口挂掉也只影响正向文本词表更新后顺手删掉插件目录下的缓存文件否则改词表不生效会让人误以为节点坏了。这个方向做下来一个下午能出一个能交付的版本性价比最高的投入顺序是先写缓存再写词表最后才碰前端体验。希望帮到你。本文还有配套的精品资源点击获取
返回列表