chore: 提交 README、lora_info_db、web 相关变更
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
ab0415af75
commit
7a08c1558a
@@ -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,本功能在该界面下不可用。
|
||||
|
||||
## 📁 项目结构
|
||||
|
||||
```
|
||||
|
||||
@@ -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}"
|
||||
}
|
||||
}
|
||||
@@ -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,4 +1,5 @@
|
||||
import { app } from "../../scripts/app.js";
|
||||
import "./context_menu_nest.js";
|
||||
|
||||
app.registerExtension({
|
||||
name: "ComfyUI.UniversalToolkit",
|
||||
|
||||
Reference in New Issue
Block a user