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
|
- **操作系统**:Windows, macOS, Linux
|
||||||
- **GPU支持**:NVIDIA CUDA (推荐)
|
- **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,
|
"examplePrompt": null,
|
||||||
"baseModel": "Flux.1 Kontext",
|
"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}"
|
"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 { app } from "../../scripts/app.js";
|
||||||
|
import "./context_menu_nest.js";
|
||||||
|
|
||||||
app.registerExtension({
|
app.registerExtension({
|
||||||
name: "ComfyUI.UniversalToolkit",
|
name: "ComfyUI.UniversalToolkit",
|
||||||
|
|||||||
Reference in New Issue
Block a user