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
+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",