Compare commits
473 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3425fb7a14 | |||
| c69e7bcf03 | |||
| 85ebcd9897 | |||
| 69b6f1a66b | |||
| e4a90089ab | |||
| 674b9f3705 | |||
| 4941fb8aa0 | |||
| 183af0dfa5 | |||
| 45ac5429f8 | |||
| c771977a95 | |||
| 668d7bbb2c | |||
| 926cfabb58 | |||
| a9a8d05115 | |||
| e368f4366a | |||
| dc5bddbc17 | |||
| 358a480408 | |||
| c96fdb3c7a | |||
| c090abcc02 | |||
| 1ff02be35f | |||
| 10fbfb88f7 | |||
| 9753df72ed | |||
| 095cc3f792 | |||
| 656171037b | |||
| 7ac10f9442 | |||
| 3925ba27b4 | |||
| 44ba79aa31 | |||
| 14d0e31268 | |||
| 033acffad1 | |||
| d29ff808a5 | |||
| dc9b6d655b | |||
| d340c85013 | |||
| e328353664 | |||
| 02785af8fd | |||
| 736ae5d63e | |||
| e1eeb617d2 | |||
| 23b6c7f0de | |||
| 997f97e1fc | |||
| ff335ff1a0 | |||
| cb3036ef81 | |||
| f762906188 | |||
| dde7920f8c | |||
| 1a0d24110a | |||
| e79f6c4471 | |||
| a8a7024a84 | |||
| d93d002da0 | |||
| baaa0479e8 | |||
| cc3bd7a056 | |||
| 4ecefb3b71 | |||
| f24b5aa251 | |||
| de547da4cd | |||
| 0f884166a6 | |||
| 63379f759d | |||
| 8fdff20243 | |||
| 5dfa07ca03 | |||
| 343645be6a | |||
| 91bf21d7a8 | |||
| be6516cfd3 | |||
| 61f1e516a3 | |||
| 73b2278b45 | |||
| aa625e30b6 | |||
| 29a46fe4ce | |||
| 5b3ee49530 | |||
| a9158a101f | |||
| feed8abb34 | |||
| 70decc740f | |||
| 5b5c83f8c5 | |||
| 773c06f40d | |||
| 737e6ad5ed | |||
| 81bca9c94e | |||
| eef0654de2 | |||
| 997a00b8a2 | |||
| 4d25232c5f | |||
| 135befa101 | |||
| 44cac3fc43 | |||
| 449fa3510e | |||
| d958af8aad | |||
| 09f8d5cb2d | |||
| aedc99cefd | |||
| b32cab6e9a | |||
| a95186965e | |||
| 7067de1bb2 | |||
| f45d878d21 | |||
| a0532b938d | |||
| 6ad12b7652 | |||
| 02887c6c9b | |||
| 1b645e1cc3 | |||
| 0a4b2a0488 | |||
| d4ce6ddc52 | |||
| 5a5b989533 | |||
| b57cffb0fa | |||
| 72aa95cacf | |||
| 14ef448937 | |||
| 1c10607c06 | |||
| 41e53a1f2a | |||
| cde83e9a38 | |||
| 6c206b1c72 | |||
| a474219e7b | |||
| 3b8d25eb31 | |||
| 0faa0aa668 | |||
| 0fcdcc93a2 | |||
| 461d5e72fe | |||
| 3f030a2121 | |||
| 7fb8e8662f | |||
| dd3ab9cff2 | |||
| 97b518de12 | |||
| d2a795c866 | |||
| 8a3d65be20 | |||
| b2126d8ba5 | |||
| 6386411d21 | |||
| 4250244136 | |||
| 77c4f9993d | |||
| c7c8417577 | |||
| 9d0985ded8 | |||
| 3663e10e33 | |||
| 5f37a82c3c | |||
| 026bf1dfd7 | |||
| 643a6e5080 | |||
| 5267502896 | |||
| c3c152122d | |||
| afeac097e5 | |||
| e5cea64132 | |||
| 26da78cf15 | |||
| 179a1e1ca0 | |||
| b379d275d1 | |||
| 133cdfb203 | |||
| 2b79edd9be | |||
| 3862a92e04 | |||
| f4e3817fcc | |||
| 61f0f5d67c | |||
| 87f57551ea | |||
| ee51efed69 | |||
| 5dab865681 | |||
| 8c0581eebc | |||
| a72f9f422c | |||
| 1354a8c970 | |||
| 00a5115267 | |||
| 00282eab7b | |||
| bec128de58 | |||
| 9edfa7b4fa | |||
| a9af70e5f0 | |||
| 910caf593f | |||
| 02dc072dc7 | |||
| 78fb354452 | |||
| 66f5eca7fa | |||
| d3906e3cbc | |||
| 079ac254ce | |||
| be95396a57 | |||
| 59cbed429f | |||
| d49df7aebb | |||
| 0aa9faad2e | |||
| 1337def888 | |||
| 4b100c558b | |||
| 1425a71ece | |||
| e0640e7014 | |||
| a8524508fe | |||
| a5ff973d53 | |||
| 337c9aa2c7 | |||
| f1448403ac | |||
| d0b5f77ec6 | |||
| 9cb22ffb60 | |||
| f556962d82 | |||
| d28448d519 | |||
| c590a88ffd | |||
| a1fc6c817b | |||
| 5554e52799 | |||
| ca749eb4d2 | |||
| 41ceee3d24 | |||
| 5acfd52986 | |||
| ec4c7b2f6a | |||
| 22a3d8f95f | |||
| 06b89ca277 | |||
| 9e5ffbd00a | |||
| 39e92ed778 | |||
| 68a3ec567a | |||
| 28231e81b3 | |||
| b2ee0feeaa | |||
| 5541b6b366 | |||
| 408a5fe27e | |||
| bffc73f976 | |||
| bd6edfc9dd | |||
| 2cb24e8a94 | |||
| a49779c4d2 | |||
| 15a5a5f5df | |||
| b5e0558d6e | |||
| 4d683b23fc | |||
| c13da606b2 | |||
| c792f9277c | |||
| b430f42622 | |||
| fee822f5ae | |||
| 192659ecbd | |||
| 810431b9e2 | |||
| 02d845adf3 | |||
| 89c7b960fb | |||
| ed1e399a56 | |||
| 8a3ce1ae57 | |||
| d89ff649f8 | |||
| 24a73b5d1c | |||
| 4d0c40ff8a | |||
| b5a2bed539 | |||
| 0efb79f571 | |||
| df944b9a0f | |||
| 2c11846430 | |||
| 0035c01186 | |||
| 34be3384fe | |||
| ebbc7b3335 | |||
| 4ccc8c3086 | |||
| af9ebc9568 | |||
| ca4b61c5f0 | |||
| 393839b3ab | |||
| dadfc96e00 | |||
| a0a33aef03 | |||
| 99ed81e0f5 | |||
| 5b697db219 | |||
| 8e5bf46e14 | |||
| 9f649b0900 | |||
| abb15e06d3 | |||
| 11a317493e | |||
| e8cece0c1b | |||
| 1ab882f81d | |||
| b9338186e3 | |||
| 7c3cbff425 | |||
| 1ff0afc633 | |||
| bfe7ee8fba | |||
| 49c73ed10e | |||
| f571baacf9 | |||
| 6f02e1114c | |||
| e230f43565 | |||
| 0d9593e71b | |||
| 20778ecfb0 | |||
| 2ea991d960 | |||
| 119c107834 | |||
| 800a0d0449 | |||
| 95c43f0189 | |||
| 9c77176c7f | |||
| ddb6a55cd6 | |||
| 56a4f6fdd7 | |||
| 8a30f788b5 | |||
| 380a1c2c8c | |||
| cd8e8335cf | |||
| 6e1beb54a4 | |||
| 9217c965dd | |||
| a4d71ef487 | |||
| 518f332047 | |||
| 9257d497b8 | |||
| 07cf5de4f7 | |||
| 43ad69e48d | |||
| c62e236cc6 | |||
| 15a2fbb293 | |||
| 16800c3fa0 | |||
| ce09f41aa3 | |||
| 47dc2f036a | |||
| f27a154bfd | |||
| 79757366e8 | |||
| 2cd9a417d6 | |||
| deb05c6cc3 | |||
| b6f171de51 | |||
| a58d5f6999 | |||
| e0b3f3eb45 | |||
| 4bbc8594a7 | |||
| 1ab2b1aeb3 | |||
| 3a377300e1 | |||
| 33a07e3a86 | |||
| 212cafc1d7 | |||
| 2643b3cbcc | |||
| d445229b6d | |||
| dab5c451b0 | |||
| 7bdf06131a | |||
| 854648d5af | |||
| c5f7b97359 | |||
| dd8a727ad6 | |||
| 6c627fe422 | |||
| ee980e1caf | |||
| 22bfaf6527 | |||
| 48ab48cc30 | |||
| a0b14d4127 | |||
| 03f9fe1a70 | |||
| 8915b8d796 | |||
| c77ffeeec0 | |||
| 4acf5660b2 | |||
| 2d9f0a668c | |||
| 9e6cb246cc | |||
| 14544ca63d | |||
| 26b347c04c | |||
| 36f75d1811 | |||
| ffaeb6d3ff | |||
| 6cc1ad4cc0 | |||
| 27fc787294 | |||
| d23286d390 | |||
| 7c3ccc76c3 | |||
| 892dc5d4f3 | |||
| e278692749 | |||
| 8d77dd2246 | |||
| 14ede2a585 | |||
| 5b525622f1 | |||
| a24b11905c | |||
| 5d70858341 | |||
| 3daa006741 | |||
| 0bcc0c2101 | |||
| b8850c808c | |||
| f4f2c01ac1 | |||
| 7072e82dff | |||
| 53dc36c4cf | |||
| 5aadc3af00 | |||
| 8c28a698ed | |||
| 5ed6d8b202 | |||
| b73dc7bf5e | |||
| d7799964de | |||
| 71d0f4ab63 | |||
| d479dcde81 | |||
| ae536017d5 | |||
| 67ddfce279 | |||
| b1f39b34d7 | |||
| 6cf958ccce | |||
| 5378f0a8e9 | |||
| e13bf68775 | |||
| eaed3677d3 | |||
| b9c88da54d | |||
| 104ae77f7a | |||
| bfcb2ce61b | |||
| d970fe68ea | |||
| 63ba5fed09 | |||
| 98a8464933 | |||
| 7e3e6726e0 | |||
| 09567b2bb2 | |||
| f3bd116184 | |||
| 7509737563 | |||
| cfb815d879 | |||
| 44241fb967 | |||
| c4b45129bd | |||
| 70741008ca | |||
| 6c2d2cae2a | |||
| 28f13d3311 | |||
| 4e31aaa8fb | |||
| ba99f0c2cc | |||
| e0a96b4937 | |||
| 82c055f527 | |||
| f94008192c | |||
| 3895d5279e | |||
| 41be94690f | |||
| 3d85ecc525 | |||
| 7da00796e5 | |||
| 6086419cb6 | |||
| 5bc1f2f2c0 | |||
| 32a83b211e | |||
| bead7b3a7f | |||
| 815d6d6572 | |||
| fbecbee4c3 | |||
| b9a7d2a78c | |||
| 95ce812992 | |||
| 9a36f4748c | |||
| 50b7849a35 | |||
| 6f1245b27c | |||
| cc87ed3899 | |||
| 1d9037fefe | |||
| 03016e2d16 | |||
| bdfb70a58a | |||
| 3d41617f4e | |||
| 35151ffdd1 | |||
| 4527d41a7a | |||
| 553cba12f3 | |||
| 00fb9c88e1 | |||
| 116e068ac3 | |||
| 1010dd2d28 | |||
| 68bc8302fd | |||
| 596dad5cda | |||
| a924c280fb | |||
| 7354242906 | |||
| 3d0bcf5979 | |||
| e7d0b158e9 | |||
| 10ff90787c | |||
| 330c4657b1 | |||
| 72a109f109 | |||
| cf45c51dfb | |||
| 0b013adb34 | |||
| 7457d91f64 | |||
| 7fe1159426 | |||
| c2665e3677 | |||
| d63de803a4 | |||
| 11aca3513c | |||
| 561c9f40e5 | |||
| 54ed13aadf | |||
| 109cc21337 | |||
| 7e46b30fa5 | |||
| 0ba112c2c7 | |||
| fc15d94170 | |||
| dcb37d9c55 | |||
| 755b9d6342 | |||
| 3d6151c94f | |||
| 590bd8c4b9 | |||
| e99aafd876 | |||
| 1f0adf8bcf | |||
| dbd5d5fb43 | |||
| a8b0e3641b | |||
| 9efb350be9 | |||
| 8d9820b3fb | |||
| 103f89551a | |||
| 6030d961ad | |||
| ee08c9e17f | |||
| 48dd9a3240 | |||
| e122e206a6 | |||
| 398b905758 | |||
| dc2ec08fe3 | |||
| 3bf5edf5c9 | |||
| 134bca526c | |||
| 3393e58b06 | |||
| 648d7e73c6 | |||
| eab6cdeee4 | |||
| e8ec1ce8e3 | |||
| b3581564ed | |||
| 29e1bd95fd | |||
| 8bff401c14 | |||
| 41798e9255 | |||
| 9e4f0228d1 | |||
| 76ee93c98c | |||
| fb1a89efb7 | |||
| aface43554 | |||
| a35f0157b2 | |||
| 9b32162906 | |||
| 21bba62572 | |||
| 302327d6b3 | |||
| 5667e8bcbb | |||
| ae66bd0e31 | |||
| 48dfadc02d | |||
| 3df6272bb6 | |||
| e7f9bcda01 | |||
| 205044ca66 | |||
| d497eb1f00 | |||
| 4e6f970ee9 | |||
| 0b6cdda6f5 | |||
| a896ded763 | |||
| fb5dd9ebc2 | |||
| c8b7db6c38 | |||
| 44a3191be3 | |||
| b4f7cdc9e7 | |||
| 8da07018d5 | |||
| 0c19a27065 | |||
| 3296b0ecdf | |||
| 0a07261124 | |||
| 33106d0ecf | |||
| 5bb887206a | |||
| b30b0e27cb | |||
| 363736489c | |||
| 8dbf5e87a0 | |||
| 0b30f2cb50 | |||
| ba5265dac4 | |||
| ecb9c65917 | |||
| 8a98474600 | |||
| b072216e67 | |||
| cfb3181716 | |||
| ab684cdc99 | |||
| facadc3a44 | |||
| f599bc22d7 | |||
| 281319d2da | |||
| 5cb203685c | |||
| 300c6e7406 | |||
| 9c4d6a0773 | |||
| 01fa37900b | |||
| edbe744e17 | |||
| 2a32a1a4a8 | |||
| 404bdb21e6 | |||
| b260c9a512 | |||
| 4b941adb6a | |||
| bd752550a8 | |||
| b8b71bb961 | |||
| 5aaf7a4092 | |||
| 030e02ffb8 | |||
| 60746c6253 | |||
| d962aa03f4 | |||
| 121a5a1888 | |||
| 9e4a2aae43 | |||
| ee6eb685e7 | |||
| 09a38a32ce | |||
| d13b19d43d |
@@ -4,7 +4,7 @@ on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- manager-v4
|
||||
paths:
|
||||
- "pyproject.toml"
|
||||
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.9'
|
||||
python-version: '3.x'
|
||||
|
||||
- name: Install build dependencies
|
||||
run: |
|
||||
@@ -31,28 +31,28 @@ jobs:
|
||||
- name: Get current version
|
||||
id: current_version
|
||||
run: |
|
||||
CURRENT_VERSION=$(grep -oP 'version = "\K[^"]+' pyproject.toml)
|
||||
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: Create GitHub Release
|
||||
# id: create_release
|
||||
# uses: softprops/action-gh-release@v2
|
||||
# env:
|
||||
# GITHUB_TOKEN: ${{ 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
|
||||
uses: pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc
|
||||
with:
|
||||
password: ${{ secrets.PYPI_TOKEN }}
|
||||
skip-existing: true
|
||||
verbose: true
|
||||
verbose: true
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
name: Publish to Comfy registry
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main-blocked
|
||||
paths:
|
||||
- "pyproject.toml"
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
publish-node:
|
||||
name: Publish Custom Node to registry
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.repository_owner == 'ltdrdata' || github.repository_owner == 'Comfy-Org' }}
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v4
|
||||
- name: Publish Custom Node
|
||||
uses: Comfy-Org/publish-node-action@v1
|
||||
with:
|
||||
## Add your own personal access token to your Github Repository secrets and reference it here.
|
||||
personal_access_token: ${{ secrets.REGISTRY_ACCESS_TOKEN }}
|
||||
@@ -89,20 +89,20 @@
|
||||
|
||||
|
||||
## Paths
|
||||
In `ComfyUI-Manager` V3.0 and later, configuration files and dynamically generated files are located under `<USER_DIRECTORY>/default/ComfyUI-Manager/`.
|
||||
In `ComfyUI-Manager` V4.0.3b4 and later, configuration files and dynamically generated files are located under `<USER_DIRECTORY>/__manager/`.
|
||||
|
||||
* <USER_DIRECTORY>
|
||||
* If executed without any options, the path defaults to ComfyUI/user.
|
||||
* It can be set using --user-directory <USER_DIRECTORY>.
|
||||
* <USER_DIRECTORY>
|
||||
* If executed without any options, the path defaults to ComfyUI/user.
|
||||
* It can be set using --user-directory <USER_DIRECTORY>.
|
||||
|
||||
* Basic config files: `<USER_DIRECTORY>/default/ComfyUI-Manager/config.ini`
|
||||
* Configurable channel lists: `<USER_DIRECTORY>/default/ComfyUI-Manager/channels.ini`
|
||||
* Configurable pip overrides: `<USER_DIRECTORY>/default/ComfyUI-Manager/pip_overrides.json`
|
||||
* Configurable pip blacklist: `<USER_DIRECTORY>/default/ComfyUI-Manager/pip_blacklist.list`
|
||||
* Configurable pip auto fix: `<USER_DIRECTORY>/default/ComfyUI-Manager/pip_auto_fix.list`
|
||||
* Saved snapshot files: `<USER_DIRECTORY>/default/ComfyUI-Manager/snapshots`
|
||||
* Startup script files: `<USER_DIRECTORY>/default/ComfyUI-Manager/startup-scripts`
|
||||
* Component files: `<USER_DIRECTORY>/default/ComfyUI-Manager/components`
|
||||
* Basic config files: `<USER_DIRECTORY>/__manager/config.ini`
|
||||
* Configurable channel lists: `<USER_DIRECTORY>/__manager/channels.ini`
|
||||
* Configurable pip overrides: `<USER_DIRECTORY>/__manager/pip_overrides.json`
|
||||
* Configurable pip blacklist: `<USER_DIRECTORY>/__manager/pip_blacklist.list`
|
||||
* Configurable pip auto fix: `<USER_DIRECTORY>/__manager/pip_auto_fix.list`
|
||||
* Saved snapshot files: `<USER_DIRECTORY>/__manager/snapshots`
|
||||
* Startup script files: `<USER_DIRECTORY>/__manager/startup-scripts`
|
||||
* Component files: `<USER_DIRECTORY>/__manager/components`
|
||||
|
||||
|
||||
## `extra_model_paths.yaml` Configuration
|
||||
@@ -115,17 +115,17 @@ The following settings are applied based on the section marked as `is_default`.
|
||||
|
||||
## Snapshot-Manager
|
||||
* When you press `Save snapshot` or use `Update All` on `Manager Menu`, the current installation status snapshot is saved.
|
||||
* Snapshot file dir: `<USER_DIRECTORY>/default/ComfyUI-Manager/snapshots`
|
||||
* Snapshot file dir: `<USER_DIRECTORY>/__manager/snapshots`
|
||||
* You can rename snapshot file.
|
||||
* Press the "Restore" button to revert to the installation status of the respective snapshot.
|
||||
* However, for custom nodes not managed by Git, snapshot support is incomplete.
|
||||
* When you press `Restore`, it will take effect on the next ComfyUI startup.
|
||||
* The selected snapshot file is saved in `<USER_DIRECTORY>/default/ComfyUI-Manager/startup-scripts/restore-snapshot.json`, and upon restarting ComfyUI, the snapshot is applied and then deleted.
|
||||
* The selected snapshot file is saved in `<USER_DIRECTORY>/__manager/startup-scripts/restore-snapshot.json`, and upon restarting ComfyUI, the snapshot is applied and then deleted.
|
||||
|
||||

