diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml new file mode 100644 index 00000000..10b97e59 --- /dev/null +++ b/.github/workflows/publish-to-pypi.yml @@ -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 \ No newline at end of file diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 463ecca9..e47764af 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -14,7 +14,7 @@ jobs: publish-node: name: Publish Custom Node to registry runs-on: ubuntu-latest - if: ${{ github.repository_owner == 'ltdrdata' }} + if: ${{ github.repository_owner == 'ltdrdata' || github.repository_owner == 'Comfy-Org' }} steps: - name: Check out code uses: actions/checkout@v4 diff --git a/MANIFEST.in b/MANIFEST.in index dd70bd12..8af7bc57 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,2 +1,7 @@ include comfyui_manager/js/* -include comfyui_manager/*.json \ No newline at end of file +include comfyui_manager/*.json +include comfyui_manager/glob/* +include LICENSE.txt +include README.md +include requirements.txt +include pyproject.toml \ No newline at end of file diff --git a/comfyui_manager/__init__.py b/comfyui_manager/__init__.py index 1dd0791e..1c9bf30a 100644 --- a/comfyui_manager/__init__.py +++ b/comfyui_manager/__init__.py @@ -1,6 +1,8 @@ import os 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(): from . import prestartup_script # noqa: F401 @@ -13,9 +15,13 @@ def start(): from .glob import share_3rdparty # noqa: F401 from .glob import cm_global # noqa: F401 - if os.environ.get('ENABLE_LEGACY_COMFYUI_MANAGER_FRONT', 'false') == 'true': - import nodes - nodes.EXTENSION_WEB_DIRS['comfyui-manager-legacy'] = os.path.join(os.path.dirname(__file__), 'js') + should_show_legacy_manager_front = os.environ.get('ENABLE_LEGACY_COMFYUI_MANAGER_FRONT', 'false') == 'true' or ENABLE_LEGACY_COMFYUI_MANAGER_FRONT_DEFAULT + if not args.disable_manager and should_show_legacy_manager_front: + 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: diff --git a/comfyui_manager/glob/enums.py b/comfyui_manager/glob/enums.py new file mode 100644 index 00000000..bd4a57f1 --- /dev/null +++ b/comfyui_manager/glob/enums.py @@ -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" \ No newline at end of file diff --git a/comfyui_manager/glob/manager_core.py b/comfyui_manager/glob/manager_core.py index 93d7be18..cf1c7027 100644 --- a/comfyui_manager/glob/manager_core.py +++ b/comfyui_manager/glob/manager_core.py @@ -38,7 +38,7 @@ from . import manager_util from . import git_utils from . import manager_downloader from .node_package import InstalledNodePackage - +from .enums import NetworkMode, SecurityLevel, DBMode version_code = [4, 0] 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): os.makedirs(manager_util.cache_dir) - + if not os.path.exists(manager_batch_history_path): os.makedirs(manager_batch_history_path) @@ -558,7 +558,7 @@ class UnifiedManager: ver = str(manager_util.StrictVersion(info['version'])) return {'id': cnr['id'], 'cnr': cnr, 'ver': ver} else: - return None + return {'id': info['id'], 'ver': info['version']} else: return None @@ -734,7 +734,7 @@ class UnifiedManager: 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.cnr_inactive_nodes = {} # node_id -> node_version -> fullpath self.nightly_inactive_nodes = {} # node_id -> fullpath @@ -742,17 +742,18 @@ class UnifiedManager: self.unknown_active_nodes = {} # node_id -> repo url * 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 - # reload 'cnr_map' and 'repo_cnr_map' - cnrs = await cnr_utils.get_cnr_data(cache_mode=cache_mode=='cache', dont_wait=dont_wait) + if update_cnr_map: + # 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: - self.cnr_map[x['id']] = x - if 'repository' in x: - normalized_url = git_utils.normalize_url(x['repository']) - self.repo_cnr_map[normalized_url] = x + for x in cnrs: + self.cnr_map[x['id']] = x + if 'repository' in x: + normalized_url = git_utils.normalize_url(x['repository']) + self.repo_cnr_map[normalized_url] = x # reload node status info from 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), 'downgrade_blacklist': default_conf.get('downgrade_blacklist', '').lower(), 'always_lazy_install': get_bool('always_lazy_install', False), - 'network_mode': default_conf.get('network_mode', 'public').lower(), - 'security_level': default_conf.get('security_level', 'normal').lower(), - 'db_mode': default_conf.get('db_mode', 'cache').lower(), + 'network_mode': default_conf.get('network_mode', NetworkMode.PUBLIC.value).lower(), + 'security_level': default_conf.get('security_level', SecurityLevel.NORMAL.value).lower(), + 'db_mode': default_conf.get('db_mode', DBMode.CACHE.value).lower(), } except Exception: @@ -1700,9 +1701,9 @@ def read_config(): 'model_download_by_agent': False, 'downgrade_blacklist': '', 'always_lazy_install': False, - 'network_mode': 'public', # public | private | offline - 'security_level': 'normal', # strong | normal | normal- | weak - 'db_mode': 'cache', # local | cache | remote + 'network_mode': NetworkMode.OFFLINE.value, + 'security_level': SecurityLevel.NORMAL.value, + '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 = 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 if os.path.exists(cache_uri): json_obj = await manager_util.get_data(cache_uri) diff --git a/comfyui_manager/glob/manager_server.py b/comfyui_manager/glob/manager_server.py index f05fa7e6..870445f8 100644 --- a/comfyui_manager/glob/manager_server.py +++ b/comfyui_manager/glob/manager_server.py @@ -25,7 +25,12 @@ from . import manager_downloader 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 = "-" comfyui_tag = None @@ -411,7 +416,7 @@ class TaskBatch: item = self.tasks[self.current_index] self.current_index += 1 return item - + def done_count(self): return len(self.nodepack_result) + len(self.model_result) @@ -487,7 +492,7 @@ async def task_worker(): ui_id, cnr_id = item core.unified_manager.unified_enable(cnr_id) return 'success' - + async def do_update(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}") res = {'status': 'batch-done', - 'nodepack_result': cur_batch.nodepack_result, + 'nodepack_result': cur_batch.nodepack_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(), 'batch_id': cur_batch.batch_id, 'remaining_batch_count': len(task_batch_queue) } @@ -773,10 +778,10 @@ async def queue_batch(request): res = await _update_custom_node(x) if res.status != 200: failed.add(x[0]) - + elif k == 'update_comfyui': await update_comfyui(None) - + elif k == 'disable': for x in v: await _disable_node(x) @@ -988,7 +993,7 @@ def populate_markdown(x): # freeze imported version startup_time_installed_node_packs = core.get_installed_node_packs() -@routes.get("/customnode/installed") +@routes.get("/v2/customnode/installed") async def installed_list(request): mode = request.query.get('mode', 'default') @@ -1294,7 +1299,7 @@ async def abort_queue(request): if len(task_batch_queue) > 0: task_batch_queue[0].abort() task_batch_queue.popleft() - + return web.Response(status=200) @@ -1396,10 +1401,10 @@ async def queue_start(request): with task_worker_lock: finalize_temp_queue_batch() return _queue_start() - + def _queue_start(): global task_worker_thread - + if task_worker_thread is not None and task_worker_thread.is_alive(): return web.Response(status=201) # already in-progress @@ -1588,7 +1593,7 @@ async def check_whitelist_for_model(item): async def install_model(request): json_data = await request.json() return await _install_model(json_data) - + async def _install_model(json_data): 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}") 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") b = get_cache("extension-node-map.json") c = get_cache("model-list.json") @@ -1910,6 +1915,8 @@ async def default_cache_update(): # load at least once await core.unified_manager.reload('remote', dont_wait=False) 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.") diff --git a/comfyui_manager/glob/manager_util.py b/comfyui_manager/glob/manager_util.py index 2fac5156..ae63bdd8 100644 --- a/comfyui_manager/glob/manager_util.py +++ b/comfyui_manager/glob/manager_util.py @@ -25,6 +25,8 @@ cache_dir = os.path.join(comfyui_manager_path, '.cache') # This path is also up 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(): if platform.system() != "Windows": diff --git a/comfyui_manager/js/comfyui-manager.js b/comfyui_manager/js/comfyui-manager.js index ed2192be..41400ad6 100644 --- a/comfyui_manager/js/comfyui-manager.js +++ b/comfyui_manager/js/comfyui-manager.js @@ -189,8 +189,7 @@ docStyle.innerHTML = ` } `; -function is_legacy_front() { - let compareVersion = '1.2.49'; +function isBeforeFrontendVersion(compareVersion) { try { const frontendVersion = window['__COMFYUI_FRONTEND_VERSION__']; 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); var update_comfyui_button = null; @@ -476,9 +478,9 @@ async function updateComfyUI() { // set_inprogress_mode(); showTerminal(); - + batch_id = generateUUID(); - + let batch = {}; batch['batch_id'] = batch_id; batch['update_comfyui'] = true; @@ -671,7 +673,7 @@ async function onQueueStatus(event) { if(batch_id != event.detail.batch_id) { return; } - + let success_list = []; let failed_list = []; let comfyui_state = null; @@ -778,7 +780,7 @@ async function updateAll(update_comfyui) { showTerminal(); batch_id = generateUUID(); - + let batch = {}; if(update_comfyui) { update_all_button.innerText = "Updating ComfyUI..."; @@ -1516,7 +1518,10 @@ app.registerExtension({ }).element ); - app.menu?.settingsGroup.element.before(cmGroup.element); + const shouldShowLegacyMenuItems = isNotNewManagerUI(); + if (shouldShowLegacyMenuItems) { + app.menu?.settingsGroup.element.before(cmGroup.element); + } } catch(exception) { console.log('ComfyUI is outdated. New style menu based features are disabled.'); diff --git a/comfyui_manager/js/custom-nodes-manager.js b/comfyui_manager/js/custom-nodes-manager.js index e8c714f4..f96ea340 100644 --- a/comfyui_manager/js/custom-nodes-manager.js +++ b/comfyui_manager/js/custom-nodes-manager.js @@ -1533,7 +1533,7 @@ export class CustomNodesManager { else { this.batch_id = generateUUID(); batch['batch_id'] = this.batch_id; - + const res = await api.fetchApi(`/v2/manager/queue/batch`, { method: 'POST', body: JSON.stringify(batch) @@ -1548,7 +1548,7 @@ export class CustomNodesManager { errorMsg = `[FAIL] ${item.title}`; } } - + this.showStop(); showTerminal(); } @@ -1556,6 +1556,9 @@ export class CustomNodesManager { async onQueueStatus(event) { 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') { const hash = event.detail.target; diff --git a/pyproject.toml b/pyproject.toml index 1efae58f..710270a9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,16 +12,16 @@ readme = "README.md" keywords = ["comfyui", "comfyui-manager"] maintainers = [ - { name = "Dr.Lt.Data", email = "dr.lt.data@gmail.com" }, - { name = "Yoland Yan", email = "yoland@drip.art" }, - { name = "James Kwon", email = "hongilkwon316@gmail.com" }, - { name = "Robin Huang", email = "robin@drip.art" }, + { name = "Dr.Lt.Data", email = "dr.lt.data@gmail.com" }, + { name = "Yoland Yan", email = "yoland@drip.art" }, + { name = "James Kwon", email = "hongilkwon316@gmail.com" }, + { name = "Robin Huang", email = "robin@drip.art" }, ] classifiers = [ - "Development Status :: 4 - Beta", - "Intended Audience :: Developers", - "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", + "Development Status :: 4 - Beta", + "Intended Audience :: Developers", + "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", ] dependencies = [ @@ -54,9 +54,9 @@ target-version = "py39" [tool.ruff.lint] select = [ - "E4", # default - "E7", # default - "E9", # default - "F", # default - "I", # isort-like behavior (import statement sorting) + "E4", # default + "E7", # default + "E9", # default + "F", # default + "I", # isort-like behavior (import statement sorting) ]