chore: 提交 README、lora_info_db、web 相关变更

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Cyber Dick Lang
2026-02-14 20:26:04 +08:00
co-authored by Cursor
parent ab0415af75
commit 7a08c1558a
4 changed files with 165 additions and 0 deletions
+13
View File
@@ -375,6 +375,19 @@ AudioCropProcess_UTK
- **操作系统**:Windows, macOS, Linux
- **GPU支持**:NVIDIA CUDA (推荐)
## 界面设置与 Nodes 2.0 说明
### 启用上下文菜单自动嵌套子目录
在 ComfyUI 设置中可开启「启用上下文菜单自动嵌套子目录」。开启后,在模型/文件选择等带路径的 combo 下拉中,选项会按子目录折叠为层级菜单,便于在子目录较多的场景下选择。
**关于 Nodes 2.0**:该功能通过修补 LiteGraph 的 `ContextMenu` 实现。若使用 **Nodes 2.0**(官方 Vue 前端),combo 下拉可能不再经过 `ContextMenu`,导致本功能不生效。此时可:
- 在 ComfyUI 菜单中切换回 **LiteGraph Canvas**(关闭 Nodes 2.0)以使用本功能,或
- 关注 ComfyUI 后续是否提供 combo 选项转换的扩展 API。
验证方式:开启本设置后,点击任意模型/文件类节点的下拉,若浏览器控制台出现 `UniversalToolkit contextMenu nest patch applied` 日志,说明补丁已生效;若无该日志,则当前前端未使用 ContextMenu,本功能在该界面下不可用。
## 📁 项目结构
```
+7
View File
@@ -66,5 +66,12 @@
"examplePrompt": null,
"baseModel": "Flux.1 Kontext",
"metaInfo": "{\n \"name\": \"HDR360\",\n \"sshs_legacy_hash\": \"6bb22ed6\",\n \"format\": \"pt\",\n \"training_info\": \"{\\\"step\\\": 5500, \\\"epoch\\\": 56}\",\n \"sshs_model_hash\": \"d904a130eb54877361ec4cd9ebd82c3f937c1128a56ea68115c5db0ba1291638\",\n \"version\": \"1.0\",\n \"ss_base_model_version\": \"sd_1.5\",\n \"ss_output_name\": \"HDR360\",\n \"software\": \"{\\\"name\\\": \\\"ai-toolkit\\\", \\\"repo\\\": \\\"https://github.com/ostris/ai-toolkit\\\", \\\"version\\\": \\\"0.3.3\\\"}\"\n}"
},
"zimage\\ysy zbase alpha\\ysy zbase alpha_000003000.safetensors": {
"output": "",
"trainedWords": "",
"examplePrompt": null,
"baseModel": "",
"metaInfo": "{\n \"name\": \"ysy zbase alpha\",\n \"training_info\": \"{\\\"step\\\": 3000, \\\"epoch\\\": 40}\",\n \"version\": \"1.0\",\n \"ss_tag_frequency\": \"{\\\"1_Tagaysy\\\": {\\\"Tagaysy\\\": 1}}\",\n \"format\": \"pt\",\n \"sshs_legacy_hash\": \"7d24986a\",\n \"ss_base_model_version\": \"zimage\",\n \"ss_output_name\": \"ysy zbase alpha\",\n \"sshs_model_hash\": \"17b842123ec61768345a3e0d7a8d84625ec9f9a7f0a4d97336c4f72f84cd0228\",\n \"software\": \"{\\\"name\\\": \\\"ai-toolkit\\\", \\\"repo\\\": \\\"https://github.com/ostris/ai-toolkit\\\", \\\"version\\\": \\\"0.7.22\\\"}\"\n}"
}
}
+144
View File
@@ -0,0 +1,144 @@
/**
* 启用上下文菜单自动嵌套子目录
* 参考 ComfyUI-Easy-Use 的 easyContextMenu.js 实现,仅移植路径嵌套逻辑,不含缩略图。
*/
import { app } from "../../scripts/app.js";
const SETTING_ID = "UniversalToolkit.ContextMenuNestSub";
const THRESHOLD = 10;
app.registerExtension({
name: "UniversalToolkit.contextMenuNest",
async setup(app) {
app.ui.settings.addSetting({
id: SETTING_ID,
name: "启用上下文菜单自动嵌套子目录(不适用于 Nodes 2.0)",
type: "boolean",
defaultValue: true,
tooltip: "仅在使用 LiteGraph Canvas 时生效;Nodes 2.0 下 combo 下拉不经过 ContextMenu,本功能不可用。",
});
const getEnabled = () => !!app.ui.settings.getSettingValue(SETTING_ID, true);
const existingContextMenu = LiteGraph.ContextMenu;
LiteGraph.ContextMenu = function (values, options) {
// 方案一:验证 Nodes 2.0 下是否仍会调用此处(点击 combo 下拉时若出现 log 则补丁生效)
console.log("UniversalToolkit contextMenu nest patch applied", values?.length);
const enabled = getEnabled();
if (
!enabled ||
(values?.length || 0) <= THRESHOLD ||
!(options?.callback) ||
values.some((i) => typeof i !== "string")
) {
return existingContextMenu.apply(this, [...arguments]);
}
const compatValues = values;
const originalValues = [...compatValues];
const folders = {};
const specialOps = [];
const folderless = [];
for (const value of compatValues) {
const splitBy = value.indexOf("/") > -1 ? "/" : "\\";
const valueSplit = value.split(splitBy);
if (valueSplit.length > 1) {
const key = valueSplit.shift();
folders[key] = folders[key] || [];
folders[key].push(valueSplit.join(splitBy));
} else if (value === "CHOOSE" || value.startsWith("DISABLE ")) {
specialOps.push(value);
} else {
folderless.push(value);
}
}
const foldersCount = Object.keys(folders).length;
if (foldersCount === 0) {
return existingContextMenu.apply(this, [...arguments]);
}
const oldCallback = options.callback;
options.callback = null;
const newCallback = (item, opts) => {
if (["None", "无", "無", "なし"].includes(item.content)) {
oldCallback("None", opts);
} else {
const full = originalValues.find((i) => i.endsWith(item.content));
oldCallback(full != null ? full : item.content, opts);
}
};
const addContent = (content) => ({
content,
callback: newCallback,
});
const add_sub_folder = (folder, folderName) => {
const subs = [];
const less = [];
const b = folder.map((name) => {
const _folders = {};
const splitBy = name.indexOf("/") > -1 ? "/" : "\\";
const valueSplit = name.split(splitBy);
if (valueSplit.length > 1) {
const key = valueSplit.shift();
_folders[key] = _folders[key] || [];
_folders[key].push(valueSplit.join(splitBy));
}
const subKeys = Object.keys(_folders);
if (subKeys.length > 0) {
const key = subKeys[0];
const val = _folders[key][0];
if (key && val) subs.push({ key, value: val });
else less.push(addContent(name));
} else {
less.push(addContent(name));
}
return addContent(name);
});
if (subs.length > 0) {
const subsObj = {};
subs.forEach((item) => {
subsObj[item.key] = subsObj[item.key] || [];
subsObj[item.key].push(item.value);
});
return [
...Object.entries(subsObj).map((f) => ({
content: f[0],
has_submenu: true,
callback: () => {},
submenu: {
options: add_sub_folder(f[1], f[0]),
},
})),
...less,
];
}
return b;
};
const newValues = [];
for (const [folderName, folder] of Object.entries(folders)) {
newValues.push({
content: folderName,
has_submenu: true,
callback: () => {},
submenu: {
options: add_sub_folder(folder, folderName),
},
});
}
newValues.push(...folderless.map((f) => addContent(f)));
if (specialOps.length > 0) {
newValues.push(...specialOps.map((f) => addContent(f)));
}
return existingContextMenu.call(this, newValues, options);
};
LiteGraph.ContextMenu.prototype = existingContextMenu.prototype;
},
});
+1
View File
@@ -1,4 +1,5 @@
import { app } from "../../scripts/app.js";
import "./context_menu_nest.js";
app.registerExtension({
name: "ComfyUI.UniversalToolkit",