diff --git a/README.en.md b/README.en.md index 0ccab0d..abb9559 100644 --- a/README.en.md +++ b/README.en.md @@ -33,8 +33,7 @@ **v1.1.2 (2024/3/25)** -PS: Please update [ComfyUI_IPAdapter_plus](https://github.com/cubiq/ComfyUI_IPAdapter_plus) to v2, and moved v1 models to **ComfyUI\models\ipadapter** (Otherwise, the latest model is automatically downloaded from Huggingface) -
+- Added **Enable ContextMenu Auto Nest Subdirectories** The setting item is enabled by default, and it can be classified into subdirectories, checkpoints and loras previews - Added `easy sv3dLoader` - Added `easy dynamiCrafterLoader` - Added `easy ipadapterApply` diff --git a/README.md b/README.md index b5a31e4..a72b2e2 100644 --- a/README.md +++ b/README.md @@ -37,8 +37,7 @@ **v1.1.2 (2024/3/25)** -PS: 请更新至最新版v2的 [ComfyUI_IPAdapter_plus](https://github.com/cubiq/ComfyUI_IPAdapter_plus), 并移动v1版本模型文件至 ComfyUI\models\ipadapter (否则会自动从huggingface下载最新模型) -
+- 增加 **启用上下文菜单自动嵌套子目录** 设置项,默认为启用状态,可分类子目录及checkpoints、loras预览图 - 增加 `easy sv3dLoader` - 增加 `easy dynamiCrafterLoader` - 增加 `easy ipadapterApply` diff --git a/py/api.py b/py/api.py index d103511..e3ff12d 100644 --- a/py/api.py +++ b/py/api.py @@ -3,6 +3,7 @@ import os import torch import sys import json +import folder_paths from server import PromptServer from .config import RESOURCES_DIR, FOOOCUS_STYLES_DIR, FOOOCUS_STYLES_SAMPLES from .easyNodes import easyCache @@ -111,6 +112,22 @@ async def getModelsList(request): else: return web.Response(status=400) +# get models thumbnails +@PromptServer.instance.routes.get("/easyuse/models/thumbnail") +async def getModelsThumbnail(request): + checkpoints = folder_paths.get_filename_list("checkpoints_thumb") + loras = folder_paths.get_filename_list("loras_thumb") + checkpoints_full = [] + loras_full = [] + for index, i in enumerate(checkpoints): + full_path = folder_paths.get_full_path('checkpoints_thumb', str(i)) + if full_path: + checkpoints_full.append(full_path) + for index, i in enumerate(loras): + full_path = folder_paths.get_full_path('loras_thumb', str(i)) + if full_path: + loras_full.append(full_path) + return web.json_response(checkpoints_full + loras_full) NODE_CLASS_MAPPINGS = {} NODE_DISPLAY_NAME_MAPPINGS = {} \ No newline at end of file diff --git a/py/easyNodes.py b/py/easyNodes.py index 37bfb2d..0820363 100644 --- a/py/easyNodes.py +++ b/py/easyNodes.py @@ -30,6 +30,8 @@ from .libs.easing import EasingBase sampler = easySampler() easyCache = easyLoader() +image_suffixs = set([".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp", ".tiff", ".svg", ".ico", ".apng", ".tif", ".hdr", ".exr"]) + model_path = folder_paths.models_dir add_folder_path_and_extensions("ultralytics_bbox", [os.path.join(model_path, "ultralytics", "bbox")], folder_paths.supported_pt_extensions) add_folder_path_and_extensions("ultralytics_segm", [os.path.join(model_path, "ultralytics", "segm")], folder_paths.supported_pt_extensions) @@ -45,6 +47,9 @@ add_folder_path_and_extensions("rembg", [os.path.join(model_path, "rembg")], fol add_folder_path_and_extensions("ipadapter", [os.path.join(model_path, "ipadapter")], folder_paths.supported_pt_extensions) add_folder_path_and_extensions("dynamicrafter_models", [os.path.join(model_path, "dynamicrafter_models")], folder_paths.supported_pt_extensions) +add_folder_path_and_extensions("checkpoints_thumb", [os.path.join(model_path, "checkpoints")], image_suffixs) +add_folder_path_and_extensions("loras_thumb", [os.path.join(model_path, "loras")], image_suffixs) + # ---------------------------------------------------------------提示词 开始----------------------------------------------------------------------# # 正面提示词 diff --git a/web/css/contextmenu.css b/web/css/contextmenu.css new file mode 100644 index 0000000..9fc0e95 --- /dev/null +++ b/web/css/contextmenu.css @@ -0,0 +1,20 @@ +.easyuse-model{ + position:relative; +} +.easyuse-model:hover img{ + display: block; + opacity: 1; +} +.easyuse-model img{ + position: absolute; + z-index:1; + right:-155px; + top:0; + width:150px; + height:auto; + display: none; + filter:brightness(70%); + -webkit-filter: brightness(70%); + opacity: 0; + transition:all 0.5s ease-in-out; +} \ No newline at end of file diff --git a/web/css/index.css b/web/css/index.css index fd11b8e..2069ddf 100644 --- a/web/css/index.css +++ b/web/css/index.css @@ -1,4 +1,5 @@ @import "theme.css"; @import "dropdown.css"; @import "selector.css"; -@import "groupmap.css"; \ No newline at end of file +@import "groupmap.css"; +@import "contextmenu.css"; \ No newline at end of file diff --git a/web/js/common/i18n.js b/web/js/common/i18n.js index 1673a07..ce26b0d 100644 --- a/web/js/common/i18n.js +++ b/web/js/common/i18n.js @@ -10,8 +10,9 @@ const zhCN = { "Auto Sorting": "自动排序", "Toggle `Show/Hide` can set mode of group, LongPress can set group nodes to never": "点击`启用中/已忽略`可设置组模式, 长按可停用该组节点", // Quick - "Enable ALT+1~9 to paste nodes from nodes template (ComfyUI-Easy-Use)": "启用ALT1~9从节点模板粘贴到工作流(ComfyUI-Easy-Use)", - "Enable process bar in queue button (ComfyUI-Easy-Use)": "启用提示词队列进度显示条(ComfyUI-Easy-Use)" + "Enable ALT+1~9 to paste nodes from nodes template (ComfyUI-Easy-Use)": "启用ALT1~9从节点模板粘贴到工作流 (ComfyUI-Easy-Use)", + "Enable process bar in queue button (ComfyUI-Easy-Use)": "启用提示词队列进度显示条 (ComfyUI-Easy-Use)", + "Enable ContextMenu Auto Nest Subdirectories (ComfyUI-Easy-Use)": "启用上下文菜单自动嵌套子目录 (ComfyUI-Easy-Use)" } export const $t = (key) => { diff --git a/web/js/common/utils.js b/web/js/common/utils.js index 4ba496e..56ddf0e 100644 --- a/web/js/common/utils.js +++ b/web/js/common/utils.js @@ -38,4 +38,11 @@ export function deepEqual(obj1, obj2) { export function getLocale(){ const locale = localStorage['AGL.Locale'] || localStorage['Comfy.Settings.AGL.Locale'] || 'en-US' return locale +} + +export function spliceExtension(fileName){ + return fileName.substring(0,fileName.lastIndexOf('.')) +} +export function getExtension(fileName){ + return fileName.substring(fileName.lastIndexOf('.') + 1) } \ No newline at end of file diff --git a/web/js/easy/easyContextMenu.js b/web/js/easy/easyContextMenu.js index 2e07938..511405f 100644 --- a/web/js/easy/easyContextMenu.js +++ b/web/js/easy/easyContextMenu.js @@ -1,116 +1,128 @@ -// import {app} from "/scripts/app.js"; -// import {api} from "/scripts/api.js"; -// import {$el} from "/scripts/ui.js"; -// -// let script=document.createElement("script"); -// script.type="text/JavaScript"; -// script.innerHTML = ` -// function displayImage(e) { -// console.log(e) -// } -// ` -// document.getElementsByTagName('head')[0].appendChild(script); -// -// const Loaders = ['easy fullLoader','easy a1111Loader','easy comfyLoader'] -// app.registerExtension({ -// name:"comfy.easyUse.contextMenu", -// async setup(){ -// const existingContextMenu = LiteGraph.ContextMenu; -// LiteGraph.ContextMenu = function(values,options){ -// const threshold = 20; -// const enabled = true; -// if(!enabled || (values?.length || 0) <= threshold || !(options?.callback) || values.some(i => typeof i !== 'string')){ -// if(enabled){ -// // console.log('Skipping context menu auto nesting for incompatible menu.'); -// } -// 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.values(folders).length; -// if(foldersCount > 0){ -// const oldcallback = options.callback; -// options.callback = null; -// const newCallback = (item,options) => { -// if(['None','无','無','なし'].includes(item.content)) oldcallback('None',options) -// else oldcallback(originalValues.find(i => i.endsWith(item.content),options)); -// }; -// const addContent = (content, folderName='') => { -// const name = folderName ? `${folderName}/${content}` : content; -// // 获取图像 -// // const imgRes = api.fetchApi(`/easyuse/model/thumbnail?name=${name}`) -// // if (imgRes.status === 200) { -// // let data = await imgRes.json(); -// // console.log(data) -// // } -// -// const newContent = $el( -// "span.easyuse-model", -// { -// $: (el) => { -// el.onmousemove = (e) => { -// console.log(1) -// }; -// el.onmouseout = () => { -// console.log(2) -// // hiddenImage() -// }; -// el.onmouseover = (e) => { -// console.log(1) -// // displayImage(el.dataset.imgName, styleName) -// }; -// }, -// },content) -// -// -// return { -// content, -// title:newContent.outerHTML, -// callback: newCallback -// } -// } -// const newValues = []; -// for(const [folderName,folder] of Object.entries(folders)){ -// newValues.push({ -// content:folderName, -// has_submenu:true, -// callback:() => {}, -// submenu:{ -// options:folder.map(f => addContent(f,folderName)), -// } -// }); -// } -// newValues.push(...folderless.map(f => ({ -// content:f, -// callback:newCallback -// }))); -// if(specialOps.length > 0) -// newValues.push(...specialOps.map(f => ({ -// content:f, -// callback:newCallback -// }))); -// return existingContextMenu.call(this,newValues,options); -// } -// return existingContextMenu.apply(this,[...arguments]); -// } -// LiteGraph.ContextMenu.prototype = existingContextMenu.prototype; -// }, -// -// }) -// +import {app} from "/scripts/app.js"; +import {api} from "/scripts/api.js"; +import {$el} from "/scripts/ui.js"; +import {$t} from "../common/i18n.js"; +import {getExtension, spliceExtension} from '../common/utils.js' + +const setting_id = "Comfy.EasyUse.MenuNestSub" +let enableMenuNestSub = true +let thumbnails = [] + +export function addMenuNestSubSetting(app) { + app.ui.settings.addSetting({ + id: setting_id, + name: $t("Enable ContextMenu Auto Nest Subdirectories (ComfyUI-Easy-Use)"), + type: "boolean", + defaultValue: enableMenuNestSub, + onChange(value) { + enableMenuNestSub = !!value; + }, + }); +} + +const getEnableMenuNestSub = _ => app.ui.settings.getSettingValue(setting_id, true) + +const Loaders = ['easy fullLoader','easy a1111Loader','easy comfyLoader'] +app.registerExtension({ + name:"comfy.easyUse.contextMenu", + async setup(app){ + addMenuNestSubSetting(app) + // 获取所有模型图像 + const imgRes = await api.fetchApi(`/easyuse/models/thumbnail`) + if (imgRes.status === 200) { + let data = await imgRes.json(); + thumbnails = data + } + const existingContextMenu = LiteGraph.ContextMenu; + LiteGraph.ContextMenu = function(values,options){ + const threshold = 15; + const enabled = getEnableMenuNestSub(); + if(!enabled || (values?.length || 0) <= threshold || !(options?.callback) || values.some(i => typeof i !== 'string')){ + if(enabled){ + // console.log('Skipping context menu auto nesting for incompatible menu.'); + } + 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.values(folders).length; + if(foldersCount > 0){ + const oldcallback = options.callback; + options.callback = null; + const newCallback = (item,options) => { + if(['None','无','無','なし'].includes(item.content)) oldcallback('None',options) + else oldcallback(originalValues.find(i => i.endsWith(item.content),options)); + }; + const addContent = (content, folderName='') => { + const name = folderName ? folderName + '\\' + spliceExtension(content) : spliceExtension(content); + const ext = getExtension(content) + const time = new Date().getTime() + let thumbnail = '' + if(['ckpt', 'pt', 'bin', 'pth', 'safetensors'].includes(ext)){ + for(let i=0;i {}, + submenu:{ + options:folder.map(f => addContent(f,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); + } + return existingContextMenu.apply(this,[...arguments]); + } + LiteGraph.ContextMenu.prototype = existingContextMenu.prototype; + }, + +}) +