|
||||
|
||||
|
||||
## cm-cli: command line tools for power user
|
||||
## cm-cli: command line tools for power users
|
||||
* A tool is provided that allows you to use the features of ComfyUI-Manager without running ComfyUI.
|
||||
* For more details, please refer to the [cm-cli documentation](docs/en/cm-cli.md).
|
||||
|
||||
@@ -169,12 +169,12 @@ The following settings are applied based on the section marked as `is_default`.
|
||||
}
|
||||
```
|
||||
* `<current timestamp>` Ensure that the timestamp is always unique.
|
||||
* "components" should have the same structure as the content of the file stored in `<USER_DIRECTORY>/default/ComfyUI-Manager/components`.
|
||||
* "components" should have the same structure as the content of the file stored in `<USER_DIRECTORY>/__manager/components`.
|
||||
* `<component name>`: The name should be in the format `<prefix>::<node name>`.
|
||||
* `<compnent nodeata>`: In the nodedata of the group node.
|
||||
* `<component node data>`: In the node data of the group node.
|
||||
* `<version>`: Only two formats are allowed: `major.minor.patch` or `major.minor`. (e.g. `1.0`, `2.2.1`)
|
||||
* `<datetime>`: Saved time
|
||||
* `<packname>`: If the packname is not empty, the category becomes packname/workflow, and it is saved in the <packname>.pack file in `<USER_DIRECTORY>/default/ComfyUI-Manager/components`.
|
||||
* `<packname>`: If the packname is not empty, the category becomes packname/workflow, and it is saved in the <packname>.pack file in `<USER_DIRECTORY>/__manager/components`.
|
||||
* `<category>`: If there is neither a category nor a packname, it is saved in the components category.
|
||||
```
|
||||
"version":"1.0",
|
||||
@@ -189,7 +189,7 @@ The following settings are applied based on the section marked as `is_default`.
|
||||
* Dragging and dropping or pasting a single component will add a node. However, when adding multiple components, nodes will not be added.
|
||||
|
||||
|
||||
## Support of missing nodes installation
|
||||
## Support for installing missing nodes
|
||||
|
||||

