把 NovelAI 提示词法典接进 ComfyUI:节点上开一个非模态窗口,窗口正面就是线上站点 (novelai.quicktagcloud.com)本身 —— 站点的全部功能都在里面: 浏览例图、搜索筛选、NAI↔SD 语法切换、Tag 中转站、灯箱、站点自己的收藏。
插件只在这之上补三样东西:把词条推送到节点、自己的一份收藏、打开你的图库。
改造说明:这版把原先内嵌的「本地离线站点」整个去掉了 —— 那一套要跟着仓库分发 45 MB 词库、 还要另外下 1.4 GB 例图。现在窗口直连线上站点,插件目录里只剩图库那一页。 代价是需要联网。
- 装:把整个
ComfyUI-m8tags文件夹放进<ComfyUI>/custom_nodes/,重启 ComfyUI。 - 连:加一个
法典图鉴 · 文本编码,把它的clip接上 checkpoint 加载器的 CLIP, 它的positive/negative两个输出分别接 KSampler 的positive/negative。 这个节点本身就等于一个「CLIP文本编码」,不用再串一个。 - 逛:点节点上的
前往词典站寻找灵感开窗口,在右边站点里挑。 - 取:在卡片上点
全部(或负面)复制 → 回到窗口点+ 从剪贴板取词→ 点推送到节点。
想省掉第 4 步的手点,见下面「运行前自动换词」。
┌──────────────────────────────────────────────────────────────┐
│ ◆ 法典图鉴 [搜词条…] [搜索] [我的图库] [浏览器打开] [关闭] │
├──────────────────────────────────┬───────────────────────────┤
│ │ +从剪贴板取词 -取负面 ★收藏│
│ ├───────────────────────────┤
│ 线上站点(iframe) │ 已选栏 │
│ 浏览 / 搜索 / NAI↔SD / │ POSITIVE(转换·字数) │
│ 中转站 / 收藏 / 灯箱 │ NEGATIVE │
│ │ [清空] [推送到节点] │
└──────────────────────────────────┴───────────────────────────┘
顶栏那几个按钮:搜索 是把关键词送进站点自己的搜索(带 ?q= 跳过去),
我的图库 把右侧换成图库页,浏览器打开 在独立标签页里打开线上站点。
关窗只是收起来,不销毁 —— iframe 一直活着。你翻到哪一页、搜过什么、滚到哪儿, 下次点开原样还在;窗口里的已选栏、预览框(连你手改过的内容)也一样。 换节点打开也不重置,只是「推送到节点」的目标跟着换。
ComfyUI 页面本身刷新过的话,iframe 会被重建 —— 这种情况靠存在浏览器里的那份地址回位, 恢复到上次停的那一页。想换个位置,用顶栏的搜索框,或者直接在站点里点。
站点在 iframe 里,而它是另一个域名。浏览器对跨域 iframe 的隔离是硬的:
父页面读不到它的 URL、读不到它的 DOM,而站点本身也没有留任何跨窗口通信接口
(没有 postMessage、没有 BroadcastChannel、没有暴露全局对象 —— 这几条都实测过)。
所以「你在网页里选了哪一条」插件无从得知,中间必须过一次剪贴板:
- 在站点卡片上点
全部(复制正向 + 角色词)或负面(复制负向) - 回到右侧栏点
+ 从剪贴板取词或- 取负面
内容会原样进正/负预览框,插件不替你猜格式 —— 站点自己就有 SD 权重开关,
你在站点里开着 SD 模式复制,拿到的就是 SD 语法;没开就是 NAI 语法。
想就地转一次,点预览框边上的 转成 A1111。
合并规则:按逗号拆开、忽略大小写去重。站点复制「正向 + 角色词」时是用换行分段的, 插件会先把换行折成逗号。
两种东西都能收,都带分类:
- 词条文本 —— 取词之后点
★ 收藏,下次点载入直接进已选栏 - 网页位置 —— 点
存当前网页位置,把窗口现在这一页(法典 + 路径 + 搜索词)存下来, 下次点打开窗口直接跳过去
存在浏览器里(ComfyUI 这个源的 localStorage,键名 qtc-m8-favs)。
站点自己那套收藏在它自己的域名下 —— 那是站点的事,两边互不干扰。
窗口顶栏点 我的图库 就切过去(插件自己伺服的 atlas/gallery.html)。
把图拖进去,自动读出底模、LoRA、提示词,只给 C 站链接不下载。
图片存在 <插件目录>/atlas/self-image/,索引是同目录的 index.js。
这一页跟线上站点没关系,断网也能用。
节点上的 随机提示词 从线上那份数据里抽 —— 插件按需取,用完就扔,不落盘:
data-source.json → current.json(当前发布号)
→ releases/<发布号>/codexes.json(法典清单)
→ releases/<发布号>/<法典id>.json(用到哪部才拉哪部)
一个进程里只留内存缓存;站点一发新版(发布号变了),缓存整个作废重取。
法典来源下拉决定从哪部抽。选全部法典(不含 R18)时不会抽画师词典里的词条 —— 那种词典通篇是画师 tag,一条就能把整张图的画风带偏。想抽就明确选中那一部。- 抽到的是 NAI 原文,切
语法就地转成 A1111(纯前端转换,不重新联网), 转换时权重为负的那批会挪进负向框。
不是从池子里均匀抓一条 —— 那样会把 👻👻👻、只有一两个 tag 的条目也照样抽出来,
观感就是"随机质量一般"。现在的规则:
- 先过滤:空 tags、纯符号 / emoji、tag 少于 3 个的,一律不进池子。
实测线上确有这些:
suozhang里有 26 条空 tags,enter_codex里有👻👻👻 - 再按信息量加权:tag 越多权重越高(20 个封顶,免得超长条目一家独大), 有配图的再抬一半 —— 那说明它被社区拿来当过示例
长尾仍然抽得到,只是不再跟好词平分概率。
NAI v5 认中文,所以线上数据里混着中文。我把全部含中文的词条捞出来看过, 只有下面三类,没有整句中文描述:
| 长什么样 | 例子 | 怎么处理 |
|---|---|---|
| 全角括号注释 | (效果优化组件)、(此tag须在最前) |
整段删掉 |
| NAI 多角色标记 | 角色1: arm around the shoulder |
只去标记,内容留着 |
| 中文方括号分组 | 【豹】、【与下列选择搭配】 |
整段删掉 |
SD / A1111 一个字都不认这些,留在提示词里就是噪声。插件在渲染时(两种语法下都清一遍)
以及点 转成 A1111 时都会过一遍。半角括号 (blush:1.2) 是权重语法,绝不碰 ——
这条有专门的测试钉着。
点节点上的 运行自动随机:关(点击开启)。开着的时候,每次运行工作流都会在提交前重新抽一条
—— Run 按钮、Ctrl+Enter、只跑选中的输出节点、队列空时的自动续跑全算。
抽完立刻写进框里,提交上去的就是你看到的那一份。开关是每个节点各自一份,
存在节点的 properties.codexAtlasAutoRandom 里,跟着工作流走。
抽词接口报错(没网、取不到数据)时只弹一条提示,不会拦住运行。
| 入口 | 输出 | 什么时候用 |
|---|---|---|
法典图鉴 · 文本编码 |
CONDITIONING ×2 |
推荐,自带 clip 输入,直接接 KSampler |
法典图鉴 · 提示词 |
STRING ×2 |
只想产出文字,或者把文本转给别的节点 |
法典图鉴 · 只抽正向 |
STRING |
正负各摆一个节点:这个只往正向框抽 |
法典图鉴 · 只抽负向 |
STRING |
只往负向框抽。注意负向料少,见下 |
内置 CLIP文本编码 上的按钮 |
无(改造原节点) | 工作流里已经堆了一堆 CLIPTextEncode |
只抽负向那个节点要有心理准备:线上自带负向的词条只占约 3.9%,而且高度集中在
enter_codex 那一部(其它几个法典是 0%~1.5%)。所以选「全部法典」时它会在几部之间
多试几次;真抽不到会明确告诉你去换哪部,不会悄悄塞一条空的给你。
第三个是设置项:设置 → 搜「法典图鉴」→ 打开
「法典图鉴:在「CLIP文本编码」节点上显示按钮」(默认关,改完刷新页面生效)。
节点上的控件:前往词典站寻找灵感 / 随机提示词 / 运行自动随机 / 语法切换 / 法典来源 / negative。
ComfyUI-m8tags/
├── __init__.py ComfyUI 入口
├── py/
│ ├── nodes.py 两个节点(纯透传,文本处理全在前端)
│ ├── routes.py 后端接口:在线取数、随机抽词、我的图库
│ └── store.py 路径解析(插件目录 / atlas 目录怎么找)
├── js/
│ └── codex_atlas.js 前端:窗口、节点按钮、语法转换、收藏
├── atlas/
│ ├── gallery.html 我的图库(独立页)
│ ├── gallery.js 图库逻辑
│ ├── gallery-meta.js PNG 元数据解析
│ ├── app.css
│ └── self-image/ 你上传的图 + 索引(gitignore)
├── tests/
└── config.json 可选,本机配置(gitignore)
| 方法 | 路径 | 干什么 |
|---|---|---|
| GET | /codex_atlas/codexes |
法典清单(取线上,带缓存) |
| GET | /codex_atlas/random?codex=<id> |
随机抽一条,返回 NAI 原文与负向 |
| GET | /codex_atlas/status |
后端身份与可用功能 |
| GET | /codex_atlas/atlas/{tail} |
图库页静态文件 |
| POST | /codex_atlas/self-image |
存图(base64) |
| POST | /codex_atlas/self-image/delete |
删图(不可逆,会记流水) |
| POST | /codex_atlas/self-image/group |
改分组 |
node tests/test_convert.mjs # 语法转换回归
node tests/test_auto_random.mjs # 前端行为:开关、只抽开着的那几个、收藏存取
python -m unittest discover -s tests -p "test_*.py"
下面两个要真浏览器(用 CHROME 环境变量指过去,不指就用本机装的 Playwright chromium):
node tests/verify-gallery.mjs # 图库独立页能不能用(不需要联网)
node tests/verify-window.mjs # 真开一次窗口:iframe 加载、图库切换、收藏板、关窗(要联网)
test_auto_random.mjs 不起浏览器:把模块里那行裸 import 换成注入的假 app,
照 ComfyUI 的顺序真跑一遍(setup() → nodeCreated() → 点按钮 → queuePrompt()),
断言的是行为而不是文本 —— 纯文本断言("文件里有某函数名")测不出少写一次 await 这类问题。
test_routes.py 里的在线取数用例不发真请求:_http_json 整个换成假的,四跳链条每跳单独验。
verify-window.mjs 起一个假后端(假的 /scripts/app.js + 真的 codex_atlas.js 与 atlas/),
然后在真浏览器里开一次窗口。这条验的是交付物本身 —— "某个按钮根本没挂上"这种问题,
读代码看不出来。它就是这么抓到一个真 bug 的:预览框的 input 回调闭包抓了参数 node,
而窗口是公开调试入口、不传 node 也能开,于是 renderPicks 一渲染就抛异常。
- 需要联网:窗口正面是线上站点,随机抽词也要取线上数据。图库那一页不用。
- 首次抽词要等几秒:那一下要串四跳网络,把发布指针、法典清单和第一部法典取回来 (实测 3~13 秒,看法典多大)。之后有半小时内存缓存,再抽就是毫秒级。 它不会卡住 ComfyUI —— 取数走独立线程(这条有回归测试钉着)。
- 跨域隔离:插件读不到站点内部状态(这是浏览器的规定,不是没做), 所以推送必须过一次剪贴板。
- API 提交不触发自动随机:那条钩子挂在浏览器前端的运行入口上。
用脚本直接 POST
/prompt提交工作流时不会换词,跑的是框里最后一次的内容。 - 站点改版可能让默认参数(
c/p)失效 —— 那几个参数是站点自己的路由, 失效了换一下默认法典即可,其它功能不受影响。
- 本仓库只做检索与索引:不携带任何词条数据与配图,那些都在线上站点那边。
- 词条内容与图片版权归各自作者所有,本仓库只做可视化整理与索引。