Merge branch 'feat/cacheless-v2' into draft-v4

This commit is contained in:
Dr.Lt.Data 2025-04-12 20:11:33 +09:00
commit a76ef49d2d
11 changed files with 162 additions and 58 deletions

58
.github/workflows/publish-to-pypi.yml vendored Normal file
View File

@ -0,0 +1,58 @@
name: Publish to PyPI
on:
workflow_dispatch:
push:
branches:
- main
paths:
- "pyproject.toml"
jobs:
build-and-publish:
runs-on: ubuntu-latest
if: ${{ github.repository_owner == 'ltdrdata' || github.repository_owner == 'Comfy-Org' }}
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.9'
- name: Install build dependencies
run: |
python -m pip install --upgrade pip
python -m pip install build twine
- name: Get current version
id: current_version
run: |
CURRENT_VERSION=$(grep -oP 'version = "\K[^"]+' pyproject.toml)
echo "version=$CURRENT_VERSION" >> $GITHUB_OUTPUT
echo "Current version: $CURRENT_VERSION"
- name: Build package
run: python -m build
- name: Create GitHub Release
id: create_release
uses: softprops/action-gh-release@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
files: dist/*
tag_name: v${{ steps.current_version.outputs.version }}
draft: false
prerelease: false
generate_release_notes: true
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
password: ${{ secrets.PYPI_TOKEN }}
skip-existing: true
verbose: true

View File

@ -14,7 +14,7 @@ jobs:
publish-node: publish-node:
name: Publish Custom Node to registry name: Publish Custom Node to registry
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: ${{ github.repository_owner == 'ltdrdata' }} if: ${{ github.repository_owner == 'ltdrdata' || github.repository_owner == 'Comfy-Org' }}
steps: steps:
- name: Check out code - name: Check out code
uses: actions/checkout@v4 uses: actions/checkout@v4

View File

@ -1,2 +1,7 @@
include comfyui_manager/js/* include comfyui_manager/js/*
include comfyui_manager/*.json include comfyui_manager/*.json
include comfyui_manager/glob/*
include LICENSE.txt
include README.md
include requirements.txt
include pyproject.toml

View File

@ -1,6 +1,8 @@
import os import os
import logging import logging
from comfy.cli_args import args
ENABLE_LEGACY_COMFYUI_MANAGER_FRONT_DEFAULT = True # Enable legacy ComfyUI Manager frontend while new UI is in beta phase
def prestartup(): def prestartup():
from . import prestartup_script # noqa: F401 from . import prestartup_script # noqa: F401
@ -13,9 +15,13 @@ def start():
from .glob import share_3rdparty # noqa: F401 from .glob import share_3rdparty # noqa: F401
from .glob import cm_global # noqa: F401 from .glob import cm_global # noqa: F401
if os.environ.get('ENABLE_LEGACY_COMFYUI_MANAGER_FRONT', 'false') == 'true': should_show_legacy_manager_front = os.environ.get('ENABLE_LEGACY_COMFYUI_MANAGER_FRONT', 'false') == 'true' or ENABLE_LEGACY_COMFYUI_MANAGER_FRONT_DEFAULT
import nodes if not args.disable_manager and should_show_legacy_manager_front:
nodes.EXTENSION_WEB_DIRS['comfyui-manager-legacy'] = os.path.join(os.path.dirname(__file__), 'js') try:
import nodes
nodes.EXTENSION_WEB_DIRS['comfyui-manager-legacy'] = os.path.join(os.path.dirname(__file__), 'js')
except Exception as e:
print("Error enabling legacy ComfyUI Manager frontend:", e)
def should_be_disabled(fullpath:str) -> bool: def should_be_disabled(fullpath:str) -> bool:

View File

@ -0,0 +1,17 @@
import enum
class NetworkMode(enum.Enum):
PUBLIC = "public"
PRIVATE = "private"
OFFLINE = "offline"
class SecurityLevel(enum.Enum):
STRONG = "strong"
NORMAL = "normal"
NORMAL_MINUS = "normal-minus"
WEAK = "weak"
class DBMode(enum.Enum):
LOCAL = "local"
CACHE = "cache"
REMOTE = "remote"

View File

@ -38,7 +38,7 @@ from . import manager_util
from . import git_utils from . import git_utils
from . import manager_downloader from . import manager_downloader
from .node_package import InstalledNodePackage from .node_package import InstalledNodePackage
from .enums import NetworkMode, SecurityLevel, DBMode
version_code = [4, 0] version_code = [4, 0]
version_str = f"V{version_code[0]}.{version_code[1]}" + (f'.{version_code[2]}' if len(version_code) > 2 else '') version_str = f"V{version_code[0]}.{version_code[1]}" + (f'.{version_code[2]}' if len(version_code) > 2 else '')
@ -237,7 +237,7 @@ def update_user_directory(user_dir):
if not os.path.exists(manager_util.cache_dir): if not os.path.exists(manager_util.cache_dir):
os.makedirs(manager_util.cache_dir) os.makedirs(manager_util.cache_dir)
if not os.path.exists(manager_batch_history_path): if not os.path.exists(manager_batch_history_path):
os.makedirs(manager_batch_history_path) os.makedirs(manager_batch_history_path)
@ -558,7 +558,7 @@ class UnifiedManager:
ver = str(manager_util.StrictVersion(info['version'])) ver = str(manager_util.StrictVersion(info['version']))
return {'id': cnr['id'], 'cnr': cnr, 'ver': ver} return {'id': cnr['id'], 'cnr': cnr, 'ver': ver}
else: else:
return None return {'id': info['id'], 'ver': info['version']}
else: else:
return None return None
@ -734,7 +734,7 @@ class UnifiedManager:
return latest return latest
async def reload(self, cache_mode, dont_wait=True): async def reload(self, cache_mode, dont_wait=True, update_cnr_map=True):
self.custom_node_map_cache = {} self.custom_node_map_cache = {}
self.cnr_inactive_nodes = {} # node_id -> node_version -> fullpath self.cnr_inactive_nodes = {} # node_id -> node_version -> fullpath
self.nightly_inactive_nodes = {} # node_id -> fullpath self.nightly_inactive_nodes = {} # node_id -> fullpath
@ -742,17 +742,18 @@ class UnifiedManager:
self.unknown_active_nodes = {} # node_id -> repo url * fullpath self.unknown_active_nodes = {} # node_id -> repo url * fullpath
self.active_nodes = {} # node_id -> node_version * fullpath self.active_nodes = {} # node_id -> node_version * fullpath
if get_config()['network_mode'] != 'public': if get_config()['network_mode'] != 'public' or manager_util.is_manager_pip_package():
dont_wait = True dont_wait = True
# reload 'cnr_map' and 'repo_cnr_map' if update_cnr_map:
cnrs = await cnr_utils.get_cnr_data(cache_mode=cache_mode=='cache', dont_wait=dont_wait) # reload 'cnr_map' and 'repo_cnr_map'
cnrs = await cnr_utils.get_cnr_data(cache_mode=cache_mode=='cache', dont_wait=dont_wait)
for x in cnrs: for x in cnrs:
self.cnr_map[x['id']] = x self.cnr_map[x['id']] = x
if 'repository' in x: if 'repository' in x:
normalized_url = git_utils.normalize_url(x['repository']) normalized_url = git_utils.normalize_url(x['repository'])
self.repo_cnr_map[normalized_url] = x self.repo_cnr_map[normalized_url] = x
# reload node status info from custom_nodes/* # reload node status info from custom_nodes/*
for custom_nodes_path in folder_paths.get_folder_paths('custom_nodes'): for custom_nodes_path in folder_paths.get_folder_paths('custom_nodes'):
@ -1677,9 +1678,9 @@ def read_config():
'model_download_by_agent': get_bool('model_download_by_agent', False), 'model_download_by_agent': get_bool('model_download_by_agent', False),
'downgrade_blacklist': default_conf.get('downgrade_blacklist', '').lower(), 'downgrade_blacklist': default_conf.get('downgrade_blacklist', '').lower(),
'always_lazy_install': get_bool('always_lazy_install', False), 'always_lazy_install': get_bool('always_lazy_install', False),
'network_mode': default_conf.get('network_mode', 'public').lower(), 'network_mode': default_conf.get('network_mode', NetworkMode.PUBLIC.value).lower(),
'security_level': default_conf.get('security_level', 'normal').lower(), 'security_level': default_conf.get('security_level', SecurityLevel.NORMAL.value).lower(),
'db_mode': default_conf.get('db_mode', 'cache').lower(), 'db_mode': default_conf.get('db_mode', DBMode.CACHE.value).lower(),
} }
except Exception: except Exception:
@ -1700,9 +1701,9 @@ def read_config():
'model_download_by_agent': False, 'model_download_by_agent': False,
'downgrade_blacklist': '', 'downgrade_blacklist': '',
'always_lazy_install': False, 'always_lazy_install': False,
'network_mode': 'public', # public | private | offline 'network_mode': NetworkMode.OFFLINE.value,
'security_level': 'normal', # strong | normal | normal- | weak 'security_level': SecurityLevel.NORMAL.value,
'db_mode': 'cache', # local | cache | remote 'db_mode': DBMode.CACHE.value,
} }
@ -2199,7 +2200,7 @@ async def get_data_by_mode(mode, filename, channel_url=None):
cache_uri = str(manager_util.simple_hash(uri))+'_'+filename cache_uri = str(manager_util.simple_hash(uri))+'_'+filename
cache_uri = os.path.join(manager_util.cache_dir, cache_uri) cache_uri = os.path.join(manager_util.cache_dir, cache_uri)
if get_config()['network_mode'] == 'offline': if get_config()['network_mode'] == 'offline' or manager_util.is_manager_pip_package():
# offline network mode # offline network mode
if os.path.exists(cache_uri): if os.path.exists(cache_uri):
json_obj = await manager_util.get_data(cache_uri) json_obj = await manager_util.get_data(cache_uri)

View File

@ -25,7 +25,12 @@ from . import manager_downloader
logging.info(f"### Loading: ComfyUI-Manager ({core.version_str})") logging.info(f"### Loading: ComfyUI-Manager ({core.version_str})")
logging.info("[ComfyUI-Manager] network_mode: " + core.get_config()['network_mode'])
if not manager_util.is_manager_pip_package():
network_mode_description = "offline"
else:
network_mode_description = core.get_config()['network_mode']
logging.info("[ComfyUI-Manager] network_mode: " + network_mode_description)
comfy_ui_hash = "-" comfy_ui_hash = "-"
comfyui_tag = None comfyui_tag = None
@ -411,7 +416,7 @@ class TaskBatch:
item = self.tasks[self.current_index] item = self.tasks[self.current_index]
self.current_index += 1 self.current_index += 1
return item return item
def done_count(self): def done_count(self):
return len(self.nodepack_result) + len(self.model_result) return len(self.nodepack_result) + len(self.model_result)
@ -487,7 +492,7 @@ async def task_worker():
ui_id, cnr_id = item ui_id, cnr_id = item
core.unified_manager.unified_enable(cnr_id) core.unified_manager.unified_enable(cnr_id)
return 'success' return 'success'
async def do_update(item): async def do_update(item):
ui_id, node_name, node_ver = item ui_id, node_name, node_ver = item
@ -664,9 +669,9 @@ async def task_worker():
logging.info(f"\n[ComfyUI-Manager] A tasks batch(batch_id={cur_batch.batch_id}) is completed.\nstat={cur_batch.stats}") logging.info(f"\n[ComfyUI-Manager] A tasks batch(batch_id={cur_batch.batch_id}) is completed.\nstat={cur_batch.stats}")
res = {'status': 'batch-done', res = {'status': 'batch-done',
'nodepack_result': cur_batch.nodepack_result, 'nodepack_result': cur_batch.nodepack_result,
'model_result': cur_batch.model_result, 'model_result': cur_batch.model_result,
'total_count': cur_batch.total_count(), 'total_count': cur_batch.total_count(),
'done_count': cur_batch.done_count(), 'done_count': cur_batch.done_count(),
'batch_id': cur_batch.batch_id, 'batch_id': cur_batch.batch_id,
'remaining_batch_count': len(task_batch_queue) } 'remaining_batch_count': len(task_batch_queue) }
@ -773,10 +778,10 @@ async def queue_batch(request):
res = await _update_custom_node(x) res = await _update_custom_node(x)
if res.status != 200: if res.status != 200:
failed.add(x[0]) failed.add(x[0])
elif k == 'update_comfyui': elif k == 'update_comfyui':
await update_comfyui(None) await update_comfyui(None)
elif k == 'disable': elif k == 'disable':
for x in v: for x in v:
await _disable_node(x) await _disable_node(x)
@ -988,7 +993,7 @@ def populate_markdown(x):
# freeze imported version # freeze imported version
startup_time_installed_node_packs = core.get_installed_node_packs() startup_time_installed_node_packs = core.get_installed_node_packs()
@routes.get("/customnode/installed") @routes.get("/v2/customnode/installed")
async def installed_list(request): async def installed_list(request):
mode = request.query.get('mode', 'default') mode = request.query.get('mode', 'default')
@ -1294,7 +1299,7 @@ async def abort_queue(request):
if len(task_batch_queue) > 0: if len(task_batch_queue) > 0:
task_batch_queue[0].abort() task_batch_queue[0].abort()
task_batch_queue.popleft() task_batch_queue.popleft()
return web.Response(status=200) return web.Response(status=200)
@ -1396,10 +1401,10 @@ async def queue_start(request):
with task_worker_lock: with task_worker_lock:
finalize_temp_queue_batch() finalize_temp_queue_batch()
return _queue_start() return _queue_start()
def _queue_start(): def _queue_start():
global task_worker_thread global task_worker_thread
if task_worker_thread is not None and task_worker_thread.is_alive(): if task_worker_thread is not None and task_worker_thread.is_alive():
return web.Response(status=201) # already in-progress return web.Response(status=201) # already in-progress
@ -1588,7 +1593,7 @@ async def check_whitelist_for_model(item):
async def install_model(request): async def install_model(request):
json_data = await request.json() json_data = await request.json()
return await _install_model(json_data) return await _install_model(json_data)
async def _install_model(json_data): async def _install_model(json_data):
if not is_allowed_security_level('middle'): if not is_allowed_security_level('middle'):
@ -1895,7 +1900,7 @@ async def default_cache_update():
logging.error(f"[ComfyUI-Manager] Failed to perform initial fetching '{filename}': {e}") logging.error(f"[ComfyUI-Manager] Failed to perform initial fetching '{filename}': {e}")
traceback.print_exc() traceback.print_exc()
if core.get_config()['network_mode'] != 'offline': if core.get_config()['network_mode'] != 'offline' and not manager_util.is_manager_pip_package():
a = get_cache("custom-node-list.json") a = get_cache("custom-node-list.json")
b = get_cache("extension-node-map.json") b = get_cache("extension-node-map.json")
c = get_cache("model-list.json") c = get_cache("model-list.json")
@ -1910,6 +1915,8 @@ async def default_cache_update():
# load at least once # load at least once
await core.unified_manager.reload('remote', dont_wait=False) await core.unified_manager.reload('remote', dont_wait=False)
await core.unified_manager.get_custom_nodes(channel_url, 'remote') await core.unified_manager.get_custom_nodes(channel_url, 'remote')
else:
await core.unified_manager.reload('remote', dont_wait=False, update_cnr_map=False)
logging.info("[ComfyUI-Manager] All startup tasks have been completed.") logging.info("[ComfyUI-Manager] All startup tasks have been completed.")

View File

@ -25,6 +25,8 @@ cache_dir = os.path.join(comfyui_manager_path, '.cache') # This path is also up
use_uv = False use_uv = False
def is_manager_pip_package():
return not os.path.exists(os.path.join(comfyui_manager_path, '..', 'custom_nodes'))
def add_python_path_to_env(): def add_python_path_to_env():
if platform.system() != "Windows": if platform.system() != "Windows":

View File

@ -189,8 +189,7 @@ docStyle.innerHTML = `
} }
`; `;
function is_legacy_front() { function isBeforeFrontendVersion(compareVersion) {
let compareVersion = '1.2.49';
try { try {
const frontendVersion = window['__COMFYUI_FRONTEND_VERSION__']; const frontendVersion = window['__COMFYUI_FRONTEND_VERSION__'];
if (typeof frontendVersion !== 'string') { if (typeof frontendVersion !== 'string') {
@ -223,6 +222,9 @@ function is_legacy_front() {
} }
} }
const is_legacy_front = () => isBeforeFrontendVersion('1.2.49');
const isNotNewManagerUI = () => isBeforeFrontendVersion('1.16.4');
document.head.appendChild(docStyle); document.head.appendChild(docStyle);
var update_comfyui_button = null; var update_comfyui_button = null;
@ -476,9 +478,9 @@ async function updateComfyUI() {
// set_inprogress_mode(); // set_inprogress_mode();
showTerminal(); showTerminal();
batch_id = generateUUID(); batch_id = generateUUID();
let batch = {}; let batch = {};
batch['batch_id'] = batch_id; batch['batch_id'] = batch_id;
batch['update_comfyui'] = true; batch['update_comfyui'] = true;
@ -671,7 +673,7 @@ async function onQueueStatus(event) {
if(batch_id != event.detail.batch_id) { if(batch_id != event.detail.batch_id) {
return; return;
} }
let success_list = []; let success_list = [];
let failed_list = []; let failed_list = [];
let comfyui_state = null; let comfyui_state = null;
@ -778,7 +780,7 @@ async function updateAll(update_comfyui) {
showTerminal(); showTerminal();
batch_id = generateUUID(); batch_id = generateUUID();
let batch = {}; let batch = {};
if(update_comfyui) { if(update_comfyui) {
update_all_button.innerText = "Updating ComfyUI..."; update_all_button.innerText = "Updating ComfyUI...";
@ -1516,7 +1518,10 @@ app.registerExtension({
}).element }).element
); );
app.menu?.settingsGroup.element.before(cmGroup.element); const shouldShowLegacyMenuItems = isNotNewManagerUI();
if (shouldShowLegacyMenuItems) {
app.menu?.settingsGroup.element.before(cmGroup.element);
}
} }
catch(exception) { catch(exception) {
console.log('ComfyUI is outdated. New style menu based features are disabled.'); console.log('ComfyUI is outdated. New style menu based features are disabled.');

View File

@ -1533,7 +1533,7 @@ export class CustomNodesManager {
else { else {
this.batch_id = generateUUID(); this.batch_id = generateUUID();
batch['batch_id'] = this.batch_id; batch['batch_id'] = this.batch_id;
const res = await api.fetchApi(`/v2/manager/queue/batch`, { const res = await api.fetchApi(`/v2/manager/queue/batch`, {
method: 'POST', method: 'POST',
body: JSON.stringify(batch) body: JSON.stringify(batch)
@ -1548,7 +1548,7 @@ export class CustomNodesManager {
errorMsg = `[FAIL] ${item.title}`; errorMsg = `[FAIL] ${item.title}`;
} }
} }
this.showStop(); this.showStop();
showTerminal(); showTerminal();
} }
@ -1556,6 +1556,9 @@ export class CustomNodesManager {
async onQueueStatus(event) { async onQueueStatus(event) {
let self = CustomNodesManager.instance; let self = CustomNodesManager.instance;
// If legacy manager front is not open, return early (using new manager front)
if (self.element?.style.display === 'none') return
if(event.detail.status == 'in_progress' && event.detail.ui_target == 'nodepack_manager') { if(event.detail.status == 'in_progress' && event.detail.ui_target == 'nodepack_manager') {
const hash = event.detail.target; const hash = event.detail.target;

View File

@ -12,16 +12,16 @@ readme = "README.md"
keywords = ["comfyui", "comfyui-manager"] keywords = ["comfyui", "comfyui-manager"]
maintainers = [ maintainers = [
{ name = "Dr.Lt.Data", email = "dr.lt.data@gmail.com" }, { name = "Dr.Lt.Data", email = "dr.lt.data@gmail.com" },
{ name = "Yoland Yan", email = "yoland@drip.art" }, { name = "Yoland Yan", email = "yoland@drip.art" },
{ name = "James Kwon", email = "hongilkwon316@gmail.com" }, { name = "James Kwon", email = "hongilkwon316@gmail.com" },
{ name = "Robin Huang", email = "robin@drip.art" }, { name = "Robin Huang", email = "robin@drip.art" },
] ]
classifiers = [ classifiers = [
"Development Status :: 4 - Beta", "Development Status :: 4 - Beta",
"Intended Audience :: Developers", "Intended Audience :: Developers",
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)", "License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
] ]
dependencies = [ dependencies = [
@ -54,9 +54,9 @@ target-version = "py39"
[tool.ruff.lint] [tool.ruff.lint]
select = [ select = [
"E4", # default "E4", # default
"E7", # default "E7", # default
"E9", # default "E9", # default
"F", # default "F", # default
"I", # isort-like behavior (import statement sorting) "I", # isort-like behavior (import statement sorting)
] ]