|
||||
|
||||
@@ -229,10 +229,10 @@ The following settings are applied based on the section marked as `is_default`.
|
||||
* Logging to file feature
|
||||
* This feature is enabled by default and can be disabled by setting `file_logging = False` in the `config.ini`.
|
||||
|
||||
* Fix node(recreate): When right-clicking on a node and selecting `Fix node (recreate)`, you can recreate the node. The widget's values are reset, while the connections maintain those with the same names.
|
||||
* Fix node (recreate): When right-clicking on a node and selecting `Fix node (recreate)`, you can recreate the node. The widget's values are reset, while the connections maintain those with the same names.
|
||||
* It is used to correct errors in nodes of old workflows created before, which are incompatible with the version changes of custom nodes.
|
||||
|
||||
* Double-Click Node Title: You can set the double click behavior of nodes in the ComfyUI-Manager menu.
|
||||
* Double-Click Node Title: You can set the double-click behavior of nodes in the ComfyUI-Manager menu.
|
||||
* `Copy All Connections`, `Copy Input Connections`: Double-clicking a node copies the connections of the nearest node.
|
||||
* This action targets the nearest node within a straight-line distance of 1000 pixels from the center of the node.
|
||||
* In the case of `Copy All Connections`, it duplicates existing outputs, but since it does not allow duplicate connections, the existing output connections of the original node are disconnected.
|
||||
@@ -298,17 +298,17 @@ When you run the `scan.sh` script:
|
||||
|
||||
* It updates the `github-stats.json`.
|
||||
* This uses the GitHub API, so set your token with `export GITHUB_TOKEN=your_token_here` to avoid quickly reaching the rate limit and malfunctioning.
|
||||
* To skip this step, add the `--skip-update-stat` option.
|
||||
* To skip this step, add the `--skip-stat-update` option.
|
||||
|
||||
* The `--skip-all` option applies both `--skip-update` and `--skip-stat-update`.
|
||||
|
||||
|
||||
## Troubleshooting
|
||||
* If your `git.exe` is installed in a specific location other than system git, please install ComfyUI-Manager and run ComfyUI. Then, specify the path including the file name in `git_exe = ` in the `<USER_DIRECTORY>/default/ComfyUI-Manager/config.ini` file that is generated.
|
||||
* If your `git.exe` is installed in a specific location other than system git, please install ComfyUI-Manager and run ComfyUI. Then, specify the path including the file name in `git_exe = ` in the `<USER_DIRECTORY>/__manager/config.ini` file that is generated.
|
||||
* If updating ComfyUI-Manager itself fails, please go to the **ComfyUI-Manager** directory and execute the command `git update-ref refs/remotes/origin/main a361cc1 && git fetch --all && git pull`.
|
||||
* If you encounter the error message `Overlapped Object has pending operation at deallocation on Comfyui Manager load` under Windows
|
||||
* If you encounter the error message `Overlapped Object has pending operation at deallocation on ComfyUI Manager load` under Windows
|
||||
* Edit `config.ini` file: add `windows_selector_event_loop_policy = True`
|
||||
* if `SSL: CERTIFICATE_VERIFY_FAILED` error is occured.
|
||||
* If the `SSL: CERTIFICATE_VERIFY_FAILED` error occurs.
|
||||
* Edit `config.ini` file: add `bypass_ssl = True`
|
||||
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ find ~/.tmp/default -name "*.py" -print0 | xargs -0 grep -E "crypto|^_A="
|
||||
|
||||
echo
|
||||
echo CHECK3
|
||||
find ~/.tmp/default -name "requirements.txt" | xargs grep "^\s*https\\?:"
|
||||
find ~/.tmp/default -name "requirements.txt" | xargs grep "\.whl"
|
||||
find ~/.tmp/default -name "requirements.txt" | xargs grep "^\s*[^#]*https\?:"
|
||||
find ~/.tmp/default -name "requirements.txt" | xargs grep "^\s*[^#].*\.whl"
|
||||
|
||||
echo
|
||||
|
||||
@@ -15,7 +15,7 @@ def start():
|
||||
logging.info('[START] ComfyUI-Manager')
|
||||
from .common import cm_global # noqa: F401
|
||||
|
||||
if not args.disable_manager:
|
||||
if args.enable_manager:
|
||||
if args.enable_manager_legacy_ui:
|
||||
try:
|
||||
from .legacy import manager_server # noqa: F401
|
||||
@@ -42,7 +42,7 @@ def should_be_disabled(fullpath:str) -> bool:
|
||||
1. Disables the legacy ComfyUI-Manager.
|
||||
2. The blocklist can be expanded later based on policies.
|
||||
"""
|
||||
if not args.disable_manager:
|
||||
if args.enable_manager:
|
||||
# In cases where installation is done via a zip archive, the directory name may not be comfyui-manager, and it may not contain a git repository.
|
||||
# It is assumed that any installed legacy ComfyUI-Manager will have at least 'comfyui-manager' in its directory name.
|
||||
dir_name = os.path.basename(fullpath).lower()
|
||||
|
||||
@@ -11,6 +11,7 @@ from . import manager_util
|
||||
|
||||
import requests
|
||||
import toml
|
||||
import logging
|
||||
|
||||
base_url = "https://api.comfy.org"
|
||||
|
||||
@@ -23,7 +24,7 @@ async def get_cnr_data(cache_mode=True, dont_wait=True):
|
||||
try:
|
||||
return await _get_cnr_data(cache_mode, dont_wait)
|
||||
except asyncio.TimeoutError:
|
||||
print("A timeout occurred during the fetch process from ComfyRegistry.")
|
||||
logging.info("A timeout occurred during the fetch process from ComfyRegistry.")
|
||||
return await _get_cnr_data(cache_mode=True, dont_wait=True) # timeout fallback
|
||||
|
||||
async def _get_cnr_data(cache_mode=True, dont_wait=True):
|
||||
@@ -79,12 +80,12 @@ async def _get_cnr_data(cache_mode=True, dont_wait=True):
|
||||
full_nodes[x['id']] = x
|
||||
|
||||
if page % 5 == 0:
|
||||
print(f"FETCH ComfyRegistry Data: {page}/{sub_json_obj['totalPages']}")
|
||||
logging.info(f"FETCH ComfyRegistry Data: {page}/{sub_json_obj['totalPages']}")
|
||||
|
||||
page += 1
|
||||
time.sleep(0.5)
|
||||
|
||||
print("FETCH ComfyRegistry Data [DONE]")
|
||||
logging.info("FETCH ComfyRegistry Data [DONE]")
|
||||
|
||||
for v in full_nodes.values():
|
||||
if 'latest_version' not in v:
|
||||
@@ -100,7 +101,7 @@ async def _get_cnr_data(cache_mode=True, dont_wait=True):
|
||||
if cache_state == 'not-cached':
|
||||
return {}
|
||||
else:
|
||||
print("[ComfyUI-Manager] The ComfyRegistry cache update is still in progress, so an outdated cache is being used.")
|
||||
logging.info("[ComfyUI-Manager] The ComfyRegistry cache update is still in progress, so an outdated cache is being used.")
|
||||
with open(manager_util.get_cache_path(uri), 'r', encoding="UTF-8", errors="ignore") as json_file:
|
||||
return json.load(json_file)['nodes']
|
||||
|
||||
@@ -114,7 +115,7 @@ async def _get_cnr_data(cache_mode=True, dont_wait=True):
|
||||
return json_obj['nodes']
|
||||
except Exception:
|
||||
res = {}
|
||||
print("Cannot connect to comfyregistry.")
|
||||
logging.warning("Cannot connect to comfyregistry.")
|
||||
finally:
|
||||
if cache_mode:
|
||||
is_cache_loading = False
|
||||
@@ -211,6 +212,7 @@ def read_cnr_info(fullpath):
|
||||
|
||||
project = data.get('project', {})
|
||||
name = project.get('name').strip().lower()
|
||||
original_name = project.get('name')
|
||||
|
||||
# normalize version
|
||||
# for example: 2.5 -> 2.5.0
|
||||
@@ -222,6 +224,7 @@ def read_cnr_info(fullpath):
|
||||
if name and version: # repository is optional
|
||||
return {
|
||||
"id": name,
|
||||
"original_name": original_name,
|
||||
"version": version,
|
||||
"url": repository
|
||||
}
|
||||
@@ -238,7 +241,7 @@ def generate_cnr_id(fullpath, cnr_id):
|
||||
with open(cnr_id_path, "w") as f:
|
||||
return f.write(cnr_id)
|
||||
except Exception:
|
||||
print(f"[ComfyUI Manager] unable to create file: {cnr_id_path}")
|
||||
logging.error(f"[ComfyUI Manager] unable to create file: {cnr_id_path}")
|
||||
|
||||
|
||||
def read_cnr_id(fullpath):
|
||||
|
||||
@@ -34,7 +34,7 @@ manager_pip_blacklist_path = None
|
||||
manager_components_path = None
|
||||
manager_batch_history_path = None
|
||||
|
||||
def update_user_directory(user_dir):
|
||||
def update_user_directory(manager_dir):
|
||||
global manager_files_path
|
||||
global manager_config_path
|
||||
global manager_channel_list_path
|
||||
@@ -45,7 +45,7 @@ def update_user_directory(user_dir):
|
||||
global manager_components_path
|
||||
global manager_batch_history_path
|
||||
|
||||
manager_files_path = os.path.abspath(os.path.join(user_dir, 'default', 'ComfyUI-Manager'))
|
||||
manager_files_path = manager_dir
|
||||
if not os.path.exists(manager_files_path):
|
||||
os.makedirs(manager_files_path)
|
||||
|
||||
@@ -73,7 +73,7 @@ def update_user_directory(user_dir):
|
||||
|
||||
try:
|
||||
import folder_paths
|
||||
update_user_directory(folder_paths.get_user_directory())
|
||||
update_user_directory(folder_paths.get_system_user_directory("manager"))
|
||||
|
||||
except Exception:
|
||||
# fallback:
|
||||
|
||||
@@ -15,6 +15,7 @@ import re
|
||||
import logging
|
||||
import platform
|
||||
import shlex
|
||||
from functools import lru_cache
|
||||
|
||||
|
||||
cache_lock = threading.Lock()
|
||||
@@ -38,18 +39,64 @@ def add_python_path_to_env():
|
||||
os.environ['PATH'] = os.path.dirname(sys.executable)+sep+os.environ['PATH']
|
||||
|
||||
|
||||
@lru_cache(maxsize=2)
|
||||
def get_pip_cmd(force_uv=False):
|
||||
"""
|
||||
Get the base pip command, with automatic fallback to uv if pip is unavailable.
|
||||
|
||||
Args:
|
||||
force_uv (bool): If True, use uv directly without trying pip
|
||||
|
||||
Returns:
|
||||
list: Base command for pip operations
|
||||
"""
|
||||
embedded = 'python_embeded' in sys.executable
|
||||
|
||||
# Try pip first (unless forcing uv)
|
||||
if not force_uv:
|
||||
try:
|
||||
test_cmd = [sys.executable] + (['-s'] if embedded else []) + ['-m', 'pip', '--version']
|
||||
subprocess.check_output(test_cmd, stderr=subprocess.DEVNULL, timeout=5)
|
||||
return [sys.executable] + (['-s'] if embedded else []) + ['-m', 'pip']
|
||||
except Exception:
|
||||
logging.warning("[ComfyUI-Manager] python -m pip not available. Falling back to uv.")
|
||||
|
||||
# Try uv (either forced or pip failed)
|
||||
import shutil
|
||||
|
||||
# Try uv as Python module
|
||||
try:
|
||||
test_cmd = [sys.executable] + (['-s'] if embedded else []) + ['-m', 'uv', '--version']
|
||||
subprocess.check_output(test_cmd, stderr=subprocess.DEVNULL, timeout=5)
|
||||
logging.info("[ComfyUI-Manager] Using uv as Python module for pip operations.")
|
||||
return [sys.executable] + (['-s'] if embedded else []) + ['-m', 'uv', 'pip']
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Try standalone uv
|
||||
if shutil.which('uv'):
|
||||
logging.info("[ComfyUI-Manager] Using standalone uv for pip operations.")
|
||||
return ['uv', 'pip']
|
||||
|
||||
# Nothing worked
|
||||
logging.error("[ComfyUI-Manager] Neither python -m pip nor uv are available. Cannot proceed with package operations.")
|
||||
raise Exception("Neither pip nor uv are available for package management")
|
||||
|
||||
|
||||
def make_pip_cmd(cmd):
|
||||
if 'python_embeded' in sys.executable:
|
||||
if use_uv:
|
||||
return [sys.executable, '-s', '-m', 'uv', 'pip'] + cmd
|
||||
else:
|
||||
return [sys.executable, '-s', '-m', 'pip'] + cmd
|
||||
else:
|
||||
# FIXED: https://github.com/ltdrdata/ComfyUI-Manager/issues/1667
|
||||
if use_uv:
|
||||
return [sys.executable, '-m', 'uv', 'pip'] + cmd
|
||||
else:
|
||||
return [sys.executable, '-m', 'pip'] + cmd
|
||||
"""
|
||||
Create a pip command by combining the cached base pip command with the given arguments.
|
||||
|
||||
Args:
|
||||
cmd (list): List of pip command arguments (e.g., ['install', 'package'])
|
||||
|
||||
Returns:
|
||||
list: Complete command list ready for subprocess execution
|
||||
"""
|
||||
global use_uv
|
||||
base_cmd = get_pip_cmd(force_uv=use_uv)
|
||||
return base_cmd + cmd
|
||||
|
||||
|
||||
# DON'T USE StrictVersion - cannot handle pre_release version
|
||||
# try:
|
||||
|
||||
Executable → Regular
+7552
-517
File diff suppressed because it is too large
Load Diff
@@ -30,6 +30,12 @@ from .generated_models import (
|
||||
InstalledModelInfo,
|
||||
ComfyUIVersionInfo,
|
||||
|
||||
# Import Fail Info Models
|
||||
ImportFailInfoBulkRequest,
|
||||
ImportFailInfoBulkResponse,
|
||||
ImportFailInfoItem,
|
||||
ImportFailInfoItem1,
|
||||
|
||||
# Other models
|
||||
OperationType,
|
||||
OperationResult,
|
||||
@@ -88,6 +94,12 @@ __all__ = [
|
||||
"InstalledModelInfo",
|
||||
"ComfyUIVersionInfo",
|
||||
|
||||
# Import Fail Info Models
|
||||
"ImportFailInfoBulkRequest",
|
||||
"ImportFailInfoBulkResponse",
|
||||
"ImportFailInfoItem",
|
||||
"ImportFailInfoItem1",
|
||||
|
||||
# Other models
|
||||
"OperationType",
|
||||
"OperationResult",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# generated by datamodel-codegen:
|
||||
# filename: openapi.yaml
|
||||
# timestamp: 2025-06-27T04:01:45+00:00
|
||||
# timestamp: 2025-07-31T04:52:26+00:00
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -454,6 +454,24 @@ class BatchExecutionRecord(BaseModel):
|
||||
)
|
||||
|
||||
|
||||
class ImportFailInfoBulkRequest(BaseModel):
|
||||
cnr_ids: Optional[List[str]] = Field(
|
||||
None, description="A list of CNR IDs to check."
|
||||
)
|
||||
urls: Optional[List[str]] = Field(
|
||||
None, description="A list of repository URLs to check."
|
||||
)
|
||||
|
||||
|
||||
class ImportFailInfoItem1(BaseModel):
|
||||
error: Optional[str] = None
|
||||
traceback: Optional[str] = None
|
||||
|
||||
|
||||
class ImportFailInfoItem(RootModel[Optional[ImportFailInfoItem1]]):
|
||||
root: Optional[ImportFailInfoItem1]
|
||||
|
||||
|
||||
class QueueTaskItem(BaseModel):
|
||||
ui_id: str = Field(..., description="Unique identifier for the task")
|
||||
client_id: str = Field(..., description="Client identifier that initiated the task")
|
||||
@@ -537,3 +555,7 @@ class HistoryResponse(BaseModel):
|
||||
history: Optional[Dict[str, TaskHistoryItem]] = Field(
|
||||
None, description="Map of task IDs to their history items"
|
||||
)
|
||||
|
||||
|
||||
class ImportFailInfoBulkResponse(RootModel[Optional[Dict[str, ImportFailInfoItem]]]):
|
||||
root: Optional[Dict[str, ImportFailInfoItem]] = None
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -41,11 +41,12 @@ from ..common.enums import NetworkMode, SecurityLevel, DBMode
|
||||
from ..common import context
|
||||
|
||||
|
||||
version_code = [4, 0]
|
||||
version_code = [4, 0, 3]
|
||||
version_str = f"V{version_code[0]}.{version_code[1]}" + (f'.{version_code[2]}' if len(version_code) > 2 else '')
|
||||
|
||||
|
||||
DEFAULT_CHANNEL = "https://raw.githubusercontent.com/ltdrdata/ComfyUI-Manager/main"
|
||||
DEFAULT_CHANNEL_LEGACY = "https://raw.githubusercontent.com/ltdrdata/ComfyUI-Manager/main"
|
||||
|
||||
|
||||
default_custom_nodes_path = None
|
||||
@@ -153,14 +154,8 @@ def check_invalid_nodes():
|
||||
cached_config = None
|
||||
js_path = None
|
||||
|
||||
comfy_ui_required_revision = 1930
|
||||
comfy_ui_required_commit_datetime = datetime(2024, 1, 24, 0, 0, 0)
|
||||
|
||||
comfy_ui_revision = "Unknown"
|
||||
comfy_ui_commit_datetime = datetime(1900, 1, 1, 0, 0, 0)
|
||||
|
||||
channel_dict = None
|
||||
valid_channels = {'default', 'local'}
|
||||
valid_channels = {'default', 'local', DEFAULT_CHANNEL, DEFAULT_CHANNEL_LEGACY}
|
||||
channel_list = None
|
||||
|
||||
|
||||
@@ -1008,7 +1003,6 @@ class UnifiedManager:
|
||||
"""
|
||||
|
||||
result = ManagedResult('enable')
|
||||
|
||||
if 'comfyui-manager' in node_id.lower():
|
||||
return result.fail(f"ignored: enabling '{node_id}'")
|
||||
|
||||
@@ -1479,7 +1473,7 @@ def identify_node_pack_from_path(fullpath):
|
||||
# cnr
|
||||
cnr = cnr_utils.read_cnr_info(fullpath)
|
||||
if cnr is not None:
|
||||
return module_name, cnr['version'], cnr['id'], None
|
||||
return module_name, cnr['version'], cnr['original_name'], None
|
||||
|
||||
return None
|
||||
else:
|
||||
@@ -1529,7 +1523,10 @@ def get_installed_node_packs():
|
||||
if info is None:
|
||||
continue
|
||||
|
||||
res[info[0]] = { 'ver': info[1], 'cnr_id': info[2], 'aux_id': info[3], 'enabled': False }
|
||||
# NOTE: don't add disabled nodepack if there is enabled nodepack
|
||||
original_name = info[0].split('@')[0]
|
||||
if original_name not in res:
|
||||
res[info[0]] = { 'ver': info[1], 'cnr_id': info[2], 'aux_id': info[3], 'enabled': False }
|
||||
|
||||
return res
|
||||
|
||||
@@ -1786,16 +1783,6 @@ def try_install_script(url, repo_path, install_cmd, instant_execution=False):
|
||||
print(f"\n## ComfyUI-Manager: EXECUTE => {install_cmd}")
|
||||
code = manager_funcs.run_script(install_cmd, cwd=repo_path)
|
||||
|
||||
if platform.system() != "Windows":
|
||||
try:
|
||||
if not os.environ.get('__COMFYUI_DESKTOP_VERSION__') and comfy_ui_commit_datetime.date() < comfy_ui_required_commit_datetime.date():
|
||||
print("\n\n###################################################################")
|
||||
print(f"[WARN] ComfyUI-Manager: Your ComfyUI version ({comfy_ui_revision})[{comfy_ui_commit_datetime.date()}] is too old. Please update to the latest version.")
|
||||
print("[WARN] The extension installation feature may not work properly in the current installed ComfyUI version on Windows environment.")
|
||||
print("###################################################################\n\n")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if code != 0:
|
||||
if url is None:
|
||||
url = os.path.dirname(repo_path)
|
||||
@@ -1914,6 +1901,27 @@ def execute_install_script(url, repo_path, lazy_mode=False, instant_execution=Fa
|
||||
return True
|
||||
|
||||
|
||||
def install_manager_requirements(repo_path):
|
||||
"""
|
||||
Install packages from manager_requirements.txt if it exists.
|
||||
This is specifically for ComfyUI's manager_requirements.txt.
|
||||
"""
|
||||
manager_requirements_path = os.path.join(repo_path, "manager_requirements.txt")
|
||||
if not os.path.exists(manager_requirements_path):
|
||||
return
|
||||
|
||||
logging.info("[ComfyUI-Manager] Installing manager_requirements.txt")
|
||||
with open(manager_requirements_path, "r") as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if line and not line.startswith('#'):
|
||||
if '#' in line:
|
||||
line = line.split('#')[0].strip()
|
||||
if line:
|
||||
install_cmd = manager_util.make_pip_cmd(["install", line])
|
||||
subprocess.run(install_cmd)
|
||||
|
||||
|
||||
def git_repo_update_check_with(path, do_fetch=False, do_update=False, no_deps=False):
|
||||
"""
|
||||
|
||||
@@ -2447,6 +2455,7 @@ def update_to_stable_comfyui(repo_path):
|
||||
else:
|
||||
logging.info(f"[ComfyUI-Manager] Updating ComfyUI: {current_tag} -> {latest_tag}")
|
||||
repo.git.checkout(latest_tag)
|
||||
execute_install_script("ComfyUI", repo_path, instant_execution=False, no_deps=False)
|
||||
return 'updated', latest_tag
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
|
||||
@@ -61,6 +61,7 @@ from ..data_models import (
|
||||
ManagerMessageName,
|
||||
BatchExecutionRecord,
|
||||
ComfyUISystemState,
|
||||
ImportFailInfoBulkRequest,
|
||||
BatchOperation,
|
||||
InstalledNodeInfo,
|
||||
ComfyUIVersionInfo,
|
||||
@@ -967,6 +968,8 @@ async def task_worker():
|
||||
logging.error("ComfyUI update failed")
|
||||
return "fail"
|
||||
elif res == "updated":
|
||||
core.install_manager_requirements(repo_path)
|
||||
|
||||
if is_stable:
|
||||
logging.info("ComfyUI is updated to latest stable version.")
|
||||
return "success-stable-" + latest_tag
|
||||
@@ -1656,6 +1659,67 @@ async def import_fail_info(request):
|
||||
return web.Response(status=500, text="Internal server error")
|
||||
|
||||
|
||||
@routes.post("/v2/customnode/import_fail_info_bulk")
|
||||
async def import_fail_info_bulk(request):
|
||||
try:
|
||||
json_data = await request.json()
|
||||
|
||||
# Validate input using Pydantic model
|
||||
request_data = ImportFailInfoBulkRequest.model_validate(json_data)
|
||||
|
||||
# Ensure we have either cnr_ids or urls
|
||||
if not request_data.cnr_ids and not request_data.urls:
|
||||
return web.Response(
|
||||
status=400, text="Either 'cnr_ids' or 'urls' field is required"
|
||||
)
|
||||
|
||||
await core.unified_manager.reload('cache')
|
||||
await core.unified_manager.get_custom_nodes('default', 'cache')
|
||||
|
||||
results = {}
|
||||
|
||||
if request_data.cnr_ids:
|
||||
for cnr_id in request_data.cnr_ids:
|
||||
module_name = core.unified_manager.get_module_name(cnr_id)
|
||||
if module_name is not None:
|
||||
info = cm_global.error_dict.get(module_name)
|
||||
if info is not None:
|
||||
# Convert error_dict format to API spec format
|
||||
results[cnr_id] = {
|
||||
'error': info.get('msg', ''),
|
||||
'traceback': info.get('traceback', '')
|
||||
}
|
||||
else:
|
||||
results[cnr_id] = None
|
||||
else:
|
||||
results[cnr_id] = None
|
||||
|
||||
if request_data.urls:
|
||||
for url in request_data.urls:
|
||||
module_name = core.unified_manager.get_module_name(url)
|
||||
if module_name is not None:
|
||||
info = cm_global.error_dict.get(module_name)
|
||||
if info is not None:
|
||||
# Convert error_dict format to API spec format
|
||||
results[url] = {
|
||||
'error': info.get('msg', ''),
|
||||
'traceback': info.get('traceback', '')
|
||||
}
|
||||
else:
|
||||
results[url] = None
|
||||
else:
|
||||
results[url] = None
|
||||
|
||||
# Return results directly as JSON
|
||||
return web.json_response(results, content_type="application/json")
|
||||
except ValidationError as e:
|
||||
logging.error(f"[ComfyUI-Manager] Invalid request data: {e}")
|
||||
return web.Response(status=400, text=f"Invalid request data: {e}")
|
||||
except Exception as e:
|
||||
logging.error(f"[ComfyUI-Manager] Error processing bulk import fail info: {e}")
|
||||
return web.Response(status=500, text="Internal server error")
|
||||
|
||||
|
||||
@routes.get("/v2/manager/queue/reset")
|
||||
async def reset_queue(request):
|
||||
logging.debug("[ComfyUI-Manager] Queue reset requested")
|
||||
@@ -1983,10 +2047,7 @@ async def default_cache_update():
|
||||
)
|
||||
traceback.print_exc()
|
||||
|
||||
if (
|
||||
core.get_config()["network_mode"] != "offline"
|
||||
and not manager_util.is_manager_pip_package()
|
||||
):
|
||||
if core.get_config()["network_mode"] != "offline":
|
||||
a = get_cache("custom-node-list.json")
|
||||
b = get_cache("extension-node-map.json")
|
||||
c = get_cache("model-list.json")
|
||||
|
||||
@@ -11,6 +11,15 @@ import hashlib
|
||||
import folder_paths
|
||||
from server import PromptServer
|
||||
import logging
|
||||
import sys
|
||||
|
||||
|
||||
try:
|
||||
from nio import AsyncClient, LoginResponse, UploadResponse
|
||||
matrix_nio_is_available = True
|
||||
except Exception:
|
||||
logging.warning(f"[ComfyUI-Manager] The matrix sharing feature has been disabled because the `matrix-nio` dependency is not installed.\n\tTo use this feature, please run the following command:\n\t{sys.executable} -m pip install matrix-nio\n")
|
||||
matrix_nio_is_available = False
|
||||
|
||||
|
||||
def extract_model_file_names(json_data):
|
||||
@@ -193,6 +202,14 @@ async def get_esheep_workflow_and_images(request):
|
||||
return web.Response(status=200, text=json.dumps(data))
|
||||
|
||||
|
||||
@PromptServer.instance.routes.get("/v2/manager/get_matrix_dep_status")
|
||||
async def get_matrix_dep_status(request):
|
||||
if matrix_nio_is_available:
|
||||
return web.Response(status=200, text='available')
|
||||
else:
|
||||
return web.Response(status=200, text='unavailable')
|
||||
|
||||
|
||||
def set_matrix_auth(json_data):
|
||||
homeserver = json_data['homeserver']
|
||||
username = json_data['username']
|
||||
@@ -332,15 +349,12 @@ async def share_art(request):
|
||||
workflowId = upload_workflow_json["workflowId"]
|
||||
|
||||
# check if the user has provided Matrix credentials
|
||||
if "matrix" in share_destinations:
|
||||
if matrix_nio_is_available and "matrix" in share_destinations:
|
||||
comfyui_share_room_id = '!LGYSoacpJPhIfBqVfb:matrix.org'
|
||||
filename = os.path.basename(asset_filepath)
|
||||
content_type = assetFileType
|
||||
|
||||
try:
|
||||
from matrix_client.api import MatrixHttpApi
|
||||
from matrix_client.client import MatrixClient
|
||||
|
||||
homeserver = 'matrix.org'
|
||||
if matrix_auth:
|
||||
homeserver = matrix_auth.get('homeserver', 'matrix.org')
|
||||
@@ -348,20 +362,35 @@ async def share_art(request):
|
||||
if not homeserver.startswith("https://"):
|
||||
homeserver = "https://" + homeserver
|
||||
|
||||
client = MatrixClient(homeserver)
|
||||
try:
|
||||
token = client.login(username=matrix_auth['username'], password=matrix_auth['password'])
|
||||
if not token:
|
||||
return web.json_response({"error": "Invalid Matrix credentials."}, content_type='application/json', status=400)
|
||||
except Exception:
|
||||
client = AsyncClient(homeserver, matrix_auth['username'])
|
||||
|
||||
# Login
|
||||
login_resp = await client.login(matrix_auth['password'])
|
||||
if not isinstance(login_resp, LoginResponse) or not login_resp.access_token:
|
||||
await client.close()
|
||||
return web.json_response({"error": "Invalid Matrix credentials."}, content_type='application/json', status=400)
|
||||
|
||||
matrix = MatrixHttpApi(homeserver, token=token)
|
||||
# Upload asset
|
||||
with open(asset_filepath, 'rb') as f:
|
||||
mxc_url = matrix.media_upload(f.read(), content_type, filename=filename)['content_uri']
|
||||
upload_resp, _maybe_keys = await client.upload(f, content_type=content_type, filename=filename)
|
||||
asset_data = f.seek(0) or f.read() # get size for info below
|
||||
if not isinstance(upload_resp, UploadResponse) or not upload_resp.content_uri:
|
||||
await client.close()
|
||||
return web.json_response({"error": "Failed to upload asset to Matrix."}, content_type='application/json', status=500)
|
||||
mxc_url = upload_resp.content_uri
|
||||
|
||||
workflow_json_mxc_url = matrix.media_upload(prompt['workflow'], 'application/json', filename='workflow.json')['content_uri']
|
||||
# Upload workflow JSON
|
||||
import io
|
||||
workflow_json_bytes = json.dumps(prompt['workflow']).encode('utf-8')
|
||||
workflow_io = io.BytesIO(workflow_json_bytes)
|
||||
upload_workflow_resp, _maybe_keys = await client.upload(workflow_io, content_type='application/json', filename='workflow.json')
|
||||
workflow_io.seek(0)
|
||||
if not isinstance(upload_workflow_resp, UploadResponse) or not upload_workflow_resp.content_uri:
|
||||
await client.close()
|
||||
return web.json_response({"error": "Failed to upload workflow to Matrix."}, content_type='application/json', status=500)
|
||||
workflow_json_mxc_url = upload_workflow_resp.content_uri
|
||||
|
||||
# Send text message
|
||||
text_content = ""
|
||||
if title:
|
||||
text_content += f"{title}\n"
|
||||
@@ -369,11 +398,47 @@ async def share_art(request):
|
||||
text_content += f"{description}\n"
|
||||
if credits:
|
||||
text_content += f"\ncredits: {credits}\n"
|
||||
matrix.send_message(comfyui_share_room_id, text_content)
|
||||
matrix.send_content(comfyui_share_room_id, mxc_url, filename, 'm.image')
|
||||
matrix.send_content(comfyui_share_room_id, workflow_json_mxc_url, 'workflow.json', 'm.file')
|
||||
except Exception:
|
||||
logging.exception("An error occurred")
|
||||
await client.room_send(
|
||||
room_id=comfyui_share_room_id,
|
||||
message_type="m.room.message",
|
||||
content={"msgtype": "m.text", "body": text_content}
|
||||
)
|
||||
|
||||
# Send image
|
||||
await client.room_send(
|
||||
room_id=comfyui_share_room_id,
|
||||
message_type="m.room.message",
|
||||
content={
|
||||
"msgtype": "m.image",
|
||||
"body": filename,
|
||||
"url": mxc_url,
|
||||
"info": {
|
||||
"mimetype": content_type,
|
||||
"size": len(asset_data)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
# Send workflow JSON file
|
||||
await client.room_send(
|
||||
room_id=comfyui_share_room_id,
|
||||
message_type="m.room.message",
|
||||
content={
|
||||
"msgtype": "m.file",
|
||||
"body": "workflow.json",
|
||||
"url": workflow_json_mxc_url,
|
||||
"info": {
|
||||
"mimetype": "application/json",
|
||||
"size": len(workflow_json_bytes)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
await client.close()
|
||||
|
||||
except:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return web.json_response({"error": "An error occurred when sharing your art to Matrix."}, content_type='application/json', status=500)
|
||||
|
||||
return web.json_response({
|
||||
|
||||
@@ -13,7 +13,7 @@ This directory contains the JavaScript frontend implementation for ComfyUI-Manag
|
||||
## Sharing Components
|
||||
|
||||
- **comfyui-share-common.js**: Base functionality for workflow sharing features.
|
||||
- **comfyui-share-copus.js**: Integration with the ComfyUI Opus sharing platform.
|
||||
- **comfyui-share-copus.js**: Integration with the ComfyUI Copus sharing platform.
|
||||
- **comfyui-share-openart.js**: Integration with the OpenArt sharing platform.
|
||||
- **comfyui-share-youml.js**: Integration with the YouML sharing platform.
|
||||
|
||||
@@ -47,4 +47,4 @@ CSS files are included for specific components:
|
||||
- **custom-nodes-manager.css**: Styling for the node management UI
|
||||
- **model-manager.css**: Styling for the model management UI
|
||||
|
||||
This frontend implementation provides a comprehensive yet user-friendly interface for managing the ComfyUI ecosystem.
|
||||
This frontend implementation provides a comprehensive yet user-friendly interface for managing the ComfyUI ecosystem.
|
||||
|
||||
@@ -1514,8 +1514,6 @@ app.registerExtension({
|
||||
tooltip: "Share"
|
||||
}).element
|
||||
);
|
||||
|
||||
app.menu?.settingsGroup.element.before(cmGroup.element);
|
||||
}
|
||||
catch(exception) {
|
||||
console.log('ComfyUI is outdated. New style menu based features are disabled.');
|
||||
|
||||
@@ -552,6 +552,20 @@ export class ShareDialog extends ComfyDialog {
|
||||
this.matrix_destination_checkbox.style.color = "var(--fg-color)";
|
||||
this.matrix_destination_checkbox.checked = this.share_option === 'matrix'; //true;
|
||||
|
||||
try {
|
||||
api.fetchApi(`/v2/manager/get_matrix_dep_status`)
|
||||
.then(response => response.text())
|
||||
.then(data => {
|
||||
if(data == 'unavailable') {
|
||||
matrix_destination_checkbox_text.style.textDecoration = "line-through";
|
||||
this.matrix_destination_checkbox.disabled = true;
|
||||
this.matrix_destination_checkbox.title = "It has been disabled because the 'matrix-nio' dependency is not installed. Please install this dependency to use the matrix sharing feature.";
|
||||
matrix_destination_checkbox_text.title = "It has been disabled because the 'matrix-nio' dependency is not installed. Please install this dependency to use the matrix sharing feature.";
|
||||
}
|
||||
})
|
||||
.catch(error => {});
|
||||
} catch (error) {}
|
||||
|
||||
this.comfyworkflows_destination_checkbox = $el("input", { type: 'checkbox', id: "comfyworkflows_destination" }, [])
|
||||
const comfyworkflows_destination_checkbox_text = $el("label", {}, [" ComfyWorkflows.com"])
|
||||
this.comfyworkflows_destination_checkbox.style.color = "var(--fg-color)";
|
||||
|
||||
@@ -201,13 +201,15 @@ export class CopusShareDialog extends ComfyDialog {
|
||||
});
|
||||
this.LockInput = $el("input", {
|
||||
type: "text",
|
||||
placeholder: "",
|
||||
placeholder: "0",
|
||||
style: {
|
||||
width: "100px",
|
||||
padding: "7px",
|
||||
paddingLeft: "30px",
|
||||
borderRadius: "4px",
|
||||
border: "1px solid #ddd",
|
||||
boxSizing: "border-box",
|
||||
position: "relative",
|
||||
},
|
||||
oninput: (event) => {
|
||||
let input = event.target.value;
|
||||
@@ -375,7 +377,7 @@ export class CopusShareDialog extends ComfyDialog {
|
||||
});
|
||||
|
||||
const blockChainSection_lock = $el("div", { style: sectionStyle }, [
|
||||
$el("label", { style: labelStyle }, ["6️⃣ Pay to download"]),
|
||||
$el("label", { style: labelStyle }, ["6️⃣ Download threshold"]),
|
||||
$el(
|
||||
"label",
|
||||
{
|
||||
@@ -395,6 +397,7 @@ export class CopusShareDialog extends ComfyDialog {
|
||||
marginLeft: "5px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
position: "relative",
|
||||
},
|
||||
},
|
||||
[
|
||||
@@ -408,8 +411,18 @@ export class CopusShareDialog extends ComfyDialog {
|
||||
color: "#fff",
|
||||
},
|
||||
},
|
||||
["Price US$"]
|
||||
["Unlock with"]
|
||||
),
|
||||
$el("img", {
|
||||
style: {
|
||||
width: "16px",
|
||||
height: "16px",
|
||||
position: "absolute",
|
||||
right: "75px",
|
||||
zIndex: "100",
|
||||
},
|
||||
src: "https://static.copus.io/images/admin/202507/prod/e2919a1d8f3c2d99d3b8fe27ff94b841.png",
|
||||
}),
|
||||
this.LockInput,
|
||||
]
|
||||
),
|
||||
@@ -420,7 +433,7 @@ export class CopusShareDialog extends ComfyDialog {
|
||||
{ style: { display: "flex", alignItems: "center", cursor: "pointer" } },
|
||||
[
|
||||
this.radioButtonsCheckOff_lock,
|
||||
$el(
|
||||
$el(
|
||||
"div",
|
||||
{
|
||||
style: {
|
||||
@@ -429,9 +442,7 @@ export class CopusShareDialog extends ComfyDialog {
|
||||
alignItems: "center",
|
||||
},
|
||||
},
|
||||
[
|
||||
$el("span", { style: { marginLeft: "5px" } }, ["OFF"]),
|
||||
]
|
||||
[$el("span", { style: { marginLeft: "5px" } }, ["OFF"])]
|
||||
),
|
||||
]
|
||||
),
|
||||
@@ -440,7 +451,6 @@ export class CopusShareDialog extends ComfyDialog {
|
||||
"p",
|
||||
{ style: { fontSize: "16px", color: "#fff", margin: "10px 0 0 0" } },
|
||||
[
|
||||
"Get paid from your workflow. You can change the price and withdraw your earnings on Copus.",
|
||||
]
|
||||
),
|
||||
]);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
.cn-manager {
|
||||
--grid-font: -apple-system, BlinkMacSystemFont, "Segue UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
|
||||
--grid-font: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
|
||||
z-index: 1099;
|
||||
width: 80%;
|
||||
height: 80%;
|
||||
|
||||
@@ -1626,17 +1626,35 @@ export class CustomNodesManager {
|
||||
getNodesInWorkflow() {
|
||||
let usedGroupNodes = new Set();
|
||||
let allUsedNodes = {};
|
||||
const visitedGraphs = new Set();
|
||||
|
||||
for(let k in app.graph._nodes) {
|
||||
let node = app.graph._nodes[k];
|
||||
const visitGraph = (graph) => {
|
||||
if (!graph || visitedGraphs.has(graph)) return;
|
||||
visitedGraphs.add(graph);
|
||||
|
||||
if(node.type.startsWith('workflow>')) {
|
||||
usedGroupNodes.add(node.type.slice(9));
|
||||
continue;
|
||||
const nodes = graph._nodes || graph.nodes || [];
|
||||
for(let k in nodes) {
|
||||
let node = nodes[k];
|
||||
if (!node) continue;
|
||||
|
||||
// If it's a SubgraphNode, recurse into its graph and continue searching
|
||||
if (node.isSubgraphNode?.() && node.subgraph) {
|
||||
visitGraph(node.subgraph);
|
||||
}
|
||||
|
||||
if (!node.type) continue;
|
||||
|
||||
// Group nodes / components
|
||||
if(typeof node.type === 'string' && node.type.startsWith('workflow>')) {
|
||||
usedGroupNodes.add(node.type.slice(9));
|
||||
continue;
|
||||
}
|
||||
|
||||
allUsedNodes[node.type] = node;
|
||||
}
|
||||
};
|
||||
|
||||
allUsedNodes[node.type] = node;
|
||||
}
|
||||
visitGraph(app.graph);
|
||||
|
||||
for(let k of usedGroupNodes) {
|
||||
let subnodes = app.graph.extra.groupNodes[k]?.nodes;
|
||||
|
||||
@@ -41,11 +41,12 @@ from ..common.enums import NetworkMode, SecurityLevel, DBMode
|
||||
from ..common import context
|
||||
|
||||
|
||||
version_code = [4, 0]
|
||||
version_code = [4, 0, 3]
|
||||
version_str = f"V{version_code[0]}.{version_code[1]}" + (f'.{version_code[2]}' if len(version_code) > 2 else '')
|
||||
|
||||
|
||||
DEFAULT_CHANNEL = "https://raw.githubusercontent.com/Comfy-Org/ComfyUI-Manager/main"
|
||||
DEFAULT_CHANNEL_LEGACY = "https://raw.githubusercontent.com/ltdrdata/ComfyUI-Manager/main"
|
||||
|
||||
|
||||
default_custom_nodes_path = None
|
||||
@@ -160,7 +161,7 @@ comfy_ui_revision = "Unknown"
|
||||
comfy_ui_commit_datetime = datetime(1900, 1, 1, 0, 0, 0)
|
||||
|
||||
channel_dict = None
|
||||
valid_channels = {'default', 'local'}
|
||||
valid_channels = {'default', 'local', DEFAULT_CHANNEL, DEFAULT_CHANNEL_LEGACY}
|
||||
channel_list = None
|
||||
|
||||
|
||||
@@ -1390,6 +1391,7 @@ class UnifiedManager:
|
||||
return ManagedResult('skip')
|
||||
elif self.is_disabled(node_id):
|
||||
return self.unified_enable(node_id)
|
||||
|
||||
else:
|
||||
version_spec = self.resolve_unspecified_version(node_id)
|
||||
|
||||
@@ -1911,6 +1913,27 @@ def execute_install_script(url, repo_path, lazy_mode=False, instant_execution=Fa
|
||||
return True
|
||||
|
||||
|
||||
def install_manager_requirements(repo_path):
|
||||
"""
|
||||
Install packages from manager_requirements.txt if it exists.
|
||||
This is specifically for ComfyUI's manager_requirements.txt.
|
||||
"""
|
||||
manager_requirements_path = os.path.join(repo_path, "manager_requirements.txt")
|
||||
if not os.path.exists(manager_requirements_path):
|
||||
return
|
||||
|
||||
logging.info("[ComfyUI-Manager] Installing manager_requirements.txt")
|
||||
with open(manager_requirements_path, "r") as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if line and not line.startswith('#'):
|
||||
if '#' in line:
|
||||
line = line.split('#')[0].strip()
|
||||
if line:
|
||||
install_cmd = manager_util.make_pip_cmd(["install", line])
|
||||
subprocess.run(install_cmd)
|
||||
|
||||
|
||||
def git_repo_update_check_with(path, do_fetch=False, do_update=False, no_deps=False):
|
||||
"""
|
||||
|
||||
@@ -2430,6 +2453,7 @@ def update_to_stable_comfyui(repo_path):
|
||||
else:
|
||||
logging.info(f"[ComfyUI-Manager] Updating ComfyUI: {current_tag} -> {latest_tag}")
|
||||
repo.git.checkout(latest_tag)
|
||||
execute_install_script("ComfyUI", repo_path, instant_execution=False, no_deps=False)
|
||||
return 'updated', latest_tag
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
@@ -2561,9 +2585,13 @@ def check_state_of_git_node_pack_single(item, do_fetch=False, do_update_check=Tr
|
||||
|
||||
|
||||
def get_installed_pip_packages():
|
||||
# extract pip package infos
|
||||
cmd = manager_util.make_pip_cmd(['freeze'])
|
||||
pips = subprocess.check_output(cmd, text=True).split('\n')
|
||||
try:
|
||||
# extract pip package infos
|
||||
cmd = manager_util.make_pip_cmd(['freeze'])
|
||||
pips = subprocess.check_output(cmd, text=True).split('\n')
|
||||
except Exception as e:
|
||||
logging.warning("[ComfyUI-Manager] Could not enumerate pip packages for snapshot: %s", e)
|
||||
return {}
|
||||
|
||||
res = {}
|
||||
for x in pips:
|
||||
|
||||
@@ -561,6 +561,8 @@ async def task_worker():
|
||||
logging.error("ComfyUI update failed")
|
||||
return "fail"
|
||||
elif res == "updated":
|
||||
core.install_manager_requirements(repo_path)
|
||||
|
||||
if is_stable:
|
||||
logging.info("ComfyUI is updated to latest stable version.")
|
||||
return "success-stable-"+latest_tag
|
||||
@@ -1072,12 +1074,15 @@ async def fetch_customnode_list(request):
|
||||
if channel != 'local':
|
||||
found = 'custom'
|
||||
|
||||
for name, url in core.get_channel_dict().items():
|
||||
if url == channel:
|
||||
found = name
|
||||
break
|
||||
if channel == core.DEFAULT_CHANNEL or channel == core.DEFAULT_CHANNEL_LEGACY:
|
||||
channel = 'default'
|
||||
else:
|
||||
for name, url in core.get_channel_dict().items():
|
||||
if url == channel:
|
||||
found = name
|
||||
break
|
||||
|
||||
channel = found
|
||||
channel = found
|
||||
|
||||
result = dict(channel=channel, node_packs=node_packs.to_dict())
|
||||
|
||||
@@ -1308,6 +1313,65 @@ async def import_fail_info(request):
|
||||
return web.Response(status=400)
|
||||
|
||||
|
||||
@routes.post("/v2/customnode/import_fail_info_bulk")
|
||||
async def import_fail_info_bulk(request):
|
||||
try:
|
||||
json_data = await request.json()
|
||||
|
||||
# Basic validation - ensure we have either cnr_ids or urls
|
||||
if not isinstance(json_data, dict):
|
||||
return web.Response(status=400, text="Request body must be a JSON object")
|
||||
|
||||
if "cnr_ids" not in json_data and "urls" not in json_data:
|
||||
return web.Response(
|
||||
status=400, text="Either 'cnr_ids' or 'urls' field is required"
|
||||
)
|
||||
|
||||
await core.unified_manager.reload('cache')
|
||||
await core.unified_manager.get_custom_nodes('default', 'cache')
|
||||
|
||||
results = {}
|
||||
|
||||
if "cnr_ids" in json_data:
|
||||
if not isinstance(json_data["cnr_ids"], list):
|
||||
return web.Response(status=400, text="'cnr_ids' must be an array")
|
||||
for cnr_id in json_data["cnr_ids"]:
|
||||
if not isinstance(cnr_id, str):
|
||||
results[cnr_id] = {"error": "cnr_id must be a string"}
|
||||
continue
|
||||
module_name = core.unified_manager.get_module_name(cnr_id)
|
||||
if module_name is not None:
|
||||
info = cm_global.error_dict.get(module_name)
|
||||
if info is not None:
|
||||
results[cnr_id] = info
|
||||
else:
|
||||
results[cnr_id] = None
|
||||
else:
|
||||
results[cnr_id] = None
|
||||
|
||||
if "urls" in json_data:
|
||||
if not isinstance(json_data["urls"], list):
|
||||
return web.Response(status=400, text="'urls' must be an array")
|
||||
for url in json_data["urls"]:
|
||||
if not isinstance(url, str):
|
||||
results[url] = {"error": "url must be a string"}
|
||||
continue
|
||||
module_name = core.unified_manager.get_module_name(url)
|
||||
if module_name is not None:
|
||||
info = cm_global.error_dict.get(module_name)
|
||||
if info is not None:
|
||||
results[url] = info
|
||||
else:
|
||||
results[url] = None
|
||||
else:
|
||||
results[url] = None
|
||||
|
||||
return web.json_response(results)
|
||||
except Exception as e:
|
||||
logging.error(f"[ComfyUI-Manager] Error processing bulk import fail info: {e}")
|
||||
return web.Response(status=500, text="Internal server error")
|
||||
|
||||
|
||||
@routes.post("/v2/manager/queue/reinstall")
|
||||
async def reinstall_custom_node(request):
|
||||
await uninstall_custom_node(request)
|
||||
|
||||
@@ -10,6 +10,16 @@ import hashlib
|
||||
|
||||
import folder_paths
|
||||
from server import PromptServer
|
||||
import logging
|
||||
import sys
|
||||
|
||||
|
||||
try:
|
||||
from nio import AsyncClient, LoginResponse, UploadResponse
|
||||
matrix_nio_is_available = True
|
||||
except Exception:
|
||||
logging.warning(f"[ComfyUI-Manager] The matrix sharing feature has been disabled because the `matrix-nio` dependency is not installed.\n\tTo use this feature, please run the following command:\n\t{sys.executable} -m pip install matrix-nio\n")
|
||||
matrix_nio_is_available = False
|
||||
|
||||
|
||||
def extract_model_file_names(json_data):
|
||||
@@ -192,6 +202,14 @@ async def get_esheep_workflow_and_images(request):
|
||||
return web.Response(status=200, text=json.dumps(data))
|
||||
|
||||
|
||||
@PromptServer.instance.routes.get("/v2/manager/get_matrix_dep_status")
|
||||
async def get_matrix_dep_status(request):
|
||||
if matrix_nio_is_available:
|
||||
return web.Response(status=200, text='available')
|
||||
else:
|
||||
return web.Response(status=200, text='unavailable')
|
||||
|
||||
|
||||
def set_matrix_auth(json_data):
|
||||
homeserver = json_data['homeserver']
|
||||
username = json_data['username']
|
||||
@@ -331,14 +349,12 @@ async def share_art(request):
|
||||
workflowId = upload_workflow_json["workflowId"]
|
||||
|
||||
# check if the user has provided Matrix credentials
|
||||
if "matrix" in share_destinations:
|
||||
if matrix_nio_is_available and "matrix" in share_destinations:
|
||||
comfyui_share_room_id = '!LGYSoacpJPhIfBqVfb:matrix.org'
|
||||
filename = os.path.basename(asset_filepath)
|
||||
content_type = assetFileType
|
||||
|
||||
try:
|
||||
from nio import AsyncClient, LoginResponse, UploadResponse
|
||||
|
||||
homeserver = 'matrix.org'
|
||||
if matrix_auth:
|
||||
homeserver = matrix_auth.get('homeserver', 'matrix.org')
|
||||
|
||||
@@ -5045,6 +5045,105 @@
|
||||
"size": "1.26GB"
|
||||
},
|
||||
|
||||
{
|
||||
"name": "Comfy-Org/Wan2.2 i2v high noise 14B (fp16)",
|
||||
"type": "diffusion_model",
|
||||
"base": "Wan2.2",
|
||||
"save_path": "diffusion_models/Wan2.2",
|
||||
"description": "Wan2.2 diffusion model for i2v high noise 14B (fp16)",
|
||||
"reference": "https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged",
|
||||
"filename": "wan2.2_i2v_high_noise_14B_fp16.safetensors",
|
||||
"url": "https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged/resolve/main/split_files/diffusion_models/wan2.2_i2v_high_noise_14B_fp16.safetensors",
|
||||
"size": "28.6GB"
|
||||
},
|
||||
{
|
||||
"name": "Comfy-Org/Wan2.2 i2v high noise 14B (fp8_scaled)",
|
||||
"type": "diffusion_model",
|
||||
"base": "Wan2.2",
|
||||
"save_path": "diffusion_models/Wan2.2",
|
||||
"description": "Wan2.2 diffusion model for i2v high noise 14B (fp8_scaled)",
|
||||
"reference": "https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged",
|
||||
"filename": "wan2.2_i2v_high_noise_14B_fp8_scaled.safetensors",
|
||||
"url": "https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged/resolve/main/split_files/diffusion_models/wan2.2_i2v_high_noise_14B_fp8_scaled.safetensors",
|
||||
"size": "14.3GB"
|
||||
},
|
||||
{
|
||||
"name": "Comfy-Org/Wan2.2 i2v low noise 14B (fp16)",
|
||||
"type": "diffusion_model",
|
||||
"base": "Wan2.2",
|
||||
"save_path": "diffusion_models/Wan2.2",
|
||||
"description": "Wan2.2 diffusion model for i2v low noise 14B (fp16)",
|
||||
"reference": "https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged",
|
||||
"filename": "wan2.2_i2v_low_noise_14B_fp16.safetensors",
|
||||
"url": "https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged/resolve/main/split_files/diffusion_models/wan2.2_i2v_low_noise_14B_fp16.safetensors",
|
||||
"size": "28.6GB"
|
||||
},
|
||||
{
|
||||
"name": "Comfy-Org/Wan2.2 i2v low noise 14B (fp8_scaled)",
|
||||
"type": "diffusion_model",
|
||||
"base": "Wan2.2",
|
||||
"save_path": "diffusion_models/Wan2.2",
|
||||
"description": "Wan2.2 diffusion model for i2v low noise 14B (fp8_scaled)",
|
||||
"reference": "https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged",
|
||||
"filename": "wan2.2_i2v_low_noise_14B_fp8_scaled.safetensors",
|
||||
"url": "https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged/resolve/main/split_files/diffusion_models/wan2.2_i2v_low_noise_14B_fp8_scaled.safetensors",
|
||||
"size": "14.3GB"
|
||||
},
|
||||
{
|
||||
"name": "Comfy-Org/Wan2.2 t2v high noise 14B (fp16)",
|
||||
"type": "diffusion_model",
|
||||
"base": "Wan2.2",
|
||||
"save_path": "diffusion_models/Wan2.2",
|
||||
"description": "Wan2.2 diffusion model for t2v high noise 14B (fp16)",
|
||||
"reference": "https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged",
|
||||
"filename": "wan2.2_t2v_high_noise_14B_fp16.safetensors",
|
||||
"url": "https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged/resolve/main/split_files/diffusion_models/wan2.2_t2v_high_noise_14B_fp16.safetensors",
|
||||
"size": "28.6GB"
|
||||
},
|
||||
{
|
||||
"name": "Comfy-Org/Wan2.2 t2v high noise 14B (fp8_scaled)",
|
||||
"type": "diffusion_model",
|
||||
"base": "Wan2.2",
|
||||
"save_path": "diffusion_models/Wan2.2",
|
||||
"description": "Wan2.2 diffusion model for t2v high noise 14B (fp8_scaled)",
|
||||
"reference": "https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged",
|
||||
"filename": "wan2.2_t2v_high_noise_14B_fp8_scaled.safetensors",
|
||||
"url": "https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged/resolve/main/split_files/diffusion_models/wan2.2_t2v_high_noise_14B_fp8_scaled.safetensors",
|
||||
"size": "14.3GB"
|
||||
},
|
||||
{
|
||||
"name": "Comfy-Org/Wan2.2 t2v low noise 14B (fp16)",
|
||||
"type": "diffusion_model",
|
||||
"base": "Wan2.2",
|
||||
"save_path": "diffusion_models/Wan2.2",
|
||||
"description": "Wan2.2 diffusion model for t2v low noise 14B (fp16)",
|
||||
"reference": "https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged",
|
||||
"filename": "wan2.2_t2v_low_noise_14B_fp16.safetensors",
|
||||
"url": "https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged/resolve/main/split_files/diffusion_models/wan2.2_t2v_low_noise_14B_fp16.safetensors",
|
||||
"size": "28.6GB"
|
||||
},
|
||||
{
|
||||
"name": "Comfy-Org/Wan2.2 t2v low noise 14B (fp8_scaled)",
|
||||
"type": "diffusion_model",
|
||||
"base": "Wan2.2",
|
||||
"save_path": "diffusion_models/Wan2.2",
|
||||
"description": "Wan2.2 diffusion model for t2v low noise 14B (fp8_scaled)",
|
||||
"reference": "https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged",
|
||||
"filename": "wan2.2_t2v_low_noise_14B_fp8_scaled.safetensors",
|
||||
"url": "https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged/resolve/main/split_files/diffusion_models/wan2.2_t2v_low_noise_14B_fp8_scaled.safetensors",
|
||||
"size": "14.3GB"
|
||||
},
|
||||
{
|
||||
"name": "Comfy-Org/Wan2.2 ti2v 5B (fp16)",
|
||||
"type": "diffusion_model",
|
||||
"base": "Wan2.2",
|
||||
"save_path": "diffusion_models/Wan2.2",
|
||||
"description": "Wan2.2 diffusion model for ti2v 5B (fp16)",
|
||||
"reference": "https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged",
|
||||
"filename": "wan2.2_ti2v_5B_fp16.safetensors",
|
||||
"url": "https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged/resolve/main/split_files/diffusion_models/wan2.2_ti2v_5B_fp16.safetensors",
|
||||
"size": "10.0GB"
|
||||
},
|
||||
|
||||
{
|
||||
"name": "Comfy-Org/umt5_xxl_fp16.safetensors",
|
||||
@@ -5256,6 +5355,317 @@
|
||||
"filename": "LBM_relighting.safetensors",
|
||||
"url": "https://huggingface.co/jasperai/LBM_relighting/resolve/main/model.safetensors",
|
||||
"size": "5.02GB"
|
||||
},
|
||||
{
|
||||
"name": "Qwen-Image VAE",
|
||||
"type": "VAE",
|
||||
"base": "Qwen-Image",
|
||||
"save_path": "vae/qwen-image",
|
||||
"description": "VAE model for Qwen-Image",
|
||||
"reference": "https://huggingface.co/Comfy-Org/Qwen-Image_ComfyUI",
|
||||
"filename": "qwen_image_vae.safetensors",
|
||||
"url": "https://huggingface.co/Comfy-Org/Qwen-Image_ComfyUI/resolve/main/split_files/vae/qwen_image_vae.safetensors",
|
||||
"size": "335MB"
|
||||
},
|
||||
{
|
||||
"name": "Qwen 2.5 VL 7B Text Encoder (fp8_scaled)",
|
||||
"type": "clip",
|
||||
"base": "Qwen-2.5-VL",
|
||||
"save_path": "text_encoders/qwen",
|
||||
"description": "Qwen 2.5 VL 7B text encoder model (fp8_scaled)",
|
||||
"reference": "https://huggingface.co/Comfy-Org/Qwen-Image_ComfyUI",
|
||||
"filename": "qwen_2.5_vl_7b_fp8_scaled.safetensors",
|
||||
"url": "https://huggingface.co/Comfy-Org/Qwen-Image_ComfyUI/resolve/main/split_files/text_encoders/qwen_2.5_vl_7b_fp8_scaled.safetensors",
|
||||
"size": "3.75GB"
|
||||
},
|
||||
{
|
||||
"name": "Qwen 2.5 VL 7B Text Encoder",
|
||||
"type": "clip",
|
||||
"base": "Qwen-2.5-VL",
|
||||
"save_path": "text_encoders/qwen",
|
||||
"description": "Qwen 2.5 VL 7B text encoder model",
|
||||
"reference": "https://huggingface.co/Comfy-Org/Qwen-Image_ComfyUI",
|
||||
"filename": "qwen_2.5_vl_7b.safetensors",
|
||||
"url": "https://huggingface.co/Comfy-Org/Qwen-Image_ComfyUI/resolve/main/split_files/text_encoders/qwen_2.5_vl_7b.safetensors",
|
||||
"size": "7.51GB"
|
||||
},
|
||||
{
|
||||
"name": "Qwen-Image Diffusion Model (fp8_e4m3fn)",
|
||||
"type": "diffusion_model",
|
||||
"base": "Qwen-Image",
|
||||
"save_path": "diffusion_models/qwen-image",
|
||||
"description": "Qwen-Image diffusion model (fp8_e4m3fn)",
|
||||
"reference": "https://huggingface.co/Comfy-Org/Qwen-Image_ComfyUI",
|
||||
"filename": "qwen_image_fp8_e4m3fn.safetensors",
|
||||
"url": "https://huggingface.co/Comfy-Org/Qwen-Image_ComfyUI/resolve/main/split_files/diffusion_models/qwen_image_fp8_e4m3fn.safetensors",
|
||||
"size": "4.89GB"
|
||||
},
|
||||
{
|
||||
"name": "Qwen-Image Diffusion Model (bf16)",
|
||||
"type": "diffusion_model",
|
||||
"base": "Qwen-Image",
|
||||
"save_path": "diffusion_models/qwen-image",
|
||||
"description": "Qwen-Image diffusion model (bf16)",
|
||||
"reference": "https://huggingface.co/Comfy-Org/Qwen-Image_ComfyUI",
|
||||
"filename": "qwen_image_bf16.safetensors",
|
||||
"url": "https://huggingface.co/Comfy-Org/Qwen-Image_ComfyUI/resolve/main/split_files/diffusion_models/qwen_image_bf16.safetensors",
|
||||
"size": "9.78GB"
|
||||
},
|
||||
{
|
||||
"name": "Qwen-Image-Edit 2509 Diffusion Model (fp8_e4m3fn)",
|
||||
"type": "diffusion_model",
|
||||
"base": "Qwen-Image-Edit",
|
||||
"save_path": "diffusion_models/qwen-image-edit",
|
||||
"description": "Qwen-Image-Edit 2509 diffusion model (fp8_e4m3fn)",
|
||||
"reference": "https://huggingface.co/Comfy-Org/Qwen-Image-Edit_ComfyUI",
|
||||
"filename": "qwen_image_edit_2509_fp8_e4m3fn.safetensors",
|
||||
"url": "https://huggingface.co/Comfy-Org/Qwen-Image-Edit_ComfyUI/resolve/main/split_files/diffusion_models/qwen_image_edit_2509_fp8_e4m3fn.safetensors",
|
||||
"size": "4.89GB"
|
||||
},
|
||||
|
||||
{
|
||||
"name": "Qwen-Image-Edit 2509 Diffusion Model (bf16)",
|
||||
"type": "diffusion_model",
|
||||
"base": "Qwen-Image-Edit",
|
||||
"save_path": "diffusion_models/qwen-image-edit",
|
||||
"description": "Qwen-Image-Edit 2509 diffusion model (bf16)",
|
||||
"reference": "https://huggingface.co/Comfy-Org/Qwen-Image-Edit_ComfyUI",
|
||||
"filename": "qwen_image_edit_2509_bf16.safetensors",
|
||||
"url": "https://huggingface.co/Comfy-Org/Qwen-Image-Edit_ComfyUI/resolve/main/split_files/diffusion_models/qwen_image_edit_2509_bf16.safetensors",
|
||||
"size": "9.78GB"
|
||||
},
|
||||
|
||||
{
|
||||
"name": "Qwen-Image-Edit Diffusion Model (fp8_e4m3fn)",
|
||||
"type": "diffusion_model",
|
||||
"base": "Qwen-Image-Edit",
|
||||
"save_path": "diffusion_models/qwen-image-edit",
|
||||
"description": "Qwen-Image-Edit diffusion model (fp8_e4m3fn)",
|
||||
"reference": "https://huggingface.co/Comfy-Org/Qwen-Image-Edit_ComfyUI",
|
||||
"filename": "qwen_image_edit_fp8_e4m3fn.safetensors",
|
||||
"url": "https://huggingface.co/Comfy-Org/Qwen-Image-Edit_ComfyUI/resolve/main/split_files/diffusion_models/qwen_image_edit_fp8_e4m3fn.safetensors",
|
||||
"size": "4.89GB"
|
||||
},
|
||||
|
||||
{
|
||||
"name": "Qwen-Image-Edit Diffusion Model (bf16)",
|
||||
"type": "diffusion_model",
|
||||
"base": "Qwen-Image-Edit",
|
||||
"save_path": "diffusion_models/qwen-image-edit",
|
||||
"description": "Qwen-Image-Edit diffusion model (bf16)",
|
||||
"reference": "https://huggingface.co/Comfy-Org/Qwen-Image-Edit_ComfyUI",
|
||||
"filename": "qwen_image_edit_bf16.safetensors",
|
||||
"url": "https://huggingface.co/Comfy-Org/Qwen-Image-Edit_ComfyUI/resolve/main/split_files/diffusion_models/qwen_image_edit_bf16.safetensors",
|
||||
"size": "9.78GB"
|
||||
},
|
||||
{
|
||||
"name": "Qwen-Image-Lightning 8steps V1.0",
|
||||
"type": "lora",
|
||||
"base": "Qwen-Image",
|
||||
"save_path": "loras/qwen-image-lightning",
|
||||
"description": "Qwen-Image-Lightning 8-step LoRA model V1.0",
|
||||
"reference": "https://huggingface.co/lightx2v/Qwen-Image-Lightning",
|
||||
"filename": "Qwen-Image-Lightning-8steps-V1.0.safetensors",
|
||||
"url": "https://huggingface.co/lightx2v/Qwen-Image-Lightning/resolve/main/Qwen-Image-Lightning-8steps-V1.0.safetensors",
|
||||
"size": "9.78GB"
|
||||
},
|
||||
{
|
||||
"name": "Qwen-Image-Lightning 4steps V1.0",
|
||||
"type": "lora",
|
||||
"base": "Qwen-Image",
|
||||
"save_path": "loras/qwen-image-lightning",
|
||||
"description": "Qwen-Image-Lightning 4-step LoRA model V1.0",
|
||||
"reference": "https://huggingface.co/lightx2v/Qwen-Image-Lightning",
|
||||
"filename": "Qwen-Image-Lightning-4steps-V1.0.safetensors",
|
||||
"url": "https://huggingface.co/lightx2v/Qwen-Image-Lightning/resolve/main/Qwen-Image-Lightning-4steps-V1.0.safetensors",
|
||||
"size": "9.78GB"
|
||||
},
|
||||
{
|
||||
"name": "Qwen-Image-Lightning 4steps V1.0 (bf16)",
|
||||
"type": "lora",
|
||||
"base": "Qwen-Image",
|
||||
"save_path": "loras/qwen-image-lightning",
|
||||
"description": "Qwen-Image-Lightning 4-step LoRA model V1.0 (bf16)",
|
||||
"reference": "https://huggingface.co/lightx2v/Qwen-Image-Lightning",
|
||||
"filename": "Qwen-Image-Lightning-4steps-V1.0-bf16.safetensors",
|
||||
"url": "https://huggingface.co/lightx2v/Qwen-Image-Lightning/resolve/main/Qwen-Image-Lightning-4steps-V1.0-bf16.safetensors",
|
||||
"size": "19.6GB"
|
||||
},
|
||||
{
|
||||
"name": "Qwen-Image-Lightning 4steps V2.0",
|
||||
"type": "lora",
|
||||
"base": "Qwen-Image",
|
||||
"save_path": "loras/qwen-image-lightning",
|
||||
"description": "Qwen-Image-Lightning 4-step LoRA model V2.0",
|
||||
"reference": "https://huggingface.co/lightx2v/Qwen-Image-Lightning",
|
||||
"filename": "Qwen-Image-Lightning-4steps-V2.0.safetensors",
|
||||
"url": "https://huggingface.co/lightx2v/Qwen-Image-Lightning/resolve/main/Qwen-Image-Lightning-4steps-V2.0.safetensors",
|
||||
"size": "9.78GB"
|
||||
},
|
||||
{
|
||||
"name": "Qwen-Image-Lightning 4steps V2.0 (bf16)",
|
||||
"type": "lora",
|
||||
"base": "Qwen-Image",
|
||||
"save_path": "loras/qwen-image-lightning",
|
||||
"description": "Qwen-Image-Lightning 4-step LoRA model V2.0 (bf16)",
|
||||
"reference": "https://huggingface.co/lightx2v/Qwen-Image-Lightning",
|
||||
"filename": "Qwen-Image-Lightning-4steps-V2.0-bf16.safetensors",
|
||||
"url": "https://huggingface.co/lightx2v/Qwen-Image-Lightning/resolve/main/Qwen-Image-Lightning-4steps-V2.0-bf16.safetensors",
|
||||
"size": "19.6GB"
|
||||
},
|
||||
{
|
||||
"name": "Qwen-Image-Lightning 8steps V1.1",
|
||||
"type": "lora",
|
||||
"base": "Qwen-Image",
|
||||
"save_path": "loras/qwen-image-lightning",
|
||||
"description": "Qwen-Image-Lightning 8-step LoRA model V1.1",
|
||||
"reference": "https://huggingface.co/lightx2v/Qwen-Image-Lightning",
|
||||
"filename": "Qwen-Image-Lightning-8steps-V1.1.safetensors",
|
||||
"url": "https://huggingface.co/lightx2v/Qwen-Image-Lightning/resolve/main/Qwen-Image-Lightning-8steps-V1.1.safetensors",
|
||||
"size": "9.78GB"
|
||||
},
|
||||
{
|
||||
"name": "Qwen-Image-Lightning 8steps V1.1 (bf16)",
|
||||
"type": "lora",
|
||||
"base": "Qwen-Image",
|
||||
"save_path": "loras/qwen-image-lightning",
|
||||
"description": "Qwen-Image-Lightning 8-step LoRA model V1.1 (bf16)",
|
||||
"reference": "https://huggingface.co/lightx2v/Qwen-Image-Lightning",
|
||||
"filename": "Qwen-Image-Lightning-8steps-V1.1-bf16.safetensors",
|
||||
"url": "https://huggingface.co/lightx2v/Qwen-Image-Lightning/resolve/main/Qwen-Image-Lightning-8steps-V1.1-bf16.safetensors",
|
||||
"size": "19.6GB"
|
||||
},
|
||||
{
|
||||
"name": "Qwen-Image-Lightning 8steps V2.0",
|
||||
"type": "lora",
|
||||
"base": "Qwen-Image",
|
||||
"save_path": "loras/qwen-image-lightning",
|
||||
"description": "Qwen-Image-Lightning 8-step LoRA model V2.0",
|
||||
"reference": "https://huggingface.co/lightx2v/Qwen-Image-Lightning",
|
||||
"filename": "Qwen-Image-Lightning-8steps-V2.0.safetensors",
|
||||
"url": "https://huggingface.co/lightx2v/Qwen-Image-Lightning/resolve/main/Qwen-Image-Lightning-8steps-V2.0.safetensors",
|
||||
"size": "9.78GB"
|
||||
},
|
||||
{
|
||||
"name": "Qwen-Image-Lightning 8steps V2.0 (bf16)",
|
||||
"type": "lora",
|
||||
"base": "Qwen-Image",
|
||||
"save_path": "loras/qwen-image-lightning",
|
||||
"description": "Qwen-Image-Lightning 8-step LoRA model V2.0 (bf16)",
|
||||
"reference": "https://huggingface.co/lightx2v/Qwen-Image-Lightning",
|
||||
"filename": "Qwen-Image-Lightning-8steps-V2.0-bf16.safetensors",
|
||||
"url": "https://huggingface.co/lightx2v/Qwen-Image-Lightning/resolve/main/Qwen-Image-Lightning-8steps-V2.0-bf16.safetensors",
|
||||
"size": "19.6GB"
|
||||
},
|
||||
{
|
||||
"name": "Qwen-Image-Edit-Lightning 4steps V1.0",
|
||||
"type": "lora",
|
||||
"base": "Qwen-Image-Edit",
|
||||
"save_path": "loras/qwen-image-edit-lightning",
|
||||
"description": "Qwen-Image-Edit-Lightning 4-step LoRA model V1.0",
|
||||
"reference": "https://huggingface.co/lightx2v/Qwen-Image-Lightning",
|
||||
"filename": "Qwen-Image-Edit-Lightning-4steps-V1.0.safetensors",
|
||||
"url": "https://huggingface.co/lightx2v/Qwen-Image-Lightning/resolve/main/Qwen-Image-Edit-Lightning-4steps-V1.0.safetensors",
|
||||
"size": "9.78GB"
|
||||
},
|
||||
{
|
||||
"name": "Qwen-Image-Edit-Lightning 4steps V1.0 (bf16)",
|
||||
"type": "lora",
|
||||
"base": "Qwen-Image-Edit",
|
||||
"save_path": "loras/qwen-image-edit-lightning",
|
||||
"description": "Qwen-Image-Edit-Lightning 4-step LoRA model V1.0 (bf16)",
|
||||
"reference": "https://huggingface.co/lightx2v/Qwen-Image-Lightning",
|
||||
"filename": "Qwen-Image-Edit-Lightning-4steps-V1.0-bf16.safetensors",
|
||||
"url": "https://huggingface.co/lightx2v/Qwen-Image-Lightning/resolve/main/Qwen-Image-Edit-Lightning-4steps-V1.0-bf16.safetensors",
|
||||
"size": "19.6GB"
|
||||
},
|
||||
{
|
||||
"name": "Qwen-Image-Edit-Lightning 8steps V1.0",
|
||||
"type": "lora",
|
||||
"base": "Qwen-Image-Edit",
|
||||
"save_path": "loras/qwen-image-edit-lightning",
|
||||
"description": "Qwen-Image-Edit-Lightning 8-step LoRA model V1.0",
|
||||
"reference": "https://huggingface.co/lightx2v/Qwen-Image-Lightning",
|
||||
"filename": "Qwen-Image-Edit-Lightning-8steps-V1.0.safetensors",
|
||||
"url": "https://huggingface.co/lightx2v/Qwen-Image-Lightning/resolve/main/Qwen-Image-Edit-Lightning-8steps-V1.0.safetensors",
|
||||
"size": "9.78GB"
|
||||
},
|
||||
{
|
||||
"name": "Qwen-Image-Edit-Lightning 8steps V1.0 (bf16)",
|
||||
"type": "lora",
|
||||
"base": "Qwen-Image-Edit",
|
||||
"save_path": "loras/qwen-image-edit-lightning",
|
||||
"description": "Qwen-Image-Edit-Lightning 8-step LoRA model V1.0 (bf16)",
|
||||
"reference": "https://huggingface.co/lightx2v/Qwen-Image-Lightning",
|
||||
"filename": "Qwen-Image-Edit-Lightning-8steps-V1.0-bf16.safetensors",
|
||||
"url": "https://huggingface.co/lightx2v/Qwen-Image-Lightning/resolve/main/Qwen-Image-Edit-Lightning-8steps-V1.0-bf16.safetensors",
|
||||
"size": "19.6GB"
|
||||
},
|
||||
{
|
||||
"name": "Qwen-Image-Edit-2509-Lightning 4steps V1.0 (bf16)",
|
||||
"type": "lora",
|
||||
"base": "Qwen-Image-Edit",
|
||||
"save_path": "loras/qwen-image-edit-lightning",
|
||||
"description": "Qwen-Image-Edit-2509-Lightning 4-step LoRA model V1.0 (bf16)",
|
||||
"reference": "https://huggingface.co/lightx2v/Qwen-Image-Lightning",
|
||||
"filename": "Qwen-Image-Edit-2509-Lightning-4steps-V1.0-bf16.safetensors",
|
||||
"url": "https://huggingface.co/lightx2v/Qwen-Image-Lightning/resolve/main/Qwen-Image-Edit-2509/Qwen-Image-Edit-2509-Lightning-4steps-V1.0-bf16.safetensors",
|
||||
"size": "19.6GB"
|
||||
},
|
||||
{
|
||||
"name": "Qwen-Image-Edit-2509-Lightning 4steps V1.0 (fp32)",
|
||||
"type": "lora",
|
||||
"base": "Qwen-Image-Edit",
|
||||
"save_path": "loras/qwen-image-edit-lightning",
|
||||
"description": "Qwen-Image-Edit-2509-Lightning 4-step LoRA model V1.0 (fp32)",
|
||||
"reference": "https://huggingface.co/lightx2v/Qwen-Image-Lightning",
|
||||
"filename": "Qwen-Image-Edit-2509-Lightning-4steps-V1.0-fp32.safetensors",
|
||||
"url": "https://huggingface.co/lightx2v/Qwen-Image-Lightning/resolve/main/Qwen-Image-Edit-2509/Qwen-Image-Edit-2509-Lightning-4steps-V1.0-fp32.safetensors",
|
||||
"size": "39.1GB"
|
||||
},
|
||||
{
|
||||
"name": "Qwen-Image-Edit-2509-Lightning 8steps V1.0 (bf16)",
|
||||
"type": "lora",
|
||||
"base": "Qwen-Image-Edit",
|
||||
"save_path": "loras/qwen-image-edit-lightning",
|
||||
"description": "Qwen-Image-Edit-2509-Lightning 8-step LoRA model V1.0 (bf16)",
|
||||
"reference": "https://huggingface.co/lightx2v/Qwen-Image-Lightning",
|
||||
"filename": "Qwen-Image-Edit-2509-Lightning-8steps-V1.0-bf16.safetensors",
|
||||
"url": "https://huggingface.co/lightx2v/Qwen-Image-Lightning/resolve/main/Qwen-Image-Edit-2509/Qwen-Image-Edit-2509-Lightning-8steps-V1.0-bf16.safetensors",
|
||||
"size": "19.6GB"
|
||||
},
|
||||
{
|
||||
"name": "Qwen-Image-Edit-2509-Lightning 8steps V1.0 (fp32)",
|
||||
"type": "lora",
|
||||
"base": "Qwen-Image-Edit",
|
||||
"save_path": "loras/qwen-image-edit-lightning",
|
||||
"description": "Qwen-Image-Edit-2509-Lightning 8-step LoRA model V1.0 (fp32)",
|
||||
"reference": "https://huggingface.co/lightx2v/Qwen-Image-Lightning",
|
||||
"filename": "Qwen-Image-Edit-2509-Lightning-8steps-V1.0-fp32.safetensors",
|
||||
"url": "https://huggingface.co/lightx2v/Qwen-Image-Lightning/resolve/main/Qwen-Image-Edit-2509/Qwen-Image-Edit-2509-Lightning-8steps-V1.0-fp32.safetensors",
|
||||
"size": "39.1GB"
|
||||
},
|
||||
{
|
||||
"name": "Qwen-Image InstantX ControlNet Union",
|
||||
"type": "controlnet",
|
||||
"base": "Qwen-Image",
|
||||
"save_path": "controlnet/qwen-image/instantx",
|
||||
"description": "Qwen-Image InstantX ControlNet Union model",
|
||||
"reference": "https://huggingface.co/Comfy-Org/Qwen-Image-InstantX-ControlNets",
|
||||
"filename": "Qwen-Image-InstantX-ControlNet-Union.safetensors",
|
||||
"url": "https://huggingface.co/Comfy-Org/Qwen-Image-InstantX-ControlNets/resolve/main/split_files/controlnet/Qwen-Image-InstantX-ControlNet-Union.safetensors",
|
||||
"size": "2.54GB"
|
||||
},
|
||||
{
|
||||
"name": "Qwen-Image InstantX ControlNet Inpainting",
|
||||
"type": "controlnet",
|
||||
"base": "Qwen-Image",
|
||||
"save_path": "controlnet/qwen-image/instantx",
|
||||
"description": "Qwen-Image InstantX ControlNet Inpainting model",
|
||||
"reference": "https://huggingface.co/Comfy-Org/Qwen-Image-InstantX-ControlNets",
|
||||
"filename": "Qwen-Image-InstantX-ControlNet-Inpainting.safetensors",
|
||||
"url": "https://huggingface.co/Comfy-Org/Qwen-Image-InstantX-ControlNets/resolve/main/split_files/controlnet/Qwen-Image-InstantX-ControlNet-Inpainting.safetensors",
|
||||
"size": "2.54GB"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -80,7 +80,7 @@ cm_global.register_api('cm.is_import_failed_extension', is_import_failed_extensi
|
||||
comfyui_manager_path = os.path.abspath(os.path.dirname(__file__))
|
||||
|
||||
custom_nodes_base_path = folder_paths.get_folder_paths('custom_nodes')[0]
|
||||
manager_files_path = os.path.abspath(os.path.join(folder_paths.get_user_directory(), 'default', 'ComfyUI-Manager'))
|
||||
manager_files_path = folder_paths.get_system_user_directory("manager")
|
||||
manager_pip_overrides_path = os.path.join(manager_files_path, "pip_overrides.json")
|
||||
manager_pip_blacklist_path = os.path.join(manager_files_path, "pip_blacklist.list")
|
||||
restore_snapshot_path = os.path.join(manager_files_path, "startup-scripts", "restore-snapshot.json")
|
||||
@@ -483,7 +483,7 @@ check_bypass_ssl()
|
||||
|
||||
# Perform install
|
||||
processed_install = set()
|
||||
script_list_path = os.path.join(folder_paths.user_directory, "default", "ComfyUI-Manager", "startup-scripts", "install-scripts.txt")
|
||||
script_list_path = os.path.join(manager_files_path, "startup-scripts", "install-scripts.txt")
|
||||
pip_fixer = manager_util.PIPFixer(manager_util.get_installed_packages(), comfy_path, manager_files_path)
|
||||
|
||||
|
||||
|
||||
+2
-2
@@ -139,9 +139,9 @@ You can set whether to use ComfyUI-Manager solely via CLI.
|
||||
`restore-dependencies`
|
||||
|
||||
* This command can be used if custom nodes are installed under the `ComfyUI/custom_nodes` path but their dependencies are not installed.
|
||||
* It is useful when starting a new cloud instance, like colab, where dependencies need to be reinstalled and installation scripts re-executed.
|
||||
* It is useful when starting a new cloud instance, like Colab, where dependencies need to be reinstalled and installation scripts re-executed.
|
||||
* It can also be utilized if ComfyUI is reinstalled and only the custom_nodes path has been backed up and restored.
|
||||
|
||||
### 7. Clear
|
||||
|
||||
In the GUI, installations, updates, or snapshot restorations are scheduled to execute the next time ComfyUI is launched. The `clear` command clears this scheduled state, ensuring no pre-execution actions are applied.
|
||||
In the GUI, installations, updates, or snapshot restorations are scheduled to execute the next time ComfyUI is launched. The `clear` command clears this scheduled state, ensuring no pre-execution actions are applied.
|
||||
|
||||
+13
-13
@@ -23,13 +23,13 @@ OPTIONS:
|
||||
## How To Use?
|
||||
* `python cm-cli.py` 를 통해서 실행 시킬 수 있습니다.
|
||||
* 예를 들어 custom node를 모두 업데이트 하고 싶다면
|
||||
* ComfyUI-Manager경로 에서 `python cm-cli.py update all` 를 command를 실행할 수 있습니다.
|
||||
* ComfyUI-Manager 경로에서 `python cm-cli.py update all` 명령을 실행할 수 있습니다.
|
||||
* ComfyUI 경로에서 실행한다면, `python custom_nodes/ComfyUI-Manager/cm-cli.py update all` 와 같이 cm-cli.py 의 경로를 지정할 수도 있습니다.
|
||||
|
||||
## Prerequisite
|
||||
* ComfyUI 를 실행하는 python과 동일한 python 환경에서 실행해야 합니다.
|
||||
* venv를 사용할 경우 해당 venv를 activate 한 상태에서 실행해야 합니다.
|
||||
* portable 버전을 사용할 경우 run_nvidia_gpu.bat 파일이 있는 경로인 경우, 다음과 같은 방식으로 코맨드를 실행해야 합니다.
|
||||
* portable 버전을 사용할 경우 run_nvidia_gpu.bat 파일이 있는 경로인 경우, 다음과 같은 방식으로 명령을 실행해야 합니다.
|
||||
`.\python_embeded\python.exe ComfyUI\custom_nodes\ComfyUI-Manager\cm-cli.py update all`
|
||||
* ComfyUI 의 경로는 COMFYUI_PATH 환경 변수로 설정할 수 있습니다. 만약 생략할 경우 다음과 같은 경고 메시지가 나타나며, ComfyUI-Manager가 설치된 경로를 기준으로 상대 경로로 설정됩니다.
|
||||
```
|
||||
@@ -40,8 +40,8 @@ OPTIONS:
|
||||
|
||||
### 1. --channel, --mode
|
||||
* 정보 보기 기능과 커스텀 노드 관리 기능의 경우는 --channel과 --mode를 통해 정보 DB를 설정할 수 있습니다.
|
||||
* 예들 들어 `python cm-cli.py update all --channel recent --mode remote`와 같은 command를 실행할 경우, 현재 ComfyUI-Manager repo에 내장된 로컬의 정보가 아닌 remote의 최신 정보를 기준으로 동작하며, recent channel에 있는 목록을 대상으로만 동작합니다.
|
||||
* --channel, --mode 는 `simple-show, show, install, uninstall, update, disable, enable, fix` command에서만 사용 가능합니다.
|
||||
* 예를 들어 `python cm-cli.py update all --channel recent --mode remote`와 같은 명령을 실행할 경우, 현재 ComfyUI-Manager repo에 내장된 로컬의 정보가 아닌 remote의 최신 정보를 기준으로 동작하며, recent channel에 있는 목록을 대상으로만 동작합니다.
|
||||
* --channel, --mode 는 `simple-show, show, install, uninstall, update, disable, enable, fix` 명령에서만 사용 가능합니다.
|
||||
|
||||
### 2. 관리 정보 보기
|
||||
|
||||
@@ -51,7 +51,7 @@ OPTIONS:
|
||||
* `[show|simple-show]` - `show`는 상세하게 정보를 보여주며, `simple-show`는 간단하게 정보를 보여줍니다.
|
||||
|
||||
|
||||
`python cm-cli.py show installed` 와 같은 코맨드를 실행하면 설치된 커스텀 노드의 정보를 상세하게 보여줍니다.
|
||||
`python cm-cli.py show installed` 와 같은 명령을 실행하면 설치된 커스텀 노드의 정보를 상세하게 보여줍니다.
|
||||
```
|
||||
-= ComfyUI-Manager CLI (V2.24) =-
|
||||
|
||||
@@ -67,7 +67,7 @@ FETCH DATA from: https://raw.githubusercontent.com/ltdrdata/ComfyUI-Manager/main
|
||||
[ DISABLED ] ComfyUI-Loopchain (author: Fannovel16)
|
||||
```
|
||||
|
||||
`python cm-cli.py simple-show installed` 와 같은 코맨드를 이용해서 설치된 커스텀 노드의 정보를 간단하게 보여줍니다.
|
||||
`python cm-cli.py simple-show installed` 와 같은 명령을 이용해서 설치된 커스텀 노드의 정보를 간단하게 보여줍니다.
|
||||
|
||||
```
|
||||
-= ComfyUI-Manager CLI (V2.24) =-
|
||||
@@ -89,7 +89,7 @@ ComfyUI-Loopchain
|
||||
* `installed`: enable, disable 여부와 상관없이 설치된 모든 노드를 보여줍니다
|
||||
* `not-installed`: 설치되지 않은 커스텀 노드의 목록을 보여줍니다.
|
||||
* `all`: 모든 커스텀 노드의 목록을 보여줍니다.
|
||||
* `snapshot`: 현재 설치된 커스텀 노드의 snapshot 정보를 보여줍니다. `show`롤 통해서 볼 경우는 json 출력 형태로 보여주며, `simple-show`를 통해서 볼 경우는 간단하게, 커밋 해시와 함께 보여줍니다.
|
||||
* `snapshot`: 현재 설치된 커스텀 노드의 snapshot 정보를 보여줍니다. `show`를 통해서 볼 경우는 json 출력 형태로 보여주며, `simple-show`를 통해서 볼 경우는 간단하게, 커밋 해시와 함께 보여줍니다.
|
||||
* `snapshot-list`: ComfyUI-Manager/snapshots 에 저장된 snapshot 파일의 목록을 보여줍니다.
|
||||
|
||||
### 3. 커스텀 노드 관리 하기
|
||||
@@ -98,7 +98,7 @@ ComfyUI-Loopchain
|
||||
|
||||
* `python cm-cli.py install ComfyUI-Impact-Pack ComfyUI-Inspire-Pack ComfyUI_experiments` 와 같이 커스텀 노드의 이름을 나열해서 관리 기능을 적용할 수 있습니다.
|
||||
* 커스텀 노드의 이름은 `show`를 했을 때 보여주는 이름이며, git repository의 이름입니다.
|
||||
(추후 nickname 을 사용가능하돌고 업데이트 할 예정입니다.)
|
||||
(추후 nickname을 사용 가능하도록 업데이트할 예정입니다.)
|
||||
|
||||
`[update|disable|enable|fix] all ?[--channel <channel name>] ?[--mode [remote|local|cache]]`
|
||||
|
||||
@@ -124,7 +124,7 @@ ComfyUI-Loopchain
|
||||
* `--pip-non-local-url`: web URL에 등록된 pip 패키지들에 대해서 복구를 수행
|
||||
* `--pip-local-url`: local 경로를 지정하고 있는 pip 패키지들에 대해서 복구를 수행
|
||||
* `--user-directory`: 사용자 디렉토리 설정
|
||||
* `--restore-to`: 복구될 커스텀 노드가 설치될 경로. (이 옵션을 적용할 경우 오직 대상 경로에 설치된 custom nodes 만 설치된 것으로 인식함.)
|
||||
* `--restore-to`: 복구될 커스텀 노드가 설치될 경로. (이 옵션을 적용할 경우 오직 대상 경로에 설치된 custom nodes만 설치된 것으로 인식함.)
|
||||
|
||||
### 5. CLI only mode
|
||||
|
||||
@@ -133,7 +133,7 @@ ComfyUI-Manager를 CLI로만 사용할 것인지를 설정할 수 있습니다.
|
||||
`cli-only-mode [enable|disable]`
|
||||
|
||||
* security 혹은 policy 의 이유로 GUI 를 통한 ComfyUI-Manager 사용을 제한하고 싶은 경우 이 모드를 사용할 수 있습니다.
|
||||
* CLI only mode를 적용할 경우 ComfyUI-Manager 가 매우 제한된 상태로 로드되어, 내부적으로 제공하는 web API가 비활성화 되며, 메인 메뉴에서도 Manager 버튼이 표시되지 않습니다.
|
||||
* CLI only mode를 적용할 경우 ComfyUI-Manager 가 매우 제한된 상태로 로드되어, 내부적으로 제공하는 web API가 비활성화되며, 메인 메뉴에서도 Manager 버튼이 표시되지 않습니다.
|
||||
|
||||
|
||||
### 6. 의존성 설치
|
||||
@@ -141,10 +141,10 @@ ComfyUI-Manager를 CLI로만 사용할 것인지를 설정할 수 있습니다.
|
||||
`restore-dependencies`
|
||||
|
||||
* `ComfyUI/custom_nodes` 하위 경로에 커스텀 노드들이 설치되어 있긴 하지만, 의존성이 설치되지 않은 경우 사용할 수 있습니다.
|
||||
* colab 과 같이 cloud instance를 새로 시작하는 경우 의존성 재설치 및 설치 스크립트가 재실행 되어야 하는 경우 사용합니다.
|
||||
* ComfyUI을 재설치할 경우, custom_nodes 경로만 백업했다가 재설치 할 경우 활용 가능합니다.
|
||||
* Colab과 같이 cloud instance를 새로 시작하는 경우 의존성 재설치 및 설치 스크립트가 재실행되어야 하는 경우 사용합니다.
|
||||
* ComfyUI를 재설치할 경우, custom_nodes 경로만 백업했다가 재설치할 경우 활용 가능합니다.
|
||||
|
||||
|
||||
### 7. clear
|
||||
|
||||
GUI에서 install, update를 하거나 snapshot 을 restore하는 경우 예약을 통해서 다음번 ComfyUI를 실행할 경우 실행되는 구조입니다. `clear` 는 이런 예약 상태를 clear해서, 아무런 사전 실행이 적용되지 않도록 합니다.
|
||||
GUI에서 install, update를 하거나 snapshot을 restore하는 경우 예약을 통해서 다음번 ComfyUI를 실행할 경우 실행되는 구조입니다. `clear` 는 이런 예약 상태를 clear해서, 아무런 사전 실행이 적용되지 않도록 합니다.
|
||||
|
||||
+2895
-709
File diff suppressed because it is too large
Load Diff
+3845
-1182
File diff suppressed because it is too large
Load Diff
+2343
-1233
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,15 @@
|
||||
{
|
||||
"custom_nodes": [
|
||||
{
|
||||
"author": "synchronicity-labs",
|
||||
"title": "ComfyUI Sync Lipsync Node",
|
||||
"reference": "https://github.com/synchronicity-labs/sync-comfyui",
|
||||
"files": [
|
||||
"https://github.com/synchronicity-labs/sync-comfyui"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "This custom node allows you to perform audio-video lip synchronization inside ComfyUI using a simple interface."
|
||||
},
|
||||
{
|
||||
"author": "joaomede",
|
||||
"title": "ComfyUI-Unload-Model-Fork",
|
||||
@@ -159,6 +169,16 @@
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "A fork of KJNodes for ComfyUI.\nVarious quality of life -nodes for ComfyUI, mostly just visual stuff to improve usability"
|
||||
},
|
||||
{
|
||||
"author": "huixingyun",
|
||||
"title": "ComfyUI-SoundFlow",
|
||||
"reference": "https://github.com/huixingyun/ComfyUI-SoundFlow",
|
||||
"files": [
|
||||
"https://github.com/huixingyun/ComfyUI-SoundFlow"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "forked from https://github.com/fredconex/ComfyUI-SoundFlow (removed)"
|
||||
}
|
||||
]
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
+553
-553
File diff suppressed because it is too large
Load Diff
+11433
-1236
File diff suppressed because it is too large
Load Diff
+101
-103
@@ -1,5 +1,106 @@
|
||||
{
|
||||
"models": [
|
||||
|
||||
{
|
||||
"name": "Comfy-Org/Wan2.2 i2v high noise 14B (fp16)",
|
||||
"type": "diffusion_model",
|
||||
"base": "Wan2.2",
|
||||
"save_path": "diffusion_models/Wan2.2",
|
||||
"description": "Wan2.2 diffusion model for i2v high noise 14B (fp16)",
|
||||
"reference": "https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged",
|
||||
"filename": "wan2.2_i2v_high_noise_14B_fp16.safetensors",
|
||||
"url": "https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged/resolve/main/split_files/diffusion_models/wan2.2_i2v_high_noise_14B_fp16.safetensors",
|
||||
"size": "28.6GB"
|
||||
},
|
||||
{
|
||||
"name": "Comfy-Org/Wan2.2 i2v high noise 14B (fp8_scaled)",
|
||||
"type": "diffusion_model",
|
||||
"base": "Wan2.2",
|
||||
"save_path": "diffusion_models/Wan2.2",
|
||||
"description": "Wan2.2 diffusion model for i2v high noise 14B (fp8_scaled)",
|
||||
"reference": "https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged",
|
||||
"filename": "wan2.2_i2v_high_noise_14B_fp8_scaled.safetensors",
|
||||
"url": "https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged/resolve/main/split_files/diffusion_models/wan2.2_i2v_high_noise_14B_fp8_scaled.safetensors",
|
||||
"size": "14.3GB"
|
||||
},
|
||||
{
|
||||
"name": "Comfy-Org/Wan2.2 i2v low noise 14B (fp16)",
|
||||
"type": "diffusion_model",
|
||||
"base": "Wan2.2",
|
||||
"save_path": "diffusion_models/Wan2.2",
|
||||
"description": "Wan2.2 diffusion model for i2v low noise 14B (fp16)",
|
||||
"reference": "https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged",
|
||||
"filename": "wan2.2_i2v_low_noise_14B_fp16.safetensors",
|
||||
"url": "https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged/resolve/main/split_files/diffusion_models/wan2.2_i2v_low_noise_14B_fp16.safetensors",
|
||||
"size": "28.6GB"
|
||||
},
|
||||
{
|
||||
"name": "Comfy-Org/Wan2.2 i2v low noise 14B (fp8_scaled)",
|
||||
"type": "diffusion_model",
|
||||
"base": "Wan2.2",
|
||||
"save_path": "diffusion_models/Wan2.2",
|
||||
"description": "Wan2.2 diffusion model for i2v low noise 14B (fp8_scaled)",
|
||||
"reference": "https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged",
|
||||
"filename": "wan2.2_i2v_low_noise_14B_fp8_scaled.safetensors",
|
||||
"url": "https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged/resolve/main/split_files/diffusion_models/wan2.2_i2v_low_noise_14B_fp8_scaled.safetensors",
|
||||
"size": "14.3GB"
|
||||
},
|
||||
{
|
||||
"name": "Comfy-Org/Wan2.2 t2v high noise 14B (fp16)",
|
||||
"type": "diffusion_model",
|
||||
"base": "Wan2.2",
|
||||
"save_path": "diffusion_models/Wan2.2",
|
||||
"description": "Wan2.2 diffusion model for t2v high noise 14B (fp16)",
|
||||
"reference": "https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged",
|
||||
"filename": "wan2.2_t2v_high_noise_14B_fp16.safetensors",
|
||||
"url": "https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged/resolve/main/split_files/diffusion_models/wan2.2_t2v_high_noise_14B_fp16.safetensors",
|
||||
"size": "28.6GB"
|
||||
},
|
||||
{
|
||||
"name": "Comfy-Org/Wan2.2 t2v high noise 14B (fp8_scaled)",
|
||||
"type": "diffusion_model",
|
||||
"base": "Wan2.2",
|
||||
"save_path": "diffusion_models/Wan2.2",
|
||||
"description": "Wan2.2 diffusion model for t2v high noise 14B (fp8_scaled)",
|
||||
"reference": "https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged",
|
||||
"filename": "wan2.2_t2v_high_noise_14B_fp8_scaled.safetensors",
|
||||
"url": "https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged/resolve/main/split_files/diffusion_models/wan2.2_t2v_high_noise_14B_fp8_scaled.safetensors",
|
||||
"size": "14.3GB"
|
||||
},
|
||||
{
|
||||
"name": "Comfy-Org/Wan2.2 t2v low noise 14B (fp16)",
|
||||
"type": "diffusion_model",
|
||||
"base": "Wan2.2",
|
||||
"save_path": "diffusion_models/Wan2.2",
|
||||
"description": "Wan2.2 diffusion model for t2v low noise 14B (fp16)",
|
||||
"reference": "https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged",
|
||||
"filename": "wan2.2_t2v_low_noise_14B_fp16.safetensors",
|
||||
"url": "https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged/resolve/main/split_files/diffusion_models/wan2.2_t2v_low_noise_14B_fp16.safetensors",
|
||||
"size": "28.6GB"
|
||||
},
|
||||
{
|
||||
"name": "Comfy-Org/Wan2.2 t2v low noise 14B (fp8_scaled)",
|
||||
"type": "diffusion_model",
|
||||
"base": "Wan2.2",
|
||||
"save_path": "diffusion_models/Wan2.2",
|
||||
"description": "Wan2.2 diffusion model for t2v low noise 14B (fp8_scaled)",
|
||||
"reference": "https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged",
|
||||
"filename": "wan2.2_t2v_low_noise_14B_fp8_scaled.safetensors",
|
||||
"url": "https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged/resolve/main/split_files/diffusion_models/wan2.2_t2v_low_noise_14B_fp8_scaled.safetensors",
|
||||
"size": "14.3GB"
|
||||
},
|
||||
{
|
||||
"name": "Comfy-Org/Wan2.2 ti2v 5B (fp16)",
|
||||
"type": "diffusion_model",
|
||||
"base": "Wan2.2",
|
||||
"save_path": "diffusion_models/Wan2.2",
|
||||
"description": "Wan2.2 diffusion model for ti2v 5B (fp16)",
|
||||
"reference": "https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged",
|
||||
"filename": "wan2.2_ti2v_5B_fp16.safetensors",
|
||||
"url": "https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged/resolve/main/split_files/diffusion_models/wan2.2_ti2v_5B_fp16.safetensors",
|
||||
"size": "10.0GB"
|
||||
},
|
||||
|
||||
{
|
||||
"name": "sam2.1_hiera_tiny.pt",
|
||||
"type": "sam2.1",
|
||||
@@ -586,109 +687,6 @@
|
||||
"filename": "llava_llama3_fp16.safetensors",
|
||||
"url": "https://huggingface.co/Comfy-Org/HunyuanVideo_repackaged/resolve/main/split_files/text_encoders/llava_llama3_fp16.safetensors",
|
||||
"size": "16.1GB"
|
||||
},
|
||||
|
||||
{
|
||||
"name": "PixArt-Sigma-XL-2-512-MS.safetensors (diffusion)",
|
||||
"type": "diffusion_model",
|
||||
"base": "pixart-sigma",
|
||||
"save_path": "diffusion_models/PixArt-Sigma",
|
||||
"description": "PixArt-Sigma Diffusion model",
|
||||
"reference": "https://huggingface.co/PixArt-alpha/PixArt-Sigma-XL-2-512-MS",
|
||||
"filename": "PixArt-Sigma-XL-2-512-MS.safetensors",
|
||||
"url": "https://huggingface.co/PixArt-alpha/PixArt-Sigma-XL-2-512-MS/resolve/main/transformer/diffusion_pytorch_model.safetensors",
|
||||
"size": "2.44GB"
|
||||
},
|
||||
{
|
||||
"name": "PixArt-Sigma-XL-2-1024-MS.safetensors (diffusion)",
|
||||
"type": "diffusion_model",
|
||||
"base": "pixart-sigma",
|
||||
"save_path": "diffusion_models/PixArt-Sigma",
|
||||
"description": "PixArt-Sigma Diffusion model",
|
||||
"reference": "https://huggingface.co/PixArt-alpha/PixArt-Sigma-XL-2-1024-MS",
|
||||
"filename": "PixArt-Sigma-XL-2-1024-MS.safetensors",
|
||||
"url": "https://huggingface.co/PixArt-alpha/PixArt-Sigma-XL-2-1024-MS/resolve/main/transformer/diffusion_pytorch_model.safetensors",
|
||||
"size": "2.44GB"
|
||||
},
|
||||
{
|
||||
"name": "PixArt-XL-2-1024-MS.safetensors (diffusion)",
|
||||
"type": "diffusion_model",
|
||||
"base": "pixart-alpha",
|
||||
"save_path": "diffusion_models/PixArt-Alpha",
|
||||
"description": "PixArt-Alpha Diffusion model",
|
||||
"reference": "https://huggingface.co/PixArt-alpha/PixArt-XL-2-1024-MS",
|
||||
"filename": "PixArt-XL-2-1024-MS.safetensors",
|
||||
"url": "https://huggingface.co/PixArt-alpha/PixArt-XL-2-1024-MS/resolve/main/transformer/diffusion_pytorch_model.safetensors",
|
||||
"size": "2.45GB"
|
||||
},
|
||||
|
||||
{
|
||||
"name": "Comfy-Org/hunyuan_video_t2v_720p_bf16.safetensors",
|
||||
"type": "diffusion_model",
|
||||
"base": "Hunyuan Video",
|
||||
"save_path": "diffusion_models/hunyuan_video",
|
||||
"description": "Huyuan Video diffusion model. repackaged version.",
|
||||
"reference": "https://huggingface.co/Comfy-Org/HunyuanVideo_repackaged",
|
||||
"filename": "hunyuan_video_t2v_720p_bf16.safetensors",
|
||||
"url": "https://huggingface.co/Comfy-Org/HunyuanVideo_repackaged/resolve/main/split_files/diffusion_models/hunyuan_video_t2v_720p_bf16.safetensors",
|
||||
"size": "25.6GB"
|
||||
},
|
||||
{
|
||||
"name": "Comfy-Org/hunyuan_video_vae_bf16.safetensors",
|
||||
"type": "VAE",
|
||||
"base": "Hunyuan Video",
|
||||
"save_path": "VAE",
|
||||
"description": "Huyuan Video VAE model. repackaged version.",
|
||||
"reference": "https://huggingface.co/Comfy-Org/HunyuanVideo_repackaged",
|
||||
"filename": "hunyuan_video_vae_bf16.safetensors",
|
||||
"url": "https://huggingface.co/Comfy-Org/HunyuanVideo_repackaged/resolve/main/split_files/vae/hunyuan_video_vae_bf16.safetensors",
|
||||
"size": "493MB"
|
||||
},
|
||||
|
||||
{
|
||||
"name": "LTX-Video 2B v0.9.1 Checkpoint",
|
||||
"type": "checkpoint",
|
||||
"base": "LTX-Video",
|
||||
"save_path": "checkpoints/LTXV",
|
||||
"description": "LTX-Video is the first DiT-based video generation model capable of generating high-quality videos in real-time. It produces 24 FPS videos at a 768x512 resolution faster than they can be watched. Trained on a large-scale dataset of diverse videos, the model generates high-resolution videos with realistic and varied content.",
|
||||
"reference": "https://huggingface.co/Lightricks/LTX-Video",
|
||||
"filename": "ltx-video-2b-v0.9.1.safetensors",
|
||||
"url": "https://huggingface.co/Lightricks/LTX-Video/resolve/main/ltx-video-2b-v0.9.1.safetensors",
|
||||
"size": "5.72GB"
|
||||
},
|
||||
|
||||
{
|
||||
"name": "XLabs-AI/flux-canny-controlnet-v3.safetensors",
|
||||
"type": "controlnet",
|
||||
"base": "FLUX.1",
|
||||
"save_path": "xlabs/controlnets",
|
||||
"description": "ControlNet checkpoints for FLUX.1-dev model by Black Forest Labs.",
|
||||
"reference": "https://huggingface.co/XLabs-AI/flux-controlnet-collections",
|
||||
"filename": "flux-canny-controlnet-v3.safetensors",
|
||||
"url": "https://huggingface.co/XLabs-AI/flux-controlnet-collections/resolve/main/flux-canny-controlnet-v3.safetensors",
|
||||
"size": "1.49GB"
|
||||
},
|
||||
{
|
||||
"name": "XLabs-AI/flux-depth-controlnet-v3.safetensors",
|
||||
"type": "controlnet",
|
||||
"base": "FLUX.1",
|
||||
"save_path": "xlabs/controlnets",
|
||||
"description": "ControlNet checkpoints for FLUX.1-dev model by Black Forest Labs.",
|
||||
"reference": "https://huggingface.co/XLabs-AI/flux-controlnet-collections",
|
||||
"filename": "flux-depth-controlnet-v3.safetensors",
|
||||
"url": "https://huggingface.co/XLabs-AI/flux-controlnet-collections/resolve/main/flux-depth-controlnet-v3.safetensors",
|
||||
"size": "1.49GB"
|
||||
},
|
||||
{
|
||||
"name": "XLabs-AI/flux-hed-controlnet-v3.safetensors",
|
||||
"type": "controlnet",
|
||||
"base": "FLUX.1",
|
||||
"save_path": "xlabs/controlnets",
|
||||
"description": "ControlNet checkpoints for FLUX.1-dev model by Black Forest Labs.",
|
||||
"reference": "https://huggingface.co/XLabs-AI/flux-controlnet-collections",
|
||||
"filename": "flux-hed-controlnet-v3.safetensors",
|
||||
"url": "https://huggingface.co/XLabs-AI/flux-controlnet-collections/resolve/main/flux-hed-controlnet-v3.safetensors",
|
||||
"size": "1.49GB"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -10,6 +10,16 @@
|
||||
"install_type": "git-clone",
|
||||
"description": "A minimal template for creating React/TypeScript frontend extensions for ComfyUI, with complete boilerplate setup including internationalization and unit testing."
|
||||
},
|
||||
{
|
||||
"author": "comfyui-wiki",
|
||||
"title": "ComfyUI-i18n-demo",
|
||||
"reference": "https://github.com/comfyui-wiki/ComfyUI-i18n-demo",
|
||||
"files": [
|
||||
"https://github.com/comfyui-wiki/ComfyUI-i18n-demo"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "ComfyUI custom node develop i18n support demo "
|
||||
},
|
||||
{
|
||||
"author": "Suzie1",
|
||||
"title": "Guide To Making Custom Nodes in ComfyUI",
|
||||
@@ -341,6 +351,26 @@
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "A minimal test suite demonstrating how remote COMBO inputs behave in ComfyUI, with and without force_input"
|
||||
},
|
||||
{
|
||||
"author": "J1mB091",
|
||||
"title": "ComfyUI-J1mB091 Custom Nodes",
|
||||
"reference": "https://github.com/J1mB091/ComfyUI-J1mB091",
|
||||
"files": [
|
||||
"https://github.com/J1mB091/ComfyUI-J1mB091"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Vibe Coded ComfyUI Custom Nodes"
|
||||
},
|
||||
{
|
||||
"author": "aiforhumans",
|
||||
"title": "XDev Nodes - Complete Toolkit",
|
||||
"reference": "https://github.com/aiforhumans/comfyui-xdev-nodes",
|
||||
"files": [
|
||||
"https://github.com/aiforhumans/comfyui-xdev-nodes"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "Complete ComfyUI development toolkit with 8 professional nodes including VAE tools, universal type testing, and comprehensive debugging infrastructure."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -782,6 +782,39 @@ components:
|
||||
minimum: 0
|
||||
default: 0
|
||||
required: [batch_id, start_time, state_before]
|
||||
|
||||
ImportFailInfoBulkRequest:
|
||||
type: object
|
||||
properties:
|
||||
cnr_ids:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
description: A list of CNR IDs to check.
|
||||
urls:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
description: A list of repository URLs to check.
|
||||
|
||||
ImportFailInfoBulkResponse:
|
||||
type: object
|
||||
additionalProperties:
|
||||
$ref: '#/components/schemas/ImportFailInfoItem'
|
||||
description: >-
|
||||
A dictionary where each key is a cnr_id or url from the request,
|
||||
and the value is the corresponding error info.
|
||||
|
||||
ImportFailInfoItem:
|
||||
oneOf:
|
||||
- type: object
|
||||
properties:
|
||||
error:
|
||||
type: string
|
||||
traceback:
|
||||
type: string
|
||||
- type: "null"
|
||||
|
||||
securitySchemes:
|
||||
securityLevel:
|
||||
type: apiKey
|
||||
@@ -1017,6 +1050,32 @@ paths:
|
||||
description: Processing started
|
||||
'201':
|
||||
description: Processing already in progress
|
||||
|
||||
/v2/customnode/import_fail_info_bulk:
|
||||
post:
|
||||
summary: Get import failure info for multiple nodes
|
||||
description: Retrieves recorded import failure information for a list of custom nodes.
|
||||
tags:
|
||||
- customnode
|
||||
requestBody:
|
||||
description: A list of CNR IDs or repository URLs to check.
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ImportFailInfoBulkRequest'
|
||||
responses:
|
||||
'200':
|
||||
description: A dictionary containing the import failure information.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ImportFailInfoBulkResponse'
|
||||
'400':
|
||||
description: Bad Request. The request body is invalid.
|
||||
'500':
|
||||
description: Internal Server Error.
|
||||
|
||||
/v2/manager/queue/reset:
|
||||
get:
|
||||
summary: Reset queue
|
||||
|
||||
+3
-3
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
|
||||
[project]
|
||||
name = "comfyui-manager"
|
||||
license = { text = "GPL-3.0-only" }
|
||||
version = "4.0.0-beta.10"
|
||||
version = "4.0.3b4"
|
||||
requires-python = ">= 3.9"
|
||||
description = "ComfyUI-Manager provides features to install and manage custom nodes for ComfyUI, as well as various functionalities to assist with ComfyUI."
|
||||
readme = "README.md"
|
||||
@@ -19,7 +19,7 @@ maintainers = [
|
||||
]
|
||||
|
||||
classifiers = [
|
||||
"Development Status :: 4 - Beta",
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
"Intended Audience :: Developers",
|
||||
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
|
||||
]
|
||||
@@ -27,7 +27,7 @@ classifiers = [
|
||||
dependencies = [
|
||||
"GitPython",
|
||||
"PyGithub",
|
||||
"matrix-client==0.4.0",
|
||||
# "matrix-nio",
|
||||
"transformers",
|
||||
"huggingface-hub>0.20",
|
||||
"typer",
|
||||
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
GitPython
|
||||
PyGithub
|
||||
matrix-nio
|
||||
# matrix-nio
|
||||
transformers
|
||||
huggingface-hub>0.20
|
||||
huggingface-hub
|
||||
typer
|
||||
rich
|
||||
typing-extensions
|
||||
|
||||
@@ -9,4 +9,4 @@ lint.select = [
|
||||
"F",
|
||||
]
|
||||
|
||||
exclude = ["*.ipynb"]
|
||||
exclude = ["*.ipynb", "tests"]
|
||||
|
||||
+348
-62
@@ -7,13 +7,15 @@ import concurrent
|
||||
import datetime
|
||||
import concurrent.futures
|
||||
import requests
|
||||
import warnings
|
||||
import argparse
|
||||
|
||||
builtin_nodes = set()
|
||||
|
||||
import sys
|
||||
|
||||
from urllib.parse import urlparse
|
||||
from github import Github
|
||||
from github import Github, Auth
|
||||
|
||||
|
||||
def download_url(url, dest_folder, filename=None):
|
||||
@@ -39,26 +41,51 @@ def download_url(url, dest_folder, filename=None):
|
||||
raise Exception(f"Failed to download file from {url}")
|
||||
|
||||
|
||||
# prepare temp dir
|
||||
if len(sys.argv) > 1:
|
||||
temp_dir = sys.argv[1]
|
||||
else:
|
||||
temp_dir = os.path.join(os.getcwd(), ".tmp")
|
||||
def parse_arguments():
|
||||
"""Parse command-line arguments"""
|
||||
parser = argparse.ArgumentParser(
|
||||
description='ComfyUI Manager Node Scanner',
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog='''
|
||||
Examples:
|
||||
# Standard mode
|
||||
python3 scanner.py
|
||||
python3 scanner.py --skip-update
|
||||
|
||||
if not os.path.exists(temp_dir):
|
||||
os.makedirs(temp_dir)
|
||||
# Scan-only mode
|
||||
python3 scanner.py --scan-only temp-urls-clean.list
|
||||
python3 scanner.py --scan-only urls.list --temp-dir /custom/temp
|
||||
python3 scanner.py --scan-only urls.list --skip-update
|
||||
'''
|
||||
)
|
||||
|
||||
parser.add_argument('--scan-only', type=str, metavar='URL_LIST_FILE',
|
||||
help='Scan-only mode: provide URL list file (one URL per line)')
|
||||
parser.add_argument('--temp-dir', type=str, metavar='DIR',
|
||||
help='Temporary directory for cloned repositories')
|
||||
parser.add_argument('--skip-update', action='store_true',
|
||||
help='Skip git clone/pull operations')
|
||||
parser.add_argument('--skip-stat-update', action='store_true',
|
||||
help='Skip GitHub stats collection')
|
||||
parser.add_argument('--skip-all', action='store_true',
|
||||
help='Skip all update operations')
|
||||
|
||||
# Backward compatibility: positional argument for temp_dir
|
||||
parser.add_argument('temp_dir_positional', nargs='?', metavar='TEMP_DIR',
|
||||
help='(Legacy) Temporary directory path')
|
||||
|
||||
args = parser.parse_args()
|
||||
return args
|
||||
|
||||
|
||||
skip_update = '--skip-update' in sys.argv or '--skip-all' in sys.argv
|
||||
skip_stat_update = '--skip-stat-update' in sys.argv or '--skip-all' in sys.argv
|
||||
|
||||
if not skip_stat_update:
|
||||
g = Github(os.environ.get('GITHUB_TOKEN'))
|
||||
else:
|
||||
g = None
|
||||
|
||||
|
||||
print(f"TEMP DIR: {temp_dir}")
|
||||
# Module-level variables (will be set in main if running as script)
|
||||
args = None
|
||||
scan_only_mode = False
|
||||
url_list_file = None
|
||||
temp_dir = None
|
||||
skip_update = False
|
||||
skip_stat_update = True
|
||||
g = None
|
||||
|
||||
|
||||
parse_cnt = 0
|
||||
@@ -73,12 +100,22 @@ def extract_nodes(code_text):
|
||||
parse_cnt += 1
|
||||
|
||||
code_text = re.sub(r'\\[^"\']', '', code_text)
|
||||
parsed_code = ast.parse(code_text)
|
||||
with warnings.catch_warnings():
|
||||
warnings.filterwarnings('ignore', category=SyntaxWarning)
|
||||
warnings.filterwarnings('ignore', category=DeprecationWarning)
|
||||
parsed_code = ast.parse(code_text)
|
||||
|
||||
# Support both ast.Assign and ast.AnnAssign (for type-annotated assignments)
|
||||
assignments = (node for node in parsed_code.body if isinstance(node, (ast.Assign, ast.AnnAssign)))
|
||||
|
||||
assignments = (node for node in parsed_code.body if isinstance(node, ast.Assign))
|
||||
|
||||
for assignment in assignments:
|
||||
if isinstance(assignment.targets[0], ast.Name) and assignment.targets[0].id in ['NODE_CONFIG', 'NODE_CLASS_MAPPINGS']:
|
||||
# Handle ast.AnnAssign (e.g., NODE_CLASS_MAPPINGS: Type = {...})
|
||||
if isinstance(assignment, ast.AnnAssign):
|
||||
if isinstance(assignment.target, ast.Name) and assignment.target.id in ['NODE_CONFIG', 'NODE_CLASS_MAPPINGS']:
|
||||
node_class_mappings = assignment.value
|
||||
break
|
||||
# Handle ast.Assign (e.g., NODE_CLASS_MAPPINGS = {...})
|
||||
elif isinstance(assignment.targets[0], ast.Name) and assignment.targets[0].id in ['NODE_CONFIG', 'NODE_CLASS_MAPPINGS']:
|
||||
node_class_mappings = assignment.value
|
||||
break
|
||||
else:
|
||||
@@ -90,7 +127,7 @@ def extract_nodes(code_text):
|
||||
for key in node_class_mappings.keys:
|
||||
if key is not None and isinstance(key.value, str):
|
||||
s.add(key.value.strip())
|
||||
|
||||
|
||||
return s
|
||||
else:
|
||||
return set()
|
||||
@@ -98,6 +135,99 @@ def extract_nodes(code_text):
|
||||
return set()
|
||||
|
||||
|
||||
def has_comfy_node_base(class_node):
|
||||
"""Check if class inherits from io.ComfyNode or ComfyNode"""
|
||||
for base in class_node.bases:
|
||||
# Case 1: ComfyNode
|
||||
if isinstance(base, ast.Name) and base.id == 'ComfyNode':
|
||||
return True
|
||||
# Case 2: io.ComfyNode
|
||||
elif isinstance(base, ast.Attribute):
|
||||
if base.attr == 'ComfyNode':
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def extract_keyword_value(call_node, keyword):
|
||||
"""
|
||||
Extract string value of keyword argument
|
||||
Schema(node_id="MyNode") -> "MyNode"
|
||||
"""
|
||||
for kw in call_node.keywords:
|
||||
if kw.arg == keyword:
|
||||
# ast.Constant (Python 3.8+)
|
||||
if isinstance(kw.value, ast.Constant):
|
||||
if isinstance(kw.value.value, str):
|
||||
return kw.value.value
|
||||
# ast.Str (Python 3.7-) - suppress deprecation warning
|
||||
else:
|
||||
with warnings.catch_warnings():
|
||||
warnings.filterwarnings('ignore', category=DeprecationWarning)
|
||||
if hasattr(ast, 'Str') and isinstance(kw.value, ast.Str):
|
||||
return kw.value.s
|
||||
return None
|
||||
|
||||
|
||||
def is_schema_call(call_node):
|
||||
"""Check if ast.Call is io.Schema() or Schema()"""
|
||||
func = call_node.func
|
||||
if isinstance(func, ast.Name) and func.id == 'Schema':
|
||||
return True
|
||||
elif isinstance(func, ast.Attribute) and func.attr == 'Schema':
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def extract_node_id_from_schema(class_node):
|
||||
"""
|
||||
Extract node_id from define_schema() method
|
||||
"""
|
||||
for item in class_node.body:
|
||||
if isinstance(item, ast.FunctionDef) and item.name == 'define_schema':
|
||||
# Walk through function body
|
||||
for stmt in ast.walk(item):
|
||||
if isinstance(stmt, ast.Call):
|
||||
# Check if it's Schema() call
|
||||
if is_schema_call(stmt):
|
||||
node_id = extract_keyword_value(stmt, 'node_id')
|
||||
if node_id:
|
||||
return node_id
|
||||
return None
|
||||
|
||||
|
||||
def extract_v3_nodes(code_text):
|
||||
"""
|
||||
Extract V3 node IDs using AST parsing
|
||||
Returns: set of node_id strings
|
||||
"""
|
||||
global parse_cnt
|
||||
|
||||
try:
|
||||
if parse_cnt % 100 == 0:
|
||||
print(".", end="", flush=True)
|
||||
parse_cnt += 1
|
||||
|
||||
with warnings.catch_warnings():
|
||||
warnings.filterwarnings('ignore', category=SyntaxWarning)
|
||||
warnings.filterwarnings('ignore', category=DeprecationWarning)
|
||||
tree = ast.parse(code_text)
|
||||
except (SyntaxError, UnicodeDecodeError):
|
||||
return set()
|
||||
|
||||
nodes = set()
|
||||
|
||||
# Find io.ComfyNode subclasses
|
||||
for node in ast.walk(tree):
|
||||
if isinstance(node, ast.ClassDef):
|
||||
# Check if inherits from ComfyNode
|
||||
if has_comfy_node_base(node):
|
||||
node_id = extract_node_id_from_schema(node)
|
||||
if node_id:
|
||||
nodes.add(node_id)
|
||||
|
||||
return nodes
|
||||
|
||||
|
||||
# scan
|
||||
def scan_in_file(filename, is_builtin=False):
|
||||
global builtin_nodes
|
||||
@@ -105,13 +235,18 @@ def scan_in_file(filename, is_builtin=False):
|
||||
with open(filename, encoding='utf-8', errors='ignore') as file:
|
||||
code = file.read()
|
||||
|
||||
pattern = r"_CLASS_MAPPINGS\s*=\s*{([^}]*)}"
|
||||
# Support type annotations (e.g., NODE_CLASS_MAPPINGS: Type = {...}) and line continuations (\)
|
||||
pattern = r"_CLASS_MAPPINGS\s*(?::\s*\w+\s*)?=\s*(?:\\\s*)?{([^}]*)}"
|
||||
regex = re.compile(pattern, re.MULTILINE | re.DOTALL)
|
||||
|
||||
nodes = set()
|
||||
class_dict = {}
|
||||
|
||||
# V1 nodes detection
|
||||
nodes |= extract_nodes(code)
|
||||
|
||||
# V3 nodes detection
|
||||
nodes |= extract_v3_nodes(code)
|
||||
code = re.sub(r'^#.*?$', '', code, flags=re.MULTILINE)
|
||||
|
||||
def extract_keys(pattern, code):
|
||||
@@ -208,6 +343,53 @@ def get_nodes(target_dir):
|
||||
return py_files, directories
|
||||
|
||||
|
||||
def get_urls_from_list_file(list_file):
|
||||
"""
|
||||
Read URLs from list file for scan-only mode
|
||||
|
||||
Args:
|
||||
list_file (str): Path to URL list file (one URL per line)
|
||||
|
||||
Returns:
|
||||
list of tuples: [(url, "", None, None), ...]
|
||||
Format: (url, title, preemptions, nodename_pattern)
|
||||
- title: Empty string
|
||||
- preemptions: None
|
||||
- nodename_pattern: None
|
||||
|
||||
File format:
|
||||
https://github.com/owner/repo1
|
||||
https://github.com/owner/repo2
|
||||
# Comments starting with # are ignored
|
||||
|
||||
Raises:
|
||||
FileNotFoundError: If list_file does not exist
|
||||
"""
|
||||
if not os.path.exists(list_file):
|
||||
raise FileNotFoundError(f"URL list file not found: {list_file}")
|
||||
|
||||
urls = []
|
||||
with open(list_file, 'r', encoding='utf-8') as f:
|
||||
for line_num, line in enumerate(f, 1):
|
||||
line = line.strip()
|
||||
|
||||
# Skip empty lines and comments
|
||||
if not line or line.startswith('#'):
|
||||
continue
|
||||
|
||||
# Validate URL format (basic check)
|
||||
if not (line.startswith('http://') or line.startswith('https://')):
|
||||
print(f"WARNING: Line {line_num} is not a valid URL: {line}")
|
||||
continue
|
||||
|
||||
# Add URL with empty metadata
|
||||
# (url, title, preemptions, nodename_pattern)
|
||||
urls.append((line, "", None, None))
|
||||
|
||||
print(f"Loaded {len(urls)} URLs from {list_file}")
|
||||
return urls
|
||||
|
||||
|
||||
def get_git_urls_from_json(json_file):
|
||||
with open(json_file, encoding='utf-8') as file:
|
||||
data = json.load(file)
|
||||
@@ -264,13 +446,43 @@ def clone_or_pull_git_repository(git_url):
|
||||
print(f"Failed to clone '{repo_name}': {e}")
|
||||
|
||||
|
||||
def update_custom_nodes():
|
||||
def update_custom_nodes(scan_only_mode=False, url_list_file=None):
|
||||
"""
|
||||
Update custom nodes by cloning/pulling repositories
|
||||
|
||||
Args:
|
||||
scan_only_mode (bool): If True, use URL list file instead of custom-node-list.json
|
||||
url_list_file (str): Path to URL list file (required if scan_only_mode=True)
|
||||
|
||||
Returns:
|
||||
dict: node_info mapping {repo_name: (url, title, preemptions, node_pattern)}
|
||||
"""
|
||||
if not os.path.exists(temp_dir):
|
||||
os.makedirs(temp_dir)
|
||||
|
||||
node_info = {}
|
||||
|
||||
git_url_titles_preemptions = get_git_urls_from_json('custom-node-list.json')
|
||||
# Select URL source based on mode
|
||||
if scan_only_mode:
|
||||
if not url_list_file:
|
||||
raise ValueError("url_list_file is required in scan-only mode")
|
||||
|
||||
git_url_titles_preemptions = get_urls_from_list_file(url_list_file)
|
||||
print("\n[Scan-Only Mode]")
|
||||
print(f" - URL source: {url_list_file}")
|
||||
print(" - GitHub stats: DISABLED")
|
||||
print(f" - Git clone/pull: {'ENABLED' if not skip_update else 'DISABLED'}")
|
||||
print(" - Metadata: EMPTY")
|
||||
else:
|
||||
if not os.path.exists('custom-node-list.json'):
|
||||
raise FileNotFoundError("custom-node-list.json not found")
|
||||
|
||||
git_url_titles_preemptions = get_git_urls_from_json('custom-node-list.json')
|
||||
print("\n[Standard Mode]")
|
||||
print(" - URL source: custom-node-list.json")
|
||||
print(f" - GitHub stats: {'ENABLED' if not skip_stat_update else 'DISABLED'}")
|
||||
print(f" - Git clone/pull: {'ENABLED' if not skip_update else 'DISABLED'}")
|
||||
print(" - Metadata: FULL")
|
||||
|
||||
def process_git_url_title(url, title, preemptions, node_pattern):
|
||||
name = os.path.basename(url)
|
||||
@@ -382,46 +594,59 @@ def update_custom_nodes():
|
||||
if not skip_stat_update:
|
||||
process_git_stats(git_url_titles_preemptions)
|
||||
|
||||
# Git clone/pull for all repositories
|
||||
with concurrent.futures.ThreadPoolExecutor(11) as executor:
|
||||
for url, title, preemptions, node_pattern in git_url_titles_preemptions:
|
||||
executor.submit(process_git_url_title, url, title, preemptions, node_pattern)
|
||||
|
||||
py_url_titles_and_pattern = get_py_urls_from_json('custom-node-list.json')
|
||||
# .py file download (skip in scan-only mode - only process git repos)
|
||||
if not scan_only_mode:
|
||||
py_url_titles_and_pattern = get_py_urls_from_json('custom-node-list.json')
|
||||
|
||||
def download_and_store_info(url_title_preemptions_and_pattern):
|
||||
url, title, preemptions, node_pattern = url_title_preemptions_and_pattern
|
||||
name = os.path.basename(url)
|
||||
if name.endswith(".py"):
|
||||
node_info[name] = (url, title, preemptions, node_pattern)
|
||||
def download_and_store_info(url_title_preemptions_and_pattern):
|
||||
url, title, preemptions, node_pattern = url_title_preemptions_and_pattern
|
||||
name = os.path.basename(url)
|
||||
if name.endswith(".py"):
|
||||
node_info[name] = (url, title, preemptions, node_pattern)
|
||||
|
||||
try:
|
||||
download_url(url, temp_dir)
|
||||
except Exception:
|
||||
print(f"[ERROR] Cannot download '{url}'")
|
||||
|
||||
with concurrent.futures.ThreadPoolExecutor(10) as executor:
|
||||
executor.map(download_and_store_info, py_url_titles_and_pattern)
|
||||
with concurrent.futures.ThreadPoolExecutor(10) as executor:
|
||||
executor.map(download_and_store_info, py_url_titles_and_pattern)
|
||||
|
||||
return node_info
|
||||
|
||||
|
||||
def gen_json(node_info):
|
||||
def gen_json(node_info, scan_only_mode=False):
|
||||
"""
|
||||
Generate extension-node-map.json from scanned node information
|
||||
|
||||
Args:
|
||||
node_info (dict): Repository metadata mapping
|
||||
scan_only_mode (bool): If True, exclude metadata from output
|
||||
"""
|
||||
# scan from .py file
|
||||
node_files, node_dirs = get_nodes(temp_dir)
|
||||
|
||||
comfyui_path = os.path.abspath(os.path.join(temp_dir, "ComfyUI"))
|
||||
node_dirs.remove(comfyui_path)
|
||||
node_dirs = [comfyui_path] + node_dirs
|
||||
# Only reorder if ComfyUI exists in the list
|
||||
if comfyui_path in node_dirs:
|
||||
node_dirs.remove(comfyui_path)
|
||||
node_dirs = [comfyui_path] + node_dirs
|
||||
|
||||
data = {}
|
||||
for dirname in node_dirs:
|
||||
py_files = get_py_file_paths(dirname)
|
||||
metadata = {}
|
||||
|
||||
|
||||
nodes = set()
|
||||
for py in py_files:
|
||||
nodes_in_file, metadata_in_file = scan_in_file(py, dirname == "ComfyUI")
|
||||
nodes.update(nodes_in_file)
|
||||
# Include metadata from .py files in both modes
|
||||
metadata.update(metadata_in_file)
|
||||
|
||||
dirname = os.path.basename(dirname)
|
||||
@@ -436,17 +661,28 @@ def gen_json(node_info):
|
||||
if dirname in node_info:
|
||||
git_url, title, preemptions, node_pattern = node_info[dirname]
|
||||
|
||||
metadata['title_aux'] = title
|
||||
# Conditionally add metadata based on mode
|
||||
if not scan_only_mode:
|
||||
# Standard mode: include all metadata
|
||||
metadata['title_aux'] = title
|
||||
|
||||
if preemptions is not None:
|
||||
metadata['preemptions'] = preemptions
|
||||
if preemptions is not None:
|
||||
metadata['preemptions'] = preemptions
|
||||
|
||||
if node_pattern is not None:
|
||||
metadata['nodename_pattern'] = node_pattern
|
||||
if node_pattern is not None:
|
||||
metadata['nodename_pattern'] = node_pattern
|
||||
# Scan-only mode: metadata remains empty
|
||||
|
||||
data[git_url] = (nodes, metadata)
|
||||
else:
|
||||
print(f"WARN: {dirname} is removed from custom-node-list.json")
|
||||
# Scan-only mode: Repository not in node_info (expected behavior)
|
||||
# Construct URL from dirname (author_repo format)
|
||||
if '_' in dirname:
|
||||
parts = dirname.split('_', 1)
|
||||
git_url = f"https://github.com/{parts[0]}/{parts[1]}"
|
||||
data[git_url] = (nodes, metadata)
|
||||
else:
|
||||
print(f"WARN: {dirname} is removed from custom-node-list.json")
|
||||
|
||||
for file in node_files:
|
||||
nodes, metadata = scan_in_file(file)
|
||||
@@ -459,13 +695,16 @@ def gen_json(node_info):
|
||||
|
||||
if file in node_info:
|
||||
url, title, preemptions, node_pattern = node_info[file]
|
||||
metadata['title_aux'] = title
|
||||
|
||||
if preemptions is not None:
|
||||
metadata['preemptions'] = preemptions
|
||||
|
||||
if node_pattern is not None:
|
||||
metadata['nodename_pattern'] = node_pattern
|
||||
# Conditionally add metadata based on mode
|
||||
if not scan_only_mode:
|
||||
metadata['title_aux'] = title
|
||||
|
||||
if preemptions is not None:
|
||||
metadata['preemptions'] = preemptions
|
||||
|
||||
if node_pattern is not None:
|
||||
metadata['nodename_pattern'] = node_pattern
|
||||
|
||||
data[url] = (nodes, metadata)
|
||||
else:
|
||||
@@ -477,6 +716,10 @@ def gen_json(node_info):
|
||||
for extension in extensions:
|
||||
node_list_json_path = os.path.join(temp_dir, extension, 'node_list.json')
|
||||
if os.path.exists(node_list_json_path):
|
||||
# Skip if extension not in node_info (scan-only mode with limited URLs)
|
||||
if extension not in node_info:
|
||||
continue
|
||||
|
||||
git_url, title, preemptions, node_pattern = node_info[extension]
|
||||
|
||||
with open(node_list_json_path, 'r', encoding='utf-8') as f:
|
||||
@@ -506,14 +749,16 @@ def gen_json(node_info):
|
||||
print("------------------------------------------------------")
|
||||
node_list_json = {}
|
||||
|
||||
metadata_in_url['title_aux'] = title
|
||||
# Conditionally add metadata based on mode
|
||||
if not scan_only_mode:
|
||||
metadata_in_url['title_aux'] = title
|
||||
|
||||
if preemptions is not None:
|
||||
metadata['preemptions'] = preemptions
|
||||
if preemptions is not None:
|
||||
metadata_in_url['preemptions'] = preemptions
|
||||
|
||||
if node_pattern is not None:
|
||||
metadata_in_url['nodename_pattern'] = node_pattern
|
||||
|
||||
if node_pattern is not None:
|
||||
metadata_in_url['nodename_pattern'] = node_pattern
|
||||
|
||||
nodes = list(nodes)
|
||||
nodes.sort()
|
||||
data[git_url] = (nodes, metadata_in_url)
|
||||
@@ -523,12 +768,53 @@ def gen_json(node_info):
|
||||
json.dump(data, file, indent=4, sort_keys=True)
|
||||
|
||||
|
||||
print("### ComfyUI Manager Node Scanner ###")
|
||||
if __name__ == "__main__":
|
||||
# Parse arguments
|
||||
args = parse_arguments()
|
||||
|
||||
print("\n# Updating extensions\n")
|
||||
updated_node_info = update_custom_nodes()
|
||||
# Determine mode
|
||||
scan_only_mode = args.scan_only is not None
|
||||
url_list_file = args.scan_only if scan_only_mode else None
|
||||
|
||||
print("\n# 'extension-node-map.json' file is generated.\n")
|
||||
gen_json(updated_node_info)
|
||||
# Determine temp_dir
|
||||
if args.temp_dir:
|
||||
temp_dir = args.temp_dir
|
||||
elif args.temp_dir_positional:
|
||||
temp_dir = args.temp_dir_positional
|
||||
else:
|
||||
temp_dir = os.path.join(os.getcwd(), ".tmp")
|
||||
|
||||
print("\nDONE.\n")
|
||||
if not os.path.exists(temp_dir):
|
||||
os.makedirs(temp_dir)
|
||||
|
||||
# Determine skip flags
|
||||
skip_update = args.skip_update or args.skip_all
|
||||
skip_stat_update = args.skip_stat_update or args.skip_all or scan_only_mode
|
||||
|
||||
if not skip_stat_update:
|
||||
auth = Auth.Token(os.environ.get('GITHUB_TOKEN'))
|
||||
g = Github(auth=auth)
|
||||
else:
|
||||
g = None
|
||||
|
||||
print("### ComfyUI Manager Node Scanner ###")
|
||||
|
||||
if scan_only_mode:
|
||||
print(f"\n# [Scan-Only Mode] Processing URL list: {url_list_file}\n")
|
||||
else:
|
||||
print("\n# [Standard Mode] Updating extensions\n")
|
||||
|
||||
# Update/clone repositories and collect node info
|
||||
updated_node_info = update_custom_nodes(scan_only_mode, url_list_file)
|
||||
|
||||
print("\n# Generating 'extension-node-map.json'...\n")
|
||||
|
||||
# Generate extension-node-map.json
|
||||
gen_json(updated_node_info, scan_only_mode)
|
||||
|
||||
print("\n✅ DONE.\n")
|
||||
|
||||
if scan_only_mode:
|
||||
print("Output: extension-node-map.json (node mappings only)")
|
||||
else:
|
||||
print("Output: extension-node-map.json (full metadata)")
|
||||
Reference in New Issue
Block a user